From 328b5b9a60375e69192605a8fa65c73c18af92a4 Mon Sep 17 00:00:00 2001 From: winlin Date: Wed, 4 Jan 2023 19:54:45 +0800 Subject: [PATCH 1/2] SRT: Upgrade libsrt from 1.4.1 to 1.5.1. v6.0.12 --- trunk/doc/CHANGELOG.md | 1 + trunk/src/core/srs_core_version6.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 9532940977..28471a608f 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -8,6 +8,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2023-01-04, Merge [#3362](https://github.com/ossrs/srs/issues/3362): SRT: Upgrade libsrt from 1.4.1 to 1.5.1. v6.0.12 * v6.0, 2023-01-02, For [#465](https://github.com/ossrs/srs/issues/465): HLS: Support HEVC over HLS. v6.0.11 * v6.0, 2022-12-30, Support first SRS6 version. v6.0.10 * v6.0, 2022-12-26, For [#465](https://github.com/ossrs/srs/issues/465): TS: Support disable audio or video to make mpegts.js happy. v6.0.9 diff --git a/trunk/src/core/srs_core_version6.hpp b/trunk/src/core/srs_core_version6.hpp index 4257b3b01c..8e0a9226e3 100644 --- a/trunk/src/core/srs_core_version6.hpp +++ b/trunk/src/core/srs_core_version6.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 6 #define VERSION_MINOR 0 -#define VERSION_REVISION 11 +#define VERSION_REVISION 12 #endif From e9473b998943487a0ddbe6f8549ae840d4430237 Mon Sep 17 00:00:00 2001 From: hondaxiao Date: Wed, 4 Jan 2023 13:49:57 +0800 Subject: [PATCH 2/2] SRT: update libsrt from 1.4.1 to 1.5.1 --- trunk/3rdparty/srt-1-fit/CMakeLists.txt | 794 +- .../srt-1-fit/common/win/ATTIC/winporting.h | 63 - .../srt-1-fit/common/win/syslog_defs.h | 4 +- trunk/3rdparty/srt-1-fit/common/win/wintime.h | 7 +- trunk/3rdparty/srt-1-fit/common/win_time.cpp | 38 +- trunk/3rdparty/srt-1-fit/configure | 25 +- trunk/3rdparty/srt-1-fit/configure-data.tcl | 106 +- .../srt-1-fit/haicrypt/cryspr-config.h | 12 +- .../srt-1-fit/haicrypt/cryspr-gnutls.c | 39 + .../srt-1-fit/haicrypt/cryspr-mbedtls.c | 74 +- .../srt-1-fit/haicrypt/cryspr-mbedtls.h | 4 +- .../srt-1-fit/haicrypt/cryspr-openssl-evp.c | 335 + .../srt-1-fit/haicrypt/cryspr-openssl-evp.h | 63 + .../srt-1-fit/haicrypt/cryspr-openssl.c | 43 +- trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c | 277 +- trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h | 13 + .../srt-1-fit/haicrypt/filelist-gnutls.maf | 1 - .../srt-1-fit/haicrypt/filelist-mbedtls.maf | 1 - .../haicrypt/filelist-openssl-evp.maf | 24 + .../srt-1-fit/haicrypt/filelist-openssl.maf | 1 - trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h | 44 +- .../srt-1-fit/haicrypt/haicrypt_log.cpp | 11 +- .../srt-1-fit/haicrypt/haicrypt_log.h | 4 +- trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c | 4 - trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h | 4 +- .../srt-1-fit/haicrypt/hcrypt_ctx_tx.c | 50 +- trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c | 4 +- trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c | 22 +- .../srt-1-fit/haicrypt/hcrypt_xpt_sta.c | 180 - .../srt-1-fit/scripts/CheckCXXAtomic.cmake | 62 + .../scripts/CheckGCCAtomicIntrinsics.cmake | 113 + .../srt-1-fit/scripts/FindMbedTLS.cmake | 2 +- .../scripts/FindPThreadGetSetName.cmake | 73 + .../srt-1-fit/scripts/ShowProjectConfig.cmake | 191 + .../srt-1-fit/scripts/build-windows.bat | 3 + .../srt-1-fit/scripts/build-windows.ps1 | 238 + .../srt-1-fit/scripts/collect-gcov.sh | 7 + .../srt-1-fit/scripts/gather-package.bat | 20 +- .../scripts/generate-error-types.tcl | 251 + .../scripts/generate-logging-defs.tcl | 454 + .../3rdparty/srt-1-fit/scripts/haiUtil.cmake | 229 +- trunk/3rdparty/srt-1-fit/scripts/iOS.cmake | 4 +- trunk/3rdparty/srt-1-fit/scripts/maf.vim | 38 + .../srt-1-fit/scripts/release-notes/README.md | 18 + .../release-notes/generate_release_notes.py | 120 + .../scripts/release-notes/requirements.txt | 3 + .../scripts/set-version-metadata.ps1 | 13 +- trunk/3rdparty/srt-1-fit/scripts/srt-dev.lua | 938 ++ .../srt-1-fit/scripts/tcp-echo-client.tcl | 2 +- .../scripts/win-installer/.gitignore | 10 + .../srt-1-fit/scripts/win-installer/README.md | 123 + .../win-installer/build-win-installer.ps1 | 227 + .../scripts/win-installer/install-libsrt.ps1 | 131 + .../scripts/win-installer/install-nsis.ps1 | 122 + .../scripts/win-installer/install-openssl.ps1 | 119 + .../scripts/win-installer/libsrt.nsi | 217 + .../scripts/win-installer/libsrt.props | 38 + .../3rdparty/srt-1-fit/srtcore/ATTIC/ccc.cpp | 360 - trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.h | 219 - .../srt-1-fit/srtcore/access_control.h | 79 + trunk/3rdparty/srt-1-fit/srtcore/api.cpp | 6812 +++++++----- trunk/3rdparty/srt-1-fit/srtcore/api.h | 533 +- trunk/3rdparty/srt-1-fit/srtcore/atomic.h | 311 + .../3rdparty/srt-1-fit/srtcore/atomic_clock.h | 91 + .../3rdparty/srt-1-fit/srtcore/atomic_msvc.h | 245 + trunk/3rdparty/srt-1-fit/srtcore/buffer.cpp | 2833 ++--- trunk/3rdparty/srt-1-fit/srtcore/buffer.h | 843 +- .../3rdparty/srt-1-fit/srtcore/buffer_rcv.cpp | 1072 ++ trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.h | 367 + trunk/3rdparty/srt-1-fit/srtcore/cache.cpp | 52 +- trunk/3rdparty/srt-1-fit/srtcore/cache.h | 55 +- trunk/3rdparty/srt-1-fit/srtcore/channel.cpp | 979 +- trunk/3rdparty/srt-1-fit/srtcore/channel.h | 162 +- trunk/3rdparty/srt-1-fit/srtcore/common.cpp | 909 +- trunk/3rdparty/srt-1-fit/srtcore/common.h | 961 +- trunk/3rdparty/srt-1-fit/srtcore/congctl.cpp | 129 +- trunk/3rdparty/srt-1-fit/srtcore/congctl.h | 58 +- trunk/3rdparty/srt-1-fit/srtcore/core.cpp | 9669 ++++++++++------- trunk/3rdparty/srt-1-fit/srtcore/core.h | 1023 +- trunk/3rdparty/srt-1-fit/srtcore/crypto.cpp | 338 +- trunk/3rdparty/srt-1-fit/srtcore/crypto.h | 70 +- trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp | 457 +- trunk/3rdparty/srt-1-fit/srtcore/epoll.h | 269 +- trunk/3rdparty/srt-1-fit/srtcore/fec.cpp | 932 +- trunk/3rdparty/srt-1-fit/srtcore/fec.h | 53 +- trunk/3rdparty/srt-1-fit/srtcore/filelist.maf | 37 +- trunk/3rdparty/srt-1-fit/srtcore/group.cpp | 4717 ++++++++ trunk/3rdparty/srt-1-fit/srtcore/group.h | 822 ++ .../srt-1-fit/srtcore/group_backup.cpp | 159 + .../3rdparty/srt-1-fit/srtcore/group_backup.h | 128 + .../srt-1-fit/srtcore/group_common.cpp | 63 + .../3rdparty/srt-1-fit/srtcore/group_common.h | 62 + .../3rdparty/srt-1-fit/srtcore/handshake.cpp | 129 +- trunk/3rdparty/srt-1-fit/srtcore/handshake.h | 182 +- trunk/3rdparty/srt-1-fit/srtcore/list.cpp | 1239 ++- trunk/3rdparty/srt-1-fit/srtcore/list.h | 283 +- .../srt-1-fit/srtcore/logger_default.cpp | 53 + .../srt-1-fit/srtcore/logger_defs.cpp | 55 + .../3rdparty/srt-1-fit/srtcore/logger_defs.h | 61 + trunk/3rdparty/srt-1-fit/srtcore/logging.h | 69 +- .../3rdparty/srt-1-fit/srtcore/logging_api.h | 5 +- trunk/3rdparty/srt-1-fit/srtcore/md5.cpp | 14 +- trunk/3rdparty/srt-1-fit/srtcore/md5.h | 15 +- .../3rdparty/srt-1-fit/srtcore/netinet_any.h | 327 +- trunk/3rdparty/srt-1-fit/srtcore/packet.cpp | 456 +- trunk/3rdparty/srt-1-fit/srtcore/packet.h | 369 +- .../srt-1-fit/srtcore/packetfilter.cpp | 184 +- .../3rdparty/srt-1-fit/srtcore/packetfilter.h | 49 +- .../srt-1-fit/srtcore/packetfilter_api.h | 36 +- .../srt-1-fit/srtcore/packetfilter_builtin.h | 4 +- .../3rdparty/srt-1-fit/srtcore/platform_sys.h | 97 +- trunk/3rdparty/srt-1-fit/srtcore/queue.cpp | 1224 ++- trunk/3rdparty/srt-1-fit/srtcore/queue.h | 706 +- .../srt-1-fit/srtcore/socketconfig.cpp | 1020 ++ .../3rdparty/srt-1-fit/srtcore/socketconfig.h | 377 + trunk/3rdparty/srt-1-fit/srtcore/srt.h | 612 +- trunk/3rdparty/srt-1-fit/srtcore/srt4udt.h | 71 - .../srt-1-fit/srtcore/srt_attr_defs.h | 191 + .../3rdparty/srt-1-fit/srtcore/srt_c_api.cpp | 244 +- trunk/3rdparty/srt-1-fit/srtcore/srt_compat.c | 4 +- trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h | 14 +- trunk/3rdparty/srt-1-fit/srtcore/stats.h | 221 + .../srt-1-fit/srtcore/strerror_defs.cpp | 154 + trunk/3rdparty/srt-1-fit/srtcore/sync.cpp | 355 + trunk/3rdparty/srt-1-fit/srtcore/sync.h | 945 ++ .../3rdparty/srt-1-fit/srtcore/sync_cxx11.cpp | 108 + .../3rdparty/srt-1-fit/srtcore/sync_posix.cpp | 572 + trunk/3rdparty/srt-1-fit/srtcore/threadname.h | 225 +- .../3rdparty/srt-1-fit/srtcore/tsbpd_time.cpp | 267 + trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.h | 163 + trunk/3rdparty/srt-1-fit/srtcore/udt.h | 345 +- trunk/3rdparty/srt-1-fit/srtcore/utilities.h | 403 +- trunk/3rdparty/srt-1-fit/srtcore/version.h.in | 8 +- trunk/3rdparty/srt-1-fit/srtcore/window.cpp | 48 +- trunk/3rdparty/srt-1-fit/srtcore/window.h | 123 +- trunk/src/app/srs_app_config.cpp | 4 +- trunk/src/app/srs_app_config.hpp | 2 +- trunk/src/app/srs_app_srt_server.cpp | 2 +- trunk/src/protocol/srs_protocol_srt.cpp | 4 +- trunk/src/protocol/srs_protocol_srt.hpp | 4 +- trunk/src/utest/srs_utest_srt.cpp | 7 +- 141 files changed, 38504 insertions(+), 15428 deletions(-) delete mode 100644 trunk/3rdparty/srt-1-fit/common/win/ATTIC/winporting.h create mode 100644 trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.c create mode 100644 trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.h create mode 100644 trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl-evp.maf delete mode 100644 trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_xpt_sta.c create mode 100644 trunk/3rdparty/srt-1-fit/scripts/CheckCXXAtomic.cmake create mode 100644 trunk/3rdparty/srt-1-fit/scripts/CheckGCCAtomicIntrinsics.cmake create mode 100644 trunk/3rdparty/srt-1-fit/scripts/FindPThreadGetSetName.cmake create mode 100644 trunk/3rdparty/srt-1-fit/scripts/ShowProjectConfig.cmake create mode 100644 trunk/3rdparty/srt-1-fit/scripts/build-windows.bat create mode 100644 trunk/3rdparty/srt-1-fit/scripts/build-windows.ps1 create mode 100644 trunk/3rdparty/srt-1-fit/scripts/collect-gcov.sh create mode 100755 trunk/3rdparty/srt-1-fit/scripts/generate-error-types.tcl create mode 100755 trunk/3rdparty/srt-1-fit/scripts/generate-logging-defs.tcl create mode 100644 trunk/3rdparty/srt-1-fit/scripts/maf.vim create mode 100644 trunk/3rdparty/srt-1-fit/scripts/release-notes/README.md create mode 100644 trunk/3rdparty/srt-1-fit/scripts/release-notes/generate_release_notes.py create mode 100644 trunk/3rdparty/srt-1-fit/scripts/release-notes/requirements.txt create mode 100644 trunk/3rdparty/srt-1-fit/scripts/srt-dev.lua create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/.gitignore create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/README.md create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/build-win-installer.ps1 create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/install-libsrt.ps1 create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/install-nsis.ps1 create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/install-openssl.ps1 create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.nsi create mode 100644 trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.props delete mode 100644 trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.cpp delete mode 100644 trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/access_control.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/atomic.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/atomic_clock.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/atomic_msvc.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.h mode change 100755 => 100644 trunk/3rdparty/srt-1-fit/srtcore/epoll.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/group.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/group.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/group_backup.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/group_backup.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/group_common.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/group_common.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/logger_default.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/logger_defs.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/logger_defs.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/socketconfig.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/socketconfig.h delete mode 100644 trunk/3rdparty/srt-1-fit/srtcore/srt4udt.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/srt_attr_defs.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/stats.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/strerror_defs.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/sync.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/sync.h create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/sync_cxx11.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/sync_posix.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.cpp create mode 100644 trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.h mode change 100755 => 100644 trunk/3rdparty/srt-1-fit/srtcore/utilities.h diff --git a/trunk/3rdparty/srt-1-fit/CMakeLists.txt b/trunk/3rdparty/srt-1-fit/CMakeLists.txt index 7fafe77ccc..b43b4b63d5 100644 --- a/trunk/3rdparty/srt-1-fit/CMakeLists.txt +++ b/trunk/3rdparty/srt-1-fit/CMakeLists.txt @@ -8,14 +8,22 @@ # cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) -# XXX This can be potentially done in future, but there still exist -# some dependent project using cmake 2.8 - this can't be done this way. -#cmake_minimum_required (VERSION 3.0.2 FATAL_ERROR) -#project(SRT VERSION "1.4.1") -project(SRT C CXX) +set (SRT_VERSION 1.5.1) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") -include(haiUtil) +include(haiUtil) # needed for set_version_variables +# CMake version 3.0 introduced the VERSION option of the project() command +# to specify a project version as well as the name. +if(${CMAKE_VERSION} VERSION_LESS "3.0.0") + project(SRT C CXX) + # Sets SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH + set_version_variables(SRT_VERSION ${SRT_VERSION}) +else() + cmake_policy(SET CMP0048 NEW) + # Also sets SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH + project(SRT VERSION ${SRT_VERSION} LANGUAGES C CXX) +endif() + include(FindPkgConfig) # XXX See 'if (MINGW)' condition below, may need fixing. include(FindThreads) @@ -23,24 +31,21 @@ include(CheckFunctionExists) # Platform shortcuts string(TOLOWER ${CMAKE_SYSTEM_NAME} SYSNAME_LC) -set_if(DARWIN ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +set_if(DARWIN (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + OR (${CMAKE_SYSTEM_NAME} MATCHES "iOS") + OR (${CMAKE_SYSTEM_NAME} MATCHES "tvOS") + OR (${CMAKE_SYSTEM_NAME} MATCHES "watchOS")) set_if(LINUX ${CMAKE_SYSTEM_NAME} MATCHES "Linux") set_if(BSD ${SYSNAME_LC} MATCHES "bsd$") set_if(MICROSOFT WIN32 AND (NOT MINGW AND NOT CYGWIN)) set_if(GNU ${CMAKE_SYSTEM_NAME} MATCHES "GNU") -set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR CYGWIN OR GNU) +set_if(ANDROID ${SYSNAME_LC} MATCHES "android") +set_if(SUNOS "${SYSNAME_LC}" MATCHES "sunos") +set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR (CYGWIN AND CYGWIN_USE_POSIX)) +set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU) +set_if(NEED_DESTINATION ${CMAKE_VERSION} VERSION_LESS "3.14.0") -# Not sure what to do in case of compiling by MSVC. -# This will make installdir in C:\Program Files\SRT then -# inside "bin" and "lib64" directories. At least this maintains -# the current status. Shall this be not desired, override values -# of CMAKE_INSTALL_BINDIR, CMAKE_INSTALL_LIBDIR and CMAKE_INSTALL_INCLUDEDIR. -if (NOT DEFINED CMAKE_INSTALL_LIBDIR) - include(GNUInstallDirs) -endif() - -set (SRT_VERSION 1.4.1) -set_version_variables(SRT_VERSION ${SRT_VERSION}) +include(GNUInstallDirs) # The CMAKE_BUILD_TYPE seems not to be always set, weird. if (NOT DEFINED ENABLE_DEBUG) @@ -52,15 +57,28 @@ if (NOT DEFINED ENABLE_DEBUG) endif() endif() -# Set CMAKE_BUILD_TYPE properly, now that you know -# that ENABLE_DEBUG is set as it should. - -if (ENABLE_DEBUG EQUAL 2) - set (CMAKE_BUILD_TYPE "RelWithDebInfo") -elseif (ENABLE_DEBUG) # 1, ON, YES, TRUE, Y, or any other non-zero number - set (CMAKE_BUILD_TYPE "Debug") -else() - set (CMAKE_BUILD_TYPE "Release") +# XXX This is a kind of workaround - this part to set the build +# type and associated other flags should not be done for build +# systems (cmake generators) that generate a multi-configuration +# build definition. At least it is known that MSVC does it and it +# sets _DEBUG and NDEBUG flags itself, so this shouldn't be done +# at all in this case. +if (NOT MICROSOFT) + + # Set CMAKE_BUILD_TYPE properly, now that you know + # that ENABLE_DEBUG is set as it should. + if (ENABLE_DEBUG EQUAL 2) + set (CMAKE_BUILD_TYPE "RelWithDebInfo") + add_definitions(-DNDEBUG) + elseif (ENABLE_DEBUG) # 1, ON, YES, TRUE, Y, or any other non-zero number + set (CMAKE_BUILD_TYPE "Debug") + + # Add _DEBUG macro in debug mode only, to enable SRT_ASSERT(). + add_definitions(-D_DEBUG) + else() + set (CMAKE_BUILD_TYPE "Release") + add_definitions(-DNDEBUG) + endif() endif() message(STATUS "BUILD TYPE: ${CMAKE_BUILD_TYPE}") @@ -79,10 +97,19 @@ foreach(ef ${enforcers}) add_definitions(-D${ef}${val}) endforeach() +# NOTE: Known options you can change using ENFORCE_ variables: + +# SRT_ENABLE_ECN 1 /* Early Congestion Notification (for source bitrate control) */ +# SRT_DEBUG_TSBPD_OUTJITTER 1 /* Packet Delivery histogram */ +# SRT_DEBUG_TRACE_DRIFT 1 /* Create a trace log for Encoder-Decoder Clock Drift */ +# SRT_DEBUG_TSBPD_WRAP 1 /* Debug packet timestamp wraparound */ +# SRT_DEBUG_TLPKTDROP_DROPSEQ 1 +# SRT_DEBUG_SNDQ_HIGHRATE 1 +# SRT_DEBUG_BONDING_STATES 1 +# SRT_DEBUG_RTT 1 /* RTT trace */ +# SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ + # option defaults -# XXX CHANGE: Logging is enabled now by default, -# use ENABLE_LOGGING=NO in cmake or -# --disable-logging in configure. set(ENABLE_HEAVY_LOGGING_DEFAULT OFF) # Always turn logging on if the build type is debug @@ -91,10 +118,26 @@ if (ENABLE_DEBUG) endif() +set(ENABLE_STDCXX_SYNC_DEFAULT OFF) +set(ENABLE_MONOTONIC_CLOCK_DEFAULT OFF) +set(MONOTONIC_CLOCK_LINKLIB "") +if (MICROSOFT) + set(ENABLE_STDCXX_SYNC_DEFAULT ON) +elseif (POSIX) + test_requires_clock_gettime(ENABLE_MONOTONIC_CLOCK_DEFAULT MONOTONIC_CLOCK_LINKLIB) +endif() + + # options option(CYGWIN_USE_POSIX "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin." OFF) -option(ENABLE_CXX11 "Should the c++11 parts (srt-live-transmit) be enabled" ON) +if (CMAKE_CXX_COMPILER_ID MATCHES "^GNU$" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.7) + option(ENABLE_CXX11 "Should the c++11 parts (srt-live-transmit) be enabled" OFF) +else() + option(ENABLE_CXX11 "Should the c++11 parts (srt-live-transmit) be enabled" ON) +endif() option(ENABLE_APPS "Should the Support Applications be Built?" ON) +option(ENABLE_BONDING "Should the bonding functionality be enabled?" OFF) +option(ENABLE_TESTING "Should the Developer Test Applications be Built?" OFF) option(ENABLE_PROFILE "Should instrument the code for profiling. Ignored for non-GNU compiler." $ENV{HAI_BUILD_PROFILE}) option(ENABLE_LOGGING "Should logging be enabled" ON) option(ENABLE_HEAVY_LOGGING "Should heavy debug logging be enabled" ${ENABLE_HEAVY_LOGGING_DEFAULT}) @@ -102,7 +145,6 @@ option(ENABLE_HAICRYPT_LOGGING "Should logging in haicrypt be enabled" 0) option(ENABLE_SHARED "Should libsrt be built as a shared library" ON) option(ENABLE_STATIC "Should libsrt be built as a static library" ON) option(ENABLE_RELATIVE_LIBPATH "Should application contain relative library paths, like ../lib" OFF) -option(ENABLE_SUFLIP "Should suflip tool be built" OFF) option(ENABLE_GETNAMEINFO "In-logs sockaddr-to-string should do rev-dns" OFF) option(ENABLE_UNITTESTS "Enable unit tests" OFF) option(ENABLE_ENCRYPTION "Enable encryption in SRT" ON) @@ -110,13 +152,36 @@ option(ENABLE_CXX_DEPS "Extra library dependencies in srt.pc for the CXX librari option(USE_STATIC_LIBSTDCXX "Should use static rather than shared libstdc++" OFF) option(ENABLE_INET_PTON "Set to OFF to prevent usage of inet_pton when building against modern SDKs while still requiring compatibility with older Windows versions, such as Windows XP, Windows Server 2003 etc." ON) option(ENABLE_CODE_COVERAGE "Enable code coverage reporting" OFF) -option(ENABLE_MONOTONIC_CLOCK "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/" OFF) +option(ENABLE_MONOTONIC_CLOCK "Enforced clock_gettime with monotonic clock on GC CV" ${ENABLE_MONOTONIC_CLOCK_DEFAULT}) +option(ENABLE_STDCXX_SYNC "Use C++11 chrono and threads for timing instead of pthreads" ${ENABLE_STDCXX_SYNC_DEFAULT}) option(USE_OPENSSL_PC "Use pkg-config to find OpenSSL libraries" ON) +option(OPENSSL_USE_STATIC_LIBS "Link OpenSSL libraries statically." OFF) option(USE_BUSY_WAITING "Enable more accurate sending times at a cost of potentially higher CPU load" OFF) option(USE_GNUSTL "Get c++ library/headers from the gnustl.pc" OFF) +option(ENABLE_SOCK_CLOEXEC "Enable setting SOCK_CLOEXEC on a socket" ON) +option(ENABLE_SHOW_PROJECT_CONFIG "Enable show Project Configuration" OFF) +option(ENABLE_NEW_RCVBUFFER "Enable new receiver buffer implementation" ON) + +option(ENABLE_CLANG_TSA "Enable Clang Thread Safety Analysis" OFF) + +# NOTE: Use ATOMIC_USE_SRT_SYNC_MUTEX and will override the auto-detection of the +# Atomic implemetation in srtcore/atomic.h. +option(ATOMIC_USE_SRT_SYNC_MUTEX "Use srt::sync::Mutex to Implement Atomics" OFF) +if (ATOMIC_USE_SRT_SYNC_MUTEX) + add_definitions(-DATOMIC_USE_SRT_SYNC_MUTEX=1) +endif() set(TARGET_srt "srt" CACHE STRING "The name for the SRT library") +# Use application-defined group reader +# (currently the only one implemented) +add_definitions(-DSRT_ENABLE_APP_READER) + +# XXX This was added once as experimental, it is now in force for +# write-blocking-mode sockets. Still unclear if all issues around +# closing while data still not written are eliminated. +add_definitions(-DSRT_ENABLE_CLOSE_SYNCH) + if (NOT ENABLE_LOGGING) set (ENABLE_HEAVY_LOGGING OFF) message(STATUS "LOGGING: DISABLED") @@ -149,7 +214,6 @@ if (NOT USE_ENCLIB) else() set (USE_ENCLIB openssl) endif() - endif() set(USE_ENCLIB "${USE_ENCLIB}" CACHE STRING "The crypto library that SRT uses") @@ -160,28 +224,50 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -# Handle WITH_COMPILER_PREFIX and WITH_COMPILER_TYPE options -if (DEFINED WITH_COMPILER_PREFIX) - message(STATUS "Handling compiler with WITH_COMPILER_PREFIX=${WITH_COMPILER_PREFIX}") - # Check also type. Default is gcc. - if (NOT DEFINED WITH_COMPILER_TYPE) - set (WITH_COMPILER_TYPE gcc) +if (NOT DEFINED WITH_COMPILER_TYPE) + + # This is for a case when you provided the prefix, but you didn't + # provide compiler type. This option is in this form predicted to work + # only on POSIX systems. Just typical compilers for Linux and Mac are + # included. + if (DARWIN) + set (WITH_COMPILER_TYPE clang) + elseif (POSIX) # Posix, but not DARWIN + set(WITH_COMPILER_TYPE gcc) + else() + get_filename_component(WITH_COMPILER_TYPE ${CMAKE_C_COMPILER} NAME) endif() + set (USING_DEFAULT_COMPILER_PREFIX 1) +endif() - if (${WITH_COMPILER_TYPE} STREQUAL gcc) - set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}gcc) - set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}g++) - elseif (${WITH_COMPILER_TYPE} STREQUAL cc) - set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}cc) - set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}c++) +if (NOT USING_DEFAULT_COMPILER_PREFIX OR DEFINED WITH_COMPILER_PREFIX) + message(STATUS "Handling compiler with PREFIX=${WITH_COMPILER_PREFIX} TYPE=${WITH_COMPILER_TYPE}") + + parse_compiler_type(${WITH_COMPILER_TYPE} COMPILER_TYPE COMPILER_SUFFIX) + + if (${COMPILER_TYPE} STREQUAL gcc) + set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}gcc${COMPILER_SUFFIX}) + set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}g++${COMPILER_SUFFIX}) + set (HAVE_COMPILER_GNU_COMPAT 1) + elseif (${COMPILER_TYPE} STREQUAL cc) + set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}cc${COMPILER_SUFFIX}) + set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}c++${COMPILER_SUFFIX}) + set (HAVE_COMPILER_GNU_COMPAT 1) + elseif (${COMPILER_TYPE} STREQUAL icc) + set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}icc${COMPILER_SUFFIX}) + set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}icpc${COMPILER_SUFFIX}) + set (HAVE_COMPILER_GNU_COMPAT 1) else() # Use blindly for C compiler and ++ for C++. # At least this matches clang. set (CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}${WITH_COMPILER_TYPE}) - set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}${WITH_COMPILER_TYPE}++) + set (CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}${COMPILER_TYPE}++${COMPILER_SUFFIX}) + if (${COMPILER_TYPE} STREQUAL clang) + set (HAVE_COMPILER_GNU_COMPAT 1) + endif() endif() - message(STATUS "Compiler type: ${WITH_COMPILER_TYPE}. C: ${CMAKE_C_COMPILER}; C++: ${CMAKE_CXX_COMPILER}") + unset(USING_DEFAULT_COMPILER_PREFIX) else() message(STATUS "No WITH_COMPILER_PREFIX - using C++ compiler ${CMAKE_CXX_COMPILER}") endif() @@ -210,7 +296,15 @@ if (DEFINED HAVE_INET_PTON) add_definitions(-DHAVE_INET_PTON=1) endif() +# Defines HAVE_PTHREAD_GETNAME_* and HAVE_PTHREAD_SETNAME_* +include(FindPThreadGetSetName) +FindPThreadGetSetName() + if (ENABLE_MONOTONIC_CLOCK) + if (NOT ENABLE_MONOTONIC_CLOCK_DEFAULT) + message(FATAL_ERROR "Your platform does not support CLOCK_MONOTONIC. Build with -DENABLE_MONOTONIC_CLOCK=OFF.") + endif() + set (WITH_EXTRALIBS "${WITH_EXTRALIBS} ${MONOTONIC_CLOCK_LINKLIB}") add_definitions(-DENABLE_MONOTONIC_CLOCK=1) endif() @@ -232,18 +326,38 @@ if (ENABLE_ENCRYPTION) link_directories( ${SSL_LIBRARY_DIRS} ) - else() # Common for mbedtls and openssl - if ("${USE_ENCLIB}" STREQUAL "mbedtls") - add_definitions(-DUSE_MBEDTLS=1) - set (SSL_REQUIRED_MODULES "mbedtls mbedcrypto") - else() - add_definitions(-DUSE_OPENSSL=1) - set (SSL_REQUIRED_MODULES "openssl libcrypto") + elseif ("${USE_ENCLIB}" STREQUAL "mbedtls") + add_definitions(-DUSE_MBEDTLS=1) + if ("${SSL_LIBRARY_DIRS}" STREQUAL "") + set(MBEDTLS_PREFIX "${CMAKE_PREFIX_PATH}" CACHE PATH "The path of mbedtls") + find_package(MbedTLS REQUIRED) + set (SSL_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) + set (SSL_LIBRARIES ${MBEDTLS_LIBRARIES}) endif() + if ("${SSL_LIBRARIES}" STREQUAL "") + set (SSL_LIBRARIES mbedtls mbedcrypto) + endif() + message(STATUS "SSL enforced mbedtls: -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") + + foreach(LIB ${SSL_LIBRARIES}) + if(IS_ABSOLUTE ${LIB} AND EXISTS ${LIB}) + set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${LIB}) + else() + set(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} "-l${LIB}") + endif() + endforeach() + elseif ("${USE_ENCLIB}" STREQUAL "openssl-evp") + # Openssl-EVP requires CRYSPR2 + add_definitions(-DUSE_OPENSSL_EVP=1 -DCRYSPR2) + set (SSL_REQUIRED_MODULES "openssl libcrypto") # Try using pkg-config method first if enabled, # fall back to find_package method otherwise if (USE_OPENSSL_PC) pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) + if (OPENSSL_USE_STATIC_LIBS) + # use `pkg-config --static xxx` found libs + set(SSL_LIBRARIES ${SSL_STATIC_LIBRARIES}) + endif() endif() if (SSL_FOUND) # We have some cases when pkg-config is improperly configured @@ -264,24 +378,45 @@ if (ENABLE_ENCRYPTION) ) message(STATUS "SSL via pkg-config: -L ${SSL_LIBRARY_DIRS} -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") else() - if ("${USE_ENCLIB}" STREQUAL "mbedtls") - if ("${SSL_LIBRARY_DIRS}" STREQUAL "") - set(MBEDTLS_PREFIX "${CMAKE_PREFIX_PATH}" CACHE PATH "The path of mbedtls") - find_package(MbedTLS REQUIRED) - set (SSL_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) - set (SSL_LIBRARIES ${MBEDTLS_LIBRARIES}) - endif() - if ("${SSL_LIBRARIES}" STREQUAL "") - set (SSL_LIBRARIES mbedtls mbedcrypto) - endif() - message(STATUS "SSL enforced mbedtls: -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") - else() - find_package(OpenSSL REQUIRED) - set (SSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) - set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) - message(STATUS "SSL via find_package(OpenSSL): -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") + find_package(OpenSSL REQUIRED) + set (SSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) + message(STATUS "SSL via find_package(OpenSSL): -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") + endif() + else() # openssl + # Openssl (Direct-AES API) can use CRYSPR2 + add_definitions(-DUSE_OPENSSL=1 -DCRYSPR2) + set (SSL_REQUIRED_MODULES "openssl libcrypto") + # Try using pkg-config method first if enabled, + # fall back to find_package method otherwise + if (USE_OPENSSL_PC) + pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) + endif() + if (SSL_FOUND) + # We have some cases when pkg-config is improperly configured + # When it doesn't ship the -L and -I options, and the CMAKE_PREFIX_PATH + # is set (also through `configure`), then we have this problem. If so, + # set forcefully the -I and -L contents to prefix/include and + # prefix/lib. + if ("${SSL_LIBRARY_DIRS}" STREQUAL "") + if (NOT "${CMAKE_PREFIX_PATH}" STREQUAL "") + message(STATUS "WARNING: pkg-config has incorrect prefix - enforcing target path prefix: ${CMAKE_PREFIX_PATH}") + set (SSL_LIBRARY_DIRS ${CMAKE_PREFIX_PATH}/${CMAKE_INSTALL_LIBDIR}) + set (SSL_INCLUDE_DIRS ${CMAKE_PREFIX_PATH}/include) + endif() endif() + + link_directories( + ${SSL_LIBRARY_DIRS} + ) + message(STATUS "SSL via pkg-config: -L ${SSL_LIBRARY_DIRS} -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") + else() + find_package(OpenSSL REQUIRED) + set (SSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) + set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) + message(STATUS "SSL via find_package(OpenSSL): -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") endif() + endif() add_definitions(-DSRT_ENABLE_ENCRYPTION) @@ -291,22 +426,58 @@ else() message(STATUS "ENCRYPTION: DISABLED") endif() -if ( USE_GNUSTL ) +if (USE_GNUSTL) pkg_check_modules (GNUSTL REQUIRED gnustl) link_directories(${GNUSTL_LIBRARY_DIRS}) include_directories(${GNUSTL_INCLUDE_DIRS}) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) endif() +if (USING_DEFAULT_COMPILER_PREFIX) # Detect if the compiler is GNU compatable for flags -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Intel|Clang|AppleClang") +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Intel|Clang|AppleClang") message(STATUS "COMPILER: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER}) - GNU compat") set(HAVE_COMPILER_GNU_COMPAT 1) + + # See https://gcc.gnu.org/projects/cxx-status.html + # At the bottom there's information about C++98, which is default up to 6.1 version. + # For all other compilers - including Clang - we state that the default C++ standard is AT LEAST 11. + if (${CMAKE_CXX_COMPILER_ID} STREQUAL GNU AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6.1) + message(STATUS "NOTE: GCC ${CMAKE_CXX_COMPILER_VERSION} is detected with default C++98. Forcing C++11 on applications.") + set (FORCE_CXX_STANDARD 1) + elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang|AppleClang") + message(STATUS "NOTE: CLANG ${CMAKE_CXX_COMPILER_VERSION} detected, unsure if >=C++11 is default, forcing C++11 on applications") + set (FORCE_CXX_STANDARD 1) + else() + message(STATUS "NOTE: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} - assuming default C++11.") + endif() else() message(STATUS "COMPILER: ${CMAKE_CXX_COMPILER_ID} (${CMAKE_CXX_COMPILER}) - NOT GNU compat") set(HAVE_COMPILER_GNU_COMPAT 0) endif() +else() # Compiler altered by WITH_COMPILER_TYPE/PREFIX - can't rely on CMAKE_CXX_* + + # Force the C++ standard as C++11 + # HAVE_COMPILER_GNU_COMPAT was set in the handler of WITH_COMPILER_TYPE + set (FORCE_CXX_STANDARD 1) + message(STATUS "COMPILER CHANGED TO: ${COMPILER_TYPE} - forcing C++11 standard for apps") +endif() + +# Check for GCC Atomic Intrinsics and C++11 Atomics. +# Sets: +# HAVE_LIBATOMIC +# HAVE_LIBATOMIC_COMPILES +# HAVE_LIBATOMIC_COMPILES_STATIC +# HAVE_GCCATOMIC_INTRINSICS +# HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC +include(CheckGCCAtomicIntrinsics) +CheckGCCAtomicIntrinsics() +# HAVE_CXX_ATOMIC +# HAVE_CXX_ATOMIC_STATIC +include(CheckCXXAtomic) +CheckCXXAtomic() + if (DISABLE_CXX11) set (ENABLE_CXX11 0) elseif( DEFINED ENABLE_CXX11 ) @@ -314,8 +485,130 @@ else() set (ENABLE_CXX11 1) endif() +function (srt_check_cxxstd stdval OUT_STD OUT_PFX) + + set (STDPFX c++) + if (stdval MATCHES "([^+]+\\++)([0-9]*)") + set (STDPFX ${CMAKE_MATCH_1}) + set (STDCXX ${CMAKE_MATCH_2}) + elseif (stdval MATCHES "[0-9]*") + set (STDCXX ${stdval}) + else() + set (STDCXX 0) + endif() + + # Handle C++98 < C++11 + # Please fix this around 2070 year. + if (${STDCXX} GREATER 80) + set (STDCXX 03) + endif() + + # return + set (${OUT_STD} ${STDCXX} PARENT_SCOPE) + set (${OUT_PFX} ${STDPFX} PARENT_SCOPE) +endfunction() + if (NOT ENABLE_CXX11) message(WARNING "Parts that require C++11 support will be disabled (srt-live-transmit)") + if (ENABLE_STDCXX_SYNC) + message(FATAL_ERROR "ENABLE_STDCXX_SYNC is set, but C++11 is disabled by ENABLE_CXX11") + endif() +elseif (ENABLE_STDCXX_SYNC) + add_definitions(-DENABLE_STDCXX_SYNC=1) + if (DEFINED USE_CXX_STD) + srt_check_cxxstd(${USE_CXX_STD} STDCXX STDPFX) + # If defined, make sure it's at least C++11 + if (${STDCXX} LESS 11) + message(FATAL_ERROR "If ENABLE_STDCXX_SYNC, then USE_CXX_STD must specify at least C++11") + endif() + else() + set (USE_CXX_STD 11) + endif() +endif() + +message(STATUS "STDCXX_SYNC: ${ENABLE_STDCXX_SYNC}") +message(STATUS "MONOTONIC_CLOCK: ${ENABLE_MONOTONIC_CLOCK}") + +if (ENABLE_SOCK_CLOEXEC) + add_definitions(-DENABLE_SOCK_CLOEXEC=1) +endif() + +if (ENABLE_NEW_RCVBUFFER) + add_definitions(-DENABLE_NEW_RCVBUFFER=1) + message(STATUS "RECEIVER_BUFFER: NEW") +else() + remove_definitions(-DENABLE_NEW_RCVBUFFER) + message(STATUS "RECEIVER_BUFFER: OLD") +endif() + +if (CMAKE_MAJOR_VERSION LESS 3) + set (FORCE_CXX_STANDARD_GNUONLY 1) +endif() + +if (DEFINED USE_CXX_STD) + srt_check_cxxstd(${USE_CXX_STD} STDCXX STDPFX) + + if (${STDCXX} EQUAL 0) + message(FATAL_ERROR "USE_CXX_STD: Must specify 03/11/14/17/20 possibly with c++/gnu++ prefix") + endif() + + if (NOT STDCXX STREQUAL "") + + if (${STDCXX} LESS 11) + if (ENABLE_STDCXX_SYNC) + message(FATAL_ERROR "If ENABLE_STDCXX_SYNC, then you can't USE_CXX_STD less than 11") + endif() + # Set back to 98 because cmake doesn't understand 03. + set (STDCXX 98) + # This enforces C++03 standard on SRT. + # Apps still use C++11 + + # Set this through independent flags + set (USE_CXX_STD_LIB ${STDCXX}) + set (FORCE_CXX_STANDARD 1) + if (NOT ENABLE_APPS) + set (USE_CXX_STD_APP ${STDCXX}) + message(STATUS "C++ STANDARD: library: C++${STDCXX}, apps disabled (examples will follow C++${STDCXX})") + else() + set (USE_CXX_STD_APP "") + message(STATUS "C++ STANDARD: library: C++${STDCXX}, but apps still at least C++11") + endif() + elseif (FORCE_CXX_STANDARD_GNUONLY) + # CMake is too old to handle CMAKE_CXX_STANDARD, + # use bare GNU options. + set (FORCE_CXX_STANDARD 1) + set (USE_CXX_STD_APP ${STDCXX}) + set (USE_CXX_STD_LIB ${STDCXX}) + message(STATUS "C++ STANDARD: using C++${STDCXX} for all - GNU only") + else() + # This enforces this standard on both apps and library, + # so set this as global C++ standard option + set (CMAKE_CXX_STANDARD ${STDCXX}) + unset (FORCE_CXX_STANDARD) + + # Do not set variables to not duplicate flags + set (USE_CXX_STD_LIB "") + set (USE_CXX_STD_APP "") + message(STATUS "C++ STANDARD: using C++${STDCXX} for all") + endif() + + message(STATUS "C++: Setting C++ standard for gnu compiler: lib: ${USE_CXX_STD_LIB} apps: ${USE_CXX_STD_APP}") + endif() +else() + set (USE_CXX_STD_LIB "") + set (USE_CXX_STD_APP "") +endif() + +if (FORCE_CXX_STANDARD) + message(STATUS "C++ STD: Forcing C++11 on applications") + if (USE_CXX_STD_APP STREQUAL "") + set (USE_CXX_STD_APP 11) + endif() + + if (USE_CXX_STD_LIB STREQUAL "" AND ENABLE_STDCXX_SYNC) + message(STATUS "C++ STD: Forcing C++11 on library, as C++11 sync requested") + set (USE_CXX_STD_LIB 11) + endif() endif() # add extra warning flags for gccish compilers @@ -334,14 +627,6 @@ if (USE_STATIC_LIBSTDCXX) endif() endif() -# We need clock_gettime, but on some systems this is only provided -# by librt. Check if librt is required. -if (ENABLE_MONOTONIC_CLOCK AND LINUX) - # "requires" - exits on FATAL_ERROR when clock_gettime not available - test_requires_clock_gettime(NEED_CLOCK_GETTIME) - set (WITH_EXTRALIBS "${WITH_EXTRALIBS} ${NEED_CLOCK_GETTIME}") -endif() - # This options is necessary on some systems; on a cross-ARM compiler it # has been detected, for example, that -lrt is necessary for some applications @@ -358,8 +643,6 @@ endif() set (srt_libspec_shared ${ENABLE_SHARED}) set (srt_libspec_static ${ENABLE_STATIC}) -set (haicrypt_libspec VIRTUAL) - set (srtpack_libspec_common) if (srt_libspec_shared) list(APPEND srtpack_libspec_common ${TARGET_srt}_shared) @@ -379,20 +662,25 @@ if(WIN32) message(STATUS "DETECTED SYSTEM: WINDOWS; WIN32=1; PTW32_STATIC_LIB=1") add_definitions(-DWIN32=1 -DPTW32_STATIC_LIB=1) elseif(DARWIN) - message(STATUS "DETECTED SYSTEM: DARWIN; OSX=1") - add_definitions(-DOSX=1) + message(STATUS "DETECTED SYSTEM: DARWIN") elseif(BSD) message(STATUS "DETECTED SYSTEM: BSD; BSD=1") add_definitions(-DBSD=1) elseif(LINUX) add_definitions(-DLINUX=1) message(STATUS "DETECTED SYSTEM: LINUX; LINUX=1" ) +elseif(ANDROID) + add_definitions(-DLINUX=1) + message(STATUS "DETECTED SYSTEM: ANDROID; LINUX=1" ) elseif(CYGWIN) add_definitions(-DCYGWIN=1) message(STATUS "DETECTED SYSTEM: CYGWIN (posix mode); CYGWIN=1") elseif(GNU) add_definitions(-DGNU=1) message(STATUS "DETECTED SYSTEM: GNU; GNU=1" ) +elseif(SUNOS) + add_definitions(-DSUNOS=1) + message(STATUS "DETECTED SYSTEM: SunOS|Solaris; SUNOS=1" ) else() message(FATAL_ERROR "Unsupported system: ${CMAKE_SYSTEM_NAME}") endif() @@ -404,6 +692,11 @@ add_definitions( -DSRT_VERSION="${SRT_VERSION}" ) +if (LINUX) +# This is an option supported only on Linux + add_definitions(-DSRT_ENABLE_BINDTODEVICE) +endif() + # This is obligatory include directory for all targets. This is only # for private headers. Installable headers should be exclusively used DIRECTLY. include_directories(${SRT_SRC_COMMON_DIR} ${SRT_SRC_SRTCORE_DIR} ${SRT_SRC_HAICRYPT_DIR}) @@ -427,6 +720,19 @@ if (ENABLE_GETNAMEINFO) list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_GETNAMEINFO=1") endif() +# ENABLE_EXPERIMENTAL_BONDING is deprecated. Use ENABLE_BONDING. ENABLE_EXPERIMENTAL_BONDING is be removed in v1.6.0. +if (ENABLE_EXPERIMENTAL_BONDING) + message(DEPRECATION "ENABLE_EXPERIMENTAL_BONDING is deprecated. Please use ENABLE_BONDING instead.") + set (ENABLE_BONDING ON) +endif() + +if (ENABLE_BONDING) + list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_BONDING=1") + message(STATUS "ENABLE_BONDING: ON") +else() + message(STATUS "ENABLE_BONDING: OFF") +endif() + if (ENABLE_THREAD_CHECK) add_definitions( -DSRT_ENABLE_THREADCHECK=1 @@ -435,6 +741,11 @@ if (ENABLE_THREAD_CHECK) ) endif() +if (ENABLE_CLANG_TSA) + list(APPEND SRT_EXTRA_CFLAGS "-Wthread-safety") + message(STATUS "Clang TSA: Enabled") +endif() + if (ENABLE_PROFILE) if (HAVE_COMPILER_GNU_COMPAT) # They are actually cflags, not definitions, but CMake is stupid enough. @@ -449,12 +760,16 @@ if (ENABLE_CODE_COVERAGE) if (HAVE_COMPILER_GNU_COMPAT) add_definitions(-g -O0 --coverage) link_libraries(--coverage) + message(STATUS "ENABLE_CODE_COVERAGE: ON") else() - message(FATAL_ERROR "Code coverage option is not supported on this platform") + message(FATAL_ERROR "ENABLE_CODE_COVERAGE: option is not supported on this platform") endif() endif() -if (PTHREAD_LIBRARY AND PTHREAD_INCLUDE_DIR) +# On Linux pthreads have to be linked even when using C++11 threads +if (ENABLE_STDCXX_SYNC AND NOT LINUX) + message(STATUS "Pthread library: C++11") +elseif (PTHREAD_LIBRARY AND PTHREAD_INCLUDE_DIR) message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") message(STATUS "Pthread include dir: ${PTHREAD_INCLUDE_DIR}") elseif (MICROSOFT) @@ -462,7 +777,7 @@ elseif (MICROSOFT) if (NOT PTHREAD_INCLUDE_DIR OR NOT PTHREAD_LIBRARY) #search package folders with GLOB to add as extra hint for headers - file(GLOB PTHREAD_PACKAGE_INCLUDE_HINT ./packages/cinegy.pthreads-win*/sources) + file(GLOB PTHREAD_PACKAGE_INCLUDE_HINT ./_packages/cinegy.pthreads-win*/sources) if (PTHREAD_PACKAGE_INCLUDE_HINT) message(STATUS "PTHREAD_PACKAGE_INCLUDE_HINT value: ${PTHREAD_PACKAGE_INCLUDE_HINT}") endif() @@ -477,7 +792,7 @@ elseif (MICROSOFT) endif() #search package folders with GLOB to add as extra hint for libs - file(GLOB PTHREAD_PACKAGE_LIB_HINT ./packages/cinegy.pthreads-win*/runtimes/win-*/native/release) + file(GLOB PTHREAD_PACKAGE_LIB_HINT ./_packages/cinegy.pthreads-win*/runtimes/win-*/native/release) if (PTHREAD_PACKAGE_LIB_HINT) message(STATUS "PTHREAD_PACKAGE_LIB_HINT value: ${PTHREAD_PACKAGE_LIB_HINT}") endif() @@ -558,8 +873,6 @@ endif() # so consider adding at least one real source file to any target that references $. set(OBJECT_LIB_SUPPORT "${PROJECT_SOURCE_DIR}/cmake_object_lib_support.c") -add_library(haicrypt_virtual OBJECT ${SOURCES_haicrypt} ${SOURCES_common}) - # NOTE: The "virtual library" is a library specification that cmake # doesn't support (the library of OBJECT type is something in kind of that, # but not fully supported - for example it doesn't support transitive flags, @@ -572,6 +885,13 @@ add_library(haicrypt_virtual OBJECT ${SOURCES_haicrypt} ${SOURCES_common}) # --- # Target: srt. DEFINITION ONLY. Haicrypt flag settings follow. # --- + +if (ENABLE_SHARED AND MICROSOFT) + #add resource files to shared library, to set DLL metadata on Windows DLLs + set (EXTRA_WIN32_SHARED 1) + message(STATUS "WIN32: extra resource file will be added") +endif() + MafReadDir(srtcore filelist.maf SOURCES SOURCES_srt PUBLIC_HEADERS HEADERS_srt @@ -582,54 +902,66 @@ MafReadDir(srtcore filelist.maf # Auto generated version file and add it to the HEADERS_srt list. if(DEFINED ENV{APPVEYOR_BUILD_NUMBER}) set(SRT_VERSION_BUILD ON) - set(APPVEYOR_BUILD_NUMBER_STRING $ENV{APPVEYOR_BUILD_NUMBER}) + set(CI_BUILD_NUMBER_STRING $ENV{APPVEYOR_BUILD_NUMBER}) message(STATUS "AppVeyor build environment detected: Adding build number to version header") endif() +if(DEFINED ENV{TEAMCITY_VERSION}) + set(SRT_VERSION_BUILD ON) + set(CI_BUILD_NUMBER_STRING $ENV{CI_BUILD_COUNTER}) + message(STATUS "TeamCity build environment detected: Adding build counter to version header") +endif() configure_file("srtcore/version.h.in" "version.h" @ONLY) list(INSERT HEADERS_srt 0 "${CMAKE_CURRENT_BINARY_DIR}/version.h") include_directories("${CMAKE_CURRENT_BINARY_DIR}") -add_library(srt_virtual OBJECT ${SOURCES_srt} ${SOURCES_srt_extra} ${HEADERS_srt}) +add_library(srt_virtual OBJECT ${SOURCES_srt} ${SOURCES_srt_extra} ${HEADERS_srt} ${SOURCES_haicrypt} ${SOURCES_common}) if (ENABLE_SHARED) # Set this to sources as well, as it won't be automatically handled - foreach (tar srt_virtual haicrypt_virtual) - set_target_properties(${tar} PROPERTIES POSITION_INDEPENDENT_CODE 1) - endforeach() + set_target_properties(srt_virtual PROPERTIES POSITION_INDEPENDENT_CODE 1) +endif() - #add resource files to shared library, to set DLL metadata on Windows DLLs - if (MICROSOFT) - MafReadDir(srtcore filelist.maf - SOURCES_WIN32_SHARED SOURCES_srt_shared_win32 - ) - - message(STATUS "WINDOWS detected: Adding sources to SRT shared project: ${SOURCES_srt_shared_win32}") +macro(srt_set_stdcxx targetname spec) + set (stdcxxspec ${spec}) + if (NOT "${stdcxxspec}" STREQUAL "") + if (FORCE_CXX_STANDARD_GNUONLY) + target_compile_options(${targetname} PRIVATE -std=c++${stdcxxspec}) + message(STATUS "C++ STD: ${targetname}: forced C++${stdcxxspec} standard - GNU option: -std=c++${stdcxxspec}") + else() + set_target_properties(${targetname} PROPERTIES CXX_STANDARD ${stdcxxspec}) + message(STATUS "C++ STD: ${targetname}: forced C++${stdcxxspec} standard - portable way") + endif() + else() + message(STATUS "APP: ${targetname}: using default C++ standard") endif() -endif() +endmacro() -# Manual handling of dependency on virtual library -# By setting the target, all settings applied to the haicrypt target -# will now apply to the dependent library. -#list(APPEND SOURCES_srt ${SOURCES_haicrypt}) -set (VIRTUAL_srt $ $) + +srt_set_stdcxx(srt_virtual "${USE_CXX_STD_LIB}") + +set (VIRTUAL_srt $) if (srt_libspec_shared) - add_library(${TARGET_srt}_shared SHARED ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt} ${SOURCES_srt_shared_win32} ${HEADERS_srt_shared_win32}) + add_library(${TARGET_srt}_shared SHARED ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) # shared libraries need PIC set (CMAKE_POSITION_INDEPENDENT_CODE ON) set_property(TARGET ${TARGET_srt}_shared PROPERTY OUTPUT_NAME ${TARGET_srt}) - set_target_properties (${TARGET_srt}_shared PROPERTIES VERSION ${SRT_VERSION} SOVERSION ${SRT_VERSION_MAJOR}) + set_target_properties (${TARGET_srt}_shared PROPERTIES VERSION ${SRT_VERSION} SOVERSION ${SRT_VERSION_MAJOR}.${SRT_VERSION_MINOR}) list (APPEND INSTALL_TARGETS ${TARGET_srt}_shared) if (ENABLE_ENCRYPTION) target_link_libraries(${TARGET_srt}_shared PRIVATE ${SSL_LIBRARIES}) endif() if (MICROSOFT) target_link_libraries(${TARGET_srt}_shared PRIVATE ws2_32.lib) - set_target_properties(${TARGET_srt}_shared PROPERTIES LINK_FLAGS "/DELAYLOAD:libeay32.dll") + if (OPENSSL_USE_STATIC_LIBS) + target_link_libraries(${TARGET_srt}_shared PRIVATE crypt32.lib) + else() + set_target_properties(${TARGET_srt}_shared PROPERTIES LINK_FLAGS "/DELAYLOAD:libeay32.dll") + endif() elseif (MINGW) - target_link_libraries(${TARGET_srt}_shared PRIVATE wsock32.lib ws2_32.lib) + target_link_libraries(${TARGET_srt}_shared PRIVATE wsock32 ws2_32) elseif (APPLE) set_property(TARGET ${TARGET_srt}_shared PROPERTY MACOSX_RPATH ON) endif() @@ -661,6 +993,9 @@ if (srt_libspec_static) endif() if (MICROSOFT) target_link_libraries(${TARGET_srt}_static PRIVATE ws2_32.lib) + if (OPENSSL_USE_STATIC_LIBS) + target_link_libraries(${TARGET_srt}_static PRIVATE crypt32.lib) + endif() elseif (MINGW) target_link_libraries(${TARGET_srt}_static PRIVATE wsock32 ws2_32) endif() @@ -669,30 +1004,18 @@ if (srt_libspec_static) endif() endif() - - -# --- -# And back to target: haicrypt. Both targets must be defined -# prior to setting flags, and after defining the list of sources -# can no longer be extended. -# -# For haicrypt.spec = VIRTUAL, these settings apply to srt. -# Otherwise they apply to haicrypt. -# --- - - -target_include_directories(haicrypt_virtual PRIVATE ${SSL_INCLUDE_DIRS}) +target_include_directories(srt_virtual PRIVATE ${SSL_INCLUDE_DIRS}) if (MICROSOFT) - set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib) + if (OPENSSL_USE_STATIC_LIBS) + set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib crypt32.lib) + else() + set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib) + endif() elseif (MINGW) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} -lwsock32 -lws2_32) endif() -# --- -# So, back to target: srt. Setting the rest of the settings for srt target. -# --- - # Applying this to public includes is not transitive enough. # On Windows, apps require this as well, so it's safer to # spread this to all targets. @@ -713,16 +1036,34 @@ endforeach() set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${PTHREAD_LIBRARY}) target_compile_definitions(srt_virtual PRIVATE -DSRT_EXPORTS ) -target_compile_definitions(haicrypt_virtual PUBLIC -DHAICRYPT_DYNAMIC) if (ENABLE_SHARED) - target_compile_definitions(srt_virtual PUBLIC -DSRT_DYNAMIC) - target_compile_definitions(haicrypt_virtual PRIVATE -DHAICRYPT_EXPORTS) + target_compile_definitions(srt_virtual PUBLIC -DSRT_DYNAMIC) endif() if (srt_libspec_shared) -if (MICROSOFT) - target_link_libraries(${TARGET_srt}_shared PUBLIC Ws2_32.lib) + if (MICROSOFT) + target_link_libraries(${TARGET_srt}_shared PUBLIC Ws2_32.lib) + if (OPENSSL_USE_STATIC_LIBS) + target_link_libraries(${TARGET_srt}_shared PUBLIC crypt32.lib) + endif() + endif() endif() + +# Required by some toolchains when statically linking this library if the +# GCC Atomic Intrinsics are being used. +if (HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC AND HAVE_LIBATOMIC) + if (srt_libspec_static) + target_link_libraries(${TARGET_srt}_static PUBLIC atomic) + endif() + if (srt_libspec_shared) + target_link_libraries(${TARGET_srt}_shared PUBLIC atomic) + endif() +elseif (HAVE_LIBATOMIC AND HAVE_LIBATOMIC_COMPILES_STATIC) + # This is a workaround for ANDROID NDK<17 builds, which need to link + # to libatomic when linking statically to the SRT library. + if (srt_libspec_static) + target_link_libraries(${TARGET_srt}_static PUBLIC atomic) + endif() endif() # Cygwin installs the *.dll libraries in bin directory and uses PATH. @@ -733,15 +1074,29 @@ if (CYGWIN) endif() message(STATUS "INSTALL DIRS: bin=${CMAKE_INSTALL_BINDIR} lib=${CMAKE_INSTALL_LIBDIR} shlib=${INSTALL_SHARED_DIR} include=${CMAKE_INSTALL_INCLUDEDIR}") - -install(TARGETS ${INSTALL_TARGETS} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +if (NEED_DESTINATION) + if (DEFINED CMAKE_INSTALL_BINDIR AND DEFINED CMAKE_INSTALL_LIBDIR AND NOT INSTALL_SHARED_DIR STREQUAL "") + install(TARGETS ${INSTALL_TARGETS} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${INSTALL_SHARED_DIR} + ) + else() + message(WARNING "No location to install ${INSTALL_TARGETS}") + endif() +elseif (NOT INSTALL_SHARED_DIR STREQUAL "") + install(TARGETS ${INSTALL_TARGETS} LIBRARY DESTINATION ${INSTALL_SHARED_DIR} -) -install(FILES ${HEADERS_srt} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/srt) -if (WIN32) - install(FILES ${HEADERS_srt_win32} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/srt/win) + ) +else() + install(TARGETS ${INSTALL_TARGETS}) +endif() + +if (DEFINED CMAKE_INSTALL_INCLUDEDIR) + install(FILES ${HEADERS_srt} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/srt) + if (WIN32) + install(FILES ${HEADERS_srt_win32} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/srt/win) + endif() endif() # --- @@ -777,22 +1132,16 @@ endif() join_arguments(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE}) -# haisrt.pc left temporarily for backward compatibility. To be removed in future! -configure_file(scripts/srt.pc.in haisrt.pc @ONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/haisrt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -configure_file(scripts/srt.pc.in srt.pc @ONLY) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/srt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +if (DEFINED CMAKE_INSTALL_LIBDIR) + # haisrt.pc left temporarily for backward compatibility. To be removed in future! + configure_file(scripts/srt.pc.in haisrt.pc @ONLY) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/haisrt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + configure_file(scripts/srt.pc.in srt.pc @ONLY) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/srt.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +endif() # Applications -if (HAVE_COMPILER_GNU_COMPAT AND ENABLE_CXX11) - message(STATUS "C++ VERSION: Setting C++11 compat flag for gnu compiler") - set (CFLAGS_CXX_STANDARD "-std=c++11") -else() - message(STATUS "C++ VERSION: leaving default, not a GNU compiler, assuming C++11 or newer is default.") - set (CFLAGS_CXX_STANDARD "") -endif() - # If static is available, link apps against static one. # Otherwise link against shared one. @@ -807,16 +1156,27 @@ else() message(FATAL_ERROR "Either ENABLE_STATIC or ENABLE_SHARED has to be ON!") endif() -macro(srt_add_program name) +macro(srt_add_program_dont_install name) add_executable(${name} ${ARGN}) target_include_directories(${name} PRIVATE apps) target_include_directories(${name} PRIVATE common) - install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endmacro() + +macro(srt_add_program name) + srt_add_program_dont_install(${name} ${ARGN}) + if(NOT NEED_DESTINATION) + install(TARGETS ${name} RUNTIME) + elseif (DEFINED CMAKE_INSTALL_BINDIR) + install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + else() + message(WARNING "No location to install program ${name}") + endif() endmacro() macro(srt_make_application name) - target_compile_options(${name} PRIVATE ${CFLAGS_CXX_STANDARD}) + srt_set_stdcxx(${name} "${USE_CXX_STD_APP}") + # This is recommended by cmake, but it doesn't work anyway. # What is needed is that this below CMAKE_INSTALL_RPATH (yes, relative) # is added as is. @@ -833,10 +1193,9 @@ macro(srt_make_application name) # XXX not sure about Mac. # See this name used already in install(${TARGET_srt} LIBRARY DESTINATION...). set(FORCE_RPATH LINK_FLAGS -Wl,-rpath,.,-rpath,../${CMAKE_INSTALL_LIBDIR} BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE) - endif() - # We state that Darwin always uses CLANG compiler, which honors this flag the same way. - set_target_properties(${name} PROPERTIES COMPILE_FLAGS "${CFLAGS_CXX_STANDARD}" ${FORCE_RPATH}) + set_target_properties(${name} PROPERTIES ${FORCE_RPATH}) + endif() target_link_libraries(${name} ${srt_link_library}) if (USE_GNUSTL) @@ -850,7 +1209,13 @@ endmacro() macro(srt_add_application name) # ARGN=sources... srt_add_program(${name} apps/${name}.cpp ${ARGN}) srt_make_application(${name}) - install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + if(NOT NEED_DESTINATION) + install(TARGETS ${name} RUNTIME) + elseif (DEFINED CMAKE_INSTALL_BINDIR) + install(TARGETS ${name} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + else() + message(WARNING "No location to install program ${name}") + endif() endmacro() ## FIXME: transmitmedia.cpp does not build on OpenBSD @@ -866,6 +1231,8 @@ endif() if (ENABLE_APPS) + message(STATUS "APPS: ENABLED, std=${USE_CXX_STD_APP}") + # Make a virtual library of all shared app files MafReadDir(apps support.maf SOURCES SOURCES_support @@ -876,7 +1243,7 @@ if (ENABLE_APPS) # library should be changed into a static one and made useful # for users. add_library(srtsupport_virtual OBJECT ${SOURCES_support}) - target_compile_options(srtsupport_virtual PUBLIC ${CFLAGS_CXX_STANDARD}) + srt_set_stdcxx(srtsupport_virtual "${USE_CXX_STD_APP}") set (VIRTUAL_srtsupport $) # Applications @@ -899,6 +1266,7 @@ if (ENABLE_APPS) endif() if (ENABLE_TESTING) + message(STATUS "DEVEL APPS (testing): ENABLED") macro(srt_add_testprogram name) # Variables in macros are not local. Clear them forcefully. @@ -911,16 +1279,17 @@ if (ENABLE_APPS) # For testing applications, every application has its exclusive # list of source files in its own Manifest file. MafReadDir(testing ${name}.maf SOURCES SOURCES_app) - srt_add_program(${name} ${SOURCES_app}) + srt_add_program_dont_install(${name} ${SOURCES_app}) endmacro() srt_add_testprogram(utility-test) + srt_set_stdcxx(utility-test "${USE_CXX_STD_APP}") if (NOT WIN32) # This program is symlinked under git-cygwin. # Avoid misleading syntax error. srt_add_testprogram(uriparser-test) target_compile_options(uriparser-test PRIVATE -DTEST) - target_compile_options(uriparser-test PRIVATE ${CFLAGS_CXX_STANDARD}) + srt_set_stdcxx(uriparser-test "${USE_CXX_STD_APP}") endif() srt_add_testprogram(srt-test-live) @@ -935,8 +1304,19 @@ if (ENABLE_APPS) srt_add_testprogram(srt-test-multiplex) srt_make_application(srt-test-multiplex) + + if (ENABLE_BONDING) + srt_add_testprogram(srt-test-mpbond) + srt_make_application(srt-test-mpbond) + endif() + + else() + message(STATUS "DEVEL APPS (testing): DISABLED") endif() + +else() + message(STATUS "APPS: DISABLED") endif() if (ENABLE_EXAMPLES) @@ -944,31 +1324,43 @@ if (ENABLE_EXAMPLES) # No examples should need C++11 macro(srt_add_example mainsrc) get_filename_component(name ${mainsrc} NAME_WE) - srt_add_program(${name} examples/${mainsrc} ${ARGN}) + srt_add_program_dont_install(${name} examples/${mainsrc} ${ARGN}) + target_link_libraries(${name} ${srt_link_library} ${DEPENDS_srt}) endmacro() - srt_add_example(sendfile.cpp) - srt_make_application(sendfile) + srt_add_example(recvlive.cpp) + srt_add_example(sendfile.cpp) + srt_add_example(recvfile.cpp) - srt_make_application(recvfile) - - srt_add_example(recvlive.cpp) - srt_make_application(recvlive) srt_add_example(test-c-client.c) - srt_make_application(test-c-client) + + srt_add_example(example-client-nonblock.c) srt_add_example(test-c-server.c) - srt_make_application(test-c-server) - srt_add_example(testcapi-connect.c) - target_link_libraries(testcapi-connect ${srt_link_library} ${DEPENDS_srt}) +if (ENABLE_BONDING) + srt_add_example(test-c-client-bonding.c) + srt_add_example(test-c-server-bonding.c) +endif() + + srt_add_example(testcapi-connect.c) endif() if (ENABLE_UNITTESTS AND ENABLE_CXX11) + + if (${CMAKE_VERSION} VERSION_LESS "3.10.0") + message(STATUS "VERSION < 3.10 -- adding test using the old method") + set (USE_OLD_ADD_METHOD 1) + else() + message(STATUS "VERSION > 3.10 -- using NEW POLICY for in_list operator") + cmake_policy(SET CMP0057 NEW) # Support the new IN_LIST operator. + endif() + + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) find_package(GTest 1.8) @@ -983,14 +1375,13 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) endif() MafReadDir(test filelist.maf + HEADERS SOURCES_unittests SOURCES SOURCES_unittests ) - - message(STATUS "Unit test sources: ${SOURCES_unittests}") - srt_add_program(test-srt ${SOURCES_unittests}) + srt_add_program_dont_install(test-srt ${SOURCES_unittests}) srt_make_application(test-srt) - target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS}) + target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) target_link_libraries( test-srt @@ -999,19 +1390,31 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) ${PTHREAD_LIBRARY} ) - add_test( - NAME + if (USE_OLD_ADD_METHOD) + add_test( + NAME test-srt + COMMAND ${CMAKE_BINARY_DIR}/test-srt + ) + else() + gtest_add_tests( test-srt - COMMAND - ${CMAKE_BINARY_DIR}/test-srt - ) + "" + AUTO + ) + endif() enable_testing() endif() -install(PROGRAMS scripts/srt-ffplay DESTINATION ${CMAKE_INSTALL_BINDIR}) +if(NOT NEED_DESTINATION) + install(PROGRAMS scripts/srt-ffplay TYPE BIN) +elseif (DEFINED CMAKE_INSTALL_BINDIR) + install(PROGRAMS scripts/srt-ffplay DESTINATION ${CMAKE_INSTALL_BINDIR}) +else() + message(WARNING "No location to install scripts/srt-ffplay") +endif() if (DEFINED SRT_EXTRA_APPS_INC) @@ -1020,12 +1423,7 @@ if (DEFINED SRT_EXTRA_APPS_INC) # already provided and define additional targets. endif() -if ( ENABLE_SUFLIP ) - set (SOURCES_suflip - ${CMAKE_CURRENT_SOURCE_DIR}/apps/suflip.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/common/uriparser.cpp - ) - srt_add_program(suflip ${SOURCES_suflip}) - target_link_libraries(suflip ${srt_link_library}) - install(TARGETS suflip RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -endif () +if (ENABLE_SHOW_PROJECT_CONFIG) + include(ShowProjectConfig) + ShowProjectConfig() +endif() diff --git a/trunk/3rdparty/srt-1-fit/common/win/ATTIC/winporting.h b/trunk/3rdparty/srt-1-fit/common/win/ATTIC/winporting.h deleted file mode 100644 index 098ed2d3b5..0000000000 --- a/trunk/3rdparty/srt-1-fit/common/win/ATTIC/winporting.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef _WINPORTING_H_ -#define _WINPORTING_H_ - -// NOTE: This file has been borrowed from LCM project -// http://lcm-proj.github.io/ - -#if !defined(__MINGW32__) -#define strtoll _strtoi64 -#define strdup _strdup -#define mode_t int -#define snprintf _snprintf -//#define PATH_MAX MAX_PATH -#define fseeko _fseeki64 -#define ftello _ftelli64 -//#define socklen_t int -#define in_addr_t in_addr -#define SHUT_RDWR SD_BOTH -#define HUGE HUGE_VAL -#define O_NONBLOCK 0x4000 -#define F_GETFL 3 -#define F_SETFL 4 -#endif - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Microsoft implementation of these structures has the -// pointer and length in reversed positions. -typedef struct iovec -{ - ULONG iov_len; - char *iov_base; -} iovec; - -typedef struct msghdr -{ - struct sockaddr *msg_name; - int msg_namelen; - struct iovec *msg_iov; - ULONG msg_iovlen; - int msg_controllen; - char *msg_control; - ULONG msg_flags; -} msghdr; - -//typedef long int ssize_t; - -//int inet_aton(const char *cp, struct in_addr *inp); - -int fcntl (int fd, int flag1, ...); - -size_t recvmsg ( SOCKET s, struct msghdr *msg, int flags ); -size_t sendmsg ( SOCKET s, const struct msghdr *msg, int flags ); - -#ifdef __cplusplus -} -#endif - -#endif // _WINPORTING_H_ diff --git a/trunk/3rdparty/srt-1-fit/common/win/syslog_defs.h b/trunk/3rdparty/srt-1-fit/common/win/syslog_defs.h index 6d761b5e5c..7d83126412 100644 --- a/trunk/3rdparty/srt-1-fit/common/win/syslog_defs.h +++ b/trunk/3rdparty/srt-1-fit/common/win/syslog_defs.h @@ -1,5 +1,5 @@ -#ifndef INC__WINDOWS_SYSLOG_DEFS_H -#define INC__WINDOWS_SYSLOG_DEFS_H +#ifndef INC_SRT_WINDOWS_SYSLOG_DEFS_H +#define INC_SRT_WINDOWS_SYSLOG_DEFS_H #define LOG_EMERG 0 #define LOG_ALERT 1 diff --git a/trunk/3rdparty/srt-1-fit/common/win/wintime.h b/trunk/3rdparty/srt-1-fit/common/win/wintime.h index 781bbe5634..ff1bc65de6 100644 --- a/trunk/3rdparty/srt-1-fit/common/win/wintime.h +++ b/trunk/3rdparty/srt-1-fit/common/win/wintime.h @@ -1,5 +1,5 @@ -#ifndef INC__WIN_WINTIME -#define INC__WIN_WINTIME +#ifndef INC_SRT_WIN_WINTIME +#define INC_SRT_WIN_WINTIME #include #include @@ -7,7 +7,6 @@ // where pthread.h, which defines _POSIX_THREAD_SAFE_FUNCTIONS, // has to be included before time.h so that time.h defines // localtime_r correctly -#include #include #ifdef __cplusplus @@ -53,4 +52,4 @@ SRTCOMPAT_WINTIME_STATIC_INLINE_DECL int gettimeofday( } #endif -#endif // INC__WIN_WINTIME +#endif // INC_SRT_WIN_WINTIME diff --git a/trunk/3rdparty/srt-1-fit/common/win_time.cpp b/trunk/3rdparty/srt-1-fit/common/win_time.cpp index 83ea7b0982..2778cea797 100644 --- a/trunk/3rdparty/srt-1-fit/common/win_time.cpp +++ b/trunk/3rdparty/srt-1-fit/common/win_time.cpp @@ -27,39 +27,11 @@ void SRTCompat_timeradd(struct timeval *a, struct timeval *b, struct timeval *re } } -int SRTCompat_gettimeofday(struct timeval* tp, struct timezone* tz) +int SRTCompat_gettimeofday(struct timeval* tp, struct timezone*) { - static LARGE_INTEGER tickFrequency, epochOffset; - - // For our first call, use "ftime()", so that we get a time with a proper epoch. - // For subsequent calls, use "QueryPerformanceCount()", because it's more fine-grain. - static int isFirstCall = 1; - - LARGE_INTEGER tickNow; - QueryPerformanceCounter(&tickNow); - - if (isFirstCall) - { - struct timeb tb; - ftime(&tb); - tp->tv_sec = (long)tb.time; - tp->tv_usec = 1000*tb.millitm; - - // Also get our counter frequency: - QueryPerformanceFrequency(&tickFrequency); - - // And compute an offset to add to subsequent counter times, so we get a proper epoch: - epochOffset.QuadPart = tb.time*tickFrequency.QuadPart + (tb.millitm*tickFrequency.QuadPart)/1000 - tickNow.QuadPart; - - isFirstCall = 0; // for next time - } - else - { - // Adjust our counter time so that we get a proper epoch: - tickNow.QuadPart += epochOffset.QuadPart; - - tp->tv_sec = (long) (tickNow.QuadPart / tickFrequency.QuadPart); - tp->tv_usec = (long) (((tickNow.QuadPart % tickFrequency.QuadPart) * 1000000L) / tickFrequency.QuadPart); - } + struct timeb tb; + ftime(&tb); + tp->tv_sec = (long)tb.time; + tp->tv_usec = 1000*tb.millitm; return 0; } diff --git a/trunk/3rdparty/srt-1-fit/configure b/trunk/3rdparty/srt-1-fit/configure index 946df108dd..796e0a1511 100755 --- a/trunk/3rdparty/srt-1-fit/configure +++ b/trunk/3rdparty/srt-1-fit/configure @@ -102,7 +102,7 @@ foreach {o desc} $options { } -if { $argv == "--help" } { +if { $argv == "--help" || $argv == "-h" } { puts stderr "Usage: ./configure \[options\]" puts stderr "OPTIONS:" foreach o [lsort [array names opt]] { @@ -153,8 +153,10 @@ foreach a $argv { set type "" - if { [string first = $a] != -1 } { - lassign [split $a =] a val + if { [set a1 [string first = $a]] != -1 } { + # Do not split. Options may include =. + set val [string range $a $a1+1 end] + set a [string range $a 0 $a1-1] } if { [dict exists $::alias $a] } { @@ -194,6 +196,23 @@ if { $saveopt != "" } { error "Extra unhandled argument: $saveopt" } +# Save the original call into config-status.sh + +set ofd [open config-status.sh w] +puts $ofd "#!/bin/bash" +puts -nonewline $ofd "$argv0 " +foreach a $argv { + set len 1 + if {[catch {llength $a} len] || $len > 1 } { + puts -nonewline $ofd "'$a' " + } else { + puts -nonewline $ofd "$a " + } +} +puts $ofd "" +close $ofd +file attributes config-status.sh -permissions +x + set cmakeopt "" resolve_disablers diff --git a/trunk/3rdparty/srt-1-fit/configure-data.tcl b/trunk/3rdparty/srt-1-fit/configure-data.tcl index 446b9514b8..eace99da14 100644 --- a/trunk/3rdparty/srt-1-fit/configure-data.tcl +++ b/trunk/3rdparty/srt-1-fit/configure-data.tcl @@ -25,47 +25,55 @@ # Options processed here internally, not passed to cmake set internal_options { - with-compiler-prefix= "set C/C++ toolchains gcc and g++" - with-compiler-type= "compiler type: gcc(default), cc, others simply add ++ for C++" - with-srt-name= "Override srt library name" - with-haicrypt-name= "Override haicrypt library name (if compiled separately)" + with-compiler-prefix= "set C/C++ toolchains gcc and g++" + with-compiler-type= "compiler type: gcc(default), cc, others simply add ++ for C++" + with-srt-name= "Override srt library name" + with-haicrypt-name= "Override haicrypt library name (if compiled separately)" + with-atomic= "Select implementation for atomics (compiler-intrinsics or sync-mutex)" } # Options that refer directly to variables used in CMakeLists.txt set cmake_options { cygwin-use-posix "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin. (default: OFF)" - enable-encryption "Should encryption features be enabled (default: ON)" - enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON)" + enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON, with gcc < 4.7 OFF)" enable-apps "Should the Support Applications be Built? (default: ON)" + enable-bonding "Enable 'bonding' SRT feature (default: OFF)" enable-testing "Should developer testing applications be built (default: OFF)" - enable-c++-deps "Extra library dependencies in srt.pc for C language (default: OFF)" - enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)" + enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)" enable-logging "Should logging be enabled (default: ON)" - enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)" + enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)" enable-haicrypt-logging "Should logging in haicrypt be enabled (default: OFF)" - enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)" - enable-code-coverage "Enable code coverage reporting (default: OFF)" - enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)" - enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)" - enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)" enable-shared "Should libsrt be built as a shared library (default: ON)" enable-static "Should libsrt be built as a static library (default: ON)" - enable-suflip "Should suflip tool be built (default: OFF)" + enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)" enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)" - enable-unittests "Enable unit tests (default: OFF)" + enable-unittests "Enable Unit Tests (will download Google UT) (default: OFF)" + enable-encryption "Should encryption features be enabled (default: ON)" + enable-c++-deps "Extra library dependencies in srt.pc for C language (default: ON)" + use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)" + enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)" + enable-code-coverage "Enable code coverage reporting (default: OFF)" + enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)" enable-thread-check "Enable #include that implements THREAD_* macros" - openssl-crypto-library= "Path to a library." - openssl-include-dir= "Path to a file." - openssl-ssl-library= "Path to a library." - pkg-config-executable= "pkg-config executable" - pthread-include-dir= "Path to a file." - pthread-library= "Path to a library." + enable-stdc++-sync "Use standard C++11 chrono/threads instead of pthread wrapper (default: OFF, on Windows: ON)" + use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)" + openssl-use-static-libs "Link OpenSSL statically (default: OFF)." use-busy-waiting "Enable more accurate sending times at a cost of potentially higher CPU load (default: OFF)" use-gnustl "Get c++ library/headers from the gnustl.pc" + enable-sock-cloexec "Enable setting SOCK_CLOEXEC on a socket (default: ON)" + enable-show-project-config "Enables use of ShowProjectConfig() in cmake (default: OFF)" + enable-new-rcvbuffer "Enables the new receiver buffer implementation (default: ON)" + enable-clang-tsa "Enable Clang's Thread-Safety-Analysis (default: OFF)" + atomic-use-srt-sync-mutex "Use mutex to implement atomics (alias: --with-atomic=sync-mutex) (default: OFF)" + use-enclib "Encryption library to be used: openssl(default), gnutls, mbedtls" - use-gnutls "DEPRECATED. Use USE_ENCLIB=openssl|gnutls|mbedtls instead" - use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)" - use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)" + enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)" + pkg-config-executable= "pkg-config executable" + openssl-crypto-library= "OpenSSL: Path to a libcrypto library." + openssl-include-dir= "OpenSSL: Path to includes." + openssl-ssl-library= "OpenSSL: Path to a libssl library." + pthread-include-dir= "PThread: Path to includes" + pthread-library= "PThread: Path to the pthread library." } set options $internal_options$cmake_options @@ -162,6 +170,24 @@ proc preprocess {} { set ::haicrypt_name $::optval(--with-haicrypt-name) unset ::optval(--with-haicrypt-name) } + + if { "--with-atomic" in $::optkeys } { + switch -- $::optval(--with-atomic) { + compiler-intrinsics { + } + + sync-mutex { + set ::optval(--atomic-use-srt-sync-mutex) 1 + } + + default { + puts "ERROR: --with-atomic option accepts two values: compiler-intrinsics (default) or sync-mutex" + exit 1 + } + } + + unset ::optval(--with-atomic) + } } proc GetCompilerCommand {} { @@ -170,9 +196,16 @@ proc GetCompilerCommand {} { # --cmake-c[++]-compiler # (cmake-toolchain-file will set things up without the need to check things here) + set compiler gcc + if { [info exists ::optval(--with-compiler-type)] } { + set compiler $::optval(--with-compiler-type) + } + if { [info exists ::optval(--with-compiler-prefix)] } { set prefix $::optval(--with-compiler-prefix) - return ${prefix}gcc + return ${prefix}$compiler + } else { + return $compiler } if { [info exists ::optval(--cmake-c-compiler)] } { @@ -201,6 +234,7 @@ proc postprocess {} { set toolchain_changed no foreach changer { --with-compiler-prefix + --with-compiler-type --cmake-c-compiler --cmake-c++-compiler --cmake-cxx-compiler @@ -223,6 +257,7 @@ proc postprocess {} { # Check characteristics of the compiler - in particular, whether the target is different # than the current target. set compiler_path "" + set target_platform "" set cmd [GetCompilerCommand] if { $cmd != "" } { set gcc_version [exec $cmd -v 2>@1] @@ -237,7 +272,7 @@ proc postprocess {} { } if { $target_platform == "" } { - puts "NOTE: can't obtain target from gcc -v: $l" + puts "NOTE: can't obtain target from '[file tail $cmd] -v': $l - ASSUMING HOST compiler" } else { if { $target_platform != $::tcl_platform(machine) } { puts "NOTE: foreign target type detected ($target)" ;# - setting CROSSCOMPILING flag" @@ -245,6 +280,8 @@ proc postprocess {} { set iscross 1 } } + } else { + puts "CONFIGURE: default compiler used" } } @@ -332,8 +369,19 @@ proc postprocess {} { # Otherwise don't set PKG_CONFIG_PATH and we'll see. } - if { $::HAVE_DARWIN && !$toolchain_changed} { - + set use_brew 0 + if { $::HAVE_DARWIN && !$toolchain_changed } { + set use_brew 1 + } + if { $use_brew } { + foreach item $::cmakeopt { + if { [string first "Android" $item] != -1 } { + set use_brew 0 + break + } + } + } + if { $use_brew } { if { $have_gnutls } { # Use gnutls explicitly, as found in brew set er [catch {exec brew info gnutls} res] diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-config.h b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-config.h index 747653d12b..ee4921dbe5 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-config.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-config.h @@ -1,5 +1,5 @@ -#ifndef INC__CRYSPR_CONFIG_H -#define INC__CRYSPR_CONFIG_H +#ifndef INC_SRT_CRYSPR_CONFIG_H +#define INC_SRT_CRYSPR_CONFIG_H // Size of the single block for encryption. // This might need tweaking for particular implementation library. @@ -8,14 +8,22 @@ #if defined(USE_OPENSSL) #include "cryspr-openssl.h" #define cryspr4SRT() crysprOpenSSL() +#define CRYSPR_IMPL_DESC "OpenSSL-AES" +#elif defined(USE_OPENSSL_EVP) +#include "cryspr-openssl-evp.h" +#define cryspr4SRT() crysprOpenSSL_EVP() +#define CRYSPR_IMPL_DESC "OpenSSL-EVP" #elif defined(USE_GNUTLS) #include "cryspr-gnutls.h" #define cryspr4SRT() crysprGnuTLS() +#define CRYSPR_IMPL_DESC "GnuTLS" #elif defined(USE_MBEDTLS) #include "cryspr-mbedtls.h" #define cryspr4SRT() crysprMbedtls() +#define CRYSPR_IMPL_DESC "MbedTLS" #else #error Cryspr implementation not selected. Please define USE_* + OPENSSL/GNUTLS/MBEDTLS. +#define CRYSPR_IMPL_DESC "No Cipher" #endif diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-gnutls.c b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-gnutls.c index 217a70e8ac..95eaf2856c 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-gnutls.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-gnutls.c @@ -13,6 +13,8 @@ written by Haivision Systems Inc. + 2022-05-19 (jdube) + CRYSPR2 adaptation 2019-06-27 (jdube) GnuTLS/Nettle CRYSPR/4SRT (CRYypto Service PRovider for SRT) *****************************************************************************/ @@ -24,6 +26,10 @@ written by typedef struct tag_crysprGnuTLS_AES_cb { CRYSPR_cb ccb; /* CRYSPR control block */ /* Add other cryptolib specific data here */ +#ifdef CRYSPR2 + CRYSPR_AESCTX aes_kek_buf; /* Key Encrypting Key (KEK) */ + CRYSPR_AESCTX aes_sek_buf[2]; /* even/odd Stream Encrypting Key (SEK) */ +#endif } crysprGnuTLS_cb; @@ -33,11 +39,14 @@ int crysprGnuTLS_Prng(unsigned char *rn, int len) } int crysprGnuTLS_AES_SetKey( + int cipher_type, /* One of HCRYPT_CTX_MODE_[CLRTXT|AESECB|AESCTR] */ bool bEncrypt, /* true:encrypt key, false:decrypt key*/ const unsigned char *kstr, /* key string */ size_t kstr_len, /* kstr length in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* Cryptolib Specific AES key context */ { + (void)cipher_type; + if (bEncrypt) { /* Encrypt key */ if (!(kstr_len == 16 || kstr_len == 24 || kstr_len == 32)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) bad length\n"); @@ -114,6 +123,31 @@ int crysprGnuTLS_AES_CtrCipher( /* AES-CTR128 Encryption */ return 0; } +#ifdef CRYSPR2 +static CRYSPR_cb *crysprGnuTLS_Open(CRYSPR_methods *cryspr, size_t max_len) +{ + crysprGnuTLS_cb *aes_data; + CRYSPR_cb *cryspr_cb; + + aes_data = (crysprGnuTLS_cb *)crysprHelper_Open(cryspr, sizeof(crysprGnuTLS_cb), max_len); + if (NULL == aes_data) { + HCRYPT_LOG(LOG_ERR, "crysprHelper_Open(%p, %zd, %zd) failed\n", cryspr, sizeof(crysprGnuTLS_cb), max_len); + return(NULL); + } + + aes_data->ccb.aes_kek = &aes_data->aes_kek_buf; //key encrypting key + aes_data->ccb.aes_sek[0] = &aes_data->aes_sek_buf[0]; //stream encrypting key + aes_data->ccb.aes_sek[1] = &aes_data->aes_sek_buf[1]; //stream encrypting key + + return(&aes_data->ccb); +} + +static int crysprGnuTLS_Close(CRYSPR_cb *cryspr_cb) +{ + return(crysprHelper_Close(cryspr_cb)); +} +#endif /* CRYSPR2 */ + #ifdef CRYSPR_HAS_PBKDF2 /* * Password-based Key Derivation Function @@ -157,8 +191,13 @@ CRYSPR_methods *crysprGnuTLS(void) #endif //--Crypto Session (Top API) + #ifdef CRYSPR2 + crysprGnuTLS_methods.open = crysprGnuTLS_Open; + crysprGnuTLS_methods.close = crysprGnuTLS_Close; + #else /* CRYSPR2 */ // crysprGnuTLS_methods.open = // crysprGnuTLS_methods.close = + #endif /* CRYSPR2 */ //--Keying material (km) encryption #if CRYSPR_HAS_PBKDF2 crysprGnuTLS_methods.km_pbkdf2 = crysprGnuTLS_KmPbkdf2; diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.c b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.c index c10727a0aa..c220162937 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.c @@ -13,6 +13,8 @@ written by Haivision Systems Inc. + 2022-05-19 (jdube) + CRYSPR2 adaptation 2019-06-27 (jdube) GnuTLS/Nettle CRYSPR/4SRT (CRYypto Service PRovider for SRT) *****************************************************************************/ @@ -29,11 +31,14 @@ written by // Static members of cryspr::mbedtls class. static mbedtls_ctr_drbg_context crysprMbedtls_ctr_drbg; static mbedtls_entropy_context crysprMbedtls_entropy; -static mbedtls_md_context_t crysprMbedtls_mdctx; typedef struct tag_crysprGnuTLS_AES_cb { CRYSPR_cb ccb; /* CRYSPR control block */ /* Add other cryptolib specific data here */ +#ifdef CRYSPR2 + CRYSPR_AESCTX aes_kek_buf; /* Key Encrypting Key (KEK) */ + CRYSPR_AESCTX aes_sek_buf[2]; /* even/odd Stream Encrypting Key (SEK) */ +#endif } crysprMbedtls_cb; @@ -49,18 +54,23 @@ int crysprMbedtls_Prng(unsigned char *rn, int len) } int crysprMbedtls_AES_SetKey( + int cipher_type, /* One of HCRYPT_CTX_MODE_[CLRTXT|AESECB|AESCTR] */ bool bEncrypt, /* true:encrypt key, false:decrypt key*/ const unsigned char *kstr, /* key string */ size_t kstr_len, /* kstr length in bytes (16, 24, or 32 bytes, for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* Cryptolib Specific AES key context */ { + (void)cipher_type; + if (!(kstr_len == 16 || kstr_len == 24 || kstr_len == 32)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) bad length\n"); return -1; } int ret; - +#ifdef CRYSPR2 + (void)cipher_type; +#endif // mbedtls uses the "bits" convention (128, 192, 254), just like openssl. // kstr_len is in "bytes" convention (16, 24, 32). @@ -146,6 +156,31 @@ int crysprMbedtls_AES_CtrCipher( /* AES-CTR128 Encryption */ return 0; } +#ifdef CRYSPR2 +static CRYSPR_cb *crysprMbedtls_Open(CRYSPR_methods *cryspr, size_t max_len) +{ + crysprMbedtls_cb *aes_data; + CRYSPR_cb *cryspr_cb; + + aes_data = (crysprMbedtls_cb *)crysprHelper_Open(cryspr, sizeof(crysprMbedtls_cb), max_len); + if (NULL == aes_data) { + HCRYPT_LOG(LOG_ERR, "crysprHelper_Open(%p, %zd, %zd) failed\n", cryspr, sizeof(crysprMbedtls_cb), max_len); + return(NULL); + } + + aes_data->ccb.aes_kek = &aes_data->aes_kek_buf; //key encrypting key + aes_data->ccb.aes_sek[0] = &aes_data->aes_sek_buf[0]; //stream encrypting key + aes_data->ccb.aes_sek[1] = &aes_data->aes_sek_buf[1]; //stream encrypting key + + return(&aes_data->ccb); +} + +static int crysprMbedtls_Close(CRYSPR_cb *cryspr_cb) +{ + return(crysprHelper_Close(cryspr_cb)); +} +#endif /* CRYSPR2 */ + /* * Password-based Key Derivation Function */ @@ -161,10 +196,30 @@ int crysprMbedtls_KmPbkdf2( { (void)cryspr_cb; - int ret = mbedtls_pkcs5_pbkdf2_hmac(&crysprMbedtls_mdctx, + const mbedtls_md_info_t* ifo = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + if ( ifo == NULL ) { + // XXX report error, log? + return -1; + } + + mbedtls_md_context_t mdctx; + mbedtls_md_init(&mdctx); + + const int yes_use_hmac = 1; + int ret; + if ( (ret = mbedtls_md_setup(&mdctx, ifo, yes_use_hmac)) != 0 ) { + mbedtls_md_free(&mdctx); + + // XXX report error, log? + return ret; + } + + ret = mbedtls_pkcs5_pbkdf2_hmac(&mdctx, (unsigned char*)passwd, passwd_len, salt, salt_len, itr, key_len, out); + mbedtls_md_free(&mdctx); + if (ret == 0) return 0; @@ -196,8 +251,13 @@ CRYSPR_methods *crysprMbedtls(void) #endif //--Crypto Session (Top API) +#ifdef CRYSPR2 + crysprMbedtls_methods.open = crysprMbedtls_Open; + crysprMbedtls_methods.close = crysprMbedtls_Close; +#else // crysprMbedtls_methods.open = // crysprMbedtls_methods.close = +#endif //--Keying material (km) encryption crysprMbedtls_methods.km_pbkdf2 = crysprMbedtls_KmPbkdf2; // crysprMbedtls_methods.km_setkey = @@ -220,14 +280,6 @@ CRYSPR_methods *crysprMbedtls(void) return NULL; } - // Ok, mbedtls with all flexibility you couldn't make it more complicated. - - mbedtls_md_init(&crysprMbedtls_mdctx); - const mbedtls_md_info_t* ifo = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); - const int yes_use_hmac = 1; - mbedtls_md_setup(&crysprMbedtls_mdctx, ifo, yes_use_hmac); - - return(&crysprMbedtls_methods); } diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.h b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.h index aa10da8743..a83b397c16 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.h @@ -52,10 +52,10 @@ written by /* #define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK -It is set from hte keystring through CRYSPR_methods.aes_set_key and passed +It is set from the keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_XXX. */ -typedef struct mbedtls_aes_context CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ +typedef mbedtls_aes_context CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ struct tag_CRYSPR_methods *crysprMbedtls(void); diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.c b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.c new file mode 100644 index 0000000000..85e9f9d493 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.c @@ -0,0 +1,335 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2019 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +written by + Haivision Systems Inc. + + 2022-05-19 (jdube) + OpenSSL EVP CRYSPR/4SRT (CRYypto Service PRovider for SRT). +*****************************************************************************/ + +#include "hcrypt.h" + +#include + +typedef struct tag_crysprOpenSSL_EVP_cb +{ + CRYSPR_cb ccb; + /* Add cryptolib specific data here */ +} crysprOpenSSL_EVP_cb; + +int crysprOpenSSL_EVP_Prng(unsigned char* rn, int len) +{ + return (RAND_bytes(rn, len) <= 0 ? -1 : 0); +} + +const EVP_CIPHER* (*Xcipher_fnptr)(void) = EVP_aes_128_ecb; + +const EVP_CIPHER* (*_crysprOpenSSL_EVP_cipher_fnptr[][3])(void) = { + {NULL, NULL, NULL}, + {EVP_aes_128_ecb, EVP_aes_192_ecb, EVP_aes_256_ecb}, + {EVP_aes_128_ctr, EVP_aes_192_ctr, EVP_aes_256_ctr}, +}; + +int crysprOpenSSL_EVP_AES_SetKey( + int cipher_type, /* One of HCRYPT_CTX_MODE_[CLRTXT|AESECB|AESCTR] */ + bool bEncrypt, /* true Enxcrypt key, false: decrypt */ + const unsigned char* kstr, /* key sttring*/ + size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128, AES192, or AES256) */ + CRYSPR_AESCTX* aes_key) /* CRYpto Service PRovider AES Key context */ +{ + const EVP_CIPHER* cipher = NULL; + int idxKlen = (kstr_len / 8) - 2; /* key_len index in cipher_fnptr array in [0,1,2] range */ + + switch (cipher_type) + { + case HCRYPT_CTX_MODE_CLRTXT: + return 0; + case HCRYPT_CTX_MODE_AESECB: + break; + case HCRYPT_CTX_MODE_AESCTR: +#if !CRYSPR_HAS_AESCTR + /* internal implementation of AES-CTR using crypto lib's AES-ECB */ + cipher_type = HCRYPT_CTX_MODE_AESECB; +#endif + break; + default: + HCRYPT_LOG(LOG_ERR, + "invalid cipher type (%d). Expected: [%d..%d]\n", + cipher_type, + HCRYPT_CTX_MODE_AESECB, + HCRYPT_CTX_MODE_AESCTR); + return (-1); + } + switch (kstr_len) + { + case 128 / 8: + case 192 / 8: + case 256 / 8: + break; + default: + HCRYPT_LOG(LOG_ERR, "invalid key length (%d). Expected: 16, 24, 32\n", (int)kstr_len); + return -1; + } + cipher = _crysprOpenSSL_EVP_cipher_fnptr[cipher_type][idxKlen](); + + if (bEncrypt) + { /* Encrypt key */ + if (!EVP_EncryptInit_ex(aes_key, cipher, NULL, kstr, NULL)) + { + HCRYPT_LOG(LOG_ERR, "%s", "EVP_CipherInit_ex(kek) failed\n"); + return (-1); + } + } + else + { /* Decrypt key */ + if (!EVP_DecryptInit_ex(aes_key, cipher, NULL, kstr, NULL)) + { + HCRYPT_LOG(LOG_ERR, "%s", "EVP_CipherInit_ex(kek) failed\n"); + return (-1); + } + } + return (0); +} + +static CRYSPR_cb* crysprOpenSSL_EVP_Open(CRYSPR_methods* cryspr, size_t max_len) +{ + CRYSPR_cb* cryspr_cb = crysprHelper_Open(cryspr, sizeof(*cryspr_cb), max_len); + if (NULL == cryspr_cb) + { + HCRYPT_LOG(LOG_ERR, "crysprFallback_Open(%p, %zd) failed\n", cryspr, max_len); + return (NULL); + } + + cryspr_cb->aes_kek = EVP_CIPHER_CTX_new(); + + cryspr_cb->aes_sek[0] = EVP_CIPHER_CTX_new(); + + cryspr_cb->aes_sek[1] = EVP_CIPHER_CTX_new(); + + return (cryspr_cb); +} + +static int crysprOpenSSL_EVP_Close(CRYSPR_cb* cryspr_cb) +{ + if (NULL != cryspr_cb) + { + EVP_CIPHER_CTX_free(cryspr_cb->aes_sek[0]); + EVP_CIPHER_CTX_free(cryspr_cb->aes_sek[1]); + EVP_CIPHER_CTX_free(cryspr_cb->aes_kek); + } + return (crysprHelper_Close(cryspr_cb)); +} + +#if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) + +int crysprOpenSSL_EVP_AES_EcbCipher(bool bEncrypt, /* true:encrypt, false:decrypt */ + CRYSPR_AESCTX* aes_key, /* CRYpto Service PRovider AES Key context */ + const unsigned char* indata, /* src (clear text if encrypt, cipher text otherwise)*/ + size_t inlen, /* indata length */ + unsigned char* out_txt, /* dst (cipher text if encrypt, clear text otherwise) */ + size_t* outlen_p) /* in/out dst len */ +{ + int nmore = inlen % CRYSPR_AESBLKSZ; /* bytes in last incomplete block */ + int nblk = inlen / CRYSPR_AESBLKSZ + (nmore ? 1 : 0); /* blocks including incomplete */ + size_t outsiz = (outlen_p ? *outlen_p : 0); + int c_len = 0, f_len = 0; + + (void)bEncrypt; // not needed, alreadydefined in context + + if (outsiz % CRYSPR_AESBLKSZ) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EcbCipher() no room for PKCS7 padding"); + return (-1); /* output buf size must be a multiple of AES block size (16) */ + } + if ((outsiz > 16) && ((int)outsiz < (nblk * CRYSPR_AESBLKSZ))) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EcbCipher() no room for PKCS7 padding"); + return (-1); /* output buf size must have room for PKCS7 padding */ + } + /* allows reusing of 'e' for multiple encryption cycles */ + if (!EVP_CipherInit_ex(aes_key, NULL, NULL, NULL, NULL, -1)) + { + HCRYPT_LOG(LOG_ERR, "EVP_CipherInit_ex(%p,NULL,...,-1) failed\n", aes_key); + return -1; + } + if (!EVP_CIPHER_CTX_set_padding(aes_key, 0)) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_CIPHER_CTX_set_padding(%p) failed", aes_key); + return -1; + } + + /* update ciphertext, c_len is filled with the length of ciphertext generated, + * cryptoPtr->cipher_in_len is the size of plain/cipher text in bytes + */ + if (!EVP_CipherUpdate(aes_key, out_txt, &c_len, indata, inlen)) + { + HCRYPT_LOG(LOG_ERR, "EVP_CipherUpdate(%p, out, %d, in, %d) failed\n", aes_key, c_len, inlen); + return -1; + } + + /* update ciphertext with the final remaining bytes */ + /* Useless with pre-padding */ + f_len = 0; + if (0 == EVP_CipherFinal_ex(aes_key, &out_txt[c_len], &f_len)) + { +#if ENABLE_HAICRYPT_LOGGING + char szErrBuf[256]; + HCRYPT_LOG(LOG_ERR, + "EVP_CipherFinal_ex(ctx,&out[%d],%d)) failed: %s\n", + c_len, + f_len, + ERR_error_string(ERR_get_error(), szErrBuf)); +#endif /*ENABLE_HAICRYPT_LOGGING*/ + return -1; + } + if (outlen_p != NULL) *outlen_p = nblk * CRYSPR_AESBLKSZ; + return 0; +} +#endif /* !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) */ + +int crysprOpenSSL_EVP_AES_CtrCipher(bool bEncrypt, /* true:encrypt, false:decrypt */ + CRYSPR_AESCTX* aes_key, /* CRYpto Service PRovider AES Key context */ + unsigned char* iv, /* iv */ + const unsigned char* indata, /* src */ + size_t inlen, /* length */ + unsigned char* out_txt) /* dest */ + +{ + int c_len, f_len; + + (void)bEncrypt; + + /* allows reusing of 'e' for multiple encryption cycles */ + if (!EVP_CipherInit_ex(aes_key, NULL, NULL, NULL, iv, -1)) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_CipherInit_ex() failed"); + return -1; + } + if (!EVP_CIPHER_CTX_set_padding(aes_key, 0)) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_CIPHER_CTX_set_padding() failed"); + return -1; + } + + /* update ciphertext, c_len is filled with the length of ciphertext generated, + * cryptoPtr->cipher_in_len is the size of plain/cipher text in bytes + */ + if (!EVP_CipherUpdate(aes_key, out_txt, &c_len, indata, inlen)) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_CipherUpdate() failed"); + return -1; + } + + /* update ciphertext with the final remaining bytes */ + /* Useless with pre-padding */ + f_len = 0; + if (0 == EVP_CipherFinal_ex(aes_key, &out_txt[c_len], &f_len)) + { +#if ENABLE_HAICRYPT_LOGGING + char szErrBuf[256]; + HCRYPT_LOG(LOG_ERR, + "EVP_CipherFinal_ex(ctx,&out[%d],%d)) failed: %s\n", + c_len, + f_len, + ERR_error_string(ERR_get_error(), szErrBuf)); +#endif /*ENABLE_HAICRYPT_LOGGING*/ + return -1; + } + return 0; +} + +/* + * Password-based Key Derivation Function + */ +int crysprOpenSSL_EVP_KmPbkdf2(CRYSPR_cb* cryspr_cb, + char* passwd, /* passphrase */ + size_t passwd_len, /* passphrase len */ + unsigned char* salt, /* salt */ + size_t salt_len, /* salt_len */ + int itr, /* iterations */ + size_t key_len, /* key_len */ + unsigned char* out) /* derived key */ +{ + (void)cryspr_cb; + int rc = PKCS5_PBKDF2_HMAC_SHA1(passwd, passwd_len, salt, salt_len, itr, key_len, out); + return (rc == 1 ? 0 : -1); +} + +#if CRYSPR_HAS_AESKWRAP +int crysprOpenSSL_EVP_KmWrap(CRYSPR_cb* cryspr_cb, unsigned char* wrap, const unsigned char* sek, unsigned int seklen) +{ + crysprOpenSSL_EVP_cb* aes_data = (crysprOpenSSL_EVP_cb*)cryspr_cb; + EVP_CIPHER_CTX* kek = CRYSPR_GETKEK(cryspr_cb); // key encrypting key + + return (((seklen + HAICRYPT_WRAPKEY_SIGN_SZ) == (unsigned int)AES_wrap_key(kek, NULL, wrap, sek, seklen)) ? 0 : -1); +} + +int crysprOpenSSL_EVP_KmUnwrap(CRYSPR_cb* cryspr_cb, + unsigned char* sek, // Stream encrypting key + const unsigned char* wrap, + unsigned int wraplen) +{ + crysprOpenSSL_EVP_cb* aes_data = (crysprOpenSSL_EVP_cb*)cryspr_cb; + EVP_CIPHER_CTX* kek = CRYSPR_GETKEK(cryspr_cb); // key encrypting key + + return (((wraplen - HAICRYPT_WRAPKEY_SIGN_SZ) == (unsigned int)AES_unwrap_key(kek, NULL, sek, wrap, wraplen)) ? 0 + : -1); +} +#endif /*CRYSPR_HAS_AESKWRAP*/ + +static CRYSPR_methods crysprOpenSSL_EVP_methods; + +CRYSPR_methods* crysprOpenSSL_EVP(void) +{ + if (NULL == crysprOpenSSL_EVP_methods.open) + { + crysprInit(&crysprOpenSSL_EVP_methods); // Default/fallback methods + + crysprOpenSSL_EVP_methods.prng = crysprOpenSSL_EVP_Prng; + //--CryptoLib Primitive API----------------------------------------------- + crysprOpenSSL_EVP_methods.aes_set_key = crysprOpenSSL_EVP_AES_SetKey; +#if CRYSPR_HAS_AESCTR + crysprOpenSSL_EVP_methods.aes_ctr_cipher = crysprOpenSSL_EVP_AES_CtrCipher; +#endif +#if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) + /* AES-ECB only required if cryspr has no AES-CTR and no AES KeyWrap */ + /* OpenSSL has both AESCTR and AESKWRP and the AESECB wrapper is only used + to test the falback methods */ + crysprOpenSSL_EVP_methods.aes_ecb_cipher = crysprOpenSSL_EVP_AES_EcbCipher; +#endif +#if !CRYSPR_HAS_PBKDF2 + crysprOpenSSL_EVP_methods.sha1_msg_digest = NULL; // Required to use eventual default/fallback KmPbkdf2 +#endif + + //--Crypto Session API----------------------------------------- + crysprOpenSSL_EVP_methods.open = crysprOpenSSL_EVP_Open; + crysprOpenSSL_EVP_methods.close = crysprOpenSSL_EVP_Close; + //--Keying material (km) encryption + +#if CRYSPR_HAS_PBKDF2 + crysprOpenSSL_EVP_methods.km_pbkdf2 = crysprOpenSSL_EVP_KmPbkdf2; +#else +#error There is no default/fallback method for PBKDF2 +#endif + // crysprOpenSSL_EVP_methods.km_setkey = +#if CRYSPR_HAS_AESKWRAP + crysprOpenSSL_EVP_methods.km_wrap = crysprOpenSSL_EVP_KmWrap; + crysprOpenSSL_EVP_methods.km_unwrap = crysprOpenSSL_EVP_KmUnwrap; +#endif + + //--Media stream (ms) encryption + // crysprOpenSSL_EVP_methods.ms_setkey = + // crysprOpenSSL_EVP_methods.ms_encrypt = + // crysprOpenSSL_EVP_methods.ms_decrypt = + } + return (&crysprOpenSSL_EVP_methods); +} diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.h b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.h new file mode 100644 index 0000000000..61cb68617b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.h @@ -0,0 +1,63 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2019 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +written by + Haivision Systems Inc. + + 2022-05-19 (jdube) + OpenSSL EVP AES CRYSPR/4SRT (CRYypto Service PRovider for SRT). +*****************************************************************************/ + +#ifndef CRYSPR_OPENSSL_H +#define CRYSPR_OPENSSL_H + +#include /* PKCS5_xxx() */ +#include /* AES_xxx() */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(OPENSSL_IS_BORINGSSL)) +#include /* CRYPTO_xxx() */ +#endif +#include +#include +#include /* OPENSSL_VERSION_NUMBER */ + +/* Define CRYSPR_HAS_AESCTR to 1 if this CRYSPR has AESCTR cipher mode + if not set it 0 to use enable CTR cipher mode implementation using ECB cipher mode + and provide the aes_ecb_cipher method. +*/ +#define CRYSPR_HAS_AESCTR 1 + +/* Define CRYSPR_HAS_AESKWRAP to 1 if this CRYSPR has AES Key Wrap + if not set to 0 to enable default/fallback crysprFallback_AES_WrapKey/crysprFallback_AES_UnwrapKey methods + and provide the aes_ecb_cipher method . +*/ +#if 1 // Force internal AES-WRAP (using AES-ECB) until implemented with EVP (OPENSSL_VERSION_NUMBER < 0x00xxxxxxL) +#define CRYSPR_HAS_AESKWRAP 0 +#else +#define CRYSPR_HAS_AESKWRAP 1 +#endif + +/* Define CRYSPR_HAS_PBKDF2 to 1 if this CRYSPR has SHA1-HMAC Password-based Key Derivaion Function 2 + if not set to 0 to enable not-yet-implemented/fallback crysprFallback.km_pbkdf2 method + and provide the sha1_msg_digest method. +*/ +#define CRYSPR_HAS_PBKDF2 1 /* Define to 1 if CRYSPR has Password-based Key Derivaion Function 2 */ + +/* +#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. +This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK +It is set from hte keystring through CRYSPR_methods.aes_set_key and passed +to CRYSPR_methods.aes_*. +*/ +typedef EVP_CIPHER_CTX CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ + +struct tag_CRYSPR_methods* crysprOpenSSL_EVP(void); + +#endif /* CRYSPR_OPENSSL_H */ diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl.c b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl.c index a5d50ac651..3af907d242 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl.c @@ -13,18 +13,23 @@ written by Haivision Systems Inc. + 2022-05-19 (jdube) + CRYSPR2 adaptation 2019-06-26 (jdube) - OpenSSL CRYSPR/4SRT (CRYypto Service PRovider for SRT). + OpenSSL Direct AES CRYSPR/4SRT (CRYypto Service PRovider for SRT). *****************************************************************************/ #include "hcrypt.h" #include - typedef struct tag_crysprOpenSSL_AES_cb { - CRYSPR_cb ccb; - /* Add cryptolib specific data here */ + CRYSPR_cb ccb; + /* Add cryptolib specific data here */ +#ifdef CRYSPR2 + CRYSPR_AESCTX aes_kek_buf; /* Key Encrypting Key (KEK) */ + CRYSPR_AESCTX aes_sek_buf[2]; /* even/odd Stream Encrypting Key (SEK) */ +#endif } crysprOpenSSL_cb; @@ -34,11 +39,14 @@ int crysprOpenSSL_Prng(unsigned char *rn, int len) } int crysprOpenSSL_AES_SetKey( + int cipher_type, /* One of HCRYPT_CTX_MODE_[CLRTXT|AESECB|AESCTR] */ bool bEncrypt, /* true Enxcrypt key, false: decrypt */ const unsigned char *kstr, /* key sttring*/ size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* CRYpto Service PRovider AES Key context */ { + (void)cipher_type; + if (bEncrypt) { /* Encrypt key */ if (AES_set_encrypt_key(kstr, kstr_len * 8, aes_key)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) failed\n"); @@ -123,7 +131,24 @@ int crysprOpenSSL_AES_CtrCipher( #endif return 0; } +#ifdef CRYSPR2 +static CRYSPR_cb *crysprOpenSSL_Open(CRYSPR_methods *cryspr, size_t max_len) +{ + crysprOpenSSL_cb *aes_data; + + aes_data = (crysprOpenSSL_cb *)crysprHelper_Open(cryspr, sizeof(crysprOpenSSL_cb), max_len); + if (NULL == aes_data) { + HCRYPT_LOG(LOG_ERR, "crysprHelper_Open(%p, %zd, %zd) failed\n", cryspr, sizeof(crysprOpenSSL_cb), max_len); + return(NULL); + } + + aes_data->ccb.aes_kek = &aes_data->aes_kek_buf; //key encrypting key + aes_data->ccb.aes_sek[0] = &aes_data->aes_sek_buf[0]; //stream encrypting key + aes_data->ccb.aes_sek[1] = &aes_data->aes_sek_buf[1]; //stream encrypting key + return(&aes_data->ccb); +} +#endif /* CRYSPR2 */ /* * Password-based Key Derivation Function */ @@ -148,8 +173,7 @@ int crysprOpenSSL_KmWrap(CRYSPR_cb *cryspr_cb, const unsigned char *sek, unsigned int seklen) { - crysprOpenSSL_cb *aes_data = (crysprOpenSSL_cb *)cryspr_cb; - AES_KEY *kek = &aes_data->ccb.aes_kek; //key encrypting key + AES_KEY *kek = CRYSPR_GETKEK(cryspr_cb); //key encrypting key return(((seklen + HAICRYPT_WRAPKEY_SIGN_SZ) == (unsigned int)AES_wrap_key(kek, NULL, wrap, sek, seklen)) ? 0 : -1); } @@ -160,8 +184,7 @@ int crysprOpenSSL_KmUnwrap( const unsigned char *wrap, unsigned int wraplen) { - crysprOpenSSL_cb *aes_data = (crysprOpenSSL_cb *)cryspr_cb; - AES_KEY *kek = &aes_data->ccb.aes_kek; //key encrypting key + AES_KEY *kek = CRYSPR_GETKEK(cryspr_cb); //key encrypting key return(((wraplen - HAICRYPT_WRAPKEY_SIGN_SZ) == (unsigned int)AES_unwrap_key(kek, NULL, sek, wrap, wraplen)) ? 0 : -1); } @@ -192,7 +215,11 @@ CRYSPR_methods *crysprOpenSSL(void) #endif //--Crypto Session API----------------------------------------- +#ifdef CRYSPR2 + crysprOpenSSL_methods.open = crysprOpenSSL_Open; +#else // crysprOpenSSL_methods.open = +#endif // crysprOpenSSL_methods.close = //--Keying material (km) encryption diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c index e59cbfc764..e6aa40219a 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c @@ -31,11 +31,13 @@ int crysprStub_Prng(unsigned char *rn, int len) } int crysprStub_AES_SetKey( + int cipher_type, /* One of HCRYPT_CTX_MODE_[CLRTXT|AESECB|AESCTR|AESGDM] */ bool bEncrypt, /* true Enxcrypt key, false: decrypt */ const unsigned char *kstr, /* key sttring*/ size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ CRYSPR_AESCTX *aes_key) /* Cryptolib Specific AES key context */ { + (void)cipher_type; (void)bEncrypt; (void)kstr; (void)kstr_len; @@ -122,10 +124,10 @@ int crysprStub_KmPbkdf2( static int crysprFallback_KmSetKey(CRYSPR_cb *cryspr_cb, bool bWrap, const unsigned char *kek, size_t kek_len) { - CRYSPR_AESCTX *aes_kek = &cryspr_cb->aes_kek; + CRYSPR_AESCTX *aes_kek = CRYSPR_GETKEK(cryspr_cb); - if (cryspr_cb->cryspr->aes_set_key(bWrap, kek, kek_len, aes_kek)) { - HCRYPT_LOG(LOG_ERR, "AES_set_%s_key(kek) failed\n", bWrap? "encrypt": "decrypt"); + if (cryspr_cb->cryspr->aes_set_key(HCRYPT_CTX_MODE_AESECB, bWrap, kek, kek_len, aes_kek)) { + HCRYPT_LOG(LOG_ERR, "aes_set_%s_key(kek) failed\n", bWrap? "encrypt": "decrypt"); return(-1); } return(0); @@ -163,7 +165,9 @@ int crysprFallback_AES_WrapKey(CRYSPR_cb *cryspr_cb, memcpy(B + 8, R, 8); { size_t outlen = 16; - cryspr_cb->cryspr->aes_ecb_cipher(true, &cryspr_cb->aes_kek, B, 16, B, &outlen); + CRYSPR_AESCTX *aes_kek = CRYSPR_GETKEK(cryspr_cb); + + cryspr_cb->cryspr->aes_ecb_cipher(true, aes_kek, B, 16, B, &outlen); } A[7] ^= (unsigned char)(t & 0xff); if (t > 0xff) @@ -211,7 +215,9 @@ int crysprFallback_AES_UnwrapKey(CRYSPR_cb *cryspr_cb, memcpy(B + 8, R, 8); { size_t outlen = 16; - cryspr_cb->cryspr->aes_ecb_cipher(false, &cryspr_cb->aes_kek, B, 16, B, &outlen); + CRYSPR_AESCTX *aes_kek = CRYSPR_GETKEK(cryspr_cb); + + cryspr_cb->cryspr->aes_ecb_cipher(false, aes_kek, B, 16, B, &outlen); } memcpy(R, B + 8, 8); } @@ -237,20 +243,23 @@ static unsigned char *_crysprFallback_GetOutbuf(CRYSPR_cb *cryspr_cb, size_t pfx return(out_buf); } -static CRYSPR_cb *crysprFallback_Open(CRYSPR_methods *cryspr, size_t max_len) +CRYSPR_cb *crysprHelper_Open(CRYSPR_methods *cryspr, size_t cb_len, size_t max_len) { CRYSPR_cb *cryspr_cb; unsigned char *membuf; size_t memsiz, padded_len = hcryptMsg_PaddedLen(max_len, 128/8); - HCRYPT_LOG(LOG_DEBUG, "%s", "Using OpenSSL AES\n"); - - memsiz = sizeof(*cryspr_cb) + (CRYSPR_OUTMSGMAX * padded_len); + if(cb_len < sizeof(*cryspr_cb)) { + HCRYPT_LOG(LOG_ERR, "crysprHelper_Open() cb_len too small (%zd < %zd)n", + cb_len, sizeof(*cryspr_cb)); + return(NULL); + } + memsiz = cb_len + (CRYSPR_OUTMSGMAX * padded_len); #if !CRYSPR_HAS_AESCTR memsiz += HCRYPT_CTR_STREAM_SZ; #endif /* !CRYSPR_HAS_AESCTR */ - cryspr_cb = malloc(memsiz); + cryspr_cb = calloc(1, memsiz); if (NULL == cryspr_cb) { HCRYPT_LOG(LOG_ERR, "malloc(%zd) failed\n", memsiz); return(NULL); @@ -258,6 +267,9 @@ static CRYSPR_cb *crysprFallback_Open(CRYSPR_methods *cryspr, size_t max_len) membuf = (unsigned char *)cryspr_cb; membuf += sizeof(*cryspr_cb); + /*reserve cryspr's private data that caller will initialize */ + membuf += (cb_len-sizeof(CRYSPR_cb)); + #if !CRYSPR_HAS_AESCTR cryspr_cb->ctr_stream = membuf; membuf += HCRYPT_CTR_STREAM_SZ; @@ -275,26 +287,37 @@ static CRYSPR_cb *crysprFallback_Open(CRYSPR_methods *cryspr, size_t max_len) return(cryspr_cb); } +int crysprHelper_Close(CRYSPR_cb *cryspr_cb) +{ + free(cryspr_cb); + return(0); +} + +static CRYSPR_cb *crysprFallback_Open(CRYSPR_methods *cryspr, size_t max_len) +{ + CRYSPR_cb *cryspr_cb; + + cryspr_cb = crysprHelper_Open(cryspr, sizeof(CRYSPR_cb), max_len); + return(cryspr_cb); +} + static int crysprFallback_Close(CRYSPR_cb *cryspr_cb) { - if (NULL != cryspr_cb) { - free(cryspr_cb); - } - return(0); + return(crysprHelper_Close(cryspr_cb)); } static int crysprFallback_MsSetKey(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, const unsigned char *key, size_t key_len) { - CRYSPR_AESCTX *aes_sek = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; /* Ctx tells if it's for odd or even key */ + CRYSPR_AESCTX *aes_sek = CRYSPR_GETSEK(cryspr_cb, hcryptCtx_GetKeyIndex(ctx)); /* Ctx tells if it's for odd or even key */ if ((ctx->flags & HCRYPT_CTX_F_ENCRYPT) /* Encrypt key */ || (ctx->mode == HCRYPT_CTX_MODE_AESCTR)) { /* CTR mode decrypts using encryption methods */ - if (cryspr_cb->cryspr->aes_set_key(true, key, key_len, aes_sek)) { + if (cryspr_cb->cryspr->aes_set_key(HCRYPT_CTX_MODE_AESCTR, true, key, key_len, aes_sek)) { HCRYPT_LOG(LOG_ERR, "%s", "CRYSPR->set_encrypt_key(sek) failed\n"); return(-1); } } else { /* Decrypt key */ - if (cryspr_cb->cryspr->aes_set_key(false, key, key_len, aes_sek)) { + if (cryspr_cb->cryspr->aes_set_key(HCRYPT_CTX_MODE_AESCTR, false, key, key_len, aes_sek)) { HCRYPT_LOG(LOG_ERR, "%s", "CRYSPR->set_decrypt_key(sek) failed\n"); return(-1); } @@ -376,135 +399,78 @@ static int crysprFallback_MsEncrypt( /* Get buffer room from the internal circular output buffer */ out_msg = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, in_data[0].len); - if (NULL != out_msg) { - switch(ctx->mode) { - case HCRYPT_CTX_MODE_AESCTR: /* Counter mode */ - { -#if CRYSPR_HAS_AESCTR - /* Get current key (odd|even) from context */ - CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; - unsigned char iv[CRYSPR_AESBLKSZ]; - - /* Get input packet index (in network order) */ - hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); - - /* - * Compute the Initial Vector - * IV (128-bit): - * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | 0s | pki | ctr | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * XOR - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | nonce + - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * - * pki (32-bit): packet index - * ctr (16-bit): block counter - * nonce (112-bit): number used once (salt) - */ - hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); + if (NULL == out_msg) { + /* input data too big */ + return(-1); + } - cryspr_cb->cryspr->aes_ctr_cipher(true, aes_key, iv, in_data[0].payload, in_data[0].len, - &out_msg[pfx_len]); -#else /*CRYSPR_HAS_AESCTR*/ - /* Get current key (odd|even) from context */ - CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; - unsigned char iv[CRYSPR_AESBLKSZ]; - int iret = 0; + switch(ctx->mode) { + case HCRYPT_CTX_MODE_AESCTR: /* Counter mode */ + { + /* Get current key (odd|even) from context */ + CRYSPR_AESCTX *aes_key = CRYSPR_GETSEK(cryspr_cb, hcryptCtx_GetKeyIndex(ctx)); /* Ctx tells if it's for odd or even key */ - /* Get input packet index (in network order) */ - hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); + unsigned char iv[CRYSPR_AESBLKSZ]; - /* - * Compute the Initial Vector - * IV (128-bit): - * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | 0s | pki | ctr | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * XOR - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | nonce + - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * - * pki (32-bit): packet index - * ctr (16-bit): block counter - * nonce (112-bit): number used once (salt) - */ - hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); + /* Get input packet index (in network order) */ + hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); - /* Create CtrStream. May be longer than in_len (next cryspr block size boundary) */ - iret = _crysprFallback_AES_SetCtrStream(cryspr_cb, ctx, in_data[0].len, iv); - if (iret) { - return(iret); - } - /* Reserve output buffer for cryspr */ - out_msg = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, cryspr_cb->ctr_stream_len); + /* + * Compute the Initial Vector + * IV (128-bit): + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0s | pki | ctr | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * XOR + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | nonce + + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * pki (32-bit): packet index + * ctr (16-bit): block counter + * nonce (112-bit): number used once (salt) + */ + hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); - /* Create KeyStream (encrypt CtrStream) */ - iret = cryspr_cb->cryspr->aes_ecb_cipher(true, aes_key, - cryspr_cb->ctr_stream, cryspr_cb->ctr_stream_len, - &out_msg[pfx_len], &out_len); - if (iret) { - HCRYPT_LOG(LOG_ERR, "%s", "hcOpenSSL_AES_ecb_cipher(encrypt, failed\n"); - return(iret); - } -#endif/*CRYSPR_HAS_AESCTR*/ - /* Prepend packet prefix (clear text) in output buffer */ - memcpy(out_msg, in_data[0].pfx, pfx_len); - /* CTR mode output length is same as input, no padding */ - out_len = in_data[0].len; - break; +#if CRYSPR_HAS_AESCTR + cryspr_cb->cryspr->aes_ctr_cipher(true, aes_key, iv, in_data[0].payload, in_data[0].len, + &out_msg[pfx_len]); +#else /*CRYSPR_HAS_AESCTR*/ + /* Create CtrStream. May be longer than in_len (next cryspr block size boundary) */ + int iret = _crysprFallback_AES_SetCtrStream(cryspr_cb, ctx, in_data[0].len, iv); + if (iret) { + return(iret); } - case HCRYPT_CTX_MODE_CLRTXT: /* Clear text mode (transparent mode for tests) */ - memcpy(&out_msg[pfx_len], in_data[0].payload, in_data[0].len); - memcpy(out_msg, in_data[0].pfx, pfx_len); - out_len = in_data[0].len; - break; - default: - /* Unsupported cipher mode */ - return(-1); + /* Reserve output buffer for cryspr */ + out_msg = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, cryspr_cb->ctr_stream_len); + + /* Create KeyStream (encrypt CtrStream) */ + iret = cryspr_cb->cryspr->aes_ecb_cipher(true, aes_key, + cryspr_cb->ctr_stream, cryspr_cb->ctr_stream_len, + &out_msg[pfx_len], &out_len); + if (iret) { + HCRYPT_LOG(LOG_ERR, "%s", "hcOpenSSL_AES_ecb_cipher(encrypt, failed\n"); + return(iret); + } +#endif/*CRYSPR_HAS_AESCTR*/ + /* Prepend packet prefix (clear text) in output buffer */ + memcpy(out_msg, in_data[0].pfx, pfx_len); + /* CTR mode output length is same as input, no padding */ + out_len = in_data[0].len; + break; } - } else { - /* input data too big */ - return(-1); + case HCRYPT_CTX_MODE_CLRTXT: /* Clear text mode (transparent mode for tests) */ + memcpy(&out_msg[pfx_len], in_data[0].payload, in_data[0].len); + memcpy(out_msg, in_data[0].pfx, pfx_len); + out_len = in_data[0].len; + break; + default: + /* Unsupported cipher mode */ + return(-1); } - if (out_len > 0) { - /* Encrypted messages have been produced */ - if (NULL == out_p) { - /* - * Application did not provided output buffer, - * so copy encrypted message back in input buffer - */ - memcpy(in_data[0].pfx, out_msg, pfx_len); -#if !CRYSPR_HAS_AESCTR - if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { - /* XOR KeyStream with input text directly in input buffer */ - hcrypt_XorStream(in_data[0].payload, &out_msg[pfx_len], out_len); - }else{ - /* Copy output data back in input buffer */ - memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); - } -#else /* CRYSPR_HAS_AESCTR */ - /* Copy output data back in input buffer */ - memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); -#endif /* CRYSPR_HAS_AESCTR */ - } else { - /* Copy header in output buffer if needed */ - if (pfx_len > 0) memcpy(out_msg, in_data[0].pfx, pfx_len); -#if !CRYSPR_HAS_AESCTR - if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { - hcrypt_XorStream(&out_msg[pfx_len], in_data[0].payload, out_len); - } -#endif /* CRYSPR_HAS_AESCTR */ - out_p[0] = out_msg; - out_len_p[0] = pfx_len + out_len; - *nbout_p = 1; - } - } else { + if (out_len <= 0) { /* * Nothing out * This is not an error for implementations using deferred/async processing @@ -514,6 +480,39 @@ static int crysprFallback_MsEncrypt( if (nbout_p != NULL) *nbout_p = 0; return(-1); } + + /* Encrypted messages have been produced */ + if (NULL == out_p) { + /* + * Application did not provided output buffer, + * so copy encrypted message back in input buffer + */ + memcpy(in_data[0].pfx, out_msg, pfx_len); +#if !CRYSPR_HAS_AESCTR + if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { + /* XOR KeyStream with input text directly in input buffer */ + hcrypt_XorStream(in_data[0].payload, &out_msg[pfx_len], out_len); + }else{ + /* Copy output data back in input buffer */ + memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); + } +#else /* CRYSPR_HAS_AESCTR */ + /* Copy output data back in input buffer */ + memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); +#endif /* CRYSPR_HAS_AESCTR */ + } else { + /* Copy header in output buffer if needed */ + if (pfx_len > 0) memcpy(out_msg, in_data[0].pfx, pfx_len); +#if !CRYSPR_HAS_AESCTR + if (ctx->mode == HCRYPT_CTX_MODE_AESCTR) { + hcrypt_XorStream(&out_msg[pfx_len], in_data[0].payload, out_len); + } +#endif /* CRYSPR_HAS_AESCTR */ + out_p[0] = out_msg; + out_len_p[0] = pfx_len + out_len; + *nbout_p = 1; + } + return(0); } @@ -537,7 +536,7 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, { #if CRYSPR_HAS_AESCTR /* Get current key (odd|even) from context */ - CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; + CRYSPR_AESCTX *aes_key = CRYSPR_GETSEK(cryspr_cb, hcryptCtx_GetKeyIndex(ctx)); unsigned char iv[CRYSPR_AESBLKSZ]; /* Get input packet index (in network order) */ @@ -566,7 +565,7 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, out_len = in_data[0].len; #else /*CRYSPR_HAS_AESCTR*/ /* Get current key (odd|even) from context */ - CRYSPR_AESCTX *aes_key = &cryspr_cb->aes_sek[hcryptCtx_GetKeyIndex(ctx)]; + CRYSPR_AESCTX *aes_key = CRYSPR_GETSEK(cryspr_cb, hcryptCtx_GetKeyIndex(ctx)); unsigned char iv[CRYSPR_AESBLKSZ]; int iret = 0; diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h index 45a300feff..d515c3a4be 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h @@ -39,8 +39,17 @@ extern "C" { #include "cryspr-config.h" typedef struct tag_CRYSPR_cb { +#ifdef CRYSPR2 + CRYSPR_AESCTX *aes_kek; /* Key Encrypting Key (KEK) */ + CRYSPR_AESCTX *aes_sek[2]; /* even/odd Stream Encrypting Key (SEK) */ +#define CRYSPR_GETKEK(cb) ((cb)->aes_kek) +#define CRYSPR_GETSEK(cb,kk) ((cb)->aes_sek[kk]) +#else /*CRYSPR2*/ CRYSPR_AESCTX aes_kek; /* Key Encrypting Key (KEK) */ CRYSPR_AESCTX aes_sek[2]; /* even/odd Stream Encrypting Key (SEK) */ +#define CRYSPR_GETKEK(cb) (&((cb)->aes_kek)) +#define CRYSPR_GETSEK(cb,kk) (&((cb)->aes_sek[kk])) +#endif /*CRYSPR2*/ struct tag_CRYSPR_methods *cryspr; @@ -69,6 +78,7 @@ typedef struct tag_CRYSPR_methods { int rn_len); int (*aes_set_key)( + int cipher_type, /* One of HCRYPT_CTX_MODE_[CLRTXT|AESECB|AESCTR|AESGDM] */ bool bEncrypt, /* true Enxcrypt key, false: decrypt */ const unsigned char *kstr,/* key string*/ size_t kstr_len, /* kstr len in bytes (16, 24, or 32 bytes (for AES128,AES192, or AES256) */ @@ -194,6 +204,9 @@ typedef struct tag_CRYSPR_methods { } CRYSPR_methods; +CRYSPR_cb *crysprHelper_Open(CRYSPR_methods *cryspr, size_t cb_len, size_t max_len); +int crysprHelper_Close(CRYSPR_cb *cryspr_cb); + CRYSPR_methods *crysprInit(CRYSPR_methods *cryspr); #ifdef __cplusplus diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-gnutls.maf b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-gnutls.maf index 8a38714473..b0f78346b3 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-gnutls.maf +++ b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-gnutls.maf @@ -23,5 +23,4 @@ hcrypt_rx.c hcrypt_sa.c hcrypt_tx.c hcrypt_xpt_srt.c -hcrypt_xpt_sta.c haicrypt_log.cpp diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-mbedtls.maf b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-mbedtls.maf index 835c1f535a..ac6d383ca7 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-mbedtls.maf +++ b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-mbedtls.maf @@ -21,5 +21,4 @@ hcrypt_rx.c hcrypt_sa.c hcrypt_tx.c hcrypt_xpt_srt.c -hcrypt_xpt_sta.c haicrypt_log.cpp diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl-evp.maf b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl-evp.maf new file mode 100644 index 0000000000..9548dc7964 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl-evp.maf @@ -0,0 +1,24 @@ +# HaiCrypt library contents + +PUBLIC HEADERS +haicrypt.h +hcrypt_ctx.h +hcrypt_msg.h + +PRIVATE HEADERS +hcrypt.h +cryspr.h +cryspr-openssl-evp.h +haicrypt_log.h + +SOURCES +cryspr.c +cryspr-openssl-evp.c +hcrypt.c +hcrypt_ctx_rx.c +hcrypt_ctx_tx.c +hcrypt_rx.c +hcrypt_sa.c +hcrypt_tx.c +hcrypt_xpt_srt.c +haicrypt_log.cpp diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl.maf b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl.maf index e3ef0a1aaf..0179042c49 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl.maf +++ b/trunk/3rdparty/srt-1-fit/haicrypt/filelist-openssl.maf @@ -21,5 +21,4 @@ hcrypt_rx.c hcrypt_sa.c hcrypt_tx.c hcrypt_xpt_srt.c -hcrypt_xpt_sta.c haicrypt_log.cpp diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h index 79763b7af8..04eb18b767 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h @@ -27,25 +27,9 @@ written by #ifdef __cplusplus extern "C" { #endif - -// setup exports -#if defined _WIN32 && !defined __MINGW__ -#ifdef HAICRYPT_DYNAMIC -#ifdef HAICRYPT_EXPORTS -#define HAICRYPT_API __declspec(dllexport) -#else -#define HAICRYPT_API __declspec(dllimport) -#endif -#else -#define HAICRYPT_API -#endif -#else -#define HAICRYPT_API -#endif - typedef void *HaiCrypt_Cryspr; -HAICRYPT_API HaiCrypt_Cryspr HaiCryptCryspr_Get_Instance (void); /* Return a default cryspr instance */ +HaiCrypt_Cryspr HaiCryptCryspr_Get_Instance (void); /* Return a default cryspr instance */ #define HAICRYPT_CIPHER_BLK_SZ 16 /* AES Block Size */ @@ -108,21 +92,21 @@ typedef struct hcrypt_Session_str* HaiCrypt_Handle; -HAICRYPT_API int HaiCrypt_SetLogLevel(int level, int logfa); +int HaiCrypt_SetLogLevel(int level, int logfa); -HAICRYPT_API int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); -HAICRYPT_API int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handle *phhc); -HAICRYPT_API int HaiCrypt_Close(HaiCrypt_Handle hhc); -HAICRYPT_API int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_p); -HAICRYPT_API int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, - void *out_p[], size_t out_len_p[], int maxout); -HAICRYPT_API int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, - void *out_p[], size_t out_len_p[], int maxout); +int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); +int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handle *phhc); +int HaiCrypt_Close(HaiCrypt_Handle hhc); +int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_p); +int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, + void *out_p[], size_t out_len_p[], int maxout); +int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, + void *out_p[], size_t out_len_p[], int maxout); -HAICRYPT_API int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc); -HAICRYPT_API int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout); -HAICRYPT_API int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); -HAICRYPT_API int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); +int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc); +int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout); +int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); +int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); /* Status values */ diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.cpp b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.cpp index 85a25b1fd8..2d5353da39 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.cpp +++ b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.cpp @@ -10,6 +10,8 @@ #if ENABLE_HAICRYPT_LOGGING +#include "haicrypt_log.h" + #include "hcrypt.h" #include "haicrypt.h" #include "../srtcore/srt.h" @@ -18,7 +20,7 @@ extern srt_logging::LogConfig srt_logger_config; // LOGFA symbol defined in srt.h -srt_logging::Logger hclog(SRT_LOGFA_HAICRYPT, srt_logger_config, "SRT.k"); +srt_logging::Logger hclog(SRT_LOGFA_HAICRYPT, srt_logger_config, "SRT.hc"); extern "C" { @@ -97,12 +99,7 @@ void HaiCrypt_DumpConfig(const HaiCrypt_Cfg* cfg) LOGC(hclog.Debug, log << "CFG DUMP: flags=" << cfg_flags.str() << " xport=" << (cfg->xport == HAICRYPT_XPT_SRT ? "SRT" : "INVALID") << " cipher=" - << (cfg->cipher == HaiCryptCipher_OpenSSL_EVP_CTR() ? "OSSL-EVP-CTR": - cfg->cipher == HaiCryptCipher_OpenSSL_AES() ? "OSSL-AES": - // This below is used as the only one when Nettle is used. When OpenSSL - // is used, one of the above will trigger, and the one below will then never trigger. - cfg->cipher == HaiCryptCipher_Get_Instance() ? "Nettle-AES": - "UNKNOWN") + << CRYSPR_IMPL_DESC << " key_len=" << cfg->key_len << " data_max_len=" << cfg->data_max_len); diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.h b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.h index 665bc70cae..9655f722b6 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.h @@ -1,5 +1,5 @@ -#ifndef IMC__HAICRYPT_LOG_H -#define IMC__HAICRYPT_LOG_H +#ifndef INC_SRT_HAICRYPT_LOG_H +#define INC_SRT_HAICRYPT_LOG_H #ifdef __cplusplus extern "C" { diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c index 00f777b2a5..a94bd9af75 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c @@ -74,10 +74,6 @@ static hcrypt_Session* sHaiCrypt_PrepareHandle(const HaiCrypt_Cfg* cfg, HaiCrypt /* Setup transport packet info */ switch (cfg->xport) { - case HAICRYPT_XPT_STANDALONE: - crypto->se = HCRYPT_SE_TSUDP; - crypto->msg_info = hcryptMsg_STA_MsgInfo(); - break; case HAICRYPT_XPT_SRT: crypto->se = HCRYPT_SE_TSSRT; crypto->msg_info = hcryptMsg_SRT_MsgInfo(); diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h index f2193f5866..c4fbf87f91 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h @@ -24,8 +24,8 @@ written by MINGW-W64 Build. *****************************************************************************/ -#ifndef HCRYPT_H -#define HCRYPT_H +#ifndef INC_SRT_HCRYPT_H +#define INC_SRT_HCRYPT_H #include diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_tx.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_tx.c index 77560a543d..e518f51386 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_tx.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_tx.c @@ -74,9 +74,10 @@ int hcryptCtx_Tx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx) HCRYPT_PRINTKEY(ctx->sek, ctx->sek_len, "sek"); /* Regenerate KEK if Password-based (uses newly generated salt and sek_len) */ - if ((0 < ctx->cfg.pwd_len) - && (0 > (iret = hcryptCtx_GenSecret(crypto, ctx)))) { - return(iret); + if (0 < ctx->cfg.pwd_len) { + iret = hcryptCtx_GenSecret(crypto, ctx); + if (iret < 0) + return(iret); } /* Assemble the new Keying Material message */ @@ -106,22 +107,22 @@ int hcryptCtx_Tx_CloneKey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const hcrypt_ ASSERT(HCRYPT_CTX_S_SARDY <= ctx->status); - const hcrypt_Ctx* ctxSrc = cryptoSrc->ctx; - if (!ctxSrc) - { - /* Probbly the context is not yet completely initialized, so - * use blindly the first context from the pair - */ - ctxSrc = &cryptoSrc->ctx_pair[0]; - } + const hcrypt_Ctx* ctxSrc = cryptoSrc->ctx; + if (!ctxSrc) + { + /* Probbly the context is not yet completely initialized, so + * use blindly the first context from the pair + */ + ctxSrc = &cryptoSrc->ctx_pair[0]; + } - /* Copy SALT (instead of generating) */ - ctx->salt_len = ctxSrc->salt_len; - memcpy(ctx->salt, ctxSrc->salt, ctx->salt_len); + /* Copy SALT (instead of generating) */ + ctx->salt_len = ctxSrc->salt_len; + memcpy(ctx->salt, ctxSrc->salt, ctx->salt_len); - /* Copy SEK */ - ctx->sek_len = ctxSrc->sek_len; - memcpy(ctx->sek, ctxSrc->sek, ctx->sek_len); + /* Copy SEK */ + ctx->sek_len = ctxSrc->sek_len; + memcpy(ctx->sek, ctxSrc->sek, ctx->sek_len); /* Set SEK in cryspr */ if (crypto->cryspr->ms_setkey(crypto->cryspr_cb, ctx, ctx->sek, ctx->sek_len)) { @@ -133,11 +134,12 @@ int hcryptCtx_Tx_CloneKey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const hcrypt_ HCRYPT_PRINTKEY(ctx->sek, ctx->sek_len, "sek"); /* Regenerate KEK if Password-based (uses newly generated salt and sek_len) */ - /* (note for CloneKey imp: it's expected that the same passphrase-salt pair - shall generate the same KEK. GenSecret also prints the KEK */ - if ((0 < ctx->cfg.pwd_len) - && (0 > (iret = hcryptCtx_GenSecret(crypto, ctx)))) { - return(iret); + /* (note for CloneKey imp: it's expected that the same passphrase-salt pair + shall generate the same KEK. GenSecret also prints the KEK */ + if (0 < ctx->cfg.pwd_len) { + iret = hcryptCtx_GenSecret(crypto, ctx); + if (iret < 0) + return(iret); } /* Assemble the new Keying Material message */ @@ -336,8 +338,8 @@ int hcryptCtx_Tx_ManageKM(hcrypt_Session *crypto) ASSERT(NULL != ctx); HCRYPT_LOG(LOG_DEBUG, "KM[%d] KEY STATUS: pkt_cnt=%u against ref.rate=%u and pre.announce=%u\n", - (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2, - ctx->pkt_cnt, crypto->km.refresh_rate, crypto->km.pre_announce); + (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2, + ctx->pkt_cnt, crypto->km.refresh_rate, crypto->km.pre_announce); if ((ctx->pkt_cnt > crypto->km.refresh_rate) || (ctx->pkt_cnt == 0)) { //rolled over diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c index 995334d81a..6884c76b13 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c @@ -37,7 +37,7 @@ int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_ ctx->cfg.pwd_len = 0; /* KEK: Key Encrypting Key */ if (0 > (iret = crypto->cryspr->km_setkey(crypto->cryspr_cb, - (HCRYPT_CTX_F_ENCRYPT & ctx->flags ? true : false), + ((HCRYPT_CTX_F_ENCRYPT & ctx->flags) ? true : false), secret->str, secret->len))) { HCRYPT_LOG(LOG_ERR, "km_setkey(pdkek[%zd]) failed (rc=%d)\n", secret->len, iret); return(-1); @@ -87,7 +87,7 @@ int hcryptCtx_GenSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx) HCRYPT_PRINTKEY(kek, kek_len, "kek"); /* KEK: Key Encrypting Key */ - if (0 > (iret = crypto->cryspr->km_setkey(crypto->cryspr_cb, (HCRYPT_CTX_F_ENCRYPT & ctx->flags ? true : false), kek, kek_len))) { + if (0 > (iret = crypto->cryspr->km_setkey(crypto->cryspr_cb, ((HCRYPT_CTX_F_ENCRYPT & ctx->flags) ? true : false), kek, kek_len))) { HCRYPT_LOG(LOG_ERR, "km_setkey(pdkek[%zd]) failed (rc=%d)\n", kek_len, iret); return(-1); } diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c index d1ceab26cc..85fc798cbe 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c @@ -55,11 +55,11 @@ int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_ int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx = NULL; + hcrypt_Ctx *ctx = crypto->ctx; int nbout = 0; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx)) + || (NULL == ctx) || (NULL == out_p) || (NULL == out_len_p)) { HCRYPT_LOG(LOG_ERR, "ManageKeys: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); @@ -69,7 +69,8 @@ int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[ /* Manage Key Material (refresh, announce, decommission) */ hcryptCtx_Tx_ManageKM(crypto); - if (NULL == (ctx = crypto->ctx)) { + ctx = crypto->ctx; + if (NULL == ctx) { HCRYPT_LOG(LOG_ERR, "%s", "crypto context not defined\n"); return(-1); } @@ -82,10 +83,10 @@ int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[ int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx = NULL; + hcrypt_Ctx *ctx = crypto->ctx; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx))){ + || (NULL == ctx)){ HCRYPT_LOG(LOG_ERR, "GetKeyFlags: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } @@ -96,11 +97,11 @@ int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *in_pfx, unsigned char *in_data, size_t in_len) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx = NULL; + hcrypt_Ctx *ctx = crypto->ctx; int nbout = 0; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx))){ + || (NULL == ctx)){ HCRYPT_LOG(LOG_ERR, "Tx_Data: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } @@ -129,11 +130,11 @@ int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx = NULL; + hcrypt_Ctx *ctx = crypto->ctx; int nb, nbout = 0; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx)) + || (NULL == ctx) || (NULL == out_p) || (NULL == out_len_p)) { HCRYPT_LOG(LOG_ERR, "Tx_Process: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); @@ -143,7 +144,8 @@ int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, /* Manage Key Material (refresh, announce, decommission) */ hcryptCtx_Tx_ManageKM(crypto); - if (NULL == (ctx = crypto->ctx)) { + ctx = crypto->ctx; + if (NULL == ctx) { HCRYPT_LOG(LOG_ERR, "%s", "crypto context not defined\n"); return(-1); } diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_xpt_sta.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_xpt_sta.c deleted file mode 100644 index cc2651be03..0000000000 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_xpt_sta.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * SRT - Secure, Reliable, Transport - * Copyright (c) 2018 Haivision Systems Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - */ - - -/***************************************************************************** -written by - Haivision Systems Inc. - - 2011-06-23 (jdube) - HaiCrypt initial implementation. - 2014-03-11 (jdube) - Adaptation for SRT. -*****************************************************************************/ - -#include /* memset, memcpy */ -#include /* time() */ -#ifdef _WIN32 - #include - #include -#else - #include /* htonl, ntohl */ -#endif -#include "hcrypt.h" - -/* - * HaiCrypt Standalone Transport Media Stream (MS) Data Msg Prefix: - * Cache maintained in network order - * - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ - * 0x00 |0|Vers | PT | Sign | resv |KF | - * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ - * 0x04 | pki | - * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ - * | payload... | - */ - -/* - * HaiCrypt Standalone Transport Keying Material (KM) Msg (no prefix, use KM Msg directly): - * Cache maintained in network order - * - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ - * 0x00 |0|Vers | PT | Sign | resv | - * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ - * ... . - */ - -#define HCRYPT_MSG_STA_HDR_SZ 4 -#define HCRYPT_MSG_STA_PKI_SZ 4 -#define HCRYPT_MSG_STA_PFX_SZ (HCRYPT_MSG_STA_HDR_SZ + HCRYPT_MSG_STA_PKI_SZ) - -#define HCRYPT_MSG_STA_OFS_VERSION HCRYPT_MSG_KM_OFS_VERSION -#define HCRYPT_MSG_STA_OFS_PT HCRYPT_MSG_KM_OFS_PT -#define HCRYPT_MSG_STA_OFS_SIGN HCRYPT_MSG_KM_OFS_SIGN -#define HCRYPT_MSG_STA_OFS_KFLGS HCRYPT_MSG_KM_OFS_KFLGS - -#define HCRYPT_MSG_STA_OFS_PKI HCRYPT_MSG_STA_HDR_SZ - -#define hcryptMsg_STA_GetVersion(msg) (((msg)[HCRYPT_MSG_STA_OFS_VERSION]>>4)& 0xF) -#define hcryptMsg_STA_GetPktType(msg) (((msg)[HCRYPT_MSG_STA_OFS_PT]) & 0xF) -#define hcryptMsg_STA_GetSign(msg) (((msg)[HCRYPT_MSG_STA_OFS_SIGN]<<8) | (msg)[HCRYPT_MSG_STA_OFS_SIGN+1]) - -static hcrypt_MsgInfo _hcMsg_STA_MsgInfo; - -static unsigned hcryptMsg_STA_GetKeyFlags(unsigned char *msg) -{ - return((unsigned)(msg[HCRYPT_MSG_STA_OFS_KFLGS] & HCRYPT_MSG_F_xSEK)); -} - -static hcrypt_Pki hcryptMsg_STA_GetPki(unsigned char *msg, int nwkorder) -{ - hcrypt_Pki pki; - memcpy(&pki, &msg[HCRYPT_MSG_STA_OFS_PKI], sizeof(pki)); //header is in host order - return (nwkorder ? pki : ntohl(pki)); -} - -static void hcryptMsg_STA_SetPki(unsigned char *msg, hcrypt_Pki pki) -{ - hcrypt_Pki nwk_pki = htonl(pki); - memcpy(&msg[HCRYPT_MSG_STA_OFS_PKI], &nwk_pki, sizeof(nwk_pki)); //header is in host order -} - -static void hcryptMsg_STA_ResetCache(unsigned char *pfx_cache, unsigned pkt_type, unsigned kflgs) -{ - pfx_cache[HCRYPT_MSG_STA_OFS_VERSION] = (unsigned char)((HCRYPT_MSG_VERSION << 4) | pkt_type); // version || PT - pfx_cache[HCRYPT_MSG_STA_OFS_SIGN] = (unsigned char)((HCRYPT_MSG_SIGN >> 8) & 0xFF); // Haivision PnP Mfr ID - pfx_cache[HCRYPT_MSG_STA_OFS_SIGN+1] = (unsigned char)(HCRYPT_MSG_SIGN & 0xFF); - - switch(pkt_type) { - case HCRYPT_MSG_PT_MS: - pfx_cache[HCRYPT_MSG_STA_OFS_KFLGS] = (unsigned char)kflgs; //HCRYPT_MSG_F_xxx - hcryptMsg_STA_SetPki(pfx_cache, 0); - break; - case HCRYPT_MSG_PT_KM: - pfx_cache[HCRYPT_MSG_KM_OFS_KFLGS] = (unsigned char)kflgs; //HCRYPT_MSG_F_xxx - break; - default: - break; - } -} - -static void hcryptMsg_STA_IndexMsg(unsigned char *msg, unsigned char *pfx_cache) -{ - hcrypt_Pki pki = hcryptMsg_STA_GetPki(pfx_cache, 0); //Get in host order - memcpy(msg, pfx_cache, HCRYPT_MSG_STA_PFX_SZ); - hcryptMsg_SetPki(&_hcMsg_STA_MsgInfo, pfx_cache, ++pki); -} - -static time_t _tLastLogTime = 0; - -static int hcryptMsg_STA_ParseMsg(unsigned char *msg) -{ - int rc; - - if ((HCRYPT_MSG_VERSION != hcryptMsg_STA_GetVersion(msg)) /* Version 1 */ - || (HCRYPT_MSG_SIGN != hcryptMsg_STA_GetSign(msg))) { /* 'HAI' PnP Mfr ID */ - time_t tCurrentTime = time(NULL); - // invalid data - if ((tCurrentTime - _tLastLogTime) >= 2 || (0 == _tLastLogTime)) - { - _tLastLogTime = tCurrentTime; - HCRYPT_LOG(LOG_ERR, "invalid msg hdr: 0x%02x %02x%02x %02x\n", - msg[0], msg[1], msg[2], msg[3]); - } - return(-1); /* Invalid packet */ - } - rc = hcryptMsg_STA_GetPktType(msg); - switch(rc) { - case HCRYPT_MSG_PT_MS: - if (hcryptMsg_HasNoSek(&_hcMsg_STA_MsgInfo, msg) - || hcryptMsg_HasBothSek(&_hcMsg_STA_MsgInfo, msg)) { - HCRYPT_LOG(LOG_ERR, "invalid MS msg flgs: %02x\n", - hcryptMsg_GetKeyIndex(&_hcMsg_STA_MsgInfo, msg)); - return(-1); - } - break; - case HCRYPT_MSG_PT_KM: - if (HCRYPT_SE_TSUDP != hcryptMsg_KM_GetSE(msg)) { - HCRYPT_LOG(LOG_ERR, "invalid KM msg SE: %d\n", - hcryptMsg_KM_GetSE(msg)); - } else if (hcryptMsg_KM_HasNoSek(msg)) { - HCRYPT_LOG(LOG_ERR, "invalid KM msg flgs: %02x\n", - hcryptMsg_KM_GetKeyIndex(msg)); - return(-1); - } - break; - default: - HCRYPT_LOG(LOG_ERR, "invalid pkt type: %d\n", rc); - rc = 0; /* unknown packet type */ - break; - } - return(rc); /* -1: error, 0: unknown: >0: PT */ -} - -static hcrypt_MsgInfo _hcMsg_STA_MsgInfo; - -hcrypt_MsgInfo *hcryptMsg_STA_MsgInfo(void) -{ - _hcMsg_STA_MsgInfo.hdr_len = HCRYPT_MSG_STA_HDR_SZ; - _hcMsg_STA_MsgInfo.pfx_len = HCRYPT_MSG_STA_PFX_SZ; - _hcMsg_STA_MsgInfo.getKeyFlags = hcryptMsg_STA_GetKeyFlags; - _hcMsg_STA_MsgInfo.getPki = hcryptMsg_STA_GetPki; - _hcMsg_STA_MsgInfo.setPki = hcryptMsg_STA_SetPki; - _hcMsg_STA_MsgInfo.resetCache = hcryptMsg_STA_ResetCache; - _hcMsg_STA_MsgInfo.indexMsg = hcryptMsg_STA_IndexMsg; - _hcMsg_STA_MsgInfo.parseMsg = hcryptMsg_STA_ParseMsg; - - return(&_hcMsg_STA_MsgInfo); -} - diff --git a/trunk/3rdparty/srt-1-fit/scripts/CheckCXXAtomic.cmake b/trunk/3rdparty/srt-1-fit/scripts/CheckCXXAtomic.cmake new file mode 100644 index 0000000000..d3a3b9e71e --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/CheckCXXAtomic.cmake @@ -0,0 +1,62 @@ +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021 Haivision Systems Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +# Check for c++11 std::atomic. +# +# Sets: +# HAVE_CXX_ATOMIC +# HAVE_CXX_ATOMIC_STATIC + +include(CheckCXXSourceCompiles) +include(CheckLibraryExists) + +function(CheckCXXAtomic) + + unset(HAVE_CXX_ATOMIC CACHE) + unset(HAVE_CXX_ATOMIC_STATIC CACHE) + + unset(CMAKE_REQUIRED_FLAGS) + unset(CMAKE_REQUIRED_LIBRARIES) + unset(CMAKE_REQUIRED_LINK_OPTIONS) + + set(CheckCXXAtomic_CODE + " + #include + #include + int main(void) + { + std::atomic x(0); + std::atomic y(0); + return x + y; + } + ") + + set(CMAKE_REQUIRED_FLAGS "-std=c++11") + + check_cxx_source_compiles( + "${CheckCXXAtomic_CODE}" + HAVE_CXX_ATOMIC) + + if(HAVE_CXX_ATOMIC) + # CMAKE_REQUIRED_LINK_OPTIONS was introduced in CMake 3.14. + if(CMAKE_VERSION VERSION_LESS "3.14") + set(CMAKE_REQUIRED_LINK_OPTIONS "-static") + else() + set(CMAKE_REQUIRED_FLAGS "-std=c++11 -static") + endif() + check_cxx_source_compiles( + "${CheckCXXAtomic_CODE}" + HAVE_CXX_ATOMIC_STATIC) + endif() + + unset(CMAKE_REQUIRED_FLAGS) + unset(CMAKE_REQUIRED_LIBRARIES) + unset(CMAKE_REQUIRED_LINK_OPTIONS) + +endfunction(CheckCXXAtomic) diff --git a/trunk/3rdparty/srt-1-fit/scripts/CheckGCCAtomicIntrinsics.cmake b/trunk/3rdparty/srt-1-fit/scripts/CheckGCCAtomicIntrinsics.cmake new file mode 100644 index 0000000000..f47b14d61b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/CheckGCCAtomicIntrinsics.cmake @@ -0,0 +1,113 @@ +# +# SRT - Secure, Reliable, Transport Copyright (c) 2021 Haivision Systems Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at http://mozilla.org/MPL/2.0/. +# + +# Check for GCC Atomic Intrinsics and whether libatomic is required. +# +# Sets: +# HAVE_LIBATOMIC +# HAVE_LIBATOMIC_COMPILES +# HAVE_LIBATOMIC_COMPILES_STATIC +# HAVE_GCCATOMIC_INTRINSICS +# HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC +# +# See +# https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html +# https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync + +include(CheckCSourceCompiles) +include(CheckLibraryExists) + +function(CheckGCCAtomicIntrinsics) + + unset(HAVE_LIBATOMIC CACHE) + unset(HAVE_LIBATOMIC_COMPILES CACHE) + unset(HAVE_LIBATOMIC_COMPILES_STATIC CACHE) + unset(HAVE_GCCATOMIC_INTRINSICS CACHE) + unset(HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC CACHE) + + set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) # CMake 3.6 + + unset(CMAKE_REQUIRED_FLAGS) + unset(CMAKE_REQUIRED_LIBRARIES) + unset(CMAKE_REQUIRED_LINK_OPTIONS) + + # Check for existance of libatomic and whether this symbol is present. + check_library_exists(atomic __atomic_fetch_add_8 "" HAVE_LIBATOMIC) + + set(CheckLibAtomicCompiles_CODE + " + int main(void) + { + const int result = 0; + return result; + } + ") + + set(CMAKE_REQUIRED_LIBRARIES "atomic") + + # Check that the compiler can build a simple application and link with + # libatomic. + check_c_source_compiles("${CheckLibAtomicCompiles_CODE}" + HAVE_LIBATOMIC_COMPILES) + if(NOT HAVE_LIBATOMIC_COMPILES) + set(HAVE_LIBATOMIC + 0 + CACHE INTERNAL "" FORCE) + endif() + if(HAVE_LIBATOMIC AND HAVE_LIBATOMIC_COMPILES) + # CMAKE_REQUIRED_LINK_OPTIONS was introduced in CMake 3.14. + if(CMAKE_VERSION VERSION_LESS "3.14") + set(CMAKE_REQUIRED_LINK_OPTIONS "-static") + else() + set(CMAKE_REQUIRED_FLAGS "-static") + endif() + # Check that the compiler can build a simple application and statically link + # with libatomic. + check_c_source_compiles("${CheckLibAtomicCompiles_CODE}" + HAVE_LIBATOMIC_COMPILES_STATIC) + else() + set(HAVE_LIBATOMIC_COMPILES_STATIC + 0 + CACHE INTERNAL "" FORCE) + endif() + + unset(CMAKE_REQUIRED_FLAGS) + unset(CMAKE_REQUIRED_LIBRARIES) + unset(CMAKE_REQUIRED_LINK_OPTIONS) + + set(CheckGCCAtomicIntrinsics_CODE + " + #include + #include + int main(void) + { + ptrdiff_t x = 0; + intmax_t y = 0; + __atomic_add_fetch(&x, 1, __ATOMIC_SEQ_CST); + __atomic_add_fetch(&y, 1, __ATOMIC_SEQ_CST); + return __atomic_sub_fetch(&x, 1, __ATOMIC_SEQ_CST) + + __atomic_sub_fetch(&y, 1, __ATOMIC_SEQ_CST); + } + ") + + set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) # CMake 3.6 + check_c_source_compiles("${CheckGCCAtomicIntrinsics_CODE}" + HAVE_GCCATOMIC_INTRINSICS) + + if(NOT HAVE_GCCATOMIC_INTRINSICS AND HAVE_LIBATOMIC) + set(CMAKE_REQUIRED_LIBRARIES "atomic") + check_c_source_compiles("${CheckGCCAtomicIntrinsics_CODE}" + HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC) + if(HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC) + set(HAVE_GCCATOMIC_INTRINSICS + 1 + CACHE INTERNAL "" FORCE) + endif() + endif() + +endfunction(CheckGCCAtomicIntrinsics) diff --git a/trunk/3rdparty/srt-1-fit/scripts/FindMbedTLS.cmake b/trunk/3rdparty/srt-1-fit/scripts/FindMbedTLS.cmake index 7dca6e47e2..6622556225 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/FindMbedTLS.cmake +++ b/trunk/3rdparty/srt-1-fit/scripts/FindMbedTLS.cmake @@ -111,5 +111,5 @@ endif() # Now we've accounted for the 3-vs-1 library case: include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Libmbedtls DEFAULT_MSG MBEDTLS_LIBRARIES MBEDTLS_INCLUDE_DIRS) +find_package_handle_standard_args(MbedTLS DEFAULT_MSG MBEDTLS_LIBRARIES MBEDTLS_INCLUDE_DIRS) mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES MBEDTLS_INCLUDE_DIRS) diff --git a/trunk/3rdparty/srt-1-fit/scripts/FindPThreadGetSetName.cmake b/trunk/3rdparty/srt-1-fit/scripts/FindPThreadGetSetName.cmake new file mode 100644 index 0000000000..65685e1eb7 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/FindPThreadGetSetName.cmake @@ -0,0 +1,73 @@ +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021 Haivision Systems Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +# Check for pthread_getname_np(3) and pthread_setname_np(3) +# used in srtcore/threadname.h. +# +# Some BSD distros need to include for pthread_getname_np(). +# +# TODO: Some BSD distros have pthread_get_name_np() and pthread_set_name_np() +# instead of pthread_getname_np() and pthread_setname_np(). +# +# Sets: +# HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H +# HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H +# HAVE_PTHREAD_GETNAME_NP +# HAVE_PTHREAD_SETNAME_NP +# Sets as appropriate: +# add_definitions(-DHAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H=1) +# add_definitions(-DHAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H=1) +# add_definitions(-DHAVE_PTHREAD_GETNAME_NP=1) +# add_definitions(-DHAVE_PTHREAD_SETNAME_NP=1) + +include(CheckSymbolExists) + +function(FindPThreadGetSetName) + + unset(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H CACHE) + unset(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H CACHE) + unset(HAVE_PTHREAD_GETNAME_NP CACHE) + unset(HAVE_PTHREAD_SETNAME_NP CACHE) + + set(CMAKE_REQUIRED_DEFINITIONS + -D_GNU_SOURCE -D_DARWIN_C_SOURCE -D_POSIX_SOURCE=1) + set(CMAKE_REQUIRED_FLAGS "-pthread") + + message(STATUS "Checking for pthread_(g/s)etname_np in 'pthread_np.h':") + check_symbol_exists( + pthread_getname_np "pthread_np.h" HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) + if (HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) + add_definitions(-DHAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H=1) + endif() + check_symbol_exists( + pthread_setname_np "pthread_np.h" HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) + if (HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) + add_definitions(-DHAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H=1) + endif() + + message(STATUS "Checking for pthread_(g/s)etname_np in 'pthread.h':") + check_symbol_exists(pthread_getname_np "pthread.h" HAVE_PTHREAD_GETNAME_NP) + if (HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) + set(HAVE_PTHREAD_GETNAME_NP 1 CACHE INTERNAL "" FORCE) + endif() + check_symbol_exists(pthread_setname_np "pthread.h" HAVE_PTHREAD_SETNAME_NP) + if (HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) + set(HAVE_PTHREAD_SETNAME_NP 1 CACHE INTERNAL "" FORCE) + endif() + if (HAVE_PTHREAD_GETNAME_NP) + add_definitions(-DHAVE_PTHREAD_GETNAME_NP=1) + endif() + if (HAVE_PTHREAD_SETNAME_NP) + add_definitions(-DHAVE_PTHREAD_SETNAME_NP=1) + endif() + + unset(CMAKE_REQUIRED_DEFINITIONS) + unset(CMAKE_REQUIRED_FLAGS) + +endfunction(FindPThreadGetSetName) diff --git a/trunk/3rdparty/srt-1-fit/scripts/ShowProjectConfig.cmake b/trunk/3rdparty/srt-1-fit/scripts/ShowProjectConfig.cmake new file mode 100644 index 0000000000..9cb959294b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/ShowProjectConfig.cmake @@ -0,0 +1,191 @@ +# +# SRT - Secure, Reliable, Transport Copyright (c) 2021 Haivision Systems Inc. +# +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at http://mozilla.org/MPL/2.0/. +# + +function(ShowProjectConfig) + + set(__ssl_configuration) + if (SSL_FOUND OR SSL_LIBRARIES) + set(__ssl_configuration + " SSL Configuration: + SSL_FOUND=${SSL_FOUND} + SSL_INCLUDE_DIRS=${SSL_INCLUDE_DIRS} + SSL_LIBRARIES=${SSL_LIBRARIES} + SSL_VERSION=${SSL_VERSION}\n") + endif() + + set(static_property_link_libraries) + if (srt_libspec_static) + get_target_property( + static_property_link_libraries + ${TARGET_srt}_static + LINK_LIBRARIES) + endif() + set(shared_property_link_libraries) + if (srt_libspec_shared) + get_target_property( + shared_property_link_libraries + ${TARGET_srt}_shared + LINK_LIBRARIES) + endif() + + # See https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#id13 + set(__more_tc1_config) + if (CMAKE_CROSSCOMPILING) + set(__more_tc1_config + " CMAKE_SYSROOT: ${CMAKE_SYSROOT}\n") + endif() + + # See https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#id13 + set(__more_tc2_config) + if (APPLE) + set(__more_tc2_config + " CMAKE_INSTALL_NAME_TOOL: ${CMAKE_INSTALL_NAME_TOOL} + CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT} + CMAKE_OSX_ARCHITECTURES: ${CMAKE_OSX_ARCHITECTURES} + CMAKE_OSX_DEPLOYMENT_TARGET: ${CMAKE_OSX_DEPLOYMENT_TARGET} + CMAKE_OSX_SYSROOT: ${CMAKE_OSX_SYSROOT}\n") + elseif (ANDROID) + set(__more_tc2_config + " CMAKE_ANDROID_NDK: ${CMAKE_ANDROID_NDK} + CMAKE_ANDROID_STANDALONE_TOOLCHAIN: ${CMAKE_ANDROID_STANDALONE_TOOLCHAIN} + CMAKE_ANDROID_API: ${CMAKE_ANDROID_API} + CMAKE_ANDROID_ARCH_ABI: ${CMAKE_ANDROID_ARCH_ABI} + CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION: ${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION} + CMAKE_ANDROID_STL_TYPE: ${CMAKE_ANDROID_STL_TYPE}\n") + endif() + + message(STATUS + "\n" + "========================================================================\n" + "= Project Configuration:\n" + "========================================================================\n" + " SRT Version:\n" + " SRT_VERSION: ${SRT_VERSION}\n" + " SRT_VERSION_BUILD: ${SRT_VERSION_BUILD}\n" + " CMake Configuration:\n" + " CMAKE_VERSION: ${CMAKE_VERSION}\n" + " CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}\n" + " CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}\n" + " Target Configuration:\n" + " CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}\n" + " CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}\n" + " CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}\n" + " CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}\n" + " DARWIN: ${DARWIN}\n" + " LINUX: ${LINUX}\n" + " BSD: ${BSD}\n" + " MICROSOFT: ${MICROSOFT}\n" + " GNU: ${GNU}\n" + " ANDROID: ${ANDROID}\n" + " SUNOS: ${SUNOS}\n" + " POSIX: ${POSIX}\n" + " SYMLINKABLE: ${SYMLINKABLE}\n" + " APPLE: ${APPLE}\n" + " UNIX: ${UNIX}\n" + " WIN32: ${WIN32}\n" + " MINGW: ${MINGW}\n" + " CYGWIN: ${CYGWIN}\n" + " CYGWIN_USE_POSIX: ${CYGWIN_USE_POSIX}\n" + " Toolchain Configuration:\n" + " CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}\n" + " CMAKE_CROSSCOMPILING: ${CMAKE_CROSSCOMPILING}\n" + "${__more_tc1_config}" + " CMAKE_C_COMPILER_ID: ${CMAKE_C_COMPILER_ID}\n" + " CMAKE_C_COMPILER_VERSION: ${CMAKE_C_COMPILER_VERSION}\n" + " CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}\n" + " CMAKE_C_FLAGS: '${CMAKE_C_FLAGS}'\n" + " CMAKE_C_COMPILE_FEATURES: ${CMAKE_C_COMPILE_FEATURES}\n" + " CMAKE_C_STANDARD: ${CMAKE_CXX_STANDARD}\n" + " CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}\n" + " CMAKE_CXX_COMPILER_VERSION: ${CMAKE_CXX_COMPILER_VERSION}\n" + " CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}\n" + " CMAKE_CXX_FLAGS: '${CMAKE_CXX_FLAGS}'\n" + " CMAKE_CXX_COMPILE_FEATURES: ${CMAKE_CXX_COMPILE_FEATURES}\n" + " CMAKE_CXX_STANDARD: ${CMAKE_CXX_STANDARD}\n" + " CMAKE_LINKER: ${CMAKE_LINKER}\n" + #" CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}\n" + #" CMAKE_EXE_LINKER_FLAGS_INIT: ${CMAKE_EXE_LINKER_FLAGS_INIT}\n" + #" CMAKE_MODULE_LINKER_FLAGS: ${CMAKE_MODULE_LINKER_FLAGS}\n" + #" CMAKE_MODULE_LINKER_FLAGS_INIT: ${CMAKE_MODULE_LINKER_FLAGS_INIT}\n" + #" CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}\n" + #" CMAKE_SHARED_LINKER_FLAGS_INIT: ${CMAKE_SHARED_LINKER_FLAGS_INIT}\n" + #" CMAKE_STATIC_LINKER_FLAGS: ${CMAKE_STATIC_LINKER_FLAGS}\n" + #" CMAKE_STATIC_LINKER_FLAGS_INIT: ${CMAKE_STATIC_LINKER_FLAGS_INIT}\n" + " CMAKE_NM: ${CMAKE_NM}\n" + " CMAKE_AR: ${CMAKE_AR}\n" + " CMAKE_RANLIB: ${CMAKE_RANLIB}\n" + "${__more_tc2_config}" + " HAVE_COMPILER_GNU_COMPAT: ${HAVE_COMPILER_GNU_COMPAT}\n" + " CMAKE_THREAD_LIBS: ${CMAKE_THREAD_LIBS}\n" + " CMAKE_THREAD_LIBS_INIT: ${CMAKE_THREAD_LIBS_INIT}\n" + " ENABLE_THREAD_CHECK: ${ENABLE_THREAD_CHECK}\n" + " USE_CXX_STD_APP: ${USE_CXX_STD_APP}\n" + " USE_CXX_STD_LIB: ${USE_CXX_STD_LIB}\n" + " STDCXX: ${STDCXX}\n" + " USE_CXX_STD: ${USE_CXX_STD}\n" + " HAVE_CLOCK_GETTIME_IN: ${HAVE_CLOCK_GETTIME_IN}\n" + " HAVE_CLOCK_GETTIME_LIBRT: ${HAVE_CLOCK_GETTIME_LIBRT}\n" + " HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H: ${HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H}\n" + " HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H: ${HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H}\n" + " HAVE_PTHREAD_GETNAME_NP: ${HAVE_PTHREAD_GETNAME_NP}\n" + " HAVE_PTHREAD_SETNAME_NP: ${HAVE_PTHREAD_SETNAME_NP}\n" + " HAVE_LIBATOMIC: ${HAVE_LIBATOMIC}\n" + " HAVE_LIBATOMIC_COMPILES: ${HAVE_LIBATOMIC_COMPILES}\n" + " HAVE_LIBATOMIC_COMPILES_STATIC: ${HAVE_LIBATOMIC_COMPILES_STATIC}\n" + " HAVE_GCCATOMIC_INTRINSICS: ${HAVE_GCCATOMIC_INTRINSICS}\n" + " HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC: ${HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC}\n" + " HAVE_CXX_ATOMIC: ${HAVE_CXX_ATOMIC}\n" + " HAVE_CXX_ATOMIC_STATIC: ${HAVE_CXX_ATOMIC_STATIC}\n" + " Project Configuration:\n" + " ENABLE_DEBUG: ${ENABLE_DEBUG}\n" + " ENABLE_CXX11: ${ENABLE_CXX11}\n" + " ENABLE_APPS: ${ENABLE_APPS}\n" + " ENABLE_EXAMPLES: ${ENABLE_EXAMPLES}\n" + " ENABLE_BONDING: ${ENABLE_BONDING}\n" + " ENABLE_TESTING: ${ENABLE_TESTING}\n" + " ENABLE_PROFILE: ${ENABLE_PROFILE}\n" + " ENABLE_LOGGING: ${ENABLE_LOGGING}\n" + " ENABLE_HEAVY_LOGGING: ${ENABLE_HEAVY_LOGGING}\n" + " ENABLE_HAICRYPT_LOGGING: ${ENABLE_HAICRYPT_LOGGING}\n" + " ENABLE_SHARED: ${ENABLE_SHARED}\n" + " ENABLE_STATIC: ${ENABLE_STATIC}\n" + " ENABLE_RELATIVE_LIBPATH: ${ENABLE_RELATIVE_LIBPATH}\n" + " ENABLE_GETNAMEINFO: ${ENABLE_GETNAMEINFO}\n" + " ENABLE_UNITTESTS: ${ENABLE_UNITTESTS}\n" + " ENABLE_ENCRYPTION: ${ENABLE_ENCRYPTION}\n" + " ENABLE_CXX_DEPS: ${ENABLE_CXX_DEPS}\n" + " USE_STATIC_LIBSTDCXX: ${USE_STATIC_LIBSTDCXX}\n" + " ENABLE_INET_PTON: ${ENABLE_INET_PTON}\n" + " ENABLE_CODE_COVERAGE: ${ENABLE_CODE_COVERAGE}\n" + " ENABLE_MONOTONIC_CLOCK: ${ENABLE_MONOTONIC_CLOCK}\n" + " ENABLE_STDCXX_SYNC: ${ENABLE_STDCXX_SYNC}\n" + " USE_OPENSSL_PC: ${USE_OPENSSL_PC}\n" + " OPENSSL_USE_STATIC_LIBS: ${OPENSSL_USE_STATIC_LIBS}\n" + " USE_BUSY_WAITING: ${USE_BUSY_WAITING}\n" + " USE_GNUSTL: ${USE_GNUSTL}\n" + " ENABLE_SOCK_CLOEXEC: ${ENABLE_SOCK_CLOEXEC}\n" + " ENABLE_SHOW_PROJECT_CONFIG: ${ENABLE_SHOW_PROJECT_CONFIG}\n" + " ENABLE_CLANG_TSA: ${ENABLE_CLANG_TSA}\n" + " ATOMIC_USE_SRT_SYNC_MUTEX: ${ATOMIC_USE_SRT_SYNC_MUTEX}\n" + " Constructed Configuration:\n" + " DISABLE_CXX11: ${DISABLE_CXX11}\n" + " HAVE_INET_PTON: ${HAVE_INET_PTON}\n" + " PTHREAD_LIBRARY: ${PTHREAD_LIBRARY}\n" + " USE_ENCLIB: ${USE_ENCLIB}\n" + "${__ssl_configuration}" + " TARGET_srt: ${TARGET_srt}\n" + " srt_libspec_static: ${srt_libspec_static}\n" + " srt_libspec_shared: ${srt_libspec_shared}\n" + " SRT_LIBS_PRIVATE: ${SRT_LIBS_PRIVATE}\n" + " Target Link Libraries:\n" + " Static: ${static_property_link_libraries}\n" + " Shared: ${shared_property_link_libraries}\n" + "========================================================================\n" + ) + +endfunction(ShowProjectConfig) diff --git a/trunk/3rdparty/srt-1-fit/scripts/build-windows.bat b/trunk/3rdparty/srt-1-fit/scripts/build-windows.bat new file mode 100644 index 0000000000..78a926cb53 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-windows.bat @@ -0,0 +1,3 @@ +@ECHO OFF +%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\PowerShell.exe -Command "& '%~dpn0.ps1'" +pause diff --git a/trunk/3rdparty/srt-1-fit/scripts/build-windows.ps1 b/trunk/3rdparty/srt-1-fit/scripts/build-windows.ps1 new file mode 100644 index 0000000000..cd25da12c2 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-windows.ps1 @@ -0,0 +1,238 @@ +################################################################################ +# Windows SRT Build Script +#============================ +# Usable on a Windows PC with Powershell and Visual studio, +# or called by CI systems like AppVeyor +# +# By default produces a VS2019 64-bit Release binary using C++11 threads, without +# encryption or unit tests enabled, but including test apps. +# Before enabling any encryption options, install OpenSSL or set VCKPG flag to build +################################################################################ + +param ( + [Parameter()][String]$VS_VERSION = "2019", + [Parameter()][String]$CONFIGURATION = "Release", + [Parameter()][String]$DEVENV_PLATFORM = "x64", + [Parameter()][String]$ENABLE_ENCRYPTION = "OFF", + [Parameter()][String]$STATIC_LINK_SSL = "OFF", + [Parameter()][String]$CXX11 = "ON", + [Parameter()][String]$BUILD_APPS = "ON", + [Parameter()][String]$UNIT_TESTS = "OFF", + [Parameter()][String]$BUILD_DIR = "_build", + [Parameter()][String]$VCPKG_OPENSSL = "OFF", + [Parameter()][String]$BONDING = "OFF" +) + +# cmake can be optionally installed (useful when running interactively on a developer station). +# The URL for automatic download is defined later in the script, but it should be possible to just vary the +# specific version set below and the URL should be stable enough to still work - you have been warned. +$cmakeVersion = "3.23.2" + +# make all errors trigger a script stop, rather than just carry on +$ErrorActionPreference = "Stop" + +$projectRoot = Join-Path $PSScriptRoot "/.." -Resolve + +# if running within AppVeyor, use environment variables to set params instead of passed-in values +if ( $Env:APPVEYOR ) { + if ( $Env:PLATFORM -eq 'x86' ) { $DEVENV_PLATFORM = 'Win32' } else { $DEVENV_PLATFORM = 'x64' } + if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2019' ) { $VS_VERSION='2019' } + if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2015' ) { $VS_VERSION='2015' } + if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2013' ) { $VS_VERSION='2013' } + + #if not statically linking OpenSSL, set flag to gather the specific openssl package from the build server into package + if ( $STATIC_LINK_SSL -eq 'OFF' ) { $Env:GATHER_SSL_INTO_PACKAGE = $true } + + #if unit tests are on, set flag to actually execute ctest step + if ( $UNIT_TESTS -eq 'ON' ) { $Env:RUN_UNIT_TESTS = $true } + + $CONFIGURATION = $Env:CONFIGURATION + + #appveyor has many openssl installations - place the latest one in the default location unless VS2013 + if( $VS_VERSION -ne '2013' ) { + Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null + Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null + } +} + +# persist VS_VERSION so it can be used in an artifact name later +$Env:VS_VERSION = $VS_VERSION + +# select the appropriate cmake generator string given the environment +if ( $VS_VERSION -eq '2019' ) { $CMAKE_GENERATOR = 'Visual Studio 16 2019'; $MSBUILDVER = "16.0"; } +if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015'; $MSBUILDVER = "14.0"; } +if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015 Win64'; $MSBUILDVER = "14.0"; } +if ( $VS_VERSION -eq '2013' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 12 2013'; $MSBUILDVER = "12.0"; } +if ( $VS_VERSION -eq '2013' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 12 2013 Win64'; $MSBUILDVER = "12.0"; } + +# clear any previous build and create & enter the build directory +$buildDir = Join-Path "$projectRoot" "$BUILD_DIR" +Write-Output "Creating (or cleaning if already existing) the folder $buildDir for project files and outputs" +Remove-Item -Path $buildDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null +New-Item -ItemType Directory -Path $buildDir -ErrorAction SilentlyContinue | Out-Null +Push-Location $buildDir + +# check cmake is installed +if ( $null -eq (Get-Command "cmake.exe" -ErrorAction SilentlyContinue) ) { + $installCmake = Read-Host "Unable to find cmake in your PATH - would you like to download and install automatically? [yes/no]" + + if ( $installCmake -eq "y" -or $installCmake -eq "yes" ) { + # download cmake and run MSI for user + $client = New-Object System.Net.WebClient + $tempDownloadFile = New-TemporaryFile + + $cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v$cmakeVersion/cmake-$cmakeVersion-win64-x64.msi" + $cmakeMsiFile = "$tempDownloadFile.cmake-$cmakeVersion-win64-x64.msi" + Write-Output "Downloading cmake from $cmakeUrl (temporary file location $cmakeMsiFile)" + Write-Output "Note: select the option to add cmake to path for this script to operate" + $client.DownloadFile("$cmakeUrl", "$cmakeMsiFile") + Start-Process $cmakeMsiFile -Wait + Remove-Item $cmakeMsiFile + Write-Output "Cmake should have installed, this script will now exit because of path updates - please now re-run this script" + throw + } + else{ + Write-Output "Quitting because cmake is required" + throw + } +} + +# get pthreads from nuget if CXX11 is not enabled +if ( $CXX11 -eq "OFF" ) { + # get pthreads (this is legacy, and is only availble in nuget for VS2015 and VS2013) + if ( $VS_VERSION -gt 2015 ) { + Write-Output "Pthreads is not recommended for use beyond VS2015 and is not supported by this build script - aborting build" + throw + } + if ( $DEVENV_PLATFORM -eq 'Win32' ) { + nuget install cinegy.pthreads-win32-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages + } + else { + nuget install cinegy.pthreads-win64-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages + } +} + +# check to see if static SSL linking was requested, and enable encryption if not already ON +if ( $STATIC_LINK_SSL -eq "ON" ) { + if ( $ENABLE_ENCRYPTION -eq "OFF" ) { + # requesting a static link implicitly requires encryption support + Write-Output "Static linking to OpenSSL requested, will force encryption feature ON" + $ENABLE_ENCRYPTION = "ON" + } +} + +# check to see if VCPKG is marked to provide OpenSSL, and enable encryption if not already ON +if ( $VCPKG_OPENSSL -eq "ON" ) { + if ( $ENABLE_ENCRYPTION -eq "OFF" ) { + # requesting VCPKG to provide OpenSSL requires encryption support + Write-Output "VCPKG compilation of OpenSSL requested, will force encryption feature ON" + $ENABLE_ENCRYPTION = "ON" + } +} + +# build the cmake command flags from arguments +$cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " + + "-DENABLE_STDCXX_SYNC=$CXX11 " + + "-DENABLE_APPS=$BUILD_APPS " + + "-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " + + "-DENABLE_BONDING=$BONDING " + + "-DENABLE_UNITTESTS=$UNIT_TESTS" + +# if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package +if ( $VCPKG_OPENSSL -eq 'ON' ) { + Push-Location $projectRoot + Write-Output "Cloning VCPKG into: $(Get-Location)" + if (Test-Path -Path ".\vcpkg") { + Set-Location .\vcpkg + git pull + } else { + git clone https://github.com/microsoft/vcpkg + Set-Location .\vcpkg + } + + .\bootstrap-vcpkg.bat + + if($DEVENV_PLATFORM -EQ "x64"){ + if($STATIC_LINK_SSL -EQ "ON"){ + .\vcpkg install openssl:x64-windows-static + $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x64-windows-static" + } + else{ + .\vcpkg install openssl:x64-windows + } + } + else{ + if($STATIC_LINK_SSL -EQ "ON"){ + .\vcpkg install openssl:x86-windows-static + $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x86-windows-static" + } + else{ + .\vcpkg install openssl:x86-windows + } + } + + .\vcpkg integrate install + Pop-Location + $cmakeFlags += " -DCMAKE_TOOLCHAIN_FILE=$projectRoot\vcpkg\scripts\buildsystems\vcpkg.cmake" +} +else { + $cmakeFlags += " -DOPENSSL_USE_STATIC_LIBS=$STATIC_LINK_SSL " +} + +# cmake uses a flag for architecture from vs2019, so add that as a suffix +if ( $VS_VERSION -eq '2019' ) { + $cmakeFlags += " -A `"$DEVENV_PLATFORM`"" +} + +# fire cmake to build project files +$execVar = "cmake ../ -G`"$CMAKE_GENERATOR`" $cmakeFlags" +Write-Output $execVar + +# Reset reaction to Continue for cmake as it sometimes tends to print +# things on stderr, which is understood by PowerShell as error. The +# exit code from cmake will be checked anyway. +$ErrorActionPreference = "Continue" +Invoke-Expression "& $execVar" + +# check build ran OK, exit if cmake failed +if( $LASTEXITCODE -ne 0 ) { + Write-Output "Non-zero exit code from cmake: $LASTEXITCODE" + throw +} + +$ErrorActionPreference = "Stop" + +# run the set-version-metadata script to inject build numbers into appveyors console and the resulting DLL +. $PSScriptRoot/set-version-metadata.ps1 + +# look for msbuild +$msBuildPath = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue +if ( $null -eq $msBuildPath ) { + # no mbsuild in the path, so try to locate with 'vswhere' + $vsWherePath = Get-Command "vswhere.exe" -ErrorAction SilentlyContinue + if ( $null -eq $vsWherePath ) { + # no vswhere in the path, so check the Microsoft published location (true since VS2017 Update 2) + $vsWherePath = Get-Command "${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -ErrorAction SilentlyContinue + if ( $null -eq $vsWherePath ) { + Write-Output "Cannot find vswhere (used to locate msbuild). Please install VS2017 update 2 (or later) or add vswhere to your path and try again" + throw + } + } + $msBuildPath = & $vsWherePath -products * -version $MSBUILDVER -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1 + if ( $null -eq $msBuildPath ) { + Write-Output "vswhere.exe cannot find msbuild for the specified Visual Studio version - please check the installation" + throw + } +} + +& $msBuildPath SRT.sln -m /p:Configuration=$CONFIGURATION /p:Platform=$DEVENV_PLATFORM + +# return to the directory previously occupied before running the script +Pop-Location + +# if msbuild returned non-zero, throw to cause failure in CI +if( $LASTEXITCODE -ne 0 ) { + throw +} diff --git a/trunk/3rdparty/srt-1-fit/scripts/collect-gcov.sh b/trunk/3rdparty/srt-1-fit/scripts/collect-gcov.sh new file mode 100644 index 0000000000..7b458e6c1a --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/collect-gcov.sh @@ -0,0 +1,7 @@ +#!/bin/bash +shopt -s globstar +gcov_data_dir="." +for x in ./**/*.o; do + echo "x: $x" + gcov "$gcov_data_dir/$x" +done diff --git a/trunk/3rdparty/srt-1-fit/scripts/gather-package.bat b/trunk/3rdparty/srt-1-fit/scripts/gather-package.bat index e56a7bf3ef..a0221cc054 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/gather-package.bat +++ b/trunk/3rdparty/srt-1-fit/scripts/gather-package.bat @@ -15,7 +15,9 @@ md %APPVEYOR_BUILD_FOLDER%\package\include md %APPVEYOR_BUILD_FOLDER%\package\include\win md %APPVEYOR_BUILD_FOLDER%\package\bin md %APPVEYOR_BUILD_FOLDER%\package\lib -md %APPVEYOR_BUILD_FOLDER%\package\openssl-win%FOLDER_PLATFORM% +IF "%GATHER_SSL_INTO_PACKAGE%"=="True" ( + md %APPVEYOR_BUILD_FOLDER%\package\openssl-win%FOLDER_PLATFORM% +) rem Gather SRT includes, binaries and libs copy %APPVEYOR_BUILD_FOLDER%\version.h %APPVEYOR_BUILD_FOLDER%\package\include\ @@ -23,13 +25,13 @@ copy %APPVEYOR_BUILD_FOLDER%\srtcore\*.h %APPVEYOR_BUILD_FOLDER%\package\include copy %APPVEYOR_BUILD_FOLDER%\haicrypt\*.h %APPVEYOR_BUILD_FOLDER%\package\include\ copy %APPVEYOR_BUILD_FOLDER%\common\*.h %APPVEYOR_BUILD_FOLDER%\package\include\ copy %APPVEYOR_BUILD_FOLDER%\common\win\*.h %APPVEYOR_BUILD_FOLDER%\package\include\win\ -copy %APPVEYOR_BUILD_FOLDER%\%CONFIGURATION%\*.exe %APPVEYOR_BUILD_FOLDER%\package\bin\ -copy %APPVEYOR_BUILD_FOLDER%\%CONFIGURATION%\*.dll %APPVEYOR_BUILD_FOLDER%\package\bin\ -copy %APPVEYOR_BUILD_FOLDER%\%CONFIGURATION%\*.lib %APPVEYOR_BUILD_FOLDER%\package\lib\ -IF "%CONFIGURATION%"=="Debug" ( - copy %APPVEYOR_BUILD_FOLDER%\%CONFIGURATION%\*.pdb %APPVEYOR_BUILD_FOLDER%\package\bin\ -) +copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.exe %APPVEYOR_BUILD_FOLDER%\package\bin\ +copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.dll %APPVEYOR_BUILD_FOLDER%\package\bin\ +copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.lib %APPVEYOR_BUILD_FOLDER%\package\lib\ +copy %APPVEYOR_BUILD_FOLDER%\_build\%CONFIGURATION%\*.pdb %APPVEYOR_BUILD_FOLDER%\package\bin\ -rem gather 3rd party openssl elements -(robocopy c:\openssl-win%FOLDER_PLATFORM%\ %APPVEYOR_BUILD_FOLDER%\package\openssl-win%FOLDER_PLATFORM% /s /e /np) ^& IF %ERRORLEVEL% GTR 1 exit %ERRORLEVEL% +rem Gather 3rd party openssl elements +IF "%GATHER_SSL_INTO_PACKAGE%"=="True" ( + (robocopy c:\openssl-win%FOLDER_PLATFORM%\ %APPVEYOR_BUILD_FOLDER%\package\openssl-win%FOLDER_PLATFORM% /s /e /np) ^& IF %ERRORLEVEL% GTR 1 exit %ERRORLEVEL% +) exit 0 diff --git a/trunk/3rdparty/srt-1-fit/scripts/generate-error-types.tcl b/trunk/3rdparty/srt-1-fit/scripts/generate-error-types.tcl new file mode 100755 index 0000000000..b51d60eb17 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/generate-error-types.tcl @@ -0,0 +1,251 @@ +#!/usr/bin/tclsh +#* +#* SRT - Secure, Reliable, Transport +#* Copyright (c) 2020 Haivision Systems Inc. +#* +#* This Source Code Form is subject to the terms of the Mozilla Public +#* License, v. 2.0. If a copy of the MPL was not distributed with this +#* file, You can obtain one at http://mozilla.org/MPL/2.0/. +#* +#*/ +# +#***************************************************************************** +#written by +# Haivision Systems Inc. +#***************************************************************************** + +set code_major { + UNKNOWN -1 + SUCCESS 0 + SETUP 1 + CONNECTION 2 + SYSTEMRES 3 + FILESYSTEM 4 + NOTSUP 5 + AGAIN 6 + PEERERROR 7 +} + +set code_minor { + NONE 0 + TIMEOUT 1 + REJECTED 2 + NORES 3 + SECURITY 4 + CLOSED 5 + + + CONNLOST 1 + NOCONN 2 + + THREAD 1 + MEMORY 2 + OBJECT 3 + + SEEKGFAIL 1 + READFAIL 2 + SEEKPFAIL 3 + WRITEFAIL 4 + + ISBOUND 1 + ISCONNECTED 2 + INVAL 3 + SIDINVAL 4 + ISUNBOUND 5 + NOLISTEN 6 + ISRENDEZVOUS 7 + ISRENDUNBOUND 8 + INVALMSGAPI 9 + INVALBUFFERAPI 10 + BUSY 11 + XSIZE 12 + EIDINVAL 13 + EEMPTY 14 + BUSYPORT 15 + + WRAVAIL 1 + RDAVAIL 2 + XMTIMEOUT 3 + CONGESTION 4 +} + + +set errortypes { + + SUCCESS "Success" { + NONE "" + } + + SETUP "Connection setup failure" { + NONE "" + TIMEOUT "connection timed out" + REJECTED "connection rejected" + NORES "unable to create/configure SRT socket" + SECURITY "aborted for security reasons" + CLOSED "socket closed during operation" + } + + CONNECTION "" { + NONE "" + CONNLOST "Connection was broken" + NOCONN "Connection does not exist" + } + + SYSTEMRES "System resource failure" { + NONE "" + THREAD "unable to create new threads" + MEMORY "unable to allocate buffers" + OBJECT "unable to allocate a system object" + } + + FILESYSTEM "File system failure" { + NONE "" + SEEKGFAIL "cannot seek read position" + READFAIL "failure in read" + SEEKPFAIL "cannot seek write position" + WRITEFAIL "failure in write" + } + + NOTSUP "Operation not supported" { + NONE "" + ISBOUND "Cannot do this operation on a BOUND socket" + ISCONNECTED "Cannot do this operation on a CONNECTED socket" + INVAL "Bad parameters" + SIDINVAL "Invalid socket ID" + ISUNBOUND "Cannot do this operation on an UNBOUND socket" + NOLISTEN "Socket is not in listening state" + ISRENDEZVOUS "Listen/accept is not supported in rendezous connection setup" + ISRENDUNBOUND "Cannot call connect on UNBOUND socket in rendezvous connection setup" + INVALMSGAPI "Incorrect use of Message API (sendmsg/recvmsg)." + INVALBUFFERAPI "Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)." + BUSY "Another socket is already listening on the same port" + XSIZE "Message is too large to send (it must be less than the SRT send buffer size)" + EIDINVAL "Invalid epoll ID" + EEMPTY "All sockets removed from epoll, waiting would deadlock" + BUSYPORT "Another socket is bound to that port and is not reusable for requested settings" + } + + AGAIN "Non-blocking call failure" { + NONE "" + WRAVAIL "no buffer available for sending" + RDAVAIL "no data available for reading" + XMTIMEOUT "transmission timed out" + CONGESTION "early congestion notification" + } + + PEERERROR "The peer side has signaled an error" { + NONE "" + } + +} + +set main_array_item { +const char** strerror_array_major [] = { +$minor_array_list +}; +} + +set major_size_item { +const size_t strerror_array_sizes [] = { +$minor_array_sizes +}; +} + +set minor_array_item { +const char* strerror_msgs_$majorlc [] = { +$minor_message_items +}; +} + +set strerror_function { +const char* strerror_get_message(size_t major, size_t minor) +{ + static const char* const undefined = "UNDEFINED ERROR"; + + // Extract the major array + if (major >= sizeof(strerror_array_major)/sizeof(const char**)) + { + return undefined; + } + + const char** array = strerror_array_major[major]; + size_t size = strerror_array_sizes[major]; + + if (minor >= size) + { + return undefined; + } + + return array[minor]; +} + +} + +set globalheader { + /* + WARNING: Generated from ../scripts/generate-error-types.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + +#include + +} + +proc Generate:imp {} { + + puts $::globalheader + + puts "namespace srt\n\{" + + # Generate major array + set majitem 0 + set minor_array_sizes "" + foreach {mt mm cont} $::errortypes { + + puts "// MJ_$mt '$mm'" + + # Generate minor array + set majorlc [string tolower $mt] + set minor_message_items "" + set minitem 0 + foreach {mnt mnm} $cont { + if {$mm == ""} { + set msg $mnm + } elseif {$mnm == ""} { + set msg $mm + } else { + set msg "$mm: $mnm" + } + append minor_message_items " \"$msg\", // MN_$mnt = $minitem\n" + incr minitem + } + append minor_message_items " \"\"" + puts [subst -nobackslashes -nocommands $::minor_array_item] + + append minor_array_list " strerror_msgs_$majorlc, // MJ_$mt = $majitem\n" + #append minor_array_sizes " [expr {$minitem}],\n" + append minor_array_sizes " SRT_ARRAY_SIZE(strerror_msgs_$majorlc) - 1,\n" + incr majitem + } + append minor_array_list " NULL" + append minor_array_sizes " 0" + + puts [subst -nobackslashes -nocommands $::main_array_item] + puts {#define SRT_ARRAY_SIZE(ARR) sizeof(ARR) / sizeof(ARR[0])} + puts [subst -nobackslashes -nocommands $::major_size_item] + + puts $::strerror_function + + puts "\} // namespace srt" +} + + +set defmode imp +if {[lindex $argv 0] != ""} { + set defmode [lindex $argv 0] +} + +Generate:$defmode diff --git a/trunk/3rdparty/srt-1-fit/scripts/generate-logging-defs.tcl b/trunk/3rdparty/srt-1-fit/scripts/generate-logging-defs.tcl new file mode 100755 index 0000000000..a86ab9a62b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/generate-logging-defs.tcl @@ -0,0 +1,454 @@ +#!/usr/bin/tclsh +#* +#* SRT - Secure, Reliable, Transport +#* Copyright (c) 2020 Haivision Systems Inc. +#* +#* This Source Code Form is subject to the terms of the Mozilla Public +#* License, v. 2.0. If a copy of the MPL was not distributed with this +#* file, You can obtain one at http://mozilla.org/MPL/2.0/. +#* +#*/ +# +#***************************************************************************** +#written by +# Haivision Systems Inc. +#***************************************************************************** + +# What fields are there in every entry +set model { + longname + shortname + id + description +} + +# Logger definitions. +# Comments here allowed, just only for the whole line. + +# Use values greater than 0. Value 0 is reserved for LOGFA_GENERAL, +# which is considered always enabled. +set loggers { + GENERAL gg 0 "General uncategorized log, for serious issues only" + SOCKMGMT sm 1 "Socket create/open/close/configure activities" + CONN cn 2 "Connection establishment and handshake" + XTIMER xt 3 "The checkTimer and around activities" + TSBPD ts 4 "The TsBPD thread" + RSRC rs 5 "System resource allocation and management" + CONGEST cc 7 "Congestion control module" + PFILTER pf 8 "Packet filter module" + API_CTRL ac 11 "API part for socket and library managmenet" + QUE_CTRL qc 13 "Queue control activities" + EPOLL_UPD ei 16 "EPoll, internal update activities" + + API_RECV ar 21 "API part for receiving" + BUF_RECV br 22 "Buffer, receiving side" + QUE_RECV qr 23 "Queue, receiving side" + CHN_RECV kr 24 "CChannel, receiving side" + GRP_RECV gr 25 "Group, receiving side" + + API_SEND as 31 "API part for sending" + BUF_SEND bs 32 "Buffer, sending side" + QUE_SEND qs 33 "Queue, sending side" + CHN_SEND ks 34 "CChannel, sending side" + GRP_SEND gs 35 "Group, sending side" + + INTERNAL in 41 "Internal activities not connected directly to a socket" + QUE_MGMT qm 43 "Queue, management part" + CHN_MGMT km 44 "CChannel, management part" + GRP_MGMT gm 45 "Group, management part" + EPOLL_API ea 46 "EPoll, API part" +} + +set hidden_loggers { + # Haicrypt logging - usually off. + HAICRYPT hc 6 "Haicrypt module area" + + # defined in apps, this is only a stub to lock the value + APPLOG ap 10 "Applications" +} + +set globalheader { + /* + WARNING: Generated from ../scripts/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + +} + + +# This defines, what kind of definition will be generated +# for a given file out of the log FA entry list. + +# Fields: +# - prefix/postfix model +# - logger_format +# - hidden_logger_format + +# COMMENTS NOT ALLOWED HERE! Only as C++ comments inside C++ model code. +set special { + srtcore/logger_default.cpp { + if {"$longname" == "HAICRYPT"} { + puts $od " +#if ENABLE_HAICRYPT_LOGGING + allfa.set(SRT_LOGFA_HAICRYPT, true); +#endif" + } + } +} + +proc GenerateModelForSrtH {} { + + # `path` will be set to the git top path + global path + + set fd [open [file join $path srtcore/srt.h] r] + + set contents "" + + set state read + set pass looking + + while { [gets $fd line] != -1 } { + if { $state == "read" } { + + if { $pass != "passed" } { + + set re [regexp {SRT_LOGFA BEGIN GENERATED SECTION} $line] + if {$re} { + set state skip + set pass found + } + + } + + append contents "$line\n" + continue + } + + if {$state == "skip"} { + if { [string trim $line] == "" } { + # Empty line, continue skipping + continue + } + + set re [regexp {SRT_LOGFA END GENERATED SECTION} $line] + if {!$re} { + # Still SRT_LOGFA definitions + continue + } + + # End of generated section. Switch back to pass-thru. + + # First fill the gap + append contents "\n\$entries\n\n" + + append contents "$line\n" + set state read + set pass passed + } + } + + close $fd + + # Sanity check + if {$pass != "passed"} { + error "Invalid contents of `srt.h` file, can't find '#define SRT_LOGFA_' phrase" + } + + return $contents +} + +# COMMENTS NOT ALLOWED HERE! Only as C++ comments inside C++ model code. +# (NOTE: Tcl syntax highlighter will likely falsely highlight # as comment here) +# +# Model: TARGET-NAME { format-model logger-pattern hidden-logger-pattern } +# +# Special syntax: +# +# % : a high-level command execution. This declares a command that +# must be executed to GENERATE the model. Then, [subst] is executed +# on the results. +# +# = : when placed as the hidden-logger-pattern, it's equal to logger-pattern. +# +set generation { + srtcore/srt.h { + + {%GenerateModelForSrtH} + + {#define [format "%-20s %-3d" SRT_LOGFA_${longname} $id] // ${shortname}log: $description} + + = + } + + srtcore/logger_default.cpp { + + { + $globalheader + #include "srt.h" + #include "logging.h" + #include "logger_defs.h" + + namespace srt_logging + { + AllFaOn::AllFaOn() + { + $entries + } + } // namespace srt_logging + + } + + { + allfa.set(SRT_LOGFA_${longname}, true); + } + } + + srtcore/logger_defs.cpp { + + { + $globalheader + #include "srt.h" + #include "logging.h" + #include "logger_defs.h" + + namespace srt_logging { AllFaOn logger_fa_all; } + // We need it outside the namespace to preserve the global name. + // It's a part of "hidden API" (used by applications) + SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa); + + namespace srt_logging + { + $entries + } // namespace srt_logging + } + + { + Logger ${shortname}log(SRT_LOGFA_${longname}, srt_logger_config, "SRT.${shortname}"); + } + } + + srtcore/logger_defs.h { + { + $globalheader + #ifndef INC_SRT_LOGGER_DEFS_H + #define INC_SRT_LOGGER_DEFS_H + + #include "srt.h" + #include "logging.h" + + namespace srt_logging + { + struct AllFaOn + { + LogConfig::fa_bitset_t allfa; + AllFaOn(); + }; + + $entries + + } // namespace srt_logging + + #endif + } + + { + extern Logger ${shortname}log; + } + } + + apps/logsupport_appdefs.cpp { + { + $globalheader + #include "logsupport.hpp" + + LogFANames::LogFANames() + { + $entries + } + } + + { + Install("$longname", SRT_LOGFA_${longname}); + } + + { + Install("$longname", SRT_LOGFA_${longname}); + } + } +} + +# EXECUTION + +set here [file dirname [file normalize $argv0]] + +if {[lindex [file split $here] end] != "scripts"} { + puts stderr "The script is in weird location." + exit 1 +} + +set path [file join {*}[lrange [file split $here] 0 end-1]] + +# Utility. Allows to put line-oriented comments and have empty lines +proc no_comments {input} { + set output "" + foreach line [split $input \n] { + set nn [string trim $line] + if { $nn == "" || [string index $nn 0] == "#" } { + continue + } + append output $line\n + } + + return $output +} + +proc generate_file {od target} { + + global globalheader + lassign [dict get $::generation $target] format_model pattern hpattern + + set ptabprefix "" + + if {[string index $format_model 0] == "%"} { + set command [string range $format_model 1 end] + set format_model [eval $command] + } + + if {$format_model != ""} { + set beginindex 0 + while { [string index $format_model $beginindex] == "\n" } { + incr beginindex + } + + set endindex $beginindex + while { [string is space [string index $format_model $endindex]] } { + incr endindex + } + + set tabprefix [string range $pattern $beginindex $endindex-1] + + set newformat "" + foreach line [split $format_model \n] { + if {[string trim $line] == ""} { + append newformat "\n" + continue + } + + if {[string first $tabprefix $line] == 0} { + set line [string range $line [string length $tabprefix] end] + } + append newformat $line\n + + set ie [string first {$} $line] + if {$ie != -1} { + if {[string range $line $ie end] == {$entries}} { + set ptabprefix "[string range $line 0 $ie-1]" + } + } + } + + set format_model $newformat + unset newformat + } + + set entries "" + + if {[string trim $pattern] != "" } { + + set prevval 0 + set pattern [string trim $pattern] + + # The first "$::model" will expand into variable names + # as defined there. + foreach [list {*}$::model] [no_comments $::loggers] { + if {$prevval + 1 != $id} { + append entries "\n" + } + + append entries "${ptabprefix}[subst -nobackslashes $pattern]\n" + set prevval $id + } + } + + if {$hpattern != ""} { + if {$hpattern == "="} { + set hpattern $pattern + } else { + set hpattern [string trim $hpattern] + } + + # Extra line to separate from the normal entries + append entries "\n" + foreach [list {*}$::model] [no_comments $::hidden_loggers] { + append entries "${ptabprefix}[subst -nobackslashes $hpattern]\n" + } + } + + if { [dict exists $::special $target] } { + set code [subst [dict get $::special $target]] + + # The code should contain "append entries" ! + eval $code + } + + set entries [string trim $entries] + + if {$format_model == ""} { + set format_model $entries + } + + # For any case, cut external spaces + puts $od [string trim [subst -nocommands -nobackslashes $format_model]] +} + +proc debug_vars {list} { + set output "" + foreach name $list { + upvar $name _${name} + lappend output "${name}=[set _${name}]" + } + + return $output +} + +# MAIN + +set entryfiles $argv + +if {$entryfiles == ""} { + set entryfiles [dict keys $generation] +} else { + foreach ef $entryfiles { + if { $ef ni [dict keys $generation] } { + error "Unknown generation target: $entryfiles" + } + } +} + +foreach f $entryfiles { + + # Set simple relative path, if the file isn't defined as path. + if { [llength [file split $f]] == 1 } { + set filepath $f + } else { + set filepath [file join $path $f] + } + + puts stderr "Generating '$filepath'" + set od [open $filepath.tmp w] + generate_file $od $f + close $od + if { [file exists $filepath] } { + puts "WARNING: will overwrite exiting '$f'. Hit ENTER to confirm, or Control-C to stop" + gets stdin + } + + file rename -force $filepath.tmp $filepath +} + +puts stderr Done. + diff --git a/trunk/3rdparty/srt-1-fit/scripts/haiUtil.cmake b/trunk/3rdparty/srt-1-fit/scripts/haiUtil.cmake index b05a2b6857..2226619893 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/haiUtil.cmake +++ b/trunk/3rdparty/srt-1-fit/scripts/haiUtil.cmake @@ -12,11 +12,11 @@ include(CheckCXXSourceCompiles) # Useful for combinging paths function(adddirname prefix lst out_lst) - set(output) - foreach(item ${lst}) - list(APPEND output "${prefix}/${item}") - endforeach() - set(${out_lst} ${${out_lst}} ${output} PARENT_SCOPE) + set(output) + foreach(item ${lst}) + list(APPEND output "${prefix}/${item}") + endforeach() + set(${out_lst} ${${out_lst}} ${output} PARENT_SCOPE) endfunction() # Splits a version formed as "major.minor.patch" recorded in variable 'prefix' @@ -32,11 +32,11 @@ ENDMACRO(set_version_variables) # Sets given variable to 1, if the condition that follows it is satisfied. # Otherwise set it to 0. MACRO(set_if varname) - IF(${ARGN}) - SET(${varname} 1) - ELSE(${ARGN}) - SET(${varname} 0) - ENDIF(${ARGN}) + IF(${ARGN}) + SET(${varname} 1) + ELSE(${ARGN}) + SET(${varname} 0) + ENDIF(${ARGN}) ENDMACRO(set_if) FUNCTION(join_arguments outvar) @@ -49,83 +49,8 @@ FUNCTION(join_arguments outvar) set (${outvar} ${output} PARENT_SCOPE) ENDFUNCTION() -# LEGACY. PLEASE DON'T USE ANYMORE. -MACRO(MafRead maffile) - message(WARNING "MafRead is deprecated. Please use MafReadDir instead") - # ARGN contains the extra "section-variable" pairs - # If empty, return nothing - set (MAFREAD_TAGS - SOURCES # source files - PUBLIC_HEADERS # installable headers for include - PROTECTED_HEADERS # installable headers used by other headers - PRIVATE_HEADERS # non-installable headers - ) - cmake_parse_arguments(MAFREAD_VAR "" "${MAFREAD_TAGS}" "" ${ARGN}) - # Arguments for these tags are variables to be filled - # with the contents of particular section. - # While reading the file, extract the section. - # Section is recognized by either first uppercase character or space. - - # @c http://cmake.org/pipermail/cmake/2007-May/014222.html - FILE(READ ${maffile} MAFREAD_CONTENTS) - STRING(REGEX REPLACE ";" "\\\\;" MAFREAD_CONTENTS "${MAFREAD_CONTENTS}") - STRING(REGEX REPLACE "\n" ";" MAFREAD_CONTENTS "${MAFREAD_CONTENTS}") - - #message("DEBUG: MAF FILE CONTENTS: ${MAFREAD_CONTENTS}") - #message("DEBUG: PASSED VARIABLES:") - #foreach(DEBUG_VAR ${MAFREAD_TAGS}) - # message("DEBUG: ${DEBUG_VAR}=${MAFREAD_VAR_${DEBUG_VAR}}") - #endforeach() - - # The unnamed section becomes SOURCES - set (MAFREAD_VARIABLE ${MAFREAD_VAR_SOURCES}) - set (MAFREAD_UNASSIGNED "") - - FOREACH(MAFREAD_LINE ${MAFREAD_CONTENTS}) - # Test what this line is - string(STRIP ${MAFREAD_LINE} MAFREAD_OLINE) - string(SUBSTRING ${MAFREAD_OLINE} 0 1 MAFREAD_FIRST) - #message("DEBUG: LINE='${MAFREAD_LINE}' FIRST='${MAFREAD_FIRST}'") - - # The 'continue' command is cmake 3.2 - very late discovery - if (MAFREAD_FIRST STREQUAL "") - #message("DEBUG: ... skipped: empty") - elseif (MAFREAD_FIRST STREQUAL "#") - #message("DEBUG: ... skipped: comment") - else() - # Will be skipped if the line was a comment/empty - string(REGEX MATCH "[ A-Z]" MAFREAD_SECMARK ${MAFREAD_FIRST}) - if (MAFREAD_SECMARK STREQUAL "") - # This isn't a section, it's a list element. - #message("DEBUG: ITEM: ${MAFREAD_OLINE} --> ${MAFREAD_VARIABLE}") - LIST(APPEND ${MAFREAD_VARIABLE} ${MAFREAD_OLINE}) - else() - # It's a section - change the running variable - # Make it section name - STRING(REPLACE " " "_" MAFREAD_SECNAME ${MAFREAD_OLINE}) - set(MAFREAD_VARIABLE ${MAFREAD_VAR_${MAFREAD_SECNAME}}) - if (MAFREAD_VARIABLE STREQUAL "") - set(MAFREAD_VARIABLE MAFREAD_UNASSIGNED) - endif() - #message("DEBUG: NEW SECTION: '${MAFREAD_SECNAME}' --> VARIABLE: '${MAFREAD_VARIABLE}'") - endif() - endif() - ENDFOREACH() - - # Final debug report - #set (ALL_VARS "") - #message("DEBUG: extracted variables:") - #foreach(DEBUG_VAR ${MAFREAD_TAGS}) - # list(APPEND ALL_VARS ${MAFREAD_VAR_${DEBUG_VAR}}) - #endforeach() - #list(REMOVE_DUPLICATES ALL_VARS) - #foreach(DEBUG_VAR ${ALL_VARS}) - # message("DEBUG: --> ${DEBUG_VAR} = ${${DEBUG_VAR}}") - #endforeach() -ENDMACRO(MafRead) - -# New version of MafRead macro, which automatically adds directory -# prefix. This should also resolve each relative path. +# The directory specifies the location of maffile and +# all files specified in the list. MACRO(MafReadDir directory maffile) # ARGN contains the extra "section-variable" pairs # If empty, return nothing @@ -155,11 +80,11 @@ MACRO(MafReadDir directory maffile) configure_file(${directory}/${maffile} dummy_${maffile}.cmake.out) file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/dummy_${maffile}.cmake.out) - #message("DEBUG: MAF FILE CONTENTS: ${MAFREAD_CONTENTS}") - #message("DEBUG: PASSED VARIABLES:") - #foreach(DEBUG_VAR ${MAFREAD_TAGS}) - # message("DEBUG: ${DEBUG_VAR}=${MAFREAD_VAR_${DEBUG_VAR}}") - #endforeach() + #message("DEBUG: MAF FILE CONTENTS: ${MAFREAD_CONTENTS}") + #message("DEBUG: PASSED VARIABLES:") + #foreach(DEBUG_VAR ${MAFREAD_TAGS}) + # message("DEBUG: ${DEBUG_VAR}=${MAFREAD_VAR_${DEBUG_VAR}}") + #endforeach() # The unnamed section becomes SOURCES set (MAFREAD_VARIABLE ${MAFREAD_VAR_SOURCES}) @@ -188,11 +113,60 @@ MACRO(MafReadDir directory maffile) if (${MAFREAD_SECTION_TYPE} STREQUAL file) get_filename_component(MAFREAD_OLINE ${directory}/${MAFREAD_OLINE} ABSOLUTE) endif() - LIST(APPEND ${MAFREAD_VARIABLE} ${MAFREAD_OLINE}) + + set (MAFREAD_CONDITION_OK 1) + if (DEFINED MAFREAD_CONDITION_LIST) + FOREACH(MFITEM IN ITEMS ${MAFREAD_CONDITION_LIST}) + separate_arguments(MFITEM) + FOREACH(MFVAR IN ITEMS ${MFITEM}) + STRING(SUBSTRING ${MFVAR} 0 1 MFPREFIX) + if (MFPREFIX STREQUAL "!") + STRING(SUBSTRING ${MFVAR} 1 -1 MFVAR) + if (${MFVAR}) + set (MFCONDITION_RESULT 0) + else() + set (MFCONDITION_RESULT 1) + endif() + else() + if (${MFVAR}) + set (MFCONDITION_RESULT 1) + else() + set (MFCONDITION_RESULT 0) + endif() + endif() + #message("CONDITION: ${MFPREFIX} ${MFVAR} -> ${MFCONDITION_RESULT}") + + MATH(EXPR MAFREAD_CONDITION_OK "${MAFREAD_CONDITION_OK} & (${MFCONDITION_RESULT})") + ENDFOREACH() + ENDFOREACH() + endif() + + if (MAFREAD_CONDITION_OK) + LIST(APPEND ${MAFREAD_VARIABLE} ${MAFREAD_OLINE}) + else() + #message("... NOT ADDED ITEM: ${MAFREAD_OLINE}") + endif() else() - # It's a section - change the running variable + # It's a section + # Check for conditionals (clear current conditions first) + unset(MAFREAD_CONDITION_LIST) + + STRING(FIND ${MAFREAD_OLINE} " -" MAFREAD_HAVE_CONDITION) + if (NOT MAFREAD_HAVE_CONDITION EQUAL -1) + # Cut off conditional specification, and + # grab the section name and condition list + STRING(REPLACE " -" ";" MAFREAD_CONDITION_LIST ${MAFREAD_OLINE}) + + #message("CONDITION READ: ${MAFREAD_CONDITION_LIST}") + + LIST(GET MAFREAD_CONDITION_LIST 0 MAFREAD_OLINE) + LIST(REMOVE_AT MAFREAD_CONDITION_LIST 0) + #message("EXTRACTING SECTION=${MAFREAD_OLINE} CONDITIONS=${MAFREAD_CONDITION_LIST}") + endif() + # change the running variable # Make it section name STRING(REPLACE " " "_" MAFREAD_SECNAME ${MAFREAD_OLINE}) + #message("MAF SECTION: ${MAFREAD_SECNAME}") # The cmake's version of 'if (MAFREAD_SECNAME[0] == '-')' - sigh... string(SUBSTRING ${MAFREAD_SECNAME} 0 1 MAFREAD_SECNAME0) @@ -212,15 +186,15 @@ MACRO(MafReadDir directory maffile) ENDFOREACH() # Final debug report - #set (ALL_VARS "") - #message("DEBUG: extracted variables:") - #foreach(DEBUG_VAR ${MAFREAD_TAGS}) - # list(APPEND ALL_VARS ${MAFREAD_VAR_${DEBUG_VAR}}) - #endforeach() - #list(REMOVE_DUPLICATES ALL_VARS) - #foreach(DEBUG_VAR ${ALL_VARS}) - # message("DEBUG: --> ${DEBUG_VAR} = ${${DEBUG_VAR}}") - #endforeach() + #set (ALL_VARS "") + #message("DEBUG: extracted variables:") + #foreach(DEBUG_VAR ${MAFREAD_TAGS}) + # list(APPEND ALL_VARS ${MAFREAD_VAR_${DEBUG_VAR}}) + #endforeach() + #list(REMOVE_DUPLICATES ALL_VARS) + #foreach(DEBUG_VAR ${ALL_VARS}) + # message("DEBUG: --> ${DEBUG_VAR} = ${${DEBUG_VAR}}") + #endforeach() ENDMACRO(MafReadDir) # NOTE: This is historical only. Not in use. @@ -240,9 +214,9 @@ MACRO(GetMafHeaders directory outvar) ENDMACRO(GetMafHeaders) function (getVarsWith _prefix _varResult) - get_cmake_property(_vars VARIABLES) - string (REGEX MATCHALL "(^|;)${_prefix}[A-Za-z0-9_]*" _matchedVars "${_vars}") - set (${_varResult} ${_matchedVars} PARENT_SCOPE) + get_cmake_property(_vars VARIABLES) + string (REGEX MATCHALL "(^|;)${_prefix}[A-Za-z0-9_]*" _matchedVars "${_vars}") + set (${_varResult} ${_matchedVars} PARENT_SCOPE) endfunction() function (check_testcode_compiles testcode libraries _successful) @@ -254,15 +228,18 @@ function (check_testcode_compiles testcode libraries _successful) set (CMAKE_REQUIRED_LIBRARIES ${save_required_libraries}) endfunction() -function (test_requires_clock_gettime _result) +function (test_requires_clock_gettime _enable _linklib) # This function tests if clock_gettime can be used # - at all # - with or without librt # Result will be: - # rt (if librt required) - # "" (if no extra libraries required) - # -- killed by FATAL_ERROR if clock_gettime is not available + # - CLOCK_MONOTONIC is available, link with librt: + # _enable = ON; _linklib = "-lrt". + # - CLOCK_MONOTONIC is available, link without librt: + # _enable = ON; _linklib = "". + # - CLOCK_MONOTONIC is not available: + # _enable = OFF; _linklib = "-". set (code " #include @@ -275,17 +252,39 @@ function (test_requires_clock_gettime _result) check_testcode_compiles(${code} "" HAVE_CLOCK_GETTIME_IN) if (HAVE_CLOCK_GETTIME_IN) - message(STATUS "Checked clock_gettime(): no extra libs needed") - set (${_result} "" PARENT_SCOPE) + message(STATUS "CLOCK_MONOTONIC: available, no extra libs needed") + set (${_enable} ON PARENT_SCOPE) + set (${_linklib} "" PARENT_SCOPE) return() endif() check_testcode_compiles(${code} "rt" HAVE_CLOCK_GETTIME_LIBRT) if (HAVE_CLOCK_GETTIME_LIBRT) - message(STATUS "Checked clock_gettime(): requires -lrt") - set (${_result} "-lrt" PARENT_SCOPE) + message(STATUS "CLOCK_MONOTONIC: available, requires -lrt") + set (${_enable} ON PARENT_SCOPE) + set (${_linklib} "-lrt" PARENT_SCOPE) return() endif() - message(FATAL_ERROR "clock_gettime() is not available on this system") + set (${_enable} OFF PARENT_SCOPE) + set (${_linklib} "-" PARENT_SCOPE) + message(STATUS "CLOCK_MONOTONIC: not available on this system") +endfunction() + +function (parse_compiler_type wct _type _suffix) + if (wct STREQUAL "") + set(${_type} "" PARENT_SCOPE) + set(${_suffix} "" PARENT_SCOPE) + else() + string(REPLACE "-" ";" OUTLIST ${wct}) + list(LENGTH OUTLIST OUTLEN) + list(GET OUTLIST 0 ITEM) + set(${_type} ${ITEM} PARENT_SCOPE) + if (OUTLEN LESS 2) + set(_suffix "" PARENT_SCOPE) + else() + list(GET OUTLIST 1 ITEM) + set(${_suffix} "-${ITEM}" PARENT_SCOPE) + endif() + endif() endfunction() diff --git a/trunk/3rdparty/srt-1-fit/scripts/iOS.cmake b/trunk/3rdparty/srt-1-fit/scripts/iOS.cmake index 73544a29b5..dfb6aeb97a 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/iOS.cmake +++ b/trunk/3rdparty/srt-1-fit/scripts/iOS.cmake @@ -151,10 +151,10 @@ if (NOT DEFINED IOS_ARCH) set (IOS_ARCH x86_64) endif (${IOS_PLATFORM} STREQUAL OS) endif(NOT DEFINED IOS_ARCH) -set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE string "Build architecture for iOS") +set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS") # Set the find root to the iOS developer roots and to user defined paths -set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE string "iOS find search path root") +set (CMAKE_FIND_ROOT_PATH ${CMAKE_IOS_DEVELOPER_ROOT} ${CMAKE_IOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE STRING "iOS find search path root") # default to searching for frameworks first set (CMAKE_FIND_FRAMEWORK FIRST) diff --git a/trunk/3rdparty/srt-1-fit/scripts/maf.vim b/trunk/3rdparty/srt-1-fit/scripts/maf.vim new file mode 100644 index 0000000000..2ae97b260b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/maf.vim @@ -0,0 +1,38 @@ +" +" SRT - Secure, Reliable, Transport +" Copyright (c) 2020 Haivision Systems Inc. +" +" This Source Code Form is subject to the terms of the Mozilla Public +" License, v. 2.0. If a copy of the MPL was not distributed with this +" file, You can obtain one at http://mozilla.org/MPL/2.0/. +" +" This file describes MAF ("manifest") file syntax used by +" SRT project. +" + + +if exists("b:current_syntax") + finish +endif + +" conditionals +syn match mafCondition contained " - [!A-Za-z].*"hs=s+2 + +" section +syn match mafSection "^[A-Z][0-9A-Za-z_].*$" contains=mafCondition +syn match mafsection "^ .*$" contains=mafCondition + +" comments +syn match mafComment "^\s*\zs#.*$" +syn match mafComment "\s\zs#.*$" +syn match mafComment contained "#.*$" + + +" hilites + +hi def link mafComment Comment +hi def link mafSection Statement +hi def link mafCondition Number + + +let b:current_syntax = "maf" diff --git a/trunk/3rdparty/srt-1-fit/scripts/release-notes/README.md b/trunk/3rdparty/srt-1-fit/scripts/release-notes/README.md new file mode 100644 index 0000000000..1eba3eeb21 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/release-notes/README.md @@ -0,0 +1,18 @@ +# Script Description + +Script designed to generate release notes template with main sections, contributors list, and detailed changelog out of `.csv` SRT git log file. The output `release-notes.md` file is generated in the root folder. + +In order to obtain the git log file since the previous release (e.g., v1.4.0), use the following command: + +``` +git log --pretty=format:"%h|%s|%an|%ae" v1.4.0...HEAD^ > commits.csv +``` + +## Requirements + +* Python 3.6+ + +To install Python libraries use: +``` +pip install -r requirements.txt +``` diff --git a/trunk/3rdparty/srt-1-fit/scripts/release-notes/generate_release_notes.py b/trunk/3rdparty/srt-1-fit/scripts/release-notes/generate_release_notes.py new file mode 100644 index 0000000000..2392b43484 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/release-notes/generate_release_notes.py @@ -0,0 +1,120 @@ +import enum + +import click +import numpy as np +import pandas as pd + + +@enum.unique +class Area(enum.Enum): + core = 'core' + tests = 'tests' + build = 'build' + apps = 'apps' + docs = 'docs' + + +def define_area(msg): + areas = [e.value for e in Area] + + for area in areas: + if msg.startswith(f'[{area}] '): + return area + + return np.NaN + + +def delete_prefix(msg): + prefixes = [f'[{e.value}] ' for e in Area] + + for prefix in prefixes: + if msg.startswith(prefix): + return msg[len(prefix):] + + return msg[:] + + +def write_into_changelog(df, f): + f.write('\n') + for _, row in df.iterrows(): + f.write(f"\n{row['commit']} {row['message']}") + f.write('\n') + + +@click.command() +@click.argument( + 'git_log', + type=click.Path(exists=True) +) +def main(git_log): + """ + Script designed to generate release notes template with main sections, + contributors list, and detailed changelog out of .csv SRT git log file. + """ + df = pd.read_csv(git_log, sep = '|', names = ['commit', 'message', 'author', 'email']) + df['area'] = df['message'].apply(define_area) + df['message'] = df['message'].apply(delete_prefix) + + # Split commits by areas + core = df[df['area']==Area.core.value] + tests = df[df['area']==Area.tests.value] + build = df[df['area']==Area.build.value] + apps = df[df['area']==Area.apps.value] + docs = df[df['area']==Area.docs.value] + other = df[df['area'].isna()] + + # Define individual contributors + contributors = df.groupby(['author', 'email']) + contributors = list(contributors.groups.keys()) + + with open('release-notes.md', 'w') as f: + f.write('# Release Notes\n') + + f.write('\n## API / ABI / Integration Changes\n') + f.write('\n**API/ABI version: 1.x.**\n') + + f.write('\n## New Features and Improvements\n') + f.write('\n## Important Bug Fixes\n') + f.write('\n## Build\n') + f.write('\n## Documentation\n') + + f.write('\n## Contributors\n') + for name, email in contributors: + f.write(f'\n{name} <{email}>') + f.write('\n') + + f.write('\n## Changelog\n') + f.write('\n
Click to expand/collapse') + f.write('\n

') + f.write('\n') + + if not core.empty: + f.write('\n### Core Functionality') + write_into_changelog(core, f) + + if not tests.empty: + f.write('\n### Unit Tests') + write_into_changelog(tests, f) + + if not build.empty: + f.write('\n### Build Scripts (CMake, etc.)') + write_into_changelog(build, f) + + if not apps.empty: + f.write('\n### Sample Applications') + write_into_changelog(apps, f) + + if not docs.empty: + f.write('\n### Documentation') + write_into_changelog(docs, f) + + if not other.empty: + f.write('\n### Other') + write_into_changelog(other, f) + + f.write('\n

') + f.write('\n
') + + +if __name__ == '__main__': + main() diff --git a/trunk/3rdparty/srt-1-fit/scripts/release-notes/requirements.txt b/trunk/3rdparty/srt-1-fit/scripts/release-notes/requirements.txt new file mode 100644 index 0000000000..898c1f7d0c --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/release-notes/requirements.txt @@ -0,0 +1,3 @@ +click>=7.1.2 +numpy>=1.19.1 +pandas>=0.25.3 \ No newline at end of file diff --git a/trunk/3rdparty/srt-1-fit/scripts/set-version-metadata.ps1 b/trunk/3rdparty/srt-1-fit/scripts/set-version-metadata.ps1 index cd9b4be3e8..050838e5e6 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/set-version-metadata.ps1 +++ b/trunk/3rdparty/srt-1-fit/scripts/set-version-metadata.ps1 @@ -27,13 +27,22 @@ if($Env:APPVEYOR){ Update-AppveyorBuild -Version "$majorVer.$minorVer.$patchVer.$buildNum" $FileDescriptionBranchCommitValue = "$Env:APPVEYOR_REPO_NAME - $($Env:APPVEYOR_REPO_BRANCH) ($($Env:APPVEYOR_REPO_COMMIT.substring(0,8)))" } +if($Env:TEAMCITY_VERSION){ + #make TeamCity update with this new version number + Write-Output "##teamcity[buildNumber '$majorVer.$minorVer.$patchVer.$buildNum']" + Write-Output "##teamcity[setParameter name='MajorVersion' value='$majorVer']" + Write-Output "##teamcity[setParameter name='MinorVersion' value='$minorVer']" + Write-Output "##teamcity[setParameter name='PatchVersion' value='$patchVer']" + Write-Output "##teamcity[setParameter name='BuildVersion' value='$buildNum']" + $FileDescriptionBranchCommitValue = "$majorVer.$minorVer.$patchVer.$buildNum - ($($Env:BUILD_VCS_NUMBER.substring(0,8)))" +} #find C++ resource files and update file description with branch / commit details $FileDescriptionStringRegex = '(\bVALUE\s+\"FileDescription\"\s*\,\s*\")([^\"]*\\\")*[^\"]*(\")' -Get-ChildItem -Path "./srtcore/srt_shared.rc" | ForEach-Object { +Get-ChildItem -Path "../srtcore/srt_shared.rc" | ForEach-Object { $fileName = $_ - Write-Host "Processing metadata changes for file: $fileName" + Write-Output "Processing metadata changes for file: $fileName" $FileLines = Get-Content -path $fileName diff --git a/trunk/3rdparty/srt-1-fit/scripts/srt-dev.lua b/trunk/3rdparty/srt-1-fit/scripts/srt-dev.lua new file mode 100644 index 0000000000..92b5fca1a8 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/srt-dev.lua @@ -0,0 +1,938 @@ +-- @brief srt-dev Protocol dissector plugin + +-- create a new dissector +local NAME = "SRT-dev" +local srt_dev = Proto(NAME, "SRT-dev Protocol") + +-- create a preference of a Protocol +srt_dev.prefs["srt_udp_port"] = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port") + +-- create fields of srt_dev +-- Base.HEX, Base.DEC, Base.OCT, Base.UNIT_STRING, Base.NONE +local fields = srt_dev.fields +-- General field +local pack_type_select = { + [0] = "Data Packet", + [1] = "Control Packet" +} +fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX) +fields.pack_type = ProtoField.uint16("srt_dev.pack_type", "Packet Type", base.HEX, pack_type_select, 0x8000) +fields.reserve = ProtoField.uint16("srt_dev.reserve", "Reserve", base.DEC) +fields.additional_info = ProtoField.uint32("srt_dev.additional_info", "Additional Information", base.DEC) +fields.time_stamp = ProtoField.uint32("srt_dev.time_stamp", "Time Stamp", base.DEC) +fields.dst_sock = ProtoField.uint32("srt_dev.dst_sock", "Destination Socket ID", base.DEC) +fields.none = ProtoField.none("srt_dev.none", "none", base.NONE) + +-- Data packet fields +fields.data_flag_info_tree = ProtoField.uint8("srt_dev.data_flag_info_tree", "Data Flag Info", base.HEX) +local FF_state_select = { + [0] = "[Middle packet]", + [1] = "[Last packet]", + [2] = "[First packet]", + [3] = "[Single packet]" +} +fields.FF_state = ProtoField.uint8("srt_dev.FF_state", "FF state", base.HEX, FF_state_select, 0xC0) +local O_state_select = { + [0] = "[ORD_RELAX]", + [1] = "[ORD_REQUIRED]" +} +fields.O_state = ProtoField.uint8("srt_dev.O_state", "O state", base.HEX, O_state_select, 0x20) +local KK_state_select = { + [0] = "[Not encrypted]", + [1] = "[Data encrypted with even key]", + [2] = "[Data encrypted with odd key]" +} +fields.KK_state = ProtoField.uint8("srt_dev.KK_state", "KK state", base.HEX, KK_state_select, 0x18) +local R_state_select = { + [0] = "[ORIGINAL]", + [1] = "[RETRANSMITTED]" +} +fields.R_state = ProtoField.uint8("srt_dev.R_state", "R state", base.HEX, R_state_select, 0x04) +fields.seq_num = ProtoField.uint32("srt_dev.seq_num", "Sequence Number", base.DEC) +fields.msg_num = ProtoField.uint32("srt_dev.msg_num", "Message Number", base.DEC)--, nil, 0x3FFFFFF) + +-- control packet fields +local msg_type_select = { + [0] = "[HANDSHAKE]", + [1] = "[KEEPALIVE]", + [2] = "[ACK]", + [3] = "[NAK(Loss Report)]", + [4] = "[Congestion Warning]", + [5] = "[Shutdown]", + [6] = "[ACKACK]", + [7] = "[Drop Request]", + [8] = "[Peer Error]", + [0x7FFF] = "[Message Extension Type]" +} +fields.msg_type = ProtoField.uint16("srt_dev.msg_type", "Message Type", base.HEX, msg_type_select, 0x7FFF) +fields.msg_ext_type = ProtoField.uint16("srt_dev.msg_ext_type", "Message Extented Type", base.DEC) + +local flag_state_select = { + [0] = "Unset", + [1] = "Set" +} + +-- Handshake packet fields +fields.UDT_version = ProtoField.uint32("srt_dev.UDT_version", "UDT Version", base.DEC) +fields.sock_type = ProtoField.uint32("srt_dev.sock_type", "Socket Type", base.DEC) +fields.ency_fld = ProtoField.uint16("srt_dev.ency_fld", "Encryption Field", base.DEC) +fields.ext_fld = ProtoField.uint16("srt_dev.ext_fld", "Extension Fields", base.HEX) +fields.ext_fld_tree = ProtoField.uint16("srt_dev.ext_fld_tree", "Extension Fields Tree", base.HEX) +fields.hsreq = ProtoField.uint16("srt_dev.hsreq", "HS_EXT_HSREQ", base.HEX, flag_state_select, 0x1) +fields.kmreq = ProtoField.uint16("srt_dev.kmreq", "HS_EXT_KMREQ", base.HEX, flag_state_select, 0x2) +fields.config = ProtoField.uint16("srt_dev.config", "HS_EXT_CONFIG", base.HEX, flag_state_select, 0x4) +fields.isn = ProtoField.uint32("srt_dev.isn", "Initial packet sequence number", base.DEC) +fields.mss = ProtoField.uint32("srt_dev.mss", "Max Packet Size", base.DEC) +fields.fc = ProtoField.uint32("srt_dev.fc", "Maximum Flow Window Size", base.DEC) +fields.conn_type = ProtoField.int32("srt_dev.conn_type", "Connection Type", base.DEC) +fields.sock_id = ProtoField.uint32("srt_dev.sock_id", "Socket ID", base.DEC) +fields.syn_cookie = ProtoField.uint32("srt_dev.syn_cookie", "SYN cookie", base.DEC) +fields.peer_ipaddr = ProtoField.none("srt_dev.peer_ipaddr", "Peer IP address", base.NONE) +fields.peer_ipaddr_4 = ProtoField.ipv4("srt_dev.peer_ipaddr", "Peer IP address") +fields.peer_ipaddr_6 = ProtoField.ipv6("srt_dev.peer_ipaddr", "Peer IP address") +local ext_type_select = { + [-1] = "SRT_CMD_NONE", + [0] = "SRT_CMD_REJECT", + [1] = "SRT_CMD_HSREQ", + [2] = "SRT_CMD_HSRSP", + [3] = "SRT_CMD_KMREQ", + [4] = "SRT_CMD_KMRSP", + [5] = "SRT_CMD_SID", + [6] = "SRT_CMD_CONGESTION", + [7] = "SRT_CMD_FILTER", + [8] = "SRT_CMD_GROUP" +} +fields.ext_type_msg_tree = ProtoField.none("srt_dev.ext_type", "Extension Type Message", base.NONE) +fields.ext_type = ProtoField.uint16("srt_dev.ext_type", "Extension Type", base.HEX, ext_type_select, 0xF) +fields.ext_size = ProtoField.uint16("srt_dev.ext_size", "Extension Size", base.DEC) + +-- Handshake packet, ext type == SRT_CMD_HSREQ or SRT_CMD_HSRSP field +fields.srt_version = ProtoField.uint32("srt_dev.srt_version", "SRT Version", base.HEX) +fields.srt_flags = ProtoField.uint32("srt_dev.srt_flags", "SRT Flags", base.HEX) +fields.tsbpb_resv = ProtoField.uint16("srt_dev.tsbpb_resv", "TsbPb Receive", base.DEC) +fields.tsbpb_delay = ProtoField.uint16("srt_dev.tsbpb_delay", "TsbPb Delay", base.DEC) +fields.tsbpd_delay = ProtoField.uint16("srt_dev.tsbpd_delay", "TsbPd Delay", base.DEC) +fields.rcv_tsbpd_delay = ProtoField.uint16("srt_dev.rcv_tsbpd_delay", "Receiver TsbPd Delay", base.DEC) +fields.snd_tsbpd_delay = ProtoField.uint16("srt_dev.snd_tsbpd_delay", "Sender TsbPd Delay", base.DEC) + +-- V adn PT status flag +local V_state_select = { + [1] = "Initial version" +} +fields.V_state = ProtoField.uint8("srt_dev.V_state", "V", base.HEX, V_state_select, 0x70) +local PT_state_select = { + [0] = "Reserved", + [1] = "MSmsg", + [2] = "KMmsg", + [7] = "Reserved to discriminate MPEG-TS packet(0x47=sync byte)" +} +fields.PT_state = ProtoField.uint8("srt_dev.PT_state", "PT", base.HEX, state_table, 0xF) +fields.sign = ProtoField.uint16("srt_dev.sign", "Signature", base.HEX) +local resv_select = { + [0] = "Reserved for flag extension or other usage" +} +fields.resv = ProtoField.uint8("srt_dev.resv", "Resv", base.DEC, state_table, 0xFC) +fields.ext_KK_state = ProtoField.uint8("srt_dev.ext_KK_state", "KK_state", base.HEX, KK_state_select, 0x3) +fields.KEKI = ProtoField.uint32("srt_dev.KEKI", "KEKI", base.DEC) +fields.cipher = ProtoField.uint8("srt_dev.cipher", "Cipher", base.DEC) +fields.auth = ProtoField.uint8("srt_dev.auth", "auth", base.DEC) +fields.SE = ProtoField.uint8("srt_dev.SE", "SE", base.DEC) +fields.resv1 = ProtoField.uint8("srt_dev.resv1", "resv1", base.DEC) +fields.resv2 = ProtoField.uint16("srt_dev.resv2", "resv2", base.DEC) +fields.slen = ProtoField.uint8("srt_dev.slen", "Salt length(bytes)/4", base.DEC) +fields.klen = ProtoField.uint8("srt_dev.klen", "SEK length(bytes)/4", base.DEC) +fields.salt = ProtoField.uint32("srt_dev.salt", "Salt key", base.DEC) +fields.wrap = ProtoField.none("srt_dev.wrap", "Wrap key(s)", base.NONE) + +-- Wrap Field +fields.ICV = ProtoField.uint64("srt_dev.ICV", "Integerity Check Vector", base.HEX) +fields.odd_key = ProtoField.stringz("srt_dev.odd_key", "Odd key", base.ASCII) +fields.even_key = ProtoField.stringz("srt_dev.even_key", "Even key", base.ASCII) + +-- ext_type == SRT_CMD_SID field +fields.sid = ProtoField.string("srt_dev.sid", "Stream ID", base.ASCII) +-- ext_type == SRT_CMD_CONGESTION field +fields.congestion = ProtoField.string("srt_dev.congestion", "Congestion Controller", base.ASCII) +-- ext_type == SRT_CMD_FILTER field +fields.filter = ProtoField.string("srt_dev.filter", "Filter", base.ASCII) +-- ext_type == SRT_CMD_GROUP field +fields.group = ProtoField.string("srt_dev.group", "Group Data", base.ASCII) + +-- SRT flags +fields.srt_opt_tsbpdsnd = ProtoField.uint32("srt_dev.srt_opt_tsbpdsnd", "SRT_OPT_TSBPDSND", base.HEX, flag_state_select, 0x1) +fields.srt_opt_tsbpdrcv = ProtoField.uint32("srt_dev.srt_opt_tsbpdrcv", "SRT_OPT_TSBPDRCV", base.HEX, flag_state_select, 0x2) +fields.srt_opt_haicrypt = ProtoField.uint32("srt_dev.srt_opt_haicrypt", "SRT_OPT_HAICRYPT", base.HEX, flag_state_select, 0x4) +fields.srt_opt_tlpktdrop = ProtoField.uint32("srt_dev.srt_opt_tlpktdrop", "SRT_OPT_TLPKTDROP", base.HEX, flag_state_select, 0x8) +fields.srt_opt_nakreport = ProtoField.uint32("srt_dev.srt_opt_nakreport", "SRT_OPT_NAKREPORT", base.HEX, flag_state_select, 0x10) +fields.srt_opt_rexmitflg = ProtoField.uint32("srt_dev.srt_opt_rexmitflg", "SRT_OPT_REXMITFLG", base.HEX, flag_state_select, 0x20) +fields.srt_opt_stream = ProtoField.uint32("srt_dev.srt_opt_stream", "SRT_OPT_STREAM", base.HEX, flag_state_select, 0x40) + +-- ACK fields +fields.last_ack_pack = ProtoField.uint32("srt_dev.last_ack_pack", "Last ACK Packet Sequence Number", base.DEC) +fields.rtt = ProtoField.int32("srt_dev.rtt", "Round Trip Time", base.DEC) +fields.rtt_variance = ProtoField.int32("srt_dev.rtt_variance", "Round Trip Time Variance", base.DEC) +fields.buf_size = ProtoField.uint32("srt_dev.buf_size", "Available Buffer Size", base.DEC) +fields.pack_rcv_rate = ProtoField.uint32("srt_dev.pack_rcv_rate", "Packet Receiving Rate", base.DEC) +fields.est_link_capacity = ProtoField.uint32("srt_dev.est_link_capacity", "Estimated Link Capacity", base.DEC) +fields.rcv_rate = ProtoField.uint32("srt_dev.rcv_rate", "Receiving Rate", base.DEC) + +-- ACKACK fields +fields.ack_num = ProtoField.uint32("srt_dev.ack_num", "ACK number", base.DEC) +fields.ctl_info = ProtoField.uint32("srt_dev.ctl_info", "Control Information", base.DEC) + +-- KMRSP fields +local srt_km_state_select = { + [0] = "[SRT_KM_UNSECURED]", + [1] = "[SRT_KM_SECURING]", + [2] = "[SRT_KM_SECURED]", + [3] = "[SRT_KM_NOSECRET]", + [4] = "[SRT_KM_BADSECRET]" +} +fields.km_err = ProtoField.uint32("srt_dev.km_err", "Key Message Error", base.HEX, srt_km_state_select, 0xF) + +-- NAK Control Packet fields +fields.lost_list_tree = ProtoField.none("srt_dev.lost_list_tree", "Lost Packet List", base.NONE) +fields.lost_pack_seq = ProtoField.uint32("srt_dev.lost_pack_seq", "Lost Packet Sequence Number", base.DEC) +fields.lost_pack_range_tree = ProtoField.none("srt_dev.lost_pack_range_tree", "Lost Packet Range", base.NONE) +fields.lost_start = ProtoField.uint32("srt_dev.lost_start", "Lost Starting Sequence", base.DEC) +fields.lost_up_to = ProtoField.uint32("srt_dev.lost_up_to", "Lost Up To(including)", base.DEC) + +-- Dissect packet +function srt_dev.dissector (tvb, pinfo, tree) + -- Packet is based on UDP, so the data can be processed directly after UDP + local subtree = tree:add(srt_dev, tvb()) + local offset = 0 + + -- Changes the protocol name + pinfo.cols.protocol = srt_dev.name + + -- Take out the first bit of package + -- 0 -> Data Packet + -- 1 -> Control Packet + local typebit = bit.rshift(tvb(offset, 1):uint(), 7) + pack_type_tree = subtree:add(fields.pack_type_tree, tvb(offset, 4)) + + if typebit == 1 then + -- Handle Control Packet + pack_type_tree:add(fields.pack_type, tvb(offset, 2)) + + local msg_type = tvb(offset, 2):uint() + if msg_type ~= 0xFFFF then + -- If type field isn't '0x7FFF',it means packet is normal data packet, then handle type field + msg_type = bit.band(msg_type, 0x7FFF) + + function parse_three_param() + -- Ignore Additional Info (this field is not defined in this packet type) + subtree:add(fields.additional_info, tvb(offset, 4)):append_text(" [undefined]") + offset = offset + 4 + + -- Handle Time Stamp + subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") + offset = offset + 4 + + -- Handle Destination Socket + subtree:add(fields.dst_sock, tvb(offset, 4)) + offset = offset + 4 + end + + local switch = { + [0] = function() + pinfo.cols.info:append(" [HANDSHAKE]") + pack_type_tree:append_text(" [HANDSHAKE]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)) + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + + -- Handle Additional Info, Timestamp and Destination Socket + parse_three_param() + + -- Handle UDT version field + local UDT_version = tvb(offset, 4):uint() + subtree:add(fields.UDT_version, tvb(offset, 4)) + offset = offset + 4 + + if UDT_version == 4 then + -- UDT version is 4, packet is diffrent from UDT version 5 + -- Handle sock type + local sock_type = tvb(offset, 4):uint() + if sock_type == 1 then + subtree:add(fields.sock_type, tvb(offset, 4)):append_text(" [SRT_STREAM]") + elseif sock_type == 2 then + subtree:add(fields.sock_type, tvb(offset, 4)):append_text(" [SRT_DRAGAM]") + end + offset = offset + 4 + elseif UDT_version == 5 then + -- Handle Encryption Field + local encr_fld = tvb(offset, 2):int() + if encr_fld == 0 then + subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (PBKEYLEN not advertised)") + elseif encr_fld == 2 then + subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (AES-128)") + elseif encr_fld == 3 then + subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (AES-192)") + else + subtree:add(fields.ency_fld, tvb(offset, 2)):append_text(" (AES-256)") + end + offset = offset + 2 + + -- Handle Extension Field + local ext_fld = tvb(offset, 2):int() + if ext_fld == 0x4A17 then + subtree:add(fields.ext_fld, tvb(offset, 2)):append_text(" [HSv5 MAGIC]") + else + -- Extension Field is HS_EXT_prefix + -- The define is in fiel handshake.h + local ext_fld_tree = subtree:add(fields.ext_fld_tree, tvb(offset, 2)) + local str_table = { " [" } + ext_fld_tree:add(fields.hsreq, tvb(offset, 2)) + if bit.band(tvb(offset, 2):uint(), 0x1) == 1 then + table.insert(str_table, "HS_EXT_HSREQ") + table.insert(str_table, " | ") + end + ext_fld_tree:add(fields.kmreq, tvb(offset, 2)):append_text(" [HS_EXT_KMREQ]") + if bit.band(tvb(offset, 2):uint(), 0x2) == 2 then + table.insert(str_table, "HS_EXT_KMREQ") + table.insert(str_table, " | ") + end + ext_fld_tree:add(fields.config, tvb(offset, 2)):append_text(" [HS_EXT_CONFIG]") + if bit.band(tvb(offset, 2):uint(), 0x4) == 4 then + table.insert(str_table, "HS_EXT_CONFIG") + table.insert(str_table, " | ") + end + table.remove(str_table) + table.insert(str_table, "]") + if ext_fld ~= 0 then + ext_fld_tree:append_text(table.concat(str_table)) + end + end + offset = offset + 2 + end + + -- Handle Initial packet sequence number + subtree:add(fields.isn, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Maximum Packet Size + subtree:add(fields.mss, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Maximum Flow Window Size + subtree:add(fields.fc, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Connection Type + local conn_type = tvb(offset, 4):int() + local conn_type_tree = subtree:add(fields.conn_type, tvb(offset, 4)) + if conn_type == 0 then + conn_type_tree:append_text(" [WAVEAHAND] (Rendezvous Mode)") + pinfo.cols.info:append(" [WAVEAHAND] (Rendezvous Mode)") + elseif conn_type == 1 then + conn_type_tree:append_text(" [INDUCTION]") + elseif conn_type == -1 then + conn_type_tree:append_text(" [CONCLUSION]") + elseif conn_type == -2 then + conn_type_tree:append_text(" [AGREEMENT] (Rendezvous Mode)") + pinfo.cols.info:append(" [AGREEMENT] (Rendezvous Mode)") + end + offset = offset + 4 + + -- Handle Socket ID + subtree:add(fields.sock_id, tvb(offset, 4)) + offset = offset + 4 + + -- Handle SYN cookie + local syn_cookie = tvb(offset, 4):int() + subtree:add(fields.syn_cookie, tvb(offset, 4)) + if syn_cookie == 0 then + conn_type_tree:append_text(" (Caller to Listener)") + pinfo.cols.info:append(" (Caller to Listener)") + else + if conn_type == 1 then + -- reports cookie from listener + conn_type_tree:append_text(" (Listener to Caller)") + pinfo.cols.info:append(" (Listener to Caller)") + end + end + offset = offset + 4 + + -- Handle Peer IP address + -- Note the network byte order + local the_last_96_bits = 0 + the_last_96_bits = the_last_96_bits + math.floor(tvb(offset + 4, 4):int() * (2 ^ 16)) + the_last_96_bits = the_last_96_bits + math.floor(tvb(offset + 8, 4):int() * (2 ^ 8)) + the_last_96_bits = the_last_96_bits + tvb(offset + 12, 4):int() + if the_last_96_bits == 0 then + subtree:add_le(fields.peer_ipaddr_4, tvb(offset, 4)) + else + subtree:add_le(fields.peer_ipaddr, tvb(offset, 16)) + end + + offset = offset + 16 + + -- UDT version is 4, packet handle finish + if UDT_version == 4 or offset == tvb:len() then + return + end + + function process_ext_type() + -- Handle Ext Type, processing by type + local ext_type = tvb(offset, 2):int() + if ext_type == 1 or ext_type == 2 then + local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, 16)) + if ext_type == 1 then + ext_type_msg_tree:append_text(" [SRT_CMD_HSREQ]") + ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) + conn_type_tree:append_text(" (Caller to Listener)") + pinfo.cols.info:append(" (Caller to Listener)") + else + ext_type_msg_tree:append_text(" [SRT_CMD_HSRSP]") + ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) + conn_type_tree:append_text(" (Listener to Caller)") + pinfo.cols.info:append(" (Listener to Caller)") + end + offset = offset + 2 + + -- Handle Ext Size + ext_type_msg_tree:add(fields.ext_size, tvb(offset, 2)) + offset = offset + 2 + + -- Handle SRT Version + ext_type_msg_tree:add(fields.srt_version, tvb(offset, 4)) + offset = offset + 4 + + -- Handle SRT Flags + local SRT_flags_tree = ext_type_msg_tree:add(fields.srt_flags, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_tsbpdsnd, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_tsbpdrcv, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_haicrypt, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_tlpktdrop, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_nakreport, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_rexmitflg, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_stream, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Recv TsbPd Delay and Snd TsbPd Delay + if UDT_version == 4 then + ext_type_msg_tree:add(fields.tsbpd_delay, tvb(offset, 2)):append_text(" [Unused in HSv4]") + offset = offset + 2 + ext_type_msg_tree:add(fields.tsbpb_delay, tvb(offset, 2)) + offset = offset + 2 + else + ext_type_msg_tree:add(fields.rcv_tsbpd_delay, tvb(offset, 2)) + offset = offset + 2 + ext_type_msg_tree:add(fields.snd_tsbpd_delay, tvb(offset, 2)) + offset = offset + 2 + end + elseif ext_type == 3 or ext_type == 4 then + local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, 16)) + if ext_type == 3 then + ext_type_msg_tree:append_text(" [SRT_CMD_KMREQ]") + ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) + conn_type_tree:append_text(" (Listener to Caller)") + else + ext_type_msg_tree:append_text(" [SRT_CMD_KMRSP]") + ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) + end + offset = offset + 2 + + -- Handle Ext Size + local km_len = tvb(offset, 2):uint() + ext_type_msg_tree:add(fields.ext_size, tvb(offset, 2)):append_text(" (byte/4)") + offset = offset + 2 + + -- Handle SRT_CMD_KMREQ message + -- V and PT status flag + ext_type_msg_tree:add(fields.V_state, tvb(offset, 1)) + ext_type_msg_tree:add(fields.PT_state, tvb(offset, 1)) + offset = offset + 1 + + -- Handle sign + ext_type_msg_tree:add(fields.sign, tvb(offset, 2)):append_text(" (/'HAI/' PnP Vendor ID in big endian order)") + offset = offset + 2 + + -- Handle resv + ext_type_msg_tree:add(fields.resv, tvb(offset, 1)) + + -- Handle KK flag + local KK = tvb(offset, 1):uint() + ext_type_msg_tree:add(fields.ext_KK_state, tvb(offset, 1)) + offset = offset + 1 + + -- Handle KEKI + if tvb(offset, 4):uint() == 0 then + ext_type_msg_tree:add(fields.KEKI, tvb(offset, 4)):append_text(" (Default stream associated key(stream/system default))") + else + ext_type_msg_tree:add(fields.KEKI, tvb(offset, 4)):append_text(" (Reserved for manually indexed keys)") + end + offset = offset + 4 + + -- Handle Cipher + local cipher_node = ext_type_msg_tree:add(fields.cipher, tvb(offset, 1)) + local cipher = tvb(offset, 1):uint() + if cipher == 0 then + elseif cipher == 1 then + cipher_node:append_text(" (AES-ECB(potentially for VF 2.0 compatible message))") + elseif cipher == 2 then + cipher_node:append_text(" (AES-CTR[FP800-38A])") + else + cipher_node:append_text(" (AES-CCM or AES-GCM)") + end + offset = offset + 1 + + -- Handle Auth + if tvb(offset, 1):uint() == 0 then + ext_type_msg_tree:add(fields.auth, tvb(offset, 1)):append_text(" (None or KEKI indexed crypto context)") + else + ext_type_msg_tree:add(fields.auth, tvb(offset, 1)) + end + offset = offset + 1 + + -- Handle SE + local SE_node = ext_type_msg_tree:add(fields.SE, tvb(offset, 1)) + local SE = tvb(offset, 1):uint() + if SE == 0 then + SE_node:append_text( " (Unspecified or KEKI indexed crypto context)") + elseif SE == 1 then + SE_node:append_text( " (MPEG-TS/UDP)") + elseif SE == 2 then + SE_node:append_text( " (MPEG-TS/SRT)") + end + offset = offset + 1 + + -- Handle resv1 + ext_type_msg_tree:add(fields.resv1, tvb(offset, 1)) + offset = offset + 1 + + -- Handle resv2 + ext_type_msg_tree:add(fields.resv2, tvb(offset, 2)) + offset = offset + 2 + + -- Handle slen + ext_type_msg_tree:add(fields.slen, tvb(offset, 1)) + offset = offset + 1 + + -- Handle klen + local klen = tvb(offset, 1):uint() + ext_type_msg_tree:add(fields.klen, tvb(offset, 1)) + offset = offset + 1 + + -- Handle salt key + ext_type_msg_tree:add(fields.salt, tvb(offset, slen * 4)) + offset = offset + slen * 4 + + -- Handle wrap + -- Handle ICV + local wrap_len = 8 + KK * klen + local wrap_tree = ext_type_msg_tree:add(fields.wrap, tvb(offset, wrap_len)) + wrap_tree:add(fields.ICV, tvb(offset, 8)) + offset = offset + 8 + -- If KK == 2, first key is Even key + if KK == 2 then + wrap_tree:add(fields.even_key, tvb(offset, klen)) + offset = offset + klen; + end + + -- Handle Odd key + wrap_tree:add(fields.odd_key, tvb(offset, klen)) + offset = offset + klen; + elseif ext_type >= 5 and ext_type <= 8 then + local value_size = tvb(offset + 2, 2):uint() * 4 + local ext_msg_size = 2 + 2 + value_size + local type_array = { " [SRT_CMD_SID]", " [SRT_CMD_CONGESTION]", " [SRT_CMD_FILTER]", " [SRT_CMD_GROUP]" } + local field_array = { fields.sid, fields.congestion, fields.filter, fields.group } + local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, ext_msg_size)):append_text(type_array[ext_type - 4]) + ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) + offset = offset + 2 + + -- Handle Ext Msg Value Size + ext_type_msg_tree:add(fields.ext_size, tvb(offset, 2)):append_text(" (byte/4)") + offset = offset + 2 + + -- Value + local value_table = {} + for pos = 0, value_size - 4, 4 do + table.insert(value_table, string.char(tvb(offset + pos + 3, 1):uint())) + table.insert(value_table, string.char(tvb(offset + pos + 2, 1):uint())) + table.insert(value_table, string.char(tvb(offset + pos + 1, 1):uint())) + table.insert(value_table, string.char(tvb(offset + pos, 1):uint())) + end + local value = table.concat(value_table) + ext_type_msg_tree:add(field_array[ext_type - 4], tvb(offset, value_size), value) + offset = offset + value_size + elseif ext_type == -1 then + local ext_type_msg_tree = subtree:add(fields.ext_type_msg_tree, tvb(offset, tvb:len() - offset)):append_text(" [SRT_CMD_NONE]") + ext_type_msg_tree:add(fields.ext_type, tvb(offset, 2)) + offset = offset + 2 + + -- none + if offset == tvb:len() then + return + end + ext_type_msg_tree:add(fields.none, tvb(offset, tvb:len() - offset)) + offset = tvb:len() + end + if offset == tvb:len() then + return + else + process_ext_type() + end + end + + process_ext_type() + end, + [1] = function() + pinfo.cols.info:append(" [KEEPALIVE]") + pack_type_tree:append_text(" [KEEPALIVE]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)):append_text(" [KEEPALIVE]") + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + + -- Handle Additional Info, Time Stamp and Destination Socket + parse_three_param() + end, + [2] = function() + pinfo.cols.info:append(" [ACK]") + pack_type_tree:append_text(" [ACK]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)) + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + + -- Handle ACK Number + subtree:add(fields.ack_num, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Time Stamp + subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") + offset = offset + 4 + + -- Handle Destination Socket + subtree:add(fields.dst_sock, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Last Ack Packet Sequence + local last_ack_pack = tvb(offset, 4):uint() + pinfo.cols.info:append(" (Last ACK Seq:" .. last_ack_pack .. ")") + subtree:add(fields.last_ack_pack, tvb(offset, 4)) + offset = offset + 4 + + -- Handle RTT + local rtt = tvb(offset, 4):int() + subtree:add(fields.rtt, tvb(offset, 4)):append_text(" μs") + offset = offset + 4 + + -- Handle RTT variance + if rtt < 0 then + subtree:add(fields.rtt_variance, tvb(offset, 4), -tvb(offset, 4):int()) + else + subtree:add(fields.rtt_variance, tvb(offset, 4)) + end + offset = offset + 4 + + -- Handle Available Buffer Size(pkts) + subtree:add(fields.buf_size, tvb(offset, 4)):append_text(" pkts") + offset = offset + 4 + + -- Handle Packets Receiving Rate(Pkts/sec) + subtree:add(fields.pack_rcv_rate, tvb(offset, 4)):append_text(" pkts/sec") + offset = offset + 4 + + -- Handle Estmated Link Capacity + subtree:add(fields.est_link_capacity, tvb(offset, 4)):append_text(" pkts/sec") + offset = offset + 4 + + -- Handle Receiving Rate(bps) + subtree:add(fields.rcv_rate, tvb(offset, 4)):append_text(" bps") + offset = offset + 4 + end, + [3] = function() + pinfo.cols.info:append(" [NAK(loss Report)]") + pack_type_tree:append_text(" [NAK(loss Report)]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)) + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + + -- Handle Additional Info, Timestamp and Destination Socket + parse_three_param() + + -- Handle lost packet sequence + -- lua does not support changing loop variables within loops, but in the form of closures + -- https://blog.csdn.net/Ai102iA/article/details/75371239 + local start = offset + local ending = tvb:len() + local lost_list_tree = subtree:add(fields.lost_list_tree, tvb(offset, ending - offset)) + for start in function() + local first_bit = bit.rshift(tvb(start, 1):uint(), 7) + if first_bit == 1 then + local lost_pack_range_tree = lost_list_tree:add(fields.lost_pack_range_tree, tvb(start, 8)) + local lost_start = bit.band(tvb(start, 4):uint(), 0x7FFFFFFF) + lost_pack_range_tree:append_text(" (" .. lost_start .. " -> " .. tvb(start + 4, 4):uint() .. ")") + lost_pack_range_tree:add(fields.lost_start, tvb(start, 4), lost_start) + start = start + 4 + lost_pack_range_tree:add(fields.lost_up_to, tvb(start, 4)) + start = start + 4 + else + lost_list_tree:add(fields.lost_pack_seq, tvb(start, 4)) + start = start + 4 + end + return start + end + do + if start == ending then + break + end + end + end, + [4] = function() + pinfo.cols.info:append(" [Congestion Warning]") + pack_type_tree:append_text(" [Congestion Warning]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)) + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + end, + [5] = function() + pinfo.cols.info:append(" [Shutdown]") + pack_type_tree:append_text(" [Shutdown]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)) + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + + -- Handle Additional Info, Timestamp and Destination Socket + parse_three_param() + end, + [6] = function() + pinfo.cols.info:append(" [ACKACK]") + pack_type_tree:append_text(" [ACKACK]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)) + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + + -- Handle ACK sequence number + subtree:add(fields.ack_num, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Time Stamp + subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") + offset = offset + 4 + + -- Handle Destination Socket + subtree:add(fields.dst_sock, tvb(offset, 4)) + offset = offset + 4 + + -- Handle Control Information + subtree:add(fields.ctl_info, tvb(offset, 4)) + offset = offset + 4 + end, + [7] = function() + pinfo.cols.info:append(" [Drop Request]") + pack_type_tree:append_text(" [Drop Request]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Drop Request]") + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + end, + [8] = function() + pinfo.cols.info:append(" [Peer Error]") + pack_type_tree:append_text(" [Peer Error]") + pack_type_tree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Peer Error]") + pack_type_tree:add(fields.reserve, tvb(offset + 2, 2)):append_text(" [Undefined]") + offset = offset + 4 + end + } + -- Handle based on msg_type + local case = switch[msg_type] + if case then + case() + else + -- default case + subtree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Unknown Message Type]") + offset = offset + 4 + end + else + -- If type field is '0x7FFF', it means an extended type, Handle Reserve field + offset = offset + 2 + local msg_ext_type = tvb(offset, 2):uint() + if msg_ext_type == 0 then + pinfo.cols.info:append(" [Message Extension]") + + pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [Message Extension]") + offset = offset + 2 + + -- Handle Additional Info, Time Stamp and Destination Socket + parse_three_param() + + -- Control information: defined by user + elseif msg_ext_type == 1 or ext_type == 2 then + if msg_ext_type == 1 then + pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [SRT Handshake Request]") + pinfo.cols.info:append(" [SRT Handshake Request]") + elseif msg_ext_type == 2 then + pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [SRT Handshake Response]") + pinfo.cols.info:append(" [SRT Handshake Response]") + end + offset = offset + 2 + + -- Ignore additional info (this field is not defined in this packet type) + subtree:add(fields.additional_info, tvb(offset, 4)):append_text(" [undefined]") + offset = offset + 4 + + -- Handle Time Stamp + subtree:add(fields.time_stamp, tvb(offset, 4)):append_text("μs") + offset = offset + 4 + + -- Handle Destination Socket + subtree:add(fields.dst_sock, tvb(offset, 4)) + offset = offset + 4 + + -- Handle SRT Version field + subtree:add(fields.srt_version, tvb(offset, 4)) + offset = ofssset + 4 + + -- Handle SRT Flags + local SRT_flags_tree = subtree:add(fields.srt_flags, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_tsbpdsnd, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_tsbpdrcv, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_haicrypt, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_tlpktdrop, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_nakreport, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_rexmitflg, tvb(offset, 4)) + SRT_flags_tree:add(fields.srt_opt_stream, tvb(offset, 4)) + offset = offset + 4 + + -- Handle TsbPd Resv + subtree:add(fields.tsbpb_resv, tvb(offset, 2)) + offset = offset + 2 + + -- Handle TsbPb Delay + subtree:add(fields.tsbpb_delay, tvb(offset, 2)) + offset = offset + 2 + + -- Handle Reserved field + subtree:add(fields.reserve, tvb(offset, 4)) + offset = offset + 4 + elseif msg_ext_type == 3 or msg_ext_type == 4 then + if msg_ext_type == 3 then + pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [Encryption Keying Material Request]") + pinfo.cols.info:append(" [Encryption Keying Material Request]") + elseif msg_ext_type == 4 then + pack_type_tree:add(fields.msg_ext_type, tvb(offset, 2)):append_text(" [Encryption Keying Material Response]") + pinfo.cols.info:append(" [Encryption Keying Material Response]") + end + offset = offset + 2 + + -- Ignore additional info (this field is not defined in this packet type) + subtree:add(fields.additional_info, tvb(offset, 4)):append_text(" [undefined]") + offset = offset + 4 + + -- Handle Timestamp + subtree:add(fields.time_stamp, tvb(offset, 4)):append_text("μs") + offset = offset + 4 + + -- Handle Destination Socket + subtree:add(fields.dst_sock, tvb(offset, 4)) + offset = offset + 4 + + -- Handle KmErr + if msg_ext_type == 4 then + subtree:add(fields.km_err, tvb(offset, 4)) + offset = offset + 4 + return + end + + -- The encrypted message is not handled + end + end + else + -- 0 -> Data Packet + pack_type_tree:add(fields.pack_type, tvb(offset, 2)) + pack_type_tree:append_text(" (Data Packet)") + local seq_num = tvb(offset, 4):uint() + pinfo.cols.info:append(" (Data Packet)(Seq Num:" .. seq_num .. ")") + + -- The first 4 bytes are the package sequence number + subtree:add(fields.seq_num, tvb(offset, 4)) + offset = offset + 4 + + data_flag_info_tree = subtree:add(fields.data_flag_info_tree, tvb(offset, 1)) + -- Handle FF flag + local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6) + if FF_state == 0 then + data_flag_info_tree:append_text(" [Middle packet]") + elseif FF_state == 1 then + data_flag_info_tree:append_text(" [Last packet]") + elseif FF_state == 2 then + data_flag_info_tree:append_text(" [First packet]") + else + data_flag_info_tree:append_text(" [Single packet]") + end + data_flag_info_tree:add(fields.FF_state, tvb(offset, 1)) + + -- Handle O flag + local O_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0x20), 5) + if O_state == 0 then + data_flag_info_tree:append_text(" [Data delivered unordered]") + else + data_flag_info_tree:append_text(" [Data delivered in order]") + end + data_flag_info_tree:add(fields.O_state, tvb(offset, 1)) + + -- Handle KK flag + local KK_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0x18), 3) + if KK_state == 1 then + data_flag_info_tree:append_text(" [Encrypted with even key]") + elseif KK_state == 2 then + data_flag_info_tree:append_text(" [Encrypted with odd key]") + end + data_flag_info_tree:add(fields.KK_state, tvb(offset, 1)) + + -- Handle R flag + local R_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0x04), 2) + if R_state == 1 then + data_flag_info_tree:append_text(" [Retransmit packet]") + pinfo.cols.info:append(" [Retransmit packet]") + end + data_flag_info_tree:add(fields.R_state, tvb(offset, 1)) + + -- Handle message number + local msg_num = tvb(offset, 4):uint() + msg_num = bit.band(tvb(offset, 4):uint(), 0x03FFFFFF) + -- subtree:add(fields.msg_num, bit.band(tvb(offset, 4):uint(), 0x03FFFFFF)) + subtree:add(fields.msg_num, tvb(offset, 4), msg_num) + offset = offset + 4 + + -- Handle Timestamp + subtree:add(fields.time_stamp, tvb(offset, 4)):append_text(" μs") + offset = offset + 4 + + -- Handle destination socket + subtree:add(fields.dst_sock, tvb(offset, 4)) + offset = offset + 4 + end +end + +-- Add the protocol into udp table +local port = 1935 + +local function enable_dissector() + DissectorTable.get("udp.port"):add(port, srt_dev) +end + +-- Call it now - enabled by default +enable_dissector() + +local function disable_dissector() + DissectorTable.get("udp.port"):remove(port, srt_dev) +end + +-- Prefs changed will listen at new port +function srt_dev.prefs_changed() + if port ~= srt_dev.prefs.srt_udp_port then + if port ~= 0 then + disable_dissector() + end + + port = srt_dev.prefs.srt_udp_port + + if port ~= 0 then + enable_dissector() + end + end +end diff --git a/trunk/3rdparty/srt-1-fit/scripts/tcp-echo-client.tcl b/trunk/3rdparty/srt-1-fit/scripts/tcp-echo-client.tcl index 78ddf81250..f141b0fbdf 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/tcp-echo-client.tcl +++ b/trunk/3rdparty/srt-1-fit/scripts/tcp-echo-client.tcl @@ -41,7 +41,7 @@ proc ReadBack {fd} { # Nothing more to read if {$remain == 0} { - puts stderr "NOTHING MORE TO BE WRITTEN - exitting" + puts stderr "NOTHING MORE TO BE WRITTEN - exiting" set ::theend 1 return } diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/.gitignore b/trunk/3rdparty/srt-1-fit/scripts/win-installer/.gitignore new file mode 100644 index 0000000000..ed44eef33e --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/.gitignore @@ -0,0 +1,10 @@ +tmp +installers +*.exe +*~ +~* +.#* +*.bak +*.autosave +.DS_Store +._* diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/README.md b/trunk/3rdparty/srt-1-fit/scripts/win-installer/README.md new file mode 100644 index 0000000000..ff54df2588 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/README.md @@ -0,0 +1,123 @@ +# SRT Static Libraries Installer for Windows + +This directory contains scripts to build a binary installer for +libsrt on Windows systems for Visual Studio applications using SRT. + +## SRT developer: Building the libsrt installer + +### Prerequisites + +These first two steps need to be executed once only. + +- Prerequisite 1: Install OpenSSL for Windows, both 64 and 32 bits. + This can be done automatically by running the PowerShell script `install-openssl.ps1`. + +- Prerequisite 2: Install NSIS, the NullSoft Installation Scripting system. + This can be done automatically by running the PowerShell script `install-nsis.ps1`. + +### Building the libsrt installer + +To build the libsrt installer, simply run the PowerShell script `build-win-installer.ps1`. +Running it without parameters, for instance launching it from the Windows Explorer, is +sufficient to build the installer. + +Optional parameters: + +- `-Version name` : + Use the specified string as version number for libsrt. By default, if the + current commit has a tag, use that tag (initial "v" removed, for instance + `1.4.3`). Otherwise, the defaut version is a detailed version number (most + recent version, number of commits since then, short commit SHA, for instance + `1.4.3-32-g22cc924`). Use that option if necessary to specify some other + non-standard form of version string. + +- `-NoPause` : + Do not wait for the user to press `` at end of execution. By default, + execute a `pause` instruction at the end of execution, which is useful + when the script was launched from Windows Explorer. Use that option when the + script is invoked from another PowerShell script. + +The installer is then available in the directory `installers`. + +The name of the installer is `libsrt-VERS.exe` where `VERS` is the SRT version number +(see the `-Version` option). + +The installer shall then be published as a release asset in the `srt` repository +on GitHub, either as `libsrt-VERS.exe` or `libsrt-VERS-win-installer.zip`. +In the latter case, the archive shall contain `libsrt-VERS.exe`. + +## SRT user: Using the libsrt installer + +### Installing the SRT libraries + +To install the SRT libraries, simply run the `libsrt-VERS.exe` installer which is +available in the [SRT release area](https://github.com/Haivision/srt/releases). + +After installing the libsrt binaries, an environment variable named `LIBSRT` is +defined with the installation root (typically `C:\Program Files (x86)\libsrt`). + +If there is a need for automation, in a CI/CD pipeline for instance, the download +of the latest `libsrt-VERS.exe` and its installation can be automated using the +sample PowerShell script `install-libsrt.ps1` which is available in this directory. +This script may be freely copied in the user's build environment. + +When run without parameters (for instance from the Windows explorer), this +script downloads and installs the latest version of libsrt. + +Optional parameters: + +- `-Destination path` : + Specify a local directory where the libsrt package will be downloaded. + By default, use the `tmp` subdirectory from this script's directory. + +- `-ForceDownload` : + Force a download even if the package is already downloaded in the + destination path. Note that the latest version is always checked. + If a older package is already present but a newer one is available + online, the newer one is always downloaded, even without this option. + +- `-GitHubActions` : + When used in a GitHub Actions workflow, make sure that the `LIBSRT` + environment variable is propagated to subsequent jobs. In your GitHub + workflow, in the initial setup phase, use + `script-dir\install-libsrt.ps1 -GitHubActions -NoPause`. + +- `-NoInstall` : + Do not install the package, only download it. By default, libsrt is installed. + +- `-NoPause` : + Do not wait for the user to press `` at end of execution. By default, + execute a `pause` instruction at the end of execution, which is useful + when the script was launched from Windows Explorer. Use that option when the + script is invoked from another PowerShell script. + +### Building Windows applications with libsrt + +In the SRT installation root directory (specified in environment variable `LIBSRT`), +there is a Visual Studio property file named `libsrt.props`. Simply reference this +property file in your Visual Studio project to use libsrt. + +You can also do that manually by editing the application project file (the XML +file named with a `.vcxproj` extension). Add the following line just before +the end of the file: + +~~~ + +~~~ + +With this setup, just compile your application normally, either using the +Visual Studio IDE or the MSBuild command line tool. + +## Files reference + +This directory contains the following files: + +| File name | Usage +| ----------------------- | ----- +| build-win-installer.ps1 | PowerShell script to build the libsrt installer. +| install-libsrt.ps1 | Sample PowerShell script to automatically install libsrt (for user's projects). +| install-openssl.ps1 | PowerShell script to install OpenSSL (prerequisite to build the installer). +| install-nsis.ps1 | PowerShell script to install NSIS (prerequisite to build the installer). +| libsrt.nsi | NSIS installation script (used to build the installer). +| libsrt.props | Visual Studio property files to use libsrt (embedded in the installer). +| README.md | This text file. diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/build-win-installer.ps1 b/trunk/3rdparty/srt-1-fit/scripts/win-installer/build-win-installer.ps1 new file mode 100644 index 0000000000..4265edf8e8 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/build-win-installer.ps1 @@ -0,0 +1,227 @@ +#----------------------------------------------------------------------------- +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021, Thierry Lelegard +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#----------------------------------------------------------------------------- + +<# + .SYNOPSIS + + Build the SRT static libraries installer for Windows. + + .PARAMETER Version + + Use the specified string as version number from libsrt. By default, if + the current commit has a tag, use that tag (initial 'v' removed). Otherwise, + the defaut version is a detailed version number (most recent version, number + of commits since then, short commit SHA). + + .PARAMETER NoPause + + Do not wait for the user to press at end of execution. By default, + execute a "pause" instruction at the end of execution, which is useful + when the script was run from Windows Explorer. + +#> +[CmdletBinding()] +param( + [string]$Version = "", + [switch]$NoPause = $false +) +Write-Output "Building the SRT static libraries installer for Windows" + +# Directory containing this script: +$ScriptDir = $PSScriptRoot + +# The root of the srt repository is two levels up. +$RepoDir = (Split-Path -Parent (Split-Path -Parent $ScriptDir)) + +# Output directory for final installers: +$OutDir = "$ScriptDir\installers" + +# Temporary directory for build operations: +$TmpDir = "$ScriptDir\tmp" + + +#----------------------------------------------------------------------------- +# A function to exit this script with optional error message, using -NoPause +#----------------------------------------------------------------------------- + +function Exit-Script([string]$Message = "") +{ + $Code = 0 + if ($Message -ne "") { + Write-Output "ERROR: $Message" + $Code = 1 + } + if (-not $NoPause) { + pause + } + exit $Code +} + + +#----------------------------------------------------------------------------- +# Build SRT version strings +#----------------------------------------------------------------------------- + +# By default, let git format a decent version number. +if (-not $Version) { + $Version = (git describe --tags ) -replace '-g','-' +} +$Version = $Version -replace '^v','' + +# Split version string in pieces and make sure to get at least four elements. +$VField = ($Version -split "[-\. ]") + @("0", "0", "0", "0") | Select-String -Pattern '^\d*$' +$VersionInfo = "$($VField[0]).$($VField[1]).$($VField[2]).$($VField[3])" + +Write-Output "SRT version: $Version" +Write-Output "Windows version info: $VersionInfo" + + +#----------------------------------------------------------------------------- +# Initialization phase, verify prerequisites +#----------------------------------------------------------------------------- + +# Locate OpenSSL root from local installation. +$SslRoot = @{ + "x64" = "C:\Program Files\OpenSSL-Win64"; + "Win32" = "C:\Program Files (x86)\OpenSSL-Win32" +} + +# Verify OpenSSL directories. +$Missing = 0 +foreach ($file in @($SslRoot["x64"], $SslRoot["Win32"])) { + if (-not (Test-Path $file)) { + Write-Output "**** Missing $file" + $Missing = $Missing + 1 + } +} +if ($Missing -gt 0) { + Exit-Script "Missing $Missing OpenSSL files, use install-openssl.ps1 to install OpenSSL" +} + +# Locate MSBuild and CMake, regardless of Visual Studio version. +Write-Output "Searching MSBuild ..." +$MSRoots = @("C:\Program Files*\MSBuild", "C:\Program Files*\Microsoft Visual Studio", "C:\Program Files*\CMake*") +$MSBuild = Get-ChildItem -Recurse -Path $MSRoots -Include MSBuild.exe -ErrorAction Ignore | + ForEach-Object { (Get-Command $_).FileVersionInfo } | + Sort-Object -Unique -Property FileVersion | + ForEach-Object { $_.FileName} | + Select-Object -Last 1 +if (-not $MSBuild) { + Exit-Script "MSBuild not found" +} + +Write-Output "Searching CMake ..." +$CMake = Get-ChildItem -Recurse -Path $MSRoots -Include cmake.exe -ErrorAction Ignore | + ForEach-Object { (Get-Command $_).FileVersionInfo } | + Sort-Object -Unique -Property FileVersion | + ForEach-Object { $_.FileName} | + Select-Object -Last 1 +if (-not $CMake) { + Exit-Script "CMake not found, check option 'C++ CMake tools for Windows' in Visual Studio installer" +} + +# Locate NSIS, the Nullsoft Scriptable Installation System. +Write-Output "Searching NSIS ..." +$NSIS = Get-Item "C:\Program Files*\NSIS\makensis.exe" | ForEach-Object { $_.FullName} | Select-Object -Last 1 +if (-not $NSIS) { + Exit-Script "NSIS not found, use install-nsis.ps1 to install NSIS" +} + +Write-Output "MSBuild: $MSBuild" +Write-Output "CMake: $CMake" +Write-Output "NSIS: $NSIS" + +# Create the directories for builds when necessary. +[void](New-Item -Path $TmpDir -ItemType Directory -Force) +[void](New-Item -Path $OutDir -ItemType Directory -Force) + + +#----------------------------------------------------------------------------- +# Configure and build SRT library using CMake on two architectures. +#----------------------------------------------------------------------------- + +foreach ($Platform in @("x64", "Win32")) { + + # Build directory. Cleanup to force a fresh cmake config. + $BuildDir = "$TmpDir\build.$Platform" + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir + [void](New-Item -Path $BuildDir -ItemType Directory -Force) + + # Run CMake. + Write-Output "Configuring build for platform $Platform ..." + $SRoot = $SslRoot[$Platform] + & $CMake -S $RepoDir -B $BuildDir -A $Platform ` + -DENABLE_STDCXX_SYNC=ON ` + -DOPENSSL_ROOT_DIR="$SRoot" ` + -DOPENSSL_LIBRARIES="$SRoot\lib\libssl_static.lib;$SRoot\lib\libcrypto_static.lib" ` + -DOPENSSL_INCLUDE_DIR="$SRoot\include" + + # Patch version string in version.h + Get-Content "$BuildDir\version.h" | + ForEach-Object { + $_ -replace "#define *SRT_VERSION_STRING .*","#define SRT_VERSION_STRING `"$Version`"" + } | + Out-File "$BuildDir\version.new" -Encoding ascii + Move-Item "$BuildDir\version.new" "$BuildDir\version.h" -Force + + # Compile SRT. + Write-Output "Building for platform $Platform ..." + foreach ($Conf in @("Release", "Debug")) { + & $MSBuild "$BuildDir\SRT.sln" /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static + } +} + +# Verify the presence of compiled libraries. +Write-Output "Checking compiled libraries ..." +$Missing = 0 +foreach ($Conf in @("Release", "Debug")) { + foreach ($Platform in @("x64", "Win32")) { + $Path = "$TmpDir\build.$Platform\$Conf\srt_static.lib" + if (-not (Test-Path $Path)) { + Write-Output "**** Missing $Path" + $Missing = $Missing + 1 + } + } +} +if ($Missing -gt 0) { + Exit-Script "Missing $Missing files" +} + + +#----------------------------------------------------------------------------- +# Build the binary installer. +#----------------------------------------------------------------------------- + +$InstallExe = "$OutDir\libsrt-$Version.exe" +$InstallZip = "$OutDir\libsrt-$Version-win-installer.zip" + +Write-Output "Building installer ..." +& $NSIS /V2 ` + /DVersion="$Version" ` + /DVersionInfo="$VersionInfo" ` + /DOutDir="$OutDir" ` + /DBuildRoot="$TmpDir" ` + /DRepoDir="$RepoDir" ` + "$ScriptDir\libsrt.nsi" + +if (-not (Test-Path $InstallExe)) { + Exit-Script "**** Missing $InstallExe" +} + +Write-Output "Building installer archive ..." +Remove-Item -Force -ErrorAction SilentlyContinue $InstallZip +Compress-Archive -Path $InstallExe -DestinationPath $InstallZip -CompressionLevel Optimal + +if (-not (Test-Path $InstallZip)) { + Exit-Script "**** Missing $InstallZip" +} + +Exit-Script diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-libsrt.ps1 b/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-libsrt.ps1 new file mode 100644 index 0000000000..ae1b8133e6 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-libsrt.ps1 @@ -0,0 +1,131 @@ +# SRT library download and install for Windows. +# Copyright (c) 2021, Thierry Lelegard +# All rights reserved. + +<# + .SYNOPSIS + + Download and install the libsrt library for Windows. This script is + provided to automate the build of Windows applications using libsrt. + + .PARAMETER Destination + + Specify a local directory where the libsrt package will be downloaded. + By default, use "tmp" subdirectory from this script. + + .PARAMETER ForceDownload + + Force a download even if the package is already downloaded. + + .PARAMETER GitHubActions + + When used in a GitHub Actions workflow, make sure that the LIBSRT + environment variable is propagated to subsequent jobs. + + .PARAMETER NoInstall + + Do not install the package. By default, libsrt is installed. + + .PARAMETER NoPause + + Do not wait for the user to press at end of execution. By default, + execute a "pause" instruction at the end of execution, which is useful + when the script was run from Windows Explorer. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [string]$Destination = "", + [switch]$ForceDownload = $false, + [switch]$GitHubActions = $false, + [switch]$NoInstall = $false, + [switch]$NoPause = $false +) + +Write-Output "libsrt download and installation procedure" + +# Default directory for downloaded products. +if (-not $Destination) { + $Destination = "$PSScriptRoot\tmp" +} + +# A function to exit this script. +function Exit-Script([string]$Message = "") +{ + $Code = 0 + if ($Message -ne "") { + Write-Output "ERROR: $Message" + $Code = 1 + } + if (-not $NoPause) { + pause + } + exit $Code +} + +# Without this, Invoke-WebRequest is awfully slow. +$ProgressPreference = 'SilentlyContinue' + +# Get the URL of the latest libsrt installer. +$URL = (Invoke-RestMethod "https://api.github.com/repos/Haivision/srt/releases?per_page=20" | + ForEach-Object { $_.assets } | + ForEach-Object { $_.browser_download_url } | + Select-String @("/libsrt-.*\.exe$", "/libsrt-.*-win-installer\.zip$") | + Select-Object -First 1) + +if (-not $URL) { + Exit-Script "Could not find a libsrt installer on GitHub" +} +if (-not ($URL -match "\.zip$") -and -not ($URL -match "\.exe$")) { + Exit-Script "Unexpected URL, not .exe, not .zip: $URL" +} + +# Installer name and path. +$InstName = (Split-Path -Leaf $URL) +$InstPath = "$Destination\$InstName" + +# Create the directory for downloaded products when necessary. +[void](New-Item -Path $Destination -ItemType Directory -Force) + +# Download installer +if (-not $ForceDownload -and (Test-Path $InstPath)) { + Write-Output "$InstName already downloaded, use -ForceDownload to download again" +} +else { + Write-Output "Downloading $URL ..." + Invoke-WebRequest $URL.ToString() -UseBasicParsing -UserAgent Download -OutFile $InstPath + if (-not (Test-Path $InstPath)) { + Exit-Script "$URL download failed" + } +} + +# If installer is an archive, expect an exe with same name inside. +if ($InstName -match "\.zip$") { + + # Expected installer name in archive. + $ZipName = $InstName + $ZipPath = $InstPath + $InstName = $ZipName -replace '-win-installer.zip','.exe' + $InstPath = "$Destination\$InstName" + + # Extract the installer. + Remove-Item -Force $InstPath -ErrorAction SilentlyContinue + Write-Output "Expanding $ZipName ..." + Expand-Archive $ZipPath -DestinationPath $Destination + if (-not (Test-Path $InstPath)) { + Exit-Script "$InstName not found in $ZipName" + } +} + +# Install libsrt +if (-not $NoInstall) { + Write-Output "Installing $InstName" + Start-Process -FilePath $InstPath -ArgumentList @("/S") -Wait +} + +# Propagate LIBSRT in next jobs for GitHub Actions. +if ($GitHubActions -and (-not -not $env:GITHUB_ENV) -and (Test-Path $env:GITHUB_ENV)) { + $libsrt = [System.Environment]::GetEnvironmentVariable("LIBSRT","Machine") + Write-Output "LIBSRT=$libsrt" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append +} + +Exit-Script diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-nsis.ps1 b/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-nsis.ps1 new file mode 100644 index 0000000000..14879b5217 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-nsis.ps1 @@ -0,0 +1,122 @@ +#----------------------------------------------------------------------------- +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021, Thierry Lelegard +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#----------------------------------------------------------------------------- + +<# + .SYNOPSIS + + Download, expand and install NSIS, the NullSoft Installer Scripting. + + .PARAMETER ForceDownload + + Force a download even if NSIS is already downloaded. + + .PARAMETER NoInstall + + Do not install the NSIS package. By default, NSIS is installed. + + .PARAMETER NoPause + + Do not wait for the user to press at end of execution. By default, + execute a "pause" instruction at the end of execution, which is useful + when the script was run from Windows Explorer. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [switch]$ForceDownload = $false, + [switch]$NoInstall = $false, + [switch]$NoPause = $false +) + +Write-Output "NSIS download and installation procedure" +$NSISPage = "https://nsis.sourceforge.io/Download" +$FallbackURL = "http://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download" + +# A function to exit this script. +function Exit-Script([string]$Message = "") +{ + $Code = 0 + if ($Message -ne "") { + Write-Output "ERROR: $Message" + $Code = 1 + } + if (-not $NoPause) { + pause + } + exit $Code +} + +# Local file names. +$RootDir = $PSScriptRoot +$TmpDir = "$RootDir\tmp" + +# Create the directory for external products when necessary. +[void] (New-Item -Path $TmpDir -ItemType Directory -Force) + +# Without this, Invoke-WebRequest is awfully slow. +$ProgressPreference = 'SilentlyContinue' + +# Get the HTML page for NSIS downloads. +$status = 0 +$message = "" +$Ref = $null +try { + $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $NSISPage + $status = [int] [Math]::Floor($response.StatusCode / 100) +} +catch { + $message = $_.Exception.Message +} + +if ($status -ne 1 -and $status -ne 2) { + # Error fetch NSIS download page. + if ($message -eq "" -and (Test-Path variable:response)) { + Write-Output "Status code $($response.StatusCode), $($response.StatusDescription)" + } + else { + Write-Output "#### Error accessing ${NSISPage}: $message" + } +} +else { + # Parse HTML page to locate the latest installer. + $Ref = $response.Links.href | Where-Object { $_ -like "*/nsis-*-setup.exe?download" } | Select-Object -First 1 +} + +if (-not $Ref) { + # Could not find a reference to NSIS installer. + $Url = [System.Uri]$FallbackURL +} +else { + # Build the absolute URL's from base URL (the download page) and href links. + $Url = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$NSISPage, $Ref) +} + +$InstallerName = (Split-Path -Leaf $Url.LocalPath) +$InstallerPath = "$TmpDir\$InstallerName" + +# Download installer +if (-not $ForceDownload -and (Test-Path $InstallerPath)) { + Write-Output "$InstallerName already downloaded, use -ForceDownload to download again" +} +else { + Write-Output "Downloading $Url ..." + Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $InstallerPath + if (-not (Test-Path $InstallerPath)) { + Exit-Script "$Url download failed" + } +} + +# Install NSIS +if (-not $NoInstall) { + Write-Output "Installing $InstallerName" + Start-Process -FilePath $InstallerPath -ArgumentList @("/S") -Wait +} + +Exit-Script diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-openssl.ps1 b/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-openssl.ps1 new file mode 100644 index 0000000000..83b59954fe --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/install-openssl.ps1 @@ -0,0 +1,119 @@ +#----------------------------------------------------------------------------- +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021, Thierry Lelegard +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#----------------------------------------------------------------------------- + +<# + .SYNOPSIS + + Download, expand and install OpenSSL for Windows. + + .PARAMETER ForceDownload + + Force a download even if the OpenSSL installers are already downloaded. + + .PARAMETER NoInstall + + Do not install the OpenSSL packages. By default, OpenSSL is installed. + + .PARAMETER NoPause + + Do not wait for the user to press at end of execution. By default, + execute a "pause" instruction at the end of execution, which is useful + when the script was run from Windows Explorer. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [switch]$ForceDownload = $false, + [switch]$NoInstall = $false, + [switch]$NoPause = $false +) + +Write-Output "OpenSSL download and installation procedure" +$OpenSSLHomePage = "http://slproweb.com/products/Win32OpenSSL.html" + +# A function to exit this script. +function Exit-Script([string]$Message = "") +{ + $Code = 0 + if ($Message -ne "") { + Write-Output "ERROR: $Message" + $Code = 1 + } + if (-not $NoPause) { + pause + } + exit $Code +} + +# Local file names. +$RootDir = $PSScriptRoot +$TmpDir = "$RootDir\tmp" + +# Create the directory for external products when necessary. +[void] (New-Item -Path $TmpDir -ItemType Directory -Force) + +# Without this, Invoke-WebRequest is awfully slow. +$ProgressPreference = 'SilentlyContinue' + +# Get the HTML page for OpenSSL downloads. +$status = 0 +$message = "" +try { + $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $OpenSSLHomePage + $status = [int] [Math]::Floor($response.StatusCode / 100) +} +catch { + $message = $_.Exception.Message +} +if ($status -ne 1 -and $status -ne 2) { + if ($message -eq "" -and (Test-Path variable:response)) { + Exit-Script "Status code $($response.StatusCode), $($response.StatusDescription)" + } + else { + Exit-Script "#### Error accessing ${OpenSSLHomePage}: $message" + } +} + +# Parse HTML page to locate the latest MSI files. +$Ref32 = $response.Links.href | Where-Object { $_ -like "*/Win32OpenSSL-*.msi" } | Select-Object -First 1 +$Ref64 = $response.Links.href | Where-Object { $_ -like "*/Win64OpenSSL-*.msi" } | Select-Object -First 1 + +# Build the absolute URL's from base URL (the download page) and href links. +$Url32 = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$OpenSSLHomePage, $Ref32) +$Url64 = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$OpenSSLHomePage, $Ref64) + +# Download and install one MSI package. +function Download-Install([string]$Url) +{ + $MsiName = (Split-Path -Leaf $Url.toString()) + $MsiPath = "$TmpDir\$MsiName" + + if (-not $ForceDownload -and (Test-Path $MsiPath)) { + Write-Output "$MsiName already downloaded, use -ForceDownload to download again" + } + else { + Write-Output "Downloading $Url ..." + Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $MsiPath + } + + if (-not (Test-Path $MsiPath)) { + Exit-Script "$Url download failed" + } + + if (-not $NoInstall) { + Write-Output "Installing $MsiName" + Start-Process msiexec.exe -ArgumentList @("/i", $MsiPath, "/qn", "/norestart") -Wait + } +} + +# Download and install the two MSI packages. +Download-Install $Url32 +Download-Install $Url64 +Exit-Script diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.nsi b/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.nsi new file mode 100644 index 0000000000..4628642cc8 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.nsi @@ -0,0 +1,217 @@ +;----------------------------------------------------------------------------- +; +; SRT - Secure, Reliable, Transport +; Copyright (c) 2021, Thierry Lelegard +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +;----------------------------------------------------------------------------- +; +; NSIS script to build the SRT binary installer for Windows. +; Do not invoke NSIS directly, use PowerShell script build-win-installer.ps1 +; to ensure that all parameters are properly passed. +; +;----------------------------------------------------------------------------- + +Name "SRT" +Caption "SRT Libraries Installer" + +!verbose push +!verbose 0 +!include "MUI2.nsh" +!include "Sections.nsh" +!include "TextFunc.nsh" +!include "FileFunc.nsh" +!include "WinMessages.nsh" +!include "x64.nsh" +!verbose pop + +!define ProductName "libsrt" +!define Build32Dir "${BuildRoot}\build.Win32" +!define Build64Dir "${BuildRoot}\build.x64" +!define SSL32Dir "C:\Program Files (x86)\OpenSSL-Win32" +!define SSL64Dir "C:\Program Files\OpenSSL-Win64" + +; Installer file information. +VIProductVersion ${VersionInfo} +VIAddVersionKey ProductName "${ProductName}" +VIAddVersionKey ProductVersion "${Version}" +VIAddVersionKey Comments "The SRT static libraries for Visual C++ on Windows" +VIAddVersionKey CompanyName "Haivision" +VIAddVersionKey LegalCopyright "Copyright (c) 2021 Haivision Systems Inc." +VIAddVersionKey FileVersion "${VersionInfo}" +VIAddVersionKey FileDescription "SRT Installer" + +; Name of binary installer file. +OutFile "${OutDir}\${ProductName}-${Version}.exe" + +; Generate a Unicode installer (default is ANSI). +Unicode true + +; Registry key for environment variables +!define EnvironmentKey '"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +; Registry entry for product info and uninstallation info. +!define ProductKey "Software\${ProductName}" +!define UninstallKey "Software\Microsoft\Windows\CurrentVersion\Uninstall\${ProductName}" + +; Use XP manifest. +XPStyle on + +; Request administrator privileges for Windows Vista and higher. +RequestExecutionLevel admin + +; "Modern User Interface" (MUI) settings. +!define MUI_ABORTWARNING + +; Default installation folder. +InstallDir "$PROGRAMFILES\${ProductName}" + +; Get installation folder from registry if available from a previous installation. +InstallDirRegKey HKLM "${ProductKey}" "InstallDir" + +; Installer pages. +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES + +; Uninstaller pages. +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +; Languages. +!insertmacro MUI_LANGUAGE "English" + +; Installation initialization. +function .onInit + ; In 64-bit installers, don't use registry redirection. + ${If} ${RunningX64} + SetRegView 64 + ${EndIf} +functionEnd + +; Uninstallation initialization. +function un.onInit + ; In 64-bit installers, don't use registry redirection. + ${If} ${RunningX64} + SetRegView 64 + ${EndIf} +functionEnd + +; Installation section +Section "Install" + + ; Work on "all users" context, not current user. + SetShellVarContext all + + ; Delete obsolete files from previous versions. + Delete "$INSTDIR\LICENSE.pthread.txt" + Delete "$INSTDIR\include\srt\srt4udt.h" + Delete "$INSTDIR\include\srt\udt.h" + Delete "$INSTDIR\lib\Release-x64\pthread.lib" + Delete "$INSTDIR\lib\Release-Win32\pthread.lib" + Delete "$INSTDIR\lib\Debug-x64\srt.pdb" + Delete "$INSTDIR\lib\Debug-x64\pthread.pdb" + Delete "$INSTDIR\lib\Debug-x64\pthread.lib" + Delete "$INSTDIR\lib\Debug-Win32\srt.pdb" + Delete "$INSTDIR\lib\Debug-Win32\pthread.pdb" + Delete "$INSTDIR\lib\Debug-Win32\pthread.lib" + + SetOutPath "$INSTDIR" + File /oname=LICENSE.txt "${RepoDir}\LICENSE" + File "libsrt.props" + + ; Header files. + CreateDirectory "$INSTDIR\include\srt" + SetOutPath "$INSTDIR\include\srt" + File "${RepoDir}\srtcore\logging_api.h" + File "${RepoDir}\srtcore\platform_sys.h" + File "${RepoDir}\srtcore\srt.h" + File "${Build64Dir}\version.h" + + CreateDirectory "$INSTDIR\include\win" + SetOutPath "$INSTDIR\include\win" + File "${RepoDir}\common\win\syslog_defs.h" + + ; Libraries. + CreateDirectory "$INSTDIR\lib" + + CreateDirectory "$INSTDIR\lib\Release-x64" + SetOutPath "$INSTDIR\lib\Release-x64" + File /oname=srt.lib "${Build64Dir}\Release\srt_static.lib" + File /oname=libcrypto.lib "${SSL64Dir}\lib\VC\static\libcrypto64MD.lib" + File /oname=libssl.lib "${SSL64Dir}\lib\VC\static\libssl64MD.lib" + + CreateDirectory "$INSTDIR\lib\Debug-x64" + SetOutPath "$INSTDIR\lib\Debug-x64" + File /oname=srt.lib "${Build64Dir}\Debug\srt_static.lib" + File /oname=libcrypto.lib "${SSL64Dir}\lib\VC\static\libcrypto64MDd.lib" + File /oname=libssl.lib "${SSL64Dir}\lib\VC\static\libssl64MDd.lib" + + CreateDirectory "$INSTDIR\lib\Release-Win32" + SetOutPath "$INSTDIR\lib\Release-Win32" + File /oname=srt.lib "${Build32Dir}\Release\srt_static.lib" + File /oname=libcrypto.lib "${SSL32Dir}\lib\VC\static\libcrypto32MD.lib" + File /oname=libssl.lib "${SSL32Dir}\lib\VC\static\libssl32MD.lib" + + CreateDirectory "$INSTDIR\lib\Debug-Win32" + SetOutPath "$INSTDIR\lib\Debug-Win32" + File /oname=srt.lib "${Build32Dir}\Debug\srt_static.lib" + File /oname=libcrypto.lib "${SSL32Dir}\lib\VC\static\libcrypto32MDd.lib" + File /oname=libssl.lib "${SSL32Dir}\lib\VC\static\libssl32MDd.lib" + + ; Add an environment variable to installation root. + WriteRegStr HKLM ${EnvironmentKey} "LIBSRT" "$INSTDIR" + + ; Store installation folder in registry. + WriteRegStr HKLM "${ProductKey}" "InstallDir" $INSTDIR + + ; Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + + ; Declare uninstaller in "Add/Remove Software" control panel + WriteRegStr HKLM "${UninstallKey}" "DisplayName" "${ProductName}" + WriteRegStr HKLM "${UninstallKey}" "Publisher" "Haivision" + WriteRegStr HKLM "${UninstallKey}" "URLInfoAbout" "https://github.com/Haivision/srt" + WriteRegStr HKLM "${UninstallKey}" "DisplayVersion" "${Version}" + WriteRegStr HKLM "${UninstallKey}" "DisplayIcon" "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "${UninstallKey}" "UninstallString" "$INSTDIR\Uninstall.exe" + + ; Get estimated size of installed files + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UninstallKey}" "EstimatedSize" "$0" + + ; Notify applications of environment modifications + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +SectionEnd + +; Uninstallation section +Section "Uninstall" + + ; Work on "all users" context, not current user. + SetShellVarContext all + + ; Get installation folder from registry + ReadRegStr $0 HKLM "${ProductKey}" "InstallDir" + + ; Delete product registry entries + DeleteRegKey HKCU "${ProductKey}" + DeleteRegKey HKLM "${ProductKey}" + DeleteRegKey HKLM "${UninstallKey}" + DeleteRegValue HKLM ${EnvironmentKey} "LIBSRT" + + ; Delete product files. + RMDir /r "$0\include" + RMDir /r "$0\lib" + Delete "$0\libsrt.props" + Delete "$0\LICENSE*" + Delete "$0\Uninstall.exe" + RMDir "$0" + + ; Notify applications of environment modifications + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + +SectionEnd diff --git a/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.props b/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.props new file mode 100644 index 0000000000..b1da748791 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.props @@ -0,0 +1,38 @@ + + + + + + + + + + + Win32 + + + + + x64 + + + + + $(Platform) + + + + + + + + $(LIBSRT)\include;%(AdditionalIncludeDirectories) + + + srt.lib;libssl.lib;libcrypto.lib;crypt32.lib;ws2_32.lib;%(AdditionalDependencies) + $(LIBSRT)\lib\$(Configuration)-$(SrtPlatform);%(AdditionalLibraryDirectories) + /ignore:4099 %(AdditionalOptions) + + + + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.cpp b/trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.cpp deleted file mode 100644 index f4ad38608e..0000000000 --- a/trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.cpp +++ /dev/null @@ -1,360 +0,0 @@ -/* - * SRT - Secure, Reliable, Transport - * Copyright (c) 2018 Haivision Systems Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - */ - -/***************************************************************************** -Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the - above copyright notice, this list of conditions - and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the University of Illinois - nor the names of its contributors may be used to - endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*****************************************************************************/ - -/***************************************************************************** -written by - Yunhong Gu, last updated 02/21/2013 -modified by - Haivision Systems Inc. -*****************************************************************************/ - - -#include "core.h" -#include "ccc.h" -#include -#include - -CCC::CCC(): -m_iSYNInterval(CUDT::m_iSYNInterval), -m_dPktSndPeriod(1.0), -m_dCWndSize(16.0), -m_iBandwidth(), -m_dMaxCWndSize(), -m_iMSS(), -m_iSndCurrSeqNo(), -m_iRcvRate(), -m_iRTT(), -m_pcParam(NULL), -m_iPSize(0), -m_UDT(), -m_iACKPeriod(0), -m_iACKInterval(0), -m_bUserDefinedRTO(false), -m_iRTO(-1), -m_PerfInfo() -{ -} - -CCC::~CCC() -{ - delete [] m_pcParam; -} - -void CCC::setACKTimer(int msINT) -{ - m_iACKPeriod = msINT > m_iSYNInterval ? m_iSYNInterval : msINT; -} - -void CCC::setACKInterval(int pktINT) -{ - m_iACKInterval = pktINT; -} - -void CCC::setRTO(int usRTO) -{ - m_bUserDefinedRTO = true; - m_iRTO = usRTO; -} - -void CCC::sendCustomMsg(CPacket& pkt) const -{ - CUDT* u = CUDT::getUDTHandle(m_UDT); - - if (NULL != u) - { - pkt.m_iID = u->m_PeerID; -#ifdef SRT_ENABLE_CTRLTSTAMP - pkt.m_iTimeStamp = int(CTimer::getTime() - u->m_StartTime); -#endif - u->m_pSndQueue->sendto(u->m_pPeerAddr, pkt); - } -} - -const CPerfMon* CCC::getPerfInfo() -{ - try - { - CUDT* u = CUDT::getUDTHandle(m_UDT); - if (NULL != u) - u->sample(&m_PerfInfo, false); - } - catch (...) - { - return NULL; - } - - return &m_PerfInfo; -} - -void CCC::setMSS(int mss) -{ - m_iMSS = mss; -} - -void CCC::setBandwidth(int bw) -{ - m_iBandwidth = bw; -} - -void CCC::setSndCurrSeqNo(int32_t seqno) -{ - m_iSndCurrSeqNo = seqno; -} - -void CCC::setRcvRate(int rcvrate) -{ - m_iRcvRate = rcvrate; -} - -void CCC::setMaxCWndSize(int cwnd) -{ - m_dMaxCWndSize = cwnd; -} - -void CCC::setRTT(int rtt) -{ - m_iRTT = rtt; -} - -void CCC::setUserParam(const char* param, int size) -{ - delete [] m_pcParam; - m_pcParam = new char[size]; - memcpy(m_pcParam, param, size); - m_iPSize = size; -} - -// -CUDTCC::CUDTCC(): -m_iRCInterval(), -m_LastRCTime(), -m_bSlowStart(), -m_iLastAck(), -m_bLoss(), -m_iLastDecSeq(), -m_dLastDecPeriod(), -m_iNAKCount(), -m_iDecRandom(), -m_iAvgNAKNum(), -m_iDecCount() -{ -} - -void CUDTCC::init() -{ - m_iRCInterval = m_iSYNInterval; - m_LastRCTime = CTimer::getTime(); - setACKTimer(m_iRCInterval); - - m_bSlowStart = true; - m_iLastAck = m_iSndCurrSeqNo; - m_bLoss = false; - m_iLastDecSeq = CSeqNo::decseq(m_iLastAck); - m_dLastDecPeriod = 1; - m_iAvgNAKNum = 0; - m_iNAKCount = 0; - m_iDecRandom = 1; - - m_dCWndSize = 16; - m_dPktSndPeriod = 1; -} - -void CUDTCC::onACK(int32_t ack) -{ - int64_t B = 0; - double inc = 0; - // Note: 1/24/2012 - // The minimum increase parameter is increased from "1.0 / m_iMSS" to 0.01 - // because the original was too small and caused sending rate to stay at low level - // for long time. - const double min_inc = 0.01; - - uint64_t currtime = CTimer::getTime(); - if (currtime - m_LastRCTime < (uint64_t)m_iRCInterval) - return; - - m_LastRCTime = currtime; - -#ifdef SRT_ENABLE_BSTATS - //m_iRcvRate is bytes/sec - if (m_bSlowStart) - { - m_dCWndSize += CSeqNo::seqlen(m_iLastAck, ack); - m_iLastAck = ack; - - if (m_dCWndSize > m_dMaxCWndSize) - { - m_bSlowStart = false; - if (m_iRcvRate > 0) - m_dPktSndPeriod = 1000000.0 / ((m_iRcvRate + m_iMSS - 1) / m_iMSS); - else - m_dPktSndPeriod = (m_iRTT + m_iRCInterval) / m_dCWndSize; - } - } - else - m_dCWndSize = ((m_iRcvRate + m_iMSS -1) / m_iMSS) / 1000000.0 * (m_iRTT + m_iRCInterval) + 16; -#else - if (m_bSlowStart) - { - m_dCWndSize += CSeqNo::seqlen(m_iLastAck, ack); - m_iLastAck = ack; - - if (m_dCWndSize > m_dMaxCWndSize) - { - m_bSlowStart = false; - if (m_iRcvRate > 0) - m_dPktSndPeriod = 1000000.0 / m_iRcvRate; - else - m_dPktSndPeriod = (m_iRTT + m_iRCInterval) / m_dCWndSize; - } - } - else - m_dCWndSize = m_iRcvRate / 1000000.0 * (m_iRTT + m_iRCInterval) + 16; -#endif - - // During Slow Start, no rate increase - if (m_bSlowStart) - return; - - if (m_bLoss) - { - m_bLoss = false; - return; - } - - //m_iBandwidth is pkts/sec - B = (int64_t)(m_iBandwidth - 1000000.0 / m_dPktSndPeriod); - if ((m_dPktSndPeriod > m_dLastDecPeriod) && ((m_iBandwidth / 9) < B)) - B = m_iBandwidth / 9; - if (B <= 0) - inc = min_inc; - else - { - // inc = max(10 ^ ceil(log10( B * MSS * 8 ) * Beta / MSS, 1/MSS) - // Beta = 1.5 * 10^(-6) - - inc = pow(10.0, ceil(log10(B * m_iMSS * 8.0))) * 0.0000015 / m_iMSS; - - if (inc < min_inc) - inc = min_inc; - } - - m_dPktSndPeriod = (m_dPktSndPeriod * m_iRCInterval) / (m_dPktSndPeriod * inc + m_iRCInterval); -} - -void CUDTCC::onLoss(const int32_t* losslist, int) -{ - //Slow Start stopped, if it hasn't yet - if (m_bSlowStart) - { - m_bSlowStart = false; - if (m_iRcvRate > 0) - { - // Set the sending rate to the receiving rate. -#ifdef SRT_ENABLE_BSTATS - //Need average packet size here for better send period - m_dPktSndPeriod = 1000000.0 / ((m_iRcvRate + m_iMSS - 1) / m_iMSS); -#else - m_dPktSndPeriod = 1000000.0 / m_iRcvRate; -#endif - return; - } - // If no receiving rate is observed, we have to compute the sending - // rate according to the current window size, and decrease it - // using the method below. - m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval); - } - - m_bLoss = true; - - if (CSeqNo::seqcmp(losslist[0] & 0x7FFFFFFF, m_iLastDecSeq) > 0) - { - m_dLastDecPeriod = m_dPktSndPeriod; - m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.125); - - m_iAvgNAKNum = (int)ceil(m_iAvgNAKNum * 0.875 + m_iNAKCount * 0.125); - m_iNAKCount = 1; - m_iDecCount = 1; - - m_iLastDecSeq = m_iSndCurrSeqNo; - - // remove global synchronization using randomization - srand(m_iLastDecSeq); - m_iDecRandom = (int)ceil(m_iAvgNAKNum * (double(rand()) / RAND_MAX)); - if (m_iDecRandom < 1) - m_iDecRandom = 1; - } - else if ((m_iDecCount ++ < 5) && (0 == (++ m_iNAKCount % m_iDecRandom))) - { - // 0.875^5 = 0.51, rate should not be decreased by more than half within a congestion period - m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.125); - m_iLastDecSeq = m_iSndCurrSeqNo; - } -} - -void CUDTCC::onTimeout() -{ - if (m_bSlowStart) - { - m_bSlowStart = false; - if (m_iRcvRate > 0) -#ifdef SRT_ENABLE_BSTATS - // Need average packet size here - m_dPktSndPeriod = 1000000.0 / ((m_iRcvRate + m_iMSS - 1) / m_iMSS); -#else - m_dPktSndPeriod = 1000000.0 / m_iRcvRate; -#endif - else - m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval); - } - else - { - /* - m_dLastDecPeriod = m_dPktSndPeriod; - m_dPktSndPeriod = ceil(m_dPktSndPeriod * 2); - m_iLastDecSeq = m_iLastAck; - */ - } -} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.h b/trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.h deleted file mode 100644 index 5bf93032c9..0000000000 --- a/trunk/3rdparty/srt-1-fit/srtcore/ATTIC/ccc.h +++ /dev/null @@ -1,219 +0,0 @@ -/***************************************************************************** -Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the - above copyright notice, this list of conditions - and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the University of Illinois - nor the names of its contributors may be used to - endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS -IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*****************************************************************************/ - -/***************************************************************************** -written by - Yunhong Gu, last updated 02/28/2012 -*****************************************************************************/ - - -#ifndef __UDT_CCC_H__ -#define __UDT_CCC_H__ - - -#include "udt.h" -#include "packet.h" - - -class UDT_API CCC -{ -friend class CUDT; - -public: - CCC(); - virtual ~CCC(); - -private: - CCC(const CCC&); - CCC& operator=(const CCC&) {return *this;} - -public: - - /// Callback function to be called (only) at the start of a UDT connection. - /// note that this is different from CCC(), which is always called. - - virtual void init() {} - - /// Callback function to be called when a UDT connection is closed. - - virtual void close() {} - - /// Callback function to be called when an ACK packet is received. - /// @param [in] ackno the data sequence number acknowledged by this ACK. - - virtual void onACK(int32_t) {} - - /// Callback function to be called when a loss report is received. - /// @param [in] losslist list of sequence number of packets, in the format describled in packet.cpp. - /// @param [in] size length of the loss list. - - virtual void onLoss(const int32_t*, int) {} - - /// Callback function to be called when a timeout event occurs. - - virtual void onTimeout() {} - - /// Callback function to be called when a data is sent. - /// @param [in] seqno the data sequence number. - /// @param [in] size the payload size. - - virtual void onPktSent(const CPacket*) {} - - /// Callback function to be called when a data is received. - /// @param [in] seqno the data sequence number. - /// @param [in] size the payload size. - - virtual void onPktReceived(const CPacket*) {} - - /// Callback function to Process a user defined packet. - /// @param [in] pkt the user defined packet. - - virtual void processCustomMsg(const CPacket*) {} - -protected: - - /// Set periodical acknowldging and the ACK period. - /// @param [in] msINT the period to send an ACK. - - void setACKTimer(int msINT); - - /// Set packet-based acknowldging and the number of packets to send an ACK. - /// @param [in] pktINT the number of packets to send an ACK. - - void setACKInterval(int pktINT); - - /// Set RTO value. - /// @param [in] msRTO RTO in macroseconds. - - void setRTO(int usRTO); - - /// Send a user defined control packet. - /// @param [in] pkt user defined packet. - - void sendCustomMsg(CPacket& pkt) const; - - /// retrieve performance information. - /// @return Pointer to a performance info structure. - - const CPerfMon* getPerfInfo(); - - /// Set user defined parameters. - /// @param [in] param the paramters in one buffer. - /// @param [in] size the size of the buffer. - - void setUserParam(const char* param, int size); - -private: - void setMSS(int mss); - void setMaxCWndSize(int cwnd); - void setBandwidth(int bw); - void setSndCurrSeqNo(int32_t seqno); - void setRcvRate(int rcvrate); - void setRTT(int rtt); - -protected: - const int32_t& m_iSYNInterval; // UDT constant parameter, SYN - - double m_dPktSndPeriod; // Packet sending period, in microseconds - double m_dCWndSize; // Congestion window size, in packets - - int m_iBandwidth; // estimated bandwidth, packets per second - double m_dMaxCWndSize; // maximum cwnd size, in packets - - int m_iMSS; // Maximum Packet Size, including all packet headers - int32_t m_iSndCurrSeqNo; // current maximum seq no sent out - int m_iRcvRate; // packet arrive rate at receiver side, packets per second - int m_iRTT; // current estimated RTT, microsecond - - char* m_pcParam; // user defined parameter - int m_iPSize; // size of m_pcParam - -private: - UDTSOCKET m_UDT; // The UDT entity that this congestion control algorithm is bound to - - int m_iACKPeriod; // Periodical timer to send an ACK, in milliseconds - int m_iACKInterval; // How many packets to send one ACK, in packets - - bool m_bUserDefinedRTO; // if the RTO value is defined by users - int m_iRTO; // RTO value, microseconds - - CPerfMon m_PerfInfo; // protocol statistics information -}; - -class CCCVirtualFactory -{ -public: - virtual ~CCCVirtualFactory() {} - - virtual CCC* create() = 0; - virtual CCCVirtualFactory* clone() = 0; -}; - -template -class CCCFactory: public CCCVirtualFactory -{ -public: - virtual ~CCCFactory() {} - - virtual CCC* create() {return new T;} - virtual CCCVirtualFactory* clone() {return new CCCFactory;} -}; - -class CUDTCC: public CCC -{ -public: - CUDTCC(); - -public: - virtual void init(); - virtual void onACK(int32_t); - virtual void onLoss(const int32_t*, int); - virtual void onTimeout(); - -private: - int m_iRCInterval; // UDT Rate control interval - uint64_t m_LastRCTime; // last rate increase time - bool m_bSlowStart; // if in slow start phase - int32_t m_iLastAck; // last ACKed seq no - bool m_bLoss; // if loss happened since last rate increase - int32_t m_iLastDecSeq; // max pkt seq no sent out when last decrease happened - double m_dLastDecPeriod; // value of pktsndperiod when last decrease happened - int m_iNAKCount; // NAK counter - int m_iDecRandom; // random threshold on decrease by number of loss events - int m_iAvgNAKNum; // average number of NAKs per congestion - int m_iDecCount; // number of decreases in a congestion epoch -}; - -#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/access_control.h b/trunk/3rdparty/srt-1-fit/srtcore/access_control.h new file mode 100644 index 0000000000..97a1104a83 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/access_control.h @@ -0,0 +1,79 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2020 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +#ifndef INC_F_ACCESS_CONTROL_H +#define INC_F_ACCESS_CONTROL_H + +// A list of rejection codes that are SRT specific. + +#define SRT_REJX_FALLBACK 1000 // A code used in case when the application wants to report some problem, but can't precisely specify it. +#define SRT_REJX_KEY_NOTSUP 1001 // The key used in the StreamID keyed string is not supported by the service. +#define SRT_REJX_FILEPATH 1002 // The resource type designates a file and the path is either wrong syntax or not found +#define SRT_REJX_HOSTNOTFOUND 1003 // The `h` host specification was not recognized by the service + +// The list of http codes adopted for SRT. +// An example C++ header for HTTP codes can be found at: +// https://github.com/j-ulrich/http-status-codes-cpp + +// Some of the unused code can be revived in the future, if there +// happens to be a good reason for it. + +#define SRT_REJX_BAD_REQUEST 1400 // General syntax error in the SocketID specification (also a fallback code for undefined cases) +#define SRT_REJX_UNAUTHORIZED 1401 // Authentication failed, provided that the user was correctly identified and access to the required resource would be granted +#define SRT_REJX_OVERLOAD 1402 // The server is too heavily loaded, or you have exceeded credits for accessing the service and the resource. +#define SRT_REJX_FORBIDDEN 1403 // Access denied to the resource by any kind of reason. +#define SRT_REJX_NOTFOUND 1404 // Resource not found at this time. +#define SRT_REJX_BAD_MODE 1405 // The mode specified in `m` key in StreamID is not supported for this request. +#define SRT_REJX_UNACCEPTABLE 1406 // The requested parameters specified in SocketID cannot be satisfied for the requested resource. Also when m=publish and the data format is not acceptable. +// CODE NOT IN USE 407: unused: proxy functionality not predicted +// CODE NOT IN USE 408: unused: no timeout predicted for listener callback +#define SRT_REJX_CONFLICT 1409 // The resource being accessed is already locked for modification. This is in case of m=publish and the specified resource is currently read-only. +// CODE NOT IN USE 410: unused: treated as a specific case of 404 +// CODE NOT IN USE 411: unused: no reason to include lenght in the protocol +// CODE NOT IN USE 412: unused: preconditions not predicted in AC +// CODE NOT IN USE 413: unused: AC size is already defined as 512 +// CODE NOT IN USE 414: unused: AC size is already defined as 512 +#define SRT_REJX_NOTSUP_MEDIA 1415 // The media type is not supported by the application. This is the `t` key that specifies the media type as stream, file and auth, possibly extended by the application. +// CODE NOT IN USE 416: unused: no detailed specification defined +// CODE NOT IN USE 417: unused: expectations not supported +// CODE NOT IN USE 418: unused: sharks do not drink tea +// CODE NOT IN USE 419: not defined in HTTP +// CODE NOT IN USE 420: not defined in HTTP +// CODE NOT IN USE 421: unused: misdirection not supported +// CODE NOT IN USE 422: unused: aligned to general 400 +#define SRT_REJX_LOCKED 1423 // The resource being accessed is locked for any access. +#define SRT_REJX_FAILED_DEPEND 1424 // The request failed because it specified a dependent session ID that has been disconnected. +// CODE NOT IN USE 425: unused: replaying not supported +// CODE NOT IN USE 426: unused: tempting, but it requires resend in connected +// CODE NOT IN USE 427: not defined in HTTP +// CODE NOT IN USE 428: unused: renders to 409 +// CODE NOT IN USE 429: unused: renders to 402 +// CODE NOT IN USE 451: unused: renders to 403 +#define SRT_REJX_ISE 1500 // Unexpected internal server error +#define SRT_REJX_UNIMPLEMENTED 1501 // The request was recognized, but the current version doesn't support it. +#define SRT_REJX_GW 1502 // The server acts as a gateway and the target endpoint rejected the connection. +#define SRT_REJX_DOWN 1503 // The service has been temporarily taken over by a stub reporting this error. The real service can be down for maintenance or crashed. +// CODE NOT IN USE 504: unused: timeout not supported +#define SRT_REJX_VERSION 1505 // SRT version not supported. This might be either unsupported backward compatibility, or an upper value of a version. +// CODE NOT IN USE 506: unused: negotiation and references not supported +#define SRT_REJX_NOROOM 1507 // The data stream cannot be archived due to lacking storage space. This is in case when the request type was to send a file or the live stream to be archived. +// CODE NOT IN USE 508: unused: no redirection supported +// CODE NOT IN USE 509: not defined in HTTP +// CODE NOT IN USE 510: unused: extensions not supported +// CODE NOT IN USE 511: unused: intercepting proxies not supported + + + +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/api.cpp b/trunk/3rdparty/srt-1-fit/srtcore/api.cpp index 8b1003891f..d575cd9681 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/api.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/api.cpp @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,119 +50,157 @@ modified by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include #include #include #include +#include #include -#include "platform_sys.h" +#include "utilities.h" +#include "netinet_any.h" #include "api.h" #include "core.h" +#include "epoll.h" #include "logging.h" #include "threadname.h" #include "srt.h" +#include "udt.h" #ifdef _WIN32 - #include +#include #endif #ifdef _MSC_VER - #pragma warning(error: 4530) +#pragma warning(error : 4530) #endif using namespace std; using namespace srt_logging; -extern LogConfig srt_logger_config; - - -CUDTSocket::CUDTSocket(): -m_Status(SRTS_INIT), -m_ClosureTimeStamp(0), -m_iIPversion(0), -m_pSelfAddr(NULL), -m_pPeerAddr(NULL), -m_SocketID(0), -m_ListenSocket(0), -m_PeerID(0), -m_iISN(0), -m_pUDT(NULL), -m_pQueuedSockets(NULL), -m_pAcceptSockets(NULL), -m_AcceptCond(), -m_AcceptLock(), -m_uiBackLog(0), -m_iMuxID(-1) -{ - pthread_mutex_init(&m_AcceptLock, NULL); - pthread_cond_init(&m_AcceptCond, NULL); - pthread_mutex_init(&m_ControlLock, NULL); -} - -CUDTSocket::~CUDTSocket() -{ - if (m_iIPversion == AF_INET) - { - delete (sockaddr_in*)m_pSelfAddr; - delete (sockaddr_in*)m_pPeerAddr; - } - else - { - delete (sockaddr_in6*)m_pSelfAddr; - delete (sockaddr_in6*)m_pPeerAddr; - } - - delete m_pUDT; - m_pUDT = NULL; - - delete m_pQueuedSockets; - delete m_pAcceptSockets; - - pthread_mutex_destroy(&m_AcceptLock); - pthread_cond_destroy(&m_AcceptCond); - pthread_mutex_destroy(&m_ControlLock); +using namespace srt::sync; + +void srt::CUDTSocket::construct() +{ +#if ENABLE_BONDING + m_GroupOf = NULL; + m_GroupMemberData = NULL; +#endif + setupMutex(m_AcceptLock, "Accept"); + setupCond(m_AcceptCond, "Accept"); + setupMutex(m_ControlLock, "Control"); +} + +srt::CUDTSocket::~CUDTSocket() +{ + releaseMutex(m_AcceptLock); + releaseCond(m_AcceptCond); + releaseMutex(m_ControlLock); +} + +SRT_SOCKSTATUS srt::CUDTSocket::getStatus() +{ + // TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false. + // Although m_Status is still SRTS_CONNECTING, the connection is in fact to be closed due to TTL expiry. + // In this case m_bConnected is also false. Both checks are required to avoid hitting + // a regular state transition from CONNECTING to CONNECTED. + + if (m_UDT.m_bBroken) + return SRTS_BROKEN; + + // Connecting timed out + if ((m_Status == SRTS_CONNECTING) && !m_UDT.m_bConnecting && !m_UDT.m_bConnected) + return SRTS_BROKEN; + + return m_Status; +} + +// [[using locked(m_GlobControlLock)]] +void srt::CUDTSocket::breakSocket_LOCKED() +{ + // This function is intended to be called from GC, + // under a lock of m_GlobControlLock. + m_UDT.m_bBroken = true; + m_UDT.m_iBrokenCounter = 0; + HLOGC(smlog.Debug, log << "@" << m_SocketID << " CLOSING AS SOCKET"); + m_UDT.closeInternal(); + setClosed(); +} + +void srt::CUDTSocket::setClosed() +{ + m_Status = SRTS_CLOSED; + + // a socket will not be immediately removed when it is closed + // in order to prevent other methods from accessing invalid address + // a timer is started and the socket will be removed after approximately + // 1 second + m_tsClosureTimeStamp = steady_clock::now(); +} + +void srt::CUDTSocket::setBrokenClosed() +{ + m_UDT.m_iBrokenCounter = 60; + m_UDT.m_bBroken = true; + setClosed(); +} + +bool srt::CUDTSocket::readReady() +{ + // TODO: Use m_RcvBufferLock here (CUDT::isRcvReadReady())? + if (m_UDT.m_bConnected && m_UDT.m_pRcvBuffer->isRcvDataReady()) + return true; + + if (m_UDT.m_bListening) + return !m_QueuedSockets.empty(); + + return broken(); +} + +bool srt::CUDTSocket::writeReady() const +{ + return (m_UDT.m_bConnected && (m_UDT.m_pSndBuffer->getCurrBufSize() < m_UDT.m_config.iSndBufSize)) || broken(); +} + +bool srt::CUDTSocket::broken() const +{ + return m_UDT.m_bBroken || !m_UDT.m_bConnected; } //////////////////////////////////////////////////////////////////////////////// -CUDTUnited::CUDTUnited(): -m_Sockets(), -m_ControlLock(), -m_IDLock(), -m_SocketIDGenerator(0), -m_TLSError(), -m_mMultiplexer(), -m_MultiplexerLock(), -m_pCache(NULL), -m_bClosing(false), -m_GCStopLock(), -m_GCStopCond(), -m_InitLock(), -m_iInstanceCount(0), -m_bGCStatus(false), -m_GCThread(), -m_ClosedSockets() -{ - // Socket ID MUST start from a random value - // Note. Don't use CTimer here, because s_UDTUnited is a static instance of CUDTUnited - // with dynamic initialization (calling this constructor), while CTimer has - // a static member s_ullCPUFrequency with dynamic initialization. - // The order of initialization is not guaranteed. - timeval t; - gettimeofday(&t, 0); - srand((unsigned int)t.tv_usec); - m_SocketIDGenerator = 1 + (int)((1 << 30) * (double(rand()) / RAND_MAX)); - - pthread_mutex_init(&m_ControlLock, NULL); - pthread_mutex_init(&m_IDLock, NULL); - pthread_mutex_init(&m_InitLock, NULL); - - pthread_key_create(&m_TLSError, TLSDestroy); - - m_pCache = new CCache; -} - -CUDTUnited::~CUDTUnited() +srt::CUDTUnited::CUDTUnited() + : m_Sockets() + , m_GlobControlLock() + , m_IDLock() + , m_mMultiplexer() + , m_MultiplexerLock() + , m_pCache(NULL) + , m_bClosing(false) + , m_GCStopCond() + , m_InitLock() + , m_iInstanceCount(0) + , m_bGCStatus(false) + , m_ClosedSockets() +{ + // Socket ID MUST start from a random value + m_SocketIDGenerator = genRandomInt(1, MAX_SOCKET_VAL); + m_SocketIDGenerator_init = m_SocketIDGenerator; + + // XXX An unlikely exception thrown from the below calls + // might destroy the application before `main`. This shouldn't + // be a problem in general. + setupMutex(m_GCStopLock, "GCStop"); + setupCond(m_GCStopCond, "GCStop"); + setupMutex(m_GlobControlLock, "GlobControl"); + setupMutex(m_IDLock, "ID"); + setupMutex(m_InitLock, "Init"); + + m_pCache = new CCache; +} + +srt::CUDTUnited::~CUDTUnited() { // Call it if it wasn't called already. // This will happen at the end of main() of the application, @@ -172,19 +210,26 @@ CUDTUnited::~CUDTUnited() cleanup(); } - pthread_mutex_destroy(&m_ControlLock); - pthread_mutex_destroy(&m_IDLock); - pthread_mutex_destroy(&m_InitLock); - - delete (CUDTException*)pthread_getspecific(m_TLSError); - pthread_key_delete(m_TLSError); + releaseMutex(m_GlobControlLock); + releaseMutex(m_IDLock); + releaseMutex(m_InitLock); + // XXX There's some weird bug here causing this + // to hangup on Windows. This might be either something + // bigger, or some problem in pthread-win32. As this is + // the application cleanup section, this can be temporarily + // tolerated with simply exit the application without cleanup, + // counting on that the system will take care of it anyway. +#ifndef _WIN32 + releaseCond(m_GCStopCond); +#endif + releaseMutex(m_GCStopLock); delete m_pCache; } -std::string CUDTUnited::CONID(SRTSOCKET sock) +string srt::CUDTUnited::CONID(SRTSOCKET sock) { - if ( sock == 0 ) + if (sock == 0) return ""; std::ostringstream os; @@ -192,406 +237,661 @@ std::string CUDTUnited::CONID(SRTSOCKET sock) return os.str(); } -int CUDTUnited::startup() +int srt::CUDTUnited::startup() { - CGuard gcinit(m_InitLock); + ScopedLock gcinit(m_InitLock); + + if (m_iInstanceCount++ > 0) + return 1; + + // Global initialization code +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + wVersionRequested = MAKEWORD(2, 2); - if (m_iInstanceCount++ > 0) - return 0; + if (0 != WSAStartup(wVersionRequested, &wsaData)) + throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); +#endif + + CCryptoControl::globalInit(); - // Global initialization code - #ifdef _WIN32 - WORD wVersionRequested; - WSADATA wsaData; - wVersionRequested = MAKEWORD(2, 2); + PacketFilter::globalInit(); - if (0 != WSAStartup(wVersionRequested, &wsaData)) - throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); - #endif + if (m_bGCStatus) + return 1; - PacketFilter::globalInit(); + m_bClosing = false; - //init CTimer::EventLock + if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC")) + return -1; - if (m_bGCStatus) - return true; + m_bGCStatus = true; - m_bClosing = false; - pthread_mutex_init(&m_GCStopLock, NULL); -#if ENABLE_MONOTONIC_CLOCK - pthread_condattr_t CondAttribs; - pthread_condattr_init(&CondAttribs); - pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC); - pthread_cond_init(&m_GCStopCond, &CondAttribs); -#else - pthread_cond_init(&m_GCStopCond, NULL); + HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); + + return 0; +} + +int srt::CUDTUnited::cleanup() +{ + // IMPORTANT!!! + // In this function there must be NO LOGGING AT ALL. This function may + // potentially be called from within the global program destructor, and + // therefore some of the facilities used by the logging system - including + // the default std::cerr object bound to it by default, but also a different + // stream that the user's app has bound to it, and which got destroyed + // together with already exited main() - may be already deleted when + // executing this procedure. + ScopedLock gcinit(m_InitLock); + + if (--m_iInstanceCount > 0) + return 0; + + if (!m_bGCStatus) + return 0; + + { + UniqueLock gclock(m_GCStopLock); + m_bClosing = true; + } + // NOTE: we can do relaxed signaling here because + // waiting on m_GCStopCond has a 1-second timeout, + // after which the m_bClosing flag is cheched, which + // is set here above. Worst case secenario, this + // pthread_join() call will block for 1 second. + CSync::notify_one_relaxed(m_GCStopCond); + m_GCThread.join(); + + m_bGCStatus = false; + + // Global destruction code +#ifdef _WIN32 + WSACleanup(); +#endif + + return 0; +} + +SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) +{ + ScopedLock guard(m_IDLock); + + int sockval = m_SocketIDGenerator - 1; + + // First problem: zero-value should be avoided by various reasons. + + if (sockval <= 0) + { + // We have a rollover on the socket value, so + // definitely we haven't made the Columbus mistake yet. + m_SocketIDGenerator = MAX_SOCKET_VAL; + } + + // Check all sockets if any of them has this value. + // Socket IDs are begin created this way: + // + // Initial random + // | + // | + // | + // | + // ... + // The only problem might be if the number rolls over + // and reaches the same value from the opposite side. + // This is still a valid socket value, but this time + // we have to check, which sockets have been used already. + if (sockval == m_SocketIDGenerator_init) + { + // Mark that since this point on the checks for + // whether the socket ID is in use must be done. + m_SocketIDGenerator_init = 0; + } + + // This is when all socket numbers have been already used once. + // This may happen after many years of running an application + // constantly when the connection breaks and gets restored often. + if (m_SocketIDGenerator_init == 0) + { + int startval = sockval; + for (;;) // Roll until an unused value is found + { + enterCS(m_GlobControlLock); + const bool exists = +#if ENABLE_BONDING + for_group + ? m_Groups.count(sockval | SRTGROUP_MASK) + : #endif - { - ThreadName tn("SRT:GC"); - pthread_create(&m_GCThread, NULL, garbageCollect, this); - } + m_Sockets.count(sockval); + leaveCS(m_GlobControlLock); + + if (exists) + { + // The socket value is in use. + --sockval; + if (sockval <= 0) + sockval = MAX_SOCKET_VAL; + + // Before continuing, check if we haven't rolled back to start again + // This is virtually impossible, so just make an RTI error. + if (sockval == startval) + { + // Of course, we don't lack memory, but actually this is so impossible + // that a complete memory extinction is much more possible than this. + // So treat this rather as a formal fallback for something that "should + // never happen". This should make the socket creation functions, from + // socket_create and accept, return this error. + + m_SocketIDGenerator = sockval + 1; // so that any next call will cause the same error + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + + // try again, if this is a free socket + continue; + } + + // No socket found, this ID is free to use + m_SocketIDGenerator = sockval; + break; + } + } + else + { + m_SocketIDGenerator = sockval; + } + + // The socket value counter remains with the value rolled + // without the group bit set; only the returned value may have + // the group bit set. + + if (for_group) + sockval = m_SocketIDGenerator | SRTGROUP_MASK; + else + sockval = m_SocketIDGenerator; + + LOGC(smlog.Debug, log << "generateSocketID: " << (for_group ? "(group)" : "") << ": @" << sockval); + + return sockval; +} + +SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) +{ + // XXX consider using some replacement of std::unique_ptr + // so that exceptions will clean up the object without the + // need for a dedicated code. + CUDTSocket* ns = NULL; - m_bGCStatus = true; + try + { + ns = new CUDTSocket; + } + catch (...) + { + delete ns; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + + try + { + ns->m_SocketID = generateSocketID(); + } + catch (...) + { + delete ns; + throw; + } + ns->m_Status = SRTS_INIT; + ns->m_ListenSocket = 0; + ns->core().m_SocketID = ns->m_SocketID; + ns->core().m_pCache = m_pCache; + + try + { + HLOGC(smlog.Debug, log << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID); + + // protect the m_Sockets structure. + ScopedLock cs(m_GlobControlLock); + m_Sockets[ns->m_SocketID] = ns; + } + catch (...) + { + // failure and rollback + delete ns; + ns = NULL; + } + + if (!ns) + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + + if (pps) + *pps = ns; - return 0; + return ns->m_SocketID; } -int CUDTUnited::cleanup() +int srt::CUDTUnited::newConnection(const SRTSOCKET listen, + const sockaddr_any& peer, + const CPacket& hspkt, + CHandShake& w_hs, + int& w_error, + CUDT*& w_acpu) { - CGuard gcinit(m_InitLock); + CUDTSocket* ns = NULL; + w_acpu = NULL; - if (--m_iInstanceCount > 0) - return 0; + w_error = SRT_REJ_IPE; - //destroy CTimer::EventLock + // Can't manage this error through an exception because this is + // running in the listener loop. + CUDTSocket* ls = locateSocket(listen); + if (!ls) + { + LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listen << " which DOES NOT EXIST."); + return -1; + } - if (!m_bGCStatus) - return 0; + HLOGC(cnlog.Debug, + log << "newConnection: creating new socket after listener @" << listen + << " contacted with backlog=" << ls->m_uiBackLog); - m_bClosing = true; - pthread_cond_signal(&m_GCStopCond); - pthread_join(m_GCThread, NULL); - - // XXX There's some weird bug here causing this - // to hangup on Windows. This might be either something - // bigger, or some problem in pthread-win32. As this is - // the application cleanup section, this can be temporarily - // tolerated with simply exit the application without cleanup, - // counting on that the system will take care of it anyway. -#ifndef _WIN32 - pthread_mutex_destroy(&m_GCStopLock); - pthread_cond_destroy(&m_GCStopCond); + // if this connection has already been processed + if ((ns = locatePeer(peer, w_hs.m_iID, w_hs.m_iISN)) != NULL) + { + if (ns->core().m_bBroken) + { + // last connection from the "peer" address has been broken + ns->setClosed(); + + ScopedLock acceptcg(ls->m_AcceptLock); + ls->m_QueuedSockets.erase(ns->m_SocketID); + } + else + { + // connection already exist, this is a repeated connection request + // respond with existing HS information + HLOGC(cnlog.Debug, log << "newConnection: located a WORKING peer @" << w_hs.m_iID << " - ADAPTING."); + + w_hs.m_iISN = ns->core().m_iISN; + w_hs.m_iMSS = ns->core().MSS(); + w_hs.m_iFlightFlagSize = ns->core().m_config.iFlightFlagSize; + w_hs.m_iReqType = URQ_CONCLUSION; + w_hs.m_iID = ns->m_SocketID; + + // Report the original UDT because it will be + // required to complete the HS data for conclusion response. + w_acpu = &ns->core(); + + return 0; + + // except for this situation a new connection should be started + } + } + else + { + HLOGC(cnlog.Debug, + log << "newConnection: NOT located any peer @" << w_hs.m_iID << " - resuming with initial connection."); + } + + // exceeding backlog, refuse the connection request + if (ls->m_QueuedSockets.size() >= ls->m_uiBackLog) + { + w_error = SRT_REJ_BACKLOG; + LOGC(cnlog.Note, log << "newConnection: listen backlog=" << ls->m_uiBackLog << " EXCEEDED"); + return -1; + } + + try + { + ns = new CUDTSocket(*ls); + // No need to check the peer, this is the address from which the request has come. + ns->m_PeerAddr = peer; + } + catch (...) + { + w_error = SRT_REJ_RESOURCE; + delete ns; + LOGC(cnlog.Error, log << "IPE: newConnection: unexpected exception (probably std::bad_alloc)"); + return -1; + } + + ns->core().m_RejectReason = SRT_REJ_UNKNOWN; // pre-set a universal value + + try + { + ns->m_SocketID = generateSocketID(); + } + catch (const CUDTException&) + { + LOGF(cnlog.Fatal, "newConnection: IPE: all sockets occupied? Last gen=%d", m_SocketIDGenerator); + // generateSocketID throws exception, which can be naturally handled + // when the call is derived from the API call, but here it's called + // internally in response to receiving a handshake. It must be handled + // here and turned into an erroneous return value. + delete ns; + return -1; + } + + ns->m_ListenSocket = listen; + ns->core().m_SocketID = ns->m_SocketID; + ns->m_PeerID = w_hs.m_iID; + ns->m_iISN = w_hs.m_iISN; + + HLOGC(cnlog.Debug, + log << "newConnection: DATA: lsnid=" << listen << " id=" << ns->core().m_SocketID + << " peerid=" << ns->core().m_PeerID << " ISN=" << ns->m_iISN); + + int error = 0; + bool should_submit_to_accept = true; + + // Set the error code for all prospective problems below. + // It won't be interpreted when result was successful. + w_error = SRT_REJ_RESOURCE; + + // These can throw exception only when the memory allocation failed. + // CUDT::connect() translates exception into CUDTException. + // CUDT::open() may only throw original std::bad_alloc from new. + // This is only to make the library extra safe (when your machine lacks + // memory, it will continue to work, but fail to accept connection). + + try + { + // This assignment must happen b4 the call to CUDT::connect() because + // this call causes sending the SRT Handshake through this socket. + // Without this mapping the socket cannot be found and therefore + // the SRT Handshake message would fail. + HLOGF(cnlog.Debug, "newConnection: incoming %s, mapping socket %d", peer.str().c_str(), ns->m_SocketID); + { + ScopedLock cg(m_GlobControlLock); + m_Sockets[ns->m_SocketID] = ns; + } + + if (ls->core().m_cbAcceptHook) + { + if (!ls->core().runAcceptHook(&ns->core(), peer.get(), w_hs, hspkt)) + { + w_error = ns->core().m_RejectReason; + + error = 1; + goto ERR_ROLLBACK; + } + } + + // bind to the same addr of listening socket + ns->core().open(); + updateListenerMux(ns, ls); + + ns->core().acceptAndRespond(ls->m_SelfAddr, peer, hspkt, (w_hs)); + } + catch (...) + { + // Extract the error that was set in this new failed entity. + w_error = ns->core().m_RejectReason; + error = 1; + goto ERR_ROLLBACK; + } + + ns->m_Status = SRTS_CONNECTED; + + // copy address information of local node + // Precisely, what happens here is: + // - Get the IP address and port from the system database + ns->core().m_pSndQueue->m_pChannel->getSockAddr((ns->m_SelfAddr)); + // - OVERWRITE just the IP address itself by a value taken from piSelfIP + // (the family is used exactly as the one taken from what has been returned + // by getsockaddr) + CIPAddress::pton((ns->m_SelfAddr), ns->core().m_piSelfIP, peer); + + { + // protect the m_PeerRec structure (and group existence) + ScopedLock glock(m_GlobControlLock); + try + { + HLOGF(cnlog.Debug, "newConnection: mapping peer %d to that socket (%d)\n", ns->m_PeerID, ns->m_SocketID); + m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID); + } + catch (...) + { + LOGC(cnlog.Error, log << "newConnection: error when mapping peer!"); + error = 2; + } + + // The access to m_GroupOf should be also protected, as the group + // could be requested deletion in the meantime. This will hold any possible + // removal from group and resetting m_GroupOf field. + +#if ENABLE_BONDING + if (ns->m_GroupOf) + { + // XXX this might require another check of group type. + // For redundancy group, at least, update the status in the group + CUDTGroup* g = ns->m_GroupOf; + ScopedLock glock(g->m_GroupLock); + if (g->m_bClosing) + { + error = 1; // "INTERNAL REJECTION" + goto ERR_ROLLBACK; + } + + // Check if this is the first socket in the group. + // If so, give it up to accept, otherwise just do nothing + // The client will be informed about the newly added connection at the + // first moment when attempting to get the group status. + for (CUDTGroup::gli_t gi = g->m_Group.begin(); gi != g->m_Group.end(); ++gi) + { + if (gi->laststatus == SRTS_CONNECTED) + { + HLOGC(cnlog.Debug, + log << "Found another connected socket in the group: $" << gi->id + << " - socket will be NOT given up for accepting"); + should_submit_to_accept = false; + break; + } + } + + // Update the status in the group so that the next + // operation can include the socket in the group operation. + CUDTGroup::SocketData* gm = ns->m_GroupMemberData; + + HLOGC(cnlog.Debug, + log << "newConnection(GROUP): Socket @" << ns->m_SocketID << " BELONGS TO $" << g->id() << " - will " + << (should_submit_to_accept ? "" : "NOT ") << "report in accept"); + gm->sndstate = SRT_GST_IDLE; + gm->rcvstate = SRT_GST_IDLE; + gm->laststatus = SRTS_CONNECTED; + + if (!g->m_bConnected) + { + HLOGC(cnlog.Debug, log << "newConnection(GROUP): First socket connected, SETTING GROUP CONNECTED"); + g->m_bConnected = true; + } + + // XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily, + // but groupwise connections could be accepted from multiple listeners for the same group! + // m_listener MUST BE A CONTAINER, NOT POINTER!!! + // ALSO: Maybe checking "the same listener" is not necessary as subscruption may be done + // multiple times anyway? + if (!g->m_listener) + { + // Newly created group from the listener, which hasn't yet + // the listener set. + g->m_listener = ls; + + // Listen on both first connected socket and continued sockets. + // This might help with jump-over situations, and in regular continued + // sockets the IN event won't be reported anyway. + int listener_modes = SRT_EPOLL_ACCEPT | SRT_EPOLL_UPDATE; + epoll_add_usock_INTERNAL(g->m_RcvEID, ls, &listener_modes); + + // This listening should be done always when a first connected socket + // appears as accepted off the listener. This is for the sake of swait() calls + // inside the group receiving and sending functions so that they get + // interrupted when a new socket is connected. + } + + // Add also per-direction subscription for the about-to-be-accepted socket. + // Both first accepted socket that makes the group-accept and every next + // socket that adds a new link. + int read_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; + int write_modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; + epoll_add_usock_INTERNAL(g->m_RcvEID, ns, &read_modes); + epoll_add_usock_INTERNAL(g->m_SndEID, ns, &write_modes); + + // With app reader, do not set groupPacketArrival (block the + // provider array feature completely for now). + + /* SETUP HERE IF NEEDED + ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival); + */ + } + else + { + HLOGC(cnlog.Debug, log << "newConnection: Socket @" << ns->m_SocketID << " is not in a group"); + } #endif + } + + if (should_submit_to_accept) + { + enterCS(ls->m_AcceptLock); + try + { + ls->m_QueuedSockets.insert(ns->m_SocketID); + } + catch (...) + { + LOGC(cnlog.Error, log << "newConnection: error when queuing socket!"); + error = 3; + } + leaveCS(ls->m_AcceptLock); + + HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->m_SocketID << " submitted for acceptance"); + // acknowledge users waiting for new connections on the listening socket + m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, true); + + CGlobEvent::triggerEvent(); + + // XXX the exact value of 'error' is ignored + if (error > 0) + { + goto ERR_ROLLBACK; + } + + // wake up a waiting accept() call + CSync::lock_notify_one(ls->m_AcceptCond, ls->m_AcceptLock); + } + else + { + HLOGC(cnlog.Debug, + log << "ACCEPT: new socket @" << ns->m_SocketID + << " NOT submitted to acceptance, another socket in the group is already connected"); + + // acknowledge INTERNAL users waiting for new connections on the listening socket + // that are reported when a new socket is connected within an already connected group. + m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_UPDATE, true); + CGlobEvent::triggerEvent(); + } - m_bGCStatus = false; - - // Global destruction code - #ifdef _WIN32 - WSACleanup(); - #endif - - return 0; -} - -SRTSOCKET CUDTUnited::newSocket(int af, int) -{ - - CUDTSocket* ns = NULL; - - try - { - // XXX REFACTOR: - // Use sockaddr_any for m_pSelfAddr and just initialize it - // with 'af'. - ns = new CUDTSocket; - ns->m_pUDT = new CUDT; - if (af == AF_INET) - { - ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in); - ((sockaddr_in*)(ns->m_pSelfAddr))->sin_port = 0; - } - else - { - ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in6); - ((sockaddr_in6*)(ns->m_pSelfAddr))->sin6_port = 0; - } - } - catch (...) - { - delete ns; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } - - CGuard::enterCS(m_IDLock); - ns->m_SocketID = -- m_SocketIDGenerator; - CGuard::leaveCS(m_IDLock); - - ns->m_Status = SRTS_INIT; - ns->m_ListenSocket = 0; - ns->m_pUDT->m_SocketID = ns->m_SocketID; - // The "Socket type" is deprecated. For the sake of - // HSv4 there will be only a "socket type" field set - // in the handshake, always to UDT_DGRAM. - //ns->m_pUDT->m_iSockType = (type == SOCK_STREAM) ? UDT_STREAM : UDT_DGRAM; - ns->m_pUDT->m_iSockType = UDT_DGRAM; - ns->m_pUDT->m_iIPversion = ns->m_iIPversion = af; - ns->m_pUDT->m_pCache = m_pCache; - - // protect the m_Sockets structure. - CGuard::enterCS(m_ControlLock); - try - { - HLOGC(mglog.Debug, log << CONID(ns->m_SocketID) - << "newSocket: mapping socket " - << ns->m_SocketID); - m_Sockets[ns->m_SocketID] = ns; - } - catch (...) - { - //failure and rollback - CGuard::leaveCS(m_ControlLock); - delete ns; - ns = NULL; - } - CGuard::leaveCS(m_ControlLock); - - if (!ns) - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - - return ns->m_SocketID; -} - -int CUDTUnited::newConnection(const SRTSOCKET listen, const sockaddr* peer, CHandShake* hs, const CPacket& hspkt, - ref_t r_error) -{ - CUDTSocket* ns = NULL; - - *r_error = SRT_REJ_IPE; - - // Can't manage this error through an exception because this is - // running in the listener loop. - CUDTSocket* ls = locate(listen); - if (!ls) - { - LOGC(mglog.Error, log << "IPE: newConnection by listener socket id=" << listen << " which DOES NOT EXIST."); - return -1; - } - - // if this connection has already been processed - if ((ns = locate(peer, hs->m_iID, hs->m_iISN)) != NULL) - { - if (ns->m_pUDT->m_bBroken) - { - // last connection from the "peer" address has been broken - ns->m_Status = SRTS_CLOSED; - ns->m_ClosureTimeStamp = CTimer::getTime(); - - CGuard::enterCS(ls->m_AcceptLock); - ls->m_pQueuedSockets->erase(ns->m_SocketID); - ls->m_pAcceptSockets->erase(ns->m_SocketID); - CGuard::leaveCS(ls->m_AcceptLock); - } - else - { - // connection already exist, this is a repeated connection request - // respond with existing HS information - - hs->m_iISN = ns->m_pUDT->m_iISN; - hs->m_iMSS = ns->m_pUDT->m_iMSS; - hs->m_iFlightFlagSize = ns->m_pUDT->m_iFlightFlagSize; - hs->m_iReqType = URQ_CONCLUSION; - hs->m_iID = ns->m_SocketID; - - return 0; - - //except for this situation a new connection should be started - } - } - - // exceeding backlog, refuse the connection request - if (ls->m_pQueuedSockets->size() >= ls->m_uiBackLog) - { - *r_error = SRT_REJ_BACKLOG; - LOGC(mglog.Error, log << "newConnection: listen backlog=" << ls->m_uiBackLog << " EXCEEDED"); - return -1; - } - - try - { - ns = new CUDTSocket; - ns->m_pUDT = new CUDT(*(ls->m_pUDT)); - if (ls->m_iIPversion == AF_INET) - { - ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in); - ((sockaddr_in*)(ns->m_pSelfAddr))->sin_port = 0; - ns->m_pPeerAddr = (sockaddr*)(new sockaddr_in); - memcpy(ns->m_pPeerAddr, peer, sizeof(sockaddr_in)); - } - else - { - ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in6); - ((sockaddr_in6*)(ns->m_pSelfAddr))->sin6_port = 0; - ns->m_pPeerAddr = (sockaddr*)(new sockaddr_in6); - memcpy(ns->m_pPeerAddr, peer, sizeof(sockaddr_in6)); - } - } - catch (...) - { - *r_error = SRT_REJ_RESOURCE; - delete ns; - LOGC(mglog.Error, log << "IPE: newConnection: unexpected exception (probably std::bad_alloc)"); - return -1; - } - - CGuard::enterCS(m_IDLock); - ns->m_SocketID = -- m_SocketIDGenerator; - HLOGF(mglog.Debug, "newConnection: generated socket id %d", ns->m_SocketID); - CGuard::leaveCS(m_IDLock); - - ns->m_ListenSocket = listen; - ns->m_iIPversion = ls->m_iIPversion; - ns->m_pUDT->m_SocketID = ns->m_SocketID; - ns->m_PeerID = hs->m_iID; - ns->m_iISN = hs->m_iISN; - - int error = 0; - - // Set the error code for all prospective problems below. - // It won't be interpreted when result was successful. - *r_error = SRT_REJ_RESOURCE; - - // These can throw exception only when the memory allocation failed. - // CUDT::connect() translates exception into CUDTException. - // CUDT::open() may only throw original std::bad_alloc from new. - // This is only to make the library extra safe (when your machine lacks - // memory, it will continue to work, but fail to accept connection). - try - { - // This assignment must happen b4 the call to CUDT::connect() because - // this call causes sending the SRT Handshake through this socket. - // Without this mapping the socket cannot be found and therefore - // the SRT Handshake message would fail. - HLOGF(mglog.Debug, - "newConnection: incoming %s, mapping socket %d", - SockaddrToString(peer).c_str(), ns->m_SocketID); - { - CGuard cg(m_ControlLock); - m_Sockets[ns->m_SocketID] = ns; - } - - // bind to the same addr of listening socket - ns->m_pUDT->open(); - updateListenerMux(ns, ls); - if (ls->m_pUDT->m_cbAcceptHook) - { - if (!ls->m_pUDT->runAcceptHook(ns->m_pUDT, peer, hs, hspkt)) - { - error = 1; - goto ERR_ROLLBACK; - } - } - ns->m_pUDT->acceptAndRespond(peer, hs, hspkt); - } - catch (...) - { - // Extract the error that was set in this new failed entity. - *r_error = ns->m_pUDT->m_RejectReason; - error = 1; - goto ERR_ROLLBACK; - } - - ns->m_Status = SRTS_CONNECTED; - - // copy address information of local node - ns->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(ns->m_pSelfAddr); - CIPAddress::pton(ns->m_pSelfAddr, ns->m_pUDT->m_piSelfIP, ns->m_iIPversion); - - // protect the m_Sockets structure. - CGuard::enterCS(m_ControlLock); - try - { - HLOGF(mglog.Debug, - "newConnection: mapping peer %d to that socket (%d)\n", - ns->m_PeerID, ns->m_SocketID); - m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID); - } - catch (...) - { - error = 2; - } - CGuard::leaveCS(m_ControlLock); - - CGuard::enterCS(ls->m_AcceptLock); - try - { - ls->m_pQueuedSockets->insert(ns->m_SocketID); - } - catch (...) - { - error = 3; - } - CGuard::leaveCS(ls->m_AcceptLock); - - // acknowledge users waiting for new connections on the listening socket - m_EPoll.update_events(listen, ls->m_pUDT->m_sPollID, UDT_EPOLL_IN, true); - - CTimer::triggerEvent(); - - ERR_ROLLBACK: - // XXX the exact value of 'error' is ignored - if (error > 0) - { +ERR_ROLLBACK: + // XXX the exact value of 'error' is ignored + if (error > 0) + { #if ENABLE_LOGGING - static const char* why [] = { - "UNKNOWN ERROR", - "CONNECTION REJECTED", - "IPE when mapping a socket", - "IPE when inserting a socket" - }; - LOGC(mglog.Error, log << CONID(ns->m_SocketID) << "newConnection: connection rejected due to: " << why[error]); + static const char* why[] = { + "UNKNOWN ERROR", "INTERNAL REJECTION", "IPE when mapping a socket", "IPE when inserting a socket"}; + LOGC(cnlog.Warn, + log << CONID(ns->m_SocketID) << "newConnection: connection rejected due to: " << why[error] << " - " + << RequestTypeStr(URQFailure(w_error))); +#endif + + SRTSOCKET id = ns->m_SocketID; + ns->core().closeInternal(); + ns->setClosed(); + + // The mapped socket should be now unmapped to preserve the situation that + // was in the original UDT code. + // In SRT additionally the acceptAndRespond() function (it was called probably + // connect() in UDT code) may fail, in which case this socket should not be + // further processed and should be removed. + { + ScopedLock cg(m_GlobControlLock); + +#if ENABLE_BONDING + if (ns->m_GroupOf) + { + HLOGC(smlog.Debug, + log << "@" << ns->m_SocketID << " IS MEMBER OF $" << ns->m_GroupOf->id() + << " - REMOVING FROM GROUP"); + ns->removeFromGroup(true); + } #endif - SRTSOCKET id = ns->m_SocketID; - ns->m_pUDT->close(); - ns->m_Status = SRTS_CLOSED; - ns->m_ClosureTimeStamp = CTimer::getTime(); - // The mapped socket should be now unmapped to preserve the situation that - // was in the original UDT code. - // In SRT additionally the acceptAndRespond() function (it was called probably - // connect() in UDT code) may fail, in which case this socket should not be - // further processed and should be removed. - { - CGuard cg(m_ControlLock); - m_Sockets.erase(id); - m_ClosedSockets[id] = ns; - } - - return -1; - } - - // wake up a waiting accept() call - pthread_mutex_lock(&(ls->m_AcceptLock)); - pthread_cond_signal(&(ls->m_AcceptCond)); - pthread_mutex_unlock(&(ls->m_AcceptLock)); - - return 1; -} - -int CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) + m_Sockets.erase(id); + m_ClosedSockets[id] = ns; + } + + return -1; + } + + return 1; +} + +// static forwarder +int srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +{ + return uglobal().installAcceptHook(lsn, hook, opaq); +} + +int srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { try { - CUDT* lc = lookup(lsn); - lc->installAcceptHook(hook, opaq); - + CUDTSocket* s = locateSocket(lsn, ERH_THROW); + s->core().installAcceptHook(hook, opaq); } catch (CUDTException& e) { - setError(new CUDTException(e)); + SetThreadLocalError(e); return SRT_ERROR; } return 0; } -CUDT* CUDTUnited::lookup(const SRTSOCKET u) +int srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { - // protects the m_Sockets structure - CGuard cg(m_ControlLock); - - map::iterator i = m_Sockets.find(u); + return uglobal().installConnectHook(lsn, hook, opaq); +} - if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); +int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) +{ + try + { +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + GroupKeeper k(*this, u, ERH_THROW); + k.group->installConnectHook(hook, opaq); + return 0; + } +#endif + CUDTSocket* s = locateSocket(u, ERH_THROW); + s->core().installConnectHook(hook, opaq); + } + catch (CUDTException& e) + { + SetThreadLocalError(e); + return SRT_ERROR; + } - return i->second->m_pUDT; + return 0; } -SRT_SOCKSTATUS CUDTUnited::getStatus(const SRTSOCKET u) +SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) { // protects the m_Sockets structure - CGuard cg(m_ControlLock); + ScopedLock cg(m_GlobControlLock); - map::const_iterator i = m_Sockets.find(u); + sockets_t::const_iterator i = m_Sockets.find(u); if (i == m_Sockets.end()) { @@ -600,2218 +900,3256 @@ SRT_SOCKSTATUS CUDTUnited::getStatus(const SRTSOCKET u) return SRTS_NONEXIST; } - const CUDTSocket* s = i->second; + return i->second->getStatus(); +} - if (s->m_pUDT->m_bBroken) - return SRTS_BROKEN; +int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) +{ + ScopedLock cg(s->m_ControlLock); - // TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false. - // Although m_Status is still SRTS_CONNECTING, the connection is in fact to be closed due to TTL expiry. - // In this case m_bConnected is also false. Both checks are required to avoid hitting - // a regular state transition from CONNECTING to CONNECTED. - if ((s->m_Status == SRTS_CONNECTING) && !s->m_pUDT->m_bConnecting && !s->m_pUDT->m_bConnected) - return SRTS_BROKEN; + // cannot bind a socket more than once + if (s->m_Status != SRTS_INIT) + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + + s->core().open(); + updateMux(s, name); + s->m_Status = SRTS_OPENED; - return s->m_Status; + // copy address information of local node + s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); + + return 0; } -int CUDTUnited::bind(const SRTSOCKET u, const sockaddr* name, int namelen) +int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) { - CUDTSocket* s = locate(u); - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + ScopedLock cg(s->m_ControlLock); - CGuard cg(s->m_ControlLock); + // cannot bind a socket more than once + if (s->m_Status != SRTS_INIT) + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); - // cannot bind a socket more than once - if (s->m_Status != SRTS_INIT) - throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + sockaddr_any name; + socklen_t namelen = sizeof name; // max of inet and inet6 - // check the size of SOCKADDR structure - if (s->m_iIPversion == AF_INET) - { - if (namelen != sizeof(sockaddr_in)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - else - { - if (namelen != sizeof(sockaddr_in6)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } + // This will preset the sa_family as well; the namelen is given simply large + // enough for any family here. + if (::getsockname(udpsock, &name.sa, &namelen) == -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL); - s->m_pUDT->open(); - updateMux(s, name); - s->m_Status = SRTS_OPENED; + // Successfully extracted, so update the size + name.len = namelen; - // copy address information of local node - s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr); + s->core().open(); + updateMux(s, name, &udpsock); + s->m_Status = SRTS_OPENED; - return 0; + // copy address information of local node + s->core().m_pSndQueue->m_pChannel->getSockAddr(s->m_SelfAddr); + + return 0; } -int CUDTUnited::bind(SRTSOCKET u, UDPSOCKET udpsock) +int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) { - CUDTSocket* s = locate(u); - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + if (backlog <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + // Don't search for the socket if it's already -1; + // this never is a valid socket. + if (u == UDT::INVALID_SOCK) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CUDTSocket* s = locateSocket(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + ScopedLock cg(s->m_ControlLock); + + // NOTE: since now the socket is protected against simultaneous access. + // In the meantime the socket might have been closed, which means that + // it could have changed the state. It could be also set listen in another + // thread, so check it out. - CGuard cg(s->m_ControlLock); + // do nothing if the socket is already listening + if (s->m_Status == SRTS_LISTENING) + return 0; - // cannot bind a socket more than once - if (s->m_Status != SRTS_INIT) - throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + // a socket can listen only if is in OPENED status + if (s->m_Status != SRTS_OPENED) + throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); - sockaddr_in name4; - sockaddr_in6 name6; - sockaddr* name; - socklen_t namelen; + // [[using assert(s->m_Status == OPENED)]]; - if (s->m_iIPversion == AF_INET) - { - namelen = sizeof(sockaddr_in); - name = (sockaddr*)&name4; - } - else - { - namelen = sizeof(sockaddr_in6); - name = (sockaddr*)&name6; - } + // listen is not supported in rendezvous connection setup + if (s->core().m_config.bRendezvous) + throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); - if (::getsockname(udpsock, name, &namelen) == -1) - throw CUDTException(MJ_NOTSUP, MN_INVAL); + s->m_uiBackLog = backlog; - s->m_pUDT->open(); - updateMux(s, name, &udpsock); - s->m_Status = SRTS_OPENED; + // [[using assert(s->m_Status == OPENED)]]; // (still, unchanged) - // copy address information of local node - s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr); + s->core().setListenState(); // propagates CUDTException, + // if thrown, remains in OPENED state if so. + s->m_Status = SRTS_LISTENING; - return 0; + return 0; } -int CUDTUnited::listen(const SRTSOCKET u, int backlog) +SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) { - if (backlog <= 0) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + CEPollDesc* ed = 0; + int eid = m_EPoll.create(&ed); - // Don't search for the socket if it's already -1; - // this never is a valid socket. - if (u == UDT::INVALID_SOCK) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + // Destroy it at return - this function can be interrupted + // by an exception. + struct AtReturn + { + int eid; + CUDTUnited* that; + AtReturn(CUDTUnited* t, int e) + : eid(e) + , that(t) + { + } + ~AtReturn() { that->m_EPoll.release(eid); } + } l_ar(this, eid); + + // Subscribe all of listeners for accept + int events = SRT_EPOLL_ACCEPT; + + for (int i = 0; i < lsize; ++i) + { + srt_epoll_add_usock(eid, listeners[i], &events); + } - CUDTSocket* s = locate(u); - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + CEPoll::fmap_t st; + m_EPoll.swait(*ed, (st), msTimeOut, true); - CGuard cg(s->m_ControlLock); + if (st.empty()) + { + // Sanity check + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + } - // NOTE: since now the socket is protected against simultaneous access. - // In the meantime the socket might have been closed, which means that - // it could have changed the state. It could be also set listen in another - // thread, so check it out. - - // do nothing if the socket is already listening - if (s->m_Status == SRTS_LISTENING) - return 0; - - // a socket can listen only if is in OPENED status - if (s->m_Status != SRTS_OPENED) - throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); - - // [[using assert(s->m_Status == OPENED)]]; - - // listen is not supported in rendezvous connection setup - if (s->m_pUDT->m_bRendezvous) - throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); - - s->m_uiBackLog = backlog; - - try - { - s->m_pQueuedSockets = new set; - s->m_pAcceptSockets = new set; - } - catch (...) - { - delete s->m_pQueuedSockets; - delete s->m_pAcceptSockets; - - // XXX Translated std::bad_alloc into CUDTException specifying - // memory allocation failure... - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } - - // [[using assert(s->m_Status == OPENED)]]; // (still, unchanged) - - s->m_pUDT->setListenState(); // propagates CUDTException, - // if thrown, remains in OPENED state if so. - s->m_Status = SRTS_LISTENING; - - return 0; -} - -SRTSOCKET CUDTUnited::accept(const SRTSOCKET listen, sockaddr* addr, int* addrlen) -{ - if ((addr) && (!addrlen)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - CUDTSocket* ls = locate(listen); - - if (ls == NULL) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - // the "listen" socket must be in LISTENING status - if (ls->m_Status != SRTS_LISTENING) - throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); - - // no "accept" in rendezvous connection setup - if (ls->m_pUDT->m_bRendezvous) - throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); - - SRTSOCKET u = CUDT::INVALID_SOCK; - bool accepted = false; - - // !!only one conection can be set up each time!! - while (!accepted) - { - CGuard cg(ls->m_AcceptLock); - - if ((ls->m_Status != SRTS_LISTENING) || ls->m_pUDT->m_bBroken) - { - // This socket has been closed. - accepted = true; - } - else if (ls->m_pQueuedSockets->size() > 0) - { - // XXX REFACTORING REQUIRED HERE! - // Actually this should at best be something like that: - // set::iterator b = ls->m_pQueuedSockets->begin(); - // u = *b; - // ls->m_pQueuedSockets->erase(b); - // ls->m_pAcceptSockets->insert(u); - // - // It is also questionable why m_pQueuedSockets should be of type 'set'. - // There's no quick-searching capabilities of that container used anywhere except - // checkBrokenSockets and garbageCollect, which aren't performance-critical, - // whereas it's mainly used for getting the first element and iterating - // over elements, which is slow in case of std::set. It's also doubtful - // as to whether the sorting capability of std::set is properly used; - // the first is taken here, which is actually the socket with lowest - // possible descriptor value (as default operator< and ascending sorting - // used for std::set where SRTSOCKET=int). - // - // Consider using std::list or std::vector here. - - u = *(ls->m_pQueuedSockets->begin()); - ls->m_pAcceptSockets->insert(ls->m_pAcceptSockets->end(), u); - ls->m_pQueuedSockets->erase(ls->m_pQueuedSockets->begin()); - accepted = true; - } - else if (!ls->m_pUDT->m_bSynRecving) - { - accepted = true; - } - - if (!accepted && (ls->m_Status == SRTS_LISTENING)) - pthread_cond_wait(&(ls->m_AcceptCond), &(ls->m_AcceptLock)); - - if (ls->m_pQueuedSockets->empty()) - m_EPoll.update_events(listen, ls->m_pUDT->m_sPollID, UDT_EPOLL_IN, false); - } - - if (u == CUDT::INVALID_SOCK) - { - // non-blocking receiving, no connection available - if (!ls->m_pUDT->m_bSynRecving) - throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); - - // listening socket is closed - throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); - } - - if ((addr != NULL) && (addrlen != NULL)) - { - CUDTSocket* s = locate(u); - if (s == NULL) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - CGuard cg(s->m_ControlLock); - - if (AF_INET == s->m_iIPversion) - *addrlen = sizeof(sockaddr_in); - else - *addrlen = sizeof(sockaddr_in6); - - // copy address information of peer node - memcpy(addr, s->m_pPeerAddr, *addrlen); - } - - return u; -} - -int CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) -{ - CUDTSocket* s = locate(u); - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - CGuard cg(s->m_ControlLock); - - // XXX Consider translating this to using sockaddr_any, - // this should take out all the "IP version check" things. - if (AF_INET == s->m_iIPversion) - { - if (namelen != sizeof(sockaddr_in)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - else - { - if (namelen != sizeof(sockaddr_in6)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - - // a socket can "connect" only if it is in INIT or OPENED status - if (s->m_Status == SRTS_INIT) - { - if (!s->m_pUDT->m_bRendezvous) - { - s->m_pUDT->open(); // XXX here use the AF_* family value from 'name' - updateMux(s); // <<---- updateMux - // -> C(Snd|Rcv)Queue::init - // -> pthread_create(...C(Snd|Rcv)Queue::worker...) - s->m_Status = SRTS_OPENED; - } - else - throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); - } - else if (s->m_Status != SRTS_OPENED) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - // connect_complete() may be called before connect() returns. - // So we need to update the status before connect() is called, - // otherwise the status may be overwritten with wrong value - // (CONNECTED vs. CONNECTING). - s->m_Status = SRTS_CONNECTING; - - /* - * In blocking mode, connect can block for up to 30 seconds for - * rendez-vous mode. Holding the s->m_ControlLock prevent close - * from cancelling the connect - */ - try - { - // InvertedGuard unlocks in the constructor, then locks in the - // destructor, no matter if an exception has fired. - InvertedGuard l_unlocker( s->m_pUDT->m_bSynRecving ? &s->m_ControlLock : 0 ); - s->m_pUDT->startConnect(name, forced_isn); - } - catch (CUDTException& e) // Interceptor, just to change the state. - { - s->m_Status = SRTS_OPENED; - throw e; - } - - // record peer address - delete s->m_pPeerAddr; - if (AF_INET == s->m_iIPversion) - { - s->m_pPeerAddr = (sockaddr*)(new sockaddr_in); - memcpy(s->m_pPeerAddr, name, sizeof(sockaddr_in)); - } - else - { - s->m_pPeerAddr = (sockaddr*)(new sockaddr_in6); - memcpy(s->m_pPeerAddr, name, sizeof(sockaddr_in6)); - } - - // CGuard destructor will delete cg and unlock s->m_ControlLock - - return 0; -} - - -int CUDTUnited::close(const SRTSOCKET u) -{ - CUDTSocket* s = locate(u); - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - HLOGC(mglog.Debug, log << s->m_pUDT->CONID() << " CLOSE. Acquiring control lock"); - - CGuard socket_cg(s->m_ControlLock); - - HLOGC(mglog.Debug, log << s->m_pUDT->CONID() << " CLOSING (removing from listening, closing CUDT)"); - - bool synch_close_snd = s->m_pUDT->m_bSynSending; - //bool synch_close_rcv = s->m_pUDT->m_bSynRecving; - - if (s->m_Status == SRTS_LISTENING) - { - if (s->m_pUDT->m_bBroken) - return 0; - - s->m_ClosureTimeStamp = CTimer::getTime(); - s->m_pUDT->m_bBroken = true; - - // Change towards original UDT: - // Leave all the closing activities for garbageCollect to happen, - // however remove the listener from the RcvQueue IMMEDIATELY. - // Even though garbageCollect would eventually remove the listener - // as well, there would be some time interval between now and the - // moment when it's done, and during this time the application will - // be unable to bind to this port that the about-to-delete listener - // is currently occupying (due to blocked slot in the RcvQueue). - - HLOGC(mglog.Debug, log << s->m_pUDT->CONID() << " CLOSING (removing listener immediately)"); - { - CGuard cg(s->m_pUDT->m_ConnectionLock); - s->m_pUDT->m_bListening = false; - s->m_pUDT->m_pRcvQueue->removeListener(s->m_pUDT); - } - - // broadcast all "accept" waiting - pthread_mutex_lock(&(s->m_AcceptLock)); - pthread_cond_broadcast(&(s->m_AcceptCond)); - pthread_mutex_unlock(&(s->m_AcceptLock)); - - } - else - { - s->m_pUDT->close(); - - // synchronize with garbage collection. - HLOGC(mglog.Debug, log << "@" << u << "U::close done. GLOBAL CLOSE: " << s->m_pUDT->CONID() << ". Acquiring GLOBAL control lock"); - CGuard manager_cg(m_ControlLock); - - // since "s" is located before m_ControlLock, locate it again in case - // it became invalid - map::iterator i = m_Sockets.find(u); - if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) - { - HLOGC(mglog.Debug, log << "@" << u << "U::close: NOT AN ACTIVE SOCKET, returning."); - return 0; - } - s = i->second; - - s->m_Status = SRTS_CLOSED; - - // a socket will not be immediately removed when it is closed - // in order to prevent other methods from accessing invalid address - // a timer is started and the socket will be removed after approximately - // 1 second - s->m_ClosureTimeStamp = CTimer::getTime(); - - m_Sockets.erase(s->m_SocketID); - m_ClosedSockets[s->m_SocketID] = s; - HLOGC(mglog.Debug, log << "@" << u << "U::close: Socket MOVED TO CLOSED for collecting later."); - - CTimer::triggerEvent(); - } - - HLOGC(mglog.Debug, log << "%" << u << ": GLOBAL: CLOSING DONE"); - - // Check if the ID is still in closed sockets before you access it - // (the last triggerEvent could have deleted it). - if ( synch_close_snd ) - { -#if SRT_ENABLE_CLOSE_SYNCH + // Theoretically we can have a situation that more than one + // listener is ready for accept. In this case simply get + // only the first found. + int lsn = st.begin()->first; + sockaddr_storage dummy; + int outlen = sizeof dummy; + return accept(lsn, ((sockaddr*)&dummy), (&outlen)); +} - HLOGC(mglog.Debug, log << "@" << u << " GLOBAL CLOSING: sync-waiting for releasing sender resources..."); - for (;;) - { - CSndBuffer* sb = s->m_pUDT->m_pSndBuffer; - - // Disconnected from buffer - nothing more to check. - if (!sb) - { - HLOGC(mglog.Debug, log << "@" << u << " GLOBAL CLOSING: sending buffer disconnected. Allowed to close."); - break; - } - - // Sender buffer empty - if (sb->getCurrBufSize() == 0) - { - HLOGC(mglog.Debug, log << "@" << u << " GLOBAL CLOSING: sending buffer depleted. Allowed to close."); - break; - } - - // Ok, now you are keeping GC thread hands off the internal data. - // You can check then if it has already deleted the socket or not. - // The socket is either in m_ClosedSockets or is already gone. - - // Done the other way, but still done. You can stop waiting. - bool isgone = false; - { - CGuard manager_cg(m_ControlLock); - isgone = m_ClosedSockets.count(u) == 0; - } - if (!isgone) - { - isgone = !s->m_pUDT->m_bOpened; - } - if (isgone) - { - HLOGC(mglog.Debug, log << "@" << u << " GLOBAL CLOSING: ... gone in the meantime, whatever. Exiting close()."); - break; - } - - HLOGC(mglog.Debug, log << "@" << u << " GLOBAL CLOSING: ... still waiting for any update."); - CTimer::EWait wt = CTimer::waitForEvent(); - - if ( wt == CTimer::WT_ERROR ) - { - HLOGC(mglog.Debug, log << "GLOBAL CLOSING: ... ERROR WHEN WAITING FOR EVENT. Exiting close() to prevent hangup."); - break; - } - - // Continue waiting in case when an event happened or 1s waiting time passed for checkpoint. - } -#endif - } +SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen) +{ + if (pw_addr && !pw_addrlen) + { + LOGC(cnlog.Error, log << "srt_accept: provided address, but address length parameter is missing"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + CUDTSocket* ls = locateSocket(listen); + + if (ls == NULL) + { + LOGC(cnlog.Error, log << "srt_accept: invalid listener socket ID value: " << listen); + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + } - /* - This code is PUT ASIDE for now. - Most likely this will be never required. - It had to hold the closing activity until the time when the receiver buffer is depleted. - However the closing of the socket should only happen when the receiver has received - an information about that the reading is no longer possible (error report from recv/recvfile). - When this happens, the receiver buffer is definitely depleted already and there's no need to check - anything. + // the "listen" socket must be in LISTENING status + if (ls->m_Status != SRTS_LISTENING) + { + LOGC(cnlog.Error, log << "srt_accept: socket @" << listen << " is not in listening state (forgot srt_listen?)"); + throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); + } + + // no "accept" in rendezvous connection setup + if (ls->core().m_config.bRendezvous) + { + LOGC(cnlog.Fatal, + log << "CUDTUnited::accept: RENDEZVOUS flag passed through check in srt_listen when it set listen state"); + // This problem should never happen because `srt_listen` function should have + // checked this situation before and not set listen state in result. + // Inform the user about the invalid state in the universal way. + throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); + } + + SRTSOCKET u = CUDT::INVALID_SOCK; + bool accepted = false; + + // !!only one conection can be set up each time!! + while (!accepted) + { + UniqueLock accept_lock(ls->m_AcceptLock); + CSync accept_sync(ls->m_AcceptCond, accept_lock); + + if ((ls->m_Status != SRTS_LISTENING) || ls->core().m_bBroken) + { + // This socket has been closed. + accepted = true; + } + else if (ls->m_QueuedSockets.size() > 0) + { + set::iterator b = ls->m_QueuedSockets.begin(); + u = *b; + ls->m_QueuedSockets.erase(b); + accepted = true; + } + else if (!ls->core().m_config.bSynRecving) + { + accepted = true; + } + + if (!accepted && (ls->m_Status == SRTS_LISTENING)) + accept_sync.wait(); + + if (ls->m_QueuedSockets.empty()) + m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, false); + } + + if (u == CUDT::INVALID_SOCK) + { + // non-blocking receiving, no connection available + if (!ls->core().m_config.bSynRecving) + { + LOGC(cnlog.Error, log << "srt_accept: no pending connection available at the moment"); + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + + LOGC(cnlog.Error, log << "srt_accept: listener socket @" << listen << " is already closed"); + // listening socket is closed + throw CUDTException(MJ_SETUP, MN_CLOSED, 0); + } - Should there appear any other conditions in future under which the closing process should be - delayed until the receiver buffer is empty, this code can be filled here. + CUDTSocket* s = locateSocket(u); + if (s == NULL) + { + LOGC(cnlog.Error, log << "srt_accept: pending connection has unexpectedly closed"); + throw CUDTException(MJ_SETUP, MN_CLOSED, 0); + } - if ( synch_close_rcv ) - { - ... - } - */ + // Set properly the SRTO_GROUPCONNECT flag + s->core().m_config.iGroupConnect = 0; - return 0; + // Check if LISTENER has the SRTO_GROUPCONNECT flag set, + // and the already accepted socket has successfully joined + // the mirror group. If so, RETURN THE GROUP ID, not the socket ID. +#if ENABLE_BONDING + if (ls->core().m_config.iGroupConnect == 1 && s->m_GroupOf) + { + // Put a lock to protect the group against accidental deletion + // in the meantime. + ScopedLock glock(m_GlobControlLock); + // Check again; it's unlikely to happen, but + // it's a theoretically possible scenario + if (s->m_GroupOf) + { + u = s->m_GroupOf->m_GroupID; + s->core().m_config.iGroupConnect = 1; // should be derived from ls, but make sure + + // Mark the beginning of the connection at the moment + // when the group ID is returned to the app caller + s->m_GroupOf->m_stats.tsLastSampleTime = steady_clock::now(); + } + else + { + LOGC(smlog.Error, log << "accept: IPE: socket's group deleted in the meantime of accept process???"); + } + } +#endif + + ScopedLock cg(s->m_ControlLock); + + if (pw_addr != NULL && pw_addrlen != NULL) + { + // Check if the length of the buffer to fill the name in + // was large enough. + const int len = s->m_PeerAddr.size(); + if (*pw_addrlen < len) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + memcpy((pw_addr), &s->m_PeerAddr, len); + *pw_addrlen = len; + } + + return u; +} + +int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) +{ + // Here both srcname and tarname must be specified + if (!srcname || !tarname || size_t(namelen) < sizeof(sockaddr_in)) + { + LOGC(aclog.Error, + log << "connect(with source): invalid call: srcname=" << srcname << " tarname=" << tarname + << " namelen=" << namelen); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + + sockaddr_any source_addr(srcname, namelen); + if (source_addr.len == 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + sockaddr_any target_addr(tarname, namelen); + if (target_addr.len == 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + +#if ENABLE_BONDING + // Check affiliation of the socket. It's now allowed for it to be + // a group or socket. For a group, add automatically a socket to + // the group. + if (u & SRTGROUP_MASK) + { + GroupKeeper k(*this, u, ERH_THROW); + // Note: forced_isn is ignored when connecting a group. + // The group manages the ISN by itself ALWAYS, that is, + // it's generated anew for the very first socket, and then + // derived by all sockets in the group. + SRT_SOCKGROUPCONFIG gd[1] = {srt_prepare_endpoint(srcname, tarname, namelen)}; + + // When connecting to exactly one target, only this very target + // can be returned as a socket, so rewritten back array can be ignored. + return singleMemberConnect(k.group, gd); + } +#endif + + CUDTSocket* s = locateSocket(u); + if (s == NULL) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + // For a single socket, just do bind, then connect + bind(s, source_addr); + return connectIn(s, target_addr, SRT_SEQNO_NONE); +} + +int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +{ + sockaddr_any target_addr(name, namelen); + if (target_addr.len == 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + +#if ENABLE_BONDING + // Check affiliation of the socket. It's now allowed for it to be + // a group or socket. For a group, add automatically a socket to + // the group. + if (u & SRTGROUP_MASK) + { + GroupKeeper k(*this, u, ERH_THROW); + + // Note: forced_isn is ignored when connecting a group. + // The group manages the ISN by itself ALWAYS, that is, + // it's generated anew for the very first socket, and then + // derived by all sockets in the group. + SRT_SOCKGROUPCONFIG gd[1] = {srt_prepare_endpoint(NULL, name, namelen)}; + return singleMemberConnect(k.group, gd); + } +#endif + + CUDTSocket* s = locateSocket(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + return connectIn(s, target_addr, forced_isn); +} + +#if ENABLE_BONDING +int srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) +{ + int gstat = groupConnect(pg, gd, 1); + if (gstat == -1) + { + // We have only one element here, so refer to it. + // Sanity check + if (gd->errorcode == SRT_SUCCESS) + gd->errorcode = SRT_EINVPARAM; + + CodeMajor mj = CodeMajor(gd->errorcode / 1000); + CodeMinor mn = CodeMinor(gd->errorcode % 1000); + + return CUDT::APIError(mj, mn); + } + + return gstat; +} + +// [[using assert(pg->m_iBusy > 0)]] +int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) +{ + CUDTGroup& g = *pg; + SRT_ASSERT(g.m_iBusy > 0); + + // Check and report errors on data brought in by srt_prepare_endpoint, + // as the latter function has no possibility to report errors. + for (int tii = 0; tii < arraysize; ++tii) + { + if (targets[tii].srcaddr.ss_family != targets[tii].peeraddr.ss_family) + { + LOGC(aclog.Error, log << "srt_connect/group: family differs on source and target address"); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + + if (targets[tii].weight > CUDT::MAX_WEIGHT) + { + LOGC(aclog.Error, log << "srt_connect/group: weight value must be between 0 and " << (+CUDT::MAX_WEIGHT)); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + } + + // If the open state switched to OPENED, the blocking mode + // must make it wait for connecting it. Doing connect when the + // group is already OPENED returns immediately, regardless if the + // connection is going to later succeed or fail (this will be + // known in the group state information). + bool block_new_opened = !g.m_bOpened && g.m_bSynRecving; + const bool was_empty = g.groupEmpty(); + + // In case the group was retried connection, clear first all epoll readiness. + const int ncleared = m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_ERR, false); + if (was_empty || ncleared) + { + HLOGC(aclog.Debug, + log << "srt_connect/group: clearing IN/OUT because was_empty=" << was_empty + << " || ncleared=" << ncleared); + // IN/OUT only in case when the group is empty, otherwise it would + // clear out correct readiness resulting from earlier calls. + // This also should happen if ERR flag was set, as IN and OUT could be set, too. + m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); + } + SRTSOCKET retval = -1; + + int eid = -1; + int connect_modes = SRT_EPOLL_CONNECT | SRT_EPOLL_ERR; + if (block_new_opened) + { + // Create this eid only to block-wait for the first + // connection. + eid = srt_epoll_create(); + } + + // Use private map to avoid searching in the + // overall map. + map spawned; + + HLOGC(aclog.Debug, + log << "groupConnect: will connect " << arraysize << " links and " + << (block_new_opened ? "BLOCK until any is ready" : "leave the process in background")); + + for (int tii = 0; tii < arraysize; ++tii) + { + sockaddr_any target_addr(targets[tii].peeraddr); + sockaddr_any source_addr(targets[tii].srcaddr); + SRTSOCKET& sid_rloc = targets[tii].id; + int& erc_rloc = targets[tii].errorcode; + erc_rloc = SRT_SUCCESS; // preinitialized + HLOGC(aclog.Debug, log << "groupConnect: taking on " << sockaddr_any(targets[tii].peeraddr).str()); + + CUDTSocket* ns = 0; + + // NOTE: After calling newSocket, the socket is mapped into m_Sockets. + // It must be MANUALLY removed from this list in case we need it deleted. + SRTSOCKET sid = newSocket(&ns); + + if (pg->m_cbConnectHook) + { + // Derive the connect hook by the socket, if set on the group + ns->core().m_cbConnectHook = pg->m_cbConnectHook; + } + + SRT_SocketOptionObject* config = targets[tii].config; + + // XXX Support non-blocking mode: + // If the group has nonblocking set for connect (SNDSYN), + // then it must set so on the socket. Then, the connection + // process is asynchronous. The socket appears first as + // GST_PENDING state, and only after the socket becomes + // connected does its status in the group turn into GST_IDLE. + + // Set all options that were requested by the options set on a group + // prior to connecting. + string error_reason SRT_ATR_UNUSED; + try + { + for (size_t i = 0; i < g.m_config.size(); ++i) + { + HLOGC(aclog.Debug, log << "groupConnect: OPTION @" << sid << " #" << g.m_config[i].so); + error_reason = "setting group-derived option: #" + Sprint(g.m_config[i].so); + ns->core().setOpt(g.m_config[i].so, &g.m_config[i].value[0], (int)g.m_config[i].value.size()); + } + + // Do not try to set a user option if failed already. + if (config) + { + error_reason = "user option"; + ns->core().applyMemberConfigObject(*config); + } + + error_reason = "bound address"; + // We got it. Bind the socket, if the source address was set + if (!source_addr.empty()) + bind(ns, source_addr); + } + catch (CUDTException& e) + { + // Just notify the problem, but the loop must continue. + // Set the original error as reported. + targets[tii].errorcode = e.getErrorCode(); + LOGC(aclog.Error, log << "srt_connect_group: failed to set " << error_reason); + } + catch (...) + { + // Set the general EINVPARAM - this error should never happen + LOGC(aclog.Error, log << "IPE: CUDT::setOpt reported unknown exception"); + targets[tii].errorcode = SRT_EINVPARAM; + } + + // Add socket to the group. + // Do it after setting all stored options, as some of them may + // influence some group data. + + srt::groups::SocketData data = srt::groups::prepareSocketData(ns); + if (targets[tii].token != -1) + { + // Reuse the token, if specified by the caller + data.token = targets[tii].token; + } + else + { + // Otherwise generate and write back the token + data.token = CUDTGroup::genToken(); + targets[tii].token = data.token; + } + + { + ScopedLock cs(m_GlobControlLock); + if (m_Sockets.count(sid) == 0) + { + HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " deleted in process"); + // Someone deleted the socket in the meantime? + // Unlikely, but possible in theory. + // Don't delete anyhting - it's alreay done. + continue; + } + + // There's nothing wrong with preparing the data first + // even if this happens for nothing. But now, under the lock + // and after checking that the socket still exists, check now + // if this succeeded, and then also if the group is still usable. + // The group will surely exist because it's set busy, until the + // end of this function. But it might be simultaneously requested closed. + bool proceed = true; + + if (targets[tii].errorcode != SRT_SUCCESS) + { + HLOGC(aclog.Debug, + log << "srt_connect_group: not processing @" << sid << " due to error in setting options"); + proceed = false; + } + + if (g.m_bClosing) + { + HLOGC(aclog.Debug, + log << "srt_connect_group: not processing @" << sid << " due to CLOSED GROUP $" << g.m_GroupID); + proceed = false; + } + + if (proceed) + { + CUDTGroup::SocketData* f = g.add(data); + ns->m_GroupMemberData = f; + ns->m_GroupOf = &g; + f->weight = targets[tii].weight; + LOGC(aclog.Note, log << "srt_connect_group: socket @" << sid << " added to group $" << g.m_GroupID); + } + else + { + targets[tii].id = CUDT::INVALID_SOCK; + delete ns; + m_Sockets.erase(sid); + + // If failed to set options, then do not continue + // neither with binding, nor with connecting. + continue; + } + } + + // XXX This should be reenabled later, this should + // be probably still in use to exchange information about + // packets assymetrically lost. But for no other purpose. + /* + ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival); + */ + + int isn = g.currentSchedSequence(); + + // Don't synchronize ISN in case of synch on msgno. Every link + // may send their own payloads independently. + if (g.synconmsgno()) + { + HLOGC(aclog.Debug, log << "groupConnect: NOT synchronizing sequence numbers: will sync on msgno"); + isn = -1; + } + + // Set it the groupconnect option, as all in-group sockets should have. + ns->core().m_config.iGroupConnect = 1; + + // Every group member will have always nonblocking + // (this implies also non-blocking connect/accept). + // The group facility functions will block when necessary + // using epoll_wait. + ns->core().m_config.bSynRecving = false; + ns->core().m_config.bSynSending = false; + + HLOGC(aclog.Debug, log << "groupConnect: NOTIFIED AS PENDING @" << sid << " both read and write"); + // If this socket is not to block the current connect process, + // it may still be needed for the further check if the redundant + // connection succeeded or failed and whether the new socket is + // ready to use or needs to be closed. + epoll_add_usock_INTERNAL(g.m_SndEID, ns, &connect_modes); + epoll_add_usock_INTERNAL(g.m_RcvEID, ns, &connect_modes); + + // Adding a socket on which we need to block to BOTH these tracking EIDs + // and the blocker EID. We'll simply remove from them later all sockets that + // got connected state or were broken. + + if (block_new_opened) + { + HLOGC(aclog.Debug, log << "groupConnect: WILL BLOCK on @" << sid << " until connected"); + epoll_add_usock_INTERNAL(eid, ns, &connect_modes); + } + + // And connect + try + { + HLOGC(aclog.Debug, log << "groupConnect: connecting a new socket with ISN=" << isn); + connectIn(ns, target_addr, isn); + } + catch (const CUDTException& e) + { + LOGC(aclog.Error, + log << "groupConnect: socket @" << sid << " in group " << pg->id() << " failed to connect"); + // We know it does belong to a group. + // Remove it first because this involves a mutex, and we want + // to avoid locking more than one mutex at a time. + erc_rloc = e.getErrorCode(); + targets[tii].errorcode = e.getErrorCode(); + targets[tii].id = CUDT::INVALID_SOCK; + + ScopedLock cl(m_GlobControlLock); + ns->removeFromGroup(false); + m_Sockets.erase(ns->m_SocketID); + // Intercept to delete the socket on failure. + delete ns; + continue; + } + catch (...) + { + LOGC(aclog.Fatal, log << "groupConnect: IPE: UNKNOWN EXCEPTION from connectIn"); + targets[tii].errorcode = SRT_ESYSOBJ; + targets[tii].id = CUDT::INVALID_SOCK; + ScopedLock cl(m_GlobControlLock); + ns->removeFromGroup(false); + m_Sockets.erase(ns->m_SocketID); + // Intercept to delete the socket on failure. + delete ns; + + // Do not use original exception, it may crash off a C API. + throw CUDTException(MJ_SYSTEMRES, MN_OBJECT); + } + + SRT_SOCKSTATUS st; + { + ScopedLock grd(ns->m_ControlLock); + st = ns->getStatus(); + } + + { + // NOTE: Not applying m_GlobControlLock because the group is now + // set busy, so it won't be deleted, even if it was requested to be closed. + ScopedLock grd(g.m_GroupLock); + + if (!ns->m_GroupOf) + { + // The situation could get changed between the unlock and lock of m_GroupLock. + // This must be checked again. + // If a socket has been removed from group, it means that some other thread is + // currently trying to delete the socket. Therefore it doesn't have, and even shouldn't, + // be deleted here. Just exit with error report. + LOGC(aclog.Error, log << "groupConnect: self-created member socket deleted during process, SKIPPING."); + + // Do not report the error from here, just ignore this socket. + continue; + } + + // If m_GroupOf is not NULL, the m_IncludedIter is still valid. + CUDTGroup::SocketData* f = ns->m_GroupMemberData; + + // Now under a group lock, we need to make sure the group isn't being closed + // in order not to add a socket to a dead group. + if (g.m_bClosing) + { + LOGC(aclog.Error, log << "groupConnect: group deleted while connecting; breaking the process"); + + // Set the status as pending so that the socket is taken care of later. + // Note that all earlier sockets that were processed in this loop were either + // set BROKEN or PENDING. + f->sndstate = SRT_GST_PENDING; + f->rcvstate = SRT_GST_PENDING; + retval = -1; + break; + } + + HLOGC(aclog.Debug, + log << "groupConnect: @" << sid << " connection successful, setting group OPEN (was " + << (g.m_bOpened ? "ALREADY" : "NOT") << "), will " << (block_new_opened ? "" : "NOT ") + << "block the connect call, status:" << SockStatusStr(st)); + + // XXX OPEN OR CONNECTED? + // BLOCK IF NOT OPEN OR BLOCK IF NOT CONNECTED? + // + // What happens to blocking when there are 2 connections + // pending, about to be broken, and srt_connect() is called again? + // SHOULD BLOCK the latter, even though is OPEN. + // Or, OPEN should be removed from here and srt_connect(_group) + // should block always if the group doesn't have neither 1 conencted link + g.m_bOpened = true; + + g.m_stats.tsLastSampleTime = steady_clock::now(); + + f->laststatus = st; + // Check the socket status and update it. + // Turn the group state of the socket to IDLE only if + // connection is established or in progress + f->agent = source_addr; + f->peer = target_addr; + + if (st >= SRTS_BROKEN) + { + f->sndstate = SRT_GST_BROKEN; + f->rcvstate = SRT_GST_BROKEN; + epoll_remove_socket_INTERNAL(g.m_SndEID, ns); + epoll_remove_socket_INTERNAL(g.m_RcvEID, ns); + } + else + { + f->sndstate = SRT_GST_PENDING; + f->rcvstate = SRT_GST_PENDING; + spawned[sid] = ns; + + sid_rloc = sid; + erc_rloc = 0; + retval = sid; + } + } + } + + if (retval == -1) + { + HLOGC(aclog.Debug, log << "groupConnect: none succeeded as background-spawn, exit with error"); + block_new_opened = false; // Avoid executing further while loop + } + + vector broken; + + while (block_new_opened) + { + if (spawned.empty()) + { + // All were removed due to errors. + retval = -1; + break; + } + HLOGC(aclog.Debug, log << "groupConnect: first connection, applying EPOLL WAITING."); + int len = (int)spawned.size(); + vector ready(spawned.size()); + const int estat = srt_epoll_wait(eid, + NULL, + NULL, // IN/ACCEPT + &ready[0], + &len, // OUT/CONNECT + -1, // indefinitely (FIXME Check if it needs to REGARD CONNECTION TIMEOUT!) + NULL, + NULL, + NULL, + NULL); + + // Sanity check. Shouldn't happen if subs are in sync with spawned. + if (estat == -1) + { +#if ENABLE_LOGGING + CUDTException& x = CUDT::getlasterror(); + if (x.getErrorCode() != SRT_EPOLLEMPTY) + { + LOGC(aclog.Error, + log << "groupConnect: srt_epoll_wait failed not because empty, unexpected IPE:" + << x.getErrorMessage()); + } +#endif + HLOGC(aclog.Debug, log << "groupConnect: srt_epoll_wait failed - breaking the wait loop"); + retval = -1; + break; + } + + // At the moment when you are going to work with real sockets, + // lock the groups so that no one messes up with something here + // in the meantime. + + ScopedLock lock(*g.exp_groupLock()); + + // NOTE: UNDER m_GroupLock, NO API FUNCTION CALLS DARE TO HAPPEN BELOW! + + // Check first if a socket wasn't closed in the meantime. It will be + // automatically removed from all EIDs, but there's no sense in keeping + // them in 'spawned' map. + for (map::iterator y = spawned.begin(); y != spawned.end(); ++y) + { + SRTSOCKET sid = y->first; + if (y->second->getStatus() >= SRTS_BROKEN) + { + HLOGC(aclog.Debug, + log << "groupConnect: Socket @" << sid + << " got BROKEN in the meantine during the check, remove from candidates"); + // Remove from spawned and try again + broken.push_back(sid); + + epoll_remove_socket_INTERNAL(eid, y->second); + epoll_remove_socket_INTERNAL(g.m_SndEID, y->second); + epoll_remove_socket_INTERNAL(g.m_RcvEID, y->second); + } + } + + // Remove them outside the loop because this can't be done + // while iterating over the same container. + for (size_t i = 0; i < broken.size(); ++i) + spawned.erase(broken[i]); + + // Check the sockets if they were reported due + // to have connected or due to have failed. + // Distill successful ones. If distilled nothing, return -1. + // If not all sockets were reported in this instance, repeat + // the call until you get information about all of them. + for (int i = 0; i < len; ++i) + { + map::iterator x = spawned.find(ready[i]); + if (x == spawned.end()) + { + // Might be removed above - ignore it. + continue; + } + + SRTSOCKET sid = x->first; + CUDTSocket* s = x->second; + + // Check status. If failed, remove from spawned + // and try again. + SRT_SOCKSTATUS st = s->getStatus(); + if (st >= SRTS_BROKEN) + { + HLOGC(aclog.Debug, + log << "groupConnect: Socket @" << sid + << " got BROKEN during background connect, remove & TRY AGAIN"); + // Remove from spawned and try again + if (spawned.erase(sid)) + broken.push_back(sid); + + epoll_remove_socket_INTERNAL(eid, s); + epoll_remove_socket_INTERNAL(g.m_SndEID, s); + epoll_remove_socket_INTERNAL(g.m_RcvEID, s); + + continue; + } + + if (st == SRTS_CONNECTED) + { + HLOGC(aclog.Debug, + log << "groupConnect: Socket @" << sid << " got CONNECTED as first in the group - reporting"); + retval = sid; + g.m_bConnected = true; + block_new_opened = false; // Interrupt also rolling epoll (outer loop) + + // Remove this socket from SND EID because it doesn't need to + // be connection-tracked anymore. Don't remove from the RCV EID + // however because RCV procedure relies on epoll also for reading + // and when found this socket connected it will "upgrade" it to + // read-ready tracking only. + epoll_remove_socket_INTERNAL(g.m_SndEID, s); + break; + } + + // Spurious? + HLOGC(aclog.Debug, + log << "groupConnect: Socket @" << sid << " got spurious wakeup in " << SockStatusStr(st) + << " TRY AGAIN"); + } + // END of m_GroupLock CS - you can safely use API functions now. + } + // Finished, delete epoll. + if (eid != -1) + { + HLOGC(aclog.Debug, log << "connect FIRST IN THE GROUP finished, removing E" << eid); + srt_epoll_release(eid); + } + + for (vector::iterator b = broken.begin(); b != broken.end(); ++b) + { + CUDTSocket* s = locateSocket(*b, ERH_RETURN); + if (!s) + continue; + + // This will also automatically remove it from the group and all eids + close(s); + } + + // There's no possibility to report a problem on every connection + // separately in case when every single connection has failed. What + // is more interesting, it's only a matter of luck that all connections + // fail at exactly the same time. OTOH if all are to fail, this + // function will still be polling sockets to determine the last man + // standing. Each one could, however, break by a different reason, + // for example, one by timeout, another by wrong passphrase. Check + // the `errorcode` field to determine the reaon for particular link. + if (retval == -1) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + return retval; +} +#endif + +int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn) +{ + ScopedLock cg(s->m_ControlLock); + // a socket can "connect" only if it is in the following states: + // - OPENED: assume the socket binding parameters are configured + // - INIT: configure binding parameters here + // - any other (meaning, already connected): report error + + if (s->m_Status == SRTS_INIT) + { + if (s->core().m_config.bRendezvous) + throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); + + // If bind() was done first on this socket, then the + // socket will not perform this step. This actually does the + // same thing as bind() does, just with empty address so that + // the binding parameters are autoselected. + + s->core().open(); + sockaddr_any autoselect_sa(target_addr.family()); + // This will create such a sockaddr_any that + // will return true from empty(). + updateMux(s, autoselect_sa); // <<---- updateMux + // -> C(Snd|Rcv)Queue::init + // -> pthread_create(...C(Snd|Rcv)Queue::worker...) + s->m_Status = SRTS_OPENED; + } + else + { + if (s->m_Status != SRTS_OPENED) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + + // status = SRTS_OPENED, so family should be known already. + if (target_addr.family() != s->m_SelfAddr.family()) + { + LOGP(cnlog.Error, "srt_connect: socket is bound to a different family than target address"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + } + + // connect_complete() may be called before connect() returns. + // So we need to update the status before connect() is called, + // otherwise the status may be overwritten with wrong value + // (CONNECTED vs. CONNECTING). + s->m_Status = SRTS_CONNECTING; + + /* + * In blocking mode, connect can block for up to 30 seconds for + * rendez-vous mode. Holding the s->m_ControlLock prevent close + * from cancelling the connect + */ + try + { + s->core().startConnect(target_addr, forced_isn); + } + catch (const CUDTException&) // Interceptor, just to change the state. + { + s->m_Status = SRTS_OPENED; + throw; + } + + return 0; +} + +int srt::CUDTUnited::close(const SRTSOCKET u) +{ +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + GroupKeeper k(*this, u, ERH_THROW); + k.group->close(); + deleteGroup(k.group); + return 0; + } +#endif + CUDTSocket* s = locateSocket(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + return close(s); +} + +#if ENABLE_BONDING +void srt::CUDTUnited::deleteGroup(CUDTGroup* g) +{ + using srt_logging::gmlog; + + srt::sync::ScopedLock cg(m_GlobControlLock); + return deleteGroup_LOCKED(g); +} + +// [[using locked(m_GlobControlLock)]] +void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) +{ + SRT_ASSERT(g->groupEmpty()); + + // After that the group is no longer findable by GroupKeeper + m_Groups.erase(g->m_GroupID); + m_ClosedGroups[g->m_GroupID] = g; + + // Paranoid check: since the group is in m_ClosedGroups + // it may potentially be deleted. Make sure no socket points + // to it. Actually all sockets should have been already removed + // from the group container, so if any does, it's invalid. + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + if (s->m_GroupOf == g) + { + HLOGC(smlog.Debug, log << "deleteGroup: IPE: existing @" << s->m_SocketID << " points to a dead group!"); + s->m_GroupOf = NULL; + s->m_GroupMemberData = NULL; + } + } + + // Just in case, do it in closed sockets, too, although this should be + // always done before moving to it. + for (sockets_t::iterator i = m_ClosedSockets.begin(); i != m_ClosedSockets.end(); ++i) + { + CUDTSocket* s = i->second; + if (s->m_GroupOf == g) + { + HLOGC(smlog.Debug, log << "deleteGroup: IPE: closed @" << s->m_SocketID << " points to a dead group!"); + s->m_GroupOf = NULL; + s->m_GroupMemberData = NULL; + } + } +} +#endif + +int srt::CUDTUnited::close(CUDTSocket* s) +{ + HLOGC(smlog.Debug, log << s->core().CONID() << " CLOSE. Acquiring control lock"); + ScopedLock socket_cg(s->m_ControlLock); + HLOGC(smlog.Debug, log << s->core().CONID() << " CLOSING (removing from listening, closing CUDT)"); + + const bool synch_close_snd = s->core().m_config.bSynSending; + + SRTSOCKET u = s->m_SocketID; + + if (s->m_Status == SRTS_LISTENING) + { + if (s->core().m_bBroken) + return 0; + + s->m_tsClosureTimeStamp = steady_clock::now(); + s->core().m_bBroken = true; + + // Change towards original UDT: + // Leave all the closing activities for garbageCollect to happen, + // however remove the listener from the RcvQueue IMMEDIATELY. + // Even though garbageCollect would eventually remove the listener + // as well, there would be some time interval between now and the + // moment when it's done, and during this time the application will + // be unable to bind to this port that the about-to-delete listener + // is currently occupying (due to blocked slot in the RcvQueue). + + HLOGC(smlog.Debug, log << s->core().CONID() << " CLOSING (removing listener immediately)"); + s->core().notListening(); + + // broadcast all "accept" waiting + CSync::lock_notify_all(s->m_AcceptCond, s->m_AcceptLock); + } + else + { + // Note: this call may be done on a socket that hasn't finished + // sending all packets scheduled for sending, which means, this call + // may block INDEFINITELY. As long as it's acceptable to block the + // call to srt_close(), and all functions in all threads where this + // very socket is used, this shall not block the central database. + s->core().closeInternal(); + + // synchronize with garbage collection. + HLOGC(smlog.Debug, + log << "@" << u << "U::close done. GLOBAL CLOSE: " << s->core().CONID() + << ". Acquiring GLOBAL control lock"); + ScopedLock manager_cg(m_GlobControlLock); + // since "s" is located before m_GlobControlLock, locate it again in case + // it became invalid + // XXX This is very weird; if we state that the CUDTSocket object + // could not be deleted between locks, then definitely it couldn't + // also change the pointer value. There's no other reason for getting + // this iterator but to obtain the 's' pointer, which is impossible to + // be different than previous 's' (m_Sockets is a map that stores pointers + // transparently). This iterator isn't even later used to delete the socket + // from the container, though it would be more efficient. + // FURTHER RESEARCH REQUIRED. + sockets_t::iterator i = m_Sockets.find(u); + if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) + { + HLOGC(smlog.Debug, log << "@" << u << "U::close: NOT AN ACTIVE SOCKET, returning."); + return 0; + } + s = i->second; + s->setClosed(); + +#if ENABLE_BONDING + if (s->m_GroupOf) + { + HLOGC(smlog.Debug, + log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); + s->removeFromGroup(true); + } +#endif + + m_Sockets.erase(s->m_SocketID); + m_ClosedSockets[s->m_SocketID] = s; + HLOGC(smlog.Debug, log << "@" << u << "U::close: Socket MOVED TO CLOSED for collecting later."); + + CGlobEvent::triggerEvent(); + } + + HLOGC(smlog.Debug, log << "@" << u << ": GLOBAL: CLOSING DONE"); + + // Check if the ID is still in closed sockets before you access it + // (the last triggerEvent could have deleted it). + if (synch_close_snd) + { +#if SRT_ENABLE_CLOSE_SYNCH + + HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sync-waiting for releasing sender resources..."); + for (;;) + { + CSndBuffer* sb = s->core().m_pSndBuffer; + + // Disconnected from buffer - nothing more to check. + if (!sb) + { + HLOGC(smlog.Debug, + log << "@" << u << " GLOBAL CLOSING: sending buffer disconnected. Allowed to close."); + break; + } + + // Sender buffer empty + if (sb->getCurrBufSize() == 0) + { + HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: sending buffer depleted. Allowed to close."); + break; + } + + // Ok, now you are keeping GC thread hands off the internal data. + // You can check then if it has already deleted the socket or not. + // The socket is either in m_ClosedSockets or is already gone. + + // Done the other way, but still done. You can stop waiting. + bool isgone = false; + { + ScopedLock manager_cg(m_GlobControlLock); + isgone = m_ClosedSockets.count(u) == 0; + } + if (!isgone) + { + isgone = !s->core().m_bOpened; + } + if (isgone) + { + HLOGC(smlog.Debug, + log << "@" << u << " GLOBAL CLOSING: ... gone in the meantime, whatever. Exiting close()."); + break; + } + + HLOGC(smlog.Debug, log << "@" << u << " GLOBAL CLOSING: ... still waiting for any update."); + // How to handle a possible error here? + CGlobEvent::waitForEvent(); + + // Continue waiting in case when an event happened or 1s waiting time passed for checkpoint. + } +#endif + } + + /* + This code is PUT ASIDE for now. + Most likely this will be never required. + It had to hold the closing activity until the time when the receiver buffer is depleted. + However the closing of the socket should only happen when the receiver has received + an information about that the reading is no longer possible (error report from recv/recvfile). + When this happens, the receiver buffer is definitely depleted already and there's no need to check + anything. + + Should there appear any other conditions in future under which the closing process should be + delayed until the receiver buffer is empty, this code can be filled here. + + if ( synch_close_rcv ) + { + ... + } + */ + + return 0; +} + +void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) +{ + if (!pw_name || !pw_namelen) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + if (getStatus(u) != SRTS_CONNECTED) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + CUDTSocket* s = locateSocket(u); + + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (!s->core().m_bConnected || s->core().m_bBroken) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + const int len = s->m_PeerAddr.size(); + if (*pw_namelen < len) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + memcpy((pw_name), &s->m_PeerAddr.sa, len); + *pw_namelen = len; +} + +void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) +{ + if (!pw_name || !pw_namelen) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + CUDTSocket* s = locateSocket(u); + + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (s->core().m_bBroken) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (s->m_Status == SRTS_INIT) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + const int len = s->m_SelfAddr.size(); + if (*pw_namelen < len) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + memcpy((pw_name), &s->m_SelfAddr.sa, len); + *pw_namelen = len; +} + +int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) +{ + const steady_clock::time_point entertime = steady_clock::now(); + + const int64_t timeo_us = timeout ? static_cast(timeout->tv_sec) * 1000000 + timeout->tv_usec : -1; + const steady_clock::duration timeo(microseconds_from(timeo_us)); + + // initialize results + int count = 0; + set rs, ws, es; + + // retrieve related UDT sockets + vector ru, wu, eu; + CUDTSocket* s; + if (readfds) + for (set::iterator i1 = readfds->begin(); i1 != readfds->end(); ++i1) + { + if (getStatus(*i1) == SRTS_BROKEN) + { + rs.insert(*i1); + ++count; + } + else if (!(s = locateSocket(*i1))) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + else + ru.push_back(s); + } + if (writefds) + for (set::iterator i2 = writefds->begin(); i2 != writefds->end(); ++i2) + { + if (getStatus(*i2) == SRTS_BROKEN) + { + ws.insert(*i2); + ++count; + } + else if (!(s = locateSocket(*i2))) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + else + wu.push_back(s); + } + if (exceptfds) + for (set::iterator i3 = exceptfds->begin(); i3 != exceptfds->end(); ++i3) + { + if (getStatus(*i3) == SRTS_BROKEN) + { + es.insert(*i3); + ++count; + } + else if (!(s = locateSocket(*i3))) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + else + eu.push_back(s); + } + + do + { + // query read sockets + for (vector::iterator j1 = ru.begin(); j1 != ru.end(); ++j1) + { + s = *j1; + + if (s->readReady() || s->m_Status == SRTS_CLOSED) + { + rs.insert(s->m_SocketID); + ++count; + } + } + + // query write sockets + for (vector::iterator j2 = wu.begin(); j2 != wu.end(); ++j2) + { + s = *j2; + + if (s->writeReady() || s->m_Status == SRTS_CLOSED) + { + ws.insert(s->m_SocketID); + ++count; + } + } + + // query exceptions on sockets + for (vector::iterator j3 = eu.begin(); j3 != eu.end(); ++j3) + { + // check connection request status, not supported now + } + + if (0 < count) + break; + + CGlobEvent::waitForEvent(); + } while (timeo > steady_clock::now() - entertime); + + if (readfds) + *readfds = rs; + + if (writefds) + *writefds = ws; + + if (exceptfds) + *exceptfds = es; + + return count; +} + +int srt::CUDTUnited::selectEx(const vector& fds, + vector* readfds, + vector* writefds, + vector* exceptfds, + int64_t msTimeOut) +{ + const steady_clock::time_point entertime = steady_clock::now(); + + const int64_t timeo_us = msTimeOut >= 0 ? msTimeOut * 1000 : -1; + const steady_clock::duration timeo(microseconds_from(timeo_us)); + + // initialize results + int count = 0; + if (readfds) + readfds->clear(); + if (writefds) + writefds->clear(); + if (exceptfds) + exceptfds->clear(); + + do + { + for (vector::const_iterator i = fds.begin(); i != fds.end(); ++i) + { + CUDTSocket* s = locateSocket(*i); + + if ((!s) || s->core().m_bBroken || (s->m_Status == SRTS_CLOSED)) + { + if (exceptfds) + { + exceptfds->push_back(*i); + ++count; + } + continue; + } + + if (readfds) + { + if ((s->core().m_bConnected && s->core().m_pRcvBuffer->isRcvDataReady()) || + (s->core().m_bListening && (s->m_QueuedSockets.size() > 0))) + { + readfds->push_back(s->m_SocketID); + ++count; + } + } + + if (writefds) + { + if (s->core().m_bConnected && + (s->core().m_pSndBuffer->getCurrBufSize() < s->core().m_config.iSndBufSize)) + { + writefds->push_back(s->m_SocketID); + ++count; + } + } + } + + if (count > 0) + break; + + CGlobEvent::waitForEvent(); + } while (timeo > steady_clock::now() - entertime); + + return count; +} + +int srt::CUDTUnited::epoll_create() +{ + return m_EPoll.create(); +} + +int srt::CUDTUnited::epoll_clear_usocks(int eid) +{ + return m_EPoll.clear_usocks(eid); +} + +int srt::CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) +{ + int ret = -1; +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + GroupKeeper k(*this, u, ERH_THROW); + ret = m_EPoll.update_usock(eid, u, events); + k.group->addEPoll(eid); + return 0; + } +#endif + + CUDTSocket* s = locateSocket(u); + if (s) + { + ret = epoll_add_usock_INTERNAL(eid, s, events); + } + else + { + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + } + + return ret; +} + +// NOTE: WILL LOCK (serially): +// - CEPoll::m_EPollLock +// - CUDT::m_RecvLock +int srt::CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) +{ + int ret = m_EPoll.update_usock(eid, s->m_SocketID, events); + s->core().addEPoll(eid); + return ret; +} + +int srt::CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +{ + return m_EPoll.add_ssock(eid, s, events); +} + +int srt::CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +{ + return m_EPoll.update_ssock(eid, s, events); +} + +template +int srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) +{ + // XXX Not sure if this is anyhow necessary because setting readiness + // to false doesn't actually trigger any action. Further research needed. + HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING readiness on E" << eid << " of @" << ent->id()); + ent->removeEPollEvents(eid); + + // First remove the EID from the subscribed in the socket so that + // a possible call to update_events: + // - if happens before this call, can find the epoll bit update possible + // - if happens after this call, will not strike this EID + HLOGC(ealog.Debug, log << "epoll_remove_usock: REMOVING E" << eid << " from back-subscirbers in @" << ent->id()); + ent->removeEPollID(eid); + + HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING subscription on E" << eid << " of @" << ent->id()); + int no_events = 0; + int ret = m_EPoll.update_usock(eid, ent->id(), &no_events); + + return ret; +} + +// Needed internal access! +int srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) +{ + return epoll_remove_entity(eid, &s->core()); +} + +#if ENABLE_BONDING +int srt::CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g) +{ + return epoll_remove_entity(eid, g); +} +#endif + +int srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) +{ + CUDTSocket* s = 0; + +#if ENABLE_BONDING + CUDTGroup* g = 0; + if (u & SRTGROUP_MASK) + { + GroupKeeper k(*this, u, ERH_THROW); + g = k.group; + return epoll_remove_entity(eid, g); + } + else +#endif + { + s = locateSocket(u); + if (s) + return epoll_remove_entity(eid, &s->core()); + } + + LOGC(ealog.Error, + log << "remove_usock: @" << u << " not found as either socket or group. Removing only from epoll system."); + int no_events = 0; + return m_EPoll.update_usock(eid, u, &no_events); +} + +int srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) +{ + return m_EPoll.remove_ssock(eid, s); +} + +int srt::CUDTUnited::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) +{ + return m_EPoll.uwait(eid, fdsSet, fdsSize, msTimeOut); +} + +int32_t srt::CUDTUnited::epoll_set(int eid, int32_t flags) +{ + return m_EPoll.setflags(eid, flags); +} + +int srt::CUDTUnited::epoll_release(const int eid) +{ + return m_EPoll.release(eid); +} + +srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) +{ + ScopedLock cg(m_GlobControlLock); + CUDTSocket* s = locateSocket_LOCKED(u); + if (!s) + { + if (erh == ERH_RETURN) + return NULL; + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + } + + return s; +} + +// [[using locked(m_GlobControlLock)]]; +srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) +{ + sockets_t::iterator i = m_Sockets.find(u); + + if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) + { + return NULL; + } + + return i->second; +} + +#if ENABLE_BONDING +srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) +{ + ScopedLock cg(m_GlobControlLock); + + const groups_t::iterator i = m_Groups.find(u); + if (i == m_Groups.end()) + { + if (erh == ERH_THROW) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + return NULL; + } + + ScopedLock cgroup(*i->second->exp_groupLock()); + i->second->apiAcquire(); + return i->second; +} + +srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) +{ + ScopedLock cg(m_GlobControlLock); + CUDTGroup* g = s->m_GroupOf; + if (!g) + return NULL; + + // With m_GlobControlLock locked, we are sure the group + // still exists, if it wasn't removed from this socket. + g->apiAcquire(); + return g; +} +#endif + +srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) +{ + ScopedLock cg(m_GlobControlLock); + + map >::iterator i = m_PeerRec.find(CUDTSocket::getPeerSpec(id, isn)); + if (i == m_PeerRec.end()) + return NULL; + + for (set::iterator j = i->second.begin(); j != i->second.end(); ++j) + { + sockets_t::iterator k = m_Sockets.find(*j); + // this socket might have been closed and moved m_ClosedSockets + if (k == m_Sockets.end()) + continue; + + if (k->second->m_PeerAddr == peer) + { + return k->second; + } + } + + return NULL; +} + +void srt::CUDTUnited::checkBrokenSockets() +{ + ScopedLock cg(m_GlobControlLock); + +#if ENABLE_BONDING + vector delgids; + + for (groups_t::iterator i = m_ClosedGroups.begin(); i != m_ClosedGroups.end(); ++i) + { + // isStillBusy requires lock on the group, so only after an API + // function that uses it returns, and so clears the busy flag, + // a new API function won't be called anyway until it can acquire + // GlobControlLock, and all functions that have already seen this + // group as closing will not continue with the API and return. + // If we caught some API function still using the closed group, + // it's not going to wait, will be checked next time. + if (i->second->isStillBusy()) + continue; + + delgids.push_back(i->first); + delete i->second; + i->second = NULL; // just for a case, avoid a dangling pointer + } + + for (vector::iterator di = delgids.begin(); di != delgids.end(); ++di) + { + m_ClosedGroups.erase(*di); + } +#endif + + // set of sockets To Be Closed and To Be Removed + vector tbc; + vector tbr; + + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + if (!s->core().m_bBroken) + continue; + + if (s->m_Status == SRTS_LISTENING) + { + const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp; + // A listening socket should wait an extra 3 seconds + // in case a client is connecting. + if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS)) + continue; + } + else if ((s->core().m_pRcvBuffer != NULL) + // FIXED: calling isRcvDataAvailable() just to get the information + // whether there are any data waiting in the buffer, + // NOT WHETHER THEY ARE ALSO READY TO PLAY at the time when + // this function is called (isRcvDataReady also checks if the + // available data is "ready to play"). +#if ENABLE_NEW_RCVBUFFER + && s->core().m_pRcvBuffer->hasAvailablePackets()) +#else + && s->core().m_pRcvBuffer->isRcvDataAvailable()) +#endif + { + const int bc = s->core().m_iBrokenCounter.load(); + if (bc > 0) + { + // if there is still data in the receiver buffer, wait longer + s->core().m_iBrokenCounter.store(bc - 1); + continue; + } + } + +#if ENABLE_BONDING + if (s->m_GroupOf) + { + LOGC(smlog.Note, + log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); + s->removeFromGroup(true); + } +#endif + + HLOGC(smlog.Debug, log << "checkBrokenSockets: moving BROKEN socket to CLOSED: @" << i->first); + + // close broken connections and start removal timer + s->setClosed(); + tbc.push_back(i->first); + m_ClosedSockets[i->first] = s; + + // remove from listener's queue + sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); + if (ls == m_Sockets.end()) + { + ls = m_ClosedSockets.find(s->m_ListenSocket); + if (ls == m_ClosedSockets.end()) + continue; + } + + enterCS(ls->second->m_AcceptLock); + ls->second->m_QueuedSockets.erase(s->m_SocketID); + leaveCS(ls->second->m_AcceptLock); + } + + for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j) + { + // HLOGF(smlog.Debug, "checking CLOSED socket: %d\n", j->first); + if (!is_zero(j->second->core().m_tsLingerExpiration)) + { + // asynchronous close: + if ((!j->second->core().m_pSndBuffer) || (0 == j->second->core().m_pSndBuffer->getCurrBufSize()) || + (j->second->core().m_tsLingerExpiration <= steady_clock::now())) + { + HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED qualified @" << j->second->m_SocketID); + j->second->core().m_tsLingerExpiration = steady_clock::time_point(); + j->second->core().m_bClosing = true; + j->second->m_tsClosureTimeStamp = steady_clock::now(); + } + } + + // timeout 1 second to destroy a socket AND it has been removed from + // RcvUList + const steady_clock::time_point now = steady_clock::now(); + const steady_clock::duration closed_ago = now - j->second->m_tsClosureTimeStamp; + if (closed_ago > seconds_from(1)) + { + CRNode* rnode = j->second->core().m_pRNode; + if (!rnode || !rnode->m_bOnList) + { + HLOGC(smlog.Debug, + log << "checkBrokenSockets: @" << j->second->m_SocketID << " closed " + << FormatDuration(closed_ago) << " ago and removed from RcvQ - will remove"); + + // HLOGF(smlog.Debug, "will unref socket: %d\n", j->first); + tbr.push_back(j->first); + } + } + } + + // move closed sockets to the ClosedSockets structure + for (vector::iterator k = tbc.begin(); k != tbc.end(); ++k) + m_Sockets.erase(*k); + + // remove those timeout sockets + for (vector::iterator l = tbr.begin(); l != tbr.end(); ++l) + removeSocket(*l); + + HLOGC(smlog.Debug, log << "checkBrokenSockets: after removal: m_ClosedSockets.size()=" << m_ClosedSockets.size()); +} + +// [[using locked(m_GlobControlLock)]] +void srt::CUDTUnited::removeSocket(const SRTSOCKET u) +{ + sockets_t::iterator i = m_ClosedSockets.find(u); + + // invalid socket ID + if (i == m_ClosedSockets.end()) + return; + + CUDTSocket* const s = i->second; + + // The socket may be in the trashcan now, but could + // still be under processing in the sender/receiver worker + // threads. If that's the case, SKIP IT THIS TIME. The + // socket will be checked next time the GC rollover starts. + CSNode* sn = s->core().m_pSNode; + if (sn && sn->m_iHeapLoc != -1) + return; + + CRNode* rn = s->core().m_pRNode; + if (rn && rn->m_bOnList) + return; + +#if ENABLE_BONDING + if (s->m_GroupOf) + { + HLOGC(smlog.Debug, + log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); + s->removeFromGroup(true); + } +#endif + // decrease multiplexer reference count, and remove it if necessary + const int mid = s->m_iMuxID; + + { + ScopedLock cg(s->m_AcceptLock); + + // if it is a listener, close all un-accepted sockets in its queue + // and remove them later + for (set::iterator q = s->m_QueuedSockets.begin(); q != s->m_QueuedSockets.end(); ++q) + { + sockets_t::iterator si = m_Sockets.find(*q); + if (si == m_Sockets.end()) + { + // gone in the meantime + LOGC(smlog.Error, + log << "removeSocket: IPE? socket @" << (*q) << " being queued for listener socket @" + << s->m_SocketID << " is GONE in the meantime ???"); + continue; + } + + CUDTSocket* as = si->second; + + as->breakSocket_LOCKED(); + m_ClosedSockets[*q] = as; + m_Sockets.erase(*q); + } + } + + // remove from peer rec + map >::iterator j = m_PeerRec.find(s->getPeerSpec()); + if (j != m_PeerRec.end()) + { + j->second.erase(u); + if (j->second.empty()) + m_PeerRec.erase(j); + } + + /* + * Socket may be deleted while still having ePoll events set that would + * remains forever causing epoll_wait to unblock continuously for inexistent + * sockets. Get rid of all events for this socket. + */ + m_EPoll.update_events(u, s->core().m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, false); + + // delete this one + m_ClosedSockets.erase(i); + + HLOGC(smlog.Debug, log << "GC/removeSocket: closing associated UDT @" << u); + s->core().closeInternal(); + HLOGC(smlog.Debug, log << "GC/removeSocket: DELETING SOCKET @" << u); + delete s; + HLOGC(smlog.Debug, log << "GC/removeSocket: socket @" << u << " DELETED. Checking muxer."); + + if (mid == -1) + { + HLOGC(smlog.Debug, log << "GC/removeSocket: no muxer found, finishing."); + return; + } + + map::iterator m; + m = m_mMultiplexer.find(mid); + if (m == m_mMultiplexer.end()) + { + LOGC(smlog.Fatal, log << "IPE: For socket @" << u << " MUXER id=" << mid << " NOT FOUND!"); + return; + } + + CMultiplexer& mx = m->second; + + mx.m_iRefCount--; + HLOGC(smlog.Debug, log << "unrefing underlying muxer " << mid << " for @" << u << ", ref=" << mx.m_iRefCount); + if (0 == mx.m_iRefCount) + { + HLOGC(smlog.Debug, + log << "MUXER id=" << mid << " lost last socket @" << u << " - deleting muxer bound to port " + << mx.m_pChannel->bindAddressAny().hport()); + // The channel has no access to the queues and + // it looks like the multiplexer is the master of all of them. + // The queues must be silenced before closing the channel + // because this will cause error to be returned in any operation + // being currently done in the queues, if any. + mx.m_pSndQueue->setClosing(); + mx.m_pRcvQueue->setClosing(); + mx.destroy(); + m_mMultiplexer.erase(m); + } +} + +void srt::CUDTUnited::configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af) +{ + w_m.m_mcfg = s->core().m_config; + w_m.m_iIPversion = af; + w_m.m_iRefCount = 1; + w_m.m_iID = s->m_SocketID; +} + +uint16_t srt::CUDTUnited::installMuxer(CUDTSocket* w_s, CMultiplexer& fw_sm) +{ + w_s->core().m_pSndQueue = fw_sm.m_pSndQueue; + w_s->core().m_pRcvQueue = fw_sm.m_pRcvQueue; + w_s->m_iMuxID = fw_sm.m_iID; + sockaddr_any sa; + fw_sm.m_pChannel->getSockAddr((sa)); + w_s->m_SelfAddr = sa; // Will be also completed later, but here it's needed for later checks + return sa.hport(); +} + +bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket) +{ + return cfgMuxer.bReuseAddr && cfgMuxer == cfgSocket; +} + +void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* udpsock /*[[nullable]]*/) +{ + ScopedLock cg(m_GlobControlLock); + + // If udpsock is provided, then this socket will be simply + // taken for binding as a good deal. It would be nice to make + // a sanity check to see if this UDP socket isn't already installed + // in some multiplexer, but we state this UDP socket isn't accessible + // anyway so this wouldn't be possible. + if (!udpsock) + { + // If not, we need to see if there exist already a multiplexer bound + // to the same endpoint. + const int port = addr.hport(); + const CSrtConfig& cfgSocket = s->core().m_config; + + bool reuse_attempt = false; + for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) + { + CMultiplexer& m = i->second; + + // First, we need to find a multiplexer with the same port. + if (m.m_iPort != port) + { + HLOGC(smlog.Debug, + log << "bind: muxer @" << m.m_iID << " found, but for port " << m.m_iPort + << " (requested port: " << port << ")"); + continue; + } + + // If this is bound to the wildcard address, it can be reused if: + // - addr is also a wildcard + // - channel settings match + // Otherwise it's a conflict. + sockaddr_any sa; + m.m_pChannel->getSockAddr((sa)); + + HLOGC(smlog.Debug, + log << "bind: Found existing muxer @" << m.m_iID << " : " << sa.str() << " - check against " + << addr.str()); + + if (sa.isany()) + { + if (!addr.isany()) + { + LOGC(smlog.Error, + log << "bind: Address: " << addr.str() + << " conflicts with existing wildcard binding: " << sa.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + + // Still, for ANY you need either the same family, or open + // for families. + if (m.m_mcfg.iIpV6Only != -1 && m.m_mcfg.iIpV6Only != cfgSocket.iIpV6Only) + { + LOGC(smlog.Error, + log << "bind: Address: " << addr.str() + << " conflicts with existing IPv6 wildcard binding: " << sa.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + + if ((m.m_mcfg.iIpV6Only == 0 || cfgSocket.iIpV6Only == 0) && m.m_iIPversion != addr.family()) + { + LOGC(smlog.Error, + log << "bind: Address: " << addr.str() << " conflicts with IPv6 wildcard binding: " << sa.str() + << " : family " << (m.m_iIPversion == AF_INET ? "IPv4" : "IPv6") << " vs. " + << (addr.family() == AF_INET ? "IPv4" : "IPv6")); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + reuse_attempt = true; + HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable"); + } + else if (addr.isany() && addr.family() == sa.family()) + { + LOGC(smlog.Error, + log << "bind: Wildcard address: " << addr.str() + << " conflicts with existting IP binding: " << sa.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + // If this is bound to a certain address, AND: + else if (sa.equal_address(addr)) + { + // - the address is the same as addr + reuse_attempt = true; + HLOGC(smlog.Debug, log << "bind: same IP address - multiplexer reusable"); + } + else + { + HLOGC(smlog.Debug, log << "bind: IP addresses differ - ALLOWED to create a new multiplexer"); + } + // Otherwise: + // - the address is different than addr + // - the address can't be reused, but this can go on with new one. + + // If this is a reusage attempt: + if (reuse_attempt) + { + // - if the channel settings match, it can be reused + if (channelSettingsMatch(m.m_mcfg, cfgSocket)) + { + HLOGC(smlog.Debug, log << "bind: reusing multiplexer for port " << port); + // reuse the existing multiplexer + ++i->second.m_iRefCount; + installMuxer((s), (i->second)); + return; + } + else + { + // - if not, it's a conflict + LOGC(smlog.Error, + log << "bind: Address: " << addr.str() << " conflicts with binding: " << sa.str() + << " due to channel settings"); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + // If not, proceed to the next one, and when there are no reusage + // candidates, proceed with creating a new multiplexer. + + // Note that a binding to a different IP address is not treated + // as a candidate for either reuseage or conflict. + } + } + + // a new multiplexer is needed + CMultiplexer m; + configureMuxer((m), s, addr.family()); + + try + { + m.m_pChannel = new CChannel(); + m.m_pChannel->setConfig(m.m_mcfg); + + if (udpsock) + { + // In this case, addr contains the address + // that has been extracted already from the + // given socket + m.m_pChannel->attach(*udpsock, addr); + } + else if (addr.empty()) + { + // The case of previously used case of a NULL address. + // This here is used to pass family only, in this case + // just automatically bind to the "0" address to autoselect + // everything. + m.m_pChannel->open(addr.family()); + } + else + { + // If at least the IP address is specified, then bind to that + // address, but still possibly autoselect the outgoing port, if the + // port was specified as 0. + m.m_pChannel->open(addr); + } + + m.m_pTimer = new CTimer; + m.m_pSndQueue = new CSndQueue; + m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer); + m.m_pRcvQueue = new CRcvQueue; + m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer); + + // Rewrite the port here, as it might be only known upon return + // from CChannel::open. + m.m_iPort = installMuxer((s), m); + m_mMultiplexer[m.m_iID] = m; + } + catch (const CUDTException&) + { + m.destroy(); + throw; + } + catch (...) + { + m.destroy(); + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + + HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << m.m_iPort); +} + +// This function is going to find a multiplexer for the port contained +// in the 'ls' listening socket. The multiplexer must exist when the listener +// exists, otherwise the dispatching procedure wouldn't even call this +// function. By historical reasons there's also a fallback for a case when the +// multiplexer wasn't found by id, the search by port number continues. +bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) +{ + ScopedLock cg(m_GlobControlLock); + const int port = ls->m_SelfAddr.hport(); + + HLOGC(smlog.Debug, + log << "updateListenerMux: finding muxer of listener socket @" << ls->m_SocketID << " muxid=" << ls->m_iMuxID + << " bound=" << ls->m_SelfAddr.str() << " FOR @" << s->m_SocketID << " addr=" << s->m_SelfAddr.str() + << "_->_" << s->m_PeerAddr.str()); + + // First thing that should be certain here is that there should exist + // a muxer with the ID written in the listener socket's mux ID. + + CMultiplexer* mux = map_getp(m_mMultiplexer, ls->m_iMuxID); + + // NOTE: + // THIS BELOW CODE is only for a highly unlikely, and probably buggy, + // situation when the Multiplexer wasn't found by ID recorded in the listener. + CMultiplexer* fallback = NULL; + if (!mux) + { + LOGC(smlog.Error, log << "updateListenerMux: IPE? listener muxer not found by ID, trying by port"); + + // To be used as first found with different IP version + + // find the listener's address + for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) + { + CMultiplexer& m = i->second; + +#if ENABLE_HEAVY_LOGGING + ostringstream that_muxer; + that_muxer << "id=" << m.m_iID << " port=" << m.m_iPort + << " ip=" << (m.m_iIPversion == AF_INET ? "v4" : "v6"); +#endif + + if (m.m_iPort == port) + { + HLOGC(smlog.Debug, log << "updateListenerMux: reusing muxer: " << that_muxer.str()); + if (m.m_iIPversion == s->m_PeerAddr.family()) + { + mux = &m; // best match + break; + } + else + { + fallback = &m; + } + } + else + { + HLOGC(smlog.Debug, log << "updateListenerMux: SKIPPING muxer: " << that_muxer.str()); + } + } + + if (!mux && fallback) + { + // It is allowed to reuse this multiplexer, but the socket must allow both IPv4 and IPv6 + if (fallback->m_mcfg.iIpV6Only == 0) + { + HLOGC(smlog.Warn, log << "updateListenerMux: reusing multiplexer from different family"); + mux = fallback; + } + } + } + + // Checking again because the above procedure could have set it + if (mux) + { + // reuse the existing multiplexer + ++mux->m_iRefCount; + s->core().m_pSndQueue = mux->m_pSndQueue; + s->core().m_pRcvQueue = mux->m_pRcvQueue; + s->m_iMuxID = mux->m_iID; + return true; + } + + return false; +} + +void* srt::CUDTUnited::garbageCollect(void* p) +{ + CUDTUnited* self = (CUDTUnited*)p; + + THREAD_STATE_INIT("SRT:GC"); + + UniqueLock gclock(self->m_GCStopLock); + + while (!self->m_bClosing) + { + INCREMENT_THREAD_ITERATIONS(); + self->checkBrokenSockets(); + + HLOGC(inlog.Debug, log << "GC: sleep 1 s"); + self->m_GCStopCond.wait_for(gclock, seconds_from(1)); + } + + // remove all sockets and multiplexers + HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); + + { + ScopedLock glock(self->m_GlobControlLock); + + for (sockets_t::iterator i = self->m_Sockets.begin(); i != self->m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + s->breakSocket_LOCKED(); + +#if ENABLE_BONDING + if (s->m_GroupOf) + { + HLOGC(smlog.Debug, + log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() + << " (IPE?) - REMOVING FROM GROUP"); + s->removeFromGroup(false); + } +#endif + self->m_ClosedSockets[i->first] = s; + + // remove from listener's queue + sockets_t::iterator ls = self->m_Sockets.find(s->m_ListenSocket); + if (ls == self->m_Sockets.end()) + { + ls = self->m_ClosedSockets.find(s->m_ListenSocket); + if (ls == self->m_ClosedSockets.end()) + continue; + } + + enterCS(ls->second->m_AcceptLock); + ls->second->m_QueuedSockets.erase(s->m_SocketID); + leaveCS(ls->second->m_AcceptLock); + } + self->m_Sockets.clear(); + + for (sockets_t::iterator j = self->m_ClosedSockets.begin(); j != self->m_ClosedSockets.end(); ++j) + { + j->second->m_tsClosureTimeStamp = steady_clock::time_point(); + } + } + + HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); + while (true) + { + self->checkBrokenSockets(); + + enterCS(self->m_GlobControlLock); + bool empty = self->m_ClosedSockets.empty(); + leaveCS(self->m_GlobControlLock); + + if (empty) + break; + + HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep"); + srt::sync::this_thread::sleep_for(milliseconds_from(1)); + } + + THREAD_EXIT(); + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// + +int srt::CUDT::startup() +{ + return uglobal().startup(); +} + +int srt::CUDT::cleanup() +{ + return uglobal().cleanup(); +} + +SRTSOCKET srt::CUDT::socket() +{ + if (!uglobal().m_bGCStatus) + uglobal().startup(); + + try + { + return uglobal().newSocket(); + } + catch (const CUDTException& e) + { + SetThreadLocalError(e); + return INVALID_SOCK; + } + catch (const bad_alloc&) + { + SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return INVALID_SOCK; + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "socket: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return INVALID_SOCK; + } +} + +srt::CUDT::APIError::APIError(const CUDTException& e) +{ + SetThreadLocalError(e); +} + +srt::CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) +{ + SetThreadLocalError(CUDTException(mj, mn, syserr)); +} + +#if ENABLE_BONDING +// This is an internal function; 'type' should be pre-checked if it has a correct value. +// This doesn't have argument of GroupType due to header file conflicts. + +// [[using locked(s_UDTUnited.m_GlobControlLock)]] +srt::CUDTGroup& srt::CUDT::newGroup(const int type) +{ + const SRTSOCKET id = uglobal().generateSocketID(true); + + // Now map the group + return uglobal().addGroup(id, SRT_GROUP_TYPE(type)).set_id(id); +} + +SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) +{ + // Doing the same lazy-startup as with srt_create_socket() + if (!uglobal().m_bGCStatus) + uglobal().startup(); + + try + { + srt::sync::ScopedLock globlock(uglobal().m_GlobControlLock); + return newGroup(gt).id(); + // Note: potentially, after this function exits, the group + // could be deleted, immediately, from a separate thread (tho + // unlikely because the other thread would need some handle to + // keep it). But then, the first call to any API function would + // return invalid ID error. + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (...) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + + return SRT_INVALID_SOCK; +} + +// [[using locked(m_ControlLock)]] +// [[using locked(CUDT::s_UDTUnited.m_GlobControlLock)]] +void srt::CUDTSocket::removeFromGroup(bool broken) +{ + CUDTGroup* g = m_GroupOf; + if (g) + { + // Reset group-related fields immediately. They won't be accessed + // in the below calls, while the iterator will be invalidated for + // a short moment between removal from the group container and the end, + // while the GroupLock would be already taken out. It is safer to reset + // it to a NULL iterator before removal. + m_GroupOf = NULL; + m_GroupMemberData = NULL; + + bool still_have = g->remove(m_SocketID); + if (broken) + { + // Activate the SRT_EPOLL_UPDATE event on the group + // if it was because of a socket that was earlier connected + // and became broken. This is not to be sent in case when + // it is a failure during connection, or the socket was + // explicitly removed from the group. + g->activateUpdateEvent(still_have); + } + + HLOGC(smlog.Debug, + log << "removeFromGroup: socket @" << m_SocketID << " NO LONGER A MEMBER of $" << g->id() << "; group is " + << (still_have ? "still ACTIVE" : "now EMPTY")); + } +} + +SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) +{ + // Lock this for the whole function as we need the group + // to persist the call. + ScopedLock glock(uglobal().m_GlobControlLock); + CUDTSocket* s = uglobal().locateSocket_LOCKED(socket); + if (!s || !s->m_GroupOf) + return APIError(MJ_NOTSUP, MN_INVAL, 0); + + return s->m_GroupOf->id(); +} + +int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) +{ + if ((groupid & SRTGROUP_MASK) == 0 || !psize) + { + return APIError(MJ_NOTSUP, MN_INVAL, 0); + } + + CUDTUnited::GroupKeeper k(uglobal(), groupid, CUDTUnited::ERH_RETURN); + if (!k.group) + { + return APIError(MJ_NOTSUP, MN_INVAL, 0); + } + + // To get only the size of the group pdata=NULL can be used + return k.group->getGroupData(pdata, psize); +} +#endif + +int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) +{ + try + { + sockaddr_any sa(name, namelen); + if (sa.len == 0) + { + // This happens if the namelen check proved it to be + // too small for particular family, or that family is + // not recognized (is none of AF_INET, AF_INET6). + // This is a user error. + return APIError(MJ_NOTSUP, MN_INVAL, 0); + } + CUDTSocket* s = uglobal().locateSocket(u); + if (!s) + return APIError(MJ_NOTSUP, MN_INVAL, 0); + + return uglobal().bind(s, sa); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "bind: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) +{ + try + { + CUDTSocket* s = uglobal().locateSocket(u); + if (!s) + return APIError(MJ_NOTSUP, MN_INVAL, 0); + + return uglobal().bind(s, udpsock); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "bind/udp: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::listen(SRTSOCKET u, int backlog) +{ + try + { + return uglobal().listen(u, backlog); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "listen: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) +{ + try + { + return uglobal().accept_bond(listeners, lsize, msTimeOut); + } + catch (const CUDTException& e) + { + SetThreadLocalError(e); + return INVALID_SOCK; + } + catch (bad_alloc&) + { + SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return INVALID_SOCK; + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "accept_bond: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return INVALID_SOCK; + } +} + +SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) +{ + try + { + return uglobal().accept(u, addr, addrlen); + } + catch (const CUDTException& e) + { + SetThreadLocalError(e); + return INVALID_SOCK; + } + catch (const bad_alloc&) + { + SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return INVALID_SOCK; + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "accept: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return INVALID_SOCK; + } +} + +int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) +{ + try + { + return uglobal().connect(u, name, tname, namelen); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (std::exception& ee) + { + LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +#if ENABLE_BONDING +int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) +{ + if (arraysize <= 0) + return APIError(MJ_NOTSUP, MN_INVAL, 0); + + if ((grp & SRTGROUP_MASK) == 0) + { + // connectLinks accepts only GROUP id, not socket id. + return APIError(MJ_NOTSUP, MN_SIDINVAL, 0); + } + + try + { + CUDTUnited::GroupKeeper k(uglobal(), grp, CUDTUnited::ERH_THROW); + return uglobal().groupConnect(k.group, targets, arraysize); + } + catch (CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (std::exception& ee) + { + LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} +#endif + +int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +{ + try + { + return uglobal().connect(u, name, namelen, forced_isn); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::close(SRTSOCKET u) +{ + try + { + return uglobal().close(u); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "close: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) +{ + try + { + uglobal().getpeername(u, name, namelen); + return 0; + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "getpeername: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) +{ + try + { + uglobal().getsockname(u, name, namelen); + return 0; + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "getsockname: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) +{ + if (!pw_optval || !pw_optlen) + { + return APIError(MJ_NOTSUP, MN_INVAL, 0); + } + + try + { +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + k.group->getOpt(optname, (pw_optval), (*pw_optlen)); + return 0; + } +#endif + + CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + udt.getOpt(optname, (pw_optval), (*pw_optlen)); + return 0; + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "getsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) +{ + if (!optval) + return APIError(MJ_NOTSUP, MN_INVAL, 0); + + try + { +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + k.group->setOpt(optname, optval, optlen); + return 0; + } +#endif + + CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + udt.setOpt(optname, optval, optlen); + return 0; + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "setsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int) +{ + SRT_MSGCTRL mctrl = srt_msgctrl_default; + return sendmsg2(u, buf, len, (mctrl)); +} + +// --> CUDT::recv moved down + +int srt::CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) +{ + SRT_MSGCTRL mctrl = srt_msgctrl_default; + mctrl.msgttl = ttl; + mctrl.inorder = inorder; + mctrl.srctime = srctime; + return sendmsg2(u, buf, len, (mctrl)); +} + +int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) +{ + try + { +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + return k.group->send(buf, len, (w_m)); + } +#endif + + return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().sendmsg2(buf, len, (w_m)); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +int srt::CUDT::recv(SRTSOCKET u, char* buf, int len, int) +{ + SRT_MSGCTRL mctrl = srt_msgctrl_default; + int ret = recvmsg2(u, buf, len, (mctrl)); + return ret; } -int CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* name, int* namelen) +int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { - if (getStatus(u) != SRTS_CONNECTED) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - - CUDTSocket* s = locate(u); - - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - if (!s->m_pUDT->m_bConnected || s->m_pUDT->m_bBroken) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - - if (AF_INET == s->m_iIPversion) - *namelen = sizeof(sockaddr_in); - else - *namelen = sizeof(sockaddr_in6); - - // copy address information of peer node - memcpy(name, s->m_pPeerAddr, *namelen); - - return 0; + SRT_MSGCTRL mctrl = srt_msgctrl_default; + int ret = recvmsg2(u, buf, len, (mctrl)); + srctime = mctrl.srctime; + return ret; } -int CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* name, int* namelen) +int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) { - CUDTSocket* s = locate(u); - - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - if (s->m_pUDT->m_bBroken) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - - if (s->m_Status == SRTS_INIT) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - - if (AF_INET == s->m_iIPversion) - *namelen = sizeof(sockaddr_in); - else - *namelen = sizeof(sockaddr_in6); - - // copy address information of local node - memcpy(name, s->m_pSelfAddr, *namelen); + try + { +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + { + CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + return k.group->recv(buf, len, (w_m)); + } +#endif - return 0; + return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().recvmsg2(buf, len, (w_m)); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::select( - ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout) +int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) { - uint64_t entertime = CTimer::getTime(); - - uint64_t to; - if (!timeout) - to = 0xFFFFFFFFFFFFFFFFULL; - else - to = timeout->tv_sec * 1000000 + timeout->tv_usec; - - // initialize results - int count = 0; - set rs, ws, es; + try + { + CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + return udt.sendfile(ifs, offset, size, block); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "sendfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} - // retrieve related UDT sockets - vector ru, wu, eu; - CUDTSocket* s; - if (readfds) - for (set::iterator i1 = readfds->begin(); - i1 != readfds->end(); ++ i1) - { - if (getStatus(*i1) == SRTS_BROKEN) - { - rs.insert(*i1); - ++ count; - } - else if (!(s = locate(*i1))) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - else - ru.push_back(s); - } - if (writefds) - for (set::iterator i2 = writefds->begin(); - i2 != writefds->end(); ++ i2) - { - if (getStatus(*i2) == SRTS_BROKEN) - { - ws.insert(*i2); - ++ count; - } - else if (!(s = locate(*i2))) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - else - wu.push_back(s); - } - if (exceptfds) - for (set::iterator i3 = exceptfds->begin(); - i3 != exceptfds->end(); ++ i3) - { - if (getStatus(*i3) == SRTS_BROKEN) - { - es.insert(*i3); - ++ count; - } - else if (!(s = locate(*i3))) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - else - eu.push_back(s); - } - - do - { - // query read sockets - for (vector::iterator j1 = ru.begin(); j1 != ru.end(); ++ j1) - { - s = *j1; - - if ((s->m_pUDT->m_bConnected - && s->m_pUDT->m_pRcvBuffer->isRcvDataReady() - ) - || (!s->m_pUDT->m_bListening - && (s->m_pUDT->m_bBroken || !s->m_pUDT->m_bConnected)) - || (s->m_pUDT->m_bListening && (s->m_pQueuedSockets->size() > 0)) - || (s->m_Status == SRTS_CLOSED)) - { - rs.insert(s->m_SocketID); - ++ count; - } - } - - // query write sockets - for (vector::iterator j2 = wu.begin(); j2 != wu.end(); ++ j2) - { - s = *j2; - - if ((s->m_pUDT->m_bConnected - && (s->m_pUDT->m_pSndBuffer->getCurrBufSize() - < s->m_pUDT->m_iSndBufSize)) - || s->m_pUDT->m_bBroken - || !s->m_pUDT->m_bConnected - || (s->m_Status == SRTS_CLOSED)) - { - ws.insert(s->m_SocketID); - ++ count; - } - } - - // query exceptions on sockets - for (vector::iterator j3 = eu.begin(); j3 != eu.end(); ++ j3) - { - // check connection request status, not supported now - } - - if (0 < count) - break; - - CTimer::waitForEvent(); - } while (to > CTimer::getTime() - entertime); - - if (readfds) - *readfds = rs; - - if (writefds) - *writefds = ws; - - if (exceptfds) - *exceptfds = es; - - return count; -} - -int CUDTUnited::selectEx( - const vector& fds, - vector* readfds, - vector* writefds, - vector* exceptfds, - int64_t msTimeOut) -{ - uint64_t entertime = CTimer::getTime(); - - uint64_t to; - if (msTimeOut >= 0) - to = msTimeOut * 1000; - else - to = 0xFFFFFFFFFFFFFFFFULL; - - // initialize results - int count = 0; - if (readfds) - readfds->clear(); - if (writefds) - writefds->clear(); - if (exceptfds) - exceptfds->clear(); - - do - { - for (vector::const_iterator i = fds.begin(); - i != fds.end(); ++ i) - { - CUDTSocket* s = locate(*i); - - if ((!s) || s->m_pUDT->m_bBroken || (s->m_Status == SRTS_CLOSED)) - { - if (exceptfds) - { - exceptfds->push_back(*i); - ++ count; - } - continue; - } - - if (readfds) - { - if ((s->m_pUDT->m_bConnected - && s->m_pUDT->m_pRcvBuffer->isRcvDataReady() - ) - || (s->m_pUDT->m_bListening - && (s->m_pQueuedSockets->size() > 0))) - { - readfds->push_back(s->m_SocketID); - ++ count; - } - } +int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) +{ + try + { + return uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core().recvfile(ofs, offset, size, block); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "recvfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} - if (writefds) - { - if (s->m_pUDT->m_bConnected - && (s->m_pUDT->m_pSndBuffer->getCurrBufSize() - < s->m_pUDT->m_iSndBufSize)) - { - writefds->push_back(s->m_SocketID); - ++ count; - } - } - } +int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) +{ + if ((!readfds) && (!writefds) && (!exceptfds)) + { + return APIError(MJ_NOTSUP, MN_INVAL, 0); + } - if (count > 0) - break; + try + { + return uglobal().select(readfds, writefds, exceptfds, timeout); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "select: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} - CTimer::waitForEvent(); - } while (to > CTimer::getTime() - entertime); +int srt::CUDT::selectEx(const vector& fds, + vector* readfds, + vector* writefds, + vector* exceptfds, + int64_t msTimeOut) +{ + if ((!readfds) && (!writefds) && (!exceptfds)) + { + return APIError(MJ_NOTSUP, MN_INVAL, 0); + } - return count; + try + { + return uglobal().selectEx(fds, readfds, writefds, exceptfds, msTimeOut); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (bad_alloc&) + { + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "selectEx: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN); + } } -int CUDTUnited::epoll_create() +int srt::CUDT::epoll_create() { - return m_EPoll.create(); + try + { + return uglobal().epoll_create(); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_create: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_add_usock( - const int eid, const SRTSOCKET u, const int* events) +int srt::CUDT::epoll_clear_usocks(int eid) { - CUDTSocket* s = locate(u); - int ret = -1; - if (s) - { - ret = m_EPoll.add_usock(eid, u, events); - s->m_pUDT->addEPoll(eid); - } - else - { - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); - } - - return ret; + try + { + return uglobal().epoll_clear_usocks(eid); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (std::exception& ee) + { + LOGC(aclog.Fatal, + log << "epoll_clear_usocks: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_add_ssock( - const int eid, const SYSSOCKET s, const int* events) +int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { - return m_EPoll.add_ssock(eid, s, events); + try + { + return uglobal().epoll_add_usock(eid, u, events); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_add_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_update_usock( - const int eid, const SRTSOCKET u, const int* events) +int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { - CUDTSocket* s = locate(u); - int ret = -1; - if (s) - { - ret = m_EPoll.update_usock(eid, u, events); - s->m_pUDT->addEPoll(eid); - } - else - { - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); - } - - return ret; + try + { + return uglobal().epoll_add_ssock(eid, s, events); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_add_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_update_ssock( - const int eid, const SYSSOCKET s, const int* events) +int srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) { - return m_EPoll.update_ssock(eid, s, events); + try + { + return uglobal().epoll_add_usock(eid, u, events); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, + log << "epoll_update_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) +int srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) { - int ret = m_EPoll.remove_usock(eid, u); - - CUDTSocket* s = locate(u); - if (s) - { - s->m_pUDT->removeEPoll(eid); - } - //else - //{ - // throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); - //} - - return ret; + try + { + return uglobal().epoll_update_ssock(eid, s, events); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, + log << "epoll_update_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) +int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) { - return m_EPoll.remove_ssock(eid, s); + try + { + return uglobal().epoll_remove_usock(eid, u); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, + log << "epoll_remove_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_wait( - const int eid, - set* readfds, - set* writefds, - int64_t msTimeOut, - set* lrfds, - set* lwfds) +int srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) { - return m_EPoll.wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); + try + { + return uglobal().epoll_remove_ssock(eid, s); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, + log << "epoll_remove_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_uwait( - const int eid, - SRT_EPOLL_EVENT* fdsSet, - int fdsSize, - int64_t msTimeOut) +int srt::CUDT::epoll_wait(const int eid, + set* readfds, + set* writefds, + int64_t msTimeOut, + set* lrfds, + set* lwfds) { - return m_EPoll.uwait(eid, fdsSet, fdsSize, msTimeOut); + try + { + return uglobal().epoll_ref().wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_wait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int32_t CUDTUnited::epoll_set(int eid, int32_t flags) +int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { - return m_EPoll.setflags(eid, flags); + try + { + return uglobal().epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_uwait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -int CUDTUnited::epoll_release(const int eid) +int32_t srt::CUDT::epoll_set(const int eid, int32_t flags) { - return m_EPoll.release(eid); + try + { + return uglobal().epoll_set(eid, flags); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_set: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -CUDTSocket* CUDTUnited::locate(const SRTSOCKET u) +int srt::CUDT::epoll_release(const int eid) { - CGuard cg(m_ControlLock); - - map::iterator i = m_Sockets.find(u); - - if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) - return NULL; - - return i->second; + try + { + return uglobal().epoll_release(eid); + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "epoll_release: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } } -CUDTSocket* CUDTUnited::locate( - const sockaddr* peer, - const SRTSOCKET id, - int32_t isn) +srt::CUDTException& srt::CUDT::getlasterror() { - CGuard cg(m_ControlLock); - - map >::iterator i = m_PeerRec.find( - CUDTSocket::getPeerSpec(id, isn)); - if (i == m_PeerRec.end()) - return NULL; - - for (set::iterator j = i->second.begin(); - j != i->second.end(); ++ j) - { - map::iterator k = m_Sockets.find(*j); - // this socket might have been closed and moved m_ClosedSockets - if (k == m_Sockets.end()) - continue; - - if (CIPAddress::ipcmp( - peer, k->second->m_pPeerAddr, k->second->m_iIPversion)) - { - return k->second; - } - } - - return NULL; + return GetThreadLocalError(); } -void CUDTUnited::checkBrokenSockets() +int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) { - CGuard cg(m_ControlLock); - - // set of sockets To Be Closed and To Be Removed - vector tbc; - vector tbr; - - for (map::iterator i = m_Sockets.begin(); - i != m_Sockets.end(); ++ i) - { - CUDTSocket* s = i->second; - - // HLOGF(mglog.Debug, "checking EXISTING socket: %d\n", i->first); - // check broken connection - if (s->m_pUDT->m_bBroken) - { - if (s->m_Status == SRTS_LISTENING) - { - uint64_t elapsed = CTimer::getTime() - s->m_ClosureTimeStamp; - // for a listening socket, it should wait an extra 3 seconds - // in case a client is connecting - if (elapsed < 3000000) // XXX MAKE A SYMBOLIC CONSTANT HERE! - { - // HLOGF(mglog.Debug, "STILL KEEPING socket %d - // (listener, too early, w8 %fs)\n", i->first, - // double(elapsed)/1000000); - continue; - } - } - else if ((s->m_pUDT->m_pRcvBuffer != NULL) - // FIXED: calling isRcvDataAvailable() just to get the information - // whether there are any data waiting in the buffer, - // NOT WHETHER THEY ARE ALSO READY TO PLAY at the time when - // this function is called (isRcvDataReady also checks if the - // available data is "ready to play"). - && s->m_pUDT->m_pRcvBuffer->isRcvDataAvailable() - && (s->m_pUDT->m_iBrokenCounter -- > 0)) - { - // HLOGF(mglog.Debug, "STILL KEEPING socket (still have data): - // %d\n", i->first); - // if there is still data in the receiver buffer, wait longer - continue; - } - - // HLOGF(mglog.Debug, "moving socket to CLOSED: %d\n", i->first); - - //close broken connections and start removal timer - s->m_Status = SRTS_CLOSED; - s->m_ClosureTimeStamp = CTimer::getTime(); - tbc.push_back(i->first); - m_ClosedSockets[i->first] = s; - - // remove from listener's queue - map::iterator ls = m_Sockets.find( - s->m_ListenSocket); - if (ls == m_Sockets.end()) - { - ls = m_ClosedSockets.find(s->m_ListenSocket); - if (ls == m_ClosedSockets.end()) - continue; - } - - CGuard::enterCS(ls->second->m_AcceptLock); - ls->second->m_pQueuedSockets->erase(s->m_SocketID); - ls->second->m_pAcceptSockets->erase(s->m_SocketID); - CGuard::leaveCS(ls->second->m_AcceptLock); - } - } - - for (map::iterator j = m_ClosedSockets.begin(); - j != m_ClosedSockets.end(); ++ j) - { - // HLOGF(mglog.Debug, "checking CLOSED socket: %d\n", j->first); - if (j->second->m_pUDT->m_ullLingerExpiration > 0) - { - // asynchronous close: - if ((!j->second->m_pUDT->m_pSndBuffer) - || (0 == j->second->m_pUDT->m_pSndBuffer->getCurrBufSize()) - || (j->second->m_pUDT->m_ullLingerExpiration <= CTimer::getTime())) - { - j->second->m_pUDT->m_ullLingerExpiration = 0; - j->second->m_pUDT->m_bClosing = true; - j->second->m_ClosureTimeStamp = CTimer::getTime(); - } - } - - // timeout 1 second to destroy a socket AND it has been removed from - // RcvUList - if ((CTimer::getTime() - j->second->m_ClosureTimeStamp > 1000000) - && ((!j->second->m_pUDT->m_pRNode) - || !j->second->m_pUDT->m_pRNode->m_bOnList)) - { - // HLOGF(mglog.Debug, "will unref socket: %d\n", j->first); - tbr.push_back(j->first); - } - } - - // move closed sockets to the ClosedSockets structure - for (vector::iterator k = tbc.begin(); k != tbc.end(); ++ k) - m_Sockets.erase(*k); - - // remove those timeout sockets - for (vector::iterator l = tbr.begin(); l != tbr.end(); ++ l) - removeSocket(*l); -} - -void CUDTUnited::removeSocket(const SRTSOCKET u) -{ - map::iterator i = m_ClosedSockets.find(u); - - // invalid socket ID - if (i == m_ClosedSockets.end()) - return; - - // decrease multiplexer reference count, and remove it if necessary - const int mid = i->second->m_iMuxID; - - if (i->second->m_pQueuedSockets) - { - CGuard cg(i->second->m_AcceptLock); - - // if it is a listener, close all un-accepted sockets in its queue - // and remove them later - for (set::iterator q = i->second->m_pQueuedSockets->begin(); - q != i->second->m_pQueuedSockets->end(); ++ q) - { - m_Sockets[*q]->m_pUDT->m_bBroken = true; - m_Sockets[*q]->m_pUDT->close(); - m_Sockets[*q]->m_ClosureTimeStamp = CTimer::getTime(); - m_Sockets[*q]->m_Status = SRTS_CLOSED; - m_ClosedSockets[*q] = m_Sockets[*q]; - m_Sockets.erase(*q); - } - - } - - // remove from peer rec - map >::iterator j = m_PeerRec.find( - i->second->getPeerSpec()); - if (j != m_PeerRec.end()) - { - j->second.erase(u); - if (j->second.empty()) - m_PeerRec.erase(j); - } - - /* - * Socket may be deleted while still having ePoll events set that would - * remains forever causing epoll_wait to unblock continuously for inexistent - * sockets. Get rid of all events for this socket. - */ - m_EPoll.update_events(u, i->second->m_pUDT->m_sPollID, - UDT_EPOLL_IN|UDT_EPOLL_OUT|UDT_EPOLL_ERR, false); - - // delete this one - HLOGC(mglog.Debug, log << "GC/removeSocket: closing associated UDT %" << u); - i->second->m_pUDT->close(); - HLOGC(mglog.Debug, log << "GC/removeSocket: DELETING SOCKET %" << u); - delete i->second; - m_ClosedSockets.erase(i); - - if (mid == -1) - return; - - map::iterator m; - m = m_mMultiplexer.find(mid); - if (m == m_mMultiplexer.end()) - { - LOGC(mglog.Fatal, log << "IPE: For socket %" << u << " MUXER id=" << mid << " NOT FOUND!"); - return; - } - - CMultiplexer& mx = m->second; - - mx.m_iRefCount --; - // HLOGF(mglog.Debug, "unrefing underlying socket for %u: %u\n", - // u, mx.m_iRefCount); - if (0 == mx.m_iRefCount) - { - HLOGC(mglog.Debug, log << "MUXER id=" << mid << " lost last socket %" - << u << " - deleting muxer bound to port " - << mx.m_pChannel->bindAddressAny().hport()); - // The channel has no access to the queues and - // it looks like the multiplexer is the master of all of them. - // The queues must be silenced before closing the channel - // because this will cause error to be returned in any operation - // being currently done in the queues, if any. - mx.m_pSndQueue->setClosing(); - mx.m_pRcvQueue->setClosing(); - delete mx.m_pSndQueue; - delete mx.m_pRcvQueue; - mx.m_pChannel->close(); - delete mx.m_pTimer; - delete mx.m_pChannel; - m_mMultiplexer.erase(m); - } -} - -void CUDTUnited::setError(CUDTException* e) -{ - delete (CUDTException*)pthread_getspecific(m_TLSError); - pthread_setspecific(m_TLSError, e); -} - -CUDTException* CUDTUnited::getError() -{ - if(!pthread_getspecific(m_TLSError)) - pthread_setspecific(m_TLSError, new CUDTException); - return (CUDTException*)pthread_getspecific(m_TLSError); -} - - -void CUDTUnited::updateMux( - CUDTSocket* s, const sockaddr* addr, const UDPSOCKET* udpsock) -{ - CGuard cg(m_ControlLock); - - if ((s->m_pUDT->m_bReuseAddr) && (addr)) - { - int port = (AF_INET == s->m_pUDT->m_iIPversion) - ? ntohs(((sockaddr_in*)addr)->sin_port) - : ntohs(((sockaddr_in6*)addr)->sin6_port); - - // find a reusable address - for (map::iterator i = m_mMultiplexer.begin(); - i != m_mMultiplexer.end(); ++ i) - { - if ((i->second.m_iIPversion == s->m_pUDT->m_iIPversion) - && (i->second.m_iMSS == s->m_pUDT->m_iMSS) -#ifdef SRT_ENABLE_IPOPTS - && (i->second.m_iIpTTL == s->m_pUDT->m_iIpTTL) - && (i->second.m_iIpToS == s->m_pUDT->m_iIpToS) -#endif - && (i->second.m_iIpV6Only == s->m_pUDT->m_iIpV6Only) - && i->second.m_bReusable) - { - if (i->second.m_iPort == port) - { - // HLOGF(mglog.Debug, "reusing multiplexer for port - // %hd\n", port); - // reuse the existing multiplexer - ++ i->second.m_iRefCount; - s->m_pUDT->m_pSndQueue = i->second.m_pSndQueue; - s->m_pUDT->m_pRcvQueue = i->second.m_pRcvQueue; - s->m_iMuxID = i->second.m_iID; - return; - } - } - } - } - - // a new multiplexer is needed - CMultiplexer m; - m.m_iMSS = s->m_pUDT->m_iMSS; - m.m_iIPversion = s->m_pUDT->m_iIPversion; -#ifdef SRT_ENABLE_IPOPTS - m.m_iIpTTL = s->m_pUDT->m_iIpTTL; - m.m_iIpToS = s->m_pUDT->m_iIpToS; -#endif - m.m_iRefCount = 1; - m.m_iIpV6Only = s->m_pUDT->m_iIpV6Only; - m.m_bReusable = s->m_pUDT->m_bReuseAddr; - m.m_iID = s->m_SocketID; - - m.m_pChannel = new CChannel(s->m_pUDT->m_iIPversion); -#ifdef SRT_ENABLE_IPOPTS - m.m_pChannel->setIpTTL(s->m_pUDT->m_iIpTTL); - m.m_pChannel->setIpToS(s->m_pUDT->m_iIpToS); -#endif - m.m_pChannel->setSndBufSize(s->m_pUDT->m_iUDPSndBufSize); - m.m_pChannel->setRcvBufSize(s->m_pUDT->m_iUDPRcvBufSize); - if (s->m_pUDT->m_iIpV6Only != -1) - m.m_pChannel->setIpV6Only(s->m_pUDT->m_iIpV6Only); - - try - { - if (udpsock) - m.m_pChannel->attach(*udpsock); - else - m.m_pChannel->open(addr); - } - catch (CUDTException& e) - { - m.m_pChannel->close(); - delete m.m_pChannel; - throw; - } - - // XXX Simplify this. Use sockaddr_any. - sockaddr* sa = (AF_INET == s->m_pUDT->m_iIPversion) - ? (sockaddr*) new sockaddr_in - : (sockaddr*) new sockaddr_in6; - m.m_pChannel->getSockAddr(sa); - m.m_iPort = (AF_INET == s->m_pUDT->m_iIPversion) - ? ntohs(((sockaddr_in*)sa)->sin_port) - : ntohs(((sockaddr_in6*)sa)->sin6_port); - - if (AF_INET == s->m_pUDT->m_iIPversion) - delete (sockaddr_in*)sa; - else - delete (sockaddr_in6*)sa; - - m.m_pTimer = new CTimer; - - m.m_pSndQueue = new CSndQueue; - m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer); - m.m_pRcvQueue = new CRcvQueue; - m.m_pRcvQueue->init( - 32, s->m_pUDT->maxPayloadSize(), m.m_iIPversion, 1024, - m.m_pChannel, m.m_pTimer); - - m_mMultiplexer[m.m_iID] = m; - - s->m_pUDT->m_pSndQueue = m.m_pSndQueue; - s->m_pUDT->m_pRcvQueue = m.m_pRcvQueue; - s->m_iMuxID = m.m_iID; - - HLOGF(mglog.Debug, - "creating new multiplexer for port %i\n", m.m_iPort); -} - -// XXX This functionality needs strong refactoring. -// -// This function is going to find a multiplexer for the port contained -// in the 'ls' listening socket, by searching through the multiplexer -// container. -// -// Somehow, however, it's not even predicted a situation that the multiplexer -// for that port doesn't exist - that is, this function WILL find the -// multiplexer. How can it be so certain? It's because the listener has -// already created the multiplexer during the call to bind(), so if it -// didn't, this function wouldn't even have a chance to be called. -// -// Why can't then the multiplexer be recorded in the 'ls' listening socket data -// to be accessed immediately, especially when one listener can't bind to more -// than one multiplexer at a time (well, even if it could, there's still no -// reason why this should be extracted by "querying")? -// -// Maybe because the multiplexer container is a map, not a list. -// Why is this then a map? Because it's addressed by MuxID. Why do we need -// mux id? Because we don't have a list... ? -// -// But what's the multiplexer ID? It's a socket ID for which it was originally -// created. -// -// Is this then shared? Yes, only between the listener socket and the accepted -// sockets, or in case of "bound" connecting sockets (by binding you can -// enforce the port number, which can be the same for multiple SRT sockets). -// Not shared in case of unbound connecting socket or rendezvous socket. -// -// Ok, in which situation do we need dispatching by mux id? Only when the -// socket is being deleted. How does the deleting procedure know the muxer id? -// Because it is recorded here at the time when it's found, as... the socket ID -// of the actual listener socket being actually the first socket to create the -// multiplexer, so the multiplexer gets its id. -// -// Still, no reasons found why the socket can't contain a list iterator to a -// multiplexer INSTEAD of m_iMuxID. There's no danger in this solution because -// the multiplexer is never deleted until there's at least one socket using it. -// -// The multiplexer may even physically be contained in the CUDTUnited object, -// just track the multiple users of it (the listener and the accepted sockets). -// When deleting, you simply "unsubscribe" yourself from the multiplexer, which -// will unref it and remove the list element by the iterator kept by the -// socket. -void CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) -{ - CGuard cg(m_ControlLock); - - int port = (AF_INET == ls->m_iIPversion) - ? ntohs(((sockaddr_in*)ls->m_pSelfAddr)->sin_port) - : ntohs(((sockaddr_in6*)ls->m_pSelfAddr)->sin6_port); - - // find the listener's address - for (map::iterator i = m_mMultiplexer.begin(); - i != m_mMultiplexer.end(); ++ i) - { - if (i->second.m_iPort == port) - { - HLOGF(mglog.Debug, - "updateMux: reusing multiplexer for port %i\n", port); - // reuse the existing multiplexer - ++ i->second.m_iRefCount; - s->m_pUDT->m_pSndQueue = i->second.m_pSndQueue; - s->m_pUDT->m_pRcvQueue = i->second.m_pRcvQueue; - s->m_iMuxID = i->second.m_iID; - return; - } - } -} - -void* CUDTUnited::garbageCollect(void* p) -{ - CUDTUnited* self = (CUDTUnited*)p; - - THREAD_STATE_INIT("SRT:GC"); - - CGuard gcguard(self->m_GCStopLock); - - while (!self->m_bClosing) - { - INCREMENT_THREAD_ITERATIONS(); - self->checkBrokenSockets(); - - //#ifdef _WIN32 - // self->checkTLSValue(); - //#endif - - timespec timeout; -#if ENABLE_MONOTONIC_CLOCK - clock_gettime(CLOCK_MONOTONIC, &timeout); - timeout.tv_sec++; - HLOGC(mglog.Debug, log << "GC: sleep until " << FormatTime(uint64_t(timeout.tv_nsec)/1000 + 1000000*(timeout.tv_sec))); -#else - timeval now; - gettimeofday(&now, 0); - timeout.tv_sec = now.tv_sec + 1; - timeout.tv_nsec = now.tv_usec * 1000; - - HLOGC(mglog.Debug, log << "GC: sleep until " << FormatTime(uint64_t(now.tv_usec) + 1000000*(timeout.tv_sec))); +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + return groupsockbstats(u, perf, clear); #endif - pthread_cond_timedwait( - &self->m_GCStopCond, &self->m_GCStopLock, &timeout); - } - - // remove all sockets and multiplexers - HLOGC(mglog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); - CGuard::enterCS(self->m_ControlLock); - for (map::iterator i = self->m_Sockets.begin(); - i != self->m_Sockets.end(); ++ i) - { - i->second->m_pUDT->m_bBroken = true; - i->second->m_pUDT->close(); - i->second->m_Status = SRTS_CLOSED; - i->second->m_ClosureTimeStamp = CTimer::getTime(); - self->m_ClosedSockets[i->first] = i->second; - - // remove from listener's queue - map::iterator ls = self->m_Sockets.find( - i->second->m_ListenSocket); - if (ls == self->m_Sockets.end()) - { - ls = self->m_ClosedSockets.find(i->second->m_ListenSocket); - if (ls == self->m_ClosedSockets.end()) - continue; - } - - CGuard::enterCS(ls->second->m_AcceptLock); - ls->second->m_pQueuedSockets->erase(i->second->m_SocketID); - ls->second->m_pAcceptSockets->erase(i->second->m_SocketID); - CGuard::leaveCS(ls->second->m_AcceptLock); - } - self->m_Sockets.clear(); - for (map::iterator j = self->m_ClosedSockets.begin(); - j != self->m_ClosedSockets.end(); ++ j) - { - j->second->m_ClosureTimeStamp = 0; - } - CGuard::leaveCS(self->m_ControlLock); - - HLOGC(mglog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); - while (true) - { - self->checkBrokenSockets(); - - CGuard::enterCS(self->m_ControlLock); - bool empty = self->m_ClosedSockets.empty(); - CGuard::leaveCS(self->m_ControlLock); - - if (empty) - break; - - CTimer::sleep(); - } + try + { + CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + udt.bstats(perf, clear, instantaneous); + return 0; + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} - THREAD_EXIT(); - return NULL; +#if ENABLE_BONDING +int srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) +{ + try + { + CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + k.group->bstatsSocket(perf, clear); + return 0; + } + catch (const CUDTException& e) + { + SetThreadLocalError(e); + return ERROR; + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } } +#endif -//////////////////////////////////////////////////////////////////////////////// +srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u) +{ + try + { + return &uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); + } + catch (const CUDTException& e) + { + SetThreadLocalError(e); + return NULL; + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "getUDTHandle: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return NULL; + } +} -int CUDT::startup() -{ - return s_UDTUnited.startup(); -} - -int CUDT::cleanup() -{ - return s_UDTUnited.cleanup(); -} - -SRTSOCKET CUDT::socket(int af, int, int) -{ - if (!s_UDTUnited.m_bGCStatus) - s_UDTUnited.startup(); - - try - { - return s_UDTUnited.newSocket(af, 0); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return INVALID_SOCK; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "socket: UNEXPECTED EXCEPTION: " - << typeid(ee).name() - << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return INVALID_SOCK; - } -} - -int CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) -{ - try - { - return s_UDTUnited.bind(u, name, namelen); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "bind: UNEXPECTED EXCEPTION: " - << typeid(ee).name() - << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) -{ - try - { - return s_UDTUnited.bind(u, udpsock); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "bind/udp: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::listen(SRTSOCKET u, int backlog) -{ - try - { - return s_UDTUnited.listen(u, backlog); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "listen: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -SRTSOCKET CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) -{ - try - { - return s_UDTUnited.accept(u, addr, addrlen); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return INVALID_SOCK; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "accept: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return INVALID_SOCK; - } -} - -int CUDT::connect( - SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) -{ - try - { - return s_UDTUnited.connect(u, name, namelen, forced_isn); - } - catch (const CUDTException e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::close(SRTSOCKET u) -{ - try - { - return s_UDTUnited.close(u); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "close: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) -{ - try - { - return s_UDTUnited.getpeername(u, name, namelen); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "getpeername: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) -{ - try - { - return s_UDTUnited.getsockname(u, name, namelen);; - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "getsockname: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::getsockopt( - SRTSOCKET u, int, SRT_SOCKOPT optname, void* optval, int* optlen) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - udt->getOpt(optname, optval, *optlen); - return 0; - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "getsockopt: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - udt->setOpt(optname, optval, optlen); - return 0; - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "setsockopt: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::send(SRTSOCKET u, const char* buf, int len, int) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->send(buf, len); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "send: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::recv(SRTSOCKET u, char* buf, int len, int) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->recv(buf, len); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "recv: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::sendmsg( - SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, - uint64_t srctime) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->sendmsg(buf, len, ttl, inorder, srctime); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::sendmsg2( - SRTSOCKET u, const char* buf, int len, ref_t r_m) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->sendmsg2(buf, len, r_m); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::recvmsg(SRTSOCKET u, char* buf, int len, uint64_t& srctime) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->recvmsg(buf, len, srctime); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, ref_t r_m) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->recvmsg2(buf, len, r_m); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} -int64_t CUDT::sendfile( - SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->sendfile(ifs, offset, size, block); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "sendfile: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int64_t CUDT::recvfile( - SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - return udt->recvfile(ofs, offset, size, block); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "recvfile: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::select( - int, - ud_set* readfds, - ud_set* writefds, - ud_set* exceptfds, - const timeval* timeout) -{ - if ((!readfds) && (!writefds) && (!exceptfds)) - { - s_UDTUnited.setError(new CUDTException(MJ_NOTSUP, MN_INVAL, 0)); - return ERROR; - } - - try - { - return s_UDTUnited.select(readfds, writefds, exceptfds, timeout); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "select: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::selectEx( - const vector& fds, - vector* readfds, - vector* writefds, - vector* exceptfds, - int64_t msTimeOut) -{ - if ((!readfds) && (!writefds) && (!exceptfds)) - { - s_UDTUnited.setError(new CUDTException(MJ_NOTSUP, MN_INVAL, 0)); - return ERROR; - } - - try - { - return s_UDTUnited.selectEx(fds, readfds, writefds, exceptfds, msTimeOut); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (bad_alloc&) - { - s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "selectEx: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN)); - return ERROR; - } -} - -int CUDT::epoll_create() -{ - try - { - return s_UDTUnited.epoll_create(); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_create: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) -{ - try - { - return s_UDTUnited.epoll_add_usock(eid, u, events); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_add_usock: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) -{ - try - { - return s_UDTUnited.epoll_add_ssock(eid, s, events); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_add_ssock: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_update_usock( - const int eid, const SRTSOCKET u, const int* events) -{ - try - { - return s_UDTUnited.epoll_update_usock(eid, u, events); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_update_usock: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_update_ssock( - const int eid, const SYSSOCKET s, const int* events) -{ - try - { - return s_UDTUnited.epoll_update_ssock(eid, s, events); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_update_ssock: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - - -int CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) -{ - try - { - return s_UDTUnited.epoll_remove_usock(eid, u); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_remove_usock: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) -{ - try - { - return s_UDTUnited.epoll_remove_ssock(eid, s); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_remove_ssock: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_wait( - const int eid, - set* readfds, - set* writefds, - int64_t msTimeOut, - set* lrfds, - set* lwfds) -{ - try - { - return s_UDTUnited.epoll_wait( - eid, readfds, writefds, msTimeOut, lrfds, lwfds); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_wait: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_uwait( - const int eid, - SRT_EPOLL_EVENT* fdsSet, - int fdsSize, - int64_t msTimeOut) -{ - try - { - return s_UDTUnited.epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_uwait: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int32_t CUDT::epoll_set( - const int eid, - int32_t flags) -{ - try - { - return s_UDTUnited.epoll_set(eid, flags); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_set: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -int CUDT::epoll_release(const int eid) -{ - try - { - return s_UDTUnited.epoll_release(eid); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "epoll_release: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -CUDTException& CUDT::getlasterror() -{ - return *s_UDTUnited.getError(); -} - -int CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) -{ - try - { - CUDT* udt = s_UDTUnited.lookup(u); - udt->bstats(perf, clear, instantaneous); - return 0; - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return ERROR; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; - } -} - -CUDT* CUDT::getUDTHandle(SRTSOCKET u) -{ - try - { - return s_UDTUnited.lookup(u); - } - catch (const CUDTException& e) - { - s_UDTUnited.setError(new CUDTException(e)); - return NULL; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "getUDTHandle: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return NULL; - } -} - -vector CUDT::existingSockets() +vector srt::CUDT::existingSockets() { vector out; - for (std::map::iterator i - = s_UDTUnited.m_Sockets.begin(); - i != s_UDTUnited.m_Sockets.end(); ++i) + for (CUDTUnited::sockets_t::iterator i = uglobal().m_Sockets.begin(); i != uglobal().m_Sockets.end(); ++i) { out.push_back(i->first); } return out; } -SRT_SOCKSTATUS CUDT::getsockstate(SRTSOCKET u) +SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) { - try - { - return s_UDTUnited.getStatus(u); - } - catch (const CUDTException &e) - { - s_UDTUnited.setError(new CUDTException(e)); - return SRTS_NONEXIST; - } - catch (const std::exception& ee) - { - LOGC(mglog.Fatal, log << "getsockstate: UNEXPECTED EXCEPTION: " - << typeid(ee).name() << ": " << ee.what()); - s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return SRTS_NONEXIST; - } + try + { +#if ENABLE_BONDING + if (isgroup(u)) + { + CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); + return k.group->getStatus(); + } +#endif + return uglobal().getStatus(u); + } + catch (const CUDTException& e) + { + SetThreadLocalError(e); + return SRTS_NONEXIST; + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "getsockstate: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return SRTS_NONEXIST; + } } - //////////////////////////////////////////////////////////////////////////////// namespace UDT @@ -2819,250 +4157,191 @@ namespace UDT int startup() { - return CUDT::startup(); + return srt::CUDT::startup(); } int cleanup() { - return CUDT::cleanup(); -} - -SRTSOCKET socket(int af, int type, int protocol) -{ - return CUDT::socket(af, type, protocol); + return srt::CUDT::cleanup(); } int bind(SRTSOCKET u, const struct sockaddr* name, int namelen) { - return CUDT::bind(u, name, namelen); + return srt::CUDT::bind(u, name, namelen); } int bind2(SRTSOCKET u, UDPSOCKET udpsock) { - return CUDT::bind(u, udpsock); + return srt::CUDT::bind(u, udpsock); } int listen(SRTSOCKET u, int backlog) { - return CUDT::listen(u, backlog); + return srt::CUDT::listen(u, backlog); } SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen) { - return CUDT::accept(u, addr, addrlen); + return srt::CUDT::accept(u, addr, addrlen); } int connect(SRTSOCKET u, const struct sockaddr* name, int namelen) { - return CUDT::connect(u, name, namelen, 0); + return srt::CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } int close(SRTSOCKET u) { - return CUDT::close(u); + return srt::CUDT::close(u); } int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen) { - return CUDT::getpeername(u, name, namelen); + return srt::CUDT::getpeername(u, name, namelen); } int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen) { - return CUDT::getsockname(u, name, namelen); + return srt::CUDT::getsockname(u, name, namelen); } -int getsockopt( - SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen) +int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen) { - return CUDT::getsockopt(u, level, optname, optval, optlen); + return srt::CUDT::getsockopt(u, level, optname, optval, optlen); } -int setsockopt( - SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen) +int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen) { - return CUDT::setsockopt(u, level, optname, optval, optlen); + return srt::CUDT::setsockopt(u, level, optname, optval, optlen); } // DEVELOPER API -int connect_debug( - SRTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn) +int connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn) { - return CUDT::connect(u, name, namelen, forced_isn); + return srt::CUDT::connect(u, name, namelen, forced_isn); } int send(SRTSOCKET u, const char* buf, int len, int flags) { - return CUDT::send(u, buf, len, flags); + return srt::CUDT::send(u, buf, len, flags); } int recv(SRTSOCKET u, char* buf, int len, int flags) { - return CUDT::recv(u, buf, len, flags); + return srt::CUDT::recv(u, buf, len, flags); } - -int sendmsg( - SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, - uint64_t srctime) +int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) { - return CUDT::sendmsg(u, buf, len, ttl, inorder, srctime); + return srt::CUDT::sendmsg(u, buf, len, ttl, inorder, srctime); } -int recvmsg(SRTSOCKET u, char* buf, int len, uint64_t& srctime) +int recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { - return CUDT::recvmsg(u, buf, len, srctime); + return srt::CUDT::recvmsg(u, buf, len, srctime); } int recvmsg(SRTSOCKET u, char* buf, int len) { - uint64_t srctime; - - return CUDT::recvmsg(u, buf, len, srctime); + int64_t srctime; + return srt::CUDT::recvmsg(u, buf, len, srctime); } -int64_t sendfile( - SRTSOCKET u, - fstream& ifs, - int64_t& offset, - int64_t size, - int block) +int64_t sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) { - return CUDT::sendfile(u, ifs, offset, size, block); + return srt::CUDT::sendfile(u, ifs, offset, size, block); } -int64_t recvfile( - SRTSOCKET u, - fstream& ofs, - int64_t& offset, - int64_t size, - int block) +int64_t recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) { - return CUDT::recvfile(u, ofs, offset, size, block); + return srt::CUDT::recvfile(u, ofs, offset, size, block); } -int64_t sendfile2( - SRTSOCKET u, - const char* path, - int64_t* offset, - int64_t size, - int block) +int64_t sendfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { - fstream ifs(path, ios::binary | ios::in); - int64_t ret = CUDT::sendfile(u, ifs, *offset, size, block); - ifs.close(); - return ret; + fstream ifs(path, ios::binary | ios::in); + int64_t ret = srt::CUDT::sendfile(u, ifs, *offset, size, block); + ifs.close(); + return ret; } -int64_t recvfile2( - SRTSOCKET u, - const char* path, - int64_t* offset, - int64_t size, - int block) +int64_t recvfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { - fstream ofs(path, ios::binary | ios::out); - int64_t ret = CUDT::recvfile(u, ofs, *offset, size, block); - ofs.close(); - return ret; + fstream ofs(path, ios::binary | ios::out); + int64_t ret = srt::CUDT::recvfile(u, ofs, *offset, size, block); + ofs.close(); + return ret; } -int select( - int nfds, - UDSET* readfds, - UDSET* writefds, - UDSET* exceptfds, - const struct timeval* timeout) +int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout) { - return CUDT::select(nfds, readfds, writefds, exceptfds, timeout); + return srt::CUDT::select(nfds, readfds, writefds, exceptfds, timeout); } -int selectEx( - const vector& fds, - vector* readfds, - vector* writefds, - vector* exceptfds, - int64_t msTimeOut) +int selectEx(const vector& fds, + vector* readfds, + vector* writefds, + vector* exceptfds, + int64_t msTimeOut) { - return CUDT::selectEx(fds, readfds, writefds, exceptfds, msTimeOut); + return srt::CUDT::selectEx(fds, readfds, writefds, exceptfds, msTimeOut); } int epoll_create() { - return CUDT::epoll_create(); + return srt::CUDT::epoll_create(); +} + +int epoll_clear_usocks(int eid) +{ + return srt::CUDT::epoll_clear_usocks(eid); } int epoll_add_usock(int eid, SRTSOCKET u, const int* events) { - return CUDT::epoll_add_usock(eid, u, events); + return srt::CUDT::epoll_add_usock(eid, u, events); } int epoll_add_ssock(int eid, SYSSOCKET s, const int* events) { - return CUDT::epoll_add_ssock(eid, s, events); + return srt::CUDT::epoll_add_ssock(eid, s, events); } int epoll_update_usock(int eid, SRTSOCKET u, const int* events) { - return CUDT::epoll_update_usock(eid, u, events); + return srt::CUDT::epoll_update_usock(eid, u, events); } int epoll_update_ssock(int eid, SYSSOCKET s, const int* events) { - return CUDT::epoll_update_ssock(eid, s, events); + return srt::CUDT::epoll_update_ssock(eid, s, events); } int epoll_remove_usock(int eid, SRTSOCKET u) { - return CUDT::epoll_remove_usock(eid, u); + return srt::CUDT::epoll_remove_usock(eid, u); } int epoll_remove_ssock(int eid, SYSSOCKET s) { - return CUDT::epoll_remove_ssock(eid, s); + return srt::CUDT::epoll_remove_ssock(eid, s); } -int epoll_wait( - int eid, - set* readfds, - set* writefds, - int64_t msTimeOut, - set* lrfds, - set* lwfds) +int epoll_wait(int eid, + set* readfds, + set* writefds, + int64_t msTimeOut, + set* lrfds, + set* lwfds) { - return CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); + return srt::CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); } -/* - -#define SET_RESULT(val, num, fds, it) \ - if (val != NULL) \ - { \ - if (val->empty()) \ - { \ - if (num) *num = 0; \ - } \ - else \ - { \ - if (*num > static_cast(val->size())) \ - *num = val->size(); \ - int count = 0; \ - for (it = val->begin(); it != val->end(); ++ it) \ - { \ - if (count >= *num) \ - break; \ - fds[count ++] = *it; \ - } \ - } \ - } - -*/ - template inline void set_result(set* val, int* num, SOCKTYPE* fds) { - if ( !val || !num || !fds ) + if (!val || !num || !fds) return; if (*num > int(val->size())) @@ -3070,140 +4349,146 @@ inline void set_result(set* val, int* num, SOCKTYPE* fds) int count = 0; // This loop will run 0 times if val->empty() - for (typename set::const_iterator it = val->begin(); it != val->end(); ++ it) + for (typename set::const_iterator it = val->begin(); it != val->end(); ++it) { if (count >= *num) break; - fds[count ++] = *it; - } -} - -int epoll_wait2( - int eid, SRTSOCKET* readfds, - int* rnum, SRTSOCKET* writefds, - int* wnum, - int64_t msTimeOut, - SYSSOCKET* lrfds, - int* lrnum, - SYSSOCKET* lwfds, - int* lwnum) -{ - // This API is an alternative format for epoll_wait, created for - // compatability with other languages. Users need to pass in an array - // for holding the returned sockets, with the maximum array length - // stored in *rnum, etc., which will be updated with returned number - // of sockets. - - set readset; - set writeset; - set lrset; - set lwset; - set* rval = NULL; - set* wval = NULL; - set* lrval = NULL; - set* lwval = NULL; - if ((readfds != NULL) && (rnum != NULL)) - rval = &readset; - if ((writefds != NULL) && (wnum != NULL)) - wval = &writeset; - if ((lrfds != NULL) && (lrnum != NULL)) - lrval = &lrset; - if ((lwfds != NULL) && (lwnum != NULL)) - lwval = &lwset; - - int ret = CUDT::epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval); - if (ret > 0) - { - //set::const_iterator i; - //SET_RESULT(rval, rnum, readfds, i); - set_result(rval, rnum, readfds); - //SET_RESULT(wval, wnum, writefds, i); - set_result(wval, wnum, writefds); - - //set::const_iterator j; - //SET_RESULT(lrval, lrnum, lrfds, j); - set_result(lrval, lrnum, lrfds); - //SET_RESULT(lwval, lwnum, lwfds, j); - set_result(lwval, lwnum, lwfds); - } - return ret; + fds[count++] = *it; + } +} + +int epoll_wait2(int eid, + SRTSOCKET* readfds, + int* rnum, + SRTSOCKET* writefds, + int* wnum, + int64_t msTimeOut, + SYSSOCKET* lrfds, + int* lrnum, + SYSSOCKET* lwfds, + int* lwnum) +{ + // This API is an alternative format for epoll_wait, created for + // compatability with other languages. Users need to pass in an array + // for holding the returned sockets, with the maximum array length + // stored in *rnum, etc., which will be updated with returned number + // of sockets. + + set readset; + set writeset; + set lrset; + set lwset; + set* rval = NULL; + set* wval = NULL; + set* lrval = NULL; + set* lwval = NULL; + if ((readfds != NULL) && (rnum != NULL)) + rval = &readset; + if ((writefds != NULL) && (wnum != NULL)) + wval = &writeset; + if ((lrfds != NULL) && (lrnum != NULL)) + lrval = &lrset; + if ((lwfds != NULL) && (lwnum != NULL)) + lwval = &lwset; + + int ret = srt::CUDT::epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval); + if (ret > 0) + { + // set::const_iterator i; + // SET_RESULT(rval, rnum, readfds, i); + set_result(rval, rnum, readfds); + // SET_RESULT(wval, wnum, writefds, i); + set_result(wval, wnum, writefds); + + // set::const_iterator j; + // SET_RESULT(lrval, lrnum, lrfds, j); + set_result(lrval, lrnum, lrfds); + // SET_RESULT(lwval, lwnum, lwfds, j); + set_result(lwval, lwnum, lwfds); + } + return ret; } int epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { - return CUDT::epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); + return srt::CUDT::epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); } int epoll_release(int eid) { - return CUDT::epoll_release(eid); + return srt::CUDT::epoll_release(eid); } ERRORINFO& getlasterror() { - return CUDT::getlasterror(); + return srt::CUDT::getlasterror(); } int getlasterror_code() { - return CUDT::getlasterror().getErrorCode(); + return srt::CUDT::getlasterror().getErrorCode(); } const char* getlasterror_desc() { - return CUDT::getlasterror().getErrorMessage(); + return srt::CUDT::getlasterror().getErrorMessage(); } int getlasterror_errno() { - return CUDT::getlasterror().getErrno(); + return srt::CUDT::getlasterror().getErrno(); } // Get error string of a given error code const char* geterror_desc(int code, int err) { - CUDTException e (CodeMajor(code/1000), CodeMinor(code%1000), err); - return(e.getErrorMessage()); + srt::CUDTException e(CodeMajor(code / 1000), CodeMinor(code % 1000), err); + return (e.getErrorMessage()); } -int bstats(SRTSOCKET u, TRACEBSTATS* perf, bool clear) +int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear) { - return CUDT::bstats(u, perf, clear); + return srt::CUDT::bstats(u, perf, clear); } SRT_SOCKSTATUS getsockstate(SRTSOCKET u) { - return CUDT::getsockstate(u); + return srt::CUDT::getsockstate(u); } +} // namespace UDT + +namespace srt +{ + void setloglevel(LogLevel::type ll) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.max_level = ll; } void addlogfa(LogFA fa) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.set(fa, true); } void dellogfa(LogFA fa) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.set(fa, false); } void resetlogfa(set fas) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); for (int i = 0; i <= SRT_LOGFA_LASTNONE; ++i) srt_logger_config.enabled_fa.set(i, fas.count(i)); } void resetlogfa(const int* fara, size_t fara_size) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.reset(); for (const int* i = fara; i != fara + fara_size; ++i) srt_logger_config.enabled_fa.set(*i, true); @@ -3211,20 +4496,20 @@ void resetlogfa(const int* fara, size_t fara_size) void setlogstream(std::ostream& stream) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.log_stream = &stream; } void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.loghandler_opaque = opaque; - srt_logger_config.loghandler_fn = handler; + srt_logger_config.loghandler_fn = handler; } void setlogflags(int flags) { - CGuard gg(srt_logger_config.mutex); + ScopedLock gg(srt_logger_config.mutex); srt_logger_config.flags = flags; } @@ -3237,9 +4522,14 @@ SRT_API std::string getstreamid(SRTSOCKET u) return CUDT::getstreamid(u); } -SRT_REJECT_REASON getrejectreason(SRTSOCKET u) +int getrejectreason(SRTSOCKET u) { return CUDT::rejectReason(u); } -} // namespace UDT +int setrejectreason(SRTSOCKET u, int value) +{ + return CUDT::rejectReason(u, value); +} + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/api.h b/trunk/3rdparty/srt-1-fit/srtcore/api.h index 7ee6293eaa..ca812bf9e6 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/api.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/api.h @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -45,14 +45,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /***************************************************************************** written by - Yunhong Gu, last updated 09/28/2010 + Yunhong Gu, last updated 09/28/2010 modified by - Haivision Systems Inc. + Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_API_H__ -#define __UDT_API_H__ - +#ifndef INC_SRT_API_H +#define INC_SRT_API_H #include #include @@ -64,237 +63,425 @@ modified by #include "cache.h" #include "epoll.h" #include "handshake.h" +#include "core.h" +#if ENABLE_BONDING +#include "group.h" +#endif + +// Please refer to structure and locking information provided in the +// docs/dev/low-level-info.md document. + +namespace srt +{ class CUDT; +/// @brief Class CUDTSocket is a control layer on top of the CUDT core functionality layer. +/// CUDTSocket owns CUDT. class CUDTSocket { public: - CUDTSocket(); - ~CUDTSocket(); + CUDTSocket() + : m_Status(SRTS_INIT) + , m_SocketID(0) + , m_ListenSocket(0) + , m_PeerID(0) +#if ENABLE_BONDING + , m_GroupMemberData() + , m_GroupOf() +#endif + , m_iISN(0) + , m_UDT(this) + , m_AcceptCond() + , m_AcceptLock() + , m_uiBackLog(0) + , m_iMuxID(-1) + { + construct(); + } - SRT_SOCKSTATUS m_Status; //< current socket state + CUDTSocket(const CUDTSocket& ancestor) + : m_Status(SRTS_INIT) + , m_SocketID(0) + , m_ListenSocket(0) + , m_PeerID(0) +#if ENABLE_BONDING + , m_GroupMemberData() + , m_GroupOf() +#endif + , m_iISN(0) + , m_UDT(this, ancestor.m_UDT) + , m_AcceptCond() + , m_AcceptLock() + , m_uiBackLog(0) + , m_iMuxID(-1) + { + construct(); + } - /// Time when the socket is closed. - /// When the socket is closed, it is not removed immediately from the list - /// of sockets in order to prevent other methods from accessing invalid address. - /// A timer is started and the socket will be removed after approximately - /// 1 second (see CUDTUnited::checkBrokenSockets()). - uint64_t m_ClosureTimeStamp; + ~CUDTSocket(); - int m_iIPversion; //< IP version - sockaddr* m_pSelfAddr; //< pointer to the local address of the socket - sockaddr* m_pPeerAddr; //< pointer to the peer address of the socket + void construct(); - SRTSOCKET m_SocketID; //< socket ID - SRTSOCKET m_ListenSocket; //< ID of the listener socket; 0 means this is an independent socket + SRT_ATTR_GUARDED_BY(m_ControlLock) + sync::atomic m_Status; //< current socket state - SRTSOCKET m_PeerID; //< peer socket ID - int32_t m_iISN; //< initial sequence number, used to tell different connection from same IP:port + /// Time when the socket is closed. + /// When the socket is closed, it is not removed immediately from the list + /// of sockets in order to prevent other methods from accessing invalid address. + /// A timer is started and the socket will be removed after approximately + /// 1 second (see CUDTUnited::checkBrokenSockets()). + sync::steady_clock::time_point m_tsClosureTimeStamp; - CUDT* m_pUDT; //< pointer to the UDT entity + sockaddr_any m_SelfAddr; //< local address of the socket + sockaddr_any m_PeerAddr; //< peer address of the socket - std::set* m_pQueuedSockets; //< set of connections waiting for accept() - std::set* m_pAcceptSockets; //< set of accept()ed connections + SRTSOCKET m_SocketID; //< socket ID + SRTSOCKET m_ListenSocket; //< ID of the listener socket; 0 means this is an independent socket - pthread_cond_t m_AcceptCond; //< used to block "accept" call - pthread_mutex_t m_AcceptLock; //< mutex associated to m_AcceptCond + SRTSOCKET m_PeerID; //< peer socket ID +#if ENABLE_BONDING + groups::SocketData* m_GroupMemberData; //< Pointer to group member data, or NULL if not a group member + CUDTGroup* m_GroupOf; //< Group this socket is a member of, or NULL if it isn't +#endif - unsigned int m_uiBackLog; //< maximum number of connections in queue + int32_t m_iISN; //< initial sequence number, used to tell different connection from same IP:port - int m_iMuxID; //< multiplexer ID +private: + CUDT m_UDT; //< internal SRT socket logic - pthread_mutex_t m_ControlLock; //< lock this socket exclusively for control APIs: bind/listen/connect +public: + std::set m_QueuedSockets; //< set of connections waiting for accept() - static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) - { - return (id << 30) + isn; - } - int64_t getPeerSpec() - { - return getPeerSpec(m_PeerID, m_iISN); - } + sync::Condition m_AcceptCond; //< used to block "accept" call + sync::Mutex m_AcceptLock; //< mutex associated to m_AcceptCond -private: - CUDTSocket(const CUDTSocket&); - CUDTSocket& operator=(const CUDTSocket&); -}; + unsigned int m_uiBackLog; //< maximum number of connections in queue -//////////////////////////////////////////////////////////////////////////////// + // XXX A refactoring might be needed here. -class CUDTUnited -{ -friend class CUDT; -friend class CRendezvousQueue; + // There are no reasons found why the socket can't contain a list iterator to a + // multiplexer INSTEAD of m_iMuxID. There's no danger in this solution because + // the multiplexer is never deleted until there's at least one socket using it. + // + // The multiplexer may even physically be contained in the CUDTUnited object, + // just track the multiple users of it (the listener and the accepted sockets). + // When deleting, you simply "unsubscribe" yourself from the multiplexer, which + // will unref it and remove the list element by the iterator kept by the + // socket. + int m_iMuxID; //< multiplexer ID -public: - CUDTUnited(); - ~CUDTUnited(); + sync::Mutex m_ControlLock; //< lock this socket exclusively for control APIs: bind/listen/connect -public: + CUDT& core() { return m_UDT; } + const CUDT& core() const { return m_UDT; } - static std::string CONID(SRTSOCKET sock); + static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) { return (int64_t(id) << 30) + isn; } + int64_t getPeerSpec() { return getPeerSpec(m_PeerID, m_iISN); } - /// initialize the UDT library. - /// @return 0 if success, otherwise -1 is returned. + SRT_SOCKSTATUS getStatus(); - int startup(); + /// This function shall be called always wherever + /// you'd like to call cudtsocket->m_pUDT->close(), + /// from within the GC thread only (that is, only when + /// the socket should be no longer visible in the + /// connection, including for sending remaining data). + void breakSocket_LOCKED(); - /// release the UDT library. - /// @return 0 if success, otherwise -1 is returned. + /// This makes the socket no longer capable of performing any transmission + /// operation, but continues to be responsive in the connection in order + /// to finish sending the data that were scheduled for sending so far. + void setClosed(); - int cleanup(); + /// This does the same as setClosed, plus sets the m_bBroken to true. + /// Such a socket can still be read from so that remaining data from + /// the receiver buffer can be read, but no longer sends anything. + void setBrokenClosed(); + void removeFromGroup(bool broken); - /// Create a new UDT socket. - /// @param [in] af IP version, IPv4 (AF_INET) or IPv6 (AF_INET6). - /// @param [in] type (ignored) - /// @return The new UDT socket ID, or INVALID_SOCK. + // Instrumentally used by select() and also required for non-blocking + // mode check in groups + bool readReady(); + bool writeReady() const; + bool broken() const; - SRTSOCKET newSocket(int af, int ); +private: + CUDTSocket& operator=(const CUDTSocket&); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CUDTUnited +{ + friend class CUDT; + friend class CUDTGroup; + friend class CRendezvousQueue; + friend class CCryptoControl; - /// Create a new UDT connection. - /// @param [in] listen the listening UDT socket; - /// @param [in] peer peer address. - /// @param [in,out] hs handshake information from peer side (in), negotiated value (out); - /// @return If the new connection is successfully created: 1 success, 0 already exist, -1 error. +public: + CUDTUnited(); + ~CUDTUnited(); - int newConnection(const SRTSOCKET listen, const sockaddr* peer, CHandShake* hs, const CPacket& hspkt, - ref_t r_error); + // Public constants + static const int32_t MAX_SOCKET_VAL = SRTGROUP_MASK - 1; // maximum value for a regular socket - int installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); +public: + enum ErrorHandling + { + ERH_RETURN, + ERH_THROW, + ERH_ABORT + }; + static std::string CONID(SRTSOCKET sock); + + /// initialize the UDT library. + /// @return 0 if success, otherwise -1 is returned. + int startup(); + + /// release the UDT library. + /// @return 0 if success, otherwise -1 is returned. + int cleanup(); + + /// Create a new UDT socket. + /// @param [out] pps Variable (optional) to which the new socket will be written, if succeeded + /// @return The new UDT socket ID, or INVALID_SOCK. + SRTSOCKET newSocket(CUDTSocket** pps = NULL); + + /// Create (listener-side) a new socket associated with the incoming connection request. + /// @param [in] listen the listening socket ID. + /// @param [in] peer peer address. + /// @param [in,out] hs handshake information from peer side (in), negotiated value (out); + /// @param [out] w_error error code in case of failure. + /// @param [out] w_acpu reference to the existing associated socket if already exists. + /// @return 1: if the new connection was successfully created (accepted), @a w_acpu is NULL; + /// 0: the connection already exists (reference to the corresponding socket is returned in @a w_acpu). + /// -1: The connection processing failed due to memory alloation error, exceeding listener's backlog, + /// any error propagated from CUDT::open and CUDT::acceptAndRespond. + int newConnection(const SRTSOCKET listen, + const sockaddr_any& peer, + const CPacket& hspkt, + CHandShake& w_hs, + int& w_error, + CUDT*& w_acpu); + + int installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); + int installConnectHook(const SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); + + /// Check the status of the UDT socket. + /// @param [in] u the UDT socket ID. + /// @return UDT socket status, or NONEXIST if not found. + SRT_SOCKSTATUS getStatus(const SRTSOCKET u); + + // socket APIs + + int bind(CUDTSocket* u, const sockaddr_any& name); + int bind(CUDTSocket* u, UDPSOCKET udpsock); + int listen(const SRTSOCKET u, int backlog); + SRTSOCKET accept(const SRTSOCKET listen, sockaddr* addr, int* addrlen); + SRTSOCKET accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut); + int connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int tarlen); + int connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + int connectIn(CUDTSocket* s, const sockaddr_any& target, int32_t forced_isn); +#if ENABLE_BONDING + int groupConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG targets[], int arraysize); + int singleMemberConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG* target); +#endif + int close(const SRTSOCKET u); + int close(CUDTSocket* s); + void getpeername(const SRTSOCKET u, sockaddr* name, int* namelen); + void getsockname(const SRTSOCKET u, sockaddr* name, int* namelen); + int select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout); + int selectEx(const std::vector& fds, + std::vector* readfds, + std::vector* writefds, + std::vector* exceptfds, + int64_t msTimeOut); + int epoll_create(); + int epoll_clear_usocks(int eid); + int epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + int epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events); + int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + int epoll_remove_usock(const int eid, const SRTSOCKET u); + template + int epoll_remove_entity(const int eid, EntityType* ent); + int epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* ent); +#if ENABLE_BONDING + int epoll_remove_group_INTERNAL(const int eid, CUDTGroup* ent); +#endif + int epoll_remove_ssock(const int eid, const SYSSOCKET s); + int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); + int32_t epoll_set(const int eid, int32_t flags); + int epoll_release(const int eid); + +#if ENABLE_BONDING + // [[using locked(m_GlobControlLock)]] + CUDTGroup& addGroup(SRTSOCKET id, SRT_GROUP_TYPE type) + { + // This only ensures that the element exists. + // If the element was newly added, it will be NULL. + CUDTGroup*& g = m_Groups[id]; + if (!g) + { + // This is a reference to the cell, so it will + // rewrite it into the map. + g = new CUDTGroup(type); + } + + // Now we are sure that g is not NULL, + // and persistence of this object is in the map. + // The reference to the object can be safely returned here. + return *g; + } - /// look up the UDT entity according to its ID. - /// @param [in] u the UDT socket ID. - /// @return Pointer to the UDT entity. + void deleteGroup(CUDTGroup* g); + void deleteGroup_LOCKED(CUDTGroup* g); - CUDT* lookup(const SRTSOCKET u); + // [[using locked(m_GlobControlLock)]] + CUDTGroup* findPeerGroup_LOCKED(SRTSOCKET peergroup) + { + for (groups_t::iterator i = m_Groups.begin(); i != m_Groups.end(); ++i) + { + if (i->second->peerid() == peergroup) + return i->second; + } + return NULL; + } +#endif - /// Check the status of the UDT socket. - /// @param [in] u the UDT socket ID. - /// @return UDT socket status, or NONEXIST if not found. + CEPoll& epoll_ref() { return m_EPoll; } - SRT_SOCKSTATUS getStatus(const SRTSOCKET u); +private: + /// Generates a new socket ID. This function starts from a randomly + /// generated value (at initialization time) and goes backward with + /// with next calls. The possible values come from the range without + /// the SRTGROUP_MASK bit, and the group bit is set when the ID is + /// generated for groups. It is also internally checked if the + /// newly generated ID isn't already used by an existing socket or group. + /// + /// Socket ID value range. + /// - [0]: reserved for handshake procedure. If the destination Socket ID is 0 + /// (destination Socket ID unknown) the packet will be sent to the listening socket + /// or to a socket that is in the rendezvous connection phase. + /// - [1; 2 ^ 30): single socket ID range. + /// - (2 ^ 30; 2 ^ 31): group socket ID range. Effectively any positive number + /// from [1; 2 ^ 30) with bit 30 set to 1. Bit 31 is zero. + /// The most significant bit 31 (sign bit) is left unused so that checking for a value <= 0 identifies an invalid + /// socket ID. + /// + /// @param group The socket id should be for socket group. + /// @return The new socket ID. + /// @throw CUDTException if after rolling over all possible ID values nothing can be returned + SRTSOCKET generateSocketID(bool group = false); - // socket APIs +private: + typedef std::map sockets_t; // stores all the socket structures + sockets_t m_Sockets; - int bind(const SRTSOCKET u, const sockaddr* name, int namelen); - int bind(const SRTSOCKET u, UDPSOCKET udpsock); - int listen(const SRTSOCKET u, int backlog); - SRTSOCKET accept(const SRTSOCKET listen, sockaddr* addr, int* addrlen); - int connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); - int close(const SRTSOCKET u); - int getpeername(const SRTSOCKET u, sockaddr* name, int* namelen); - int getsockname(const SRTSOCKET u, sockaddr* name, int* namelen); - int select(ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout); - int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); - int epoll_create(); - int epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); - int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - int epoll_remove_usock(const int eid, const SRTSOCKET u); - int epoll_remove_ssock(const int eid, const SYSSOCKET s); - int epoll_update_usock(const int eid, const SRTSOCKET u, const int* events = NULL); - int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* lwfds = NULL); - int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); - int32_t epoll_set(const int eid, int32_t flags); - int epoll_release(const int eid); +#if ENABLE_BONDING + typedef std::map groups_t; + groups_t m_Groups; +#endif - /// record the UDT exception. - /// @param [in] e pointer to a UDT exception instance. + sync::Mutex m_GlobControlLock; // used to synchronize UDT API - void setError(CUDTException* e); + sync::Mutex m_IDLock; // used to synchronize ID generation - /// look up the most recent UDT exception. - /// @return pointer to a UDT exception instance. + SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID + SRTSOCKET m_SocketIDGenerator_init; // Keeps track of the very first one - CUDTException* getError(); + std::map > + m_PeerRec; // record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn private: -// void init(); + friend struct FLookupSocketWithEvent_LOCKED; -private: - std::map m_Sockets; // stores all the socket structures + CUDTSocket* locateSocket(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); + // This function does the same as locateSocket, except that: + // - lock on m_GlobControlLock is expected (so that you don't unlock between finding and using) + // - only return NULL if not found + CUDTSocket* locateSocket_LOCKED(SRTSOCKET u); + CUDTSocket* locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn); - pthread_mutex_t m_ControlLock; // used to synchronize UDT API +#if ENABLE_BONDING + CUDTGroup* locateAcquireGroup(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); + CUDTGroup* acquireSocketsGroup(CUDTSocket* s); - pthread_mutex_t m_IDLock; // used to synchronize ID generation - SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID + struct GroupKeeper + { + CUDTGroup* group; + + // This is intended for API functions to lock the group's existence + // for the lifetime of their call. + GroupKeeper(CUDTUnited& glob, SRTSOCKET id, ErrorHandling erh) { group = glob.locateAcquireGroup(id, erh); } + + // This is intended for TSBPD thread that should lock the group's + // existence until it exits. + GroupKeeper(CUDTUnited& glob, CUDTSocket* s) { group = glob.acquireSocketsGroup(s); } + + ~GroupKeeper() + { + if (group) + { + // We have a guarantee that if `group` was set + // as non-NULL here, it is also acquired and will not + // be deleted until this busy flag is set back to false. + sync::ScopedLock cgroup(*group->exp_groupLock()); + group->apiRelease(); + // Only now that the group lock is lifted, can the + // group be now deleted and this pointer potentially dangling + } + } + }; - std::map > m_PeerRec;// record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn +#endif + void updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* = NULL); + bool updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); -private: - pthread_key_t m_TLSError; // thread local error record (last error) - static void TLSDestroy(void* e) {if (NULL != e) delete (CUDTException*)e;} + // Utility functions for updateMux + void configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af); + uint16_t installMuxer(CUDTSocket* w_s, CMultiplexer& sm); -private: - CUDTSocket* locate(const SRTSOCKET u); - CUDTSocket* locate(const sockaddr* peer, const SRTSOCKET id, int32_t isn); - void updateMux(CUDTSocket* s, const sockaddr* addr = NULL, const UDPSOCKET* = NULL); - void updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); + /// @brief Checks if channel configuration matches the socket configuration. + /// @param cfgMuxer multiplexer configuration. + /// @param cfgSocket socket configuration. + /// @return tru if configurations match, false otherwise. + static bool channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket); private: - std::map m_mMultiplexer; // UDP multiplexer - pthread_mutex_t m_MultiplexerLock; + std::map m_mMultiplexer; // UDP multiplexer + sync::Mutex m_MultiplexerLock; private: - CCache* m_pCache; // UDT network information cache + CCache* m_pCache; // UDT network information cache private: - volatile bool m_bClosing; - pthread_mutex_t m_GCStopLock; - pthread_cond_t m_GCStopCond; + srt::sync::atomic m_bClosing; + sync::Mutex m_GCStopLock; + sync::Condition m_GCStopCond; - pthread_mutex_t m_InitLock; - int m_iInstanceCount; // number of startup() called by application - bool m_bGCStatus; // if the GC thread is working (true) + sync::Mutex m_InitLock; + int m_iInstanceCount; // number of startup() called by application + bool m_bGCStatus; // if the GC thread is working (true) - pthread_t m_GCThread; - static void* garbageCollect(void*); + sync::CThread m_GCThread; + static void* garbageCollect(void*); - std::map m_ClosedSockets; // temporarily store closed sockets + sockets_t m_ClosedSockets; // temporarily store closed sockets +#if ENABLE_BONDING + groups_t m_ClosedGroups; +#endif - void checkBrokenSockets(); - void removeSocket(const SRTSOCKET u); + void checkBrokenSockets(); + void removeSocket(const SRTSOCKET u); - CEPoll m_EPoll; // handling epoll data structures and events + CEPoll m_EPoll; // handling epoll data structures and events private: - CUDTUnited(const CUDTUnited&); - CUDTUnited& operator=(const CUDTUnited&); + CUDTUnited(const CUDTUnited&); + CUDTUnited& operator=(const CUDTUnited&); }; -// Debug support -inline std::string SockaddrToString(const sockaddr* sadr) -{ - void* addr = - sadr->sa_family == AF_INET ? - (void*)&((sockaddr_in*)sadr)->sin_addr - : sadr->sa_family == AF_INET6 ? - (void*)&((sockaddr_in6*)sadr)->sin6_addr - : 0; - // (cast to (void*) is required because otherwise the 2-3 arguments - // of ?: operator would have different types, which isn't allowed in C++. - if ( !addr ) - return "unknown:0"; - - std::ostringstream output; - char hostbuf[1024]; - int flags; - -#if ENABLE_GETNAMEINFO - flags = NI_NAMEREQD; -#else - flags = NI_NUMERICHOST | NI_NUMERICSERV; -#endif - - if (!getnameinfo(sadr, sizeof(*sadr), hostbuf, 1024, NULL, 0, flags)) - { - output << hostbuf; - } - - output << ":" << ntohs(((sockaddr_in*)sadr)->sin_port); // TRICK: sin_port and sin6_port have the same offset and size - return output.str(); -} - +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/atomic.h b/trunk/3rdparty/srt-1-fit/srtcore/atomic.h new file mode 100644 index 0000000000..f1394a7dac --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/atomic.h @@ -0,0 +1,311 @@ +//---------------------------------------------------------------------------- +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or distribute +// this software, either in source code form or as a compiled binary, for any +// purpose, commercial or non-commercial, and by any means. +// +// In jurisdictions that recognize copyright laws, the author or authors of +// this software dedicate any and all copyright interest in the software to the +// public domain. We make this dedication for the benefit of the public at +// large and to the detriment of our heirs and successors. We intend this +// dedication to be an overt act of relinquishment in perpetuity of all present +// and future rights to this software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to +//----------------------------------------------------------------------------- + +// SRT Project information: +// This file was adopted from a Public Domain project from +// https://github.com/mbitsnbites/atomic +// Only namespaces were changed to adopt it for SRT project. + +#ifndef SRT_SYNC_ATOMIC_H_ +#define SRT_SYNC_ATOMIC_H_ + +// Macro for disallowing copying of an object. +#if __cplusplus >= 201103L +#define ATOMIC_DISALLOW_COPY(T) \ + T(const T&) = delete; \ + T& operator=(const T&) = delete; +#else +#define ATOMIC_DISALLOW_COPY(T) \ + T(const T&); \ + T& operator=(const T&); +#endif + +// A portable static assert. +#if __cplusplus >= 201103L +#define ATOMIC_STATIC_ASSERT(condition, message) \ + static_assert((condition), message) +#else +// Based on: http://stackoverflow.com/a/809465/5778708 +#define ATOMIC_STATIC_ASSERT(condition, message) \ + _impl_STATIC_ASSERT_LINE(condition, __LINE__) +#define _impl_PASTE(a, b) a##b +#ifdef __GNUC__ +#define _impl_UNUSED __attribute__((__unused__)) +#else +#define _impl_UNUSED +#endif +#define _impl_STATIC_ASSERT_LINE(condition, line) \ + typedef char _impl_PASTE( \ + STATIC_ASSERT_failed_, \ + line)[(2 * static_cast(!!(condition))) - 1] _impl_UNUSED +#endif + +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + // NOTE: Defined at the top level. +#elif __cplusplus >= 201103L + // NOTE: Prefer to use the c++11 std::atomic. + #define ATOMIC_USE_CPP11_ATOMIC +#elif (defined(__clang__) && defined(__clang_major__) && (__clang_major__ > 5)) \ + || defined(__xlc__) + // NOTE: Clang <6 does not support GCC __atomic_* intrinsics. I am unsure + // about Clang6. Since Clang sets __GNUC__ and __GNUC_MINOR__ of this era + // to <4.5, older Clang will catch the setting below to use the + // POSIX Mutex Implementation. + #define ATOMIC_USE_GCC_INTRINSICS +#elif defined(__GNUC__) \ + && ( (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) ) + // NOTE: The __atomic_* family of intrisics were introduced in GCC-4.7.0. + // NOTE: This follows #if defined(__clang__), because most if, not all, + // versions of Clang define __GNUC__ and __GNUC_MINOR__ but often define + // them to 4.4 or an even earlier version. Most of the newish versions + // of Clang also support GCC Atomic Intrisics even if they set GCC version + // macros to <4.7. + #define ATOMIC_USE_GCC_INTRINSICS +#elif defined(__GNUC__) && !defined(ATOMIC_USE_SRT_SYNC_MUTEX) + // NOTE: GCC compiler built-ins for atomic operations are pure + // compiler extensions prior to GCC-4.7 and were grouped into the + // the __sync_* family of functions. GCC-4.7, both the c++11 and C11 + // standards had been finalized, and GCC updated their built-ins to + // better reflect the new memory model and the new functions grouped + // into the __atomic_* family. Also the memory models were defined + // differently, than in pre 4.7. + // TODO: PORT to the pre GCC-4.7 __sync_* intrinsics. In the meantime use + // the POSIX Mutex Implementation. + #define ATOMIC_USE_SRT_SYNC_MUTEX 1 +#elif defined(_MSC_VER) + #define ATOMIC_USE_MSVC_INTRINSICS + #include "atomic_msvc.h" +#else + #error Unsupported compiler / system. +#endif +// Include any necessary headers for the selected Atomic Implementation. +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + #include "sync.h" +#endif +#if defined(ATOMIC_USE_CPP11_ATOMIC) + #include +#endif + +namespace srt { +namespace sync { +template +class atomic { +public: + ATOMIC_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || + sizeof(T) == 8, + "Only types of size 1, 2, 4 or 8 are supported"); + + atomic() + : value_(static_cast(0)) +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + , mutex_() +#endif + { + // No-Op + } + + explicit atomic(const T value) + : value_(value) +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + , mutex_() +#endif + { + // No-Op + } + + ~atomic() + { + // No-Op + } + + /// @brief Performs an atomic increment operation (value + 1). + /// @returns The new value of the atomic object. + T operator++() { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + const T t = ++value_; + return t; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + return __atomic_add_fetch(&value_, 1, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + return msvc::interlocked::increment(&value_); +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + return ++value_; +#else + #error "Implement Me." +#endif + } + + /// @brief Performs an atomic decrement operation (value - 1). + /// @returns The new value of the atomic object. + T operator--() { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + const T t = --value_; + return t; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + return __atomic_sub_fetch(&value_, 1, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + return msvc::interlocked::decrement(&value_); +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + return --value_; +#else + #error "Implement Me." +#endif + } + + /// @brief Performs an atomic compare-and-swap (CAS) operation. + /// + /// The value of the atomic object is only updated to the new value if the + /// old value of the atomic object matches @c expected_val. + /// + /// @param expected_val The expected value of the atomic object. + /// @param new_val The new value to write to the atomic object. + /// @returns True if new_value was written to the atomic object. + bool compare_exchange(const T expected_val, const T new_val) { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + bool result = false; + if (expected_val == value_) + { + value_ = new_val; + result = true; + } + return result; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + T e = expected_val; + return __atomic_compare_exchange_n( + &value_, &e, new_val, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + const T old_val = + msvc::interlocked::compare_exchange(&value_, new_val, expected_val); + return (old_val == expected_val); +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + T e = expected_val; + return value_.compare_exchange_weak(e, new_val); +#else + #error "Implement Me." +#endif + } + + /// @brief Performs an atomic set operation. + /// + /// The value of the atomic object is unconditionally updated to the new + /// value. + /// + /// @param new_val The new value to write to the atomic object. + void store(const T new_val) { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + value_ = new_val; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + __atomic_store_n(&value_, new_val, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + (void)msvc::interlocked::exchange(&value_, new_val); +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + value_.store(new_val); +#else + #error "Implement Me." +#endif + } + + /// @returns the current value of the atomic object. + /// @note Be careful about how this is used, since any operations on the + /// returned value are inherently non-atomic. + T load() const { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + const T t = value_; + return t; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + return __atomic_load_n(&value_, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + // TODO(m): Is there a better solution for MSVC? + return value_; +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + return value_; +#else + #error "Implement Me." +#endif + } + + /// @brief Performs an atomic exchange operation. + /// + /// The value of the atomic object is unconditionally updated to the new + /// value, and the old value is returned. + /// + /// @param new_val The new value to write to the atomic object. + /// @returns the old value. + T exchange(const T new_val) { +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + ScopedLock lg_(mutex_); + const T t = value_; + value_ = new_val; + return t; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + return __atomic_exchange_n(&value_, new_val, __ATOMIC_SEQ_CST); +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + return msvc::interlocked::exchange(&value_, new_val); +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + return value_.exchange(new_val); +#else + #error "Implement Me." +#endif + } + + T operator=(const T new_value) { + store(new_value); + return new_value; + } + + operator T() const { + return load(); + } + +private: +#if defined(ATOMIC_USE_SRT_SYNC_MUTEX) && (ATOMIC_USE_SRT_SYNC_MUTEX == 1) + T value_; + mutable Mutex mutex_; +#elif defined(ATOMIC_USE_GCC_INTRINSICS) + volatile T value_; +#elif defined(ATOMIC_USE_MSVC_INTRINSICS) + volatile T value_; +#elif defined(ATOMIC_USE_CPP11_ATOMIC) + std::atomic value_; +#else + #error "Implement Me. (value_ type)" +#endif + + ATOMIC_DISALLOW_COPY(atomic) +}; + +} // namespace sync +} // namespace srt + +// Undef temporary defines. +#undef ATOMIC_USE_GCC_INTRINSICS +#undef ATOMIC_USE_MSVC_INTRINSICS +#undef ATOMIC_USE_CPP11_ATOMIC + +#endif // ATOMIC_ATOMIC_H_ diff --git a/trunk/3rdparty/srt-1-fit/srtcore/atomic_clock.h b/trunk/3rdparty/srt-1-fit/srtcore/atomic_clock.h new file mode 100644 index 0000000000..e01012313e --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/atomic_clock.h @@ -0,0 +1,91 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#ifndef INC_SRT_SYNC_ATOMIC_CLOCK_H +#define INC_SRT_SYNC_ATOMIC_CLOCK_H + +#include "sync.h" +#include "atomic.h" + +namespace srt +{ +namespace sync +{ + +template +class AtomicDuration +{ + atomic dur; + typedef typename Clock::duration duration_type; + typedef typename Clock::time_point time_point_type; +public: + + AtomicDuration() ATR_NOEXCEPT : dur(0) {} + + duration_type load() + { + int64_t val = dur.load(); + return duration_type(val); + } + + void store(const duration_type& d) + { + dur.store(d.count()); + } + + AtomicDuration& operator=(const duration_type& s) + { + dur = s.count(); + return *this; + } + + operator duration_type() const + { + return duration_type(dur); + } +}; + +template +class AtomicClock +{ + atomic dur; + typedef typename Clock::duration duration_type; + typedef typename Clock::time_point time_point_type; +public: + + AtomicClock() ATR_NOEXCEPT : dur(0) {} + + time_point_type load() const + { + int64_t val = dur.load(); + return time_point_type(duration_type(val)); + } + + void store(const time_point_type& d) + { + dur.store(uint64_t(d.time_since_epoch().count())); + } + + AtomicClock& operator=(const time_point_type& s) + { + dur = s.time_since_epoch().count(); + return *this; + } + + operator time_point_type() const + { + return time_point_type(duration_type(dur.load())); + } +}; + +} // namespace sync +} // namespace srt + +#endif // INC_SRT_SYNC_ATOMIC_CLOCK_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/atomic_msvc.h b/trunk/3rdparty/srt-1-fit/srtcore/atomic_msvc.h new file mode 100644 index 0000000000..9e4df2dbdb --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/atomic_msvc.h @@ -0,0 +1,245 @@ +//----------------------------------------------------------------------------- +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or distribute +// this software, either in source code form or as a compiled binary, for any +// purpose, commercial or non-commercial, and by any means. +// +// In jurisdictions that recognize copyright laws, the author or authors of +// this software dedicate any and all copyright interest in the software to the +// public domain. We make this dedication for the benefit of the public at +// large and to the detriment of our heirs and successors. We intend this +// dedication to be an overt act of relinquishment in perpetuity of all present +// and future rights to this software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to +//----------------------------------------------------------------------------- + +// SRT Project information: +// This file was adopted from a Public Domain project from +// https://github.com/mbitsnbites/atomic +// Only namespaces were changed to adopt it for SRT project. + +#ifndef SRT_SYNC_ATOMIC_MSVC_H_ +#define SRT_SYNC_ATOMIC_MSVC_H_ + +// Define which functions we need (don't include ). +extern "C" { +short _InterlockedIncrement16(short volatile*); +long _InterlockedIncrement(long volatile*); +__int64 _InterlockedIncrement64(__int64 volatile*); + +short _InterlockedDecrement16(short volatile*); +long _InterlockedDecrement(long volatile*); +__int64 _InterlockedDecrement64(__int64 volatile*); + +char _InterlockedExchange8(char volatile*, char); +short _InterlockedExchange16(short volatile*, short); +long __cdecl _InterlockedExchange(long volatile*, long); +__int64 _InterlockedExchange64(__int64 volatile*, __int64); + +char _InterlockedCompareExchange8(char volatile*, char, char); +short _InterlockedCompareExchange16(short volatile*, short, short); +long __cdecl _InterlockedCompareExchange(long volatile*, long, long); +__int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64); +}; + +// Define which functions we want to use as inline intriniscs. +#pragma intrinsic(_InterlockedIncrement) +#pragma intrinsic(_InterlockedIncrement16) + +#pragma intrinsic(_InterlockedDecrement) +#pragma intrinsic(_InterlockedDecrement16) + +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedCompareExchange8) +#pragma intrinsic(_InterlockedCompareExchange16) + +#pragma intrinsic(_InterlockedExchange) +#pragma intrinsic(_InterlockedExchange8) +#pragma intrinsic(_InterlockedExchange16) + +#if defined(_M_X64) +#pragma intrinsic(_InterlockedIncrement64) +#pragma intrinsic(_InterlockedDecrement64) +#pragma intrinsic(_InterlockedCompareExchange64) +#pragma intrinsic(_InterlockedExchange64) +#endif // _M_X64 + +namespace srt { +namespace sync { +namespace msvc { +template +struct interlocked { +}; + +template +struct interlocked { + static inline T increment(T volatile* x) { + // There's no _InterlockedIncrement8(). + char old_val, new_val; + do { + old_val = static_cast(*x); + new_val = old_val + static_cast(1); + } while (_InterlockedCompareExchange8(reinterpret_cast(x), + new_val, + old_val) != old_val); + return static_cast(new_val); + } + + static inline T decrement(T volatile* x) { + // There's no _InterlockedDecrement8(). + char old_val, new_val; + do { + old_val = static_cast(*x); + new_val = old_val - static_cast(1); + } while (_InterlockedCompareExchange8(reinterpret_cast(x), + new_val, + old_val) != old_val); + return static_cast(new_val); + } + + static inline T compare_exchange(T volatile* x, + const T new_val, + const T expected_val) { + return static_cast( + _InterlockedCompareExchange8(reinterpret_cast(x), + static_cast(new_val), + static_cast(expected_val))); + } + + static inline T exchange(T volatile* x, const T new_val) { + return static_cast(_InterlockedExchange8( + reinterpret_cast(x), static_cast(new_val))); + } +}; + +template +struct interlocked { + static inline T increment(T volatile* x) { + return static_cast( + _InterlockedIncrement16(reinterpret_cast(x))); + } + + static inline T decrement(T volatile* x) { + return static_cast( + _InterlockedDecrement16(reinterpret_cast(x))); + } + + static inline T compare_exchange(T volatile* x, + const T new_val, + const T expected_val) { + return static_cast( + _InterlockedCompareExchange16(reinterpret_cast(x), + static_cast(new_val), + static_cast(expected_val))); + } + + static inline T exchange(T volatile* x, const T new_val) { + return static_cast( + _InterlockedExchange16(reinterpret_cast(x), + static_cast(new_val))); + } +}; + +template +struct interlocked { + static inline T increment(T volatile* x) { + return static_cast( + _InterlockedIncrement(reinterpret_cast(x))); + } + + static inline T decrement(T volatile* x) { + return static_cast( + _InterlockedDecrement(reinterpret_cast(x))); + } + + static inline T compare_exchange(T volatile* x, + const T new_val, + const T expected_val) { + return static_cast( + _InterlockedCompareExchange(reinterpret_cast(x), + static_cast(new_val), + static_cast(expected_val))); + } + + static inline T exchange(T volatile* x, const T new_val) { + return static_cast(_InterlockedExchange( + reinterpret_cast(x), static_cast(new_val))); + } +}; + +template +struct interlocked { + static inline T increment(T volatile* x) { +#if defined(_M_X64) + return static_cast( + _InterlockedIncrement64(reinterpret_cast(x))); +#else + // There's no _InterlockedIncrement64() for 32-bit x86. + __int64 old_val, new_val; + do { + old_val = static_cast<__int64>(*x); + new_val = old_val + static_cast<__int64>(1); + } while (_InterlockedCompareExchange64( + reinterpret_cast(x), new_val, old_val) != + old_val); + return static_cast(new_val); +#endif // _M_X64 + } + + static inline T decrement(T volatile* x) { +#if defined(_M_X64) + return static_cast( + _InterlockedDecrement64(reinterpret_cast(x))); +#else + // There's no _InterlockedDecrement64() for 32-bit x86. + __int64 old_val, new_val; + do { + old_val = static_cast<__int64>(*x); + new_val = old_val - static_cast<__int64>(1); + } while (_InterlockedCompareExchange64( + reinterpret_cast(x), new_val, old_val) != + old_val); + return static_cast(new_val); +#endif // _M_X64 + } + + static inline T compare_exchange(T volatile* x, + const T new_val, + const T expected_val) { + return static_cast(_InterlockedCompareExchange64( + reinterpret_cast(x), + static_cast(new_val), + static_cast(expected_val))); + } + + static inline T exchange(T volatile* x, const T new_val) { +#if defined(_M_X64) + return static_cast( + _InterlockedExchange64(reinterpret_cast(x), + static_cast(new_val))); +#else + // There's no _InterlockedExchange64 for 32-bit x86. + __int64 old_val; + do { + old_val = static_cast<__int64>(*x); + } while (_InterlockedCompareExchange64( + reinterpret_cast(x), new_val, old_val) != + old_val); + return static_cast(old_val); +#endif // _M_X64 + } +}; +} // namespace msvc +} // namespace sync +} // namespace srt + +#endif // ATOMIC_ATOMIC_MSVC_H_ diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer.cpp b/trunk/3rdparty/srt-1-fit/srtcore/buffer.cpp index 1d46b688fe..20149446f9 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/buffer.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer.cpp @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,6 +50,8 @@ modified by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include #include #include "buffer.h" @@ -57,10 +59,110 @@ modified by #include "core.h" // provides some constants #include "logging.h" +namespace srt { + using namespace std; using namespace srt_logging; +using namespace sync; + +// You can change this value at build config by using "ENFORCE" options. +#if !defined(SRT_MAVG_SAMPLING_RATE) +#define SRT_MAVG_SAMPLING_RATE 40 +#endif + +bool AvgBufSize::isTimeToUpdate(const time_point& now) const +{ + const int usMAvgBasePeriod = 1000000; // 1s in microseconds + const int us2ms = 1000; + const int msMAvgPeriod = (usMAvgBasePeriod / SRT_MAVG_SAMPLING_RATE) / us2ms; + const uint64_t elapsed_ms = count_milliseconds(now - m_tsLastSamplingTime); // ms since last sampling + return (elapsed_ms >= msMAvgPeriod); +} + +void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes, int timespan_ms) +{ + const uint64_t elapsed_ms = count_milliseconds(now - m_tsLastSamplingTime); // ms since last sampling + m_tsLastSamplingTime = now; + const uint64_t one_second_in_ms = 1000; + if (elapsed_ms > one_second_in_ms) + { + // No sampling in last 1 sec, initialize average + m_dCountMAvg = pkts; + m_dBytesCountMAvg = bytes; + m_dTimespanMAvg = timespan_ms; + return; + } + + // + // weight last average value between -1 sec and last sampling time (LST) + // and new value between last sampling time and now + // |elapsed_ms| + // +----------------------------------+-------+ + // -1 LST 0(now) + // + m_dCountMAvg = avg_iir_w<1000, double>(m_dCountMAvg, pkts, elapsed_ms); + m_dBytesCountMAvg = avg_iir_w<1000, double>(m_dBytesCountMAvg, bytes, elapsed_ms); + m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms); +} + +int round_val(double val) +{ + return static_cast(round(val)); +} -CSndBuffer::CSndBuffer(int size, int mss) +CRateEstimator::CRateEstimator() + : m_iInRatePktsCount(0) + , m_iInRateBytesCount(0) + , m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) + , m_iInRateBps(INPUTRATE_INITIAL_BYTESPS) +{} + +void CRateEstimator::setInputRateSmpPeriod(int period) +{ + m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation +} + +void CRateEstimator::updateInputRate(const time_point& time, int pkts, int bytes) +{ + // no input rate calculation + if (m_InRatePeriod == 0) + return; + + if (is_zero(m_tsInRateStartTime)) + { + m_tsInRateStartTime = time; + return; + } + else if (time < m_tsInRateStartTime) + { + // Old packets are being submitted for estimation, e.g. during the backup link activation. + return; + } + + m_iInRatePktsCount += pkts; + m_iInRateBytesCount += bytes; + + // Trigger early update in fast start mode + const bool early_update = (m_InRatePeriod < INPUTRATE_RUNNING_US) && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS); + + const uint64_t period_us = count_microseconds(time - m_tsInRateStartTime); + if (early_update || period_us > m_InRatePeriod) + { + // Required Byte/sec rate (payload + headers) + m_iInRateBytesCount += (m_iInRatePktsCount * CPacket::SRT_DATA_HDR_SIZE); + m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / period_us); + HLOGC(bslog.Debug, + log << "updateInputRate: pkts:" << m_iInRateBytesCount << " bytes:" << m_iInRatePktsCount + << " rate=" << (m_iInRateBps * 8) / 1000 << "kbps interval=" << period_us); + m_iInRatePktsCount = 0; + m_iInRateBytesCount = 0; + m_tsInRateStartTime = time; + + setInputRateSmpPeriod(INPUTRATE_RUNNING_US); + } +} + +CSndBuffer::CSndBuffer(int size, int maxpld) : m_BufLock() , m_pBlock(NULL) , m_pFirstBlock(NULL) @@ -69,148 +171,157 @@ CSndBuffer::CSndBuffer(int size, int mss) , m_pBuffer(NULL) , m_iNextMsgNo(1) , m_iSize(size) - , m_iMSS(mss) + , m_iBlockLen(maxpld) , m_iCount(0) , m_iBytesCount(0) - , m_ullLastOriginTime_us(0) -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - , m_LastSamplingTime(0) - , m_iCountMAvg(0) - , m_iBytesCountMAvg(0) - , m_TimespanMAvg(0) -#endif - , m_iInRatePktsCount(0) - , m_iInRateBytesCount(0) - , m_InRateStartTime(0) - , m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) - , m_iInRateBps(INPUTRATE_INITIAL_BYTESPS) { - // initial physical buffer of "size" - m_pBuffer = new Buffer; - m_pBuffer->m_pcData = new char [m_iSize * m_iMSS]; - m_pBuffer->m_iSize = m_iSize; - m_pBuffer->m_pNext = NULL; - - // circular linked list for out bound packets - m_pBlock = new Block; - Block* pb = m_pBlock; - for (int i = 1; i < m_iSize; ++ i) - { - pb->m_pNext = new Block; - pb->m_iMsgNoBitset = 0; - pb = pb->m_pNext; - } - pb->m_pNext = m_pBlock; - - pb = m_pBlock; - char* pc = m_pBuffer->m_pcData; - for (int i = 0; i < m_iSize; ++ i) - { - pb->m_pcData = pc; - pb = pb->m_pNext; - pc += m_iMSS; - } - - m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock; - - pthread_mutex_init(&m_BufLock, NULL); -} + // initial physical buffer of "size" + m_pBuffer = new Buffer; + m_pBuffer->m_pcData = new char[m_iSize * m_iBlockLen]; + m_pBuffer->m_iSize = m_iSize; + m_pBuffer->m_pNext = NULL; + + // circular linked list for out bound packets + m_pBlock = new Block; + Block* pb = m_pBlock; + char* pc = m_pBuffer->m_pcData; + + for (int i = 0; i < m_iSize; ++i) + { + pb->m_iMsgNoBitset = 0; + pb->m_pcData = pc; + pc += m_iBlockLen; -CSndBuffer::~CSndBuffer() -{ - Block* pb = m_pBlock->m_pNext; - while (pb != m_pBlock) - { - Block* temp = pb; - pb = pb->m_pNext; - delete temp; - } - delete m_pBlock; - - while (m_pBuffer != NULL) - { - Buffer* temp = m_pBuffer; - m_pBuffer = m_pBuffer->m_pNext; - delete [] temp->m_pcData; - delete temp; - } - - pthread_mutex_destroy(&m_BufLock); + if (i < m_iSize - 1) + { + pb->m_pNext = new Block; + pb = pb->m_pNext; + } + } + pb->m_pNext = m_pBlock; + + m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock; + + setupMutex(m_BufLock, "Buf"); } -void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order, uint64_t srctime, ref_t r_msgno) +CSndBuffer::~CSndBuffer() { - int32_t& msgno = *r_msgno; + Block* pb = m_pBlock->m_pNext; + while (pb != m_pBlock) + { + Block* temp = pb; + pb = pb->m_pNext; + delete temp; + } + delete m_pBlock; - int size = len / m_iMSS; - if ((len % m_iMSS) != 0) - size ++; + while (m_pBuffer != NULL) + { + Buffer* temp = m_pBuffer; + m_pBuffer = m_pBuffer->m_pNext; + delete[] temp->m_pcData; + delete temp; + } - HLOGC(mglog.Debug, log << "addBuffer: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << size << " buffers for " << len << " bytes"); + releaseMutex(m_BufLock); +} - // dynamically increase sender buffer - while (size + m_iCount >= m_iSize) +void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + int32_t& w_msgno = w_mctrl.msgno; + int32_t& w_seqno = w_mctrl.pktseq; + int64_t& w_srctime = w_mctrl.srctime; + const int& ttl = w_mctrl.msgttl; + const int iPktLen = m_iBlockLen; // Payload length per packet. + int iNumBlocks = len / iPktLen; + if ((len % m_iBlockLen) != 0) + ++iNumBlocks; + + HLOGC(bslog.Debug, + log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" << m_iCount << "/" << m_iSize); + // Retrieve current time before locking the mutex to be closer to packet submission event. + const steady_clock::time_point tnow = steady_clock::now(); + + ScopedLock bufferguard(m_BufLock); + // Dynamically increase sender buffer if there is not enough room. + while (iNumBlocks + m_iCount >= m_iSize) { - HLOGC(mglog.Debug, log << "addBuffer: ... still lacking " << (size + m_iCount - m_iSize) << " buffers..."); + HLOGC(bslog.Debug, log << "addBuffer: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers..."); increase(); } - const uint64_t time = CTimer::getTime(); - int32_t inorder = order ? MSGNO_PACKET_INORDER::mask : 0; + const int32_t inorder = w_mctrl.inorder ? MSGNO_PACKET_INORDER::mask : 0; + HLOGC(bslog.Debug, + log << CONID() << "addBuffer: adding " << iNumBlocks << " packets (" << len << " bytes) to send, msgno=" + << (w_msgno > 0 ? w_msgno : m_iNextMsgNo) << (inorder ? "" : " NOT") << " in order"); + + // Calculate origin time (same for all blocks of the message). + m_tsLastOriginTime = w_srctime ? time_point() + microseconds_from(w_srctime) : tnow; + // Rewrite back the actual value, even if it stays the same, so that the calling facilities can reuse it. + // May also be a subject to conversion error, thus the actual value is signalled back. + w_srctime = count_microseconds(m_tsLastOriginTime.time_since_epoch()); - HLOGC(dlog.Debug, log << CONID() << "addBuffer: adding " - << size << " packets (" << len << " bytes) to send, msgno=" << m_iNextMsgNo - << (inorder ? "" : " NOT") << " in order"); + // The sequence number passed to this function is the sequence number + // that the very first packet from the packet series should get here. + // If there's more than one packet, this function must increase it by itself + // and then return the accordingly modified sequence number in the reference. Block* s = m_pLastBlock; - msgno = m_iNextMsgNo; - for (int i = 0; i < size; ++ i) + + if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied { - int pktlen = len - i * m_iMSS; - if (pktlen > m_iMSS) - pktlen = m_iMSS; + HLOGC(bslog.Debug, log << "addBuffer: using internally managed msgno=" << m_iNextMsgNo); + w_msgno = m_iNextMsgNo; + } + else + { + HLOGC(bslog.Debug, log << "addBuffer: OVERWRITTEN by msgno supplied by caller: msgno=" << w_msgno); + m_iNextMsgNo = w_msgno; + } - HLOGC(dlog.Debug, log << "addBuffer: spreading from=" << (i*m_iMSS) << " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData); - memcpy(s->m_pcData, data + i * m_iMSS, pktlen); + for (int i = 0; i < iNumBlocks; ++i) + { + int pktlen = len - i * iPktLen; + if (pktlen > iPktLen) + pktlen = iPktLen; + + HLOGC(bslog.Debug, + log << "addBuffer: %" << w_seqno << " #" << w_msgno << " offset=" << (i * iPktLen) + << " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData); + memcpy((s->m_pcData), data + i * iPktLen, pktlen); s->m_iLength = pktlen; + s->m_iSeqNo = w_seqno; + w_seqno = CSeqNo::incseq(w_seqno); + s->m_iMsgNoBitset = m_iNextMsgNo | inorder; if (i == 0) s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); - if (i == size - 1) + if (i == iNumBlocks - 1) s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); // NOTE: if i is neither 0 nor size-1, it resuls with PB_SUBSEQUENT. - // if i == 0 == size-1, it results with PB_SOLO. + // if i == 0 == size-1, it results with PB_SOLO. // Packets assigned to one message can be: // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message // [PB_FIRST] [PB_LAST] - 2 packets per message // [PB_SOLO] - 1 packet per message - s->m_ullSourceTime_us = srctime; - s->m_ullOriginTime_us = time; s->m_iTTL = ttl; - - // XXX unchecked condition: s->m_pNext == NULL. + s->m_tsRexmitTime = time_point(); + s->m_tsOriginTime = m_tsLastOriginTime; + // Should never happen, as the call to increase() should ensure enough buffers. SRT_ASSERT(s->m_pNext); s = s->m_pNext; } m_pLastBlock = s; - CGuard::enterCS(m_BufLock); - m_iCount += size; - + m_iCount += iNumBlocks; m_iBytesCount += len; - m_ullLastOriginTime_us = time; - - updateInputRate(time, size, len); - -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - updAvgBufSize(time); -#endif - - CGuard::leaveCS(m_BufLock); + m_rateEstimator.updateInputRate(m_tsLastOriginTime, iNumBlocks, len); + updAvgBufSize(m_tsLastOriginTime); // MSGNO_SEQ::mask has a form: 00000011111111... // At least it's known that it's from some index inside til the end (to bit 0). @@ -218,616 +329,704 @@ void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order, uint6 // maximum value has been reached. Casting to int32_t to ensure the same sign // in comparison, although it's far from reaching the sign bit. - m_iNextMsgNo ++; + const int nextmsgno = ++MsgNo(m_iNextMsgNo); + HLOGC(bslog.Debug, log << "CSndBuffer::addBuffer: updating msgno: #" << m_iNextMsgNo << " -> #" << nextmsgno); + m_iNextMsgNo = nextmsgno; +} + +int CSndBuffer::addBufferFromFile(fstream& ifs, int len) +{ + const int iPktLen = m_iBlockLen; // Payload length per packet. + int iNumBlocks = len / iPktLen; + if ((len % m_iBlockLen) != 0) + ++iNumBlocks; + + HLOGC(bslog.Debug, + log << "addBufferFromFile: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << iPktLen + << " buffers for " << len << " bytes"); + + // dynamically increase sender buffer + while (iPktLen + m_iCount >= m_iSize) + { + HLOGC(bslog.Debug, + log << "addBufferFromFile: ... still lacking " << (iPktLen + m_iCount - m_iSize) << " buffers..."); + increase(); + } + + HLOGC(bslog.Debug, + log << CONID() << "addBufferFromFile: adding " << iPktLen << " packets (" << len + << " bytes) to send, msgno=" << m_iNextMsgNo); + + Block* s = m_pLastBlock; + int total = 0; + for (int i = 0; i < iPktLen; ++i) + { + if (ifs.bad() || ifs.fail() || ifs.eof()) + break; + + int pktlen = len - i * iPktLen; + if (pktlen > iPktLen) + pktlen = iPktLen; + + HLOGC(bslog.Debug, + log << "addBufferFromFile: reading from=" << (i * iPktLen) << " size=" << pktlen + << " TO BUFFER:" << (void*)s->m_pcData); + ifs.read(s->m_pcData, pktlen); + if ((pktlen = int(ifs.gcount())) <= 0) + break; + + // currently file transfer is only available in streaming mode, message is always in order, ttl = infinite + s->m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask; + if (i == 0) + s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); + if (i == iPktLen - 1) + s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); + // NOTE: PB_FIRST | PB_LAST == PB_SOLO. + // none of PB_FIRST & PB_LAST == PB_SUBSEQUENT. + + s->m_iLength = pktlen; + s->m_iTTL = SRT_MSGTTL_INF; + s = s->m_pNext; + + total += pktlen; + } + m_pLastBlock = s; + + enterCS(m_BufLock); + m_iCount += iPktLen; + m_iBytesCount += total; + + leaveCS(m_BufLock); + + m_iNextMsgNo++; if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) m_iNextMsgNo = 1; + + return total; } -void CSndBuffer::setInputRateSmpPeriod(int period) +int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) { - m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation + int readlen = 0; + w_seqnoinc = 0; + + ScopedLock bufferguard(m_BufLock); + while (m_pCurrBlock != m_pLastBlock) + { + // Make the packet REFLECT the data stored in the buffer. + w_packet.m_pcData = m_pCurrBlock->m_pcData; + readlen = m_pCurrBlock->m_iLength; + w_packet.setLength(readlen); + w_packet.m_iSeqNo = m_pCurrBlock->m_iSeqNo; + + // 1. On submission (addBuffer), the KK flag is set to EK_NOENC (0). + // 2. The readData() is called to get the original (unique) payload not ever sent yet. + // The payload must be encrypted for the first time if the encryption + // is enabled (arg kflgs != EK_NOENC). The KK encryption flag of the data packet + // header must be set and remembered accordingly (see EncryptionKeySpec). + // 3. The next time this packet is read (only for retransmission), the payload is already + // encrypted, and the proper flag value is already stored. + + // TODO: Alternatively, encryption could happen before the packet is submitted to the buffer + // (before the addBuffer() call), and corresponding flags could be set accordingly. + // This may also put an encryption burden on the application thread, rather than the sending thread, + // which could be more efficient. Note that packet sequence number must be properly set in that case, + // as it is used as a counter for the AES encryption. + if (kflgs == -1) + { + HLOGC(bslog.Debug, log << CONID() << " CSndBuffer: ERROR: encryption required and not possible. NOT SENDING."); + readlen = 0; + } + else + { + m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); + } + + Block* p = m_pCurrBlock; + w_packet.m_iMsgNo = m_pCurrBlock->m_iMsgNoBitset; + w_srctime = m_pCurrBlock->m_tsOriginTime; + m_pCurrBlock = m_pCurrBlock->m_pNext; + + if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - w_srctime) > p->m_iTTL)) + { + LOGC(bslog.Warn, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() << " with TTL=" << p->m_iTTL); + // Skip this packet due to TTL expiry. + readlen = 0; + ++w_seqnoinc; + continue; + } + + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"); + break; + } + + return readlen; } -void CSndBuffer::updateInputRate(uint64_t time, int pkts, int bytes) +CSndBuffer::time_point CSndBuffer::peekNextOriginal() const { - //no input rate calculation - if (m_InRatePeriod == 0) - return; + ScopedLock bufferguard(m_BufLock); + if (m_pCurrBlock == m_pLastBlock) + return time_point(); + + return m_pCurrBlock->m_tsOriginTime; +} - if (m_InRateStartTime == 0) +int32_t CSndBuffer::getMsgNoAt(const int offset) +{ + ScopedLock bufferguard(m_BufLock); + + Block* p = m_pFirstBlock; + + if (p) { - m_InRateStartTime = time; - return; + HLOGC(bslog.Debug, + log << "CSndBuffer::getMsgNoAt: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" + << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); } - m_iInRatePktsCount += pkts; - m_iInRateBytesCount += bytes; - - // Trigger early update in fast start mode - const bool early_update = (m_InRatePeriod < INPUTRATE_RUNNING_US) - && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS); + if (offset >= m_iCount) + { + // Prevent accessing the last "marker" block + LOGC(bslog.Error, + log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, max offset=" << m_iCount); + return SRT_MSGNO_CONTROL; + } - const uint64_t period_us = (time - m_InRateStartTime); - if (early_update || period_us > m_InRatePeriod) + // XXX Suboptimal procedure to keep the blocks identifiable + // by sequence number. Consider using some circular buffer. + int i; + Block* ee SRT_ATR_UNUSED = 0; + for (i = 0; i < offset && p; ++i) { - //Required Byte/sec rate (payload + headers) - m_iInRateBytesCount += (m_iInRatePktsCount * CPacket::SRT_DATA_HDR_SIZE); - m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / period_us); - HLOGC(dlog.Debug, log << "updateInputRate: pkts:" << m_iInRateBytesCount << " bytes:" << m_iInRatePktsCount - << " rate=" << (m_iInRateBps*8)/1000 - << "kbps interval=" << period_us); - m_iInRatePktsCount = 0; - m_iInRateBytesCount = 0; - m_InRateStartTime = time; + ee = p; + p = p->m_pNext; + } - setInputRateSmpPeriod(INPUTRATE_RUNNING_US); + if (!p) + { + LOGC(bslog.Error, + log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, stopped at " << i << " with #" + << (ee ? ee->getMsgSeq() : SRT_MSGNO_NONE)); + return SRT_MSGNO_CONTROL; } -} + HLOGC(bslog.Debug, + log << "CSndBuffer::getMsgNoAt: offset=" << offset << " found, size=" << p->m_iLength << " %" << p->m_iSeqNo + << " #" << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); -int CSndBuffer::addBufferFromFile(fstream& ifs, int len) -{ - int size = len / m_iMSS; - if ((len % m_iMSS) != 0) - size ++; - - HLOGC(mglog.Debug, log << "addBufferFromFile: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << size << " buffers for " << len << " bytes"); - - // dynamically increase sender buffer - while (size + m_iCount >= m_iSize) - { - HLOGC(mglog.Debug, log << "addBufferFromFile: ... still lacking " << (size + m_iCount - m_iSize) << " buffers..."); - increase(); - } - - HLOGC(dlog.Debug, log << CONID() << "addBufferFromFile: adding " - << size << " packets (" << len << " bytes) to send, msgno=" << m_iNextMsgNo); - - Block* s = m_pLastBlock; - int total = 0; - for (int i = 0; i < size; ++ i) - { - if (ifs.bad() || ifs.fail() || ifs.eof()) - break; - - int pktlen = len - i * m_iMSS; - if (pktlen > m_iMSS) - pktlen = m_iMSS; - - HLOGC(dlog.Debug, log << "addBufferFromFile: reading from=" << (i*m_iMSS) << " size=" << pktlen << " TO BUFFER:" << (void*)s->m_pcData); - ifs.read(s->m_pcData, pktlen); - if ((pktlen = int(ifs.gcount())) <= 0) - break; - - // currently file transfer is only available in streaming mode, message is always in order, ttl = infinite - s->m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask; - if (i == 0) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); - if (i == size - 1) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); - // NOTE: PB_FIRST | PB_LAST == PB_SOLO. - // none of PB_FIRST & PB_LAST == PB_SUBSEQUENT. - - s->m_iLength = pktlen; - s->m_iTTL = -1; - s = s->m_pNext; - - total += pktlen; - } - m_pLastBlock = s; - - CGuard::enterCS(m_BufLock); - m_iCount += size; - m_iBytesCount += total; - - CGuard::leaveCS(m_BufLock); - - m_iNextMsgNo ++; - if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) - m_iNextMsgNo = 1; - - return total; + return p->getMsgSeq(); } -int CSndBuffer::readData(char** data, int32_t& msgno_bitset, uint64_t& srctime, int kflgs) +int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, int& w_msglen) { - // No data to read - if (m_pCurrBlock == m_pLastBlock) - return 0; - - // Make the packet REFLECT the data stored in the buffer. - *data = m_pCurrBlock->m_pcData; - int readlen = m_pCurrBlock->m_iLength; - - // XXX This is probably done because the encryption should happen - // just once, and so this sets the encryption flags to both msgno bitset - // IN THE PACKET and IN THE BLOCK. This is probably to make the encryption - // happen at the time when scheduling a new packet to send, but the packet - // must remain in the send buffer until it's ACKed. For the case of rexmit - // the packet will be taken "as is" (that is, already encrypted). - // - // The problem is in the order of things: - // 0. When the application stores the data, some of the flags for PH_MSGNO are set. - // 1. The readData() is called to get the original data sent by the application. - // 2. The data are original and must be encrypted. They WILL BE encrypted, later. - // 3. So far we are in readData() so the encryption flags must be updated NOW because - // later we won't have access to the block's data. - // 4. After exiting from readData(), the packet is being encrypted. It's immediately - // sent, however the data must remain in the sending buffer until they are ACKed. - // 5. In case when rexmission is needed, the second overloaded version of readData - // is being called, and the buffer + PH_MSGNO value is extracted. All interesting - // flags must be present and correct at that time. - // - // The only sensible way to fix this problem is to encrypt the packet not after - // extracting from here, but when the packet is stored into CSndBuffer. The appropriate - // flags for PH_MSGNO will be applied directly there. Then here the value for setting - // PH_MSGNO will be set as is. - - if (kflgs == -1) - { - HLOGC(dlog.Debug, log << CONID() << " CSndBuffer: ERROR: encryption required and not possible. NOT SENDING."); - readlen = 0; - } - else - { - m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); - } - msgno_bitset = m_pCurrBlock->m_iMsgNoBitset; - - srctime = - m_pCurrBlock->m_ullSourceTime_us ? m_pCurrBlock->m_ullSourceTime_us : - m_pCurrBlock->m_ullOriginTime_us; - - m_pCurrBlock = m_pCurrBlock->m_pNext; - - HLOGC(dlog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"); - - return readlen; + int32_t& msgno_bitset = w_packet.m_iMsgNo; + + ScopedLock bufferguard(m_BufLock); + + Block* p = m_pFirstBlock; + + // XXX Suboptimal procedure to keep the blocks identifiable + // by sequence number. Consider using some circular buffer. + for (int i = 0; i < offset && p != m_pLastBlock; ++i) + { + p = p->m_pNext; + } + if (p == m_pLastBlock) + { + LOGC(qslog.Error, log << "CSndBuffer::readData: offset " << offset << " too large!"); + return 0; + } +#if ENABLE_HEAVY_LOGGING + const int32_t first_seq = p->m_iSeqNo; + int32_t last_seq = p->m_iSeqNo; +#endif + + // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. + + // If so, then inform the caller that it should first take care of the whole + // message (all blocks with that message id). Shift the m_pCurrBlock pointer + // to the position past the last of them. Then return -1 and set the + // msgno_bitset return reference to the message id that should be dropped as + // a whole. + + // After taking care of that, the caller should immediately call this function again, + // this time possibly in order to find the real data to be sent. + + // if found block is stale + // (This is for messages that have declared TTL - messages that fail to be sent + // before the TTL defined time comes, will be dropped). + + if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL)) + { + int32_t msgno = p->getMsgSeq(); + w_msglen = 1; + p = p->m_pNext; + bool move = false; + while (p != m_pLastBlock && msgno == p->getMsgSeq()) + { +#if ENABLE_HEAVY_LOGGING + last_seq = p->m_iSeqNo; +#endif + if (p == m_pCurrBlock) + move = true; + p = p->m_pNext; + if (move) + m_pCurrBlock = p; + w_msglen++; + } + + HLOGC(qslog.Debug, + log << "CSndBuffer::readData: due to TTL exceeded, SEQ " << first_seq << " - " << last_seq << ", " + << w_msglen << " packets to drop, msgno=" << msgno); + + // If readData returns -1, then msgno_bitset is understood as a Message ID to drop. + // This means that in this case it should be written by the message sequence value only + // (not the whole 4-byte bitset written at PH_MSGNO). + msgno_bitset = msgno; + return -1; + } + + w_packet.m_pcData = p->m_pcData; + const int readlen = p->m_iLength; + w_packet.setLength(readlen); + + // XXX Here the value predicted to be applied to PH_MSGNO field is extracted. + // As this function is predicted to extract the data to send as a rexmited packet, + // the packet must be in the form ready to send - so, in case of encryption, + // encrypted, and with all ENC flags already set. So, the first call to send + // the packet originally (the other overload of this function) must set these + // flags. + w_packet.m_iMsgNo = p->m_iMsgNoBitset; + w_srctime = p->m_tsOriginTime; + + // This function is called when packet retransmission is triggered. + // Therefore we are setting the rexmit time. + p->m_tsRexmitTime = steady_clock::now(); + + HLOGC(qslog.Debug, + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.m_iSeqNo + << " size=" << readlen << " to send [REXMIT]"); + + return readlen; } -int CSndBuffer::readData(char** data, const int offset, int32_t& msgno_bitset, uint64_t& srctime, int& msglen) +sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) { - CGuard bufferguard(m_BufLock); - - Block* p = m_pFirstBlock; - - // XXX Suboptimal procedure to keep the blocks identifiable - // by sequence number. Consider using some circular buffer. - for (int i = 0; i < offset; ++ i) - p = p->m_pNext; - - // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. - - // If so, then inform the caller that it should first take care of the whole - // message (all blocks with that message id). Shift the m_pCurrBlock pointer - // to the position past the last of them. Then return -1 and set the - // msgno_bitset return reference to the message id that should be dropped as - // a whole. - - // After taking care of that, the caller should immediately call this function again, - // this time possibly in order to find the real data to be sent. - - // if found block is stale - // (This is for messages that have declared TTL - messages that fail to be sent - // before the TTL defined time comes, will be dropped). - if ((p->m_iTTL >= 0) && ((CTimer::getTime() - p->m_ullOriginTime_us) / 1000 > (uint64_t)p->m_iTTL)) - { - int32_t msgno = p->getMsgSeq(); - msglen = 1; - p = p->m_pNext; - bool move = false; - while (msgno == p->getMsgSeq()) - { - if (p == m_pCurrBlock) - move = true; - p = p->m_pNext; - if (move) - m_pCurrBlock = p; - msglen ++; - } - - HLOGC(dlog.Debug, log << "CSndBuffer::readData: due to TTL exceeded, " << msglen << " messages to drop, up to " << msgno); - - // If readData returns -1, then msgno_bitset is understood as a Message ID to drop. - // This means that in this case it should be written by the message sequence value only - // (not the whole 4-byte bitset written at PH_MSGNO). - msgno_bitset = msgno; - return -1; - } - - *data = p->m_pcData; - int readlen = p->m_iLength; - - // XXX Here the value predicted to be applied to PH_MSGNO field is extracted. - // As this function is predicted to extract the data to send as a rexmited packet, - // the packet must be in the form ready to send - so, in case of encryption, - // encrypted, and with all ENC flags already set. So, the first call to send - // the packet originally (the other overload of this function) must set these - // flags. - msgno_bitset = p->m_iMsgNoBitset; - - srctime = - p->m_ullSourceTime_us ? p->m_ullSourceTime_us : - p->m_ullOriginTime_us; - - HLOGC(dlog.Debug, log << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send [REXMIT]"); - - return readlen; + ScopedLock bufferguard(m_BufLock); + const Block* p = m_pFirstBlock; + + // XXX Suboptimal procedure to keep the blocks identifiable + // by sequence number. Consider using some circular buffer. + for (int i = 0; i < offset; ++i) + { + SRT_ASSERT(p); + p = p->m_pNext; + } + + SRT_ASSERT(p); + return p->m_tsRexmitTime; } void CSndBuffer::ackData(int offset) { - CGuard bufferguard(m_BufLock); - - bool move = false; - for (int i = 0; i < offset; ++ i) - { - m_iBytesCount -= m_pFirstBlock->m_iLength; - if (m_pFirstBlock == m_pCurrBlock) - move = true; - m_pFirstBlock = m_pFirstBlock->m_pNext; - } - if (move) - m_pCurrBlock = m_pFirstBlock; - - m_iCount -= offset; - -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - updAvgBufSize(CTimer::getTime()); -#endif + ScopedLock bufferguard(m_BufLock); + + bool move = false; + for (int i = 0; i < offset; ++i) + { + m_iBytesCount -= m_pFirstBlock->m_iLength; + if (m_pFirstBlock == m_pCurrBlock) + move = true; + m_pFirstBlock = m_pFirstBlock->m_pNext; + } + if (move) + m_pCurrBlock = m_pFirstBlock; + + m_iCount -= offset; - CTimer::triggerEvent(); + updAvgBufSize(steady_clock::now()); } int CSndBuffer::getCurrBufSize() const { - return m_iCount; + return m_iCount; } -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - -int CSndBuffer::getAvgBufSize(ref_t r_bytes, ref_t r_tsp) +int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) { - int& bytes = *r_bytes; - int& timespan = *r_tsp; - CGuard bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ + ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ /* update stats in case there was no add/ack activity lately */ - updAvgBufSize(CTimer::getTime()); - - bytes = m_iBytesCountMAvg; - timespan = m_TimespanMAvg; - return(m_iCountMAvg); + updAvgBufSize(steady_clock::now()); + + // Average number of packets and timespan could be small, + // so rounding is beneficial, while for the number of + // bytes in the buffer is a higher value, so rounding can be omitted, + // but probably better to round all three values. + w_bytes = round_val(m_mavg.bytes()); + w_tsp = round_val(m_mavg.timespan_ms()); + return round_val(m_mavg.pkts()); } -void CSndBuffer::updAvgBufSize(uint64_t now) +void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) { - const uint64_t elapsed_ms = (now - m_LastSamplingTime) / 1000; //ms since last sampling - - if ((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 > elapsed_ms) - return; - - if (1000 < elapsed_ms) - { - /* No sampling in last 1 sec, initialize average */ - m_iCountMAvg = getCurrBufSize(Ref(m_iBytesCountMAvg), Ref(m_TimespanMAvg)); - m_LastSamplingTime = now; - } - else //((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 <= elapsed_ms) - { - /* - * weight last average value between -1 sec and last sampling time (LST) - * and new value between last sampling time and now - * |elapsed_ms| - * +----------------------------------+-------+ - * -1 LST 0(now) - */ - int instspan; - int bytescount; - int count = getCurrBufSize(Ref(bytescount), Ref(instspan)); - - HLOGC(dlog.Debug, log << "updAvgBufSize: " << elapsed_ms - << ": " << count << " " << bytescount - << " " << instspan << "ms"); - - m_iCountMAvg = (int)(((count * (1000 - elapsed_ms)) + (count * elapsed_ms)) / 1000); - m_iBytesCountMAvg = (int)(((bytescount * (1000 - elapsed_ms)) + (bytescount * elapsed_ms)) / 1000); - m_TimespanMAvg = (int)(((instspan * (1000 - elapsed_ms)) + (instspan * elapsed_ms)) / 1000); - m_LastSamplingTime = now; - } + if (!m_mavg.isTimeToUpdate(now)) + return; + + int bytes = 0; + int timespan_ms = 0; + const int pkts = getCurrBufSize((bytes), (timespan_ms)); + m_mavg.update(now, pkts, bytes, timespan_ms); } -#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ +int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) +{ + w_bytes = m_iBytesCount; + /* + * Timespan can be less then 1000 us (1 ms) if few packets. + * Also, if there is only one pkt in buffer, the time difference will be 0. + * Therefore, always add 1 ms if not empty. + */ + w_timespan = 0 < m_iCount ? (int) count_milliseconds(m_tsLastOriginTime - m_pFirstBlock->m_tsOriginTime) + 1 : 0; -int CSndBuffer::getCurrBufSize(ref_t bytes, ref_t timespan) + return m_iCount; +} + +CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const { - *bytes = m_iBytesCount; - /* - * Timespan can be less then 1000 us (1 ms) if few packets. - * Also, if there is only one pkt in buffer, the time difference will be 0. - * Therefore, always add 1 ms if not empty. - */ - *timespan = 0 < m_iCount ? int((m_ullLastOriginTime_us - m_pFirstBlock->m_ullOriginTime_us) / 1000) + 1 : 0; - - return m_iCount; + ScopedLock lck(m_BufLock); + SRT_ASSERT(m_pFirstBlock); + if (m_iCount == 0) + return duration(0); + + return tnow - m_pFirstBlock->m_tsOriginTime; } -int CSndBuffer::dropLateData(int &bytes, uint64_t latetime) +int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) { - int dpkts = 0; - int dbytes = 0; - bool move = false; - - CGuard bufferguard(m_BufLock); - for (int i = 0; i < m_iCount && m_pFirstBlock->m_ullOriginTime_us < latetime; ++ i) - { - dpkts++; - dbytes += m_pFirstBlock->m_iLength; - - if (m_pFirstBlock == m_pCurrBlock) move = true; - m_pFirstBlock = m_pFirstBlock->m_pNext; - } - if (move) m_pCurrBlock = m_pFirstBlock; - m_iCount -= dpkts; - - m_iBytesCount -= dbytes; - bytes = dbytes; - -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - updAvgBufSize(CTimer::getTime()); -#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ - -// CTimer::triggerEvent(); - return(dpkts); + int dpkts = 0; + int dbytes = 0; + bool move = false; + int32_t msgno = 0; + + ScopedLock bufferguard(m_BufLock); + for (int i = 0; i < m_iCount && m_pFirstBlock->m_tsOriginTime < too_late_time; ++i) + { + dpkts++; + dbytes += m_pFirstBlock->m_iLength; + msgno = m_pFirstBlock->getMsgSeq(); + + if (m_pFirstBlock == m_pCurrBlock) + move = true; + m_pFirstBlock = m_pFirstBlock->m_pNext; + } + + if (move) + { + m_pCurrBlock = m_pFirstBlock; + } + m_iCount -= dpkts; + + m_iBytesCount -= dbytes; + w_bytes = dbytes; + + // We report the increased number towards the last ever seen + // by the loop, as this last one is the last received. So remained + // (even if "should remain") is the first after the last removed one. + w_first_msgno = ++MsgNo(msgno); + + updAvgBufSize(steady_clock::now()); + + return (dpkts); } void CSndBuffer::increase() { - int unitsize = m_pBuffer->m_iSize; - - // new physical buffer - Buffer* nbuf = NULL; - try - { - nbuf = new Buffer; - nbuf->m_pcData = new char [unitsize * m_iMSS]; - } - catch (...) - { - delete nbuf; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } - nbuf->m_iSize = unitsize; - nbuf->m_pNext = NULL; - - // insert the buffer at the end of the buffer list - Buffer* p = m_pBuffer; - while (p->m_pNext != NULL) - p = p->m_pNext; - p->m_pNext = nbuf; - - // new packet blocks - Block* nblk = NULL; - try - { - nblk = new Block; - } - catch (...) - { - delete nblk; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } - Block* pb = nblk; - for (int i = 1; i < unitsize; ++ i) - { - pb->m_pNext = new Block; - pb = pb->m_pNext; - } - - // insert the new blocks onto the existing one - pb->m_pNext = m_pLastBlock->m_pNext; - m_pLastBlock->m_pNext = nblk; - - pb = nblk; - char* pc = nbuf->m_pcData; - for (int i = 0; i < unitsize; ++ i) - { - pb->m_pcData = pc; - pb = pb->m_pNext; - pc += m_iMSS; - } - - m_iSize += unitsize; - - HLOGC(dlog.Debug, log << "CSndBuffer: BUFFER FULL - adding " << (unitsize*m_iMSS) << " bytes spread to " << unitsize << " blocks" - << " (total size: " << m_iSize << " bytes)"); + int unitsize = m_pBuffer->m_iSize; + // new physical buffer + Buffer* nbuf = NULL; + try + { + nbuf = new Buffer; + nbuf->m_pcData = new char[unitsize * m_iBlockLen]; + } + catch (...) + { + delete nbuf; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + nbuf->m_iSize = unitsize; + nbuf->m_pNext = NULL; + + // insert the buffer at the end of the buffer list + Buffer* p = m_pBuffer; + while (p->m_pNext != NULL) + p = p->m_pNext; + p->m_pNext = nbuf; + + // new packet blocks + Block* nblk = NULL; + try + { + nblk = new Block; + } + catch (...) + { + delete nblk; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + Block* pb = nblk; + for (int i = 1; i < unitsize; ++i) + { + pb->m_pNext = new Block; + pb = pb->m_pNext; + } + + // insert the new blocks onto the existing one + pb->m_pNext = m_pLastBlock->m_pNext; + m_pLastBlock->m_pNext = nblk; + + pb = nblk; + char* pc = nbuf->m_pcData; + for (int i = 0; i < unitsize; ++i) + { + pb->m_pcData = pc; + pb = pb->m_pNext; + pc += m_iBlockLen; + } + + m_iSize += unitsize; + + HLOGC(bslog.Debug, + log << "CSndBuffer: BUFFER FULL - adding " << (unitsize * m_iBlockLen) << " bytes spread to " << unitsize + << " blocks" + << " (total size: " << m_iSize << " bytes)"); } //////////////////////////////////////////////////////////////////////////////// +#if (!ENABLE_NEW_RCVBUFFER) + /* -* RcvBuffer (circular buffer): -* -* |<------------------- m_iSize ----------------------------->| -* | |<--- acked pkts -->|<--- m_iMaxPos --->| | -* | | | | | -* +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ -* | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] -* +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ -* | | | | -* | | \__last pkt received -* | \___ m_iLastAckPos: last ack sent -* \___ m_iStartPos: first message to read -* -* m_pUnit[i]->m_iFlag: 0:free, 1:good, 2:passack, 3:dropped -* -* thread safety: -* m_iStartPos: CUDT::m_RecvLock -* m_iLastAckPos: CUDT::m_AckLock -* m_iMaxPos: none? (modified on add and ack -*/ - - -// XXX Init values moved to in-class. -//const uint32_t CRcvBuffer::TSBPD_WRAP_PERIOD = (30*1000000); //30 seconds (in usec) -//const int CRcvBuffer::TSBPD_DRIFT_MAX_VALUE = 5000; // usec -//const int CRcvBuffer::TSBPD_DRIFT_MAX_SAMPLES = 1000; // ACK-ACK packets -#ifdef SRT_DEBUG_TSBPD_DRIFT -//const int CRcvBuffer::TSBPD_DRIFT_PRT_SAMPLES = 200; // ACK-ACK packets -#endif + * RcvBuffer (circular buffer): + * + * |<------------------- m_iSize ----------------------------->| + * | |<--- acked pkts -->|<--- m_iMaxPos --->| | + * | | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | | | | + * | | \__last pkt received + * | \___ m_iLastAckPos: last ack sent + * \___ m_iStartPos: first message to read + * + * m_pUnit[i]->m_iFlag: 0:free, 1:good, 2:passack, 3:dropped + * + * thread safety: + * m_iStartPos: CUDT::m_RecvLock + * m_iLastAckPos: CUDT::m_AckLock + * m_iMaxPos: none? (modified on add and ack + */ -CRcvBuffer::CRcvBuffer(CUnitQueue* queue, int bufsize_pkts): -m_pUnit(NULL), -m_iSize(bufsize_pkts), -m_pUnitQueue(queue), -m_iStartPos(0), -m_iLastAckPos(0), -m_iMaxPos(0), -m_iNotch(0) -,m_BytesCountLock() -,m_iBytesCount(0) -,m_iAckedPktsCount(0) -,m_iAckedBytesCount(0) -,m_iAvgPayloadSz(7*188) -,m_bTsbPdMode(false) -,m_uTsbPdDelay(0) -,m_ullTsbPdTimeBase(0) -,m_bTsbPdWrapCheck(false) -//,m_iTsbPdDrift(0) -//,m_TsbPdDriftSum(0) -//,m_iTsbPdDriftNbSamples(0) -#ifdef SRT_ENABLE_RCVBUFSZ_MAVG -,m_LastSamplingTime(0) -,m_TimespanMAvg(0) -,m_iCountMAvg(0) -,m_iBytesCountMAvg(0) -#endif +CRcvBuffer::CRcvBuffer(CUnitQueue* queue, int bufsize_pkts) + : m_pUnit(NULL) + , m_iSize(bufsize_pkts) + , m_pUnitQueue(queue) + , m_iStartPos(0) + , m_iLastAckPos(0) + , m_iMaxPos(0) + , m_iNotch(0) + , m_BytesCountLock() + , m_iBytesCount(0) + , m_iAckedPktsCount(0) + , m_iAckedBytesCount(0) + , m_uAvgPayloadSz(7 * 188) { - m_pUnit = new CUnit* [m_iSize]; - for (int i = 0; i < m_iSize; ++ i) - m_pUnit[i] = NULL; + m_pUnit = new CUnit*[m_iSize]; + for (int i = 0; i < m_iSize; ++i) + m_pUnit[i] = NULL; -#ifdef SRT_DEBUG_TSBPD_DRIFT - memset(m_TsbPdDriftHisto100us, 0, sizeof(m_TsbPdDriftHisto100us)); - memset(m_TsbPdDriftHisto1ms, 0, sizeof(m_TsbPdDriftHisto1ms)); -#endif - - pthread_mutex_init(&m_BytesCountLock, NULL); + setupMutex(m_BytesCountLock, "BytesCount"); } CRcvBuffer::~CRcvBuffer() { - for (int i = 0; i < m_iSize; ++ i) - { - if (m_pUnit[i] != NULL) - { - m_pUnitQueue->makeUnitFree(m_pUnit[i]); - } - } + for (int i = 0; i < m_iSize; ++i) + { + if (m_pUnit[i] != NULL) + { + m_pUnitQueue->makeUnitFree(m_pUnit[i]); + } + } - delete [] m_pUnit; + delete[] m_pUnit; - pthread_mutex_destroy(&m_BytesCountLock); + releaseMutex(m_BytesCountLock); } void CRcvBuffer::countBytes(int pkts, int bytes, bool acked) { - /* - * Byte counter changes from both sides (Recv & Ack) of the buffer - * so the higher level lock is not enough for thread safe op. - * - * pkts are... - * added (bytes>0, acked=false), - * acked (bytes>0, acked=true), - * removed (bytes<0, acked=n/a) - */ - CGuard cg(m_BytesCountLock); - - if (!acked) //adding new pkt in RcvBuffer - { - m_iBytesCount += bytes; /* added or removed bytes from rcv buffer */ - if (bytes > 0) /* Assuming one pkt when adding bytes */ - m_iAvgPayloadSz = ((m_iAvgPayloadSz * (100 - 1)) + bytes) / 100; - } - else // acking/removing pkts to/from buffer - { - m_iAckedPktsCount += pkts; /* acked or removed pkts from rcv buffer */ - m_iAckedBytesCount += bytes; /* acked or removed bytes from rcv buffer */ - - if (bytes < 0) m_iBytesCount += bytes; /* removed bytes from rcv buffer */ - } + /* + * Byte counter changes from both sides (Recv & Ack) of the buffer + * so the higher level lock is not enough for thread safe op. + * + * pkts are... + * added (bytes>0, acked=false), + * acked (bytes>0, acked=true), + * removed (bytes<0, acked=n/a) + */ + ScopedLock cg(m_BytesCountLock); + + if (!acked) // adding new pkt in RcvBuffer + { + m_iBytesCount += bytes; /* added or removed bytes from rcv buffer */ + if (bytes > 0) /* Assuming one pkt when adding bytes */ + m_uAvgPayloadSz = ((m_uAvgPayloadSz * (100 - 1)) + bytes) / 100; + } + else // acking/removing pkts to/from buffer + { + m_iAckedPktsCount += pkts; /* acked or removed pkts from rcv buffer */ + m_iAckedBytesCount += bytes; /* acked or removed bytes from rcv buffer */ + + if (bytes < 0) + m_iBytesCount += bytes; /* removed bytes from rcv buffer */ + } } int CRcvBuffer::addData(CUnit* unit, int offset) { - SRT_ASSERT(unit != NULL); - if (offset >= getAvailBufSize()) - return -1; - - const int pos = (m_iLastAckPos + offset) % m_iSize; - if (offset >= m_iMaxPos) - m_iMaxPos = offset + 1; - - if (m_pUnit[pos] != NULL) { - HLOGC(dlog.Debug, log << "addData: unit %" << unit->m_Packet.m_iSeqNo - << " rejected, already exists"); - return -1; - } - m_pUnit[pos] = unit; - countBytes(1, (int) unit->m_Packet.getLength()); - - m_pUnitQueue->makeUnitGood(unit); - - HLOGC(dlog.Debug, log << "addData: unit %" << unit->m_Packet.m_iSeqNo - << " accepted, off=" << offset << " POS=" << pos); - return 0; + SRT_ASSERT(unit != NULL); + if (offset >= getAvailBufSize()) + return -1; + + const int pos = (m_iLastAckPos + offset) % m_iSize; + if (offset >= m_iMaxPos) + m_iMaxPos = offset + 1; + + if (m_pUnit[pos] != NULL) + { + HLOGC(qrlog.Debug, log << "addData: unit %" << unit->m_Packet.m_iSeqNo << " rejected, already exists"); + return -1; + } + m_pUnit[pos] = unit; + countBytes(1, (int)unit->m_Packet.getLength()); + + m_pUnitQueue->makeUnitGood(unit); + + HLOGC(qrlog.Debug, + log << "addData: unit %" << unit->m_Packet.m_iSeqNo << " accepted, off=" << offset << " POS=" << pos); + return 0; } int CRcvBuffer::readBuffer(char* data, int len) { - int p = m_iStartPos; + int p = m_iStartPos; int lastack = m_iLastAckPos; - int rs = len; -#if ENABLE_HEAVY_LOGGING - char* begin = data; -#endif + int rs = len; + IF_HEAVY_LOGGING(char* begin = data); - const uint64_t now = (m_bTsbPdMode ? CTimer::getTime() : uint64_t()); + const bool bTsbPdEnabled = m_tsbpd.isEnabled(); + const steady_clock::time_point now = (bTsbPdEnabled ? steady_clock::now() : steady_clock::time_point()); - HLOGC(dlog.Debug, log << CONID() << "readBuffer: start=" << p << " lastack=" << lastack); + HLOGC(brlog.Debug, log << CONID() << "readBuffer: start=" << p << " lastack=" << lastack); while ((p != lastack) && (rs > 0)) { if (m_pUnit[p] == NULL) { - LOGC(dlog.Error, log << CONID() << " IPE readBuffer on null packet pointer"); + LOGC(brlog.Error, log << CONID() << "IPE readBuffer on null packet pointer"); return -1; } - if (m_bTsbPdMode) + const CPacket& pkt = m_pUnit[p]->m_Packet; + + if (bTsbPdEnabled) + { + HLOGC(brlog.Debug, + log << CONID() << "readBuffer: chk if time2play:" + << " NOW=" << FormatTime(now) + << " PKT TS=" << FormatTime(getPktTsbPdTime(pkt.getMsgTimeStamp()))); + + if ((getPktTsbPdTime(pkt.getMsgTimeStamp()) > now)) + break; /* too early for this unit, return whatever was copied */ + } + + const int pktlen = (int) pkt.getLength(); + const int remain_pktlen = pktlen - m_iNotch; + + const int unitsize = std::min(remain_pktlen, rs); + + HLOGC(brlog.Debug, + log << CONID() << "readBuffer: copying buffer #" << p << " targetpos=" << int(data - begin) + << " sourcepos=" << m_iNotch << " size=" << unitsize << " left=" << (unitsize - rs)); + memcpy((data), pkt.m_pcData + m_iNotch, unitsize); + + data += unitsize; + + if (rs >= remain_pktlen) + { + freeUnitAt(p); + p = shiftFwd(p); + + m_iNotch = 0; + } + else + m_iNotch += rs; + + rs -= unitsize; + } + + /* we removed acked bytes form receive buffer */ + countBytes(-1, -(len - rs), true); + m_iStartPos = p; + + return len - rs; +} + +int CRcvBuffer::readBufferToFile(fstream& ofs, int len) +{ + int p = m_iStartPos; + int lastack = m_iLastAckPos; + int rs = len; + + int32_t trace_seq SRT_ATR_UNUSED = SRT_SEQNO_NONE; + int trace_shift SRT_ATR_UNUSED = -1; + + while ((p != lastack) && (rs > 0)) + { +#if ENABLE_LOGGING + ++trace_shift; +#endif + // Skip empty units. Note that this shouldn't happen + // in case of a file transfer. + if (!m_pUnit[p]) { - HLOGC(dlog.Debug, log << CONID() << "readBuffer: chk if time2play: NOW=" << now << " PKT TS=" << getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp())); - if ((getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()) > now)) - break; /* too early for this unit, return whatever was copied */ + p = shiftFwd(p); + LOGC(brlog.Error, log << "readBufferToFile: IPE: NULL unit found in file transmission, last good %" + << trace_seq << " + " << trace_shift); + continue; } - int unitsize = (int) m_pUnit[p]->m_Packet.getLength() - m_iNotch; - if (unitsize > rs) - unitsize = rs; + const CPacket& pkt = m_pUnit[p]->m_Packet; - HLOGC(dlog.Debug, log << CONID() << "readBuffer: copying buffer #" << p - << " targetpos=" << int(data-begin) << " sourcepos=" << m_iNotch << " size=" << unitsize << " left=" << (unitsize-rs)); - memcpy(data, m_pUnit[p]->m_Packet.m_pcData + m_iNotch, unitsize); - data += unitsize; +#if ENABLE_LOGGING + trace_seq = pkt.getSeqNo(); +#endif + const int pktlen = (int) pkt.getLength(); + const int remain_pktlen = pktlen - m_iNotch; - if ((rs > unitsize) || (rs == int(m_pUnit[p]->m_Packet.getLength()) - m_iNotch)) - { - CUnit* tmp = m_pUnit[p]; - m_pUnit[p] = NULL; - m_pUnitQueue->makeUnitFree(tmp); + const int unitsize = std::min(remain_pktlen, rs); + + ofs.write(pkt.m_pcData + m_iNotch, unitsize); + if (ofs.fail()) + break; - if (++ p == m_iSize) - p = 0; + if (rs >= remain_pktlen) + { + freeUnitAt(p); + p = shiftFwd(p); m_iNotch = 0; } @@ -844,127 +1043,142 @@ int CRcvBuffer::readBuffer(char* data, int len) return len - rs; } -int CRcvBuffer::readBufferToFile(fstream& ofs, int len) +int CRcvBuffer::ackData(int len) { - int p = m_iStartPos; - int lastack = m_iLastAckPos; - int rs = len; - - while ((p != lastack) && (rs > 0)) - { - int unitsize = (int) m_pUnit[p]->m_Packet.getLength() - m_iNotch; - if (unitsize > rs) - unitsize = rs; - - ofs.write(m_pUnit[p]->m_Packet.m_pcData + m_iNotch, unitsize); - if (ofs.fail()) - break; - - if ((rs > unitsize) || (rs == int(m_pUnit[p]->m_Packet.getLength()) - m_iNotch)) - { - CUnit* tmp = m_pUnit[p]; - m_pUnit[p] = NULL; - m_pUnitQueue->makeUnitFree(tmp); - - if (++ p == m_iSize) - p = 0; - - m_iNotch = 0; - } - else - m_iNotch += rs; - - rs -= unitsize; - } - - /* we removed acked bytes form receive buffer */ - countBytes(-1, -(len - rs), true); - m_iStartPos = p; - - return len - rs; + SRT_ASSERT(len < m_iSize); + SRT_ASSERT(len > 0); + int end = shift(m_iLastAckPos, len); + + { + int pkts = 0; + int bytes = 0; + for (int i = m_iLastAckPos; i != end; i = shiftFwd(i)) + { + if (m_pUnit[i] == NULL) + continue; + + pkts++; + bytes += (int)m_pUnit[i]->m_Packet.getLength(); + } + if (pkts > 0) + countBytes(pkts, bytes, true); + } + + HLOGC(brlog.Debug, + log << "ackData: shift by " << len << ", start=" << m_iStartPos << " end=" << m_iLastAckPos << " -> " << end); + + m_iLastAckPos = end; + m_iMaxPos -= len; + if (m_iMaxPos < 0) + m_iMaxPos = 0; + + // Returned value is the distance towards the starting + // position from m_iLastAckPos, which is in sync with CUDT::m_iRcvLastSkipAck. + // This should help determine the sequence number at first read-ready position. + + const int dist = m_iLastAckPos - m_iStartPos; + if (dist < 0) + return dist + m_iSize; + return dist; } -void CRcvBuffer::ackData(int len) +void CRcvBuffer::skipData(int len) { - SRT_ASSERT(len < m_iSize); - SRT_ASSERT(len > 0); - - { - int pkts = 0; - int bytes = 0; - for (int i = m_iLastAckPos, n = (m_iLastAckPos + len) % m_iSize; i != n; i = (i + 1) % m_iSize) - { - if (m_pUnit[i] == NULL) - continue; - - pkts++; - bytes += (int) m_pUnit[i]->m_Packet.getLength(); - } - if (pkts > 0) countBytes(pkts, bytes, true); - } - m_iLastAckPos = (m_iLastAckPos + len) % m_iSize; - m_iMaxPos -= len; - if (m_iMaxPos < 0) - m_iMaxPos = 0; - - CTimer::triggerEvent(); + /* + * Caller need protect both AckLock and RecvLock + * to move both m_iStartPos and m_iLastAckPost + */ + if (m_iStartPos == m_iLastAckPos) + m_iStartPos = (m_iStartPos + len) % m_iSize; + m_iLastAckPos = (m_iLastAckPos + len) % m_iSize; + m_iMaxPos -= len; + if (m_iMaxPos < 0) + m_iMaxPos = 0; } -void CRcvBuffer::skipData(int len) +size_t CRcvBuffer::dropData(int len) { - /* - * Caller need protect both AckLock and RecvLock - * to move both m_iStartPos and m_iLastAckPost - */ - if (m_iStartPos == m_iLastAckPos) - m_iStartPos = (m_iStartPos + len) % m_iSize; - m_iLastAckPos = (m_iLastAckPos + len) % m_iSize; - m_iMaxPos -= len; - if (m_iMaxPos < 0) - m_iMaxPos = 0; + // This function does the same as skipData, although skipData + // should work in the condition of absence of data, so no need + // to force the units in the range to be freed. This function + // works in more general condition where we don't know if there + // are any data in the given range, but want to remove these + // "sequence positions" from the buffer, whether there are data + // at them or not. + + size_t stats_bytes = 0; + + int p = m_iStartPos; + int past_q = shift(p, len); + while (p != past_q) + { + if (m_pUnit[p] && m_pUnit[p]->m_iFlag == CUnit::GOOD) + { + stats_bytes += m_pUnit[p]->m_Packet.getLength(); + freeUnitAt(p); + } + + p = shiftFwd(p); + } + + m_iStartPos = past_q; + return stats_bytes; } -bool CRcvBuffer::getRcvFirstMsg(ref_t r_tsbpdtime, ref_t r_passack, ref_t r_skipseqno, ref_t r_curpktseq) +bool CRcvBuffer::getRcvFirstMsg(steady_clock::time_point& w_tsbpdtime, + bool& w_passack, + int32_t& w_skipseqno, + int32_t& w_curpktseq, + int32_t base_seq) { - int32_t& skipseqno = *r_skipseqno; - bool& passack = *r_passack; - skipseqno = -1; - passack = false; + HLOGC(brlog.Debug, log << "getRcvFirstMsg: base_seq=" << base_seq); + w_skipseqno = SRT_SEQNO_NONE; + w_passack = false; // tsbpdtime will be retrieved by the below call // Returned values: // - tsbpdtime: real time when the packet is ready to play (whether ready to play or not) - // - passack: false (the report concerns a packet with an exactly next sequence) - // - skipseqno == -1: no packets to skip towards the first RTP - // - ppkt: that exactly packet that is reported (for debugging purposes) + // - w_passack: false (the report concerns a packet with an exactly next sequence) + // - w_skipseqno == SRT_SEQNO_NONE: no packets to skip towards the first RTP + // - w_curpktseq: that exactly packet that is reported (for debugging purposes) // - @return: whether the reported packet is ready to play /* Check the acknowledged packets */ - if (getRcvReadyMsg(r_tsbpdtime, r_curpktseq)) + // getRcvReadyMsg returns true if the time to play for the first message + // that larger than base_seq is in the past. + if (getRcvReadyMsg((w_tsbpdtime), (w_curpktseq), -1, base_seq)) { - HLOGC(dlog.Debug, log << "getRcvFirstMsg: ready CONTIG packet: %" << (*r_curpktseq)); + HLOGC(brlog.Debug, log << "getRcvFirstMsg: ready CONTIG packet: %" << w_curpktseq); return true; } - else if (*r_tsbpdtime != 0) + else if (!is_zero(w_tsbpdtime)) { - HLOGC(dlog.Debug, log << "getRcvFirstMsg: no packets found"); + HLOGC(brlog.Debug, log << "getRcvFirstMsg: packets found, but in future"); + // This means that a message next to be played, has been found, + // but the time to play is in future. return false; } - // getRcvReadyMsg returned false and tsbpdtime == 0. + // Falling here means that there are NO PACKETS in the ACK-ed region + // (m_iStartPos - m_iLastAckPos), but we may have something in the + // region (m_iLastAckPos - (m_iLastAckPos+m_iMaxPos)), that is, packets + // that may be separated from the last ACK-ed by lost ones. // Below this line we have only two options: // - m_iMaxPos == 0, which means that no more packets are in the buffer - // - returned: tsbpdtime=0, passack=true, skipseqno=-1, ppkt=0, @return false + // - returned: tsbpdtime=0, w_passack=true, w_skipseqno=SRT_SEQNO_NONE, w_curpktseq=, @return false // - m_iMaxPos > 0, which means that there are packets arrived after a lost packet: - // - returned: tsbpdtime=PKT.TS, passack=true, skipseqno=PKT.SEQ, ppkt=PKT, @return LOCAL(PKT.TS) <= NOW + // - returned: tsbpdtime=PKT.TS, w_passack=true, w_skipseqno=PKT.SEQ, w_curpktseq=PKT, @return LOCAL(PKT.TS) <= + // NOW - /* + /* * No acked packets ready but caller want to know next packet to wait for * Check the not yet acked packets that may be stuck by missing packet(s). */ - bool haslost = false; - *r_tsbpdtime = 0; // redundant, for clarity - passack = true; + bool haslost = false; + int last_ready_pos = -1; + steady_clock::time_point tsbpdtime = steady_clock::time_point(); + w_tsbpdtime = steady_clock::time_point(); + w_passack = true; // XXX SUSPECTED ISSUE with this algorithm: // The above call to getRcvReadyMsg() should report as to whether: @@ -990,37 +1204,51 @@ bool CRcvBuffer::getRcvFirstMsg(ref_t r_tsbpdtime, ref_t r_passa // When done so, the below loop would be completely unnecessary. // Logical description of the below algorithm: - // 1. Check if the VERY FIRST PACKET is valid; if so then: - // - check if it's ready to play, return boolean value that marks it. + // 1. update w_tsbpdtime and w_curpktseq if found one packet ready to play + // - keep check the next packet if still smaller than base_seq + // 2. set w_skipseqno if found packets before w_curpktseq lost + // if no packets larger than base_seq ready to play, return the largest RTP + // else return the first one that larger than base_seq and rady to play - for (int i = m_iLastAckPos, n = (m_iLastAckPos + m_iMaxPos) % m_iSize; i != n; i = (i + 1) % m_iSize) + for (int i = m_iLastAckPos, n = shift(m_iLastAckPos, m_iMaxPos); i != n; i = shiftFwd(i)) { - if ( !m_pUnit[i] - || m_pUnit[i]->m_iFlag != CUnit::GOOD ) + if (!m_pUnit[i] || m_pUnit[i]->m_iFlag != CUnit::GOOD) { /* There are packets in the sequence not received yet */ haslost = true; - HLOGC(dlog.Debug, log << "getRcvFirstMsg: empty hole at *" << i); + HLOGC(brlog.Debug, log << "getRcvFirstMsg: empty hole at *" << i); } else { - /* We got the 1st valid packet */ - *r_tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); - if (*r_tsbpdtime <= CTimer::getTime()) + tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); + /* Packet ready to play */ + if (tsbpdtime <= steady_clock::now()) { - /* Packet ready to play */ + // If the last ready-to-play packet exists, free it. + if (!is_zero(w_tsbpdtime)) { + HLOGC(brlog.Debug, + log << "getRcvFirstMsg: found next ready packet, free last %" + << w_curpktseq << " POS=" << last_ready_pos); + SRT_ASSERT(w_curpktseq != SRT_SEQNO_NONE); + freeUnitAt(last_ready_pos); + } + w_tsbpdtime = tsbpdtime; + w_curpktseq = m_pUnit[i]->m_Packet.m_iSeqNo; + last_ready_pos = i; if (haslost) + w_skipseqno = w_curpktseq; + + if (base_seq != SRT_SEQNO_NONE && CSeqNo::seqcmp(w_curpktseq, base_seq) <= 0) { - /* - * Packet stuck on non-acked side because of missing packets. - * Tell 1st valid packet seqno so caller can skip (drop) the missing packets. - */ - skipseqno = m_pUnit[i]->m_Packet.m_iSeqNo; - *r_curpktseq = skipseqno; + HLOGC(brlog.Debug, + log << "getRcvFirstMsg: found ready packet %" << w_curpktseq + << " but not larger than base_seq, try next"); + continue; } - HLOGC(dlog.Debug, log << "getRcvFirstMsg: found ready packet, nSKIPPED: " - << ((i - m_iLastAckPos + m_iSize) % m_iSize)); + HLOGC(brlog.Debug, + log << "getRcvFirstMsg: found ready packet, nSKIPPED: " + << ((i - m_iLastAckPos + m_iSize) % m_iSize)); // NOTE: if haslost is not set, it means that this is the VERY FIRST // packet, that is, packet currently at pos = m_iLastAckPos. There's no @@ -1029,8 +1257,13 @@ bool CRcvBuffer::getRcvFirstMsg(ref_t r_tsbpdtime, ref_t r_passa // ... return true; } - HLOGC(dlog.Debug, log << "getRcvFirstMsg: found NOT READY packet, nSKIPPED: " - << ((i - m_iLastAckPos + m_iSize) % m_iSize)); + + if (!is_zero(w_tsbpdtime)) { + return true; + } + HLOGC(brlog.Debug, + log << "getRcvFirstMsg: found NOT READY packet, nSKIPPED: " + << ((i - m_iLastAckPos + m_iSize) % m_iSize)); // ... and if this first good packet WASN'T ready to play, THIS HERE RETURNS NOW, TOO, // just states that there's no ready packet to play. // ... @@ -1040,117 +1273,241 @@ bool CRcvBuffer::getRcvFirstMsg(ref_t r_tsbpdtime, ref_t r_passa // the 'haslost' is set, which means that it continues only to find the first valid // packet after stating that the very first packet isn't valid. } - HLOGC(dlog.Debug, log << "getRcvFirstMsg: found NO PACKETS"); + if (!is_zero(w_tsbpdtime)) { + return true; + } + HLOGC(brlog.Debug, log << "getRcvFirstMsg: found NO PACKETS"); return false; } -bool CRcvBuffer::getRcvReadyMsg(ref_t tsbpdtime, ref_t curpktseq) +steady_clock::time_point CRcvBuffer::debugGetDeliveryTime(int offset) +{ + int i; + if (offset > 0) + i = shift(m_iStartPos, offset); + else + i = m_iStartPos; + + CUnit* u = m_pUnit[i]; + if (!u || u->m_iFlag != CUnit::GOOD) + return steady_clock::time_point(); + + return getPktTsbPdTime(u->m_Packet.getMsgTimeStamp()); +} + +int32_t CRcvBuffer::getTopMsgno() const +{ + if (m_iStartPos == m_iLastAckPos) + return SRT_MSGNO_NONE; // No message is waiting + + if (!m_pUnit[m_iStartPos]) + return SRT_MSGNO_NONE; // pity + + return m_pUnit[m_iStartPos]->m_Packet.getMsgSeq(); +} + +bool CRcvBuffer::getRcvReadyMsg(steady_clock::time_point& w_tsbpdtime, int32_t& w_curpktseq, int upto, int base_seq) { - *tsbpdtime = 0; + const bool havelimit = upto != -1; + int end = -1, past_end = -1; + if (havelimit) + { + int stretch = (m_iSize + m_iStartPos - m_iLastAckPos) % m_iSize; + if (upto > stretch) + { + HLOGC(brlog.Debug, log << "position back " << upto << " exceeds stretch " << stretch); + // Do nothing. This position is already gone. + return false; + } + + end = m_iLastAckPos - upto; + if (end < 0) + end += m_iSize; + past_end = shiftFwd(end); // For in-loop comparison + HLOGC(brlog.Debug, log << "getRcvReadyMsg: will read from position " << end); + } + // NOTE: position m_iLastAckPos in the buffer represents the sequence number of + // CUDT::m_iRcvLastSkipAck. Therefore 'upto' contains a positive value that should + // be decreased from m_iLastAckPos to get the position in the buffer that represents + // the sequence number up to which we'd like to read. IF_HEAVY_LOGGING(const char* reason = "NOT RECEIVED"); - for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) + for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = shiftFwd(i)) { + // In case when we want to read only up to given sequence number, stop + // the loop if this number was reached. This number must be extracted from + // the buffer and any following must wait here for "better times". Note + // that the unit that points to the requested sequence must remain in + // the buffer, unless there is no valid packet at that position, in which + // case it is allowed to point to the NEXT sequence towards it, however + // if it does, this cell must remain in the buffer for prospective recovery. + if (havelimit && i == past_end) + break; + bool freeunit = false; /* Skip any invalid skipped/dropped packets */ if (m_pUnit[i] == NULL) { - HLOGC(mglog.Debug, log << "getRcvReadyMsg: POS=" << i - << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) - << " SKIPPED - no unit there"); - if (++ m_iStartPos == m_iSize) - m_iStartPos = 0; + HLOGC(brlog.Debug, + log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) + << " SKIPPED - no unit there"); + m_iStartPos = shiftFwd(m_iStartPos); continue; } - *curpktseq = m_pUnit[i]->m_Packet.getSeqNo(); + w_curpktseq = m_pUnit[i]->m_Packet.getSeqNo(); if (m_pUnit[i]->m_iFlag != CUnit::GOOD) { - HLOGC(mglog.Debug, log << "getRcvReadyMsg: POS=" << i - << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) - << " SKIPPED - unit not good"); + HLOGC(brlog.Debug, + log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) + << " SKIPPED - unit not good"); freeunit = true; } else { - *tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); - int64_t towait = (*tsbpdtime - CTimer::getTime()); - if (towait > 0) + // This does: + // 1. Get the TSBPD time of the unit. Stop and return false if this unit + // is not yet ready to play. + // 2. If it's ready to play, check also if it's decrypted. If not, skip it. + // 3. Check also if it's larger than base_seq, if not, skip it. + // 4. If it's ready to play, decrypted and larger than base, stop and return it. + if (!havelimit) { - HLOGC(mglog.Debug, log << "getRcvReadyMsg: POS=" << i - << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) - << " pkt %" << curpktseq.get() - << " NOT ready to play (only in " << (towait/1000.0) << "ms)"); - return false; - } + w_tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); + const steady_clock::duration towait = (w_tsbpdtime - steady_clock::now()); + if (towait.count() > 0) + { + HLOGC(brlog.Debug, + log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) + << " pkt %" << w_curpktseq << " NOT ready to play (only in " << count_milliseconds(towait) + << "ms)"); + return false; + } - if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) - { - IF_HEAVY_LOGGING(reason = "DECRYPTION FAILED"); - freeunit = true; /* packet not decrypted */ + if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) + { + IF_HEAVY_LOGGING(reason = "DECRYPTION FAILED"); + freeunit = true; /* packet not decrypted */ + } + else if (base_seq != SRT_SEQNO_NONE && CSeqNo::seqcmp(w_curpktseq, base_seq) <= 0) + { + IF_HEAVY_LOGGING(reason = "smaller than base_seq"); + w_tsbpdtime = steady_clock::time_point(); + freeunit = true; + } + else + { + HLOGC(brlog.Debug, + log << "getRcvReadyMsg: POS=" << i << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) + << " pkt %" << w_curpktseq << " ready to play (delayed " << count_milliseconds(towait) + << "ms)"); + return true; + } } + // In this case: + // 1. We don't even look into the packet if this is not the requested sequence. + // All packets that are earlier than the required sequence will be dropped. + // 2. When found the packet with expected sequence number, and the condition for + // good unit is passed, we get the timestamp. + // 3. If the packet is not decrypted, we allow it to be removed + // 4. If we reached the required sequence, and the packet is good, KEEP IT in the buffer, + // and return with the pointer pointing to this very buffer. Only then return true. else { - HLOGC(mglog.Debug, log << "getRcvReadyMsg: POS=" << i - << " +" << ((i - m_iStartPos + m_iSize) % m_iSize) - << " pkt %" << curpktseq.get() - << " ready to play (delayed " << (-towait/1000.0) << "ms)"); - return true; + // We have a limit up to which the reading will be done, + // no matter if the time has come or not - although retrieve it. + if (i == end) + { + HLOGC(brlog.Debug, log << "CAUGHT required seq position " << i); + // We have the packet we need. Extract its data. + w_tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); + + // If we have a decryption failure, allow the unit to be released. + if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) + { + IF_HEAVY_LOGGING(reason = "DECRYPTION FAILED"); + freeunit = true; /* packet not decrypted */ + } + else + { + // Stop here and keep the packet in the buffer, so it will be + // next extracted. + HLOGC(brlog.Debug, + log << "getRcvReadyMsg: packet seq=" << w_curpktseq << " ready for extraction"); + return true; + } + } + else + { + HLOGC(brlog.Debug, log << "SKIPPING position " << i); + // Continue the loop and remove the current packet because + // its sequence number is too old. + freeunit = true; + } } } if (freeunit) { - HLOGC(mglog.Debug, log << "getRcvReadyMsg: POS=" << i << " FREED"); + HLOGC(brlog.Debug, log << "getRcvReadyMsg: POS=" << i << " FREED: " << reason); /* removed skipped, dropped, undecryptable bytes from rcv buffer */ const int rmbytes = (int)m_pUnit[i]->m_Packet.getLength(); countBytes(-1, -rmbytes, true); - CUnit* tmp = m_pUnit[i]; - m_pUnit[i] = NULL; - m_pUnitQueue->makeUnitFree(tmp); - - if (++m_iStartPos == m_iSize) - m_iStartPos = 0; + freeUnitAt(i); + m_iStartPos = shiftFwd(m_iStartPos); } } - HLOGC(mglog.Debug, log << "getRcvReadyMsg: nothing to deliver: " << reason); + HLOGC(brlog.Debug, log << "getRcvReadyMsg: nothing to deliver: " << reason); return false; } - /* -* Return receivable data status (packet timestamp ready to play if TsbPd mode) -* Return playtime (tsbpdtime) of 1st packet in queue, ready to play or not -* -* Return data ready to be received (packet timestamp ready to play if TsbPd mode) -* Using getRcvDataSize() to know if there is something to read as it was widely -* used in the code (core.cpp) is expensive in TsbPD mode, hence this simpler function -* that only check if first packet in queue is ready. -*/ -bool CRcvBuffer::isRcvDataReady(ref_t tsbpdtime, ref_t curpktseq) + * Return receivable data status (packet timestamp_us ready to play if TsbPd mode) + * Return playtime (tsbpdtime) of 1st packet in queue, ready to play or not + * + * Return data ready to be received (packet timestamp_us ready to play if TsbPd mode) + * Using getRcvDataSize() to know if there is something to read as it was widely + * used in the code (core.cpp) is expensive in TsbPD mode, hence this simpler function + * that only check if first packet in queue is ready. + */ +bool CRcvBuffer::isRcvDataReady(steady_clock::time_point& w_tsbpdtime, int32_t& w_curpktseq, int32_t seqdistance) { - *tsbpdtime = 0; + w_tsbpdtime = steady_clock::time_point(); - if (m_bTsbPdMode) + if (m_tsbpd.isEnabled()) { - CPacket* pkt = getRcvReadyPacket(); + const CPacket* pkt = getRcvReadyPacket(seqdistance); if (!pkt) + { + HLOGC(brlog.Debug, log << "isRcvDataReady: packet NOT extracted."); return false; + } /* - * Acknowledged data is available, - * Only say ready if time to deliver. - * Report the timestamp, ready or not. - */ - *curpktseq = pkt->getSeqNo(); - *tsbpdtime = getPktTsbPdTime(pkt->getMsgTimeStamp()); + * Acknowledged data is available, + * Only say ready if time to deliver. + * Report the timestamp, ready or not. + */ + w_curpktseq = pkt->getSeqNo(); + w_tsbpdtime = getPktTsbPdTime(pkt->getMsgTimeStamp()); + + // If seqdistance was passed, then return true no matter what the + // TSBPD time states. + if (seqdistance != -1 || w_tsbpdtime <= steady_clock::now()) + { + HLOGC(brlog.Debug, + log << "isRcvDataReady: packet extracted seqdistance=" << seqdistance + << " TsbPdTime=" << FormatTime(w_tsbpdtime)); + return true; + } - return (*tsbpdtime <= CTimer::getTime()); + HLOGC(brlog.Debug, log << "isRcvDataReady: packet extracted, but NOT READY"); + return false; } return isRcvDataAvailable(); @@ -1158,15 +1515,60 @@ bool CRcvBuffer::isRcvDataReady(ref_t tsbpdtime, ref_t curpkt // XXX This function may be called only after checking // if m_bTsbPdMode. -CPacket* CRcvBuffer::getRcvReadyPacket() +CPacket* CRcvBuffer::getRcvReadyPacket(int32_t seqdistance) { - for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) + // If asked for readiness of a packet at given sequence distance + // (that is, we need to extract the packet with given sequence number), + // only check if this cell is occupied in the buffer, and if so, + // if it's occupied with a "good" unit. That's all. It doesn't + // matter whether it's ready to play. + if (seqdistance != -1) + { + // Note: seqdistance is the value to to go BACKWARDS from m_iLastAckPos, + // which is the position that is in sync with CUDT::m_iRcvLastSkipAck. This + // position is the sequence number of a packet that is NOT received, but it's + // expected to be received as next. So the minimum value of seqdistance is 1. + + // SANITY CHECK + if (seqdistance == 0) + { + LOGC(brlog.Fatal, log << "IPE: trying to extract packet past the last ACK-ed!"); + return 0; + } + + if (seqdistance > getRcvDataSize()) + { + HLOGC(brlog.Debug, + log << "getRcvReadyPacket: Sequence offset=" << seqdistance + << " is in the past (start=" << m_iStartPos << " end=" << m_iLastAckPos << ")"); + return 0; + } + + int i = shift(m_iLastAckPos, -seqdistance); + if (m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD) + { + HLOGC(brlog.Debug, log << "getRcvReadyPacket: FOUND PACKET %" << m_pUnit[i]->m_Packet.getSeqNo()); + return &m_pUnit[i]->m_Packet; + } + + HLOGC(brlog.Debug, log << "getRcvReadyPacket: Sequence offset=" << seqdistance << " IS NOT RECEIVED."); + return 0; + } + + IF_HEAVY_LOGGING(int nskipped = 0); + for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = shiftFwd(i)) { - /* + /* * Skip missing packets that did not arrive in time. */ - if ( m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD ) + if (m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD) + { + HLOGC(brlog.Debug, + log << "getRcvReadyPacket: Found next packet seq=%" << m_pUnit[i]->m_Packet.getSeqNo() << " (" + << nskipped << " empty cells skipped)"); return &m_pUnit[i]->m_Packet; + } + IF_HEAVY_LOGGING(++nskipped); } return 0; @@ -1175,18 +1577,18 @@ CPacket* CRcvBuffer::getRcvReadyPacket() #if ENABLE_HEAVY_LOGGING // This function is for debug purposes only and it's called only // from within HLOG* macros. -void CRcvBuffer::reportBufferStats() +void CRcvBuffer::reportBufferStats() const { - int nmissing = 0; - int32_t low_seq= -1, high_seq = -1; + int nmissing = 0; + int32_t low_seq = SRT_SEQNO_NONE, high_seq = SRT_SEQNO_NONE; int32_t low_ts = 0, high_ts = 0; for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) { - if ( m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD ) + if (m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD) { low_seq = m_pUnit[i]->m_Packet.m_iSeqNo; - low_ts = m_pUnit[i]->m_Packet.m_iTimeStamp; + low_ts = m_pUnit[i]->m_Packet.m_iTimeStamp; break; } ++nmissing; @@ -1196,7 +1598,7 @@ void CRcvBuffer::reportBufferStats() int n = m_iLastAckPos; if (m_pUnit[n] && m_pUnit[n]->m_iFlag == CUnit::GOOD) { - high_ts = m_pUnit[n]->m_Packet.m_iTimeStamp; + high_ts = m_pUnit[n]->m_Packet.m_iTimeStamp; high_seq = m_pUnit[n]->m_Packet.m_iSeqNo; } else @@ -1216,41 +1618,44 @@ void CRcvBuffer::reportBufferStats() uint64_t lower_time = low_ts; if (lower_time > upper_time) - upper_time += uint64_t(CPacket::MAX_TIMESTAMP)+1; + upper_time += uint64_t(CPacket::MAX_TIMESTAMP) + 1; int32_t timespan = upper_time - lower_time; - int seqspan = 0; - if (low_seq != -1 && high_seq != -1) + int seqspan = 0; + if (low_seq != SRT_SEQNO_NONE && high_seq != SRT_SEQNO_NONE) { seqspan = CSeqNo::seqoff(low_seq, high_seq); } - LOGC(dlog.Debug, log << "RCV BUF STATS: seqspan=%(" << low_seq << "-" << high_seq << ":" << seqspan << ") missing=" << nmissing << "pkts"); - LOGC(dlog.Debug, log << "RCV BUF STATS: timespan=" << timespan << "us (lo=" << FormatTime(lower_time) << " hi=" << FormatTime(upper_time) << ")"); + LOGC(brlog.Debug, + log << "RCV BUF STATS: seqspan=%(" << low_seq << "-" << high_seq << ":" << seqspan << ") missing=" << nmissing + << "pkts"); + LOGC(brlog.Debug, + log << "RCV BUF STATS: timespan=" << timespan << "us (lo=" << lower_time << " hi=" << upper_time << ")"); } #endif // ENABLE_HEAVY_LOGGING bool CRcvBuffer::isRcvDataReady() { - uint64_t tsbpdtime; - int32_t seq; + steady_clock::time_point tsbpdtime; + int32_t seq; - return isRcvDataReady(Ref(tsbpdtime), Ref(seq)); + return isRcvDataReady((tsbpdtime), (seq), -1); } int CRcvBuffer::getAvailBufSize() const { - // One slot must be empty in order to tell the difference between "empty buffer" and "full buffer" - return m_iSize - getRcvDataSize() - 1; + // One slot must be empty in order to tell the difference between "empty buffer" and "full buffer" + return m_iSize - getRcvDataSize() - 1; } int CRcvBuffer::getRcvDataSize() const { - if (m_iLastAckPos >= m_iStartPos) - return m_iLastAckPos - m_iStartPos; + if (m_iLastAckPos >= m_iStartPos) + return m_iLastAckPos - m_iStartPos; - return m_iSize + m_iLastAckPos - m_iStartPos; + return m_iSize + m_iLastAckPos - m_iStartPos; } int CRcvBuffer::debugGetSize() const @@ -1268,424 +1673,315 @@ int CRcvBuffer::debugGetSize() const return size; } - -bool CRcvBuffer::empty() const -{ - // This will not always return the intended value, - // that is, it may return false when the buffer really is - // empty - but it will return true then in one of next calls. - // This function will be always called again at some point - // if it returned false, and on true the connection - // is going to be broken - so this behavior is acceptable. - return m_iStartPos == m_iLastAckPos; -} - - -#ifdef SRT_ENABLE_RCVBUFSZ_MAVG /* Return moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ -int CRcvBuffer::getRcvAvgDataSize(int &bytes, int ×pan) +int CRcvBuffer::getRcvAvgDataSize(int& bytes, int& timespan) { - timespan = m_TimespanMAvg; - bytes = m_iBytesCountMAvg; - return(m_iCountMAvg); + // Average number of packets and timespan could be small, + // so rounding is beneficial, while for the number of + // bytes in the buffer is a higher value, so rounding can be omitted, + // but probably better to round all three values. + timespan = round_val(m_mavg.timespan_ms()); + bytes = round_val(m_mavg.bytes()); + return round_val(m_mavg.pkts()); } /* Update moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ -void CRcvBuffer::updRcvAvgDataSize(uint64_t now) +void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) { - const uint64_t elapsed_ms = (now - m_LastSamplingTime) / 1000; //ms since last sampling - - if ((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 > elapsed_ms) - return; /* Last sampling too recent, skip */ - - if (1000 < elapsed_ms) - { - /* No sampling in last 1 sec, initialize/reset moving average */ - m_iCountMAvg = getRcvDataSize(m_iBytesCountMAvg, m_TimespanMAvg); - m_LastSamplingTime = now; - - HLOGC(dlog.Debug, log << "getRcvDataSize: " << m_iCountMAvg << " " << m_iBytesCountMAvg - << " " << m_TimespanMAvg << " ms elapsed_ms: " << elapsed_ms << " ms"); - } - else if ((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 <= elapsed_ms) - { - /* - * Weight last average value between -1 sec and last sampling time (LST) - * and new value between last sampling time and now - * |elapsed_ms| - * +----------------------------------+-------+ - * -1 LST 0(now) - */ - int instspan; - int bytescount; - int count = getRcvDataSize(bytescount, instspan); - - m_iCountMAvg = (int)(((count * (1000 - elapsed_ms)) + (count * elapsed_ms)) / 1000); - m_iBytesCountMAvg = (int)(((bytescount * (1000 - elapsed_ms)) + (bytescount * elapsed_ms)) / 1000); - m_TimespanMAvg = (int)(((instspan * (1000 - elapsed_ms)) + (instspan * elapsed_ms)) / 1000); - m_LastSamplingTime = now; - - HLOGC(dlog.Debug, log << "getRcvDataSize: " << count << " " << bytescount << " " << instspan - << " ms elapsed_ms: " << elapsed_ms << " ms"); - } + if (!m_mavg.isTimeToUpdate(now)) + return; + + int bytes = 0; + int timespan_ms = 0; + const int pkts = getRcvDataSize(bytes, timespan_ms); + m_mavg.update(now, pkts, bytes, timespan_ms); } -#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ /* Return acked data pkts, bytes, and timespan (ms) of the receive buffer */ -int CRcvBuffer::getRcvDataSize(int &bytes, int ×pan) +int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) { - timespan = 0; - if (m_bTsbPdMode) - { - // Get a valid startpos. - // Skip invalid entries in the beginning, if any. - int startpos = m_iStartPos; - for (; startpos != m_iLastAckPos; startpos = (startpos + 1) % m_iSize) - { - if ((NULL != m_pUnit[startpos]) && (CUnit::GOOD == m_pUnit[startpos]->m_iFlag)) - break; - } - - int endpos = m_iLastAckPos; - - if (m_iLastAckPos != startpos) - { - /* - * |<--- DataSpan ---->|<- m_iMaxPos ->| - * +---+---+---+---+---+---+---+---+---+---+---+--- - * | | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | | m_pUnits[] - * +---+---+---+---+---+---+---+---+---+---+---+--- - * | | - * \_ m_iStartPos \_ m_iLastAckPos - * - * m_pUnits[startpos] shall be valid (->m_iFlag==CUnit::GOOD). - * If m_pUnits[m_iLastAckPos-1] is not valid (NULL or ->m_iFlag!=CUnit::GOOD), - * it means m_pUnits[m_iLastAckPos] is valid since a valid unit is needed to skip. - * Favor m_pUnits[m_iLastAckPos] if valid over [m_iLastAckPos-1] to include the whole acked interval. - */ - if ((m_iMaxPos <= 0) - || (!m_pUnit[m_iLastAckPos]) - || (m_pUnit[m_iLastAckPos]->m_iFlag != CUnit::GOOD)) - { - endpos = (m_iLastAckPos == 0 ? m_iSize - 1 : m_iLastAckPos - 1); - } - - if ((NULL != m_pUnit[endpos]) && (NULL != m_pUnit[startpos])) - { - const uint64_t startstamp = getPktTsbPdTime(m_pUnit[startpos]->m_Packet.getMsgTimeStamp()); - const uint64_t endstamp = getPktTsbPdTime(m_pUnit[endpos]->m_Packet.getMsgTimeStamp()); - /* - * There are sampling conditions where spantime is < 0 (big unsigned value). - * It has been observed after changing the SRT latency from 450 to 200 on the sender. - * - * Possible packet order corruption when dropping packet, - * cause by bad thread protection when adding packet in queue - * was later discovered and fixed. Security below kept. - * - * DateTime RecvRate LostRate DropRate AvailBw RTT RecvBufs PdDelay - * 2014-12-08T15:04:25-0500 4712 110 0 96509 33.710 393 450 - * 2014-12-08T15:04:35-0500 4512 95 0 107771 33.493 1496542976 200 - * 2014-12-08T15:04:40-0500 4213 106 3 107352 53.657 9499425 200 - * 2014-12-08T15:04:45-0500 4575 104 0 102194 53.614 59666 200 - * 2014-12-08T15:04:50-0500 4475 124 0 100543 53.526 505 200 - */ - if (endstamp > startstamp) - timespan = (int)((endstamp - startstamp) / 1000); - } - /* - * Timespan can be less then 1000 us (1 ms) if few packets. - * Also, if there is only one pkt in buffer, the time difference will be 0. - * Therefore, always add 1 ms if not empty. - */ - if (0 < m_iAckedPktsCount) - timespan += 1; - } - } - HLOGF(dlog.Debug, "getRcvDataSize: %6d %6d %6d ms\n", m_iAckedPktsCount, m_iAckedBytesCount, timespan); - bytes = m_iAckedBytesCount; - return m_iAckedPktsCount; -} + timespan = 0; + if (m_tsbpd.isEnabled()) + { + // Get a valid startpos. + // Skip invalid entries in the beginning, if any. + int startpos = m_iStartPos; + for (; startpos != m_iLastAckPos; startpos = shiftFwd(startpos)) + { + if ((NULL != m_pUnit[startpos]) && (CUnit::GOOD == m_pUnit[startpos]->m_iFlag)) + break; + } -int CRcvBuffer::getRcvAvgPayloadSize() const -{ - return m_iAvgPayloadSz; + int endpos = m_iLastAckPos; + + if (m_iLastAckPos != startpos) + { + /* + * |<--- DataSpan ---->|<- m_iMaxPos ->| + * +---+---+---+---+---+---+---+---+---+---+---+--- + * | | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | | m_pUnits[] + * +---+---+---+---+---+---+---+---+---+---+---+--- + * | | + * \_ m_iStartPos \_ m_iLastAckPos + * + * m_pUnits[startpos] shall be valid (->m_iFlag==CUnit::GOOD). + * If m_pUnits[m_iLastAckPos-1] is not valid (NULL or ->m_iFlag!=CUnit::GOOD), + * it means m_pUnits[m_iLastAckPos] is valid since a valid unit is needed to skip. + * Favor m_pUnits[m_iLastAckPos] if valid over [m_iLastAckPos-1] to include the whole acked interval. + */ + if ((m_iMaxPos <= 0) || (!m_pUnit[m_iLastAckPos]) || (m_pUnit[m_iLastAckPos]->m_iFlag != CUnit::GOOD)) + { + endpos = (m_iLastAckPos == 0 ? m_iSize - 1 : m_iLastAckPos - 1); + } + + if ((NULL != m_pUnit[endpos]) && (NULL != m_pUnit[startpos])) + { + const steady_clock::time_point startstamp = + getPktTsbPdTime(m_pUnit[startpos]->m_Packet.getMsgTimeStamp()); + const steady_clock::time_point endstamp = getPktTsbPdTime(m_pUnit[endpos]->m_Packet.getMsgTimeStamp()); + /* + * There are sampling conditions where spantime is < 0 (big unsigned value). + * It has been observed after changing the SRT latency from 450 to 200 on the sender. + * + * Possible packet order corruption when dropping packet, + * cause by bad thread protection when adding packet in queue + * was later discovered and fixed. Security below kept. + * + * DateTime RecvRate LostRate DropRate AvailBw RTT RecvBufs PdDelay + * 2014-12-08T15:04:25-0500 4712 110 0 96509 33.710 393 450 + * 2014-12-08T15:04:35-0500 4512 95 0 107771 33.493 1496542976 200 + * 2014-12-08T15:04:40-0500 4213 106 3 107352 53.657 9499425 200 + * 2014-12-08T15:04:45-0500 4575 104 0 102194 53.614 59666 200 + * 2014-12-08T15:04:50-0500 4475 124 0 100543 53.526 505 200 + */ + if (endstamp > startstamp) + timespan = count_milliseconds(endstamp - startstamp); + } + /* + * Timespan can be less then 1000 us (1 ms) if few packets. + * Also, if there is only one pkt in buffer, the time difference will be 0. + * Therefore, always add 1 ms if not empty. + */ + if (0 < m_iAckedPktsCount) + timespan += 1; + } + } + HLOGF(brlog.Debug, "getRcvDataSize: %6d %6d %6d ms\n", m_iAckedPktsCount, m_iAckedBytesCount, timespan); + bytes = m_iAckedBytesCount; + return m_iAckedPktsCount; } -void CRcvBuffer::dropMsg(int32_t msgno, bool using_rexmit_flag) +unsigned CRcvBuffer::getRcvAvgPayloadSize() const { - for (int i = m_iStartPos, n = (m_iLastAckPos + m_iMaxPos) % m_iSize; i != n; i = (i + 1) % m_iSize) - if ((m_pUnit[i] != NULL) - && (m_pUnit[i]->m_Packet.getMsgSeq(using_rexmit_flag) == msgno)) - m_pUnit[i]->m_iFlag = CUnit::DROPPED; + return m_uAvgPayloadSz; } -uint64_t CRcvBuffer::getTsbPdTimeBase(uint32_t timestamp_us) +CRcvBuffer::ReadingState CRcvBuffer::debugGetReadingState() const { - /* - * Packet timestamps wrap around every 01h11m35s (32-bit in usec) - * When added to the peer start time (base time), - * wrapped around timestamps don't provide a valid local packet delevery time. - * - * A wrap check period starts 30 seconds before the wrap point. - * In this period, timestamps smaller than 30 seconds are considered to have wrapped around (then adjusted). - * The wrap check period ends 30 seconds after the wrap point, afterwhich time base has been adjusted. - */ - uint64_t carryover = 0; - - // This function should generally return the timebase for the given timestamp_us. - // It's assumed that the timestamp_us, for which this function is being called, - // is received as monotonic clock. This function then traces the changes in the - // timestamps passed as argument and catches the moment when the 64-bit timebase - // should be increased by a "segment length" (MAX_TIMESTAMP+1). - - // The checks will be provided for the following split: - // [INITIAL30][FOLLOWING30]....[LAST30] <-- == CPacket::MAX_TIMESTAMP - // - // The following actions should be taken: - // 1. Check if this is [LAST30]. If so, ENTER TSBPD-wrap-check state - // 2. Then, it should turn into [INITIAL30] at some point. If so, use carryover MAX+1. - // 3. Then it should switch to [FOLLOWING30]. If this is detected, - // - EXIT TSBPD-wrap-check state - // - save the carryover as the current time base. + ReadingState readstate; - if (m_bTsbPdWrapCheck) + readstate.iNumAcknowledged = 0; + readstate.iNumUnacknowledged = m_iMaxPos; + + if ((NULL != m_pUnit[m_iStartPos]) && (m_pUnit[m_iStartPos]->m_iFlag == CUnit::GOOD)) { - // Wrap check period. + if (m_tsbpd.isEnabled()) + readstate.tsStart = m_tsbpd.getPktTsbPdTime(m_pUnit[m_iStartPos]->m_Packet.getMsgTimeStamp()); - if (timestamp_us < TSBPD_WRAP_PERIOD) - { - carryover = uint64_t(CPacket::MAX_TIMESTAMP) + 1; - } - // - else if ((timestamp_us >= TSBPD_WRAP_PERIOD) - && (timestamp_us <= (TSBPD_WRAP_PERIOD * 2))) - { - /* Exiting wrap check period (if for packet delivery head) */ - m_bTsbPdWrapCheck = false; - m_ullTsbPdTimeBase += uint64_t(CPacket::MAX_TIMESTAMP) + 1; - tslog.Debug("tsbpd wrap period ends"); - } + readstate.iNumAcknowledged = m_iLastAckPos > m_iStartPos + ? m_iLastAckPos - m_iStartPos + : m_iLastAckPos + (m_iSize - m_iStartPos); } - // Check if timestamp_us is in the last 30 seconds before reaching the MAX_TIMESTAMP. - else if (timestamp_us > (CPacket::MAX_TIMESTAMP - TSBPD_WRAP_PERIOD)) + + // All further stats are valid if TSBPD is enabled. + if (!m_tsbpd.isEnabled()) + return readstate; + + // m_iLastAckPos points to the first unacknowledged packet + const int iLastAckPos = (m_iLastAckPos - 1) % m_iSize; + if (m_iLastAckPos != m_iStartPos && (NULL != m_pUnit[iLastAckPos]) && (m_pUnit[iLastAckPos]->m_iFlag == CUnit::GOOD)) { - /* Approching wrap around point, start wrap check period (if for packet delivery head) */ - m_bTsbPdWrapCheck = true; - tslog.Debug("tsbpd wrap period begins"); + readstate.tsLastAck = m_tsbpd.getPktTsbPdTime(m_pUnit[iLastAckPos]->m_Packet.getMsgTimeStamp()); } - return (m_ullTsbPdTimeBase + carryover); -} + const int iEndPos = (m_iLastAckPos + m_iMaxPos - 1) % m_iSize; + if (m_iMaxPos == 0) + { + readstate.tsEnd = readstate.tsLastAck; + } + else if ((NULL != m_pUnit[iEndPos]) && (m_pUnit[iEndPos]->m_iFlag == CUnit::GOOD)) + { + readstate.tsEnd = m_tsbpd.getPktTsbPdTime(m_pUnit[iEndPos]->m_Packet.getMsgTimeStamp()); + } -uint64_t CRcvBuffer::getPktTsbPdTime(uint32_t timestamp) -{ - return(getTsbPdTimeBase(timestamp) + m_uTsbPdDelay + timestamp + m_DriftTracer.drift()); + return readstate; } -int CRcvBuffer::setRcvTsbPdMode(uint64_t timebase, uint32_t delay) +string CRcvBuffer::strFullnessState(const time_point& tsNow) const { - m_bTsbPdMode = true; - m_bTsbPdWrapCheck = false; - - // Timebase passed here comes is calculated as: - // >>> CTimer::getTime() - ctrlpkt->m_iTimeStamp - // where ctrlpkt is the packet with SRT_CMD_HSREQ message. - // - // This function is called in the HSREQ reception handler only. - m_ullTsbPdTimeBase = timebase; - // XXX Seems like this may not work correctly. - // At least this solution this way won't work with application-supplied - // timestamps. For that case the timestamps should be taken exclusively - // from the data packets because in case of application-supplied timestamps - // they come from completely different server and undergo different rules - // of network latency and drift. - m_uTsbPdDelay = delay; - return 0; -} + const ReadingState bufstate = debugGetReadingState(); + stringstream ss; -#ifdef SRT_DEBUG_TSBPD_DRIFT -void CRcvBuffer::printDriftHistogram(int64_t iDrift) -{ - /* - * Build histogram of drift values - * First line (ms): <=-10.0 -9.0 ... -1.0 - 0.0 + 1.0 ... 9.0 >=10.0 - * Second line (ms): -0.9 ... -0.1 - 0.0 + 0.1 ... 0.9 - * 0 0 0 0 0 0 0 0 0 0 - 0 + 0 0 0 1 0 0 0 0 0 0 - * 0 0 0 0 0 0 0 0 0 - 0 + 0 0 0 0 0 0 0 0 0 - */ - iDrift /= 100; // uSec to 100 uSec (0.1ms) - if (-10 < iDrift && iDrift < 10) - { - /* Fill 100us histogram -900 .. 900 us 100 us increments */ - m_TsbPdDriftHisto100us[10 + iDrift]++; - } - else + ss << "Space avail " << getAvailBufSize() << "/" << m_iSize; + ss << " pkts. Packets ACKed: " << bufstate.iNumAcknowledged; + if (!is_zero(bufstate.tsStart) && !is_zero(bufstate.tsLastAck)) { - /* Fill 1ms histogram <=-10.0, -9.0 .. 9.0, >=10.0 ms in 1 ms increments */ - iDrift /= 10; // 100uSec to 1ms - if (-10 < iDrift && iDrift < 10) m_TsbPdDriftHisto1ms[10 + iDrift]++; - else if (iDrift <= -10) m_TsbPdDriftHisto1ms[0]++; - else m_TsbPdDriftHisto1ms[20]++; + ss << " (TSBPD ready in "; + ss << count_milliseconds(bufstate.tsStart - tsNow); + ss << " : "; + ss << count_milliseconds(bufstate.tsLastAck - tsNow); + ss << " ms)"; } - if ((m_iTsbPdDriftNbSamples % TSBPD_DRIFT_PRT_SAMPLES) == 0) + ss << ", not ACKed: " << bufstate.iNumUnacknowledged; + if (!is_zero(bufstate.tsStart) && !is_zero(bufstate.tsEnd)) { - int *histo = m_TsbPdDriftHisto1ms; + ss << ", timespan "; + ss << count_milliseconds(bufstate.tsEnd - bufstate.tsStart); + ss << " ms"; + } - fprintf(stderr, "%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d - %4d + ", - histo[0],histo[1],histo[2],histo[3],histo[4], - histo[5],histo[6],histo[7],histo[8],histo[9],histo[10]); - fprintf(stderr, "%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n", - histo[11],histo[12],histo[13],histo[14],histo[15], - histo[16],histo[17],histo[18],histo[19],histo[20]); + ss << ". " SRT_SYNC_CLOCK_STR " drift " << getDrift() / 1000 << " ms."; + return ss.str(); +} - histo = m_TsbPdDriftHisto100us; - fprintf(stderr, " %4d %4d %4d %4d %4d %4d %4d %4d %4d - %4d + ", - histo[1],histo[2],histo[3],histo[4],histo[5], - histo[6],histo[7],histo[8],histo[9],histo[10]); - fprintf(stderr, "%4d %4d %4d %4d %4d %4d %4d %4d %4d\n", - histo[11],histo[12],histo[13],histo[14],histo[15], - histo[16],histo[17],histo[18],histo[19]); - } +void CRcvBuffer::dropMsg(int32_t msgno, bool using_rexmit_flag) +{ + for (int i = m_iStartPos, n = shift(m_iLastAckPos, m_iMaxPos); i != n; i = shiftFwd(i)) + if ((m_pUnit[i] != NULL) && (m_pUnit[i]->m_Packet.getMsgSeq(using_rexmit_flag) == msgno)) + m_pUnit[i]->m_iFlag = CUnit::DROPPED; } -void CRcvBuffer::printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg) +void CRcvBuffer::applyGroupTime(const steady_clock::time_point& timebase, + bool wrp, + uint32_t delay, + const steady_clock::duration& udrift) { - char szTime[32] = {}; - uint64_t now = CTimer::getTime(); - time_t tnow = (time_t)(now/1000000); - strftime(szTime, sizeof(szTime), "%H:%M:%S", localtime(&tnow)); - fprintf(stderr, "%s.%03d: tsbpd offset=%d drift=%d usec\n", - szTime, (int)((now%1000000)/1000), tsbPdOffset, tsbPdDriftAvg); - memset(m_TsbPdDriftHisto100us, 0, sizeof(m_TsbPdDriftHisto100us)); - memset(m_TsbPdDriftHisto1ms, 0, sizeof(m_TsbPdDriftHisto1ms)); + m_tsbpd.applyGroupTime(timebase, wrp, delay, udrift); } -#endif /* SRT_DEBUG_TSBPD_DRIFT */ -void CRcvBuffer::addRcvTsbPdDriftSample(uint32_t timestamp, pthread_mutex_t& mutex_to_lock) +void CRcvBuffer::applyGroupDrift(const steady_clock::time_point& timebase, + bool wrp, + const steady_clock::duration& udrift) { - if (!m_bTsbPdMode) // Not checked unless in TSBPD mode - return; - /* - * TsbPD time drift correction - * TsbPD time slowly drift over long period depleting decoder buffer or raising latency - * Re-evaluate the time adjustment value using a receiver control packet (ACK-ACK). - * ACK-ACK timestamp is RTT/2 ago (in sender's time base) - * Data packet have origin time stamp which is older when retransmitted so not suitable for this. - * - * Every TSBPD_DRIFT_MAX_SAMPLES packets, the average drift is calculated - * if -TSBPD_DRIFT_MAX_VALUE < avgTsbPdDrift < TSBPD_DRIFT_MAX_VALUE uSec, pass drift value to RcvBuffer to adjust delevery time. - * if outside this range, adjust this->TsbPdTimeOffset and RcvBuffer->TsbPdTimeBase by +-TSBPD_DRIFT_MAX_VALUE uSec - * to maintain TsbPdDrift values in reasonable range (-5ms .. +5ms). - */ + m_tsbpd.applyGroupDrift(timebase, wrp, udrift); +} - // Note important thing: this function is being called _EXCLUSIVELY_ in the handler - // of UMSG_ACKACK command reception. This means that the timestamp used here comes - // from the CONTROL domain, not DATA domain (timestamps from DATA domain may be - // either schedule time or a time supplied by the application). +void CRcvBuffer::getInternalTimeBase(steady_clock::time_point& w_timebase, bool& w_wrp, steady_clock::duration& w_udrift) +{ + return m_tsbpd.getInternalTimeBase(w_timebase, w_wrp, w_udrift); +} - int64_t iDrift = CTimer::getTime() - (getTsbPdTimeBase(timestamp) + timestamp); +steady_clock::time_point CRcvBuffer::getPktTsbPdTime(uint32_t usPktTimestamp) +{ + // Updating TSBPD time here is not very accurate and prevents from making the function constant. + // For now preserving the existing behavior. + m_tsbpd.updateTsbPdTimeBase(usPktTimestamp); + return m_tsbpd.getPktTsbPdTime(usPktTimestamp); +} - CGuard::enterCS(mutex_to_lock); +void CRcvBuffer::setRcvTsbPdMode(const steady_clock::time_point& timebase, const steady_clock::duration& delay) +{ + const bool no_wrap_check = false; + m_tsbpd.setTsbPdMode(timebase, no_wrap_check, delay); +} - bool updated = m_DriftTracer.update(iDrift); +bool CRcvBuffer::addRcvTsbPdDriftSample(uint32_t timestamp_us, const time_point& tsPktArrival, int rtt) +{ + return m_tsbpd.addDriftSample(timestamp_us, tsPktArrival, rtt); +} -#ifdef SRT_DEBUG_TSBPD_DRIFT - printDriftHistogram(iDrift); -#endif /* SRT_DEBUG_TSBPD_DRIFT */ +int CRcvBuffer::readMsg(char* data, int len) +{ + SRT_MSGCTRL dummy = srt_msgctrl_default; + return readMsg(data, len, (dummy), -1); +} - if ( updated ) - { -#ifdef SRT_DEBUG_TSBPD_DRIFT - printDriftOffset(m_DriftTracer.overdrift(), m_DriftTracer.drift()); -#endif /* SRT_DEBUG_TSBPD_DRIFT */ +// NOTE: The order of ref-arguments is odd because: +// - data and len shall be close to one another +// - upto is last because it's a kind of unusual argument that has a default value +int CRcvBuffer::readMsg(char* data, int len, SRT_MSGCTRL& w_msgctl, int upto) +{ + int p = -1, q = -1; + bool passack; -#if ENABLE_HEAVY_LOGGING - uint64_t oldbase = m_ullTsbPdTimeBase; -#endif - m_ullTsbPdTimeBase += m_DriftTracer.overdrift(); + bool empty = accessMsg((p), (q), (passack), (w_msgctl.srctime), upto); + if (empty) + return 0; - HLOGC(dlog.Debug, log << "DRIFT=" << (iDrift/1000.0) << "ms AVG=" - << (m_DriftTracer.drift()/1000.0) << "ms, TB: " - << FormatTime(oldbase) << " UPDATED TO: " << FormatTime(m_ullTsbPdTimeBase)); - } - else - { - HLOGC(dlog.Debug, log << "DRIFT=" << (iDrift/1000.0) << "ms TB REMAINS: " << FormatTime(m_ullTsbPdTimeBase)); - } + // This should happen just once. By 'empty' condition + // we have a guarantee that m_pUnit[p] exists and is valid. + CPacket& pkt1 = m_pUnit[p]->m_Packet; - CGuard::leaveCS(mutex_to_lock); + // This returns the sequence number and message number to + // the API caller. + w_msgctl.pktseq = pkt1.getSeqNo(); + w_msgctl.msgno = pkt1.getMsgSeq(); + + return extractData((data), len, p, q, passack); } -int CRcvBuffer::readMsg(char* data, int len) +#ifdef SRT_DEBUG_TSBPD_OUTJITTER +void CRcvBuffer::debugTraceJitter(time_point playtime) { - SRT_MSGCTRL dummy = srt_msgctrl_default; - return readMsg(data, len, Ref(dummy)); + uint64_t ms = count_microseconds(steady_clock::now() - playtime); + if (ms / 10 < 10) + m_ulPdHisto[0][ms / 10]++; + else if (ms / 100 < 10) + m_ulPdHisto[1][ms / 100]++; + else if (ms / 1000 < 10) + m_ulPdHisto[2][ms / 1000]++; + else + m_ulPdHisto[3][1]++; } +#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ - -int CRcvBuffer::readMsg(char* data, int len, ref_t r_msgctl) +bool CRcvBuffer::accessMsg(int& w_p, int& w_q, bool& w_passack, int64_t& w_playtime, int upto) { - SRT_MSGCTRL& msgctl = *r_msgctl; - int p, q; - bool passack; - bool empty = true; - uint64_t& rplaytime = msgctl.srctime; + // This function should do the following: + // 1. Find the first packet starting the next message (or just next packet) + // 2. When found something ready for extraction, return true. + // 3. w_p and w_q point the index range for extraction + // 4. passack decides if this range shall be removed after extraction -#ifdef ENABLE_HEAVY_LOGGING - reportBufferStats(); -#endif + bool empty = true; - if (m_bTsbPdMode) + if (m_tsbpd.isEnabled()) { - passack = false; - int seq = 0; + w_passack = false; + int seq = 0; - if (getRcvReadyMsg(Ref(rplaytime), Ref(seq))) + steady_clock::time_point play_time; + const bool isReady = getRcvReadyMsg(play_time, (seq), upto); + w_playtime = count_microseconds(play_time.time_since_epoch()); + + if (isReady) { empty = false; - // In TSBPD mode you always read one message // at a time and a message always fits in one UDP packet, // so in one "unit". - p = q = m_iStartPos; + w_p = w_q = m_iStartPos; -#ifdef SRT_DEBUG_TSBPD_OUTJITTER - uint64_t now = CTimer::getTime(); - if ((now - rplaytime)/10 < 10) - m_ulPdHisto[0][(now - rplaytime)/10]++; - else if ((now - rplaytime)/100 < 10) - m_ulPdHisto[1][(now - rplaytime)/100]++; - else if ((now - rplaytime)/1000 < 10) - m_ulPdHisto[2][(now - rplaytime)/1000]++; - else - m_ulPdHisto[3][1]++; -#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ + debugTraceJitter(play_time); } } else { - rplaytime = 0; - if (scanMsg(Ref(p), Ref(q), Ref(passack))) + w_playtime = 0; + if (scanMsg((w_p), (w_q), (w_passack))) empty = false; - } - if (empty) - return 0; - - // This should happen just once. By 'empty' condition - // we have a guarantee that m_pUnit[p] exists and is valid. - CPacket& pkt1 = m_pUnit[p]->m_Packet; - - // This returns the sequence number and message number to - // the API caller. - msgctl.pktseq = pkt1.getSeqNo(); - msgctl.msgno = pkt1.getMsgSeq(); + return empty; +} +int CRcvBuffer::extractData(char* data, int len, int p, int q, bool passack) +{ SRT_ASSERT(len > 0); - int rs = len > 0 ? len : 0; - while (p != (q + 1) % m_iSize) + int rs = len > 0 ? len : 0; + const int past_q = shiftFwd(q); + while (p != past_q) { const int pktlen = (int)m_pUnit[p]->m_Packet.getLength(); // When unitsize is less than pktlen, only a fragment is copied to the output 'data', @@ -1695,100 +1991,130 @@ int CRcvBuffer::readMsg(char* data, int len, ref_t r_msgctl) const int unitsize = ((rs >= 0) && (pktlen > rs)) ? rs : pktlen; - HLOGC(mglog.Debug, log << "readMsg: checking unit POS=" << p); + HLOGC(brlog.Debug, log << "readMsg: checking unit POS=" << p); if (unitsize > 0) { - memcpy(data, m_pUnit[p]->m_Packet.m_pcData, unitsize); + memcpy((data), m_pUnit[p]->m_Packet.m_pcData, unitsize); data += unitsize; rs -= unitsize; - -#if ENABLE_HEAVY_LOGGING - { - static uint64_t prev_now; - static uint64_t prev_srctime; - CPacket& pkt = m_pUnit[p]->m_Packet; - - int32_t seq = pkt.m_iSeqNo; - - uint64_t nowtime = CTimer::getTime(); - //CTimer::rdtsc(nowtime); - uint64_t srctime = getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()); - - int64_t timediff = nowtime - srctime; - int64_t nowdiff = prev_now ? (nowtime - prev_now) : 0; - uint64_t srctimediff = prev_srctime ? (srctime - prev_srctime) : 0; - - HLOGC(dlog.Debug, log << CONID() << "readMsg: DELIVERED seq=" << seq - << " from POS=" << p << " T=" - << FormatTime(srctime) << " in " << (timediff/1000.0) - << "ms - TIME-PREVIOUS: PKT: " << (srctimediff/1000.0) - << " LOCAL: " << (nowdiff/1000.0) - << " !" << BufferStamp(pkt.data(), pkt.size())); - - prev_now = nowtime; - prev_srctime = srctime; - } -#endif + IF_HEAVY_LOGGING(readMsgHeavyLogging(p)); } else { - HLOGC(dlog.Debug, log << CONID() << "readMsg: SKIPPED POS=" << p << " - ZERO SIZE UNIT"); + HLOGC(brlog.Debug, log << CONID() << "readMsg: SKIPPED POS=" << p << " - ZERO SIZE UNIT"); } + // Note special case for live mode (one packet per message and TSBPD=on): + // - p == q (that is, this loop passes only once) + // - no passack (the unit is always removed from the buffer) if (!passack) { - HLOGC(dlog.Debug, log << CONID() << "readMsg: FREEING UNIT POS=" << p); - CUnit* tmp = m_pUnit[p]; - m_pUnit[p] = NULL; - m_pUnitQueue->makeUnitFree(tmp); + HLOGC(brlog.Debug, log << CONID() << "readMsg: FREEING UNIT POS=" << p); + freeUnitAt(p); } else { - HLOGC(dlog.Debug, log << CONID() << "readMsg: PASSACK UNIT POS=" << p); + HLOGC(brlog.Debug, log << CONID() << "readMsg: PASSACK UNIT POS=" << p); m_pUnit[p]->m_iFlag = CUnit::PASSACK; } - if (++ p == m_iSize) - p = 0; + p = shiftFwd(p); } if (!passack) - m_iStartPos = (q + 1) % m_iSize; + m_iStartPos = past_q; + + HLOGC(brlog.Debug, + log << "rcvBuf/extractData: begin=" << m_iStartPos << " reporting extraction size=" << (len - rs)); return len - rs; } +string CRcvBuffer::debugTimeState(size_t first_n_pkts) const +{ + stringstream ss; + int ipos = m_iStartPos; + for (size_t i = 0; i < first_n_pkts; ++i, ipos = CSeqNo::incseq(ipos)) + { + const CUnit* unit = m_pUnit[ipos]; + if (!unit) + { + ss << "pkt[" << i << "] missing, "; + continue; + } + + const CPacket& pkt = unit->m_Packet; + ss << "pkt[" << i << "] ts=" << pkt.getMsgTimeStamp() << ", "; + } + return ss.str(); +} -bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) +#if ENABLE_HEAVY_LOGGING +void CRcvBuffer::readMsgHeavyLogging(int p) { - int& p = *r_p; - int& q = *r_q; + static steady_clock::time_point prev_now; + static steady_clock::time_point prev_srctime; + const CPacket& pkt = m_pUnit[p]->m_Packet; + + const int32_t seq = pkt.m_iSeqNo; + + steady_clock::time_point nowtime = steady_clock::now(); + steady_clock::time_point srctime = getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()); + + const int64_t timediff_ms = count_milliseconds(nowtime - srctime); + const int64_t nowdiff_ms = is_zero(prev_now) ? count_milliseconds(nowtime - prev_now) : 0; + const int64_t srctimediff_ms = is_zero(prev_srctime) ? count_milliseconds(srctime - prev_srctime) : 0; + + const int next_p = shiftFwd(p); + CUnit* u = m_pUnit[next_p]; + string next_playtime; + if (u && u->m_iFlag == CUnit::GOOD) + { + next_playtime = FormatTime(getPktTsbPdTime(u->m_Packet.getMsgTimeStamp())); + } + else + { + next_playtime = "NONE"; + } + + LOGC(brlog.Debug, + log << CONID() << "readMsg: DELIVERED seq=" << seq << " T=" << FormatTime(srctime) << " in " << timediff_ms + << "ms - TIME-PREVIOUS: PKT: " << srctimediff_ms << " LOCAL: " << nowdiff_ms << " !" + << BufferStamp(pkt.data(), pkt.size()) << " NEXT pkt T=" << next_playtime); + + prev_now = nowtime; + prev_srctime = srctime; +} +#endif +bool CRcvBuffer::scanMsg(int& w_p, int& w_q, bool& w_passack) +{ // empty buffer if ((m_iStartPos == m_iLastAckPos) && (m_iMaxPos <= 0)) { - HLOGC(mglog.Debug, log << "scanMsg: empty buffer"); + HLOGC(brlog.Debug, log << "scanMsg: empty buffer"); return false; } - int rmpkts = 0; + int rmpkts = 0; int rmbytes = 0; - //skip all bad msgs at the beginning + // skip all bad msgs at the beginning + // This loop rolls until the "buffer is empty" (head == tail), + // in particular, there's no unit accessible for the reader. while (m_iStartPos != m_iLastAckPos) { // Roll up to the first valid unit if (!m_pUnit[m_iStartPos]) { - if (++ m_iStartPos == m_iSize) + if (++m_iStartPos == m_iSize) m_iStartPos = 0; continue; } // Note: PB_FIRST | PB_LAST == PB_SOLO. // testing if boundary() & PB_FIRST tests if the msg is first OR solo. - if ( m_pUnit[m_iStartPos]->m_iFlag == CUnit::GOOD - && m_pUnit[m_iStartPos]->m_Packet.getMsgBoundary() & PB_FIRST ) + if (m_pUnit[m_iStartPos]->m_iFlag == CUnit::GOOD && m_pUnit[m_iStartPos]->m_Packet.getMsgBoundary() & PB_FIRST) { bool good = true; @@ -1815,10 +2141,10 @@ bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) } // Likewise, boundary() & PB_LAST will be satisfied for last OR solo. - if ( m_pUnit[i]->m_Packet.getMsgBoundary() & PB_LAST ) + if (m_pUnit[i]->m_Packet.getMsgBoundary() & PB_LAST) break; - if (++ i == m_iSize) + if (++i == m_iSize) i = 0; } @@ -1826,14 +2152,10 @@ bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) break; } - CUnit* tmp = m_pUnit[m_iStartPos]; - m_pUnit[m_iStartPos] = NULL; rmpkts++; - rmbytes += (int) tmp->m_Packet.getLength(); - m_pUnitQueue->makeUnitFree(tmp); + rmbytes += (int) freeUnitAt((size_t) m_iStartPos); - if (++ m_iStartPos == m_iSize) - m_iStartPos = 0; + m_iStartPos = shiftFwd(m_iStartPos); } /* we removed bytes form receive buffer */ countBytes(-rmpkts, -rmbytes, true); @@ -1845,13 +2167,13 @@ bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) // in which case it returns with m_iStartPos <% m_iLastAckPos (earlier) // Also all units that lied before m_iStartPos are removed. - p = -1; // message head - q = m_iStartPos; // message tail - *passack = m_iStartPos == m_iLastAckPos; + w_p = -1; // message head + w_q = m_iStartPos; // message tail + w_passack = m_iStartPos == m_iLastAckPos; bool found = false; // looking for the first message - //>>m_pUnit[size + m_iMaxPos] is not valid + //>>m_pUnit[size + m_iMaxPos] is not valid // XXX Would be nice to make some very thorough refactoring here. @@ -1859,50 +2181,65 @@ bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) // actually from the first message up to the one with PB_LAST // or PB_SOLO boundary. - // The 'i' variable used in this loop is just a stub, and the - // upper value is just to make it "virtually infinite, but with - // no exaggeration" (actually it makes sure that this loop does - // not roll more than around the whole cyclic container). This variable - // isn't used inside the loop at all. + // The 'i' variable used in this loop is just a stub and it's + // even hard to define the unit here. It is "shift towards + // m_iStartPos", so the upper value is m_iMaxPos + size. + // m_iMaxPos is itself relative to m_iLastAckPos, so + // the upper value is m_iMaxPos + difference between + // m_iLastAckPos and m_iStartPos, so that this value is relative + // to m_iStartPos. + // + // The 'i' value isn't used anywhere, although the 'q' value rolls + // in this loop in sync with 'i', with the difference that 'q' is + // wrapped around, and 'i' is just incremented normally. + // + // This makes that this loop rolls in the range by 'q' from + // m_iStartPos to m_iStartPos + UPPER, + // where UPPER = m_iLastAckPos -% m_iStartPos + m_iMaxPos + // This embraces the range from the current reading head up to + // the last packet ever received. + // + // 'passack' is set to true when the 'q' has passed through + // the border of m_iLastAckPos and fallen into the range + // of unacknowledged packets. - for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i < n; ++ i) + for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i < n; ++i) { - if (m_pUnit[q] && m_pUnit[q]->m_iFlag == CUnit::GOOD) + if (m_pUnit[w_q] && m_pUnit[w_q]->m_iFlag == CUnit::GOOD) { // Equivalent pseudocode: - // PacketBoundary bound = m_pUnit[q]->m_Packet.getMsgBoundary(); + // PacketBoundary bound = m_pUnit[w_q]->m_Packet.getMsgBoundary(); // if ( IsSet(bound, PB_FIRST) ) - // p = q; - // if ( IsSet(bound, PB_LAST) && p != -1 ) + // w_p = w_q; + // if ( IsSet(bound, PB_LAST) && w_p != -1 ) // found = true; // - // Not implemented this way because it uselessly check p for -1 + // Not implemented this way because it uselessly check w_p for -1 // also after setting it explicitly. - switch (m_pUnit[q]->m_Packet.getMsgBoundary()) + switch (m_pUnit[w_q]->m_Packet.getMsgBoundary()) { case PB_SOLO: // 11 - p = q; + w_p = w_q; found = true; break; case PB_FIRST: // 10 - p = q; + w_p = w_q; break; case PB_LAST: // 01 - if (p != -1) + if (w_p != -1) found = true; break; - case PB_SUBSEQUENT: - ; // do nothing (caught first, rolling for last) + case PB_SUBSEQUENT:; // do nothing (caught first, rolling for last) } } else { // a hole in this message, not valid, restart search - p = -1; + w_p = -1; } // 'found' is set when the current iteration hit a message with PB_LAST @@ -1910,20 +2247,20 @@ bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) if (found) { // the msg has to be ack'ed or it is allowed to read out of order, and was not read before - if (!*passack || !m_pUnit[q]->m_Packet.getMsgOrderFlag()) + if (!w_passack || !m_pUnit[w_q]->m_Packet.getMsgOrderFlag()) { - HLOGC(mglog.Debug, log << "scanMsg: found next-to-broken message, delivering OUT OF ORDER."); + HLOGC(brlog.Debug, log << "scanMsg: found next-to-broken message, delivering OUT OF ORDER."); break; } found = false; } - if (++ q == m_iSize) - q = 0; + if (++w_q == m_iSize) + w_q = 0; - if (q == m_iLastAckPos) - *passack = true; + if (w_q == m_iLastAckPos) + w_passack = true; } // no msg found @@ -1931,25 +2268,31 @@ bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) { // NOTE: // This situation may only happen if: - // - Found a packet with PB_FIRST, so p = q at the moment when it was found - // - Possibly found following components of that message up to shifted q + // - Found a packet with PB_FIRST, so w_p = w_q at the moment when it was found + // - Possibly found following components of that message up to shifted w_q // - Found no terminal packet (PB_LAST) for that message. // if the message is larger than the receiver buffer, return part of the message - if ((p != -1) && ((q + 1) % m_iSize == p)) + if ((w_p != -1) && (shiftFwd(w_q) == w_p)) { - HLOGC(mglog.Debug, log << "scanMsg: BUFFER FULL and message is INCOMPLETE. Returning PARTIAL MESSAGE."); + HLOGC(brlog.Debug, log << "scanMsg: BUFFER FULL and message is INCOMPLETE. Returning PARTIAL MESSAGE."); found = true; } else { - HLOGC(mglog.Debug, log << "scanMsg: PARTIAL or NO MESSAGE found: p=" << p << " q=" << q); + HLOGC(brlog.Debug, log << "scanMsg: PARTIAL or NO MESSAGE found: p=" << w_p << " q=" << w_q); } } else { - HLOGC(mglog.Debug, log << "scanMsg: extracted message p=" << p << " q=" << q << " (" << ((q-p+m_iSize+1)%m_iSize) << " packets)"); + HLOGC(brlog.Debug, + log << "scanMsg: extracted message p=" << w_p << " q=" << w_q << " (" + << ((w_q - w_p + m_iSize + 1) % m_iSize) << " packets)"); } return found; } + +#endif // !ENABLE_NEW_RCVBUFFER + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer.h b/trunk/3rdparty/srt-1-fit/srtcore/buffer.h index a267d8b444..48bcb43113 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/buffer.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer.h @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,461 +50,562 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_BUFFER_H__ -#define __UDT_BUFFER_H__ - +#ifndef INC_SRT_BUFFER_H +#define INC_SRT_BUFFER_H #include "udt.h" #include "list.h" #include "queue.h" +#include "tsbpd_time.h" #include "utilities.h" -#include -class CSndBuffer +// The notation used for "circular numbers" in comments: +// The "cicrular numbers" are numbers that when increased up to the +// maximum become zero, and similarly, when the zero value is decreased, +// it turns into the maximum value minus one. This wrapping works the +// same for adding and subtracting. Circular numbers cannot be multiplied. + +// Operations done on these numbers are marked with additional % character: +// a %> b : a is later than b +// a ++% (++%a) : shift a by 1 forward +// a +% b : shift a by b +// a == b : equality is same as for just numbers + +namespace srt { + +/// The AvgBufSize class is used to calculate moving average of the buffer (RCV or SND) +class AvgBufSize { -public: + typedef sync::steady_clock::time_point time_point; - // XXX There's currently no way to access the socket ID set for - // whatever the buffer is currently working for. Required to find - // some way to do this, possibly by having a "reverse pointer". - // Currently just "unimplemented". - std::string CONID() const { return ""; } +public: + AvgBufSize() + : m_dBytesCountMAvg(0.0) + , m_dCountMAvg(0.0) + , m_dTimespanMAvg(0.0) + { + } - CSndBuffer(int size = 32, int mss = 1500); - ~CSndBuffer(); +public: + bool isTimeToUpdate(const time_point& now) const; + void update(const time_point& now, int pkts, int bytes, int timespan_ms); public: + inline double pkts() const { return m_dCountMAvg; } + inline double timespan_ms() const { return m_dTimespanMAvg; } + inline double bytes() const { return m_dBytesCountMAvg; } - /// Insert a user buffer into the sending list. - /// @param [in] data pointer to the user data block. - /// @param [in] len size of the block. - /// @param [in] ttl time to live in milliseconds - /// @param [in] order if the block should be delivered in order, for DGRAM only +private: + time_point m_tsLastSamplingTime; + double m_dBytesCountMAvg; + double m_dCountMAvg; + double m_dTimespanMAvg; +}; - void addBuffer(const char* data, int len, int ttl, bool order, uint64_t srctime, ref_t r_msgno); +/// The class to estimate source bitrate based on samples submitted to the buffer. +/// Is currently only used by the CSndBuffer. +class CRateEstimator +{ + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; +public: + CRateEstimator(); - /// Read a block of data from file and insert it into the sending list. - /// @param [in] ifs input file stream. - /// @param [in] len size of the block. - /// @return actual size of data added from the file. +public: + uint64_t getInRatePeriod() const { return m_InRatePeriod; } - int addBufferFromFile(std::fstream& ifs, int len); + /// Retrieve input bitrate in bytes per second + int getInputRate() const { return m_iInRateBps; } - /// Find data position to pack a DATA packet from the furthest reading point. - /// @param [out] data the pointer to the data position. - /// @param [out] msgno message number of the packet. - /// @param [out] origintime origin time stamp of the message - /// @param [in] kflags Odd|Even crypto key flag - /// @return Actual length of data read. + void setInputRateSmpPeriod(int period); - int readData(char** data, int32_t& msgno, uint64_t& origintime, int kflgs); + /// Update input rate calculation. + /// @param [in] time current time in microseconds + /// @param [in] pkts number of packets newly added to the buffer + /// @param [in] bytes number of payload bytes in those newly added packets + /// + /// @return Current size of the data in the sending list. + void updateInputRate(const time_point& time, int pkts = 0, int bytes = 0); + void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); } - /// Find data position to pack a DATA packet for a retransmission. - /// @param [out] data the pointer to the data position. - /// @param [in] offset offset from the last ACK point. - /// @param [out] msgno message number of the packet. - /// @param [out] origintime origin time stamp of the message - /// @param [out] msglen length of the message - /// @return Actual length of data read. +private: // Constants + static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms + static const uint64_t INPUTRATE_RUNNING_US = 1000000; // 1000 ms + static const int64_t INPUTRATE_MAX_PACKETS = 2000; // ~ 21 Mbps of 1316 bytes payload + static const int INPUTRATE_INITIAL_BYTESPS = BW_INFINITE; - int readData(char** data, const int offset, int32_t& msgno, uint64_t& origintime, int& msglen); +private: + int m_iInRatePktsCount; // number of payload bytes added since InRateStartTime + int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime + time_point m_tsInRateStartTime; + uint64_t m_InRatePeriod; // usec + int m_iInRateBps; // Input Rate in Bytes/sec +}; - /// Update the ACK point and may release/unmap/return the user data according to the flag. - /// @param [in] offset number of packets acknowledged. +class CSndBuffer +{ + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; - void ackData(int offset); +public: + // XXX There's currently no way to access the socket ID set for + // whatever the buffer is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } - /// Read size of data still in the sending list. - /// @return Current size of the data in the sending list. + /// @brief CSndBuffer constructor. + /// @param size initial number of blocks (each block to store one packet payload). + /// @param maxpld maximum packet payload. + CSndBuffer(int size = 32, int maxpld = 1500); + ~CSndBuffer(); - int getCurrBufSize() const; +public: + /// Insert a user buffer into the sending list. + /// For @a w_mctrl the following fields are used: + /// INPUT: + /// - msgttl: timeout for retransmitting the message, if lost + /// - inorder: request to deliver the message in order of sending + /// - srctime: local time as a base for packet's timestamp (0 if unused) + /// - pktseq: sequence number to be stamped on the packet (-1 if unused) + /// - msgno: message number to be stamped on the packet (-1 if unused) + /// OUTPUT: + /// - srctime: local time stamped on the packet (same as input, if input wasn't 0) + /// - pktseq: sequence number to be stamped on the next packet + /// - msgno: message number stamped on the packet + /// @param [in] data pointer to the user data block. + /// @param [in] len size of the block. + /// @param [inout] w_mctrl Message control data + SRT_ATTR_EXCLUDES(m_BufLock) + void addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl); + + /// Read a block of data from file and insert it into the sending list. + /// @param [in] ifs input file stream. + /// @param [in] len size of the block. + /// @return actual size of data added from the file. + SRT_ATTR_EXCLUDES(m_BufLock) + int addBufferFromFile(std::fstream& ifs, int len); + + /// Find data position to pack a DATA packet from the furthest reading point. + /// @param [out] packet the packet to read. + /// @param [out] origintime origin time stamp of the message + /// @param [in] kflags Odd|Even crypto key flag + /// @param [out] seqnoinc the number of packets skipped due to TTL, so that seqno should be incremented. + /// @return Actual length of data read. + SRT_ATTR_EXCLUDES(m_BufLock) + int readData(CPacket& w_packet, time_point& w_origintime, int kflgs, int& w_seqnoinc); + + /// Peek an information on the next original data packet to send. + /// @return origin time stamp of the next packet; epoch start time otherwise. + SRT_ATTR_EXCLUDES(m_BufLock) + time_point peekNextOriginal() const; + + /// Find data position to pack a DATA packet for a retransmission. + /// @param [in] offset offset from the last ACK point (backward sequence number difference) + /// @param [out] packet the packet to read. + /// @param [out] origintime origin time stamp of the message + /// @param [out] msglen length of the message + /// @return Actual length of data read (return 0 if offset too large, -1 if TTL exceeded). + SRT_ATTR_EXCLUDES(m_BufLock) + int readData(const int offset, CPacket& w_packet, time_point& w_origintime, int& w_msglen); + + /// Get the time of the last retransmission (if any) of the DATA packet. + /// @param [in] offset offset from the last ACK point (backward sequence number difference) + /// + /// @return Last time of the last retransmission event for the corresponding DATA packet. + SRT_ATTR_EXCLUDES(m_BufLock) + time_point getPacketRexmitTime(const int offset); + + /// Update the ACK point and may release/unmap/return the user data according to the flag. + /// @param [in] offset number of packets acknowledged. + int32_t getMsgNoAt(const int offset); + + void ackData(int offset); + + /// Read size of data still in the sending list. + /// @return Current size of the data in the sending list. + int getCurrBufSize() const; + + SRT_ATTR_EXCLUDES(m_BufLock) + int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time); + + void updAvgBufSize(const time_point& time); + int getAvgBufSize(int& bytes, int& timespan); + int getCurrBufSize(int& bytes, int& timespan); + + /// @brief Get the buffering delay of the oldest message in the buffer. + /// @return the delay value. + SRT_ATTR_EXCLUDES(m_BufLock) + duration getBufferingDelay(const time_point& tnow) const; + + uint64_t getInRatePeriod() const { return m_rateEstimator.getInRatePeriod(); } + + /// Retrieve input bitrate in bytes per second + int getInputRate() const { return m_rateEstimator.getInputRate(); } + + void resetInputRateSmpPeriod(bool disable = false) { m_rateEstimator.resetInputRateSmpPeriod(disable); } + + const CRateEstimator& getRateEstimator() const { return m_rateEstimator; } + + void setRateEstimator(const CRateEstimator& other) { m_rateEstimator = other; } - int dropLateData(int &bytes, uint64_t latetime); +private: + void increase(); -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - void updAvgBufSize(uint64_t time); - int getAvgBufSize(ref_t bytes, ref_t timespan); -#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ - int getCurrBufSize(ref_t bytes, ref_t timespan); +private: + mutable sync::Mutex m_BufLock; // used to synchronize buffer operation - uint64_t getInRatePeriod() const { return m_InRatePeriod; } + struct Block + { + char* m_pcData; // pointer to the data block + int m_iLength; // payload length of the block. - /// Retrieve input bitrate in bytes per second - int getInputRate() const { return m_iInRateBps; } + int32_t m_iMsgNoBitset; // message number + int32_t m_iSeqNo; // sequence number for scheduling + time_point m_tsOriginTime; // block origin time (either provided from above or equals the time a message was submitted for sending. + time_point m_tsRexmitTime; // packet retransmission time + int m_iTTL; // time to live (milliseconds) - /// Update input rate calculation. - /// @param [in] time current time in microseconds - /// @param [in] pkts number of packets newly added to the buffer - /// @param [in] bytes number of payload bytes in those newly added packets - /// - /// @return Current size of the data in the sending list. - void updateInputRate(uint64_t time, int pkts = 0, int bytes = 0); + Block* m_pNext; // next block + int32_t getMsgSeq() + { + // NOTE: this extracts message ID with regard to REXMIT flag. + // This is valid only for message ID that IS GENERATED in this instance, + // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter + // for the peer that it uses LESS bits to represent the message. + return m_iMsgNoBitset & MSGNO_SEQ::mask; + } - void resetInputRateSmpPeriod(bool disable = false) - { - setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); - } + } * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock; + // m_pBlock: The head pointer + // m_pFirstBlock: The first block + // m_pCurrBlock: The current block + // m_pLastBlock: The last block (if first == last, buffer is empty) -private: + struct Buffer + { + char* m_pcData; // buffer + int m_iSize; // size + Buffer* m_pNext; // next buffer + } * m_pBuffer; // physical buffer - void increase(); - void setInputRateSmpPeriod(int period); + int32_t m_iNextMsgNo; // next message number -private: // Constants + int m_iSize; // buffer size (number of packets) + const int m_iBlockLen; // maximum length of a block holding packet payload (excluding packet header). + int m_iCount; // number of used blocks - static const uint64_t INPUTRATE_FAST_START_US = 500000; // 500 ms - static const uint64_t INPUTRATE_RUNNING_US = 1000000; // 1000 ms - static const int64_t INPUTRATE_MAX_PACKETS = 2000; // ~ 21 Mbps of 1316 bytes payload - static const int INPUTRATE_INITIAL_BYTESPS = BW_INFINITE; + int m_iBytesCount; // number of payload bytes in queue + time_point m_tsLastOriginTime; -private: - pthread_mutex_t m_BufLock; // used to synchronize buffer operation - - struct Block - { - char* m_pcData; // pointer to the data block - int m_iLength; // length of the block - - int32_t m_iMsgNoBitset; // message number - uint64_t m_ullOriginTime_us; // original request time - uint64_t m_ullSourceTime_us; - int m_iTTL; // time to live (milliseconds) - - Block* m_pNext; // next block - - int32_t getMsgSeq() - { - // NOTE: this extracts message ID with regard to REXMIT flag. - // This is valid only for message ID that IS GENERATED in this instance, - // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter - // for the peer that it uses LESS bits to represent the message. - return m_iMsgNoBitset & MSGNO_SEQ::mask; - } - - } *m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock; - - // m_pBlock: The head pointer - // m_pFirstBlock: The first block - // m_pCurrBlock: The current block - // m_pLastBlock: The last block (if first == last, buffer is empty) - - struct Buffer - { - char* m_pcData; // buffer - int m_iSize; // size - Buffer* m_pNext; // next buffer - } *m_pBuffer; // physical buffer - - int32_t m_iNextMsgNo; // next message number - - int m_iSize; // buffer size (number of packets) - int m_iMSS; // maximum seqment/packet size - - int m_iCount; // number of used blocks - - int m_iBytesCount; // number of payload bytes in queue - uint64_t m_ullLastOriginTime_us; - -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - uint64_t m_LastSamplingTime; - int m_iCountMAvg; - int m_iBytesCountMAvg; - int m_TimespanMAvg; -#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ - - int m_iInRatePktsCount; // number of payload bytes added since InRateStartTime - int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime - uint64_t m_InRateStartTime; - uint64_t m_InRatePeriod; // usec - int m_iInRateBps; // Input Rate in Bytes/sec - int m_iAvgPayloadSz; // Average packet payload size + AvgBufSize m_mavg; + CRateEstimator m_rateEstimator; private: - CSndBuffer(const CSndBuffer&); - CSndBuffer& operator=(const CSndBuffer&); + CSndBuffer(const CSndBuffer&); + CSndBuffer& operator=(const CSndBuffer&); }; //////////////////////////////////////////////////////////////////////////////// +#if (!ENABLE_NEW_RCVBUFFER) class CRcvBuffer { -public: + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; +public: // XXX There's currently no way to access the socket ID set for // whatever the queue is currently working for. Required to find // some way to do this, possibly by having a "reverse pointer". // Currently just "unimplemented". std::string CONID() const { return ""; } - - /// Construct the buffer. - /// @param [in] queue CUnitQueue that actually holds the units (packets) - /// @param [in] bufsize_pkts in units (packets) - CRcvBuffer(CUnitQueue* queue, int bufsize_pkts = 65536); - ~CRcvBuffer(); - + static const int DEFAULT_SIZE = 65536; + /// Construct the buffer. + /// @param [in] queue CUnitQueue that actually holds the units (packets) + /// @param [in] bufsize_pkts in units (packets) + CRcvBuffer(CUnitQueue* queue, int bufsize_pkts = DEFAULT_SIZE); + ~CRcvBuffer(); public: + /// Write data into the buffer. + /// @param [in] unit pointer to a data unit containing new packet + /// @param [in] offset offset from last ACK point. + /// @return 0 is success, -1 if data is repeated. + int addData(CUnit* unit, int offset); + + /// Read data into a user buffer. + /// @param [in] data pointer to user buffer. + /// @param [in] len length of user buffer. + /// @return size of data read. + int readBuffer(char* data, int len); + + /// Read data directly into file. + /// @param [in] file C++ file stream. + /// @param [in] len expected length of data to write into the file. + /// @return size of data read. + int readBufferToFile(std::fstream& ofs, int len); + + /// Update the ACK point of the buffer. + /// @param [in] len number of units to be acknowledged. + /// @return 1 if a user buffer is fulfilled, otherwise 0. + int ackData(int len); + + /// Query how many buffer space left for data receiving. + /// Actually only acknowledged packets, that are still in the buffer, + /// are considered to take buffer space. + /// + /// @return size of available buffer space (including user buffer) for data receiving. + /// Not counting unacknowledged packets. + int getAvailBufSize() const; + + /// Query how many data has been continuously received (for reading) and ready to play (tsbpdtime < now). + /// @return size of valid (continous) data for reading. + int getRcvDataSize() const; + + /// Query how many data was received and acknowledged. + /// @param [out] bytes bytes + /// @param [out] spantime spantime + /// @return size in pkts of acked data. + int getRcvDataSize(int& bytes, int& spantime); + + /// Query a 1 sec moving average of how many data was received and acknowledged. + /// @param [out] bytes bytes + /// @param [out] spantime spantime + /// @return size in pkts of acked data. + int getRcvAvgDataSize(int& bytes, int& spantime); + + /// Query how many data of the receive buffer is acknowledged. + /// @param [in] now current time in us. + /// @return none. + void updRcvAvgDataSize(const time_point& now); + + /// Query the received average payload size. + /// @return size (bytes) of payload size + unsigned getRcvAvgPayloadSize() const; + + struct ReadingState + { + time_point tsStart; + time_point tsLastAck; + time_point tsEnd; + int iNumAcknowledged; + int iNumUnacknowledged; + }; + + ReadingState debugGetReadingState() const; + + /// Form a string of the current buffer fullness state. + /// number of packets acknowledged, TSBPD readiness, etc. + std::string strFullnessState(const time_point& tsNow) const; + + /// Mark the message to be dropped from the message list. + /// @param [in] msgno message number. + /// @param [in] using_rexmit_flag whether the MSGNO field uses rexmit flag (if not, one more bit is part of the + /// msgno value) + void dropMsg(int32_t msgno, bool using_rexmit_flag); + + /// read a message. + /// @param [out] data buffer to write the message into. + /// @param [in] len size of the buffer. + /// @return actuall size of data read. + int readMsg(char* data, int len); - /// Write data into the buffer. - /// @param [in] unit pointer to a data unit containing new packet - /// @param [in] offset offset from last ACK point. - /// @return 0 is success, -1 if data is repeated. - - int addData(CUnit* unit, int offset); - - /// Read data into a user buffer. - /// @param [in] data pointer to user buffer. - /// @param [in] len length of user buffer. - /// @return size of data read. - - int readBuffer(char* data, int len); - - /// Read data directly into file. - /// @param [in] file C++ file stream. - /// @param [in] len expected length of data to write into the file. - /// @return size of data read. - - int readBufferToFile(std::fstream& ofs, int len); - - /// Update the ACK point of the buffer. - /// @param [in] len number of units to be acknowledged. - /// @return 1 if a user buffer is fulfilled, otherwise 0. - - void ackData(int len); - - /// Query how many buffer space left for data receiving. - /// Actually only acknowledged packets, that are still in the buffer, - /// are considered to take buffer space. - /// - /// @return size of available buffer space (including user buffer) for data receiving. - /// Not counting unacknowledged packets. - - int getAvailBufSize() const; - - /// Query how many data has been continuously received (for reading) and ready to play (tsbpdtime < now). - /// @return size of valid (continous) data for reading. - - int getRcvDataSize() const; - - /// Query how many data was received and acknowledged. - /// @param [out] bytes bytes - /// @param [out] spantime spantime - /// @return size in pkts of acked data. - - int getRcvDataSize(int &bytes, int &spantime); -#if SRT_ENABLE_RCVBUFSZ_MAVG - - /// Query a 1 sec moving average of how many data was received and acknowledged. - /// @param [out] bytes bytes - /// @param [out] spantime spantime - /// @return size in pkts of acked data. - - int getRcvAvgDataSize(int &bytes, int &spantime); - - /// Query how many data of the receive buffer is acknowledged. - /// @param [in] now current time in us. - /// @return none. - - void updRcvAvgDataSize(uint64_t now); -#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ - - /// Query the received average payload size. - /// @return size (bytes) of payload size - - int getRcvAvgPayloadSize() const; - - - /// Mark the message to be dropped from the message list. - /// @param [in] msgno message number. - /// @param [in] using_rexmit_flag whether the MSGNO field uses rexmit flag (if not, one more bit is part of the msgno value) - - void dropMsg(int32_t msgno, bool using_rexmit_flag); - - /// read a message. - /// @param [out] data buffer to write the message into. - /// @param [in] len size of the buffer. - /// @return actuall size of data read. - - int readMsg(char* data, int len); - - /// read a message. - /// @param [out] data buffer to write the message into. - /// @param [in] len size of the buffer. - /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay - /// @return actuall size of data read. - - int readMsg(char* data, int len, ref_t mctrl); - - /// Query if data is ready to read (tsbpdtime <= now if TsbPD is active). - /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay - /// of next packet in recv buffer, ready or not. - /// @param [out] curpktseq Sequence number of the packet if there is one ready to play - /// @return true if ready to play, false otherwise (tsbpdtime may be !0 in - /// both cases). - - bool isRcvDataReady(ref_t tsbpdtime, ref_t curpktseq); - bool isRcvDataReady(); - bool isRcvDataAvailable() - { - return m_iLastAckPos != m_iStartPos; - } - CPacket* getRcvReadyPacket(); +#if ENABLE_HEAVY_LOGGING + void readMsgHeavyLogging(int p); +#endif - /// Set TimeStamp-Based Packet Delivery Rx Mode - /// @param [in] timebase localtime base (uSec) of packet time stamps including buffering delay - /// @param [in] delay aggreed TsbPD delay - /// @return 0 + /// read a message. + /// @param [out] data buffer to write the message into. + /// @param [in] len size of the buffer. + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay + /// @return actuall size of data read. + int readMsg(char* data, int len, SRT_MSGCTRL& w_mctrl, int upto); + + /// Query if data is ready to read (tsbpdtime <= now if TsbPD is active). + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay + /// of next packet in recv buffer, ready or not. + /// @param [out] curpktseq Sequence number of the packet if there is one ready to play + /// @return true if ready to play, false otherwise (tsbpdtime may be !0 in + /// both cases). + bool isRcvDataReady(time_point& w_tsbpdtime, int32_t& w_curpktseq, int32_t seqdistance); - int setRcvTsbPdMode(uint64_t timebase, uint32_t delay); +#ifdef SRT_DEBUG_TSBPD_OUTJITTER + void debugTraceJitter(time_point t); +#else + void debugTraceJitter(time_point) {} +#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ + + bool isRcvDataReady(); + bool isRcvDataAvailable() { return m_iLastAckPos != m_iStartPos; } + CPacket* getRcvReadyPacket(int32_t seqdistance); + + /// Set TimeStamp-Based Packet Delivery Rx Mode + /// @param [in] timebase localtime base (uSec) of packet time stamps including buffering delay + /// @param [in] delay aggreed TsbPD delay + void setRcvTsbPdMode(const time_point& timebase, const duration& delay); + + /// Add packet timestamp for drift caclculation and compensation + /// @param [in] timestamp packet time stamp + /// @param [in] tsPktArrival arrival time of the packet used to extract the drift sample. + /// @param [in] rtt RTT sample + bool addRcvTsbPdDriftSample(uint32_t timestamp, const time_point& tsPktArrival, int rtt); - /// Add packet timestamp for drift caclculation and compensation - /// @param [in] timestamp packet time stamp - /// @param [ref] lock Mutex that should be locked for the operation +#ifdef SRT_DEBUG_TSBPD_DRIFT + void printDriftHistogram(int64_t iDrift); + void printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg); +#endif - void addRcvTsbPdDriftSample(uint32_t timestamp, pthread_mutex_t& lock); + /// Get information on the 1st message in queue. + // Parameters (of the 1st packet queue, ready to play or not): + /// @param [out] w_tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 + /// if none + /// @param [out] w_passack true if 1st ready packet is not yet acknowleged (allowed to be delivered to the app) + /// @param [out] w_skipseqno SRT_SEQNO_NONE or seq number of 1st unacknowledged pkt ready to play preceeded by + /// missing packets. + /// @param base_seq SRT_SEQNO_NONE or desired, ignore seq smaller than base if exist packet ready-to-play + /// and larger than base + /// @retval true 1st packet ready to play (tsbpdtime <= now). Not yet acknowledged if passack == true + /// @retval false IF tsbpdtime = 0: rcv buffer empty; ELSE: + /// IF skipseqno != SRT_SEQNO_NONE, packet ready to play preceeded by missing packets.; + /// IF skipseqno == SRT_SEQNO_NONE, no missing packet but 1st not ready to play. + bool getRcvFirstMsg(time_point& w_tsbpdtime, + bool& w_passack, + int32_t& w_skipseqno, + int32_t& w_curpktseq, + int32_t base_seq = SRT_SEQNO_NONE); + + /// Update the ACK point of the buffer. + /// @param [in] len size of data to be skip & acknowledged. + void skipData(int len); -#ifdef SRT_DEBUG_TSBPD_DRIFT - void printDriftHistogram(int64_t iDrift); - void printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg); +#if ENABLE_HEAVY_LOGGING + void reportBufferStats() const; // Heavy logging Debug only #endif + bool empty() const + { + // This will not always return the intended value, + // that is, it may return false when the buffer really is + // empty - but it will return true then in one of next calls. + // This function will be always called again at some point + // if it returned false, and on true the connection + // is going to be broken - so this behavior is acceptable. + return m_iStartPos == m_iLastAckPos; + } + bool full() const { return m_iStartPos == (m_iLastAckPos + 1) % m_iSize; } + int capacity() const { return m_iSize; } - /// Get information on the 1st message in queue. - // Parameters (of the 1st packet queue, ready to play or not): - /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if none - /// @param [out] passack true if 1st ready packet is not yet acknowleged (allowed to be delivered to the app) - /// @param [out] skipseqno -1 or seq number of 1st unacknowledged pkt ready to play preceeded by missing packets. - /// @retval true 1st packet ready to play (tsbpdtime <= now). Not yet acknowledged if passack == true - /// @retval false IF tsbpdtime = 0: rcv buffer empty; ELSE: - /// IF skipseqno != -1, packet ready to play preceeded by missing packets.; - /// IF skipseqno == -1, no missing packet but 1st not ready to play. +private: + /// This gives up unit at index p. The unit is given back to the + /// free unit storage for further assignment for the new incoming + /// data. + size_t freeUnitAt(size_t p) + { + CUnit* u = m_pUnit[p]; + m_pUnit[p] = NULL; + size_t rmbytes = u->m_Packet.getLength(); + m_pUnitQueue->makeUnitFree(u); + return rmbytes; + } + + /// Adjust receive queue to 1st ready to play message (tsbpdtime < now). + /// Parameters (of the 1st packet queue, ready to play or not): + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if + /// none + /// @param base_seq SRT_SEQNO_NONE or desired, ignore seq smaller than base + /// @retval true 1st packet ready to play without discontinuity (no hole) + /// @retval false tsbpdtime = 0: no packet ready to play + bool getRcvReadyMsg(time_point& w_tsbpdtime, int32_t& w_curpktseq, int upto, int base_seq = SRT_SEQNO_NONE); +public: + /// @brief Get clock drift in microseconds. + int64_t getDrift() const { return m_tsbpd.drift(); } - bool getRcvFirstMsg(ref_t tsbpdtime, ref_t passack, ref_t skipseqno, ref_t curpktseq); +public: + int32_t getTopMsgno() const; - /// Update the ACK point of the buffer. - /// @param [in] len size of data to be skip & acknowledged. + void getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift); - void skipData(int len); + void applyGroupTime(const time_point& timebase, bool wrapcheck, uint32_t delay, const duration& udrift); + void applyGroupDrift(const time_point& timebase, bool wrapcheck, const duration& udrift); + time_point getPktTsbPdTime(uint32_t timestamp); + int debugGetSize() const; + time_point debugGetDeliveryTime(int offset); -#if ENABLE_HEAVY_LOGGING - void reportBufferStats(); // Heavy logging Debug only -#endif + size_t dropData(int len); private: - /// Adjust receive queue to 1st ready to play message (tsbpdtime < now). - // Parameters (of the 1st packet queue, ready to play or not): - /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if none - /// @retval true 1st packet ready to play without discontinuity (no hole) - /// @retval false tsbpdtime = 0: no packet ready to play - + int extractData(char* data, int len, int p, int q, bool passack); + bool accessMsg(int& w_p, int& w_q, bool& w_passack, int64_t& w_playtime, int upto); - bool getRcvReadyMsg(ref_t tsbpdtime, ref_t curpktseq); + /// Describes the state of the first N packets + std::string debugTimeState(size_t first_n_pkts) const; -public: + /// thread safe bytes counter of the Recv & Ack buffer + /// @param [in] pkts acked or removed pkts from rcv buffer (used with acked = true) + /// @param [in] bytes number of bytes added/delete (if negative) to/from rcv buffer. + /// @param [in] acked true when adding new pkt in RcvBuffer; false when acking/removing pkts to/from buffer + void countBytes(int pkts, int bytes, bool acked = false); - // (This is exposed as used publicly in logs) - /// Get packet delivery local time base (adjusted for wrap around) - /// @param [in] timestamp packet timestamp (relative to peer StartTime), wrapping around every ~72 min - /// @return local delivery time (usec) - uint64_t getTsbPdTimeBase(uint32_t timestamp_us); +private: + bool scanMsg(int& w_start, int& w_end, bool& w_passack); - /// Get packet local delivery time - /// @param [in] timestamp packet timestamp (relative to peer StartTime), wrapping around every ~72 min - /// @return local delivery time (usec) + int shift(int basepos, int shift) const { return (basepos + shift) % m_iSize; } -public: - uint64_t getPktTsbPdTime(uint32_t timestamp); - int debugGetSize() const; - bool empty() const; + /// Simplified versions with ++ and --; avoid using division instruction + int shiftFwd(int basepos) const + { + if (++basepos == m_iSize) + return 0; + return basepos; + } - // Required by PacketFilter facility to use as a storage - // for provided packets - CUnitQueue* getUnitQueue() - { - return m_pUnitQueue; - } + int shiftBack(int basepos) const + { + if (basepos == 0) + return m_iSize - 1; + return --basepos; + } private: + CUnit** m_pUnit; // Array of pointed units collected in the buffer + const int m_iSize; // Size of the internal array of CUnit* items + CUnitQueue* m_pUnitQueue; // the shared unit queue - /// thread safe bytes counter of the Recv & Ack buffer - /// @param [in] pkts acked or removed pkts from rcv buffer (used with acked = true) - /// @param [in] bytes number of bytes added/delete (if negative) to/from rcv buffer. - /// @param [in] acked true when adding new pkt in RcvBuffer; false when acking/removing pkts to/from buffer + int m_iStartPos; // HEAD: first packet available for reading + int m_iLastAckPos; // the last ACKed position (exclusive), follows the last readable + // EMPTY: m_iStartPos = m_iLastAckPos FULL: m_iStartPos = m_iLastAckPos + 1 + int m_iMaxPos; // delta between acked-TAIL and reception-TAIL - void countBytes(int pkts, int bytes, bool acked = false); + int m_iNotch; // the starting read point of the first unit + // (this is required for stream reading mode; it's + // the position in the first unit in the list + // up to which data are already retrieved; + // in message reading mode it's unused and always 0) -private: - bool scanMsg(ref_t start, ref_t end, ref_t passack); + sync::Mutex m_BytesCountLock; // used to protect counters operations + int m_iBytesCount; // Number of payload bytes in the buffer + int m_iAckedPktsCount; // Number of acknowledged pkts in the buffer + int m_iAckedBytesCount; // Number of acknowledged payload bytes in the buffer + unsigned m_uAvgPayloadSz; // Average payload size for dropped bytes estimation -private: - CUnit** m_pUnit; // pointer to the protocol buffer (array of CUnit* items) - const int m_iSize; // size of the array of CUnit* items - CUnitQueue* m_pUnitQueue; // the shared unit queue - - int m_iStartPos; // the head position for I/O (inclusive) - int m_iLastAckPos; // the last ACKed position (exclusive) - // EMPTY: m_iStartPos = m_iLastAckPos FULL: m_iStartPos = m_iLastAckPos + 1 - int m_iMaxPos; // the furthest data position - - int m_iNotch; // the starting read point of the first unit - - pthread_mutex_t m_BytesCountLock; // used to protect counters operations - int m_iBytesCount; // Number of payload bytes in the buffer - int m_iAckedPktsCount; // Number of acknowledged pkts in the buffer - int m_iAckedBytesCount; // Number of acknowledged payload bytes in the buffer - int m_iAvgPayloadSz; // Average payload size for dropped bytes estimation - - bool m_bTsbPdMode; // true: apply TimeStamp-Based Rx Mode - uint32_t m_uTsbPdDelay; // aggreed delay - uint64_t m_ullTsbPdTimeBase; // localtime base for TsbPd mode - // Note: m_ullTsbPdTimeBase cumulates values from: - // 1. Initial SRT_CMD_HSREQ packet returned value diff to current time: - // == (NOW - PACKET_TIMESTAMP), at the time of HSREQ reception - // 2. Timestamp overflow (@c CRcvBuffer::getTsbPdTimeBase), when overflow on packet detected - // += CPacket::MAX_TIMESTAMP+1 (it's a hex round value, usually 0x1*e8). - // 3. Time drift (CRcvBuffer::addRcvTsbPdDriftSample, executed exclusively - // from UMSG_ACKACK handler). This is updated with (positive or negative) TSBPD_DRIFT_MAX_VALUE - // once the value of average drift exceeds this value in whatever direction. - // += (+/-)CRcvBuffer::TSBPD_DRIFT_MAX_VALUE - // - // XXX Application-supplied timestamps won't work therefore. This requires separate - // calculation of all these things above. - - bool m_bTsbPdWrapCheck; // true: check packet time stamp wrap around - static const uint32_t TSBPD_WRAP_PERIOD = (30*1000000); //30 seconds (in usec) - - static const int TSBPD_DRIFT_MAX_VALUE = 5000; // Max drift (usec) above which TsbPD Time Offset is adjusted - static const int TSBPD_DRIFT_MAX_SAMPLES = 1000; // Number of samples (UMSG_ACKACK packets) to perform drift caclulation and compensation - //int m_iTsbPdDrift; // recent drift in the packet time stamp - //int64_t m_TsbPdDriftSum; // Sum of sampled drift - //int m_iTsbPdDriftNbSamples; // Number of samples in sum and histogram - DriftTracer m_DriftTracer; -#ifdef SRT_ENABLE_RCVBUFSZ_MAVG - uint64_t m_LastSamplingTime; - int m_TimespanMAvg; - int m_iCountMAvg; - int m_iBytesCountMAvg; -#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ -#ifdef SRT_DEBUG_TSBPD_DRIFT - int m_TsbPdDriftHisto100us[22]; // Histogram of 100us TsbPD drift (-1.0 .. +1.0 ms in 0.1ms increment) - int m_TsbPdDriftHisto1ms[22]; // Histogram of TsbPD drift (-10.0 .. +10.0 ms, in 1.0 ms increment) - static const int TSBPD_DRIFT_PRT_SAMPLES = 200; // Number of samples (UMSG_ACKACK packets) to print hostogram -#endif /* SRT_DEBUG_TSBPD_DRIFT */ + CTsbpdTime m_tsbpd; -#ifdef SRT_DEBUG_TSBPD_OUTJITTER - unsigned long m_ulPdHisto[4][10]; -#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ + AvgBufSize m_mavg; private: - CRcvBuffer(); - CRcvBuffer(const CRcvBuffer&); - CRcvBuffer& operator=(const CRcvBuffer&); + CRcvBuffer(); + CRcvBuffer(const CRcvBuffer&); + CRcvBuffer& operator=(const CRcvBuffer&); }; +#endif // !ENABLE_NEW_RCVBUFFER + +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.cpp b/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.cpp new file mode 100644 index 0000000000..8ecbd10fe1 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.cpp @@ -0,0 +1,1072 @@ +#if ENABLE_NEW_RCVBUFFER +#include +#include +#include "buffer_rcv.h" +#include "logging.h" + +using namespace std; + +using namespace srt::sync; +using namespace srt_logging; +namespace srt_logging +{ + extern Logger brlog; +} +#define rbuflog brlog + +namespace srt { + +namespace { + struct ScopedLog + { + ScopedLog() {} + + ~ScopedLog() + { + LOGC(rbuflog.Warn, log << ss.str()); + } + + stringstream ss; + }; + +#define IF_RCVBUF_DEBUG(instr) (void)0 + + // Check if iFirstNonreadPos is in range [iStartPos, (iStartPos + iMaxPosInc) % iSize]. + // The right edge is included because we expect iFirstNonreadPos to be + // right after the last valid packet position if all packets are available. + bool isInRange(int iStartPos, int iMaxPosInc, size_t iSize, int iFirstNonreadPos) + { + if (iFirstNonreadPos == iStartPos) + return true; + + const int iLastPos = (iStartPos + iMaxPosInc) % iSize; + const bool isOverrun = iLastPos < iStartPos; + + if (isOverrun) + return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; + + return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; + } +} + + +/* + * RcvBufferNew (circular buffer): + * + * |<------------------- m_iSize ----------------------------->| + * | |<----------- m_iMaxPosInc ------------>| | + * | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | | + * | |__last pkt received + * |___ m_iStartPos: first message to read + * + * m_pUnit[i]->m_iFlag: 0:free, 1:good, 2:passack, 3:dropped + * + * thread safety: + * m_iStartPos: CUDT::m_RecvLock + * m_iLastAckPos: CUDT::m_AckLock + * m_iMaxPosInc: none? (modified on add and ack + */ + +CRcvBufferNew::CRcvBufferNew(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI) + : m_entries(size) + , m_szSize(size) // TODO: maybe just use m_entries.size() + , m_pUnitQueue(unitqueue) + , m_iStartSeqNo(initSeqNo) + , m_iStartPos(0) + , m_iFirstNonreadPos(0) + , m_iMaxPosInc(0) + , m_iNotch(0) + , m_numOutOfOrderPackets(0) + , m_iFirstReadableOutOfOrder(-1) + , m_bPeerRexmitFlag(true) + , m_bMessageAPI(bMessageAPI) + , m_iBytesCount(0) + , m_iPktsCount(0) + , m_uAvgPayloadSz(SRT_LIVE_DEF_PLSIZE) +{ + SRT_ASSERT(size < size_t(std::numeric_limits::max())); // All position pointers are integers +} + +CRcvBufferNew::~CRcvBufferNew() +{ + // Can be optimized by only iterating m_iMaxPosInc from m_iStartPos. + for (FixedArray::iterator it = m_entries.begin(); it != m_entries.end(); ++it) + { + if (!it->pUnit) + continue; + + m_pUnitQueue->makeUnitFree(it->pUnit); + it->pUnit = NULL; + } +} + +int CRcvBufferNew::insert(CUnit* unit) +{ + SRT_ASSERT(unit != NULL); + const int32_t seqno = unit->m_Packet.getSeqNo(); + const int offset = CSeqNo::seqoff(m_iStartSeqNo, seqno); + + IF_RCVBUF_DEBUG(ScopedLog scoped_log); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBufferNew::insert: seqno " << seqno); + IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << unit->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); + IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); + + if (offset < 0) + { + IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); + return -2; + } + + if (offset >= (int)capacity()) + { + IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); + return -3; + } + + // TODO: Don't do assert here. Process this situation somehow. + // If >= 2, then probably there is a long gap, and buffer needs to be reset. + SRT_ASSERT((m_iStartPos + offset) / m_szSize < 2); + + const int pos = (m_iStartPos + offset) % m_szSize; + if (offset >= m_iMaxPosInc) + m_iMaxPosInc = offset + 1; + + // Packet already exists + SRT_ASSERT(pos >= 0 && pos < int(m_szSize)); + if (m_entries[pos].status != EntryState_Empty) + { + IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); + return -1; + } + SRT_ASSERT(m_entries[pos].pUnit == NULL); + + m_pUnitQueue->makeUnitGood(unit); + m_entries[pos].pUnit = unit; + m_entries[pos].status = EntryState_Avail; + countBytes(1, (int)unit->m_Packet.getLength()); + + // If packet "in order" flag is zero, it can be read out of order. + // With TSBPD enabled packets are always assumed in order (the flag is ignored). + if (!m_tsbpd.isEnabled() && m_bMessageAPI && !unit->m_Packet.getMsgOrderFlag()) + { + ++m_numOutOfOrderPackets; + onInsertNotInOrderPacket(pos); + } + + updateNonreadPos(); + IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); + return 0; +} + +int CRcvBufferNew::dropUpTo(int32_t seqno) +{ + IF_RCVBUF_DEBUG(ScopedLog scoped_log); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBufferNew::dropUpTo: seqno " << seqno << " m_iStartSeqNo " << m_iStartSeqNo); + + int len = CSeqNo::seqoff(m_iStartSeqNo, seqno); + if (len <= 0) + { + IF_RCVBUF_DEBUG(scoped_log.ss << ". Nothing to drop."); + return 0; + } + + m_iMaxPosInc -= len; + if (m_iMaxPosInc < 0) + m_iMaxPosInc = 0; + + const int iDropCnt = len; + while (len > 0) + { + dropUnitInPos(m_iStartPos); + m_entries[m_iStartPos].status = EntryState_Empty; + SRT_ASSERT(m_entries[m_iStartPos].pUnit == NULL && m_entries[m_iStartPos].status == EntryState_Empty); + m_iStartPos = incPos(m_iStartPos); + --len; + } + + // Update positions + m_iStartSeqNo = seqno; + // Move forward if there are "read/drop" entries. + releaseNextFillerEntries(); + // Set nonread position to the starting position before updating, + // because start position was increased, and preceeding packets are invalid. + m_iFirstNonreadPos = m_iStartPos; + updateNonreadPos(); + if (!m_tsbpd.isEnabled() && m_bMessageAPI) + updateFirstReadableOutOfOrder(); + return iDropCnt; +} + +int CRcvBufferNew::dropAll() +{ + if (empty()) + return 0; + + const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosInc); + return dropUpTo(end_seqno); +} + +int CRcvBufferNew::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno) +{ + IF_RCVBUF_DEBUG(ScopedLog scoped_log); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBufferNew::dropMessage: seqnolo " << seqnolo << " seqnohi " << seqnohi << " m_iStartSeqNo " << m_iStartSeqNo); + // TODO: count bytes as removed? + const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); + if (msgno != 0) + { + IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << msgno); + int minDroppedOffset = -1; + int iDropCnt = 0; + for (int i = m_iStartPos; i != end_pos; i = incPos(i)) + { + // TODO: Maybe check status? + if (!m_entries[i].pUnit) + continue; + + // TODO: Break the loop if a massege has been found. No need to search further. + const int32_t msgseq = m_entries[i].pUnit->m_Packet.getMsgSeq(m_bPeerRexmitFlag); + if (msgseq == msgno) + { + ++iDropCnt; + dropUnitInPos(i); + m_entries[i].status = EntryState_Drop; + if (minDroppedOffset == -1) + minDroppedOffset = offPos(m_iStartPos, i); + } + } + IF_RCVBUF_DEBUG(scoped_log.ss << " iDropCnt " << iDropCnt); + // Check if units before m_iFirstNonreadPos are dropped. + bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); + releaseNextFillerEntries(); + if (needUpdateNonreadPos) + { + m_iFirstNonreadPos = m_iStartPos; + updateNonreadPos(); + } + if (!m_tsbpd.isEnabled() && m_bMessageAPI) + { + if (!checkFirstReadableOutOfOrder()) + m_iFirstReadableOutOfOrder = -1; + updateFirstReadableOutOfOrder(); + } + return iDropCnt; + } + + // Drop by packet seqno range. + const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); + const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi); + if (offset_b < 0) + { + LOGC(rbuflog.Debug, log << "CRcvBufferNew.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " + << seqnohi << "]. Buffer start " << m_iStartSeqNo << "."); + return 0; + } + + const int start_off = max(0, offset_a); + const int last_pos = incPos(m_iStartPos, offset_b); + int minDroppedOffset = -1; + int iDropCnt = 0; + for (int i = incPos(m_iStartPos, start_off); i != end_pos && i != last_pos; i = incPos(i)) + { + // Don't drop messages, if all its packets are already in the buffer. + // TODO: Don't drop a several-packet message if all packets are in the buffer. + if (m_entries[i].pUnit && m_entries[i].pUnit->m_Packet.getMsgBoundary() == PB_SOLO) + continue; + + dropUnitInPos(i); + ++iDropCnt; + m_entries[i].status = EntryState_Drop; + if (minDroppedOffset == -1) + minDroppedOffset = offPos(m_iStartPos, i); + } + + LOGC(rbuflog.Debug, log << "CRcvBufferNew.dropMessage(): [" << seqnolo << "; " + << seqnohi << "]."); + + // Check if units before m_iFirstNonreadPos are dropped. + bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); + releaseNextFillerEntries(); + if (needUpdateNonreadPos) + { + m_iFirstNonreadPos = m_iStartPos; + updateNonreadPos(); + } + if (!m_tsbpd.isEnabled() && m_bMessageAPI) + { + if (!checkFirstReadableOutOfOrder()) + m_iFirstReadableOutOfOrder = -1; + updateFirstReadableOutOfOrder(); + } + + return iDropCnt; +} + +int CRcvBufferNew::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) +{ + const bool canReadInOrder = hasReadableInorderPkts(); + if (!canReadInOrder && m_iFirstReadableOutOfOrder < 0) + { + LOGC(rbuflog.Warn, log << "CRcvBufferNew.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); + return 0; + } + + const int readPos = canReadInOrder ? m_iStartPos : m_iFirstReadableOutOfOrder; + + IF_RCVBUF_DEBUG(ScopedLog scoped_log); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBufferNew::readMessage. m_iStartSeqNo " << m_iStartSeqNo << " m_iStartPos " << m_iStartPos << " readPos " << readPos); + + size_t remain = len; + char* dst = data; + int pkts_read = 0; + int bytes_extracted = 0; // The total number of bytes extracted from the buffer. + const bool updateStartPos = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed + for (int i = readPos;; i = incPos(i)) + { + SRT_ASSERT(m_entries[i].pUnit); + if (!m_entries[i].pUnit) + { + LOGC(rbuflog.Error, log << "CRcvBufferNew::readMessage(): null packet encountered."); + break; + } + + const CPacket& packet = m_entries[i].pUnit->m_Packet; + const size_t pktsize = packet.getLength(); + const int32_t pktseqno = packet.getSeqNo(); + + // unitsize can be zero + const size_t unitsize = std::min(remain, pktsize); + memcpy(dst, packet.m_pcData, unitsize); + remain -= unitsize; + dst += unitsize; + + ++pkts_read; + bytes_extracted += (int) pktsize; + + if (m_tsbpd.isEnabled()) + updateTsbPdTimeBase(packet.getMsgTimeStamp()); + + if (m_numOutOfOrderPackets && !packet.getMsgOrderFlag()) + --m_numOutOfOrderPackets; + + const bool pbLast = packet.getMsgBoundary() & PB_LAST; + if (msgctrl && (packet.getMsgBoundary() & PB_FIRST)) + { + msgctrl->msgno = packet.getMsgSeq(m_bPeerRexmitFlag); + } + if (msgctrl && pbLast) + { + msgctrl->srctime = count_microseconds(getPktTsbPdTime(packet.getMsgTimeStamp()).time_since_epoch()); + } + if (msgctrl) + msgctrl->pktseq = pktseqno; + + releaseUnitInPos(i); + if (updateStartPos) + { + m_iStartPos = incPos(i); + --m_iMaxPosInc; + SRT_ASSERT(m_iMaxPosInc >= 0); + m_iStartSeqNo = CSeqNo::incseq(pktseqno); + } + else + { + // If out of order, only mark it read. + m_entries[i].status = EntryState_Read; + } + + if (pbLast) + { + if (readPos == m_iFirstReadableOutOfOrder) + m_iFirstReadableOutOfOrder = -1; + break; + } + } + + countBytes(-pkts_read, -bytes_extracted); + + releaseNextFillerEntries(); + + if (!isInRange(m_iStartPos, m_iMaxPosInc, m_szSize, m_iFirstNonreadPos)) + { + m_iFirstNonreadPos = m_iStartPos; + //updateNonreadPos(); + } + + if (!m_tsbpd.isEnabled()) + // We need updateFirstReadableOutOfOrder() here even if we are reading inorder, + // incase readable inorder packets are all read out. + updateFirstReadableOutOfOrder(); + + const int bytes_read = int(dst - data); + if (bytes_read < bytes_extracted) + { + LOGC(rbuflog.Error, log << "readMessage: small dst buffer, copied only " << bytes_read << "/" << bytes_extracted << " bytes."); + } + + IF_RCVBUF_DEBUG(scoped_log.ss << " pldi64 " << *reinterpret_cast(data)); + + return bytes_read; +} + +namespace { + /// @brief Writes bytes to file stream. + /// @param data pointer to data to write. + /// @param len the number of bytes to write + /// @param dst_offset ignored + /// @param arg a void pointer to the fstream to write to. + /// @return true on success, false on failure + bool writeBytesToFile(char* data, int len, int dst_offset SRT_ATR_UNUSED, void* arg) + { + fstream* pofs = reinterpret_cast(arg); + pofs->write(data, len); + return !pofs->fail(); + } + + /// @brief Copies bytes to the destination buffer. + /// @param data pointer to data to copy. + /// @param len the number of bytes to copy + /// @param dst_offset offset in destination buffer + /// @param arg A pointer to the destination buffer + /// @return true on success, false on failure + bool copyBytesToBuf(char* data, int len, int dst_offset, void* arg) + { + char* dst = reinterpret_cast(arg) + dst_offset; + memcpy(dst, data, len); + return true; + } +} + +int CRcvBufferNew::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) +{ + int p = m_iStartPos; + const int end_pos = m_iFirstNonreadPos; + + const bool bTsbPdEnabled = m_tsbpd.isEnabled(); + const steady_clock::time_point now = (bTsbPdEnabled ? steady_clock::now() : steady_clock::time_point()); + + int rs = len; + while ((p != end_pos) && (rs > 0)) + { + if (!m_entries[p].pUnit) + { + p = incPos(p); + LOGC(rbuflog.Error, log << "readBufferTo: IPE: NULL unit found in file transmission"); + return -1; + } + + const srt::CPacket& pkt = m_entries[p].pUnit->m_Packet; + + if (bTsbPdEnabled) + { + const steady_clock::time_point tsPlay = getPktTsbPdTime(pkt.getMsgTimeStamp()); + HLOGC(rbuflog.Debug, + log << "readBuffer: check if time to play:" + << " NOW=" << FormatTime(now) + << " PKT TS=" << FormatTime(tsPlay)); + + if ((tsPlay > now)) + break; /* too early for this unit, return whatever was copied */ + } + + const int pktlen = (int)pkt.getLength(); + const int remain_pktlen = pktlen - m_iNotch; + const int unitsize = std::min(remain_pktlen, rs); + + if (!funcCopyToDst(pkt.m_pcData + m_iNotch, unitsize, len - rs, arg)) + break; + + if (rs >= remain_pktlen) + { + releaseUnitInPos(p); + p = incPos(p); + m_iNotch = 0; + + m_iStartPos = p; + --m_iMaxPosInc; + SRT_ASSERT(m_iMaxPosInc >= 0); + m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + } + else + m_iNotch += rs; + + rs -= unitsize; + } + + const int iBytesRead = len - rs; + /* we removed acked bytes form receive buffer */ + countBytes(-1, -iBytesRead); + + // Update positions + // Set nonread position to the starting position before updating, + // because start position was increased, and preceeding packets are invalid. + if (!isInRange(m_iStartPos, m_iMaxPosInc, m_szSize, m_iFirstNonreadPos)) + { + m_iFirstNonreadPos = m_iStartPos; + } + + if (iBytesRead == 0) + { + LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); + } + + return iBytesRead; +} + +int CRcvBufferNew::readBuffer(char* dst, int len) +{ + return readBufferTo(len, copyBytesToBuf, reinterpret_cast(dst)); +} + +int CRcvBufferNew::readBufferToFile(fstream& ofs, int len) +{ + return readBufferTo(len, writeBytesToFile, reinterpret_cast(&ofs)); +} + +bool CRcvBufferNew::hasAvailablePackets() const +{ + return hasReadableInorderPkts() || (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); +} + +int CRcvBufferNew::getRcvDataSize() const +{ + if (m_iFirstNonreadPos >= m_iStartPos) + return m_iFirstNonreadPos - m_iStartPos; + + return int(m_szSize + m_iFirstNonreadPos - m_iStartPos); +} + +int CRcvBufferNew::getTimespan_ms() const +{ + if (!m_tsbpd.isEnabled()) + return 0; + + if (m_iMaxPosInc == 0) + return 0; + + const int lastpos = incPos(m_iStartPos, m_iMaxPosInc - 1); + int startpos = m_iStartPos; + + while (m_entries[startpos].pUnit == NULL) + { + if (startpos == lastpos) + break; + + startpos = incPos(startpos); + } + + if (m_entries[startpos].pUnit == NULL) + return 0; + + // Should not happen + SRT_ASSERT(m_entries[lastpos].pUnit != NULL); + if (m_entries[lastpos].pUnit == NULL) + return 0; + + const steady_clock::time_point startstamp = + getPktTsbPdTime(m_entries[startpos].pUnit->m_Packet.getMsgTimeStamp()); + const steady_clock::time_point endstamp = getPktTsbPdTime(m_entries[lastpos].pUnit->m_Packet.getMsgTimeStamp()); + if (endstamp < startstamp) + return 0; + + // One millisecond is added as a duration of a packet in the buffer. + // If there is only one packet in the buffer, one millisecond is returned. + return static_cast(count_milliseconds(endstamp - startstamp) + 1); +} + +int CRcvBufferNew::getRcvDataSize(int& bytes, int& timespan) const +{ + ScopedLock lck(m_BytesCountLock); + bytes = m_iBytesCount; + timespan = getTimespan_ms(); + return m_iPktsCount; +} + +CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstValidPacketInfo() const +{ + const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); + for (int i = m_iStartPos; i != end_pos; i = incPos(i)) + { + // TODO: Maybe check status? + if (!m_entries[i].pUnit) + continue; + + const CPacket& packet = m_entries[i].pUnit->m_Packet; + const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; + return info; + } + + const PacketInfo info = { -1, false, time_point() }; + return info; +} + +std::pair CRcvBufferNew::getAvailablePacketsRange() const +{ + const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, (int) countReadable()); + return std::pair(m_iStartSeqNo, seqno_last); +} + +size_t CRcvBufferNew::countReadable() const +{ + if (m_iFirstNonreadPos >= m_iStartPos) + return m_iFirstNonreadPos - m_iStartPos; + return m_szSize + m_iFirstNonreadPos - m_iStartPos; +} + +bool CRcvBufferNew::isRcvDataReady(time_point time_now) const +{ + const bool haveInorderPackets = hasReadableInorderPkts(); + if (!m_tsbpd.isEnabled()) + { + if (haveInorderPackets) + return true; + + SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); + return (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + } + + if (!haveInorderPackets) + return false; + + const PacketInfo info = getFirstValidPacketInfo(); + + return info.tsbpd_time <= time_now; +} + +CRcvBufferNew::PacketInfo CRcvBufferNew::getFirstReadablePacketInfo(time_point time_now) const +{ + const PacketInfo unreadableInfo = {SRT_SEQNO_NONE, false, time_point()}; + const bool hasInorderPackets = hasReadableInorderPkts(); + + if (!m_tsbpd.isEnabled()) + { + if (hasInorderPackets) + { + const CPacket& packet = m_entries[m_iStartPos].pUnit->m_Packet; + const PacketInfo info = {packet.getSeqNo(), false, time_point()}; + return info; + } + SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); + if (m_iFirstReadableOutOfOrder >= 0) + { + SRT_ASSERT(m_numOutOfOrderPackets > 0); + const CPacket& packet = m_entries[m_iFirstReadableOutOfOrder].pUnit->m_Packet; + const PacketInfo info = {packet.getSeqNo(), true, time_point()}; + return info; + } + return unreadableInfo; + } + + if (!hasInorderPackets) + return unreadableInfo; + + const PacketInfo info = getFirstValidPacketInfo(); + + if (info.tsbpd_time <= time_now) + return info; + else + return unreadableInfo; +} + +void CRcvBufferNew::countBytes(int pkts, int bytes) +{ + ScopedLock lock(m_BytesCountLock); + m_iBytesCount += bytes; // added or removed bytes from rcv buffer + m_iPktsCount += pkts; + if (bytes > 0) // Assuming one pkt when adding bytes + m_uAvgPayloadSz = avg_iir<100>(m_uAvgPayloadSz, (unsigned) bytes); +} + +void CRcvBufferNew::releaseUnitInPos(int pos) +{ + CUnit* tmp = m_entries[pos].pUnit; + m_entries[pos] = Entry(); // pUnit = NULL; status = Empty + if (tmp != NULL) + m_pUnitQueue->makeUnitFree(tmp); +} + +bool CRcvBufferNew::dropUnitInPos(int pos) +{ + if (!m_entries[pos].pUnit) + return false; + if (m_tsbpd.isEnabled()) + { + updateTsbPdTimeBase(m_entries[pos].pUnit->m_Packet.getMsgTimeStamp()); + } + else if (m_bMessageAPI && !m_entries[pos].pUnit->m_Packet.getMsgOrderFlag()) + { + --m_numOutOfOrderPackets; + if (pos == m_iFirstReadableOutOfOrder) + m_iFirstReadableOutOfOrder = -1; + } + releaseUnitInPos(pos); + return true; +} + +void CRcvBufferNew::releaseNextFillerEntries() +{ + int pos = m_iStartPos; + while (m_entries[pos].status == EntryState_Read || m_entries[pos].status == EntryState_Drop) + { + m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + releaseUnitInPos(pos); + pos = incPos(pos); + m_iStartPos = pos; + --m_iMaxPosInc; + if (m_iMaxPosInc < 0) + m_iMaxPosInc = 0; + } +} + +// TODO: Is this function complete? There are some comments left inside. +void CRcvBufferNew::updateNonreadPos() +{ + if (m_iMaxPosInc == 0) + return; + + const int end_pos = incPos(m_iStartPos, m_iMaxPosInc); // The empty position right after the last valid entry. + + int pos = m_iFirstNonreadPos; + while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) + { + if (m_bMessageAPI && (m_entries[pos].pUnit->m_Packet.getMsgBoundary() & PB_FIRST) == 0) + break; + + for (int i = pos; i != end_pos; i = incPos(i)) + { + if (!m_entries[i].pUnit || m_entries[pos].status != EntryState_Avail) + { + break; + } + + // Check PB_LAST only in message mode. + if (!m_bMessageAPI || m_entries[i].pUnit->m_Packet.getMsgBoundary() & PB_LAST) + { + m_iFirstNonreadPos = incPos(i); + break; + } + } + + if (pos == m_iFirstNonreadPos || !m_entries[m_iFirstNonreadPos].pUnit) + break; + + pos = m_iFirstNonreadPos; + } +} + +int CRcvBufferNew::findLastMessagePkt() +{ + for (int i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) + { + SRT_ASSERT(m_entries[i].pUnit); + + if (m_entries[i].pUnit->m_Packet.getMsgBoundary() & PB_LAST) + { + return i; + } + } + + return -1; +} + +void CRcvBufferNew::onInsertNotInOrderPacket(int insertPos) +{ + if (m_numOutOfOrderPackets == 0) + return; + + // If the following condition is true, there is already a packet, + // that can be read out of order. We don't need to search for + // another one. The search should be done when that packet is read out from the buffer. + // + // There might happen that the packet being added precedes the previously found one. + // However, it is allowed to re bead out of order, so no need to update the position. + if (m_iFirstReadableOutOfOrder >= 0) + return; + + // Just a sanity check. This function is called when a new packet is added. + // So the should be unacknowledged packets. + SRT_ASSERT(m_iMaxPosInc > 0); + SRT_ASSERT(m_entries[insertPos].pUnit); + const CPacket& pkt = m_entries[insertPos].pUnit->m_Packet; + const PacketBoundary boundary = pkt.getMsgBoundary(); + + //if ((boundary & PB_FIRST) && (boundary & PB_LAST)) + //{ + // // This packet can be read out of order + // m_iFirstReadableOutOfOrder = insertPos; + // return; + //} + + const int msgNo = pkt.getMsgSeq(m_bPeerRexmitFlag); + // First check last packet, because it is expected to be received last. + const bool hasLast = (boundary & PB_LAST) || (-1 < scanNotInOrderMessageRight(insertPos, msgNo)); + if (!hasLast) + return; + + const int firstPktPos = (boundary & PB_FIRST) + ? insertPos + : scanNotInOrderMessageLeft(insertPos, msgNo); + if (firstPktPos < 0) + return; + + m_iFirstReadableOutOfOrder = firstPktPos; + return; +} + +bool CRcvBufferNew::checkFirstReadableOutOfOrder() +{ + if (m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder < 0 || m_iMaxPosInc == 0) + return false; + + const int endPos = incPos(m_iStartPos, m_iMaxPosInc); + int msgno = -1; + for (int pos = m_iFirstReadableOutOfOrder; pos != endPos; pos = incPos(pos)) + { + if (!m_entries[pos].pUnit) + return false; + + const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + if (pkt.getMsgOrderFlag()) + return false; + + if (msgno == -1) + msgno = pkt.getMsgSeq(m_bPeerRexmitFlag); + else if (msgno != pkt.getMsgSeq(m_bPeerRexmitFlag)) + return false; + + if (pkt.getMsgBoundary() & PB_LAST) + return true; + } + + return false; +} + +void CRcvBufferNew::updateFirstReadableOutOfOrder() +{ + if (hasReadableInorderPkts() || m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder >= 0) + return; + + if (m_iMaxPosInc == 0) + return; + + // TODO: unused variable outOfOrderPktsRemain? + int outOfOrderPktsRemain = (int) m_numOutOfOrderPackets; + + // Search further packets to the right. + // First check if there are packets to the right. + const int lastPos = (m_iStartPos + m_iMaxPosInc - 1) % m_szSize; + + int posFirst = -1; + int posLast = -1; + int msgNo = -1; + + for (int pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) + { + if (!m_entries[pos].pUnit) + { + posFirst = posLast = msgNo = -1; + continue; + } + + const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + + if (pkt.getMsgOrderFlag()) // Skip in order packet + { + posFirst = posLast = msgNo = -1; + continue; + } + + --outOfOrderPktsRemain; + + const PacketBoundary boundary = pkt.getMsgBoundary(); + if (boundary & PB_FIRST) + { + posFirst = pos; + msgNo = pkt.getMsgSeq(m_bPeerRexmitFlag); + } + + if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) + { + posFirst = posLast = msgNo = -1; + continue; + } + + if (boundary & PB_LAST) + { + m_iFirstReadableOutOfOrder = posFirst; + return; + } + + if (pos == lastPos) + break; + } + + return; +} + +int CRcvBufferNew::scanNotInOrderMessageRight(const int startPos, int msgNo) const +{ + // Search further packets to the right. + // First check if there are packets to the right. + const int lastPos = (m_iStartPos + m_iMaxPosInc - 1) % m_szSize; + if (startPos == lastPos) + return -1; + + int pos = startPos; + do + { + pos = incPos(pos); + if (!m_entries[pos].pUnit) + break; + + const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + + if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) + { + LOGC(rbuflog.Error, log << "Missing PB_LAST packet for msgNo " << msgNo); + return -1; + } + + const PacketBoundary boundary = pkt.getMsgBoundary(); + if (boundary & PB_LAST) + return pos; + } while (pos != lastPos); + + return -1; +} + +int CRcvBufferNew::scanNotInOrderMessageLeft(const int startPos, int msgNo) const +{ + // Search preceeding packets to the left. + // First check if there are packets to the left. + if (startPos == m_iStartPos) + return -1; + + int pos = startPos; + do + { + pos = decPos(pos); + + if (!m_entries[pos].pUnit) + return -1; + + const CPacket& pkt = m_entries[pos].pUnit->m_Packet; + + if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) + { + LOGC(rbuflog.Error, log << "Missing PB_FIRST packet for msgNo " << msgNo); + return -1; + } + + const PacketBoundary boundary = pkt.getMsgBoundary(); + if (boundary & PB_FIRST) + return pos; + } while (pos != m_iStartPos); + + return -1; +} + +bool CRcvBufferNew::addRcvTsbPdDriftSample(uint32_t usTimestamp, const time_point& tsPktArrival, int usRTTSample) +{ + return m_tsbpd.addDriftSample(usTimestamp, tsPktArrival, usRTTSample); +} + +void CRcvBufferNew::setTsbPdMode(const steady_clock::time_point& timebase, bool wrap, duration delay) +{ + m_tsbpd.setTsbPdMode(timebase, wrap, delay); +} + +void CRcvBufferNew::applyGroupTime(const steady_clock::time_point& timebase, + bool wrp, + uint32_t delay, + const steady_clock::duration& udrift) +{ + m_tsbpd.applyGroupTime(timebase, wrp, delay, udrift); +} + +void CRcvBufferNew::applyGroupDrift(const steady_clock::time_point& timebase, + bool wrp, + const steady_clock::duration& udrift) +{ + m_tsbpd.applyGroupDrift(timebase, wrp, udrift); +} + +CRcvBufferNew::time_point CRcvBufferNew::getTsbPdTimeBase(uint32_t usPktTimestamp) const +{ + return m_tsbpd.getTsbPdTimeBase(usPktTimestamp); +} + +void CRcvBufferNew::updateTsbPdTimeBase(uint32_t usPktTimestamp) +{ + m_tsbpd.updateTsbPdTimeBase(usPktTimestamp); +} + +string CRcvBufferNew::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const +{ + stringstream ss; + + ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize; + ss << " pkts. "; + if (m_tsbpd.isEnabled() && m_iMaxPosInc > 0) + { + const PacketInfo nextValidPkt = getFirstValidPacketInfo(); + ss << "(TSBPD ready in "; + if (!is_zero(nextValidPkt.tsbpd_time)) + { + ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; + const int iLastPos = incPos(m_iStartPos, m_iMaxPosInc - 1); + if (m_entries[iLastPos].pUnit) + { + ss << ", timespan "; + const uint32_t usPktTimestamp = m_entries[iLastPos].pUnit->m_Packet.getMsgTimeStamp(); + ss << count_milliseconds(m_tsbpd.getPktTsbPdTime(usPktTimestamp) - nextValidPkt.tsbpd_time); + ss << " ms"; + } + } + else + { + ss << "n/a"; + } + + ss << "). "; + } + + ss << SRT_SYNC_CLOCK_STR " drift " << getDrift() / 1000 << " ms."; + return ss.str(); +} + +CRcvBufferNew::time_point CRcvBufferNew::getPktTsbPdTime(uint32_t usPktTimestamp) const +{ + return m_tsbpd.getPktTsbPdTime(usPktTimestamp); +} + +/* Return moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ +int CRcvBufferNew::getRcvAvgDataSize(int& bytes, int& timespan) +{ + // Average number of packets and timespan could be small, + // so rounding is beneficial, while for the number of + // bytes in the buffer is a higher value, so rounding can be omitted, + // but probably better to round all three values. + timespan = static_cast(round((m_mavg.timespan_ms()))); + bytes = static_cast(round((m_mavg.bytes()))); + return static_cast(round(m_mavg.pkts())); +} + +/* Update moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ +void CRcvBufferNew::updRcvAvgDataSize(const steady_clock::time_point& now) +{ + if (!m_mavg.isTimeToUpdate(now)) + return; + + int bytes = 0; + int timespan_ms = 0; + const int pkts = getRcvDataSize(bytes, timespan_ms); + m_mavg.update(now, pkts, bytes, timespan_ms); +} + +} // namespace srt + +#endif // ENABLE_NEW_RCVBUFFER diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.h b/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.h new file mode 100644 index 0000000000..d628c459fd --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.h @@ -0,0 +1,367 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2020 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#ifndef INC_SRT_BUFFER_RCV_H +#define INC_SRT_BUFFER_RCV_H + +#if ENABLE_NEW_RCVBUFFER + +#include "buffer.h" // AvgBufSize +#include "common.h" +#include "queue.h" +#include "sync.h" +#include "tsbpd_time.h" + +namespace srt +{ + +/* + * Circular receiver buffer. + * + * |<------------------- m_szSize ---------------------------->| + * | |<------------ m_iMaxPosInc ----------->| | + * | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | | + * | \__last pkt received + * | + * \___ m_iStartPos: first message to read + * + * m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) + * + * thread safety: + * start_pos_: CUDT::m_RecvLock + * first_unack_pos_: CUDT::m_AckLock + * max_pos_inc_: none? (modified on add and ack + * first_nonread_pos_: + */ + +class CRcvBufferNew +{ + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; + +public: + CRcvBufferNew(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI); + + ~CRcvBufferNew(); + +public: + /// Insert a unit into the buffer. + /// Similar to CRcvBuffer::addData(CUnit* unit, int offset) + /// + /// @param [in] unit pointer to a data unit containing new packet + /// @param [in] offset offset from last ACK point. + /// + /// @return 0 on success, -1 if packet is already in buffer, -2 if packet is before m_iStartSeqNo. + /// -3 if a packet is offset is ahead the buffer capacity. + // TODO: Previously '-2' also meant 'already acknowledged'. Check usage of this value. + int insert(CUnit* unit); + + /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). + /// @param [in] seqno drop units up to this sequence number + /// @return number of dropped packets. + int dropUpTo(int32_t seqno); + + /// @brief Drop all the packets in the receiver buffer. + /// The starting position and seqno are shifted right after the last packet in the buffer. + /// @return the number of dropped packets. + int dropAll(); + + /// @brief Drop the whole message from the buffer. + /// If message number is 0, then use sequence numbers to locate sequence range to drop [seqnolo, seqnohi]. + /// When one packet of the message is in the range of dropping, the whole message is to be dropped. + /// @param seqnolo sequence number of the first packet in the dropping range. + /// @param seqnohi sequence number of the last packet in the dropping range. + /// @param msgno message number to drop (0 if unknown) + /// @return the number of packets actually dropped. + int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno); + + /// Read the whole message from one or several packets. + /// + /// @param [in,out] data buffer to write the message into. + /// @param [in] len size of the buffer. + /// @param [in,out] message control data + /// + /// @return actual number of bytes extracted from the buffer. + /// 0 if nothing to read. + /// -1 on failure. + int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL); + + /// Read acknowledged data into a user buffer. + /// @param [in, out] dst pointer to the target user buffer. + /// @param [in] len length of user buffer. + /// @return size of data read. -1 on error. + int readBuffer(char* dst, int len); + + /// Read acknowledged data directly into file. + /// @param [in] ofs C++ file stream. + /// @param [in] len expected length of data to write into the file. + /// @return size of data read. -1 on error. + int readBufferToFile(std::fstream& ofs, int len); + +public: + /// Get the starting position of the buffer as a packet sequence number. + int getStartSeqNo() const { return m_iStartSeqNo; } + + /// Sets the start seqno of the buffer. + /// Must be used with caution and only when the buffer is empty. + void setStartSeqNo(int seqno) { m_iStartSeqNo = seqno; } + + /// Given the sequence number of the first unacknowledged packet + /// tells the size of the buffer available for packets. + /// Effective returns capacity of the buffer minus acknowledged packet still kept in it. + // TODO: Maybe does not need to return minus one slot now to distinguish full and empty buffer. + size_t getAvailSize(int iFirstUnackSeqNo) const + { + // Receiver buffer allows reading unacknowledged packets. + // Therefore if the first packet in the buffer is ahead of the iFirstUnackSeqNo + // then it does not have acknowledged packets and its full capacity is available. + // Otherwise subtract the number of acknowledged but not yet read packets from its capacity. + const int iRBufSeqNo = getStartSeqNo(); + if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo + { + // Full capacity is available, still don't want to encourage extra packets to come. + // Note: CSeqNo::seqlen(n, n) returns 1. + return capacity() - CSeqNo::seqlen(iFirstUnackSeqNo, iRBufSeqNo) + 1; + } + + // Note: CSeqNo::seqlen(n, n) returns 1. + return capacity() - CSeqNo::seqlen(iRBufSeqNo, iFirstUnackSeqNo) + 1; + } + + /// @brief Checks if the buffer has packets available for reading regardless of the TSBPD. + /// @return true if there are packets available for reading, false otherwise. + bool hasAvailablePackets() const; + + /// Query how many data has been continuously received (for reading) and available for reading out + /// regardless of the TSBPD. + /// TODO: Rename to countAvailablePackets(). + /// @return size of valid (continous) data for reading. + int getRcvDataSize() const; + + /// Get the number of packets, bytes and buffer timespan. + /// Differs from getRcvDataSize() that it counts all packets in the buffer, not only continious. + int getRcvDataSize(int& bytes, int& timespan) const; + + struct PacketInfo + { + int seqno; + bool seq_gap; //< true if there are missing packets in the buffer, preceding current packet + time_point tsbpd_time; + }; + + /// Get information on the 1st message in queue. + /// Similar to CRcvBuffer::getRcvFirstMsg + /// Parameters (of the 1st packet queue, ready to play or not): + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if + /// none + /// @param [out] passack true if 1st ready packet is not yet acknowleged (allowed to be delivered to the app) + /// @param [out] skipseqno -1 or seq number of 1st unacknowledged pkt ready to play preceeded by missing packets. + /// @retval true 1st packet ready to play (tsbpdtime <= now). Not yet acknowledged if passack == true + /// @retval false IF tsbpdtime = 0: rcv buffer empty; ELSE: + /// IF skipseqno != -1, packet ready to play preceeded by missing packets.; + /// IF skipseqno == -1, no missing packet but 1st not ready to play. + PacketInfo getFirstValidPacketInfo() const; + + PacketInfo getFirstReadablePacketInfo(time_point time_now) const; + + /// Get information on packets available to be read. + /// @returns a pair of sequence numbers (first available; first unavailable). + /// + /// @note CSeqNo::seqoff(first, second) is 0 if nothing to read. + std::pair getAvailablePacketsRange() const; + + size_t countReadable() const; + + bool empty() const + { + return (m_iMaxPosInc == 0); + } + + /// Return buffer capacity. + /// One slot had to be empty in order to tell the difference between "empty buffer" and "full buffer". + /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continiously. + /// TODO: Old receiver buffer capacity returns the actual size. Check for conflicts. + size_t capacity() const + { + return m_szSize - 1; + } + + int64_t getDrift() const { return m_tsbpd.drift(); } + + // TODO: make thread safe? + int debugGetSize() const + { + return getRcvDataSize(); + } + + /// Zero time to include all available packets. + /// TODO: Rename to 'canRead`. + bool isRcvDataReady(time_point time_now = time_point()) const; + + int getRcvAvgDataSize(int& bytes, int& timespan); + void updRcvAvgDataSize(const time_point& now); + + unsigned getRcvAvgPayloadSize() const { return m_uAvgPayloadSz; } + + void getInternalTimeBase(time_point& w_timebase, bool& w_wrp, duration& w_udrift) + { + return m_tsbpd.getInternalTimeBase(w_timebase, w_wrp, w_udrift); + } + +public: // Used for testing + /// Peek unit in position of seqno + const CUnit* peek(int32_t seqno); + +private: + inline int incPos(int pos, int inc = 1) const { return (pos + inc) % m_szSize; } + inline int decPos(int pos) const { return (pos - 1) >= 0 ? (pos - 1) : int(m_szSize - 1); } + inline int offPos(int pos1, int pos2) const { return (pos2 >= pos1) ? (pos2 - pos1) : int(m_szSize + pos2 - pos1); } + +private: + void countBytes(int pkts, int bytes); + void updateNonreadPos(); + void releaseUnitInPos(int pos); + + /// @brief Drop a unit from the buffer. + /// @param pos position in the m_entries of the unit to drop. + /// @return false if nothing to drop, true if the unit was dropped successfully. + bool dropUnitInPos(int pos); + + /// Release entries following the current buffer position if they were already + /// read out of order (EntryState_Read) or dropped (EntryState_Drop). + void releaseNextFillerEntries(); + + bool hasReadableInorderPkts() const { return (m_iFirstNonreadPos != m_iStartPos); } + + /// Find position of the last packet of the message. + int findLastMessagePkt(); + + /// Scan for availability of out of order packets. + void onInsertNotInOrderPacket(int insertpos); + // Check if m_iFirstReadableOutOfOrder is still readable. + bool checkFirstReadableOutOfOrder(); + void updateFirstReadableOutOfOrder(); + int scanNotInOrderMessageRight(int startPos, int msgNo) const; + int scanNotInOrderMessageLeft(int startPos, int msgNo) const; + + typedef bool copy_to_dst_f(char* data, int len, int dst_offset, void* arg); + + /// Read acknowledged data directly into file. + /// @param [in] ofs C++ file stream. + /// @param [in] len expected length of data to write into the file. + /// @return size of data read. + int readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg); + + /// @brief Estimate timespan of the stored packets (acknowledged and unacknowledged). + /// @return timespan in milliseconds + int getTimespan_ms() const; + +private: + // TODO: Call makeUnitGood upon assignment, and makeUnitFree upon clearing. + // TODO: CUnitPtr is not in use at the moment, but may be a smart pointer. + // class CUnitPtr + // { + // public: + // void operator=(CUnit* pUnit) + // { + // if (m_pUnit != NULL) + // { + // // m_pUnitQueue->makeUnitFree(m_entries[i].pUnit); + // } + // m_pUnit = pUnit; + // } + // private: + // CUnit* m_pUnit; + // }; + + enum EntryStatus + { + EntryState_Empty, //< No CUnit record. + EntryState_Avail, //< Entry is available for reading. + EntryState_Read, //< Entry has already been read (out of order). + EntryState_Drop //< Entry has been dropped. + }; + struct Entry + { + Entry() + : pUnit(NULL) + , status(EntryState_Empty) + {} + + CUnit* pUnit; + EntryStatus status; + }; + + //static Entry emptyEntry() { return Entry { NULL, EntryState_Empty }; } + + FixedArray m_entries; + + const size_t m_szSize; // size of the array of units (buffer) + CUnitQueue* m_pUnitQueue; // the shared unit queue + + int m_iStartSeqNo; + int m_iStartPos; // the head position for I/O (inclusive) + int m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) + int m_iMaxPosInc; // the furthest data position + int m_iNotch; // the starting read point of the first unit + + size_t m_numOutOfOrderPackets; // The number of stored packets with "inorder" flag set to false + int m_iFirstReadableOutOfOrder; // In case of out ouf order packet, points to a position of the first such packet to + // read + bool m_bPeerRexmitFlag; // Needed to read message number correctly + const bool m_bMessageAPI; // Operation mode flag: message or stream. + +public: // TSBPD public functions + /// Set TimeStamp-Based Packet Delivery Rx Mode + /// @param [in] timebase localtime base (uSec) of packet time stamps including buffering delay + /// @param [in] wrap Is in wrapping period + /// @param [in] delay aggreed TsbPD delay + /// + /// @return 0 + void setTsbPdMode(const time_point& timebase, bool wrap, duration delay); + + void setPeerRexmitFlag(bool flag) { m_bPeerRexmitFlag = flag; } + + void applyGroupTime(const time_point& timebase, bool wrp, uint32_t delay, const duration& udrift); + + void applyGroupDrift(const time_point& timebase, bool wrp, const duration& udrift); + + bool addRcvTsbPdDriftSample(uint32_t usTimestamp, const time_point& tsPktArrival, int usRTTSample); + + time_point getPktTsbPdTime(uint32_t usPktTimestamp) const; + + time_point getTsbPdTimeBase(uint32_t usPktTimestamp) const; + void updateTsbPdTimeBase(uint32_t usPktTimestamp); + + /// Form a string of the current buffer fullness state. + /// number of packets acknowledged, TSBPD readiness, etc. + std::string strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const; + +private: + CTsbpdTime m_tsbpd; + +private: // Statistics + AvgBufSize m_mavg; + + // TODO: m_BytesCountLock is probably not needed as the buffer has to be protected from simultaneous access. + mutable sync::Mutex m_BytesCountLock; // used to protect counters operations + int m_iBytesCount; // Number of payload bytes in the buffer + int m_iPktsCount; // Number of payload bytes in the buffer + unsigned m_uAvgPayloadSz; // Average payload size for dropped bytes estimation +}; + +} // namespace srt + +#endif // ENABLE_NEW_RCVBUFFER +#endif // INC_SRT_BUFFER_RCV_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/cache.cpp b/trunk/3rdparty/srt-1-fit/srtcore/cache.cpp index fdde5998fd..e4fa90d874 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/cache.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/cache.cpp @@ -38,10 +38,8 @@ written by Yunhong Gu, last updated 05/05/2009 *****************************************************************************/ -#ifdef _WIN32 - #include - #include -#endif + +#include "platform_sys.h" #include #include "cache.h" @@ -49,22 +47,22 @@ written by using namespace std; -CInfoBlock& CInfoBlock::operator=(const CInfoBlock& obj) +srt::CInfoBlock& srt::CInfoBlock::copyFrom(const CInfoBlock& obj) { std::copy(obj.m_piIP, obj.m_piIP + 4, m_piIP); - m_iIPversion = obj.m_iIPversion; - m_ullTimeStamp = obj.m_ullTimeStamp; - m_iRTT = obj.m_iRTT; - m_iBandwidth = obj.m_iBandwidth; - m_iLossRate = obj.m_iLossRate; + m_iIPversion = obj.m_iIPversion; + m_ullTimeStamp = obj.m_ullTimeStamp; + m_iSRTT = obj.m_iSRTT; + m_iBandwidth = obj.m_iBandwidth; + m_iLossRate = obj.m_iLossRate; m_iReorderDistance = obj.m_iReorderDistance; - m_dInterval = obj.m_dInterval; - m_dCWnd = obj.m_dCWnd; + m_dInterval = obj.m_dInterval; + m_dCWnd = obj.m_dCWnd; return *this; } -bool CInfoBlock::operator==(const CInfoBlock& obj) +bool srt::CInfoBlock::operator==(const CInfoBlock& obj) { if (m_iIPversion != obj.m_iIPversion) return false; @@ -81,24 +79,24 @@ bool CInfoBlock::operator==(const CInfoBlock& obj) return true; } -CInfoBlock* CInfoBlock::clone() +srt::CInfoBlock* srt::CInfoBlock::clone() { CInfoBlock* obj = new CInfoBlock; std::copy(m_piIP, m_piIP + 4, obj->m_piIP); - obj->m_iIPversion = m_iIPversion; - obj->m_ullTimeStamp = m_ullTimeStamp; - obj->m_iRTT = m_iRTT; - obj->m_iBandwidth = m_iBandwidth; - obj->m_iLossRate = m_iLossRate; + obj->m_iIPversion = m_iIPversion; + obj->m_ullTimeStamp = m_ullTimeStamp; + obj->m_iSRTT = m_iSRTT; + obj->m_iBandwidth = m_iBandwidth; + obj->m_iLossRate = m_iLossRate; obj->m_iReorderDistance = m_iReorderDistance; - obj->m_dInterval = m_dInterval; - obj->m_dCWnd = m_dCWnd; + obj->m_dInterval = m_dInterval; + obj->m_dCWnd = m_dCWnd; return obj; } -int CInfoBlock::getKey() +int srt::CInfoBlock::getKey() { if (m_iIPversion == AF_INET) return m_piIP[0]; @@ -106,15 +104,15 @@ int CInfoBlock::getKey() return m_piIP[0] + m_piIP[1] + m_piIP[2] + m_piIP[3]; } -void CInfoBlock::convert(const sockaddr* addr, int ver, uint32_t ip[]) +void srt::CInfoBlock::convert(const sockaddr_any& addr, uint32_t aw_ip[4]) { - if (ver == AF_INET) + if (addr.family() == AF_INET) { - ip[0] = ((sockaddr_in*)addr)->sin_addr.s_addr; - ip[1] = ip[2] = ip[3] = 0; + aw_ip[0] = addr.sin.sin_addr.s_addr; + aw_ip[1] = aw_ip[2] = aw_ip[3] = 0; } else { - memcpy((char*)ip, (char*)((sockaddr_in6*)addr)->sin6_addr.s6_addr, 16); + memcpy((aw_ip), addr.sin6.sin6_addr.s6_addr, sizeof addr.sin6.sin6_addr.s6_addr); } } diff --git a/trunk/3rdparty/srt-1-fit/srtcore/cache.h b/trunk/3rdparty/srt-1-fit/srtcore/cache.h index 346f9d8130..0dd57ba01c 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/cache.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/cache.h @@ -38,15 +38,19 @@ written by Yunhong Gu, last updated 01/27/2011 *****************************************************************************/ -#ifndef __UDT_CACHE_H__ -#define __UDT_CACHE_H__ +#ifndef INC_SRT_CACHE_H +#define INC_SRT_CACHE_H #include #include -#include "common.h" +#include "sync.h" +#include "netinet_any.h" #include "udt.h" +namespace srt +{ + class CCacheItem { public: @@ -82,13 +86,13 @@ template class CCache m_iCurrSize(0) { m_vHashPtr.resize(m_iHashSize); - CGuard::createMutex(m_Lock); + // Exception: -> CUDTUnited ctor + srt::sync::setupMutex(m_Lock, "Cache"); } ~CCache() { clear(); - CGuard::releaseMutex(m_Lock); } public: @@ -98,7 +102,7 @@ template class CCache int lookup(T* data) { - CGuard cacheguard(m_Lock); + srt::sync::ScopedLock cacheguard(m_Lock); int key = data->getKey(); if (key < 0) @@ -126,7 +130,7 @@ template class CCache int update(T* data) { - CGuard cacheguard(m_Lock); + srt::sync::ScopedLock cacheguard(m_Lock); int key = data->getKey(); if (key < 0) @@ -223,7 +227,7 @@ template class CCache int m_iHashSize; int m_iCurrSize; - pthread_mutex_t m_Lock; + srt::sync::Mutex m_Lock; private: CCache(const CCache&); @@ -234,23 +238,25 @@ template class CCache class CInfoBlock { public: - uint32_t m_piIP[4]; // IP address, machine read only, not human readable format - int m_iIPversion; // IP version - uint64_t m_ullTimeStamp; // last update time - int m_iRTT; // RTT - int m_iBandwidth; // estimated bandwidth - int m_iLossRate; // average loss rate - int m_iReorderDistance; // packet reordering distance - double m_dInterval; // inter-packet time, congestion control - double m_dCWnd; // congestion window size, congestion control + uint32_t m_piIP[4]; // IP address, machine read only, not human readable format. + int m_iIPversion; // Address family: AF_INET or AF_INET6. + uint64_t m_ullTimeStamp; // Last update time. + int m_iSRTT; // Smoothed RTT. + int m_iBandwidth; // Estimated link bandwidth. + int m_iLossRate; // Average loss rate. + int m_iReorderDistance; // Packet reordering distance. + double m_dInterval; // Inter-packet time (Congestion Control). + double m_dCWnd; // Congestion window size (Congestion Control). public: - virtual ~CInfoBlock() {} - virtual CInfoBlock& operator=(const CInfoBlock& obj); - virtual bool operator==(const CInfoBlock& obj); - virtual CInfoBlock* clone(); - virtual int getKey(); - virtual void release() {} + CInfoBlock() {} // NOTE: leaves uninitialized + CInfoBlock& copyFrom(const CInfoBlock& obj); + CInfoBlock(const CInfoBlock& src) { copyFrom(src); } + CInfoBlock& operator=(const CInfoBlock& src) { return copyFrom(src); } + bool operator==(const CInfoBlock& obj); + CInfoBlock* clone(); + int getKey(); + void release() {} public: @@ -259,8 +265,9 @@ class CInfoBlock /// @param [in] ver IP version /// @param [out] ip the result machine readable IP address in integer array - static void convert(const sockaddr* addr, int ver, uint32_t ip[]); + static void convert(const sockaddr_any& addr, uint32_t ip[4]); }; +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/channel.cpp b/trunk/3rdparty/srt-1-fit/srtcore/channel.cpp index dad6fd8dbf..30fd987787 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/channel.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/channel.cpp @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,398 +50,571 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef _WIN32 - #if __APPLE__ - #include "TargetConditionals.h" - #endif - #include - #include - #include - #include - #include - #include - #include - #include - #include -#else - #include - #include - #include -#endif +#include "platform_sys.h" #include -#include // Logging +#include // Logging #include #include #include "channel.h" +#include "core.h" // srt_logging:kmlog #include "packet.h" -#include "api.h" // SockaddrToString - possibly move it to somewhere else #include "logging.h" +#include "netinet_any.h" #include "utilities.h" #ifdef _WIN32 - typedef int socklen_t; -#endif - -#ifndef _WIN32 - #define NET_ERROR errno -#else - #define NET_ERROR WSAGetLastError() +typedef int socklen_t; #endif using namespace std; using namespace srt_logging; -CChannel::CChannel(): -m_iIPversion(AF_INET), -m_iSockAddrSize(sizeof(sockaddr_in)), -m_iSocket(), -#ifdef SRT_ENABLE_IPOPTS -m_iIpTTL(-1), /* IPv4 TTL or IPv6 HOPs [1..255] (-1:undefined) */ -m_iIpToS(-1), /* IPv4 Type of Service or IPv6 Traffic Class [0x00..0xff] (-1:undefined) */ +namespace srt +{ + +#ifdef _WIN32 +// use INVALID_SOCKET, as provided +#else +static const int INVALID_SOCKET = -1; #endif -m_iSndBufSize(65536), -m_iRcvBufSize(65536), -m_iIpV6Only(-1) + +#if ENABLE_SOCK_CLOEXEC +#ifndef _WIN32 + +#if defined(_AIX) || defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) + +// Set the CLOEXEC flag using ioctl() function +static int set_cloexec(int fd, int set) { + int r; + + do + r = ioctl(fd, set ? FIOCLEX : FIONCLEX); + while (r == -1 && errno == EINTR); + + if (r) + return errno; + + return 0; } +#else +// Set the CLOEXEC flag using fcntl() function +static int set_cloexec(int fd, int set) +{ + int flags; + int r; -CChannel::CChannel(int version): -m_iIPversion(version), -m_iSocket(), -#ifdef SRT_ENABLE_IPOPTS -m_iIpTTL(-1), -m_iIpToS(-1), -#endif -m_iSndBufSize(65536), -m_iRcvBufSize(65536), -m_iIpV6Only(-1), -m_BindAddr(version) + do + r = fcntl(fd, F_GETFD); + while (r == -1 && errno == EINTR); + + if (r == -1) + return errno; + + /* Bail out now if already set/clear. */ + if (!!(r & FD_CLOEXEC) == !!set) + return 0; + + if (set) + flags = r | FD_CLOEXEC; + else + flags = r & ~FD_CLOEXEC; + + do + r = fcntl(fd, F_SETFD, flags); + while (r == -1 && errno == EINTR); + + if (r) + return errno; + + return 0; +} +#endif // if defined(_AIX) ... +#endif // ifndef _WIN32 +#endif // if ENABLE_CLOEXEC +} // namespace srt + +srt::CChannel::CChannel() + : m_iSocket(INVALID_SOCKET) { - SRT_ASSERT(version == AF_INET || version == AF_INET6); - m_iSockAddrSize = (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); } -CChannel::~CChannel() +srt::CChannel::~CChannel() {} + +void srt::CChannel::createSocket(int family) { +#if ENABLE_SOCK_CLOEXEC + bool cloexec_flag = false; + // construct an socket +#if defined(SOCK_CLOEXEC) + m_iSocket = ::socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (m_iSocket == INVALID_SOCKET) + { + m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); + cloexec_flag = true; + } +#else + m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); + cloexec_flag = true; +#endif +#else // ENABLE_SOCK_CLOEXEC + m_iSocket = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); +#endif // ENABLE_SOCK_CLOEXEC + + if (m_iSocket == INVALID_SOCKET) + throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); + +#if ENABLE_SOCK_CLOEXEC +#ifdef _WIN32 + // XXX ::SetHandleInformation(hInputWrite, HANDLE_FLAG_INHERIT, 0) +#else + if (cloexec_flag) + { + if (0 != set_cloexec(m_iSocket, 1)) + { + throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); + } + } +#endif +#endif // ENABLE_SOCK_CLOEXEC + + if ((m_mcfg.iIpV6Only != -1) && (family == AF_INET6)) // (not an error if it fails) + { + const int res SRT_ATR_UNUSED = + ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&m_mcfg.iIpV6Only, sizeof m_mcfg.iIpV6Only); +#if ENABLE_LOGGING + if (res == -1) + { + int err = errno; + char msg[160]; + LOGC(kmlog.Error, + log << "::setsockopt: failed to set IPPROTO_IPV6/IPV6_V6ONLY = " << m_mcfg.iIpV6Only << ": " + << SysStrError(err, msg, 159)); + } +#endif // ENABLE_LOGGING + } } -void CChannel::open(const sockaddr* addr) +void srt::CChannel::open(const sockaddr_any& addr) { - // construct an socket - m_iSocket = ::socket(m_iIPversion, SOCK_DGRAM, 0); - - #ifdef _WIN32 - if (INVALID_SOCKET == m_iSocket) - #else - if (m_iSocket < 0) - #endif - throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); - - if ((m_iIpV6Only != -1) && (m_iIPversion == AF_INET6)) // (not an error if it fails) - ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)(&m_iIpV6Only), sizeof(m_iIpV6Only)); - - if (NULL != addr) - { - socklen_t namelen = m_iSockAddrSize; - - if (0 != ::bind(m_iSocket, addr, namelen)) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - memcpy(&m_BindAddr, addr, namelen); - m_BindAddr.len = namelen; - } - else - { - //sendto or WSASendTo will also automatically bind the socket - addrinfo hints; - addrinfo* res; - - memset(&hints, 0, sizeof(struct addrinfo)); - - hints.ai_flags = AI_PASSIVE; - hints.ai_family = m_iIPversion; - hints.ai_socktype = SOCK_DGRAM; - - if (0 != ::getaddrinfo(NULL, "0", &hints, &res)) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - - // On Windows ai_addrlen has type size_t (unsigned), while bind takes int. - if (0 != ::bind(m_iSocket, res->ai_addr, (socklen_t)res->ai_addrlen)) - { - ::freeaddrinfo(res); - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - memcpy(&m_BindAddr, res->ai_addr, res->ai_addrlen); - m_BindAddr.len = (socklen_t) res->ai_addrlen; - - ::freeaddrinfo(res); - } - - HLOGC(mglog.Debug, log << "CHANNEL: Bound to local address: " << SockaddrToString(&m_BindAddr)); - - setUDPSockOpt(); + createSocket(addr.family()); + socklen_t namelen = addr.size(); + + if (::bind(m_iSocket, &addr.sa, namelen) == -1) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + + m_BindAddr = addr; + LOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str()); + + setUDPSockOpt(); } -void CChannel::attach(UDPSOCKET udpsock) +void srt::CChannel::open(int family) { - m_iSocket = udpsock; - setUDPSockOpt(); + createSocket(family); + + // sendto or WSASendTo will also automatically bind the socket + addrinfo hints; + addrinfo* res; + + memset(&hints, 0, sizeof(struct addrinfo)); + + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + + const int eai = ::getaddrinfo(NULL, "0", &hints, &res); + if (eai != 0) + { + // Controversial a little bit because this function occasionally + // doesn't use errno (here: NET_ERROR for portability), instead + // it returns 0 if succeeded or an error code. This error code + // is passed here then. A controversy is around the fact that + // the receiver of this error has completely no ability to know + // what this error code's domain is, and it definitely isn't + // the same as for errno. + throw CUDTException(MJ_SETUP, MN_NORES, eai); + } + + // On Windows ai_addrlen has type size_t (unsigned), while bind takes int. + if (0 != ::bind(m_iSocket, res->ai_addr, (socklen_t)res->ai_addrlen)) + { + ::freeaddrinfo(res); + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + m_BindAddr = sockaddr_any(res->ai_addr, (sockaddr_any::len_t)res->ai_addrlen); + + ::freeaddrinfo(res); + + HLOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str()); + + setUDPSockOpt(); } -void CChannel::setUDPSockOpt() +void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr) { - #if defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - // BSD system will fail setsockopt if the requested buffer size exceeds system maximum value - int maxsize = 64000; - if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_iRcvBufSize, sizeof(int))) - ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&maxsize, sizeof(int)); - if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_iSndBufSize, sizeof(int))) - ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&maxsize, sizeof(int)); - #else - // for other systems, if requested is greated than maximum, the maximum value will be automactally used - if ((0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_iRcvBufSize, sizeof(int))) || - (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_iSndBufSize, sizeof(int)))) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - #endif - - SRT_ASSERT(m_iIPversion == AF_INET || m_iIPversion == AF_INET6); - -#ifdef SRT_ENABLE_IPOPTS - if (-1 != m_iIpTTL) - { - if (m_iIPversion == AF_INET) - { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_iIpTTL, sizeof(m_iIpTTL))) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - else - { - // If IPv6 address is unspecified, set BOTH IP_TTL and IPV6_UNICAST_HOPS. - - // For specified IPv6 address, set IPV6_UNICAST_HOPS ONLY UNLESS it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) - { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_iIpTTL, sizeof(m_iIpTTL))) - { - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - } - // For specified IPv6 address, set IP_TTL ONLY WHEN it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) - { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_iIpTTL, sizeof(m_iIpTTL))) - { - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - } - } - } - - if (-1 != m_iIpToS) - { - if (m_iIPversion == AF_INET) - { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_iIpToS, sizeof(m_iIpToS))) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - else - { - // If IPv6 address is unspecified, set BOTH IP_TOS and IPV6_TCLASS. + // The getsockname() call is done before calling it and the + // result is placed into udpsocks_addr. + m_iSocket = udpsock; + m_BindAddr = udpsocks_addr; + setUDPSockOpt(); +} -#ifdef IPV6_TCLASS - // For specified IPv6 address, set IPV6_TCLASS ONLY UNLESS it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) - { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_iIpToS, sizeof(m_iIpToS))) - { - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - } +void srt::CChannel::setUDPSockOpt() +{ +#if defined(SUNOS) + { + socklen_t optSize; + // Retrieve starting SND/RCV Buffer sizes. + int startRCVBUF = 0; + optSize = sizeof(startRCVBUF); + if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize)) + { + startRCVBUF = -1; + } + int startSNDBUF = 0; + optSize = sizeof(startSNDBUF); + if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize)) + { + startSNDBUF = -1; + } + + // SunOS will fail setsockopt() if the requested buffer size exceeds system + // maximum value. + // However, do not reduce the buffer size. + const int maxsize = 64000; + if (0 != + ::setsockopt( + m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) + { + int currentRCVBUF = 0; + optSize = sizeof(currentRCVBUF); + if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tRCVBUF, &optSize)) + { + currentRCVBUF = -1; + } + if (maxsize > currentRCVBUF) + { + ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize); + } + } + if (0 != + ::setsockopt( + m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) + { + int currentSNDBUF = 0; + optSize = sizeof(currentSNDBUF); + if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tSNDBUF, &optSize)) + { + currentSNDBUF = -1; + } + if (maxsize > currentSNDBUF) + { + ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize); + } + } + + // Retrieve ending SND/RCV Buffer sizes. + int endRCVBUF = 0; + optSize = sizeof(endRCVBUF); + if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize)) + { + endRCVBUF = -1; + } + int endSNDBUF = 0; + optSize = sizeof(endSNDBUF); + if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize)) + { + endSNDBUF = -1; + } + LOGC(kmlog.Debug, + log << "SO_RCVBUF:" + << " startRCVBUF=" << startRCVBUF << " m_mcfg.iUDPRcvBufSize=" << m_mcfg.iUDPRcvBufSize + << " endRCVBUF=" << endRCVBUF); + LOGC(kmlog.Debug, + log << "SO_SNDBUF:" + << " startSNDBUF=" << startSNDBUF << " m_mcfg.iUDPSndBufSize=" << m_mcfg.iUDPSndBufSize + << " endSNDBUF=" << endSNDBUF); + } +#elif defined(BSD) || TARGET_OS_MAC + // BSD system will fail setsockopt if the requested buffer size exceeds system maximum value + int maxsize = 64000; + if (0 != ::setsockopt( + m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) + ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize); + if (0 != ::setsockopt( + m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) + ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize); +#else + // for other systems, if requested is greated than maximum, the maximum value will be automactally used + if ((0 != + ::setsockopt( + m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) || + (0 != ::setsockopt( + m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #endif - // For specified IPv6 address, set IP_TOS ONLY WHEN it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) - { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_iIpToS, sizeof(m_iIpToS))) - { - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } - } - } - } + if (m_mcfg.iIpTTL != -1) + { + if (m_BindAddr.family() == AF_INET) + { + if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + else + { + // If IPv6 address is unspecified, set BOTH IP_TTL and IPV6_UNICAST_HOPS. + + // For specified IPv6 address, set IPV6_UNICAST_HOPS ONLY UNLESS it's an IPv4-mapped-IPv6 + if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || + !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + { + if (0 != + ::setsockopt( + m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + { + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + } + // For specified IPv6 address, set IP_TTL ONLY WHEN it's an IPv4-mapped-IPv6 + if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + { + if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + { + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + } + } + } + + if (m_mcfg.iIpToS != -1) + { + if (m_BindAddr.family() == AF_INET) + { + if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + else + { + // If IPv6 address is unspecified, set BOTH IP_TOS and IPV6_TCLASS. + +#ifdef IPV6_TCLASS + // For specified IPv6 address, set IPV6_TCLASS ONLY UNLESS it's an IPv4-mapped-IPv6 + if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || + !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + { + if (0 != ::setsockopt( + m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + { + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + } #endif + // For specified IPv6 address, set IP_TOS ONLY WHEN it's an IPv4-mapped-IPv6 + if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + { + if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + { + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + } + } + } + +#ifdef SRT_ENABLE_BINDTODEVICE + if (!m_mcfg.sBindToDevice.empty()) + { + if (m_BindAddr.family() != AF_INET) + { + LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE can only be set with AF_INET connections"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + if (0 != ::setsockopt( + m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size())) + { +#if ENABLE_LOGGING + char buf[255]; + const char* err = SysStrError(NET_ERROR, buf, 255); + LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << err); +#endif // ENABLE_LOGGING + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + } +#endif #ifdef UNIX - // Set non-blocking I/O - // UNIX does not support SO_RCVTIMEO - int opts = ::fcntl(m_iSocket, F_GETFL); - if (-1 == ::fcntl(m_iSocket, F_SETFL, opts | O_NONBLOCK)) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + // Set non-blocking I/O + // UNIX does not support SO_RCVTIMEO + int opts = ::fcntl(m_iSocket, F_GETFL); + if (-1 == ::fcntl(m_iSocket, F_SETFL, opts | O_NONBLOCK)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #elif defined(_WIN32) - u_long nonBlocking = 1; - if (0 != ioctlsocket (m_iSocket, FIONBIO, &nonBlocking)) - throw CUDTException (MJ_SETUP, MN_NORES, NET_ERROR); + u_long nonBlocking = 1; + if (0 != ioctlsocket(m_iSocket, FIONBIO, &nonBlocking)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #else - timeval tv; - tv.tv_sec = 0; -#if defined (BSD) || defined (OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - // Known BSD bug as the day I wrote this code. - // A small time out value will cause the socket to block forever. - tv.tv_usec = 10000; + timeval tv; + tv.tv_sec = 0; +#if defined(BSD) || TARGET_OS_MAC + // Known BSD bug as the day I wrote this code. + // A small time out value will cause the socket to block forever. + tv.tv_usec = 10000; #else - tv.tv_usec = 100; + tv.tv_usec = 100; #endif - // Set receiving time-out value - if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(timeval))) - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + // Set receiving time-out value + if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #endif } -void CChannel::close() const +void srt::CChannel::close() const { - #ifndef _WIN32 - ::close(m_iSocket); - #else - ::closesocket(m_iSocket); - #endif +#ifndef _WIN32 + ::close(m_iSocket); +#else + ::closesocket(m_iSocket); +#endif } -int CChannel::getSndBufSize() +int srt::CChannel::getSndBufSize() { - socklen_t size = sizeof(socklen_t); - ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char *)&m_iSndBufSize, &size); - return m_iSndBufSize; + socklen_t size = (socklen_t)sizeof m_mcfg.iUDPSndBufSize; + ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_mcfg.iUDPSndBufSize, &size); + return m_mcfg.iUDPSndBufSize; } -int CChannel::getRcvBufSize() +int srt::CChannel::getRcvBufSize() { - socklen_t size = sizeof(socklen_t); - ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char *)&m_iRcvBufSize, &size); - return m_iRcvBufSize; + socklen_t size = (socklen_t)sizeof m_mcfg.iUDPRcvBufSize; + ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_mcfg.iUDPRcvBufSize, &size); + return m_mcfg.iUDPRcvBufSize; } -void CChannel::setSndBufSize(int size) +void srt::CChannel::setConfig(const CSrtMuxerConfig& config) { - m_iSndBufSize = size; + m_mcfg = config; } -void CChannel::setRcvBufSize(int size) +int srt::CChannel::getIpTTL() const { - m_iRcvBufSize = size; -} + if (m_iSocket == INVALID_SOCKET) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); -void CChannel::setIpV6Only(int ipV6Only) -{ - m_iIpV6Only = ipV6Only; + socklen_t size = (socklen_t)sizeof m_mcfg.iIpTTL; + if (m_BindAddr.family() == AF_INET) + { + ::getsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (char*)&m_mcfg.iIpTTL, &size); + } + else if (m_BindAddr.family() == AF_INET6) + { + ::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char*)&m_mcfg.iIpTTL, &size); + } + else + { + // If family is unspecified, the socket probably doesn't exist. + LOGC(kmlog.Error, log << "IPE: CChannel::getIpTTL called with unset family"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + return m_mcfg.iIpTTL; } -#ifdef SRT_ENABLE_IPOPTS -int CChannel::getIpTTL() const +int srt::CChannel::getIpToS() const { - socklen_t size = sizeof(m_iIpTTL); - if (m_iIPversion == AF_INET) - { - ::getsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (char *)&m_iIpTTL, &size); - } - else - { - ::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&m_iIpTTL, &size); - } - return m_iIpTTL; -} + if (m_iSocket == INVALID_SOCKET) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); -int CChannel::getIpToS() const -{ - socklen_t size = sizeof(m_iIpToS); - if (m_iIPversion == AF_INET) - { - ::getsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (char *)&m_iIpToS, &size); - } - else - { + socklen_t size = (socklen_t)sizeof m_mcfg.iIpToS; + if (m_BindAddr.family() == AF_INET) + { + ::getsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (char*)&m_mcfg.iIpToS, &size); + } + else if (m_BindAddr.family() == AF_INET6) + { #ifdef IPV6_TCLASS - ::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (char *)&m_iIpToS, &size); + ::getsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (char*)&m_mcfg.iIpToS, &size); #endif - } - return m_iIpToS; -} - -void CChannel::setIpTTL(int ttl) -{ - m_iIpTTL = ttl; + } + else + { + // If family is unspecified, the socket probably doesn't exist. + LOGC(kmlog.Error, log << "IPE: CChannel::getIpToS called with unset family"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + return m_mcfg.iIpToS; } -void CChannel::setIpToS(int tos) +#ifdef SRT_ENABLE_BINDTODEVICE +bool srt::CChannel::getBind(char* dst, size_t len) { - m_iIpToS = tos; + if (m_iSocket == INVALID_SOCKET) + return false; // No socket to get data from + + // Try to obtain it directly from the function. If not possible, + // then return from internal data. + socklen_t length = len; + int res = ::getsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, dst, &length); + if (res == -1) + return false; // Happens on Linux v < 3.8 + + // For any case + dst[length] = 0; + return true; } - #endif -int CChannel::ioctlQuery(int SRT_ATR_UNUSED type) const +int srt::CChannel::ioctlQuery(int type SRT_ATR_UNUSED) const { -#ifdef unix +#if defined(unix) || defined(__APPLE__) int value = 0; - int res = ::ioctl(m_iSocket, type, &value); - if ( res != -1 ) + int res = ::ioctl(m_iSocket, type, &value); + if (res != -1) return value; #endif return -1; } -int CChannel::sockoptQuery(int SRT_ATR_UNUSED level, int SRT_ATR_UNUSED option) const +int srt::CChannel::sockoptQuery(int level SRT_ATR_UNUSED, int option SRT_ATR_UNUSED) const { -#ifdef unix - int value = 0; - socklen_t len = sizeof (int); - int res = ::getsockopt(m_iSocket, level, option, &value, &len); - if ( res != -1 ) +#if defined(unix) || defined(__APPLE__) + int value = 0; + socklen_t len = sizeof(int); + int res = ::getsockopt(m_iSocket, level, option, &value, &len); + if (res != -1) return value; #endif return -1; } -void CChannel::getSockAddr(sockaddr* addr) const +void srt::CChannel::getSockAddr(sockaddr_any& w_addr) const { - socklen_t namelen = m_iSockAddrSize; - ::getsockname(m_iSocket, addr, &namelen); + // The getsockname function requires only to have enough target + // space to copy the socket name, it doesn't have to be correlated + // with the address family. So the maximum space for any name, + // regardless of the family, does the job. + socklen_t namelen = (socklen_t)w_addr.storage_size(); + ::getsockname(m_iSocket, (w_addr.get()), (&namelen)); + w_addr.len = namelen; } -void CChannel::getPeerAddr(sockaddr* addr) const +void srt::CChannel::getPeerAddr(sockaddr_any& w_addr) const { - socklen_t namelen = m_iSockAddrSize; - ::getpeername(m_iSocket, addr, &namelen); + socklen_t namelen = (socklen_t)w_addr.storage_size(); + ::getpeername(m_iSocket, (w_addr.get()), (&namelen)); + w_addr.len = namelen; } - -int CChannel::sendto(const sockaddr* addr, CPacket& packet) const +int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet) const { -#if ENABLE_HEAVY_LOGGING - std::ostringstream spec; - - if (packet.isControl()) - { - spec << " type=CONTROL" - << " cmd=" << MessageTypeStr(packet.getType(), packet.getExtendedType()) - << " arg=" << packet.header(SRT_PH_MSGNO); - } - else - { - spec << " type=DATA" - << " %" << packet.getSeqNo() - << " msgno=" << MSGNO_SEQ::unwrap(packet.m_iMsgNo) - << packet.MessageFlagStr() - << " !" << BufferStamp(packet.m_pcData, packet.getLength()); - } - - LOGC(mglog.Debug, log << "CChannel::sendto: SENDING NOW DST=" << SockaddrToString(addr) - << " target=@" << packet.m_iID - << " size=" << packet.getLength() - << " pkt.ts=" << FormatTime(packet.m_iTimeStamp) - << spec.str()); -#endif + HLOGC(kslog.Debug, + log << "CChannel::sendto: SENDING NOW DST=" << addr.str() << " target=@" << packet.m_iID + << " size=" << packet.getLength() << " pkt.ts=" << packet.m_iTimeStamp << " " << packet.Info()); #ifdef SRT_TEST_FAKE_LOSS @@ -451,18 +624,18 @@ int CChannel::sendto(const sockaddr* addr, CPacket& packet) const #undef FAKELOSS_STRING #undef FAKELOSS_WRAP - static int dcounter = 0; + static int dcounter = 0; static int flwcounter = 0; struct FakelossConfig { - pair config; + pair config; FakelossConfig(const char* f) { vector out; Split(f, '+', back_inserter(out)); - config.first = atoi(out[0].c_str()); + config.first = atoi(out[0].c_str()); config.second = out.size() > 1 ? atoi(out[1].c_str()) : 8; } }; @@ -470,31 +643,29 @@ int CChannel::sendto(const sockaddr* addr, CPacket& packet) const if (!packet.isControl()) { - if (dcounter == 0) - { - timeval tv; - gettimeofday(&tv, 0); - srand(tv.tv_usec & 0xFFFF); - } ++dcounter; if (flwcounter) { // This is a counter of how many packets in a row shall be lost --flwcounter; - HLOGC(mglog.Debug, log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (" << flwcounter << " more to drop)"); + HLOGC(kslog.Debug, + log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (" << flwcounter + << " more to drop)"); return packet.getLength(); // fake successful sendinf } if (dcounter > 8) { // Make a random number in the range between 8 and 24 - int rnd = rand() % 16 + SRT_TEST_FAKE_LOSS; + const int rnd = srt::sync::genRandomInt(8, 24); if (dcounter > rnd) { dcounter = 1; - HLOGC(mglog.Debug, log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (will drop " << fakeloss.config.first << " more)"); + HLOGC(kslog.Debug, + log << "CChannel: TEST: FAKE LOSS OF %" << packet.getSeqNo() << " (will drop " + << fakeloss.config.first << " more)"); flwcounter = fakeloss.config.first; return packet.getLength(); // fake successful sendinf } @@ -503,80 +674,53 @@ int CChannel::sendto(const sockaddr* addr, CPacket& packet) const #endif - // convert control information into network order - // XXX USE HtoNLA! - if (packet.isControl()) - for (ptrdiff_t i = 0, n = packet.getLength() / 4; i < n; ++i) - *((uint32_t *)packet.m_pcData + i) = htonl(*((uint32_t *)packet.m_pcData + i)); - - // convert packet header into network order - //for (int j = 0; j < 4; ++ j) - // packet.m_nHeader[j] = htonl(packet.m_nHeader[j]); - uint32_t* p = packet.m_nHeader; - for (int j = 0; j < 4; ++ j) - { - *p = htonl(*p); - ++ p; - } - - #ifndef _WIN32 - msghdr mh; - mh.msg_name = (sockaddr*)addr; - mh.msg_namelen = m_iSockAddrSize; - mh.msg_iov = (iovec*)packet.m_PacketVector; - mh.msg_iovlen = 2; - mh.msg_control = NULL; - mh.msg_controllen = 0; - mh.msg_flags = 0; - - int res = ::sendmsg(m_iSocket, &mh, 0); - #else - DWORD size = (DWORD) (CPacket::HDR_SIZE + packet.getLength()); - int addrsize = m_iSockAddrSize; - int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr, addrsize, NULL, NULL); - res = (0 == res) ? size : -1; - #endif - - // convert back into local host order - //for (int k = 0; k < 4; ++ k) - // packet.m_nHeader[k] = ntohl(packet.m_nHeader[k]); - p = packet.m_nHeader; - for (int k = 0; k < 4; ++ k) - { - *p = ntohl(*p); - ++ p; - } - - if (packet.isControl()) - { - for (ptrdiff_t l = 0, n = packet.getLength() / 4; l < n; ++ l) - *((uint32_t *)packet.m_pcData + l) = ntohl(*((uint32_t *)packet.m_pcData + l)); - } - - return res; + // convert control information into network order + packet.toNL(); + +#ifndef _WIN32 + msghdr mh; + mh.msg_name = (sockaddr*)&addr; + mh.msg_namelen = addr.size(); + mh.msg_iov = (iovec*)packet.m_PacketVector; + mh.msg_iovlen = 2; + mh.msg_control = NULL; + mh.msg_controllen = 0; + mh.msg_flags = 0; + + const int res = ::sendmsg(m_iSocket, &mh, 0); +#else + DWORD size = (DWORD)(CPacket::HDR_SIZE + packet.getLength()); + int addrsize = addr.size(); + int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, NULL, NULL); + res = (0 == res) ? size : -1; +#endif + + packet.toHL(); + + return res; } -EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const +srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const { - EReadStatus status = RST_OK; - int msg_flags = 0; - int recv_size = -1; + EReadStatus status = RST_OK; + int msg_flags = 0; + int recv_size = -1; #if defined(UNIX) || defined(_WIN32) - fd_set set; + fd_set set; timeval tv; FD_ZERO(&set); FD_SET(m_iSocket, &set); - tv.tv_sec = 0; - tv.tv_usec = 10000; - const int select_ret = ::select((int) m_iSocket + 1, &set, NULL, &set, &tv); + tv.tv_sec = 0; + tv.tv_usec = 10000; + const int select_ret = ::select((int)m_iSocket + 1, &set, NULL, &set, &tv); #else - const int select_ret = 1; // the socket is expected to be in the blocking mode itself + const int select_ret = 1; // the socket is expected to be in the blocking mode itself #endif - if (select_ret == 0) // timeout + if (select_ret == 0) // timeout { - packet.setLength(-1); + w_packet.setLength(-1); return RST_AGAIN; } @@ -584,15 +728,15 @@ EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const if (select_ret > 0) { msghdr mh; - mh.msg_name = addr; - mh.msg_namelen = m_iSockAddrSize; - mh.msg_iov = packet.m_PacketVector; - mh.msg_iovlen = 2; - mh.msg_control = NULL; + mh.msg_name = (w_addr.get()); + mh.msg_namelen = w_addr.size(); + mh.msg_iov = (w_packet.m_PacketVector); + mh.msg_iovlen = 2; + mh.msg_control = NULL; mh.msg_controllen = 0; - mh.msg_flags = 0; + mh.msg_flags = 0; - recv_size = ::recvmsg(m_iSocket, &mh, 0); + recv_size = ::recvmsg(m_iSocket, (&mh), 0); msg_flags = mh.msg_flags; } @@ -621,13 +765,14 @@ EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const if (select_ret == -1 || recv_size == -1) { const int err = NET_ERROR; - if (err == EAGAIN || err == EINTR || err == ECONNREFUSED) // For EAGAIN, this isn't an error, just a useless call. + if (err == EAGAIN || err == EINTR || + err == ECONNREFUSED) // For EAGAIN, this isn't an error, just a useless call. { status = RST_AGAIN; } else { - HLOGC(mglog.Debug, log << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]"); + HLOGC(krlog.Debug, log << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]"); status = RST_ERROR; } @@ -651,15 +796,23 @@ EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const // value one Windows than 0, unless this procedure below is rewritten // to use WSARecvMsg(). - int recv_ret = SOCKET_ERROR; - DWORD flag = 0; + int recv_ret = SOCKET_ERROR; + DWORD flag = 0; - if (select_ret > 0) // the total number of socket handles that are ready + if (select_ret > 0) // the total number of socket handles that are ready { - DWORD size = (DWORD) (CPacket::HDR_SIZE + packet.getLength()); - int addrsize = m_iSockAddrSize; - - recv_ret = ::WSARecvFrom(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, &flag, addr, &addrsize, NULL, NULL); + DWORD size = (DWORD)(CPacket::HDR_SIZE + w_packet.getLength()); + int addrsize = w_addr.size(); + + recv_ret = ::WSARecvFrom(m_iSocket, + ((LPWSABUF)w_packet.m_PacketVector), + 2, + (&size), + (&flag), + (w_addr.get()), + (&addrsize), + NULL, + NULL); if (recv_ret == 0) recv_size = size; } @@ -674,19 +827,12 @@ EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const // WSAETIMEDOUT, which isn't mentioned in the documentation of WSARecvFrom at all. // // These below errors are treated as "fatal", all others are treated as "again". - static const int fatals [] = - { - WSAEFAULT, - WSAEINVAL, - WSAENETDOWN, - WSANOTINITIALISED, - WSA_OPERATION_ABORTED - }; + static const int fatals[] = {WSAEFAULT, WSAEINVAL, WSAENETDOWN, WSANOTINITIALISED, WSA_OPERATION_ABORTED}; static const int* fatals_end = fatals + Size(fatals); - const int err = NET_ERROR; + const int err = NET_ERROR; if (std::find(fatals, fatals_end, err) != fatals_end) { - HLOGC(mglog.Debug, log << CONID() << "(sys)WSARecvFrom: " << SysStrError(err) << " [" << err << "]"); + HLOGC(krlog.Debug, log << CONID() << "(sys)WSARecvFrom: " << SysStrError(err) << " [" << err << "]"); status = RST_ERROR; } else @@ -702,12 +848,12 @@ EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const msg_flags = 1; #endif - // Sanity check for a case when it didn't fill in even the header if (size_t(recv_size) < CPacket::HDR_SIZE) { status = RST_AGAIN; - HLOGC(mglog.Debug, log << CONID() << "POSSIBLE ATTACK: received too short packet with " << recv_size << " bytes"); + HLOGC(krlog.Debug, + log << CONID() << "POSSIBLE ATTACK: received too short packet with " << recv_size << " bytes"); goto Return_error; } @@ -728,38 +874,39 @@ EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const // When this happens, then you have at best a fragment of the buffer and it's // useless anyway. This is solved by dropping the packet and fake that no // packet was received, so the packet will be then retransmitted. - if ( msg_flags != 0 ) + if (msg_flags != 0) { - HLOGC(mglog.Debug, log << CONID() << "NET ERROR: packet size=" << recv_size - << " msg_flags=0x" << hex << msg_flags << ", possibly MSG_TRUNC (0x" << hex << int(MSG_TRUNC) << ")"); + HLOGC(krlog.Debug, + log << CONID() << "NET ERROR: packet size=" << recv_size << " msg_flags=0x" << hex << msg_flags + << ", possibly MSG_TRUNC)"); status = RST_AGAIN; goto Return_error; } - packet.setLength(recv_size - CPacket::HDR_SIZE); + w_packet.setLength(recv_size - CPacket::HDR_SIZE); // convert back into local host order // XXX use NtoHLA(). - //for (int i = 0; i < 4; ++ i) - // packet.m_nHeader[i] = ntohl(packet.m_nHeader[i]); + // for (int i = 0; i < 4; ++ i) + // w_packet.m_nHeader[i] = ntohl(w_packet.m_nHeader[i]); { - uint32_t* p = packet.m_nHeader; - for (size_t i = 0; i < SRT_PH__SIZE; ++ i) + uint32_t* p = w_packet.m_nHeader; + for (size_t i = 0; i < SRT_PH_E_SIZE; ++i) { *p = ntohl(*p); - ++ p; + ++p; } } - if (packet.isControl()) + if (w_packet.isControl()) { - for (size_t j = 0, n = packet.getLength() / sizeof (uint32_t); j < n; ++ j) - *((uint32_t *)packet.m_pcData + j) = ntohl(*((uint32_t *)packet.m_pcData + j)); + for (size_t j = 0, n = w_packet.getLength() / sizeof(uint32_t); j < n; ++j) + *((uint32_t*)w_packet.m_pcData + j) = ntohl(*((uint32_t*)w_packet.m_pcData + j)); } return RST_OK; Return_error: - packet.setLength(-1); + w_packet.setLength(-1); return status; } diff --git a/trunk/3rdparty/srt-1-fit/srtcore/channel.h b/trunk/3rdparty/srt-1-fit/srtcore/channel.h index 0efdcaa8fe..0255102fe7 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/channel.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/channel.h @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,138 +50,118 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_CHANNEL_H__ -#define __UDT_CHANNEL_H__ - +#ifndef INC_SRT_CHANNEL_H +#define INC_SRT_CHANNEL_H +#include "platform_sys.h" #include "udt.h" #include "packet.h" +#include "socketconfig.h" #include "netinet_any.h" -class CChannel +namespace srt { -public: - - // XXX There's currently no way to access the socket ID set for - // whatever the channel is currently working for. Required to find - // some way to do this, possibly by having a "reverse pointer". - // Currently just "unimplemented". - std::string CONID() const { return ""; } - - CChannel(); - CChannel(int version); - ~CChannel(); - - /// Open a UDP channel. - /// @param [in] addr The local address that UDP will use. - - void open(const sockaddr* addr = NULL); - /// Open a UDP channel based on an existing UDP socket. - /// @param [in] udpsock UDP socket descriptor. - - void attach(UDPSOCKET udpsock); - - /// Disconnect and close the UDP entity. +class CChannel +{ + void createSocket(int family); - void close() const; +public: + // XXX There's currently no way to access the socket ID set for + // whatever the channel is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } - /// Get the UDP sending buffer size. - /// @return Current UDP sending buffer size. + CChannel(); + ~CChannel(); - int getSndBufSize(); + /// Open a UDP channel. + /// @param [in] addr The local address that UDP will use. - /// Get the UDP receiving buffer size. - /// @return Current UDP receiving buffer size. + void open(const sockaddr_any& addr); - int getRcvBufSize(); + void open(int family); - /// Set the UDP sending buffer size. - /// @param [in] size expected UDP sending buffer size. + /// Open a UDP channel based on an existing UDP socket. + /// @param [in] udpsock UDP socket descriptor. - void setSndBufSize(int size); + void attach(UDPSOCKET udpsock, const sockaddr_any& adr); - /// Set the UDP receiving buffer size. - /// @param [in] size expected UDP receiving buffer size. + /// Disconnect and close the UDP entity. - void setRcvBufSize(int size); + void close() const; - /// Set the IPV6ONLY option. - /// @param [in] IPV6ONLY value. + /// Get the UDP sending buffer size. + /// @return Current UDP sending buffer size. - void setIpV6Only(int ipV6Only); + int getSndBufSize(); - /// Query the socket address that the channel is using. - /// @param [out] addr pointer to store the returned socket address. + /// Get the UDP receiving buffer size. + /// @return Current UDP receiving buffer size. - void getSockAddr(sockaddr* addr) const; + int getRcvBufSize(); - /// Query the peer side socket address that the channel is connect to. - /// @param [out] addr pointer to store the returned socket address. + /// Query the socket address that the channel is using. + /// @param [out] addr pointer to store the returned socket address. - void getPeerAddr(sockaddr* addr) const; + void getSockAddr(sockaddr_any& addr) const; - /// Send a packet to the given address. - /// @param [in] addr pointer to the destination address. - /// @param [in] packet reference to a CPacket entity. - /// @return Actual size of data sent. + /// Query the peer side socket address that the channel is connect to. + /// @param [out] addr pointer to store the returned socket address. - int sendto(const sockaddr* addr, CPacket& packet) const; + void getPeerAddr(sockaddr_any& addr) const; - /// Receive a packet from the channel and record the source address. - /// @param [in] addr pointer to the source address. - /// @param [in] packet reference to a CPacket entity. - /// @return Actual size of data received. + /// Send a packet to the given address. + /// @param [in] addr pointer to the destination address. + /// @param [in] packet reference to a CPacket entity. + /// @return Actual size of data sent. - EReadStatus recvfrom(sockaddr* addr, CPacket& packet) const; + int sendto(const sockaddr_any& addr, srt::CPacket& packet) const; -#ifdef SRT_ENABLE_IPOPTS - /// Set the IP TTL. - /// @param [in] ttl IP Time To Live. - /// @return none. + /// Receive a packet from the channel and record the source address. + /// @param [in] addr pointer to the source address. + /// @param [in] packet reference to a CPacket entity. + /// @return Actual size of data received. - void setIpTTL(int ttl); + EReadStatus recvfrom(sockaddr_any& addr, srt::CPacket& packet) const; - /// Set the IP Type of Service. - /// @param [in] tos IP Type of Service. + void setConfig(const CSrtMuxerConfig& config); - void setIpToS(int tos); + /// Get the IP TTL. + /// @param [in] ttl IP Time To Live. + /// @return TTL. - /// Get the IP TTL. - /// @param [in] ttl IP Time To Live. - /// @return TTL. + int getIpTTL() const; - int getIpTTL() const; + /// Get the IP Type of Service. + /// @return ToS. - /// Get the IP Type of Service. - /// @return ToS. + int getIpToS() const; - int getIpToS() const; +#ifdef SRT_ENABLE_BINDTODEVICE + bool getBind(char* dst, size_t len); #endif - int ioctlQuery(int type) const; - int sockoptQuery(int level, int option) const; + int ioctlQuery(int type) const; + int sockoptQuery(int level, int option) const; - const sockaddr* bindAddress() { return &m_BindAddr; } - const sockaddr_any& bindAddressAny() { return m_BindAddr; } + const sockaddr* bindAddress() { return m_BindAddr.get(); } + const sockaddr_any& bindAddressAny() { return m_BindAddr; } private: - void setUDPSockOpt(); + void setUDPSockOpt(); private: - const int m_iIPversion; // IP version - int m_iSockAddrSize; // socket address structure size (pre-defined to avoid run-time test) + UDPSOCKET m_iSocket; // socket descriptor - UDPSOCKET m_iSocket; // socket descriptor -#ifdef SRT_ENABLE_IPOPTS - int m_iIpTTL; - int m_iIpToS; -#endif - int m_iSndBufSize; // UDP sending buffer size - int m_iRcvBufSize; // UDP receiving buffer size - int m_iIpV6Only; // IPV6_V6ONLY option (-1 if not set) - sockaddr_any m_BindAddr; + // Mutable because when querying original settings + // this comprises the cache for extracted values, + // although the object itself isn't considered modified. + mutable CSrtMuxerConfig m_mcfg; // Note: ReuseAddr is unused and ineffective. + sockaddr_any m_BindAddr; }; +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/common.cpp b/trunk/3rdparty/srt-1-fit/srtcore/common.cpp index 5614de4afc..78014d7921 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/common.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/common.cpp @@ -50,618 +50,76 @@ modified by Haivision Systems Inc. *****************************************************************************/ - -#ifndef _WIN32 - #include - #include - #include - #if __APPLE__ - #include "TargetConditionals.h" - #endif - #if defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - #include - #endif -#else - #include - #include - #include -#ifndef __MINGW__ - #include -#endif -#endif +#define SRT_IMPORT_TIME 1 +#include "platform_sys.h" #include #include #include #include #include -#include "srt.h" +#include +#include +#include "udt.h" #include "md5.h" #include "common.h" +#include "netinet_any.h" #include "logging.h" +#include "packet.h" #include "threadname.h" #include // SysStrError -bool CTimer::m_bUseMicroSecond = false; -uint64_t CTimer::s_ullCPUFrequency = CTimer::readCPUFrequency(); - -pthread_mutex_t CTimer::m_EventLock = PTHREAD_MUTEX_INITIALIZER; -pthread_cond_t CTimer::m_EventCond = PTHREAD_COND_INITIALIZER; - -CTimer::CTimer(): -m_ullSchedTime_tk(), -m_TickCond(), -m_TickLock() -{ - pthread_mutex_init(&m_TickLock, NULL); - -#if ENABLE_MONOTONIC_CLOCK - pthread_condattr_t CondAttribs; - pthread_condattr_init(&CondAttribs); - pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC); - pthread_cond_init(&m_TickCond, &CondAttribs); -#else - pthread_cond_init(&m_TickCond, NULL); -#endif -} - -CTimer::~CTimer() -{ - pthread_mutex_destroy(&m_TickLock); - pthread_cond_destroy(&m_TickCond); -} - -void CTimer::rdtsc(uint64_t &x) -{ - if (m_bUseMicroSecond) - { - x = getTime(); - return; - } - - #ifdef IA32 - uint32_t lval, hval; - //asm volatile ("push %eax; push %ebx; push %ecx; push %edx"); - //asm volatile ("xor %eax, %eax; cpuid"); - asm volatile ("rdtsc" : "=a" (lval), "=d" (hval)); - //asm volatile ("pop %edx; pop %ecx; pop %ebx; pop %eax"); - x = hval; - x = (x << 32) | lval; - #elif defined(IA64) - asm ("mov %0=ar.itc" : "=r"(x) :: "memory"); - #elif defined(AMD64) - uint32_t lval, hval; - asm ("rdtsc" : "=a" (lval), "=d" (hval)); - x = hval; - x = (x << 32) | lval; - #elif defined(_WIN32) - // This function should not fail, because we checked the QPC - // when calling to QueryPerformanceFrequency. If it failed, - // the m_bUseMicroSecond was set to true. - QueryPerformanceCounter((LARGE_INTEGER *)&x); - #elif defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - x = mach_absolute_time(); - #else - // use system call to read time clock for other archs - x = getTime(); - #endif -} - -uint64_t CTimer::readCPUFrequency() -{ - uint64_t frequency = 1; // 1 tick per microsecond. - -#if defined(IA32) || defined(IA64) || defined(AMD64) - uint64_t t1, t2; - - rdtsc(t1); - timespec ts; - ts.tv_sec = 0; - ts.tv_nsec = 100000000; - nanosleep(&ts, NULL); - rdtsc(t2); - - // CPU clocks per microsecond - frequency = (t2 - t1) / 100000; -#elif defined(_WIN32) - LARGE_INTEGER counts_per_sec; - if (QueryPerformanceFrequency(&counts_per_sec)) - frequency = counts_per_sec.QuadPart / 1000000; -#elif defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - mach_timebase_info_data_t info; - mach_timebase_info(&info); - frequency = info.denom * uint64_t(1000) / info.numer; -#endif - - // Fall back to microsecond if the resolution is not high enough. - if (frequency < 10) - { - frequency = 1; - m_bUseMicroSecond = true; - } - return frequency; -} - -uint64_t CTimer::getCPUFrequency() -{ - return s_ullCPUFrequency; -} - -void CTimer::sleep(uint64_t interval_tk) -{ - uint64_t t; - rdtsc(t); - - // sleep next "interval" time - sleepto(t + interval_tk); -} - -void CTimer::sleepto(uint64_t nexttime_tk) -{ - // Use class member such that the method can be interrupted by others - m_ullSchedTime_tk = nexttime_tk; - - uint64_t t; - rdtsc(t); - -#if USE_BUSY_WAITING -#if defined(_WIN32) - const uint64_t threshold_us = 10000; // 10 ms on Windows: bad accuracy of timers -#else - const uint64_t threshold_us = 1000; // 1 ms on non-Windows platforms -#endif -#endif - - while (t < m_ullSchedTime_tk) - { -#if USE_BUSY_WAITING - uint64_t wait_us = (m_ullSchedTime_tk - t) / s_ullCPUFrequency; - if (wait_us <= 2 * threshold_us) - break; - wait_us -= threshold_us; -#else - const uint64_t wait_us = (m_ullSchedTime_tk - t) / s_ullCPUFrequency; - if (wait_us == 0) - break; -#endif - - timespec timeout; -#if ENABLE_MONOTONIC_CLOCK - clock_gettime(CLOCK_MONOTONIC, &timeout); - const uint64_t time_us = timeout.tv_sec * uint64_t(1000000) + (timeout.tv_nsec / 1000) + wait_us; - timeout.tv_sec = time_us / 1000000; - timeout.tv_nsec = (time_us % 1000000) * 1000; -#else - timeval now; - gettimeofday(&now, 0); - const uint64_t time_us = now.tv_sec * uint64_t(1000000) + now.tv_usec + wait_us; - timeout.tv_sec = time_us / 1000000; - timeout.tv_nsec = (time_us % 1000000) * 1000; -#endif - - THREAD_PAUSED(); - pthread_mutex_lock(&m_TickLock); - pthread_cond_timedwait(&m_TickCond, &m_TickLock, &timeout); - pthread_mutex_unlock(&m_TickLock); - THREAD_RESUMED(); - - rdtsc(t); - } - -#if USE_BUSY_WAITING - while (t < m_ullSchedTime_tk) - { -#ifdef IA32 - __asm__ volatile ("pause; rep; nop; nop; nop; nop; nop;"); -#elif IA64 - __asm__ volatile ("nop 0; nop 0; nop 0; nop 0; nop 0;"); -#elif AMD64 - __asm__ volatile ("nop; nop; nop; nop; nop;"); -#elif defined(_WIN32) && !defined(__MINGW__) - __nop(); - __nop(); - __nop(); - __nop(); - __nop(); -#endif - - rdtsc(t); - } -#endif -} - -void CTimer::interrupt() -{ - // schedule the sleepto time to the current CCs, so that it will stop - rdtsc(m_ullSchedTime_tk); - tick(); -} - -void CTimer::tick() -{ - pthread_cond_signal(&m_TickCond); -} - -uint64_t CTimer::getTime() -{ - // XXX Do further study on that. Currently Cygwin is also using gettimeofday, - // however Cygwin platform is supported only for testing purposes. - - //For other systems without microsecond level resolution, add to this conditional compile -#if defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - // Otherwise we will have an infinite recursive functions calls - if (m_bUseMicroSecond == false) - { - uint64_t x; - rdtsc(x); - return x / s_ullCPUFrequency; - } - // Specific fix may be necessary if rdtsc is not available either. - // Going further on Apple platforms might cause issue, fixed with PR #301. - // But it is very unlikely for the latest platforms. -#endif - timeval t; - gettimeofday(&t, 0); - return t.tv_sec * uint64_t(1000000) + t.tv_usec; -} - -void CTimer::triggerEvent() -{ - pthread_cond_signal(&m_EventCond); -} - -CTimer::EWait CTimer::waitForEvent() -{ - timeval now; - timespec timeout; - gettimeofday(&now, 0); - if (now.tv_usec < 990000) - { - timeout.tv_sec = now.tv_sec; - timeout.tv_nsec = (now.tv_usec + 10000) * 1000; - } - else - { - timeout.tv_sec = now.tv_sec + 1; - timeout.tv_nsec = (now.tv_usec + 10000 - 1000000) * 1000; - } - pthread_mutex_lock(&m_EventLock); - int reason = pthread_cond_timedwait(&m_EventCond, &m_EventLock, &timeout); - pthread_mutex_unlock(&m_EventLock); - - return reason == ETIMEDOUT ? WT_TIMEOUT : reason == 0 ? WT_EVENT : WT_ERROR; -} - -void CTimer::sleep() -{ - #ifndef _WIN32 - usleep(10); - #else - Sleep(1); - #endif -} - -int CTimer::condTimedWaitUS(pthread_cond_t* cond, pthread_mutex_t* mutex, uint64_t delay) { - timeval now; - gettimeofday(&now, 0); - const uint64_t time_us = now.tv_sec * uint64_t(1000000) + now.tv_usec + delay; - timespec timeout; - timeout.tv_sec = time_us / 1000000; - timeout.tv_nsec = (time_us % 1000000) * 1000; - - return pthread_cond_timedwait(cond, mutex, &timeout); -} - - -// Automatically lock in constructor -CGuard::CGuard(pthread_mutex_t& lock, bool shouldwork): - m_Mutex(lock), - m_iLocked(-1) -{ - if (shouldwork) - m_iLocked = pthread_mutex_lock(&m_Mutex); -} - -// Automatically unlock in destructor -CGuard::~CGuard() -{ - if (m_iLocked == 0) - pthread_mutex_unlock(&m_Mutex); -} - -// After calling this on a scoped lock wrapper (CGuard), -// the mutex will be unlocked right now, and no longer -// in destructor -void CGuard::forceUnlock() -{ - if (m_iLocked == 0) - { - pthread_mutex_unlock(&m_Mutex); - m_iLocked = -1; - } -} - -int CGuard::enterCS(pthread_mutex_t& lock) -{ - return pthread_mutex_lock(&lock); -} - -int CGuard::leaveCS(pthread_mutex_t& lock) -{ - return pthread_mutex_unlock(&lock); -} +using namespace std; +using namespace srt::sync; +using namespace srt_logging; -void CGuard::createMutex(pthread_mutex_t& lock) +namespace srt_logging { - pthread_mutex_init(&lock, NULL); +extern Logger inlog; } -void CGuard::releaseMutex(pthread_mutex_t& lock) +namespace srt { - pthread_mutex_destroy(&lock); -} -void CGuard::createCond(pthread_cond_t& cond) -{ - pthread_cond_init(&cond, NULL); -} +const char* strerror_get_message(size_t major, size_t minor); +} // namespace srt -void CGuard::releaseCond(pthread_cond_t& cond) -{ - pthread_cond_destroy(&cond); -} -// -CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): +srt::CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): m_iMajor(major), m_iMinor(minor) { if (err == -1) - #ifndef _WIN32 - m_iErrno = errno; - #else - m_iErrno = GetLastError(); - #endif + m_iErrno = NET_ERROR; else m_iErrno = err; } -CUDTException::CUDTException(const CUDTException& e): -m_iMajor(e.m_iMajor), -m_iMinor(e.m_iMinor), -m_iErrno(e.m_iErrno), -m_strMsg() -{ -} - -CUDTException::~CUDTException() +const char* srt::CUDTException::getErrorMessage() const ATR_NOTHROW { + return strerror_get_message(m_iMajor, m_iMinor); } -const char* CUDTException::getErrorMessage() +std::string srt::CUDTException::getErrorString() const { - // translate "Major:Minor" code into text message. - - switch (m_iMajor) - { - case MJ_SUCCESS: - m_strMsg = "Success"; - break; - - case MJ_SETUP: - m_strMsg = "Connection setup failure"; - - switch (m_iMinor) - { - case MN_TIMEOUT: - m_strMsg += ": connection time out"; - break; - - case MN_REJECTED: - m_strMsg += ": connection rejected"; - break; - - case MN_NORES: - m_strMsg += ": unable to create/configure SRT socket"; - break; - - case MN_SECURITY: - m_strMsg += ": abort for security reasons"; - break; - - default: - break; - } - - break; - - case MJ_CONNECTION: - switch (m_iMinor) - { - case MN_CONNLOST: - m_strMsg = "Connection was broken"; - break; - - case MN_NOCONN: - m_strMsg = "Connection does not exist"; - break; - - default: - break; - } - - break; - - case MJ_SYSTEMRES: - m_strMsg = "System resource failure"; - - switch (m_iMinor) - { - case MN_THREAD: - m_strMsg += ": unable to create new threads"; - break; - - case MN_MEMORY: - m_strMsg += ": unable to allocate buffers"; - break; - - default: - break; - } - - break; - - case MJ_FILESYSTEM: - m_strMsg = "File system failure"; - - switch (m_iMinor) - { - case MN_SEEKGFAIL: - m_strMsg += ": cannot seek read position"; - break; - - case MN_READFAIL: - m_strMsg += ": failure in read"; - break; - - case MN_SEEKPFAIL: - m_strMsg += ": cannot seek write position"; - break; - - case MN_WRITEFAIL: - m_strMsg += ": failure in write"; - break; - - default: - break; - } - - break; - - case MJ_NOTSUP: - m_strMsg = "Operation not supported"; - - switch (m_iMinor) - { - case MN_ISBOUND: - m_strMsg += ": Cannot do this operation on a BOUND socket"; - break; - - case MN_ISCONNECTED: - m_strMsg += ": Cannot do this operation on a CONNECTED socket"; - break; - - case MN_INVAL: - m_strMsg += ": Bad parameters"; - break; - - case MN_SIDINVAL: - m_strMsg += ": Invalid socket ID"; - break; - - case MN_ISUNBOUND: - m_strMsg += ": Cannot do this operation on an UNBOUND socket"; - break; - - case MN_NOLISTEN: - m_strMsg += ": Socket is not in listening state"; - break; - - case MN_ISRENDEZVOUS: - m_strMsg += ": Listen/accept is not supported in rendezous connection setup"; - break; - - case MN_ISRENDUNBOUND: - m_strMsg += ": Cannot call connect on UNBOUND socket in rendezvous connection setup"; - break; - - case MN_INVALMSGAPI: - m_strMsg += ": Incorrect use of Message API (sendmsg/recvmsg)."; - break; - - case MN_INVALBUFFERAPI: - m_strMsg += ": Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)."; - break; - - case MN_BUSY: - m_strMsg += ": Another socket is already listening on the same port"; - break; - - case MN_XSIZE: - m_strMsg += ": Message is too large to send (it must be less than the SRT send buffer size)"; - break; - - case MN_EIDINVAL: - m_strMsg += ": Invalid epoll ID"; - break; - - default: - break; - } - - break; - - case MJ_AGAIN: - m_strMsg = "Non-blocking call failure"; - - switch (m_iMinor) - { - case MN_WRAVAIL: - m_strMsg += ": no buffer available for sending"; - break; - - case MN_RDAVAIL: - m_strMsg += ": no data available for reading"; - break; - - case MN_XMTIMEOUT: - m_strMsg += ": transmission timed out"; - break; - -#ifdef SRT_ENABLE_ECN - case MN_CONGESTION: - m_strMsg += ": early congestion notification"; - break; -#endif /* SRT_ENABLE_ECN */ - default: - break; - } - - break; - - case MJ_PEERERROR: - m_strMsg = "The peer side has signalled an error"; - - break; - - default: - m_strMsg = "Unknown error"; - } - - // Adding "errno" information - if ((MJ_SUCCESS != m_iMajor) && (0 < m_iErrno)) - { - m_strMsg += ": " + SysStrError(m_iErrno); - } - - return m_strMsg.c_str(); + return getErrorMessage(); } #define UDT_XCODE(mj, mn) (int(mj)*1000)+int(mn) -int CUDTException::getErrorCode() const +int srt::CUDTException::getErrorCode() const { return UDT_XCODE(m_iMajor, m_iMinor); } -int CUDTException::getErrno() const +int srt::CUDTException::getErrno() const { return m_iErrno; } -void CUDTException::clear() +void srt::CUDTException::clear() { m_iMajor = MJ_SUCCESS; m_iMinor = MN_NONE; @@ -671,7 +129,7 @@ void CUDTException::clear() #undef UDT_XCODE // -bool CIPAddress::ipcmp(const sockaddr* addr1, const sockaddr* addr2, int ver) +bool srt::CIPAddress::ipcmp(const sockaddr* addr1, const sockaddr* addr2, int ver) { if (AF_INET == ver) { @@ -699,46 +157,165 @@ bool CIPAddress::ipcmp(const sockaddr* addr1, const sockaddr* addr2, int ver) return false; } -void CIPAddress::ntop(const sockaddr* addr, uint32_t ip[4], int ver) +void srt::CIPAddress::ntop(const sockaddr_any& addr, uint32_t ip[4]) { - if (AF_INET == ver) - { - sockaddr_in* a = (sockaddr_in*)addr; - ip[0] = a->sin_addr.s_addr; - } - else - { - sockaddr_in6* a = (sockaddr_in6*)addr; + if (addr.family() == AF_INET) + { + // SRT internal format of IPv4 address. + // The IPv4 address is in the first field. The rest is 0. + ip[0] = addr.sin.sin_addr.s_addr; + ip[1] = ip[2] = ip[3] = 0; + } + else + { + const sockaddr_in6* a = &addr.sin6; ip[3] = (a->sin6_addr.s6_addr[15] << 24) + (a->sin6_addr.s6_addr[14] << 16) + (a->sin6_addr.s6_addr[13] << 8) + a->sin6_addr.s6_addr[12]; ip[2] = (a->sin6_addr.s6_addr[11] << 24) + (a->sin6_addr.s6_addr[10] << 16) + (a->sin6_addr.s6_addr[9] << 8) + a->sin6_addr.s6_addr[8]; ip[1] = (a->sin6_addr.s6_addr[7] << 24) + (a->sin6_addr.s6_addr[6] << 16) + (a->sin6_addr.s6_addr[5] << 8) + a->sin6_addr.s6_addr[4]; ip[0] = (a->sin6_addr.s6_addr[3] << 24) + (a->sin6_addr.s6_addr[2] << 16) + (a->sin6_addr.s6_addr[1] << 8) + a->sin6_addr.s6_addr[0]; - } + } } -void CIPAddress::pton(sockaddr* addr, const uint32_t ip[4], int ver) +namespace srt { +bool checkMappedIPv4(const uint16_t* addr) { - if (AF_INET == ver) - { - sockaddr_in* a = (sockaddr_in*)addr; - a->sin_addr.s_addr = ip[0]; - } - else - { - sockaddr_in6* a = (sockaddr_in6*)addr; - for (int i = 0; i < 4; ++ i) - { - a->sin6_addr.s6_addr[i * 4] = ip[i] & 0xFF; - a->sin6_addr.s6_addr[i * 4 + 1] = (unsigned char)((ip[i] & 0xFF00) >> 8); - a->sin6_addr.s6_addr[i * 4 + 2] = (unsigned char)((ip[i] & 0xFF0000) >> 16); - a->sin6_addr.s6_addr[i * 4 + 3] = (unsigned char)((ip[i] & 0xFF000000) >> 24); - } - } + static const uint16_t ipv4on6_model [8] = + { + 0, 0, 0, 0, 0, 0xFFFF, 0, 0 + }; + + // Compare only first 6 words. Remaining 2 words + // comprise the IPv4 address, if these first 6 match. + const uint16_t* mbegin = ipv4on6_model; + const uint16_t* mend = ipv4on6_model + 6; + + return std::equal(mbegin, mend, addr); +} } -using namespace std; +// XXX This has void return and the first argument is passed by reference. +// Consider simply returning sockaddr_any by value. +void srt::CIPAddress::pton(sockaddr_any& w_addr, const uint32_t ip[4], const sockaddr_any& peer) +{ + //using ::srt_logging::inlog; + uint32_t* target_ipv4_addr = NULL; + + if (peer.family() == AF_INET) + { + sockaddr_in* a = (&w_addr.sin); + target_ipv4_addr = (uint32_t*) &a->sin_addr.s_addr; + } + else // AF_INET6 + { + // Check if the peer address is a model of IPv4-mapped-on-IPv6. + // If so, it means that the `ip` array should be interpreted as IPv4. + const bool is_mapped_ipv4 = checkMappedIPv4((uint16_t*)peer.sin6.sin6_addr.s6_addr); + + sockaddr_in6* a = (&w_addr.sin6); + + // This whole above procedure was only in order to EXCLUDE the + // possibility of IPv4-mapped-on-IPv6. This below may only happen + // if BOTH peers are IPv6. Otherwise we have a situation of cross-IP + // version connection in which case the address in question is always + // IPv4 in various mapping formats. + if (!is_mapped_ipv4) + { + // Here both agent and peer use IPv6, in which case + // `ip` contains the full IPv6 address, so just copy + // it as is. + + // XXX Possibly, a simple + // memcpy( (a->sin6_addr.s6_addr), ip, 16); + // would do the same thing, and faster. The address in `ip`, + // even though coded here as uint32_t, is still big endian. + for (int i = 0; i < 4; ++ i) + { + a->sin6_addr.s6_addr[i * 4 + 0] = ip[i] & 0xFF; + a->sin6_addr.s6_addr[i * 4 + 1] = (unsigned char)((ip[i] & 0xFF00) >> 8); + a->sin6_addr.s6_addr[i * 4 + 2] = (unsigned char)((ip[i] & 0xFF0000) >> 16); + a->sin6_addr.s6_addr[i * 4 + 3] = (unsigned char)((ip[i] & 0xFF000000) >> 24); + } + return; // The address is written, nothing left to do. + } + + // + // IPv4 mapped on IPv6 + + // Here agent uses IPv6 with IPPROTO_IPV6/IPV6_V6ONLY == 0 + // In this case, the address in `ip` is always an IPv4, + // although we are not certain as to whether it's using the + // IPv6 encoding (0::FFFF:IPv4) or SRT encoding (IPv4::0); + // this must be extra determined. + // + // Unfortunately, sockaddr_in6 doesn't give any straightforward + // method for it, although the official size of a single element + // of the IPv6 address is 16-bit. + + memset((a->sin6_addr.s6_addr), 0, sizeof a->sin6_addr.s6_addr); + + // The sin6_addr.s6_addr32 is non that portable to use here. + uint32_t* paddr32 = (uint32_t*)a->sin6_addr.s6_addr; + uint16_t* paddr16 = (uint16_t*)a->sin6_addr.s6_addr; + + // layout: of IPv4 address 192.168.128.2 + // 16-bit: + // [0000: 0000: 0000: 0000: 0000: FFFF: 192.168:128.2] + // 8-bit + // [00/00/00/00/00/00/00/00/00/00/FF/FF/192/168/128/2] + // 32-bit + // [00000000 && 00000000 && 0000FFFF && 192.168.128.2] + + // Spreading every 16-bit word separately to avoid endian dilemmas + paddr16[2 * 2 + 1] = 0xFFFF; + + target_ipv4_addr = &paddr32[3]; + } + // Now we have two possible formats of encoding the IPv4 address: + // 1. If peer is IPv4, it's IPv4::0 + // 2. If peer is IPv6, it's 0::FFFF:IPv4. + // + // Has any other possibility happen here, copy an empty address, + // which will be the only sign of an error. + + const uint16_t* peeraddr16 = (uint16_t*)ip; + const bool is_mapped_ipv4 = checkMappedIPv4(peeraddr16); + + if (is_mapped_ipv4) + { + *target_ipv4_addr = ip[3]; + HLOGC(inlog.Debug, log << "pton: Handshake address: " << w_addr.str() << " provided in IPv6 mapping format"); + } + // Check SRT IPv4 format. + else if ((ip[1] | ip[2] | ip[3]) == 0) + { + *target_ipv4_addr = ip[0]; + HLOGC(inlog.Debug, log << "pton: Handshake address: " << w_addr.str() << " provided in SRT IPv4 format"); + } + else + { + LOGC(inlog.Error, log << "pton: IPE or net error: can't determine IPv4 carryover format: " << std::hex + << peeraddr16[0] << ":" + << peeraddr16[1] << ":" + << peeraddr16[2] << ":" + << peeraddr16[3] << ":" + << peeraddr16[4] << ":" + << peeraddr16[5] << ":" + << peeraddr16[6] << ":" + << peeraddr16[7] << std::dec); + *target_ipv4_addr = 0; + if (peer.family() != AF_INET) + { + // Additionally overwrite the 0xFFFF that has been + // just written 50 lines above. + w_addr.sin6.sin6_addr.s6_addr[10] = 0; + w_addr.sin6.sin6_addr.s6_addr[11] = 0; + } + } +} + +namespace srt { static string ShowIP4(const sockaddr_in* sin) { ostringstream os; @@ -790,17 +367,19 @@ string CIPAddress::show(const sockaddr* adr) else return "(unsupported sockaddr type)"; } +} // namespace srt // -void CMD5::compute(const char* input, unsigned char result[16]) +void srt::CMD5::compute(const char* input, unsigned char result[16]) { md5_state_t state; md5_init(&state); - md5_append(&state, (const md5_byte_t *)input, strlen(input)); + md5_append(&state, (const md5_byte_t *)input, (int) strlen(input)); md5_finish(&state, result); } +namespace srt { std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) { using std::string; @@ -824,7 +403,9 @@ std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) "EXT:kmreq", "EXT:kmrsp", "EXT:sid", - "EXT:congctl" + "EXT:congctl", + "EXT:filter", + "EXT:group" }; @@ -865,7 +446,8 @@ std::string TransmissionEventStr(ETransmissionEvent ev) "checktimer", "send", "receive", - "custom" + "custom", + "sync" }; size_t vals_size = Size(vals); @@ -875,58 +457,94 @@ std::string TransmissionEventStr(ETransmissionEvent ev) return vals[ev]; } -extern const char* const srt_rejectreason_msg [] = { - "Unknown or erroneous", - "Error in system calls", - "Peer rejected connection", - "Resource allocation failure", - "Rogue peer or incorrect parameters", - "Listener's backlog exceeded", - "Internal Program Error", - "Socket is being closed", - "Peer version too old", - "Rendezvous-mode cookie collision", - "Incorrect passphrase", - "Password required or unexpected", - "MessageAPI/StreamAPI collision", - "Congestion controller type collision", - "Packet Filter type collision" -}; - -const char* srt_rejectreason_str(SRT_REJECT_REASON rid) +bool SrtParseConfig(string s, SrtConfig& w_config) { - int id = rid; - static const size_t ra_size = Size(srt_rejectreason_msg); - if (size_t(id) >= ra_size) - return srt_rejectreason_msg[0]; - return srt_rejectreason_msg[id]; -} + using namespace std; -// Some logging imps -#if ENABLE_LOGGING + vector parts; + Split(s, ',', back_inserter(parts)); + + w_config.type = parts[0]; + + for (vector::iterator i = parts.begin()+1; i != parts.end(); ++i) + { + vector keyval; + Split(*i, ':', back_inserter(keyval)); + if (keyval.size() != 2) + return false; + if (keyval[1] != "") + w_config.parameters[keyval[0]] = keyval[1]; + } + + return true; +} +} // namespace srt namespace srt_logging { -std::string FormatTime(uint64_t time) +// Value display utilities +// (also useful for applications) + +std::string SockStatusStr(SRT_SOCKSTATUS s) { - using namespace std; + if (int(s) < int(SRTS_INIT) || int(s) > int(SRTS_NONEXIST)) + return "???"; - time_t sec = time/1000000; - time_t usec = time%1000000; + static struct AutoMap + { + // Values start from 1, so do -1 to avoid empty cell + std::string names[int(SRTS_NONEXIST)-1+1]; - time_t tt = sec; - struct tm tm = SysLocalTime(tt); + AutoMap() + { +#define SINI(statename) names[SRTS_##statename-1] = #statename + SINI(INIT); + SINI(OPENED); + SINI(LISTENING); + SINI(CONNECTING); + SINI(CONNECTED); + SINI(BROKEN); + SINI(CLOSING); + SINI(CLOSED); + SINI(NONEXIST); +#undef SINI + } + } names; - char tmp_buf[512]; - strftime(tmp_buf, 512, "%X.", &tm); + return names.names[int(s)-1]; +} - ostringstream out; - out << tmp_buf << setfill('0') << setw(6) << usec; - return out.str(); +#if ENABLE_BONDING +std::string MemberStatusStr(SRT_MEMBERSTATUS s) +{ + if (int(s) < int(SRT_GST_PENDING) || int(s) > int(SRT_GST_BROKEN)) + return "???"; + + static struct AutoMap + { + std::string names[int(SRT_GST_BROKEN)+1]; + + AutoMap() + { +#define SINI(statename) names[SRT_GST_##statename] = #statename + SINI(PENDING); + SINI(IDLE); + SINI(RUNNING); + SINI(BROKEN); +#undef SINI + } + } names; + + return names.names[int(s)]; } +#endif -LogDispatcher::Proxy::Proxy(LogDispatcher& guy) : that(guy), that_enabled(that.CheckEnabled()) +// Logging system implementation + +#if ENABLE_LOGGING + +srt::logging::LogDispatcher::Proxy::Proxy(LogDispatcher& guy) : that(guy), that_enabled(that.CheckEnabled()) { if (that_enabled) { @@ -946,17 +564,22 @@ LogDispatcher::Proxy LogDispatcher::operator()() void LogDispatcher::CreateLogLinePrefix(std::ostringstream& serr) { using namespace std; + using namespace srt; - char tmp_buf[512]; + SRT_STATIC_ASSERT(ThreadName::BUFSIZE >= sizeof("hh:mm:ss.") * 2, // multiply 2 for some margin + "ThreadName::BUFSIZE is too small to be used for strftime"); + char tmp_buf[ThreadName::BUFSIZE]; if ( !isset(SRT_LOGF_DISABLE_TIME) ) { // Not necessary if sending through the queue. timeval tv; - gettimeofday(&tv, 0); + gettimeofday(&tv, NULL); struct tm tm = SysLocalTime((time_t) tv.tv_sec); - strftime(tmp_buf, 512, "%X.", &tm); - serr << tmp_buf << setw(6) << setfill('0') << tv.tv_usec; + if (strftime(tmp_buf, sizeof(tmp_buf), "%X.", &tm)) + { + serr << tmp_buf << setw(6) << setfill('0') << tv.tv_usec; + } } string out_prefix; @@ -1038,7 +661,7 @@ std::string LogDispatcher::Proxy::ExtractName(std::string pretty_function) return pretty_function.substr(pos+2); } +#endif } // (end namespace srt_logging) -#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/common.h b/trunk/3rdparty/srt-1-fit/srtcore/common.h index 95d9161f09..227a91861c 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/common.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/common.h @@ -50,12 +50,13 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_COMMON_H__ -#define __UDT_COMMON_H__ +#ifndef INC_SRT_COMMON_H +#define INC_SRT_COMMON_H #define _CRT_SECURE_NO_WARNINGS 1 // silences windows complaints for sscanf - +#include #include +#include #ifndef _WIN32 #include #include @@ -63,10 +64,19 @@ modified by // #include //#include #endif -#include -#include "udt.h" + +#include "srt.h" #include "utilities.h" +#include "sync.h" +#include "netinet_any.h" +#include "packetfilter_api.h" +// System-independent errno +#ifndef _WIN32 + #define NET_ERROR errno +#else + #define NET_ERROR WSAGetLastError() +#endif #ifdef _DEBUG #include @@ -75,6 +85,114 @@ modified by #define SRT_ASSERT(cond) #endif +#if HAVE_FULL_CXX11 +#define SRT_STATIC_ASSERT(cond, msg) static_assert(cond, msg) +#else +#define SRT_STATIC_ASSERT(cond, msg) +#endif + +#include + +namespace srt_logging +{ + std::string SockStatusStr(SRT_SOCKSTATUS s); +#if ENABLE_BONDING + std::string MemberStatusStr(SRT_MEMBERSTATUS s); +#endif +} + +namespace srt +{ + +// Class CUDTException exposed for C++ API. +// This is actually useless, unless you'd use a DIRECT C++ API, +// however there's no such API so far. The current C++ API for UDT/SRT +// is predicted to NEVER LET ANY EXCEPTION out of implementation, +// so it's useless to catch this exception anyway. + +class CUDTException: public std::exception +{ +public: + + CUDTException(CodeMajor major = MJ_SUCCESS, CodeMinor minor = MN_NONE, int err = -1); + virtual ~CUDTException() ATR_NOTHROW {} + + /// Get the description of the exception. + /// @return Text message for the exception description. + const char* getErrorMessage() const ATR_NOTHROW; + + virtual const char* what() const ATR_NOTHROW ATR_OVERRIDE + { + return getErrorMessage(); + } + + std::string getErrorString() const; + + /// Get the system errno for the exception. + /// @return errno. + int getErrorCode() const; + + /// Get the system network errno for the exception. + /// @return errno. + int getErrno() const; + + /// Clear the error code. + void clear(); + +private: + CodeMajor m_iMajor; // major exception categories + CodeMinor m_iMinor; // for specific error reasons + int m_iErrno; // errno returned by the system if there is any + mutable std::string m_strMsg; // text error message (cache) + + std::string m_strAPI; // the name of UDT function that returns the error + std::string m_strDebug; // debug information, set to the original place that causes the error + +public: // Legacy Error Code + + static const int EUNKNOWN = SRT_EUNKNOWN; + static const int SUCCESS = SRT_SUCCESS; + static const int ECONNSETUP = SRT_ECONNSETUP; + static const int ENOSERVER = SRT_ENOSERVER; + static const int ECONNREJ = SRT_ECONNREJ; + static const int ESOCKFAIL = SRT_ESOCKFAIL; + static const int ESECFAIL = SRT_ESECFAIL; + static const int ECONNFAIL = SRT_ECONNFAIL; + static const int ECONNLOST = SRT_ECONNLOST; + static const int ENOCONN = SRT_ENOCONN; + static const int ERESOURCE = SRT_ERESOURCE; + static const int ETHREAD = SRT_ETHREAD; + static const int ENOBUF = SRT_ENOBUF; + static const int EFILE = SRT_EFILE; + static const int EINVRDOFF = SRT_EINVRDOFF; + static const int ERDPERM = SRT_ERDPERM; + static const int EINVWROFF = SRT_EINVWROFF; + static const int EWRPERM = SRT_EWRPERM; + static const int EINVOP = SRT_EINVOP; + static const int EBOUNDSOCK = SRT_EBOUNDSOCK; + static const int ECONNSOCK = SRT_ECONNSOCK; + static const int EINVPARAM = SRT_EINVPARAM; + static const int EINVSOCK = SRT_EINVSOCK; + static const int EUNBOUNDSOCK = SRT_EUNBOUNDSOCK; + static const int ESTREAMILL = SRT_EINVALMSGAPI; + static const int EDGRAMILL = SRT_EINVALBUFFERAPI; + static const int ENOLISTEN = SRT_ENOLISTEN; + static const int ERDVNOSERV = SRT_ERDVNOSERV; + static const int ERDVUNBOUND = SRT_ERDVUNBOUND; + static const int EINVALMSGAPI = SRT_EINVALMSGAPI; + static const int EINVALBUFFERAPI = SRT_EINVALBUFFERAPI; + static const int EDUPLISTEN = SRT_EDUPLISTEN; + static const int ELARGEMSG = SRT_ELARGEMSG; + static const int EINVPOLLID = SRT_EINVPOLLID; + static const int EASYNCFAIL = SRT_EASYNCFAIL; + static const int EASYNCSND = SRT_EASYNCSND; + static const int EASYNCRCV = SRT_EASYNCRCV; + static const int ETIMEOUT = SRT_ETIMEOUT; + static const int ECONGEST = SRT_ECONGEST; + static const int EPEERERR = SRT_EPEERERR; +}; + + enum UDTSockType { @@ -161,6 +279,12 @@ enum EConnectStatus CONN_AGAIN = -2 //< No data was read, don't change any state. }; +enum EConnectMethod +{ + COM_ASYNCHRO, + COM_SYNCHRO +}; + std::string ConnectStatusStr(EConnectStatus est); @@ -171,14 +295,15 @@ enum ETransmissionEvent { TEV_INIT, // --> After creation, and after any parameters were updated. TEV_ACK, // --> When handling UMSG_ACK - older CCC:onAck() - TEV_ACKACK, // --> UDT does only RTT sync, can be read from CUDT::RTT(). + TEV_ACKACK, // --> UDT does only RTT sync, can be read from CUDT::SRTT(). TEV_LOSSREPORT, // --> When handling UMSG_LOSSREPORT - older CCC::onLoss() TEV_CHECKTIMER, // --> See TEV_CHT_REXMIT TEV_SEND, // --> When the packet is scheduled for sending - older CCC::onPktSent TEV_RECEIVE, // --> When a data packet was received - older CCC::onPktReceived TEV_CUSTOM, // --> probably dead call - older CCC::processCustomMsg + TEV_SYNC, // --> Backup group. When rate estimation is derived from an active member, and update is needed. - TEV__SIZE + TEV_E_SIZE }; std::string TransmissionEventStr(ETransmissionEvent ev); @@ -209,47 +334,47 @@ struct EventVariant enum Type {UNDEFINED, PACKET, ARRAY, ACK, STAGE, INIT} type; union U { - CPacket* packet; + const srt::CPacket* packet; int32_t ack; struct { - int32_t* ptr; + const int32_t* ptr; size_t len; } array; ECheckTimerStage stage; EInitEvent init; } u; - EventVariant() - { - type = UNDEFINED; - memset(&u, 0, sizeof u); - } template struct VariantFor; - template - void Assign(Arg arg) - { - type = tp; - (u.*(VariantFor::field())) = arg; - //(u.*field) = arg; - } - - void operator=(CPacket* arg) { Assign(arg); }; - void operator=(int32_t arg) { Assign(arg); }; - void operator=(ECheckTimerStage arg) { Assign(arg); }; - void operator=(EInitEvent arg) { Assign(arg); }; // Note: UNDEFINED and ARRAY don't have assignment operator. // For ARRAY you'll use 'set' function. For UNDEFINED there's nothing. + explicit EventVariant(const srt::CPacket* arg) + { + type = PACKET; + u.packet = arg; + } + + explicit EventVariant(int32_t arg) + { + type = ACK; + u.ack = arg; + } + + explicit EventVariant(ECheckTimerStage arg) + { + type = STAGE; + u.stage = arg; + } - template - EventVariant(T arg) + explicit EventVariant(EInitEvent arg) { - *this = arg; + type = INIT; + u.init = arg; } const int32_t* get_ptr() const @@ -257,25 +382,25 @@ struct EventVariant return u.array.ptr; } - size_t get_len() + size_t get_len() const { return u.array.len; } - void set(int32_t* ptr, size_t len) + void set(const int32_t* ptr, size_t len) { type = ARRAY; u.array.ptr = ptr; u.array.len = len; } - EventVariant(int32_t* ptr, size_t len) + EventVariant(const int32_t* ptr, size_t len) { set(ptr, len); } template - typename VariantFor::type get() + typename VariantFor::type get() const { return u.*(VariantFor::field()); } @@ -314,10 +439,10 @@ class EventArgType; // The 'type' field wouldn't be even necessary if we - +// use a full-templated version. TBD. template<> struct EventVariant::VariantFor { - typedef CPacket* type; + typedef const srt::CPacket* type; static type U::*field() {return &U::packet;} }; @@ -400,12 +525,15 @@ struct EventSlot // "Stealing" copy constructor, following the auto_ptr method. // This isn't very nice, but no other way to do it in C++03 // without rvalue-reference and move. - EventSlot(const EventSlot& victim) + void moveFrom(const EventSlot& victim) { slot = victim.slot; // Should MOVE. victim.slot = 0; } + EventSlot(const EventSlot& victim) { moveFrom(victim); } + EventSlot& operator=(const EventSlot& victim) { moveFrom(victim); return *this; } + EventSlot(void* op, EventSlotBase::dispatcher_t* disp) { slot = new SimpleEventSlot(op, disp); @@ -426,173 +554,79 @@ struct EventSlot ~EventSlot() { - if (slot) - delete slot; + delete slot; } }; -// Old UDT library specific classes, moved from utilities as utilities -// should now be general-purpose. - -class CTimer -{ -public: - CTimer(); - ~CTimer(); - -public: - - /// Sleep for "interval_tk" CCs. - /// @param [in] interval_tk CCs to sleep. - - void sleep(uint64_t interval_tk); - - /// Seelp until CC "nexttime_tk". - /// @param [in] nexttime_tk next time the caller is waken up. - - void sleepto(uint64_t nexttime_tk); - - /// Stop the sleep() or sleepto() methods. - - void interrupt(); - - /// trigger the clock for a tick, for better granuality in no_busy_waiting timer. - - void tick(); - -public: - - /// Read the CPU clock cycle into x. - /// @param [out] x to record cpu clock cycles. - - static void rdtsc(uint64_t &x); - - /// return the CPU frequency. - /// @return CPU frequency. - - static uint64_t getCPUFrequency(); - - /// check the current time, 64bit, in microseconds. - /// @return current time in microseconds. - - static uint64_t getTime(); - - /// trigger an event such as new connection, close, new data, etc. for "select" call. - - static void triggerEvent(); - - enum EWait {WT_EVENT, WT_ERROR, WT_TIMEOUT}; - - /// wait for an event to br triggered by "triggerEvent". - /// @retval WT_EVENT The event has happened - /// @retval WT_TIMEOUT The event hasn't happened, the function exited due to timeout - /// @retval WT_ERROR The function has exit due to an error - - static EWait waitForEvent(); - - /// sleep for a short interval. exact sleep time does not matter - - static void sleep(); - - /// Wait for condition with timeout - /// @param [in] cond Condition variable to wait for - /// @param [in] mutex locked mutex associated with the condition variable - /// @param [in] delay timeout in microseconds - /// @retval 0 Wait was successfull - /// @retval ETIMEDOUT The wait timed out - - static int condTimedWaitUS(pthread_cond_t* cond, pthread_mutex_t* mutex, uint64_t delay); - -private: - uint64_t getTimeInMicroSec(); - -private: - uint64_t m_ullSchedTime_tk; // next schedulled time - - pthread_cond_t m_TickCond; - pthread_mutex_t m_TickLock; - - static pthread_cond_t m_EventCond; - static pthread_mutex_t m_EventLock; - -private: - static uint64_t s_ullCPUFrequency; // CPU frequency : clock cycles per microsecond - static uint64_t readCPUFrequency(); - static bool m_bUseMicroSecond; // No higher resolution timer available, use gettimeofday(). -}; +// UDT Sequence Number 0 - (2^31 - 1) -//////////////////////////////////////////////////////////////////////////////// +// seqcmp: compare two seq#, considering the wraping +// seqlen: length from the 1st to the 2nd seq#, including both +// seqoff: offset from the 2nd to the 1st seq# +// incseq: increase the seq# by 1 +// decseq: decrease the seq# by 1 +// incseq: increase the seq# by a given offset -class CGuard +class CSeqNo { -public: - /// Constructs CGuard, which locks the given mutex for - /// the scope where this object exists. - /// @param lock Mutex to lock - /// @param if_condition If this is false, CGuard will do completely nothing - CGuard(pthread_mutex_t& lock, bool if_condition = true); - ~CGuard(); - -public: - static int enterCS(pthread_mutex_t& lock); - static int leaveCS(pthread_mutex_t& lock); - - static void createMutex(pthread_mutex_t& lock); - static void releaseMutex(pthread_mutex_t& lock); - - static void createCond(pthread_cond_t& cond); - static void releaseCond(pthread_cond_t& cond); - - void forceUnlock(); - -private: - pthread_mutex_t& m_Mutex; // Alias name of the mutex to be protected - int m_iLocked; // Locking status + int32_t value; - CGuard& operator=(const CGuard&); -}; - -class InvertedGuard -{ - pthread_mutex_t* m_pMutex; public: - InvertedGuard(pthread_mutex_t* smutex): m_pMutex(smutex) - { - if ( !smutex ) - return; + explicit CSeqNo(int32_t v): value(v) {} - CGuard::leaveCS(*smutex); - } + // Comparison + bool operator == (const CSeqNo& other) const { return other.value == value; } + bool operator < (const CSeqNo& other) const + { + return seqcmp(value, other.value) < 0; + } - ~InvertedGuard() - { - if ( !m_pMutex ) - return; + // The std::rel_ops namespace cannot be "imported" + // as a whole into the class - it can only be used + // in the application code. + bool operator != (const CSeqNo& other) const { return other.value != value; } + bool operator > (const CSeqNo& other) const { return other < *this; } + bool operator >= (const CSeqNo& other) const + { + return seqcmp(value, other.value) >= 0; + } + bool operator <=(const CSeqNo& other) const + { + return seqcmp(value, other.value) <= 0; + } - CGuard::enterCS(*m_pMutex); - } -}; + // circular arithmetics + friend int operator-(const CSeqNo& c1, const CSeqNo& c2) + { + return seqoff(c2.value, c1.value); + } -//////////////////////////////////////////////////////////////////////////////// + friend CSeqNo operator-(const CSeqNo& c1, int off) + { + return CSeqNo(decseq(c1.value, off)); + } -// UDT Sequence Number 0 - (2^31 - 1) + friend CSeqNo operator+(const CSeqNo& c1, int off) + { + return CSeqNo(incseq(c1.value, off)); + } -// seqcmp: compare two seq#, considering the wraping -// seqlen: length from the 1st to the 2nd seq#, including both -// seqoff: offset from the 2nd to the 1st seq# -// incseq: increase the seq# by 1 -// decseq: decrease the seq# by 1 -// incseq: increase the seq# by a given offset + friend CSeqNo operator+(int off, const CSeqNo& c1) + { + return CSeqNo(incseq(c1.value, off)); + } -class CSeqNo -{ -public: + CSeqNo& operator++() + { + value = incseq(value); + return *this; + } /// This behaves like seq1 - seq2, in comparison to numbers, /// and with the statement that only the sign of the result matters. - /// That is, it returns a negative value if seq1 < seq2, + /// Returns a negative value if seq1 < seq2, /// positive if seq1 > seq2, and zero if they are equal. /// The only correct application of this function is when you /// compare two values and it works faster than seqoff. However @@ -601,19 +635,25 @@ class CSeqNo /// distance between two sequence numbers. /// /// Example: to check if (seq1 %> seq2): seqcmp(seq1, seq2) > 0. + /// Note: %> stands for "later than". inline static int seqcmp(int32_t seq1, int32_t seq2) {return (abs(seq1 - seq2) < m_iSeqNoTH) ? (seq1 - seq2) : (seq2 - seq1);} /// This function measures a length of the range from seq1 to seq2, + /// including endpoints (seqlen(a, a) = 1; seqlen(a, a + 1) = 2), /// WITH A PRECONDITION that certainly @a seq1 is earlier than @a seq2. /// This can also include an enormously large distance between them, /// that is, exceeding the m_iSeqNoTH value (can be also used to test - /// if this distance is larger). Prior to calling this function the - /// caller must be certain that @a seq2 is a sequence coming from a - /// later time than @a seq1, and still, of course, this distance didn't - /// exceed m_iMaxSeqNo. + /// if this distance is larger). + /// Prior to calling this function the caller must be certain that + /// @a seq2 is a sequence coming from a later time than @a seq1, + /// and that the distance does not exceed m_iMaxSeqNo. inline static int seqlen(int32_t seq1, int32_t seq2) - {return (seq1 <= seq2) ? (seq2 - seq1 + 1) : (seq2 - seq1 + m_iMaxSeqNo + 2);} + { + SRT_ASSERT(seq1 >= 0 && seq1 <= m_iMaxSeqNo); + SRT_ASSERT(seq2 >= 0 && seq2 <= m_iMaxSeqNo); + return (seq1 <= seq2) ? (seq2 - seq1 + 1) : (seq2 - seq1 + m_iMaxSeqNo + 2); + } /// This behaves like seq2 - seq1, with the precondition that the true /// distance between two sequence numbers never exceeds m_iSeqNoTH. @@ -659,6 +699,13 @@ class CSeqNo return seq - dec; } + static int32_t maxseq(int32_t seq1, int32_t seq2) + { + if (seqcmp(seq1, seq2) < 0) + return seq2; + return seq1; + } + public: static const int32_t m_iSeqNoTH = 0x3FFFFFFF; // threshold for comparing seq. no. static const int32_t m_iMaxSeqNo = 0x7FFFFFFF; // maximum sequence number used in UDT @@ -678,15 +725,138 @@ class CAckNo static const int32_t m_iMaxAckSeqNo = 0x7FFFFFFF; // maximum ACK sub-sequence number used in UDT }; +template +class RollNumber +{ + typedef RollNumber this_t; + typedef Bits number_t; + uint32_t number; +public: + + static const size_t OVER = number_t::mask+1; + static const size_t HALF = (OVER-MIN)/2; + +private: + static int Diff(uint32_t left, uint32_t right) + { + // UNExpected order, diff is negative + if ( left < right ) + { + int32_t diff = right - left; + if ( diff >= int32_t(HALF) ) // over barrier + { + // It means that left is less than right because it was overflown + // For example: left = 0x0005, right = 0xFFF0; diff = 0xFFEB > HALF + left += OVER - MIN; // left was really 0x00010005, just narrowed. + // Now the difference is 0x0015, not 0xFFFF0015 + } + } + else + { + int32_t diff = left - right; + if ( diff >= int32_t(HALF) ) + { + right += OVER - MIN; + } + } + + return left - right; + } + +public: + explicit RollNumber(uint32_t val): number(val) + { + } + + bool operator<(const this_t& right) const + { + int32_t ndiff = number - right.number; + if (ndiff < -int32_t(HALF)) + { + // it' like ndiff > 0 + return false; + } + + if (ndiff > int32_t(HALF)) + { + // it's like ndiff < 0 + return true; + } + + return ndiff < 0; + } + + bool operator>(const this_t& right) const + { + return right < *this; + } + + bool operator==(const this_t& right) const + { + return number == right.number; + } + + bool operator<=(const this_t& right) const + { + return !(*this > right); + } + + bool operator>=(const this_t& right) const + { + return !(*this < right); + } + + void operator++(int) + { + ++number; + if (number > number_t::mask) + number = MIN; + } + + this_t& operator++() { (*this)++; return *this; } + + void operator--(int) + { + if (number == MIN) + number = number_t::mask; + else + --number; + } + this_t& operator--() { (*this)--; return *this; } + + int32_t operator-(this_t right) + { + return Diff(this->number, right.number); + } + + void operator+=(int32_t delta) + { + // NOTE: this condition in practice tests if delta is negative. + // That's because `number` is always positive, so negated delta + // can't be ever greater than this, unless it's negative. + if (-delta > int64_t(number)) + { + number = OVER - MIN + number + delta; // NOTE: delta is negative + } + else + { + number += delta; + if (number >= OVER) + number -= OVER - MIN; + } + } + + operator uint32_t() const { return number; } +}; //////////////////////////////////////////////////////////////////////////////// struct CIPAddress { static bool ipcmp(const struct sockaddr* addr1, const struct sockaddr* addr2, int ver = AF_INET); - static void ntop(const struct sockaddr* addr, uint32_t ip[4], int ver = AF_INET); - static void pton(struct sockaddr* addr, const uint32_t ip[4], int ver = AF_INET); + static void ntop(const struct sockaddr_any& addr, uint32_t ip[4]); + static void pton(sockaddr_any& addr, const uint32_t ip[4], const sockaddr_any& peer); static std::string show(const struct sockaddr* adr); }; @@ -705,22 +875,21 @@ class StatsLossRecords std::bitset array; public: - - StatsLossRecords(): initseq(-1) {} + StatsLossRecords(): initseq(SRT_SEQNO_NONE) {} // To check if this structure still keeps record of that sequence. // This is to check if the information about this not being found // is still reliable. bool exists(int32_t seq) { - return initseq != -1 && CSeqNo::seqcmp(seq, initseq) >= 0; + return initseq != SRT_SEQNO_NONE && CSeqNo::seqcmp(seq, initseq) >= 0; } int32_t base() { return initseq; } void clear() { - initseq = -1; + initseq = SRT_SEQNO_NONE; array.reset(); } @@ -817,6 +986,404 @@ class StatsLossRecords }; +// There are some better or worse things you can find outside, +// there's also boost::circular_buffer, but it's too overspoken +// to be included here. We also can't rely on boost. Maybe in future +// when it's added to the standard and SRT can heighten C++ standard +// requirements; until then it needs this replacement. +template +class CircularBuffer +{ +#ifdef SRT_TEST_CIRCULAR_BUFFER +public: +#endif + int m_iSize; + Value* m_aStorage; + int m_xBegin; + int m_xEnd; + + static void destr(Value& v) + { + v.~Value(); + } + + static void constr(Value& v) + { + new ((void*)&v) Value(); + } + + template + static void constr(Value& v, const V& source) + { + new ((void*)&v) Value(source); + } + + // Wipe the copy constructor + CircularBuffer(const CircularBuffer&); + +public: + typedef Value value_type; + + CircularBuffer(int size) + :m_iSize(size+1), + m_xBegin(0), + m_xEnd(0) + { + // We reserve one spare element just for a case. + if (size == 0) + m_aStorage = 0; + else + m_aStorage = (Value*)::operator new (sizeof(Value) * m_iSize); + } + + void set_capacity(int size) + { + reset(); + + // This isn't called resize (the size is 0 after the operation) + // nor reserve (the existing elements are removed). + if (size != m_iSize) + { + if (m_aStorage) + ::operator delete (m_aStorage); + m_iSize = size+1; + m_aStorage = (Value*)::operator new (sizeof(Value) * m_iSize); + } + } + + void reset() + { + if (m_xEnd < m_xBegin) + { + for (int i = m_xBegin; i < m_iSize; ++i) + destr(m_aStorage[i]); + for (int i = 0; i < m_xEnd; ++i) + destr(m_aStorage[i]); + } + else + { + for (int i = m_xBegin; i < m_xEnd; ++i) + destr(m_aStorage[i]); + } + + m_xBegin = 0; + m_xEnd = 0; + } + + ~CircularBuffer() + { + reset(); + ::operator delete (m_aStorage); + } + + // In the beginning, m_xBegin == m_xEnd, which + // means that the container is empty. Adding can + // be done exactly at the place pointed to by m_xEnd, + // and m_xEnd must be then shifted to the next unused one. + // When (m_xEnd + 1) % m_zSize == m_xBegin, the container + // is considered full and the element adding is rejected. + // + // This container is not designed to be STL-compatible + // because it doesn't make much sense. It's not a typical + // container, even treated as random-access container. + + int shift(int basepos, int shift) const + { + return (basepos + shift) % m_iSize; + } + + // Simplified versions with ++ and --; avoid using division instruction + int shift_forward(int basepos) const + { + if (++basepos == m_iSize) + return 0; + return basepos; + } + + int shift_backward(int basepos) const + { + if (basepos == 0) + return m_iSize-1; + return --basepos; + } + + int size() const + { + // Count the distance between begin and end + if (m_xEnd < m_xBegin) + { + // Use "merge two slices" method. + // (BEGIN - END) is the distance of the unused + // space in the middle. Used space is left to END + // and right to BEGIN, the sum of the left and right + // slice and the free space is the size. + + // This includes also a case when begin and end + // are equal, which means that it's empty, so + // spaceleft() should simply return m_iSize. + return m_iSize - (m_xBegin - m_xEnd); + } + + return m_xEnd - m_xBegin; + } + + bool empty() const { return m_xEnd == m_xBegin; } + + size_t capacity() const { return m_iSize-1; } + + int spaceleft() const + { + // It's kinda tautology, but this will be more efficient. + if (m_xEnd < m_xBegin) + { + return m_xBegin - m_xEnd; + } + + return m_iSize - (m_xEnd - m_xBegin); + } + + // This is rather written for testing and rather won't + // be used in the real code. + template + int push(const V& v) + { + // Check if you can add + int nend = shift_forward(m_xEnd); + if ( nend == m_xBegin) + return -1; + + constr(m_aStorage[m_xEnd], v); + m_xEnd = nend; + return size() - 1; + } + + Value* push() + { + int nend = shift_forward(m_xEnd); + if ( nend == m_xBegin) + return NULL; + + Value* pos = &m_aStorage[m_xEnd]; + constr(*pos); + m_xEnd = nend; + return pos; + } + + bool access(int position, Value*& w_v) + { + // This version doesn't require the boolean value to report + // whether the element is newly added because it never adds + // a new element. + int ipos, vend; + + if (!INT_checkAccess(position, ipos, vend)) + return false; + if (ipos >= vend) // exceeds + return false; + + INT_access(ipos, false, (w_v)); // never exceeds + return true; + } + + // Ok, now it's the real deal. + bool access(int position, Value*& w_v, bool& w_isnew) + { + int ipos, vend; + + if (!INT_checkAccess(position, ipos, vend)) + return false; + bool exceeds = (ipos >= vend); + w_isnew = exceeds; + + INT_access(ipos, exceeds, (w_v)); + return true; + } + +private: + bool INT_checkAccess(int position, int& ipos, int& vend) + { + // Reject if no space left. + // Also INVAL if negative position. + if (position >= (m_iSize-1) || position < 0) + return false; // That's way to far, we can't even calculate + + ipos = m_xBegin + position; + + vend = m_xEnd; + if (m_xEnd < m_xBegin) + vend += m_iSize; + + return true; + } + + void INT_access(int ipos, bool exceeds, Value*& w_v) + { + if (ipos >= m_iSize) + ipos -= m_iSize; // wrap around + + // Update the end position. + if (exceeds) + { + int nend = ipos+1; + if (m_xEnd > nend) + { + // Here we know that the current index exceeds the size. + // So, if this happens, it's m_xEnd wrapped around. + // Clear out elements in two slices: + // - from m_xEnd to m_iSize-1 + // - from 0 to nend + for (int i = m_xEnd; i < m_iSize; ++i) + constr(m_aStorage[i]); + for (int i = 0; i < nend; ++i) + constr(m_aStorage[i]); + } + else + { + for (int i = m_xEnd; i < nend; ++i) + constr(m_aStorage[i]); + } + + if (nend == m_iSize) + nend = 0; + + m_xEnd = nend; + } + + w_v = &m_aStorage[ipos]; + } + +public: + bool set(int position, const Value& newval, bool overwrite = true) + { + Value* pval = 0; + bool isnew = false; + if (!access(position, (pval), (isnew))) + return false; + + if (isnew || overwrite) + *pval = newval; + return true; + } + + template + bool update(int position, Updater updater) + { + Value* pval = 0; + bool isnew = false; + if (!access(position, (pval), (isnew))) + return false; + + updater(*pval, isnew); + return true; + } + + int getIndexFor(int position) const + { + int ipos = m_xBegin + position; + + int vend = m_xEnd; + if (vend < m_xBegin) + vend += m_iSize; + + if (ipos >= vend) + return -1; + + if (ipos >= m_iSize) + ipos -= m_iSize; + + return ipos; + } + + bool get(int position, Value& w_out) const + { + // Check if that position is occupied + if (position > m_iSize || position < 0) + return false; + + int ipos = getIndexFor(position); + if (ipos == -1) + return false; + + w_out = m_aStorage[ipos]; + return true; + } + + bool drop(int position) + { + // This function "deletes" items by shifting the + // given position to position 0. That is, + // elements from the beginning are being deleted + // up to (including) the given position. + if (position > m_iSize || position < 1) + return false; + + int ipos = m_xBegin + position; + int vend = m_xEnd; + if (vend < m_xBegin) + vend += m_iSize; + + // Destroy the elements in the removed range + + if (ipos >= vend) + { + // There was a request to drop; the position + // is higher than the number of items. Allow this + // and simply make the container empty. + reset(); + return true; + } + + // Otherwise we have a new beginning. + int nbegin = ipos; + + // Destroy the old elements + if (nbegin >= m_iSize) + { + nbegin -= m_iSize; + + for (int i = m_xBegin; i < m_iSize; ++i) + destr(m_aStorage[i]); + for (int i = 0; i < nbegin; ++i) + destr(m_aStorage[i]); + } + else + { + for (int i = m_xBegin; i < nbegin; ++i) + destr(m_aStorage[i]); + } + + m_xBegin = nbegin; + + return true; + } + + // This function searches for an element that satisfies + // the given predicate. If none found, returns -1. + template + int find_if(Predicate pred) + { + if (m_xEnd < m_xBegin) + { + // Loop in two slices + for (int i = m_xBegin; i < m_iSize; ++i) + if (pred(m_aStorage[i])) + return i - m_xBegin; + + for (int i = 0; i < m_xEnd; ++i) + if (pred(m_aStorage[i])) + return i + m_iSize - m_xBegin; + } + else + { + for (int i = m_xBegin; i < m_xEnd; ++i) + if (pred(m_aStorage[i])) + return i - m_xBegin; + } + + return -1; + } +}; + // Version parsing inline ATR_CONSTEXPR uint32_t SrtVersion(int major, int minor, int patch) { @@ -833,7 +1400,7 @@ inline int32_t SrtParseVersion(const char* v) return 0; } - return major*0x10000 + minor*0x100 + patch; + return SrtVersion(major, minor, patch); } inline std::string SrtVersionString(int version) @@ -847,4 +1414,8 @@ inline std::string SrtVersionString(int version) return buf; } +bool SrtParseConfig(std::string s, SrtConfig& w_config); + +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/congctl.cpp b/trunk/3rdparty/srt-1-fit/srtcore/congctl.cpp index dde90ac27e..33b5389e02 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/congctl.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/congctl.cpp @@ -12,7 +12,7 @@ // This is a controversial thing, so temporarily blocking //#define SRT_ENABLE_SYSTEMBUFFER_TRACE - +#include "platform_sys.h" #ifdef SRT_ENABLE_SYSTEMBUFFER_TRACE @@ -34,8 +34,11 @@ #include "logging.h" using namespace std; +using namespace srt::sync; using namespace srt_logging; +namespace srt { + SrtCongestionControlBase::SrtCongestionControlBase(CUDT* parent) { m_parent = parent; @@ -58,7 +61,7 @@ void SrtCongestion::Check() class LiveCC: public SrtCongestionControlBase { int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec) - size_t m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit + srt::sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit size_t m_zMaxPayloadSize; // NAKREPORT stuff. @@ -74,12 +77,12 @@ class LiveCC: public SrtCongestionControlBase { m_llSndMaxBW = BW_INFINITE; // 1 Gbbps in Bytes/sec BW_INFINITE m_zMaxPayloadSize = parent->OPT_PayloadSize(); - if ( m_zMaxPayloadSize == 0 ) + if (m_zMaxPayloadSize == 0) m_zMaxPayloadSize = parent->maxPayloadSize(); m_zSndAvgPayloadSize = m_zMaxPayloadSize; m_iMinNakInterval_us = 20000; //Minimum NAK Report Period (usec) - m_iNakReportAccel = 2; //Default NAK Report Period (RTT) accelerator + m_iNakReportAccel = 2; //Default NAK Report Period (RTT) accelerator (send periodic NAK every RTT/2) HLOGC(cclog.Debug, log << "Creating LiveCC: bw=" << m_llSndMaxBW << " avgplsize=" << m_zSndAvgPayloadSize); @@ -90,11 +93,11 @@ class LiveCC: public SrtCongestionControlBase // from receiving thread. parent->ConnectSignal(TEV_SEND, SSLOT(updatePayloadSize)); - /* - * Readjust the max SndPeriod onACK (and onTimeout) - */ - parent->ConnectSignal(TEV_CHECKTIMER, SSLOT(updatePktSndPeriod_onTimer)); - parent->ConnectSignal(TEV_ACK, SSLOT(updatePktSndPeriod_onAck)); + // + // Adjust the max SndPeriod onACK and onTimeout. + // + parent->ConnectSignal(TEV_CHECKTIMER, SSLOT(onRTO)); + parent->ConnectSignal(TEV_ACK, SSLOT(onAck)); } bool checkTransArgs(SrtCongestion::TransAPI api, SrtCongestion::TransDir dir, const char* , size_t size, int , bool ) ATR_OVERRIDE @@ -151,24 +154,30 @@ class LiveCC: public SrtCongestionControlBase HLOGC(cclog.Debug, log << "LiveCC: avg payload size updated: " << m_zSndAvgPayloadSize); } - void updatePktSndPeriod_onTimer(ETransmissionEvent , EventVariant var) + /// @brief On RTO event update an inter-packet send interval. + /// @param arg EventVariant::STAGE to distinguish between INIT and actual RTO. + void onRTO(ETransmissionEvent , EventVariant var) { - if ( var.get() != TEV_CHT_INIT ) + if (var.get() != TEV_CHT_INIT ) updatePktSndPeriod(); } - void updatePktSndPeriod_onAck(ETransmissionEvent , EventVariant ) + /// @brief Handle an incoming ACK event. + /// Mainly updates a send interval between packets relying on the maximum BW limit. + void onAck(ETransmissionEvent, EventVariant ) { updatePktSndPeriod(); } + /// @brief Updates a send interval between packets relying on the maximum BW limit. void updatePktSndPeriod() { // packet = payload + header - const double pktsize = (double) m_zSndAvgPayloadSize + CPacket::SRT_DATA_HDR_SIZE; + const double pktsize = (double) m_zSndAvgPayloadSize.load() + CPacket::SRT_DATA_HDR_SIZE; m_dPktSndPeriod = 1000 * 1000.0 * (pktsize / m_llSndMaxBW); HLOGC(cclog.Debug, log << "LiveCC: sending period updated: " << m_dPktSndPeriod - << " (pktsize=" << pktsize << ", bw=" << m_llSndMaxBW); + << " by avg pktsize=" << m_zSndAvgPayloadSize + << ", bw=" << m_llSndMaxBW); } void setMaxBW(int64_t maxbw) @@ -176,7 +185,6 @@ class LiveCC: public SrtCongestionControlBase m_llSndMaxBW = maxbw > 0 ? maxbw : BW_INFINITE; updatePktSndPeriod(); -#ifdef SRT_ENABLE_NOCWND /* * UDT default flow control should not trigger under normal SRT operation * UDT stops sending if the number of packets in transit (not acknowledged) @@ -186,9 +194,6 @@ class LiveCC: public SrtCongestionControlBase */ // XXX Consider making this a socket option. m_dCWndSize = m_dMaxCWndSize; -#else - m_dCWndSize = 1000; -#endif } void updateBandwidth(int64_t maxbw, int64_t bw) ATR_OVERRIDE @@ -215,7 +220,7 @@ class LiveCC: public SrtCongestionControlBase return SrtCongestion::SRM_FASTREXMIT; } - uint64_t updateNAKInterval(uint64_t nakint_tk, int /*rcv_speed*/, size_t /*loss_length*/) ATR_OVERRIDE + int64_t updateNAKInterval(int64_t nakint_us, int /*rcv_speed*/, size_t /*loss_length*/) ATR_OVERRIDE { /* * duB: @@ -233,12 +238,12 @@ class LiveCC: public SrtCongestionControlBase // Note: this value will still be reshaped to defined minimum, // as per minNAKInterval. - return nakint_tk / m_iNakReportAccel; + return nakint_us / m_iNakReportAccel; } - uint64_t minNAKInterval() ATR_OVERRIDE + int64_t minNAKInterval() ATR_OVERRIDE { - return m_iMinNakInterval_us * CTimer::getCPUFrequency(); + return m_iMinNakInterval_us; } }; @@ -250,7 +255,7 @@ class FileCC : public SrtCongestionControlBase // Fields from CUDTCC int m_iRCInterval; // UDT Rate control interval - uint64_t m_LastRCTime; // last rate increase time + steady_clock::time_point m_LastRCTime; // last rate increase time bool m_bSlowStart; // if in slow start phase int32_t m_iLastAck; // last ACKed seq no bool m_bLoss; // if loss happened since last rate increase @@ -268,7 +273,7 @@ class FileCC : public SrtCongestionControlBase FileCC(CUDT* parent) : SrtCongestionControlBase(parent) , m_iRCInterval(CUDT::COMM_SYN_INTERVAL_US) - , m_LastRCTime(CTimer::getTime()) + , m_LastRCTime(steady_clock::now()) , m_bSlowStart(true) , m_iLastAck(parent->sndSeqNo()) , m_bLoss(false) @@ -290,9 +295,9 @@ class FileCC : public SrtCongestionControlBase m_dCWndSize = 16; m_dPktSndPeriod = 1; - parent->ConnectSignal(TEV_ACK, SSLOT(updateSndPeriod)); - parent->ConnectSignal(TEV_LOSSREPORT, SSLOT(slowdownSndPeriod)); - parent->ConnectSignal(TEV_CHECKTIMER, SSLOT(speedupToWindowSize)); + parent->ConnectSignal(TEV_ACK, SSLOT(onACK)); + parent->ConnectSignal(TEV_LOSSREPORT, SSLOT(onLossReport)); + parent->ConnectSignal(TEV_CHECKTIMER, SSLOT(onRTO)); HLOGC(cclog.Debug, log << "Creating FileCC"); } @@ -306,10 +311,11 @@ class FileCC : public SrtCongestionControlBase return true; } + /// Tells if an early ACK is needed (before the next Full ACK happening every 10ms). + /// In FileCC, treat non-full-payload as an end-of-message (stream) + /// and request ACK to be sent immediately. bool needsQuickACK(const CPacket& pkt) ATR_OVERRIDE { - // For FileCC, treat non-full-buffer situation as an end-of-message situation; - // request ACK to be sent immediately. if (pkt.getLength() < m_parent->maxPayloadSize()) { // This is not a regular fixed size packet... @@ -330,14 +336,15 @@ class FileCC : public SrtCongestionControlBase } private: - - // SLOTS - void updateSndPeriod(ETransmissionEvent, EventVariant arg) + /// Handle icoming ACK event. + /// In slow start stage increase CWND. Leave slow start once maximum CWND is reached. + /// In congestion avoidance stage adjust inter packet send interval value to achieve maximum rate. + void onACK(ETransmissionEvent, EventVariant arg) { const int ack = arg.get(); - const uint64_t currtime = CTimer::getTime(); - if (currtime - m_LastRCTime < (uint64_t)m_iRCInterval) + const steady_clock::time_point currtime = steady_clock::now(); + if (count_microseconds(currtime - m_LastRCTime) < m_iRCInterval) return; m_LastRCTime = currtime; @@ -360,11 +367,11 @@ class FileCC : public SrtCongestionControlBase } else { - m_dPktSndPeriod = m_dCWndSize / (m_parent->RTT() + m_iRCInterval); + m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: UPD (slowstart:ENDED) wndsize=" << m_dCWndSize << "/" << m_dMaxCWndSize << " sndperiod=" << m_dPktSndPeriod << "us = wndsize/(RTT+RCIV) RTT=" - << m_parent->RTT() << " RCIV=" << m_iRCInterval); + << m_parent->SRTT() << " RCIV=" << m_iRCInterval); } } else @@ -376,9 +383,9 @@ class FileCC : public SrtCongestionControlBase } else { - m_dCWndSize = m_parent->deliveryRate() / 1000000.0 * (m_parent->RTT() + m_iRCInterval) + 16; + m_dCWndSize = m_parent->deliveryRate() / 1000000.0 * (m_parent->SRTT() + m_iRCInterval) + 16; HLOGC(cclog.Debug, log << "FileCC: UPD (speed mode) wndsize=" - << m_dCWndSize << "/" << m_dMaxCWndSize << " RTT = " << m_parent->RTT() + << m_dCWndSize << "/" << m_dMaxCWndSize << " RTT = " << m_parent->SRTT() << " sndperiod=" << m_dPktSndPeriod << "us. deliverRate = " << m_parent->deliveryRate() << " pkts/s)"); } @@ -393,7 +400,7 @@ class FileCC : public SrtCongestionControlBase else { double inc = 0; - const int loss_bw = 2 * (1000000 / m_dLastDecPeriod); // 2 times last loss point + const int loss_bw = static_cast(2 * (1000000 / m_dLastDecPeriod)); // 2 times last loss point const int bw_pktps = min(loss_bw, m_parent->bandwidth()); int64_t B = (int64_t)(bw_pktps - 1000000.0 / m_dPktSndPeriod); @@ -456,9 +463,10 @@ class FileCC : public SrtCongestionControlBase } - // When a lossreport has been received, it might be due to having - // reached the available bandwidth limit. Slowdown to avoid further losses. - void slowdownSndPeriod(ETransmissionEvent, EventVariant arg) + /// When a lossreport has been received, it might be due to having + /// reached the available bandwidth limit. Slowdown to avoid further losses. + /// Leave the slow start stage if it was active. + void onLossReport(ETransmissionEvent, EventVariant arg) { const int32_t* losslist = arg.get_ptr(); size_t losslist_size = arg.get_len(); @@ -483,9 +491,9 @@ class FileCC : public SrtCongestionControlBase } else { - m_dPktSndPeriod = m_dCWndSize / (m_parent->RTT() + m_iRCInterval); + m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: LOSS, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS wndsize/(RTT+RCIV) (RTT=" - << m_parent->RTT() << " RCIV=" << m_iRCInterval << ")"); + << m_parent->SRTT() << " RCIV=" << m_iRCInterval << ")"); } } @@ -493,14 +501,14 @@ class FileCC : public SrtCongestionControlBase m_bLoss = true; // TODO: const int pktsInFlight = CSeqNo::seqoff(m_iLastAck, m_parent->sndSeqNo()); - const int pktsInFlight = m_parent->RTT() / m_dPktSndPeriod; + const int pktsInFlight = static_cast(m_parent->SRTT() / m_dPktSndPeriod); const int numPktsLost = m_parent->sndLossLength(); const int lost_pcent_x10 = pktsInFlight > 0 ? (numPktsLost * 1000) / pktsInFlight : 0; HLOGC(cclog.Debug, log << "FileCC: LOSS: " << "sent=" << CSeqNo::seqlen(m_iLastAck, m_parent->sndSeqNo()) << ", inFlight=" << pktsInFlight << ", lost=" << numPktsLost << " (" - << lost_pcent_x10 / 10 << "." << lost_pcent_x10 % 10 << "\%)"); + << lost_pcent_x10 / 10 << "." << lost_pcent_x10 % 10 << "%)"); if (lost_pcent_x10 < 20) // 2.0% { HLOGC(cclog.Debug, log << "FileCC: LOSS: m_dLastDecPeriod=" << m_dLastDecPeriod << "->" << m_dPktSndPeriod); @@ -527,11 +535,8 @@ class FileCC : public SrtCongestionControlBase m_iLastDecSeq = m_parent->sndSeqNo(); - // remove global synchronization using randomization - srand(m_iLastDecSeq); - m_iDecRandom = (int)ceil(m_iAvgNAKNum * (double(rand()) / RAND_MAX)); - if (m_iDecRandom < 1) - m_iDecRandom = 1; + m_iDecRandom = m_iAvgNAKNum > 1 ? genRandomInt(1, m_iAvgNAKNum) : 1; + SRT_ASSERT(m_iDecRandom >= 1); HLOGC(cclog.Debug, log << "FileCC: LOSS:NEW lseqno=" << lossbegin << ", lastsentseqno=" << m_iLastDecSeq << ", seqdiff=" << CSeqNo::seqoff(m_iLastDecSeq, lossbegin) @@ -562,7 +567,9 @@ class FileCC : public SrtCongestionControlBase } } - void speedupToWindowSize(ETransmissionEvent, EventVariant arg) + /// @brief On retransmission timeout leave slow start stage if it was active. + /// @param arg EventVariant::STAGE to distinguish between INIT and actual RTO. + void onRTO(ETransmissionEvent, EventVariant arg) { ECheckTimerStage stg = arg.get(); @@ -583,9 +590,9 @@ class FileCC : public SrtCongestionControlBase } else { - m_dPktSndPeriod = m_dCWndSize / (m_parent->RTT() + m_iRCInterval); + m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: CHKTIMER, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS wndsize/(RTT+RCIV) (wndsize=" - << setprecision(6) << m_dCWndSize << " RTT=" << m_parent->RTT() << " RCIV=" << m_iRCInterval << ")"); + << setprecision(6) << m_dCWndSize << " RTT=" << m_parent->SRTT() << " RCIV=" << m_iRCInterval << ")"); } } else @@ -636,8 +643,18 @@ bool SrtCongestion::configure(CUDT* parent) return !!congctl; } +void SrtCongestion::dispose() +{ + if (congctl) + { + delete congctl; + congctl = 0; + } +} + SrtCongestion::~SrtCongestion() { - delete congctl; - congctl = 0; + dispose(); } + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/congctl.h b/trunk/3rdparty/srt-1-fit/srtcore/congctl.h index 0e1729f0f2..b957dbdd02 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/congctl.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/congctl.h @@ -8,17 +8,19 @@ * */ -#ifndef INC__CONGCTL_H -#define INC__CONGCTL_H +#ifndef INC_SRT_CONGCTL_H +#define INC_SRT_CONGCTL_H +#include #include #include #include +namespace srt { + class CUDT; class SrtCongestionControlBase; - -typedef SrtCongestionControlBase* srtcc_create_t(CUDT* parent); +typedef SrtCongestionControlBase* srtcc_create_t(srt::CUDT* parent); class SrtCongestion { @@ -55,13 +57,24 @@ class SrtCongestion bool operator()(NamePtr np) { return n == np.first; } }; + static NamePtr* find(const std::string& name) + { + NamePtr* end = congctls+N_CONTROLLERS; + NamePtr* try_selector = std::find_if(congctls, end, IsName(name)); + return try_selector != end ? try_selector : NULL; + } + + static bool exists(const std::string& name) + { + return find(name); + } + // You can call select() multiple times, until finally // the 'configure' method is called. bool select(const std::string& name) { - NamePtr* end = congctls+N_CONTROLLERS; - NamePtr* try_selector = std::find_if(congctls, end, IsName(name)); - if (try_selector == end) + NamePtr* try_selector = find(name); + if (!try_selector) return false; selector = try_selector - congctls; return true; @@ -79,12 +92,18 @@ class SrtCongestion // 1. The congctl is individual, so don't copy it. Set NULL. // 2. The selected name is copied so that it's configured correctly. SrtCongestion(const SrtCongestion& source): congctl(), selector(source.selector) {} + void operator=(const SrtCongestion& source) { congctl = 0; selector = source.selector; } // This function will be called by the parent CUDT // in appropriate time. It should select appropriate // congctl basing on the value in selector, then // pin oneself in into CUDT for receiving event signals. - bool configure(CUDT* parent); + bool configure(srt::CUDT* parent); + + // This function will intentionally delete the contained object. + // This makes future calls to ready() return false. Calling + // configure on it again will create it again. + void dispose(); // Will delete the pinned in congctl object. // This must be defined in *.cpp file due to virtual @@ -111,12 +130,13 @@ class SrtCongestion }; }; +class CPacket; class SrtCongestionControlBase { protected: // Here can be some common fields - CUDT* m_parent; + srt::CUDT* m_parent; double m_dPktSndPeriod; double m_dCWndSize; @@ -127,11 +147,11 @@ class SrtCongestionControlBase //int m_iMSS; // NOT REQUIRED. Use m_parent->MSS() instead. //int32_t m_iSndCurrSeqNo; // NOT REQUIRED. Use m_parent->sndSeqNo(). //int m_iRcvRate; // NOT REQUIRED. Use m_parent->deliveryRate() instead. - //int m_RTT; // NOT REQUIRED. Use m_parent->RTT() instead. + //int m_RTT; // NOT REQUIRED. Use m_parent->SRTT() instead. //char* m_pcParam; // Used to access m_llMaxBw. Use m_parent->maxBandwidth() instead. // Constructor in protected section so that this class is semi-abstract. - SrtCongestionControlBase(CUDT* parent); + SrtCongestionControlBase(srt::CUDT* parent); public: // This could be also made abstract, but this causes a linkage @@ -169,11 +189,11 @@ class SrtCongestionControlBase virtual int ACKTimeout_us() const { return 0; } // Called when the settings concerning m_llMaxBW were changed. - // Arg 1: value of CUDT::m_llMaxBW - // Arg 2: value calculated out of CUDT::m_llInputBW and CUDT::m_iOverheadBW. + // Arg 1: value of CUDT's m_config.m_llMaxBW + // Arg 2: value calculated out of CUDT's m_config.llInputBW and m_config.iOverheadBW. virtual void updateBandwidth(int64_t, int64_t) {} - virtual bool needsQuickACK(const CPacket&) + virtual bool needsQuickACK(const srt::CPacket&) { return false; } @@ -186,21 +206,21 @@ class SrtCongestionControlBase virtual SrtCongestion::RexmitMethod rexmitMethod() = 0; // Implementation enforced. - virtual uint64_t updateNAKInterval(uint64_t nakint_tk, int rcv_speed, size_t loss_length) + virtual int64_t updateNAKInterval(int64_t nakint_us, int rcv_speed, size_t loss_length) { if (rcv_speed > 0) - nakint_tk += (loss_length * uint64_t(1000000) / rcv_speed) * CTimer::getCPUFrequency(); + nakint_us += (loss_length * int64_t(1000000) / rcv_speed); - return nakint_tk; + return nakint_us; } - virtual uint64_t minNAKInterval() + virtual int64_t minNAKInterval() { return 0; // Leave default } }; - +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/core.cpp b/trunk/3rdparty/srt-1-fit/srtcore/core.cpp index edadafaaa4..93224f418b 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/core.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/core.cpp @@ -50,25 +50,30 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef _WIN32 -#include -#include -#include -#include -#include -#include -#else -#include -#include +#include "platform_sys.h" + +// Linux specific +#ifdef SRT_ENABLE_BINDTODEVICE +#include #endif + #include #include +#include +#include #include "srt.h" #include "queue.h" +#include "api.h" #include "core.h" #include "logging.h" #include "crypto.h" #include "logging_api.h" // Required due to containing extern srt_logger_config +#include "logger_defs.h" + +#if !HAVE_CXX11 +// for pthread_once +#include +#endif // Again, just in case when some "smart guy" provided such a global macro #ifdef min @@ -79,64 +84,12 @@ modified by #endif using namespace std; - -namespace srt_logging -{ - -struct AllFaOn -{ - LogConfig::fa_bitset_t allfa; - - AllFaOn() - { - // allfa.set(SRT_LOGFA_BSTATS, true); - allfa.set(SRT_LOGFA_CONTROL, true); - allfa.set(SRT_LOGFA_DATA, true); - allfa.set(SRT_LOGFA_TSBPD, true); - allfa.set(SRT_LOGFA_REXMIT, true); - allfa.set(SRT_LOGFA_CONGEST, true); -#if ENABLE_HAICRYPT_LOGGING - allfa.set(SRT_LOGFA_HAICRYPT, true); -#endif - } -} logger_fa_all; - -} // namespace srt_logging - -// We need it outside the namespace to preserve the global name. -// It's a part of "hidden API" (used by applications) -SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa); - -namespace srt_logging -{ - -Logger glog(SRT_LOGFA_GENERAL, srt_logger_config, "SRT.g"); -// Unused. If not found useful, maybe reuse for another FA. -// Logger blog(SRT_LOGFA_BSTATS, srt_logger_config, "SRT.b"); -Logger mglog(SRT_LOGFA_CONTROL, srt_logger_config, "SRT.c"); -Logger dlog(SRT_LOGFA_DATA, srt_logger_config, "SRT.d"); -Logger tslog(SRT_LOGFA_TSBPD, srt_logger_config, "SRT.t"); -Logger rxlog(SRT_LOGFA_REXMIT, srt_logger_config, "SRT.r"); -Logger cclog(SRT_LOGFA_CONGEST, srt_logger_config, "SRT.cc"); - -} // namespace srt_logging - +using namespace srt; +using namespace srt::sync; using namespace srt_logging; -CUDTUnited CUDT::s_UDTUnited; - -const SRTSOCKET UDT::INVALID_SOCK = CUDT::INVALID_SOCK; -const int UDT::ERROR = CUDT::ERROR; - -// SRT Version constants -#define SRT_VERSION_UNK 0 -#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */ -#define SRT_VERSION_MAJ(v) (0xFF0000 & (v)) /* Major number ensuring backward compatibility */ -#define SRT_VERSION_MIN(v) (0x00FF00 & (v)) -#define SRT_VERSION_PCH(v) (0x0000FF & (v)) - -// NOTE: SRT_VERSION is primarily defined in the build file. -const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION); +const SRTSOCKET UDT::INVALID_SOCK = srt::CUDT::INVALID_SOCK; +const int UDT::ERROR = srt::CUDT::ERROR; //#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */ #define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */ @@ -165,27 +118,160 @@ const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION); 2[15..0]: TsbPD delay [0..60000] msec */ -void CUDT::construct() +extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = { + SRTO_SNDSYN, + SRTO_RCVSYN, + SRTO_LINGER, + SRTO_SNDTIMEO, + SRTO_RCVTIMEO, + SRTO_MAXBW, + SRTO_INPUTBW, + SRTO_MININPUTBW, + SRTO_OHEADBW, + SRTO_SNDDROPDELAY, + SRTO_DRIFTTRACER, + SRTO_LOSSMAXTTL +}; + +const int32_t + SRTO_R_PREBIND = BIT(0), //< cannot be modified after srt_bind() + SRTO_R_PRE = BIT(1), //< cannot be modified after connection is established + SRTO_POST_SPEC = BIT(2); //< executes some action after setting the option + + +namespace srt +{ + +struct SrtOptionAction +{ + int flags[SRTO_E_SIZE]; + std::map private_default; + SrtOptionAction() + { + // Set everything to 0 to clear all flags + // When an option isn't present here, it means that: + // * it is not settable, or + // * the option is POST (non-restricted) + // * it has no post-actions + // The post-action may be defined independently on restrictions. + memset(flags, 0, sizeof flags); + + flags[SRTO_MSS] = SRTO_R_PREBIND; + flags[SRTO_FC] = SRTO_R_PRE; + flags[SRTO_SNDBUF] = SRTO_R_PREBIND; + flags[SRTO_RCVBUF] = SRTO_R_PREBIND; + flags[SRTO_UDP_SNDBUF] = SRTO_R_PREBIND; + flags[SRTO_UDP_RCVBUF] = SRTO_R_PREBIND; + flags[SRTO_RENDEZVOUS] = SRTO_R_PRE; + flags[SRTO_REUSEADDR] = SRTO_R_PREBIND; + flags[SRTO_MAXBW] = SRTO_POST_SPEC; + flags[SRTO_SENDER] = SRTO_R_PRE; + flags[SRTO_TSBPDMODE] = SRTO_R_PRE; + flags[SRTO_LATENCY] = SRTO_R_PRE; + flags[SRTO_INPUTBW] = SRTO_POST_SPEC; + flags[SRTO_MININPUTBW] = SRTO_POST_SPEC; + flags[SRTO_OHEADBW] = SRTO_POST_SPEC; + flags[SRTO_PASSPHRASE] = SRTO_R_PRE; + flags[SRTO_PBKEYLEN] = SRTO_R_PRE; + flags[SRTO_IPTTL] = SRTO_R_PREBIND; + flags[SRTO_IPTOS] = SRTO_R_PREBIND; + flags[SRTO_TLPKTDROP] = SRTO_R_PRE; + flags[SRTO_SNDDROPDELAY] = SRTO_POST_SPEC; + flags[SRTO_NAKREPORT] = SRTO_R_PRE; + flags[SRTO_VERSION] = SRTO_R_PRE; + flags[SRTO_CONNTIMEO] = SRTO_R_PRE; + flags[SRTO_LOSSMAXTTL] = SRTO_POST_SPEC; + flags[SRTO_RCVLATENCY] = SRTO_R_PRE; + flags[SRTO_PEERLATENCY] = SRTO_R_PRE; + flags[SRTO_MINVERSION] = SRTO_R_PRE; + flags[SRTO_STREAMID] = SRTO_R_PRE; + flags[SRTO_CONGESTION] = SRTO_R_PRE; + flags[SRTO_MESSAGEAPI] = SRTO_R_PRE; + flags[SRTO_PAYLOADSIZE] = SRTO_R_PRE; + flags[SRTO_TRANSTYPE] = SRTO_R_PREBIND; + flags[SRTO_KMREFRESHRATE] = SRTO_R_PRE; + flags[SRTO_KMPREANNOUNCE] = SRTO_R_PRE; + flags[SRTO_ENFORCEDENCRYPTION] = SRTO_R_PRE; + flags[SRTO_IPV6ONLY] = SRTO_R_PREBIND; + flags[SRTO_PEERIDLETIMEO] = SRTO_R_PRE; +#ifdef SRT_ENABLE_BINDTODEVICE + flags[SRTO_BINDTODEVICE] = SRTO_R_PREBIND; +#endif +#if ENABLE_BONDING + flags[SRTO_GROUPCONNECT] = SRTO_R_PRE; + flags[SRTO_GROUPMINSTABLETIMEO]= SRTO_R_PRE; +#endif + flags[SRTO_PACKETFILTER] = SRTO_R_PRE; + flags[SRTO_RETRANSMITALGO] = SRTO_R_PRE; + + // For "private" options (not derived from the listener + // socket by an accepted socket) provide below private_default + // to which these options will be reset after blindly + // copying the option object from the listener socket. + // Note that this option cannot have runtime-dependent + // default value, like options affected by SRTO_TRANSTYPE. + + // Options may be of different types, but this value should be only + // used as a source of the value. For example, in case of int64_t you'd + // have to place here a string of 8 characters. It should be copied + // always in the hardware order, as this is what will be directly + // passed to a setting function. + private_default[SRTO_STREAMID] = string(); + } +}; + +const SrtOptionAction s_sockopt_action; + +} // namespace srt + +#if HAVE_CXX11 + +CUDTUnited& srt::CUDT::uglobal() +{ + static CUDTUnited instance; + return instance; +} + +#else // !HAVE_CXX11 + +static pthread_once_t s_UDTUnitedOnce = PTHREAD_ONCE_INIT; + +static CUDTUnited *getInstance() +{ + static CUDTUnited instance; + return &instance; +} + +CUDTUnited& srt::CUDT::uglobal() +{ + // We don't want lock each time, pthread_once can be faster than mutex. + pthread_once(&s_UDTUnitedOnce, reinterpret_cast(getInstance)); + return *getInstance(); +} + +#endif + +void srt::CUDT::construct() { m_pSndBuffer = NULL; m_pRcvBuffer = NULL; m_pSndLossList = NULL; m_pRcvLossList = NULL; m_iReorderTolerance = 0; - m_iMaxReorderTolerance = 0; // Sensible optimal value is 10, 0 preserves old behavior - m_iConsecEarlyDelivery = 0; // how many times so far the packet considered lost has been received before TTL expires + // How many times so far the packet considered lost has been received + // before TTL expires. + m_iConsecEarlyDelivery = 0; m_iConsecOrderedDelivery = 0; m_pSndQueue = NULL; m_pRcvQueue = NULL; - m_pPeerAddr = NULL; m_pSNode = NULL; m_pRNode = NULL; - m_ullSndHsLastTime_us = 0; - m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; // Will be reset to 0 for HSv5, this value is important for HSv4 + // Will be reset to 0 for HSv5, this value is important for HSv4. + m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; - // Initial status + m_PeerID = 0; m_bOpened = false; m_bListening = false; m_bConnecting = false; @@ -193,776 +279,179 @@ void CUDT::construct() m_bClosing = false; m_bShutdown = false; m_bBroken = false; + m_bBreakAsUnstable = false; + // TODO: m_iBrokenCounter should be still set to some default. m_bPeerHealth = true; m_RejectReason = SRT_REJ_UNKNOWN; - m_ullLingerExpiration = 0; - m_llLastReqTime = 0; - - m_lSrtVersion = SRT_DEF_VERSION; - m_lPeerSrtVersion = 0; // not defined until connected. - m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1; - - m_iTsbPdDelay_ms = 0; - m_iPeerTsbPdDelay_ms = 0; - - m_bPeerTsbPd = false; - m_iPeerTsbPdDelay_ms = 0; - m_bTsbPd = false; - m_bTsbPdAckWakeup = false; - m_bPeerTLPktDrop = false; - - m_uKmRefreshRatePkt = 0; - m_uKmPreAnnouncePkt = 0; - - // Initilize mutex and condition variables + m_tsLastReqTime.store(steady_clock::time_point()); + m_SrtHsSide = HSD_DRAW; + m_uPeerSrtVersion = 0; // Not defined until connected. + m_iTsbPdDelay_ms = 0; + m_iPeerTsbPdDelay_ms = 0; + m_bPeerTsbPd = false; + m_iPeerTsbPdDelay_ms = 0; + m_bTsbPd = false; + m_bTsbPdAckWakeup = false; + m_bGroupTsbPd = false; + m_bPeerTLPktDrop = false; + + // Initilize mutex and condition variables. initSynch(); + + // TODO: Uncomment when the callback is implemented. + // m_cbPacketArrival.set(this, &CUDT::defaultPacketArrival); } -CUDT::CUDT() +srt::CUDT::CUDT(CUDTSocket* parent): m_parent(parent) { construct(); (void)SRT_DEF_VERSION; - // Default UDT configurations - m_iMSS = 1500; - m_bSynSending = true; - m_bSynRecving = true; - m_iFlightFlagSize = 25600; - m_iSndBufSize = 8192; - m_iRcvBufSize = 8192; // Rcv buffer MUST NOT be bigger than Flight Flag size - - // Linger: LIVE mode defaults, please refer to `SRTO_TRANSTYPE` option - // for other modes. - m_Linger.l_onoff = 0; - m_Linger.l_linger = 0; - m_iUDPSndBufSize = 65536; - m_iUDPRcvBufSize = m_iRcvBufSize * m_iMSS; - m_iSockType = UDT_DGRAM; - m_iIPversion = AF_INET; - m_bRendezvous = false; -#ifdef SRT_ENABLE_CONNTIMEO - m_iConnTimeOut = 3000; + // Runtime fields +#if ENABLE_BONDING + m_HSGroupType = SRT_GTYPE_UNDEFINED; #endif - m_iSndTimeOut = -1; - m_iRcvTimeOut = -1; - m_bReuseAddr = true; - m_llMaxBW = -1; -#ifdef SRT_ENABLE_IPOPTS - m_iIpTTL = -1; - m_iIpToS = -1; -#endif - m_CryptoSecret.len = 0; - m_iSndCryptoKeyLen = 0; - // Cfg - m_bDataSender = false; // Sender only if true: does not recv data - m_bOPT_TsbPd = true; // Enable TsbPd on sender - m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS; - m_iOPT_PeerTsbPdDelay = 0; // Peer's TsbPd delay as receiver (here is its minimum value, if used) - m_bOPT_TLPktDrop = true; - m_iOPT_SndDropDelay = 0; - m_bOPT_StrictEncryption = true; - m_iOPT_PeerIdleTimeout = COMM_RESPONSE_TIMEOUT_MS; m_bTLPktDrop = true; // Too-late Packet Drop - m_bMessageAPI = true; - m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE; - m_iIpV6Only = -1; - // Runtime - m_bRcvNakReport = true; // Receiver's Periodic NAK Reports - m_llInputBW = 0; // Application provided input bandwidth (internal input rate sampling == 0) - m_iOverheadBW = 25; // Percent above input stream rate (applies if m_llMaxBW == 0) - m_OPT_PktFilterConfigString = ""; m_pCache = NULL; + // This is in order to set it ANY kind of initial value, however + // this value should not be used when not connected and should be + // updated in the handshake. When this value is 0, it means that + // packets shall not be sent, as the other party doesn't have a + // room to receive and store it. Therefore this value should be + // overridden before any sending happens. + m_iFlowWindowSize = 0; - // Default congctl is "live". - // Available builtin congctl: "file". - // Other congctls can be registerred. - - // Note that 'select' returns false if there's no such congctl. - // If so, congctl becomes unselected. Calling 'configure' on an - // unselected congctl results in exception. - m_CongCtl.select("live"); } -CUDT::CUDT(const CUDT &ancestor) +srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor): m_parent(parent) { construct(); // XXX Consider all below fields (except m_bReuseAddr) to be put - // into a separate class for easier copying. - - // Default UDT configurations - m_iMSS = ancestor.m_iMSS; - m_bSynSending = ancestor.m_bSynSending; - m_bSynRecving = ancestor.m_bSynRecving; - m_iFlightFlagSize = ancestor.m_iFlightFlagSize; - m_iSndBufSize = ancestor.m_iSndBufSize; - m_iRcvBufSize = ancestor.m_iRcvBufSize; - m_Linger = ancestor.m_Linger; - m_iUDPSndBufSize = ancestor.m_iUDPSndBufSize; - m_iUDPRcvBufSize = ancestor.m_iUDPRcvBufSize; - m_iSockType = ancestor.m_iSockType; - m_iIPversion = ancestor.m_iIPversion; - m_bRendezvous = ancestor.m_bRendezvous; -#ifdef SRT_ENABLE_CONNTIMEO - m_iConnTimeOut = ancestor.m_iConnTimeOut; -#endif - m_iSndTimeOut = ancestor.m_iSndTimeOut; - m_iRcvTimeOut = ancestor.m_iRcvTimeOut; - m_bReuseAddr = true; // this must be true, because all accepted sockets share the same port with the listener - m_llMaxBW = ancestor.m_llMaxBW; -#ifdef SRT_ENABLE_IPOPTS - m_iIpTTL = ancestor.m_iIpTTL; - m_iIpToS = ancestor.m_iIpToS; -#endif - m_llInputBW = ancestor.m_llInputBW; - m_iOverheadBW = ancestor.m_iOverheadBW; - m_bDataSender = ancestor.m_bDataSender; - m_bOPT_TsbPd = ancestor.m_bOPT_TsbPd; - m_iOPT_TsbPdDelay = ancestor.m_iOPT_TsbPdDelay; - m_iOPT_PeerTsbPdDelay = ancestor.m_iOPT_PeerTsbPdDelay; - m_bOPT_TLPktDrop = ancestor.m_bOPT_TLPktDrop; - m_iOPT_SndDropDelay = ancestor.m_iOPT_SndDropDelay; - m_bOPT_StrictEncryption = ancestor.m_bOPT_StrictEncryption; - m_iOPT_PeerIdleTimeout = ancestor.m_iOPT_PeerIdleTimeout; - m_zOPT_ExpPayloadSize = ancestor.m_zOPT_ExpPayloadSize; - m_bTLPktDrop = ancestor.m_bTLPktDrop; - m_bMessageAPI = ancestor.m_bMessageAPI; - m_iIpV6Only = ancestor.m_iIpV6Only; - m_iReorderTolerance = ancestor.m_iMaxReorderTolerance; // Initialize with maximum value - m_iMaxReorderTolerance = ancestor.m_iMaxReorderTolerance; - // Runtime - m_bRcvNakReport = ancestor.m_bRcvNakReport; - m_OPT_PktFilterConfigString = ancestor.m_OPT_PktFilterConfigString; - - m_CryptoSecret = ancestor.m_CryptoSecret; - m_iSndCryptoKeyLen = ancestor.m_iSndCryptoKeyLen; - - m_uKmRefreshRatePkt = ancestor.m_uKmRefreshRatePkt; - m_uKmPreAnnouncePkt = ancestor.m_uKmPreAnnouncePkt; - - m_pCache = ancestor.m_pCache; - - // SrtCongestion's copy constructor copies the selection, - // but not the underlying congctl object. After - // copy-constructed, the 'configure' must be called on it again. - m_CongCtl = ancestor.m_CongCtl; -} - -CUDT::~CUDT() -{ - // release mutex/condtion variables - destroySynch(); - - // Wipeout critical data - memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret)); - - // destroy the data structures - delete m_pSndBuffer; - delete m_pRcvBuffer; - delete m_pSndLossList; - delete m_pRcvLossList; - delete m_pPeerAddr; - delete m_pSNode; - delete m_pRNode; -} - -// This function is to make it possible for both C and C++ -// API to accept both bool and int types for boolean options. -// (it's not that C couldn't use , it's that people -// often forget to use correct type). -static bool bool_int_value(const void *optval, int optlen) -{ - if (optlen == sizeof(bool)) - { - return *(bool *)optval; - } - - if (optlen == sizeof(int)) - { - return 0 != *(int *)optval; // 0!= is a windows warning-killer int-to-bool conversion - } - return false; -} - -void CUDT::setOpt(SRT_SOCKOPT optName, const void *optval, int optlen) -{ - if (m_bBroken || m_bClosing) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - - CGuard cg(m_ConnectionLock); - CGuard sendguard(m_SendLock); - CGuard recvguard(m_RecvLock); - - switch (optName) - { - case SRTO_MSS: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - - if (*(int *)optval < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - m_iMSS = *(int *)optval; - - // Packet size cannot be greater than UDP buffer size - if (m_iMSS > m_iUDPSndBufSize) - m_iMSS = m_iUDPSndBufSize; - if (m_iMSS > m_iUDPRcvBufSize) - m_iMSS = m_iUDPRcvBufSize; - - break; - - case SRTO_SNDSYN: - m_bSynSending = bool_int_value(optval, optlen); - break; - - case SRTO_RCVSYN: - m_bSynRecving = bool_int_value(optval, optlen); - break; - - case SRTO_FC: - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - if (*(int *)optval < 1) - throw CUDTException(MJ_NOTSUP, MN_INVAL); - - // Mimimum recv flight flag size is 32 packets - if (*(int *)optval > 32) - m_iFlightFlagSize = *(int *)optval; - else - m_iFlightFlagSize = 32; - - break; - - case SRTO_SNDBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - - if (*(int *)optval <= 0) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - m_iSndBufSize = *(int *)optval / (m_iMSS - CPacket::UDP_HDR_SIZE); - - break; - - case SRTO_RCVBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - - if (*(int *)optval <= 0) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - { - // This weird cast through int is required because - // API requires 'int', and internals require 'size_t'; - // their size is different on 64-bit systems. - size_t val = size_t(*(int *)optval); - - // Mimimum recv buffer size is 32 packets - size_t mssin_size = m_iMSS - CPacket::UDP_HDR_SIZE; - - // XXX This magic 32 deserves some constant - if (val > mssin_size * 32) - m_iRcvBufSize = val / mssin_size; - else - m_iRcvBufSize = 32; - - // recv buffer MUST not be greater than FC size - if (m_iRcvBufSize > m_iFlightFlagSize) - m_iRcvBufSize = m_iFlightFlagSize; - } - - break; - - case SRTO_LINGER: - m_Linger = *(linger *)optval; - break; - - case SRTO_UDP_SNDBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - - m_iUDPSndBufSize = *(int *)optval; - - if (m_iUDPSndBufSize < m_iMSS) - m_iUDPSndBufSize = m_iMSS; - - break; - - case SRTO_UDP_RCVBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - - m_iUDPRcvBufSize = *(int *)optval; - - if (m_iUDPRcvBufSize < m_iMSS) - m_iUDPRcvBufSize = m_iMSS; - - break; - - case SRTO_RENDEZVOUS: - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_bRendezvous = bool_int_value(optval, optlen); - break; - - case SRTO_SNDTIMEO: - m_iSndTimeOut = *(int *)optval; - break; - - case SRTO_RCVTIMEO: - m_iRcvTimeOut = *(int *)optval; - break; - - case SRTO_REUSEADDR: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_bReuseAddr = bool_int_value(optval, optlen); - break; - - case SRTO_MAXBW: - m_llMaxBW = *(int64_t *)optval; - - // This can be done on both connected and unconnected socket. - // When not connected, this will do nothing, however this - // event will be repeated just after connecting anyway. - if (m_bConnected) - updateCC(TEV_INIT, TEV_INIT_RESET); - break; - -#ifdef SRT_ENABLE_IPOPTS - case SRTO_IPTTL: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - if (!(*(int *)optval == -1) && !((*(int *)optval >= 1) && (*(int *)optval <= 255))) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - m_iIpTTL = *(int *)optval; - break; - - case SRTO_IPTOS: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_iIpToS = *(int *)optval; - break; -#endif - - case SRTO_INPUTBW: - m_llInputBW = *(int64_t *)optval; - // (only if connected; if not, then the value - // from m_iOverheadBW will be used initially) - if (m_bConnected) - updateCC(TEV_INIT, TEV_INIT_INPUTBW); - break; - - case SRTO_OHEADBW: - if ((*(int *)optval < 5) || (*(int *)optval > 100)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - m_iOverheadBW = *(int *)optval; - - // Changed overhead BW, so spread the change - // (only if connected; if not, then the value - // from m_iOverheadBW will be used initially) - if (m_bConnected) - updateCC(TEV_INIT, TEV_INIT_OHEADBW); - break; - - case SRTO_SENDER: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bDataSender = bool_int_value(optval, optlen); - break; - - case SRTO_TSBPDMODE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bOPT_TsbPd = bool_int_value(optval, optlen); - break; - - case SRTO_LATENCY: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iOPT_TsbPdDelay = *(int *)optval; - m_iOPT_PeerTsbPdDelay = *(int *)optval; - break; - - case SRTO_RCVLATENCY: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iOPT_TsbPdDelay = *(int *)optval; - break; - - case SRTO_PEERLATENCY: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iOPT_PeerTsbPdDelay = *(int *)optval; - break; - - case SRTO_TLPKTDROP: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bOPT_TLPktDrop = bool_int_value(optval, optlen); - break; - - case SRTO_SNDDROPDELAY: - // Surprise: you may be connected to alter this option. - // The application may manipulate this option on sender while transmitting. - m_iOPT_SndDropDelay = *(int *)optval; - break; - - case SRTO_PASSPHRASE: - // For consistency, throw exception when connected, - // no matter if otherwise the password can be set. - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - -#ifdef SRT_ENABLE_ENCRYPTION - // Password must be 10-80 characters. - // Or it can be empty to clear the password. - if ((optlen != 0) && (optlen < 10 || optlen > HAICRYPT_SECRET_MAX_SZ)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret)); - m_CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; - m_CryptoSecret.len = (optlen <= (int)sizeof(m_CryptoSecret.str) ? optlen : (int)sizeof(m_CryptoSecret.str)); - memcpy(m_CryptoSecret.str, optval, m_CryptoSecret.len); -#else - if (optlen == 0) - break; - - LOGC(mglog.Error, log << "SRTO_PASSPHRASE: encryption not enabled at compile time"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); -#endif - break; - - case SRTO_PBKEYLEN: - case _DEPRECATED_SRTO_SNDPBKEYLEN: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); -#ifdef SRT_ENABLE_ENCRYPTION - { - int v = *(int *)optval; - int allowed[4] = { - 0, // Default value, if this results for initiator, defaults to 16. See below. - 16, // AES-128 - 24, // AES-192 - 32 // AES-256 - }; - int *allowed_end = allowed + 4; - if (find(allowed, allowed_end, v) == allowed_end) - { - LOGC(mglog.Error, - log << "Invalid value for option SRTO_PBKEYLEN: " << v << "; allowed are: 0, 16, 24, 32"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - - // Note: This works a little different in HSv4 and HSv5. - - // HSv4: - // The party that is set SRTO_SENDER will send KMREQ, and it will - // use default value 16, if SRTO_PBKEYLEN is the default value 0. - // The responder that receives KMRSP has nothing to say about - // PBKEYLEN anyway and it will take the length of the key from - // the initiator (sender) as a good deal. - // - // HSv5: - // The initiator (independently on the sender) will send KMREQ, - // and as it should be the sender to decide about the PBKEYLEN. - // Your application should do the following then: - // 1. The sender should set PBKEYLEN to the required value. - // 2. If the sender is initiator, it will create the key using - // its preset PBKEYLEN (or default 16, if not set) and the - // receiver-responder will take it as a good deal. - // 3. Leave the PBKEYLEN value on the receiver as default 0. - // 4. If sender is responder, it should then advertise the PBKEYLEN - // value in the initial handshake messages (URQ_INDUCTION if - // listener, and both URQ_WAVEAHAND and URQ_CONCLUSION in case - // of rendezvous, as it is the matter of luck who of them will - // eventually become the initiator). This way the receiver - // being an initiator will set m_iSndCryptoKeyLen before setting - // up KMREQ for sending to the sender-responder. - // - // Note that in HSv5 if both sides set PBKEYLEN, the responder - // wins, unless the initiator is a sender (the effective PBKEYLEN - // will be the one advertised by the responder). If none sets, - // PBKEYLEN will default to 16. - - m_iSndCryptoKeyLen = v; - } -#else - LOGC(mglog.Error, log << "SRTO_PBKEYLEN: encryption not enabled at compile time"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); -#endif - break; - - case SRTO_NAKREPORT: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bRcvNakReport = bool_int_value(optval, optlen); - break; - -#ifdef SRT_ENABLE_CONNTIMEO - case SRTO_CONNTIMEO: - m_iConnTimeOut = *(int *)optval; - break; -#endif - - case SRTO_LOSSMAXTTL: - m_iMaxReorderTolerance = *(int *)optval; - if (!m_bConnected) - m_iReorderTolerance = m_iMaxReorderTolerance; - break; - - case SRTO_VERSION: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_lSrtVersion = *(uint32_t *)optval; - break; - - case SRTO_MINVERSION: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_lMinimumPeerSrtVersion = *(uint32_t *)optval; - break; - - case SRTO_STREAMID: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - if (size_t(optlen) > MAX_SID_LENGTH) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - m_sStreamName.assign((const char *)optval, optlen); - break; - - case SRTO_CONGESTION: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - { - string val; - if (optlen == -1) - val = (const char *)optval; - else - val.assign((const char *)optval, optlen); - - // Translate alias - if (val == "vod") - val = "file"; - - bool res = m_CongCtl.select(val); - if (!res) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - break; - - case SRTO_MESSAGEAPI: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - m_bMessageAPI = bool_int_value(optval, optlen); - break; - - case SRTO_PAYLOADSIZE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - if (*(int *)optval > SRT_LIVE_MAX_PLSIZE) - { - LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE, maximum payload per MTU."); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } + // into a separate class for easier copying. - if (m_OPT_PktFilterConfigString != "") + m_config = ancestor.m_config; + // Reset values that shall not be derived to default ones. + // These declarations should be consistent with SRTO_R_PRIVATE flag. + for (size_t i = 0; i < Size(s_sockopt_action.flags); ++i) + { + const string* pdef = map_getp(s_sockopt_action.private_default, SRT_SOCKOPT(i)); + if (pdef) { - // This means that the filter might have been installed before, - // and the fix to the maximum payload size was already applied. - // This needs to be checked now. - SrtFilterConfig fc; - if (!ParseFilterConfig(m_OPT_PktFilterConfigString, fc)) + try { - // Break silently. This should not happen - LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + // Ignore errors here - this is a development-time granted + // value, not user-provided value. + m_config.set(SRT_SOCKOPT(i), pdef->data(), (int) pdef->size()); } - - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; - if (m_zOPT_ExpPayloadSize > efc_max_payload_size) + catch (...) { - LOGC(mglog.Error, - log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE decreased by " << fc.extra_size - << " required for packet filter header"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + LOGC(gglog.Error, log << "IPE: failed to set a declared default option!"); } } + } - m_zOPT_ExpPayloadSize = *(int *)optval; - break; - - case SRTO_TRANSTYPE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - // XXX Note that here the configuration for SRTT_LIVE - // is the same as DEFAULT VALUES for these fields set - // in CUDT::CUDT. - switch (*(SRT_TRANSTYPE *)optval) - { - case SRTT_LIVE: - // Default live options: - // - tsbpd: on - // - latency: 120ms - // - linger: off - // - congctl: live - // - extraction method: message (reading call extracts one message) - m_bOPT_TsbPd = true; - m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS; - m_iOPT_PeerTsbPdDelay = 0; - m_bOPT_TLPktDrop = true; - m_iOPT_SndDropDelay = 0; - m_bMessageAPI = true; - m_bRcvNakReport = true; - m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE; - m_Linger.l_onoff = 0; - m_Linger.l_linger = 0; - m_CongCtl.select("live"); - break; + m_SrtHsSide = ancestor.m_SrtHsSide; // actually it sets it to HSD_RESPONDER + m_bTLPktDrop = ancestor.m_bTLPktDrop; + m_iReorderTolerance = m_config.iMaxReorderTolerance; // Initialize with maximum value - case SRTT_FILE: - // File transfer mode: - // - tsbpd: off - // - latency: 0 - // - linger: 2 minutes (180s) - // - congctl: file (original UDT congestion control) - // - extraction method: stream (reading call extracts as many bytes as available and fits in buffer) - m_bOPT_TsbPd = false; - m_iOPT_TsbPdDelay = 0; - m_iOPT_PeerTsbPdDelay = 0; - m_bOPT_TLPktDrop = false; - m_iOPT_SndDropDelay = -1; - m_bMessageAPI = false; - m_bRcvNakReport = false; - m_zOPT_ExpPayloadSize = 0; // use maximum - m_Linger.l_onoff = 1; - m_Linger.l_linger = 180; // 2 minutes - m_CongCtl.select("file"); - break; + // Runtime + m_pCache = ancestor.m_pCache; +} - default: - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - break; +srt::CUDT::~CUDT() +{ + // release mutex/condtion variables + destroySynch(); - case SRTO_KMREFRESHRATE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + // destroy the data structures + delete m_pSndBuffer; + delete m_pRcvBuffer; + delete m_pSndLossList; + delete m_pRcvLossList; + delete m_pSNode; + delete m_pRNode; +} - // If you first change the KMREFRESHRATE, KMPREANNOUNCE - // will be set to the maximum allowed value - m_uKmRefreshRatePkt = *(int *)optval; - if (m_uKmPreAnnouncePkt == 0 || m_uKmPreAnnouncePkt > (m_uKmRefreshRatePkt - 1) / 2) - { - m_uKmPreAnnouncePkt = (m_uKmRefreshRatePkt - 1) / 2; - LOGC(mglog.Warn, - log << "SRTO_KMREFRESHRATE=0x" << hex << m_uKmRefreshRatePkt << ": setting SRTO_KMPREANNOUNCE=0x" - << hex << m_uKmPreAnnouncePkt); - } - break; +void srt::CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) +{ + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - case SRTO_KMPREANNOUNCE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - { - int val = *(int *)optval; - int kmref = m_uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_uKmRefreshRatePkt; - if (val > (kmref - 1) / 2) - { - LOGC(mglog.Error, - log << "SRTO_KMPREANNOUNCE=0x" << hex << val << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2) - << " - OPTION REJECTED."); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } + // Match check (confirm optName as index for s_sockopt_action) + if (int(optName) < 0 || int(optName) >= int(SRTO_E_SIZE)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - m_uKmPreAnnouncePkt = val; - } - break; + // Restriction check + const int oflags = s_sockopt_action.flags[optName]; - case SRTO_ENFORCEDENCRYPTION: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + ScopedLock cg (m_ConnectionLock); + ScopedLock sendguard (m_SendLock); + ScopedLock recvguard (m_RecvLock); - m_bOPT_StrictEncryption = bool_int_value(optval, optlen); - break; + HLOGC(aclog.Debug, + log << CONID() << "OPTION: #" << optName << " value:" << FormatBinaryString((uint8_t*)optval, optlen)); - case SRTO_PEERIDLETIMEO: + if (IsSet(oflags, SRTO_R_PREBIND) && m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iOPT_PeerIdleTimeout = *(int *)optval; - break; + if (IsSet(oflags, SRTO_R_PRE) && (m_bConnected || m_bConnecting || m_bListening)) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - case SRTO_IPV6ONLY: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + // Option execution. If this returns -1, there's no such option. + const int status = m_config.set(optName, optval, optlen); + if (status == -1) + { + LOGC(aclog.Error, log << CONID() << "OPTION: #" << optName << " UNKNOWN"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } - m_iIpV6Only = *(int *)optval; - break; + // Post-action, if applicable + if (IsSet(oflags, SRTO_POST_SPEC) && m_bConnected) + { + switch (optName) + { + case SRTO_MAXBW: + updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET)); + break; - case SRTO_PACKETFILTER: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + case SRTO_INPUTBW: + case SRTO_MININPUTBW: + updateCC(TEV_INIT, EventVariant(TEV_INIT_INPUTBW)); + break; - { - string arg((char *)optval, optlen); - // Parse the configuration string prematurely - SrtFilterConfig fc; - if (!ParseFilterConfig(arg, fc)) - { - LOGC(mglog.Error, - log << "SRTO_FILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. " - "FILTERTYPE (" - << fc.type << ") must be installed (or builtin)"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } + case SRTO_OHEADBW: + updateCC(TEV_INIT, EventVariant(TEV_INIT_OHEADBW)); + break; - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; - if (m_zOPT_ExpPayloadSize > efc_max_payload_size) - { - LOGC(mglog.Warn, - log << "Due to filter-required extra " << fc.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " - << efc_max_payload_size << " bytes"); - m_zOPT_ExpPayloadSize = efc_max_payload_size; - } + case SRTO_LOSSMAXTTL: + m_iReorderTolerance = m_config.iMaxReorderTolerance; - m_OPT_PktFilterConfigString = arg; + default: break; } - break; - - default: - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } } -void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) +void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) { - CGuard cg(m_ConnectionLock); + ScopedLock cg(m_ConnectionLock); switch (optName) { case SRTO_MSS: - *(int *)optval = m_iMSS; + *(int *)optval = m_config.iMSS; optlen = sizeof(int); break; case SRTO_SNDSYN: - *(bool *)optval = m_bSynSending; + *(bool *)optval = m_config.bSynSending; optlen = sizeof(bool); break; case SRTO_RCVSYN: - *(bool *)optval = m_bSynRecving; + *(bool *)optval = m_config.bSynRecving; optlen = sizeof(bool); break; @@ -972,17 +461,17 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) break; case SRTO_FC: - *(int *)optval = m_iFlightFlagSize; + *(int *)optval = m_config.iFlightFlagSize; optlen = sizeof(int); break; case SRTO_SNDBUF: - *(int *)optval = m_iSndBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE); + *(int *)optval = m_config.iSndBufSize * (m_config.iMSS - CPacket::UDP_HDR_SIZE); optlen = sizeof(int); break; case SRTO_RCVBUF: - *(int *)optval = m_iRcvBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE); + *(int *)optval = m_config.iRcvBufSize * (m_config.iMSS - CPacket::UDP_HDR_SIZE); optlen = sizeof(int); break; @@ -990,47 +479,68 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) if (optlen < (int)(sizeof(linger))) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - *(linger *)optval = m_Linger; + *(linger *)optval = m_config.Linger; optlen = sizeof(linger); break; case SRTO_UDP_SNDBUF: - *(int *)optval = m_iUDPSndBufSize; + *(int *)optval = m_config.iUDPSndBufSize; optlen = sizeof(int); break; case SRTO_UDP_RCVBUF: - *(int *)optval = m_iUDPRcvBufSize; + *(int *)optval = m_config.iUDPRcvBufSize; optlen = sizeof(int); break; case SRTO_RENDEZVOUS: - *(bool *)optval = m_bRendezvous; + *(bool *)optval = m_config.bRendezvous; optlen = sizeof(bool); break; case SRTO_SNDTIMEO: - *(int *)optval = m_iSndTimeOut; + *(int *)optval = m_config.iSndTimeOut; optlen = sizeof(int); break; case SRTO_RCVTIMEO: - *(int *)optval = m_iRcvTimeOut; + *(int *)optval = m_config.iRcvTimeOut; optlen = sizeof(int); break; case SRTO_REUSEADDR: - *(bool *)optval = m_bReuseAddr; + *(bool *)optval = m_config.bReuseAddr; optlen = sizeof(bool); break; case SRTO_MAXBW: - *(int64_t *)optval = m_llMaxBW; + if (size_t(optlen) < sizeof(m_config.llMaxBW)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + *(int64_t *)optval = m_config.llMaxBW; optlen = sizeof(int64_t); break; + case SRTO_INPUTBW: + if (size_t(optlen) < sizeof(m_config.llInputBW)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + *(int64_t*)optval = m_config.llInputBW; + optlen = sizeof(int64_t); + break; + + case SRTO_MININPUTBW: + if (size_t(optlen) < sizeof (m_config.llMinInputBW)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + *(int64_t*)optval = m_config.llMinInputBW; + optlen = sizeof(int64_t); + break; + + case SRTO_OHEADBW: + *(int32_t *)optval = m_config.iOverheadBW; + optlen = sizeof(int32_t); + break; + case SRTO_STATE: - *(int32_t *)optval = s_UDTUnited.getStatus(m_SocketID); + *(int32_t *)optval = uglobal().getStatus(m_SocketID); optlen = sizeof(int32_t); break; @@ -1038,15 +548,15 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) { int32_t event = 0; if (m_bBroken) - event |= UDT_EPOLL_ERR; + event |= SRT_EPOLL_ERR; else { - CGuard::enterCS(m_RecvLock); - if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady()) - event |= UDT_EPOLL_IN; - CGuard::leaveCS(m_RecvLock); - if (m_pSndBuffer && (m_iSndBufSize > m_pSndBuffer->getCurrBufSize())) - event |= UDT_EPOLL_OUT; + enterCS(m_RecvLock); + if (m_pRcvBuffer && isRcvBufferReady()) + event |= SRT_EPOLL_IN; + leaveCS(m_RecvLock); + if (m_pSndBuffer && (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize())) + event |= SRT_EPOLL_OUT; } *(int32_t *)optval = event; optlen = sizeof(int32_t); @@ -1064,21 +574,20 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) case SRTO_RCVDATA: if (m_pRcvBuffer) { - CGuard::enterCS(m_RecvLock); + enterCS(m_RecvLock); *(int32_t *)optval = m_pRcvBuffer->getRcvDataSize(); - CGuard::leaveCS(m_RecvLock); + leaveCS(m_RecvLock); } else *(int32_t *)optval = 0; optlen = sizeof(int32_t); break; -#ifdef SRT_ENABLE_IPOPTS case SRTO_IPTTL: if (m_bOpened) *(int32_t *)optval = m_pSndQueue->getIpTTL(); else - *(int32_t *)optval = m_iIpTTL; + *(int32_t *)optval = m_config.iIpTTL; optlen = sizeof(int32_t); break; @@ -1086,54 +595,84 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) if (m_bOpened) *(int32_t *)optval = m_pSndQueue->getIpToS(); else - *(int32_t *)optval = m_iIpToS; + *(int32_t *)optval = m_config.iIpToS; optlen = sizeof(int32_t); break; + + case SRTO_BINDTODEVICE: +#ifdef SRT_ENABLE_BINDTODEVICE + if (optlen < IFNAMSIZ) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + if (m_bOpened && m_pSndQueue->getBind(((char*)optval), optlen)) + { + optlen = strlen((char*)optval); + break; + } + + // Fallback: return from internal data + strcpy(((char*)optval), m_config.sBindToDevice.c_str()); + optlen = m_config.sBindToDevice.size(); +#else + LOGC(smlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif + break; case SRTO_SENDER: - *(int32_t *)optval = m_bDataSender; - optlen = sizeof(int32_t); + *(bool *)optval = m_config.bDataSender; + optlen = sizeof(bool); break; case SRTO_TSBPDMODE: - *(int32_t *)optval = m_bOPT_TsbPd; - optlen = sizeof(int32_t); + *(bool *)optval = m_config.bTSBPD; + optlen = sizeof(bool); break; case SRTO_LATENCY: case SRTO_RCVLATENCY: - *(int32_t *)optval = m_iTsbPdDelay_ms; + if (m_bConnected) + *(int32_t *)optval = m_iTsbPdDelay_ms; + else + *(int32_t *)optval = m_config.iRcvLatency; optlen = sizeof(int32_t); break; case SRTO_PEERLATENCY: - *(int32_t *)optval = m_iPeerTsbPdDelay_ms; + if (m_bConnected) + *(int32_t *)optval = m_iPeerTsbPdDelay_ms; + else + *(int32_t *)optval = m_config.iPeerLatency; + optlen = sizeof(int32_t); break; case SRTO_TLPKTDROP: - *(int32_t *)optval = m_bTLPktDrop; - optlen = sizeof(int32_t); + if (m_bConnected) + *(bool *)optval = m_bTLPktDrop; + else + *(bool *)optval = m_config.bTLPktDrop; + + optlen = sizeof(bool); break; case SRTO_SNDDROPDELAY: - *(int32_t *)optval = m_iOPT_SndDropDelay; + *(int32_t *)optval = m_config.iSndDropDelay; optlen = sizeof(int32_t); break; case SRTO_PBKEYLEN: if (m_pCryptoControl) - *(int32_t *)optval = m_pCryptoControl->KeyLen(); // Running Key length. + *(int32_t *)optval = (int32_t) m_pCryptoControl->KeyLen(); // Running Key length. else - *(int32_t *)optval = m_iSndCryptoKeyLen; // May be 0. + *(int32_t *)optval = m_config.iSndCryptoKeyLen; // May be 0. optlen = sizeof(int32_t); break; case SRTO_KMSTATE: if (!m_pCryptoControl) *(int32_t *)optval = SRT_KM_S_UNSECURED; - else if (m_bDataSender) + else if (m_config.bDataSender) *(int32_t *)optval = m_pCryptoControl->m_SndKmState; else *(int32_t *)optval = m_pCryptoControl->m_RcvKmState; @@ -1157,84 +696,119 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) break; case SRTO_LOSSMAXTTL: - *(int32_t*)optval = m_iMaxReorderTolerance; + *(int32_t*)optval = m_config.iMaxReorderTolerance; optlen = sizeof(int32_t); break; case SRTO_NAKREPORT: - *(bool *)optval = m_bRcvNakReport; + *(bool *)optval = m_config.bRcvNakReport; optlen = sizeof(bool); break; case SRTO_VERSION: - *(int32_t *)optval = m_lSrtVersion; + *(int32_t *)optval = m_config.uSrtVersion; optlen = sizeof(int32_t); break; case SRTO_PEERVERSION: - *(int32_t *)optval = m_lPeerSrtVersion; + *(int32_t *)optval = m_uPeerSrtVersion; optlen = sizeof(int32_t); break; -#ifdef SRT_ENABLE_CONNTIMEO case SRTO_CONNTIMEO: - *(int *)optval = m_iConnTimeOut; - optlen = sizeof(int); + *(int*)optval = (int) count_milliseconds(m_config.tdConnTimeOut); + optlen = sizeof(int); + break; + + case SRTO_DRIFTTRACER: + *(bool*)optval = m_config.bDriftTracer; + optlen = sizeof(bool); break; -#endif case SRTO_MINVERSION: - *(uint32_t *)optval = m_lMinimumPeerSrtVersion; + *(uint32_t *)optval = m_config.uMinimumPeerSrtVersion; optlen = sizeof(uint32_t); break; case SRTO_STREAMID: - if (size_t(optlen) < m_sStreamName.size() + 1) + if (size_t(optlen) < m_config.sStreamName.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - strcpy((char *)optval, m_sStreamName.c_str()); - optlen = m_sStreamName.size(); + strcpy((char *)optval, m_config.sStreamName.c_str()); + optlen = (int) m_config.sStreamName.size(); break; case SRTO_CONGESTION: - { - string tt = m_CongCtl.selected_name(); - strcpy((char *)optval, tt.c_str()); - optlen = tt.size(); - } - break; + if (size_t(optlen) < m_config.sCongestion.size() + 1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + strcpy((char *)optval, m_config.sCongestion.c_str()); + optlen = (int) m_config.sCongestion.size(); + break; case SRTO_MESSAGEAPI: optlen = sizeof(bool); - *(bool *)optval = m_bMessageAPI; + *(bool *)optval = m_config.bMessageAPI; break; case SRTO_PAYLOADSIZE: optlen = sizeof(int); - *(int *)optval = m_zOPT_ExpPayloadSize; + *(int *)optval = (int) m_config.zExpPayloadSize; + break; + + case SRTO_KMREFRESHRATE: + optlen = sizeof(int); + *(int*)optval = (int)m_config.uKmRefreshRatePkt; + break; + + case SRTO_KMPREANNOUNCE: + optlen = sizeof(int); + *(int*)optval = (int)m_config.uKmPreAnnouncePkt; break; +#if ENABLE_BONDING + case SRTO_GROUPCONNECT: + optlen = sizeof (int); + *(int*)optval = m_config.iGroupConnect; + break; + + case SRTO_GROUPMINSTABLETIMEO: + optlen = sizeof(int); + *(int*)optval = (int)m_config.uMinStabilityTimeout_ms; + break; + + case SRTO_GROUPTYPE: + optlen = sizeof (int); + *(int*)optval = m_HSGroupType; + break; +#endif + case SRTO_ENFORCEDENCRYPTION: - optlen = sizeof(int32_t); // also with TSBPDMODE and SENDER - *(int32_t *)optval = m_bOPT_StrictEncryption; + optlen = sizeof(bool); + *(bool *)optval = m_config.bEnforcedEnc; break; case SRTO_IPV6ONLY: optlen = sizeof(int); - *(int *)optval = m_iIpV6Only; + *(int *)optval = m_config.iIpV6Only; break; case SRTO_PEERIDLETIMEO: - *(int *)optval = m_iOPT_PeerIdleTimeout; + *(int *)optval = m_config.iPeerIdleTimeout_ms; optlen = sizeof(int); break; case SRTO_PACKETFILTER: - if (size_t(optlen) < m_OPT_PktFilterConfigString.size() + 1) + if (size_t(optlen) < m_config.sPacketFilterConfig.size() + 1) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - strcpy((char *)optval, m_OPT_PktFilterConfigString.c_str()); - optlen = m_OPT_PktFilterConfigString.size(); + strcpy((char *)optval, m_config.sPacketFilterConfig.c_str()); + optlen = (int) m_config.sPacketFilterConfig.size(); + break; + + case SRTO_RETRANSMITALGO: + *(int32_t *)optval = m_config.iRetransmitAlgo; + optlen = sizeof(int32_t); break; default: @@ -1242,41 +816,57 @@ void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) } } -bool CUDT::setstreamid(SRTSOCKET u, const std::string &sid) + +#if ENABLE_BONDING +SRT_ERRNO srt::CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt) +{ + SRT_SOCKOPT this_opt = SRTO_VERSION; + for (size_t i = 0; i < opt.options.size(); ++i) + { + SRT_SocketOptionObject::SingleOption* o = opt.options[i]; + HLOGC(smlog.Debug, log << "applyMemberConfigObject: OPTION @" << m_SocketID << " #" << o->option); + this_opt = SRT_SOCKOPT(o->option); + setOpt(this_opt, o->storage, o->length); + } + return SRT_SUCCESS; +} +#endif + +bool srt::CUDT::setstreamid(SRTSOCKET u, const std::string &sid) { CUDT *that = getUDTHandle(u); if (!that) return false; - if (sid.size() > MAX_SID_LENGTH) + if (sid.size() > CSrtConfig::MAX_SID_LENGTH) return false; if (that->m_bConnected) return false; - that->m_sStreamName = sid; + that->m_config.sStreamName.set(sid); return true; } -std::string CUDT::getstreamid(SRTSOCKET u) +string srt::CUDT::getstreamid(SRTSOCKET u) { CUDT *that = getUDTHandle(u); if (!that) return ""; - return that->m_sStreamName; + return that->m_config.sStreamName.str(); } // XXX REFACTOR: Make common code for CUDT constructor and clearData, // possibly using CUDT::construct. -void CUDT::clearData() +void srt::CUDT::clearData() { // Initial sequence number, loss, acknowledgement, etc. - int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE; + int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; - HLOGC(mglog.Debug, log << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + HLOGC(cnlog.Debug, log << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); m_iEXPCount = 1; m_iBandwidth = 1; // pkts/sec @@ -1284,136 +874,98 @@ void CUDT::clearData() m_iDeliveryRate = 16; m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize; m_iAckSeqNo = 0; - m_ullLastAckTime_tk = 0; + m_tsLastAckTime = steady_clock::now(); // trace information - CGuard::enterCS(m_StatsLock); - m_stats.startTime = CTimer::getTime(); - m_stats.sentTotal = m_stats.recvTotal = m_stats.sndLossTotal = m_stats.rcvLossTotal = m_stats.retransTotal = - m_stats.sentACKTotal = m_stats.recvACKTotal = m_stats.sentNAKTotal = m_stats.recvNAKTotal = 0; - m_stats.lastSampleTime = CTimer::getTime(); - m_stats.traceSent = m_stats.traceRecv = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans = - m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0; - m_stats.traceRcvRetrans = 0; - m_stats.traceReorderDistance = 0; - m_stats.traceBelatedTime = 0.0; - m_stats.traceRcvBelated = 0; - - m_stats.sndDropTotal = 0; - m_stats.traceSndDrop = 0; - m_stats.rcvDropTotal = 0; - m_stats.traceRcvDrop = 0; - - m_stats.m_rcvUndecryptTotal = 0; - m_stats.traceRcvUndecrypt = 0; - - m_stats.bytesSentTotal = 0; - m_stats.bytesRecvTotal = 0; - m_stats.bytesRetransTotal = 0; - m_stats.traceBytesSent = 0; - m_stats.traceBytesRecv = 0; - m_stats.sndFilterExtra = 0; - m_stats.rcvFilterExtra = 0; - m_stats.rcvFilterSupply = 0; - m_stats.rcvFilterLoss = 0; - - m_stats.traceBytesRetrans = 0; -#ifdef SRT_ENABLE_LOSTBYTESCOUNT - m_stats.traceRcvBytesLoss = 0; -#endif - m_stats.sndBytesDropTotal = 0; - m_stats.rcvBytesDropTotal = 0; - m_stats.traceSndBytesDrop = 0; - m_stats.traceRcvBytesDrop = 0; - m_stats.m_rcvBytesUndecryptTotal = 0; - m_stats.traceRcvBytesUndecrypt = 0; + { + ScopedLock stat_lock(m_StatsLock); + + m_stats.tsStartTime = steady_clock::now(); + m_stats.sndr.reset(); + m_stats.rcvr.reset(); - m_stats.sndDuration = m_stats.m_sndDurationTotal = 0; - CGuard::leaveCS(m_StatsLock); + m_stats.tsLastSampleTime = steady_clock::now(); + m_stats.traceReorderDistance = 0; + m_stats.sndDuration = m_stats.m_sndDurationTotal = 0; + } // Resetting these data because this happens when agent isn't connected. m_bPeerTsbPd = false; m_iPeerTsbPdDelay_ms = 0; - m_bTsbPd = m_bOPT_TsbPd; // Take the values from user-configurable options - m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay; - m_bTLPktDrop = m_bOPT_TLPktDrop; + // TSBPD as state should be set to FALSE here. + // Only when the HSREQ handshake is exchanged, + // should they be set to possibly true. + m_bTsbPd = false; + m_bGroupTsbPd = false; + m_iTsbPdDelay_ms = m_config.iRcvLatency; + m_bTLPktDrop = m_config.bTLPktDrop; m_bPeerTLPktDrop = false; m_bPeerNakReport = false; m_bPeerRexmitFlag = false; - m_RdvState = CHandShake::RDV_INVALID; - m_ullRcvPeerStartTime = 0; + m_RdvState = CHandShake::RDV_INVALID; + m_tsRcvPeerStartTime = steady_clock::time_point(); } -void CUDT::open() +void srt::CUDT::open() { - CGuard cg(m_ConnectionLock); + ScopedLock cg(m_ConnectionLock); clearData(); // structures for queue if (m_pSNode == NULL) m_pSNode = new CSNode; - m_pSNode->m_pUDT = this; - m_pSNode->m_llTimeStamp_tk = 1; - m_pSNode->m_iHeapLoc = -1; + m_pSNode->m_pUDT = this; + m_pSNode->m_tsTimeStamp = steady_clock::now(); + m_pSNode->m_iHeapLoc = -1; if (m_pRNode == NULL) m_pRNode = new CRNode; - m_pRNode->m_pUDT = this; - m_pRNode->m_llTimeStamp_tk = 1; + m_pRNode->m_pUDT = this; + m_pRNode->m_tsTimeStamp = steady_clock::now(); m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL; m_pRNode->m_bOnList = false; - m_iRTT = 10 * COMM_SYN_INTERVAL_US; - m_iRTTVar = m_iRTT >> 1; - m_ullCPUFrequency = CTimer::getCPUFrequency(); + // Set initial values of smoothed RTT and RTT variance. + m_iSRTT = INITIAL_RTT; + m_iRTTVar = INITIAL_RTTVAR; + m_bIsFirstRTTReceived = false; // set minimum NAK and EXP timeout to 300ms - /* - XXX This code is blocked because the value of - m_ullMinNakInt_tk will be overwritten again in setupCC. - And in setupCC it will have an opportunity to make the - value overridden according to the statements in the SrtCongestion. - - #ifdef SRT_ENABLE_NAKREPORT - if (m_bRcvNakReport) - m_ullMinNakInt_tk = m_iMinNakInterval_us * m_ullCPUFrequency; - else - #endif - */ - // Set up timers - m_ullMinNakInt_tk = 300000 * m_ullCPUFrequency; - m_ullMinExpInt_tk = 300000 * m_ullCPUFrequency; - - m_ullACKInt_tk = COMM_SYN_INTERVAL_US * m_ullCPUFrequency; - m_ullNAKInt_tk = m_ullMinNakInt_tk; - - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - m_ullLastRspTime_tk = currtime_tk; - m_ullNextACKTime_tk = currtime_tk + m_ullACKInt_tk; - m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk; - m_ullLastRspAckTime_tk = currtime_tk; - m_ullLastSndTime_tk = currtime_tk; - m_iReXmitCount = 1; + m_tdMinNakInterval = milliseconds_from(300); + m_tdMinExpInterval = milliseconds_from(300); + m_tdACKInterval = microseconds_from(COMM_SYN_INTERVAL_US); + m_tdNAKInterval = m_tdMinNakInterval; + + const steady_clock::time_point currtime = steady_clock::now(); + m_tsLastRspTime.store(currtime); + m_tsNextACKTime.store(currtime + m_tdACKInterval); + m_tsNextNAKTime.store(currtime + m_tdNAKInterval); + m_tsLastRspAckTime = currtime; + m_tsLastSndTime.store(currtime); + + m_tsUnstableSince = steady_clock::time_point(); + m_tsFreshActivation = steady_clock::time_point(); + m_tsWarySince = steady_clock::time_point(); + + m_iReXmitCount = 1; m_iPktCount = 0; m_iLightACKCount = 1; - - m_ullTargetTime_tk = 0; - m_ullTimeDiff_tk = 0; + m_tsNextSendTime = steady_clock::time_point(); + m_tdSendTimeDiff = microseconds_from(0); // Now UDT is opened. m_bOpened = true; } -void CUDT::setListenState() +void srt::CUDT::setListenState() { - CGuard cg(m_ConnectionLock); + ScopedLock cg(m_ConnectionLock); if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); @@ -1432,35 +984,35 @@ void CUDT::setListenState() m_bListening = true; } -size_t CUDT::fillSrtHandshake(uint32_t *srtdata, size_t srtlen, int msgtype, int hs_version) +size_t srt::CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version) { - if (srtlen < SRT_HS__SIZE) + if (srtlen < SRT_HS_E_SIZE) { - LOGC(mglog.Fatal, - log << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS__SIZE << ")"); + LOGC(cnlog.Fatal, + log << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS_E_SIZE << ")"); return 0; } - srtlen = SRT_HS__SIZE; // We use only that much space. + srtlen = SRT_HS_E_SIZE; // We use only that much space. - memset(srtdata, 0, sizeof(uint32_t) * srtlen); + memset((aw_srtdata), 0, sizeof(uint32_t) * srtlen); /* Current version (1.x.x) SRT handshake */ - srtdata[SRT_HS_VERSION] = m_lSrtVersion; /* Required version */ - srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities(); + aw_srtdata[SRT_HS_VERSION] = m_config.uSrtVersion; /* Required version */ + aw_srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities(); switch (msgtype) { case SRT_CMD_HSREQ: - return fillSrtHandshake_HSREQ(srtdata, srtlen, hs_version); + return fillSrtHandshake_HSREQ((aw_srtdata), srtlen, hs_version); case SRT_CMD_HSRSP: - return fillSrtHandshake_HSRSP(srtdata, srtlen, hs_version); + return fillSrtHandshake_HSRSP((aw_srtdata), srtlen, hs_version); default: - LOGC(mglog.Fatal, log << "IPE: createSrtHandshake/sendSrtMsg called with value " << msgtype); + LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake/sendSrtMsg called with value " << msgtype); return 0; } } -size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version) +size_t srt::CUDT::fillSrtHandshake_HSREQ(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // INITIATOR sends HSREQ. @@ -1472,126 +1024,123 @@ size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *srtdata, size_t /* srtlen - unused // not set TsbPd mode, it will simply ignore the proposed latency (PeerTsbPdDelay), although // if it has received the Rx latency as well, it must honor it and respond accordingly // (the latter is only in case of HSv5 and bidirectional connection). - if (m_bOPT_TsbPd) + if (m_config.bTSBPD) { - m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay; - m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay; + m_iTsbPdDelay_ms = m_config.iRcvLatency; + m_iPeerTsbPdDelay_ms = m_config.iPeerLatency; /* * Sent data is real-time, use Time-based Packet Delivery, * set option bit and configured delay */ - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; if (hs_version < CUDT::HS_VERSION_SRT1) { // HSv4 - this uses only one value. - srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay_ms); + aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay_ms); } else { // HSv5 - this will be understood only since this version when this exists. - srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); + aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); - m_bTsbPd = true; // And in the reverse direction. - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; - srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; + aw_srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); // This wasn't there for HSv4, this setting is only for the receiver. // HSv5 is bidirectional, so every party is a receiver. if (m_bTLPktDrop) - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; } } - if (m_bRcvNakReport) - srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; + if (m_config.bRcvNakReport) + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; // I support SRT_OPT_REXMITFLG. Do you? - srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; // Declare the API used. The flag is set for "stream" API because // the older versions will never set this flag, but all old SRT versions use message API. - if (!m_bMessageAPI) - srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM; + if (!m_config.bMessageAPI) + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM; - HLOGC(mglog.Debug, - log << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]) - << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS[" - << SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]"); + HLOGC(cnlog.Debug, + log << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(aw_srtdata[SRT_HS_LATENCY]) + << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(aw_srtdata[SRT_HS_LATENCY]) << "] FLAGS[" + << SrtFlagString(aw_srtdata[SRT_HS_FLAGS]) << "]"); return 3; } -size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version) +size_t srt::CUDT::fillSrtHandshake_HSRSP(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { - // Setting m_ullRcvPeerStartTime is done in processSrtMsg_HSREQ(), so + // Setting m_tsRcvPeerStartTime is done in processSrtMsg_HSREQ(), so // this condition will be skipped only if this function is called without // getting first received HSREQ. Doesn't look possible in both HSv4 and HSv5. - if (m_ullRcvPeerStartTime != 0) + if (is_zero(m_tsRcvPeerStartTime)) { - // If Agent doesn't set TSBPD, it will not set the TSBPD flag back to the Peer. - // The peer doesn't have be disturbed by it anyway. - if (m_bTsbPd) - { - /* - * We got and transposed peer start time (HandShake request timestamp), - * we can support Timestamp-based Packet Delivery - */ - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; + LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake_HSRSP: m_tsRcvPeerStartTime NOT SET!"); + return 0; + } - if (hs_version < HS_VERSION_SRT1) - { - // HSv4 - this uses only one value - srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay_ms); - } - else - { - // HSv5 - this puts "agent's" latency into RCV field and "peer's" - - // into SND field. - srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); - } - } - else - { - HLOGC(mglog.Debug, log << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag."); - } + // If Agent doesn't set TSBPD, it will not set the TSBPD flag back to the Peer. + // The peer doesn't have be disturbed by it anyway. + if (isOPT_TsbPd()) + { + /* + * We got and transposed peer start time (HandShake request timestamp), + * we can support Timestamp-based Packet Delivery + */ + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; - // Hsv5, only when peer has declared TSBPD mode. - // The flag was already set, and the value already "maximized" in processSrtMsg_HSREQ(). - if (m_bPeerTsbPd && hs_version >= HS_VERSION_SRT1) + if (hs_version < HS_VERSION_SRT1) { - // HSv5 is bidirectional - so send the TSBPDSND flag, and place also the - // peer's latency into SND field. - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; - srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); - - HLOGC(mglog.Debug, - log << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms); + // HSv4 - this uses only one value + aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay_ms); } else { - HLOGC(mglog.Debug, - log << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5) - << " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND"); + // HSv5 - this puts "agent's" latency into RCV field and "peer's" - + // into SND field. + aw_srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms); } + } + else + { + HLOGC(cnlog.Debug, log << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag."); + } - if (m_bTLPktDrop) - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; + // Hsv5, only when peer has declared TSBPD mode. + // The flag was already set, and the value already "maximized" in processSrtMsg_HSREQ(). + if (m_bPeerTsbPd && hs_version >= HS_VERSION_SRT1) + { + // HSv5 is bidirectional - so send the TSBPDSND flag, and place also the + // peer's latency into SND field. + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; + aw_srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms); + + HLOGC(cnlog.Debug, + log << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms); } else { - LOGC(mglog.Fatal, log << "IPE: fillSrtHandshake_HSRSP: m_ullRcvPeerStartTime NOT SET!"); - return 0; + HLOGC(cnlog.Debug, + log << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5) + << " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND"); } - if (m_bRcvNakReport) + if (m_bTLPktDrop) + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; + + if (m_config.bRcvNakReport) { // HSv5: Note that this setting is independent on the value of // m_bPeerNakReport, which represent this setting in the peer. - srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT; /* * NAK Report is so efficient at controlling bandwidth that sender TLPktDrop * is not needed. SRT 1.0.5 to 1.0.7 sender TLPktDrop combined with SRT 1.0 @@ -1600,44 +1149,44 @@ size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *srtdata, size_t /* srtlen - unused * Disabling TLPktDrop in the receiver SRT Handshake Reply prevents the sender * from enabling Too-Late Packet Drop. */ - if (m_lPeerSrtVersion <= SrtVersion(1, 0, 7)) - srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP; + if (m_uPeerSrtVersion <= SrtVersion(1, 0, 7)) + aw_srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP; } - if (m_lSrtVersion >= SrtVersion(1, 2, 0)) + if (m_config.uSrtVersion >= SrtVersion(1, 2, 0)) { if (!m_bPeerRexmitFlag) { // Peer does not request to use rexmit flag, if so, // we won't use as well. - HLOGC(mglog.Debug, log << "HSRSP/snd: AGENT understands REXMIT flag, but PEER DOES NOT. NOT setting."); + HLOGC(cnlog.Debug, log << "HSRSP/snd: AGENT understands REXMIT flag, but PEER DOES NOT. NOT setting."); } else { // Request that the rexmit bit be used as a part of msgno. - srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; - HLOGF(mglog.Debug, "HSRSP/snd: AGENT UNDERSTANDS REXMIT flag and PEER reported that it does, too."); + aw_srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; + HLOGF(cnlog.Debug, "HSRSP/snd: AGENT UNDERSTANDS REXMIT flag and PEER reported that it does, too."); } } else { // Since this is now in the code, it can occur only in case when you change the // version specification in the build configuration. - HLOGF(mglog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag"); + HLOGF(cnlog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag"); } - HLOGC(mglog.Debug, - log << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]) - << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS[" - << SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]"); + HLOGC(cnlog.Debug, + log << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(aw_srtdata[SRT_HS_LATENCY]) + << " RCV:" << SRT_HS_LATENCY_RCV::unwrap(aw_srtdata[SRT_HS_LATENCY]) << "] FLAGS[" + << SrtFlagString(aw_srtdata[SRT_HS_FLAGS]) << "]"); return 3; } -size_t CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) +size_t srt::CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) { size_t srtlen = fillSrtHandshake(srtdata, size, cmd, handshakeVersion()); - HLOGF(mglog.Debug, + HLOGF(cnlog.Debug, "CMD:%s(%d) Len:%d Version: %s Flags: %08X (%s) sdelay:%d", MessageTypeStr(UMSG_EXT, cmd).c_str(), cmd, @@ -1650,23 +1199,16 @@ size_t CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) return srtlen; } -void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in) +void srt::CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, size_t srtlen_in) { CPacket srtpkt; int32_t srtcmd = (int32_t)cmd; - static const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ / sizeof(int32_t); + SRT_STATIC_ASSERT(SRTDATA_MAXSIZE >= SRT_HS_E_SIZE, "SRT_CMD_MAXSZ is too small to hold all the data"); + // This will be effectively larger than SRT_HS_E_SIZE, but it will be also used for incoming data. + uint32_t srtdata[SRTDATA_MAXSIZE]; - // This is in order to issue a compile error if the SRT_CMD_MAXSZ is - // too small to keep all the data. As this is "static const", declaring - // an array of such specified size in C++ isn't considered VLA. - static const int SRTDATA_SIZE = SRTDATA_MAXSIZE >= SRT_HS__SIZE ? SRTDATA_MAXSIZE : -1; - - // This will be effectively larger than SRT_HS__SIZE, but it will be also used - // for incoming data. We have a guarantee that it won't be larger than SRTDATA_MAXSIZE. - uint32_t srtdata[SRTDATA_SIZE]; - - int srtlen = 0; + size_t srtlen = 0; if (cmd == SRT_CMD_REJECT) { @@ -1680,7 +1222,7 @@ void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in) { case SRT_CMD_HSREQ: case SRT_CMD_HSRSP: - srtlen = prepareSrtHsMsg(cmd, srtdata, SRTDATA_SIZE); + srtlen = prepareSrtHsMsg(cmd, srtdata, SRTDATA_MAXSIZE); break; case SRT_CMD_KMREQ: // Sender @@ -1696,7 +1238,7 @@ void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in) break; default: - LOGF(mglog.Error, "sndSrtMsg: cmd=%d unsupported", cmd); + LOGF(cnlog.Error, "sndSrtMsg: IPE: cmd=%d unsupported", cmd); break; } @@ -1708,61 +1250,193 @@ void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in) } } +size_t srt::CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const string& str) +{ + uint32_t* space = pcmdspec + 1; + size_t wordsize = (str.size() + 3) / 4; + size_t aligned_bytesize = wordsize * 4; + + memset((space), 0, aligned_bytesize); + memcpy((space), str.data(), str.size()); + // Preswap to little endian (in place due to possible padding zeros) + HtoILA((space), space, wordsize); + + *pcmdspec = HS_CMDSPEC_CMD::wrap(cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) wordsize); + + return wordsize; +} + +#if ENABLE_BONDING +// [[using locked(m_parent->m_ControlLock)]] +// [[using locked(s_UDTUnited.m_GlobControlLock)]] +size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) +{ + SRT_ASSERT(m_parent->m_GroupOf != NULL); + uint32_t* space = pcmdspec + 1; + + SRTSOCKET id = m_parent->m_GroupOf->id(); + SRT_GROUP_TYPE tp = m_parent->m_GroupOf->type(); + uint32_t flags = 0; + + // Note: if agent is a listener, and the current version supports + // both sync methods, this flag might have been changed according to + // the wish of the caller. + if (m_parent->m_GroupOf->synconmsgno()) + flags |= SRT_GFLAG_SYNCONMSG; + + // NOTE: this code remains as is for historical reasons. + // The initial implementation stated that the peer id be + // extracted so that it can be reported and possibly the + // start time somehow encoded and written into the group + // extension, but it was later seen not necessary. Therefore + // this code remains, but now it's informational only. +#if ENABLE_HEAVY_LOGGING + m_parent->m_GroupOf->debugMasterData(m_SocketID); +#endif + + // See CUDT::interpretGroup() + + uint32_t dataword = 0 + | SrtHSRequest::HS_GROUP_TYPE::wrap(tp) + | SrtHSRequest::HS_GROUP_FLAGS::wrap(flags) + | SrtHSRequest::HS_GROUP_WEIGHT::wrap(m_parent->m_GroupMemberData->weight); + + const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(id), dataword }; + memcpy((space), storedata, sizeof storedata); + + const size_t ra_size = Size(storedata); + *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_GROUP) | HS_CMDSPEC_SIZE::wrap(ra_size); + + return ra_size; +} +#endif + +size_t srt::CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) +{ + uint32_t* space = pcmdspec + 1; + + size_t msglen = m_pCryptoControl->getKmMsg_size(ki); + // Make ra_size back in element unit + // Add one extra word if the size isn't aligned to 32-bit. + size_t ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0); + + // Store the CMD + SIZE in the next field + *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMREQ) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); + + // Copy the key - do the endian inversion because another endian inversion + // will be done for every control message before sending, and this KM message + // is ALREADY in network order. + const uint32_t* keydata = reinterpret_cast(m_pCryptoControl->getKmMsg_data(ki)); + + HLOGC(cnlog.Debug, + log << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size + << " words (KmMsg_size=" << msglen << ")"); + // XXX INSECURE ": [" << FormatBinaryString((uint8_t*)keydata, msglen) << "]"; + + // Yes, I know HtoNLA and NtoHLA do exactly the same operation, but I want + // to be clear about the true intention. + NtoHLA((space), keydata, ra_size); + + return ra_size; +} + +size_t srt::CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, size_t kmdata_wordsize) +{ + uint32_t* space = pcmdspec + 1; + const uint32_t failure_kmrsp[] = {SRT_KM_S_UNSECURED}; + const uint32_t* keydata = 0; + + // Shift the starting point with the value of previously added block, + // to start with the new one. + + size_t ra_size; + + if (kmdata_wordsize == 0) + { + LOGC(cnlog.Warn, log << "createSrtHandshake: Agent has PW, but Peer sent no KMREQ. Sending error KMRSP response"); + ra_size = 1; + keydata = failure_kmrsp; + + // Update the KM state as well + m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Agent has PW, but Peer won't decrypt + m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Peer won't encrypt as well. + } + else + { + if (!kmdata) + { + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Fatal, log << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!"); + return 0; + } + ra_size = kmdata_wordsize; + keydata = reinterpret_cast(kmdata); + } + + *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMRSP) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); + HLOGC(cnlog.Debug, + log << "createSrtHandshake: KMRSP: applying returned key length=" + << ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata, + // kmdata_wordsize*sizeof(uint32_t)) << "]"; + + NtoHLA((space), keydata, ra_size); + return ra_size; +} + + // PREREQUISITE: // pkt must be set the buffer and configured for UMSG_HANDSHAKE. // Note that this function replaces also serialization for the HSv4. -bool CUDT::createSrtHandshake(ref_t r_pkt, - ref_t r_hs, - int srths_cmd, - int srtkm_cmd, - const uint32_t * kmdata, - size_t kmdata_wordsize /* IN WORDS, NOT BYTES!!! */) +bool srt::CUDT::createSrtHandshake( + int srths_cmd, + int srtkm_cmd, + const uint32_t* kmdata, + size_t kmdata_wordsize, // IN WORDS, NOT BYTES!!! + CPacket& w_pkt, + CHandShake& w_hs) { - CPacket & pkt = *r_pkt; - CHandShake &hs = *r_hs; - // This function might be called before the opposite version was recognized. // Check if the version is exactly 4 because this means that the peer has already // sent something - asynchronously, and usually in rendezvous - and we already know // that the peer is version 4. In this case, agent must behave as HSv4, til the end. if (m_ConnRes.m_iVersion == HS_VERSION_UDT4) { - hs.m_iVersion = HS_VERSION_UDT4; - hs.m_iType = UDT_DGRAM; - if (hs.m_extension) + w_hs.m_iVersion = HS_VERSION_UDT4; + w_hs.m_iType = UDT_DGRAM; + if (w_hs.m_extension) { // Should be impossible - LOGC(mglog.Error, log << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing..."); - hs.m_extension = false; + LOGC(cnlog.Error, log << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing..."); + w_hs.m_extension = false; } } else { - hs.m_iType = 0; // Prepare it for flags + w_hs.m_iType = 0; // Prepare it for flags } - HLOGC(mglog.Debug, - log << "createSrtHandshake: buf size=" << pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd) + HLOGC(cnlog.Debug, + log << "createSrtHandshake: buf size=" << w_pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd) << " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize - << " version=" << hs.m_iVersion); + << " version=" << w_hs.m_iVersion); // Once you are certain that the version is HSv5, set the enc type flags // to advertise pbkeylen. Otherwise make sure that the old interpretation // will correctly pick up the type field. PBKEYLEN should be advertized // regardless of what URQ stage the handshake is (note that in case of rendezvous // CONCLUSION might be the FIRST MESSAGE EVER RECEIVED by a party). - if (hs.m_iVersion > HS_VERSION_UDT4) + if (w_hs.m_iVersion > HS_VERSION_UDT4) { // Check if there was a failure to receie HSREQ before trying to craft HSRSP. - // If fillSrtHandshake_HSRSP catches the condition of m_ullRcvPeerStartTime == 0, + // If fillSrtHandshake_HSRSP catches the condition of m_tsRcvPeerStartTime == steady_clock::zero(), // it will return size 0, which will mess up with further extension procedures; // PREVENT THIS HERE. - if (hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && m_ullRcvPeerStartTime == 0) + if (w_hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && is_zero(m_tsRcvPeerStartTime)) { - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. " "BLOCKING extensions."); - hs.m_extension = false; + w_hs.m_extension = false; } // The situation when this function is called without requested extensions @@ -1772,11 +1446,12 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, // // Keep 0 in the SRT_HSTYPE_HSFLAGS field, but still advertise PBKEYLEN // in the SRT_HSTYPE_ENCFLAGS field. - hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_iSndCryptoKeyLen); - bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0; - HLOGC(mglog.Debug, + w_hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_config.iSndCryptoKeyLen); + + IF_HEAVY_LOGGING(bool whether = m_config.iSndCryptoKeyLen != 0); + HLOGC(cnlog.Debug, log << "createSrtHandshake: " << (whether ? "" : "NOT ") - << " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen); + << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); // Note: This is required only when sending a HS message without SRT extensions. // When this is to be sent with SRT extensions, then KMREQ will be attached here @@ -1786,20 +1461,20 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, } else { - hs.m_iType = UDT_DGRAM; + w_hs.m_iType = UDT_DGRAM; } // values > URQ_CONCLUSION include also error types - // if (hs.m_iVersion == HS_VERSION_UDT4 || hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and + // if (w_hs.m_iVersion == HS_VERSION_UDT4 || w_hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and // it's only valid for caller-listener mode - if (!hs.m_extension) + if (!w_hs.m_extension) { // Serialize only the basic handshake, if this is predicted for // Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND. - size_t hs_size = pkt.getLength(); - hs.store_to(pkt.m_pcData, Ref(hs_size)); - pkt.setLength(hs_size); - HLOGC(mglog.Debug, log << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << hs.show()); + size_t hs_size = w_pkt.getLength(); + w_hs.store_to((w_pkt.m_pcData), (hs_size)); + w_pkt.setLength(hs_size); + HLOGC(cnlog.Debug, log << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << w_hs.show()); return true; } @@ -1807,28 +1482,22 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, if (srths_cmd == SRT_CMD_HSREQ && m_SrtHsSide == HSD_RESPONDER) { m_RejectReason = SRT_REJ_IPE; - LOGC(mglog.Fatal, log << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!"); + LOGC(cnlog.Fatal, log << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!"); return false; // should cause rejection } - string logext = "HSX"; - - bool have_kmreq = false; - bool have_sid = false; - bool have_congctl = false; - bool have_filter = false; + ostringstream logext; + logext << "HSX"; // Install the SRT extensions - hs.m_iType |= CHandShake::HS_EXT_HSREQ; + w_hs.m_iType |= CHandShake::HS_EXT_HSREQ; - if (srths_cmd == SRT_CMD_HSREQ) + bool have_sid = false; + if (srths_cmd == SRT_CMD_HSREQ && !m_config.sStreamName.empty()) { - if (m_sStreamName != "") - { - have_sid = true; - hs.m_iType |= CHandShake::HS_EXT_CONFIG; - logext += ",SID"; - } + have_sid = true; + w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; + logext << ",SID"; } // If this is a response, we have also information @@ -1841,7 +1510,7 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, { peer_filter_capable = true; } - else if (IsSet(m_lPeerSrtFlags, SRT_OPT_FILTERCAP)) + else if (IsSet(m_uPeerSrtFlags, SRT_OPT_FILTERCAP)) { peer_filter_capable = true; } @@ -1859,35 +1528,68 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, // the filter config string from the peer and therefore // possibly confronted with the contents of m_OPT_FECConfigString, // and if it decided to go with filter, it will be nonempty. - if (peer_filter_capable && m_OPT_PktFilterConfigString != "") + bool have_filter = false; + if (peer_filter_capable && !m_config.sPacketFilterConfig.empty()) { have_filter = true; - hs.m_iType |= CHandShake::HS_EXT_CONFIG; - logext += ",filter"; + w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; + logext << ",filter"; } - string sm = m_CongCtl.selected_name(); + bool have_congctl = false; + const string sm = m_config.sCongestion.str(); if (sm != "" && sm != "live") { have_congctl = true; - hs.m_iType |= CHandShake::HS_EXT_CONFIG; - logext += ",CONGCTL"; + w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; + logext << ",CONGCTL"; } + bool have_kmreq = false; // Prevent adding KMRSP only in case when BOTH: // - Agent has set no password // - no KMREQ has arrived from Peer // KMRSP must be always sent when: // - Agent set a password, Peer did not send KMREQ: Agent sets snd=NOSECRET. // - Agent set no password, but Peer sent KMREQ: Ageng sets rcv=NOSECRET. - if (m_CryptoSecret.len > 0 || kmdata_wordsize > 0) + if (m_config.CryptoSecret.len > 0 || kmdata_wordsize > 0) { have_kmreq = true; - hs.m_iType |= CHandShake::HS_EXT_KMREQ; - logext += ",KMX"; + w_hs.m_iType |= CHandShake::HS_EXT_KMREQ; + logext << ",KMX"; + } + +#if ENABLE_BONDING + bool have_group = false; + + // Note: this is done without locking because we have the following possibilities: + // + // 1. Most positive: the group will be the same all the time up to the moment when we use it. + // 2. The group will disappear when next time we try to use it having now have_group set true. + // + // Not possible that a group is NULL now but would appear later: the group must be either empty + // or already set as valid at this time. + // + // If the 2nd possibility happens, then simply it means that the group has been closed during + // the operation and the socket got this information updated in the meantime. This means that + // it was an abnormal interrupt during the processing so the handshake process should be aborted + // anyway, and that's what will be done. + + // LOCKING INFORMATION: accesing this field just for NULL check doesn't + // hurt, even if this field could be dangling in the moment. This will be + // followed by an additional check, done this time under lock, and there will + // be no dangling pointers at this time. + if (m_parent->m_GroupOf) + { + // Whatever group this socket belongs to, the information about + // the group is always sent the same way with the handshake. + have_group = true; + w_hs.m_iType |= CHandShake::HS_EXT_CONFIG; + logext << ",GROUP"; } +#endif - HLOGC(mglog.Debug, log << "createSrtHandshake: (ext: " << logext << ") data: " << hs.show()); + HLOGC(cnlog.Debug, log << "createSrtHandshake: (ext: " << logext.str() << ") data: " << w_hs.show()); // NOTE: The HSREQ is practically always required, although may happen // in future that CONCLUSION can be sent multiple times for a separate @@ -1895,14 +1597,14 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, // Also, KMREQ may occur multiple times. // So, initially store the UDT legacy handshake. - size_t hs_size = pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data - hs.store_to(pkt.m_pcData, Ref(hs_size)); // hs_size is updated + size_t hs_size = w_pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data + w_hs.store_to((w_pkt.m_pcData), (hs_size)); // hs_size is updated size_t ra_size = hs_size / sizeof(int32_t); // Now attach the SRT handshake for HSREQ size_t offset = ra_size; - uint32_t *p = reinterpret_cast(pkt.m_pcData); + uint32_t *p = reinterpret_cast(w_pkt.m_pcData); // NOTE: since this point, ra_size has a size in int32_t elements, NOT BYTES. // The first 4-byte item is the CMD/LENGTH spec. @@ -1913,48 +1615,36 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, // ra_size after that // NOTE: so far, ra_size is m_iMaxSRTPayloadSize expressed in number of elements. // WILL BE CHANGED HERE. - ra_size = fillSrtHandshake(p + offset, total_ra_size - offset, srths_cmd, HS_VERSION_SRT1); - *pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size); + ra_size = fillSrtHandshake((p + offset), total_ra_size - offset, srths_cmd, HS_VERSION_SRT1); + *pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size << " space left: " << (total_ra_size - offset)); + // Use only in REQ phase and only if stream name is set if (have_sid) { - // Use only in REQ phase and only if stream name is set - offset += ra_size; - pcmdspec = p + offset; - ++offset; - // Now prepare the string with 4-byte alignment. The string size is limited // to half the payload size. Just a sanity check to not pack too much into // the conclusion packet. size_t size_limit = m_iMaxSRTPayloadSize / 2; - if (m_sStreamName.size() >= size_limit) + if (m_config.sStreamName.size() >= size_limit) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, + LOGC(cnlog.Warn, log << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes"); return false; } - size_t wordsize = (m_sStreamName.size() + 3) / 4; - size_t aligned_bytesize = wordsize * 4; - - memset(p + offset, 0, aligned_bytesize); - memcpy(p + offset, m_sStreamName.data(), m_sStreamName.size()); - // Preswap to little endian (in place due to possible padding zeros) - HtoILA((uint32_t *)(p + offset), (uint32_t *)(p + offset), wordsize); - - ra_size = wordsize; - *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_SID) | HS_CMDSPEC_SIZE::wrap(ra_size); + offset += ra_size + 1; + ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_SID, m_config.sStreamName.str()); - HLOGC(mglog.Debug, - log << "createSrtHandshake: after SID [" << m_sStreamName << "] length=" << m_sStreamName.size() - << " alignedln=" << aligned_bytesize << ": offset=" << offset << " SID size=" << ra_size - << " space left: " << (total_ra_size - offset)); + HLOGC(cnlog.Debug, + log << "createSrtHandshake: after SID [" << m_config.sStreamName.c_str() + << "] length=" << m_config.sStreamName.size() << " alignedln=" << (4 * ra_size) + << ": offset=" << offset << " SID size=" << ra_size << " space left: " << (total_ra_size - offset)); } if (have_congctl) @@ -1963,60 +1653,83 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, // The other side should reject connection if it uses a different congctl. // The other side should also respond with the congctl it uses, if its non-default (for backward compatibility). - // XXX Consider change the congctl settings in the listener socket to "adaptive" - // congctl and also "adaptive" value of CUDT::m_bMessageAPI so that the caller - // may ask for whatever kind of transmission it wants, or select transmission - // type differently for different connections, however with the same listener. - - offset += ra_size; - pcmdspec = p + offset; - ++offset; - - size_t wordsize = (sm.size() + 3) / 4; - size_t aligned_bytesize = wordsize * 4; - - memset(p + offset, 0, aligned_bytesize); + offset += ra_size + 1; + ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_CONGESTION, sm); - memcpy(p + offset, sm.data(), sm.size()); - // Preswap to little endian (in place due to possible padding zeros) - HtoILA((uint32_t *)(p + offset), (uint32_t *)(p + offset), wordsize); - - ra_size = wordsize; - *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_CONGESTION) | HS_CMDSPEC_SIZE::wrap(ra_size); - - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size() - << " alignedln=" << aligned_bytesize << ": offset=" << offset << " CONGCTL size=" << ra_size + << " alignedln=" << (4 * ra_size) << ": offset=" << offset << " CONGCTL size=" << ra_size << " space left: " << (total_ra_size - offset)); } if (have_filter) { - offset += ra_size; - pcmdspec = p + offset; - ++offset; + offset += ra_size + 1; + ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_FILTER, m_config.sPacketFilterConfig.str()); - size_t wordsize = (m_OPT_PktFilterConfigString.size() + 3) / 4; - size_t aligned_bytesize = wordsize * 4; + HLOGC(cnlog.Debug, + log << "createSrtHandshake: after filter [" << m_config.sPacketFilterConfig.c_str() << "] length=" + << m_config.sPacketFilterConfig.size() << " alignedln=" << (4 * ra_size) << ": offset=" << offset + << " filter size=" << ra_size << " space left: " << (total_ra_size - offset)); + } - memset(p + offset, 0, aligned_bytesize); - memcpy(p + offset, m_OPT_PktFilterConfigString.data(), m_OPT_PktFilterConfigString.size()); +#if ENABLE_BONDING + // Note that this will fire in both cases: + // - When the group has been set by the user on a socket (or socket was created as a part of the group), + // and the handshake request is to be sent with informing the peer that this conenction belongs to a group + // - When the agent received a HS request with a group, has created its mirror group on its side, and + // now sends the HS response to the peer, with ITS OWN group id (the mirror one). + // + // XXX Probably a condition should be checked here around the group type. + // The time synchronization should be done only on any kind of parallel sending group. + // Currently all groups are such groups (broadcast, backup, balancing), but it may + // need to be changed for some other types. + if (have_group) + { + // NOTE: See information about mutex ordering in api.h + ScopedLock gdrg (uglobal().m_GlobControlLock); + if (!m_parent->m_GroupOf) + { + // This may only happen if since last check of m_GroupOf pointer the socket was removed + // from the group in the meantime, which can only happen due to that the group was closed. + // In such a case it simply means that the handshake process was requested to be interrupted. + LOGC(cnlog.Fatal, log << "GROUP DISAPPEARED. Socket not capable of continuing HS"); + return false; + } + else + { + if (m_parent->m_GroupOf->closing()) + { + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Error, log << "createSrtHandshake: group is closing during the process, rejecting."); + return false; - ra_size = wordsize; - *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_FILTER) | HS_CMDSPEC_SIZE::wrap(ra_size); + } + offset += ra_size + 1; + ra_size = fillHsExtGroup(p + offset - 1); - HLOGC(mglog.Debug, - log << "createSrtHandshake: after filter [" << m_OPT_PktFilterConfigString << "] length=" - << m_OPT_PktFilterConfigString.size() << " alignedln=" << aligned_bytesize << ": offset=" << offset - << " filter size=" << ra_size << " space left: " << (total_ra_size - offset)); + HLOGC(cnlog.Debug, log << "createSrtHandshake: after GROUP [" << sm << "] length=" << sm.size() + << ": offset=" << offset << " GROUP size=" << ra_size << " space left: " << (total_ra_size - offset)); + } } +#endif // When encryption turned on if (have_kmreq) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "createSrtHandshake: " - << (m_CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION")); + << (m_config.CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION")); + + if (!m_pCryptoControl && (srtkm_cmd == SRT_CMD_KMREQ || srtkm_cmd == SRT_CMD_KMRSP)) + { + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Error, log << "createSrtHandshake: IPE: need to send KM, but CryptoControl does not exist." + << " Socket state: connected=" << boolalpha << m_bConnected << ", connecting=" << m_bConnecting + << ", broken=" << m_bBroken << ", closing=" << m_bClosing << "."); + return false; + } + if (srtkm_cmd == SRT_CMD_KMREQ) { bool have_any_keys = false; @@ -2028,123 +1741,71 @@ bool CUDT::createSrtHandshake(ref_t r_pkt, m_pCryptoControl->getKmMsg_markSent(ki, false); - offset += ra_size; - - size_t msglen = m_pCryptoControl->getKmMsg_size(ki); - // Make ra_size back in element unit - // Add one extra word if the size isn't aligned to 32-bit. - ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0); - - // Store the CMD + SIZE in the next field - *(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size); - ++offset; + offset += ra_size + 1; + ra_size = fillHsExtKMREQ(p + offset - 1, ki); - // Copy the key - do the endian inversion because another endian inversion - // will be done for every control message before sending, and this KM message - // is ALREADY in network order. - const uint32_t *keydata = reinterpret_cast(m_pCryptoControl->getKmMsg_data(ki)); - - HLOGC(mglog.Debug, - log << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size - << " words (KmMsg_size=" << msglen << ")"); - // XXX INSECURE ": [" << FormatBinaryString((uint8_t*)keydata, msglen) << "]"; - - // Yes, I know HtoNLA and NtoHLA do exactly the same operation, but I want - // to be clear about the true intention. - NtoHLA(p + offset, keydata, ra_size); have_any_keys = true; } if (!have_any_keys) { m_RejectReason = SRT_REJ_IPE; - LOGC(mglog.Error, log << "createSrtHandshake: IPE: all keys have expired, no KM to send."); + LOGC(cnlog.Error, log << "createSrtHandshake: IPE: all keys have expired, no KM to send."); return false; } } else if (srtkm_cmd == SRT_CMD_KMRSP) { - uint32_t failure_kmrsp[] = {SRT_KM_S_UNSECURED}; - const uint32_t *keydata = 0; - - // Shift the starting point with the value of previously added block, - // to start with the new one. - offset += ra_size; - - if (kmdata_wordsize == 0) - { - LOGC(mglog.Error, - log << "createSrtHandshake: Agent has PW, but Peer sent no KMREQ. Sending error KMRSP response"); - ra_size = 1; - keydata = failure_kmrsp; - - // Update the KM state as well - m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Agent has PW, but Peer won't decrypt - m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Peer won't encrypt as well. - } - else - { - if (!kmdata) - { - m_RejectReason = SRT_REJ_IPE; - LOGC(mglog.Fatal, log << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!"); - return false; - } - ra_size = kmdata_wordsize; - keydata = reinterpret_cast(kmdata); - } - - *(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size); - ++offset; // Once cell, containting CMD spec and size - HLOGC(mglog.Debug, - log << "createSrtHandshake: KMRSP: applying returned key length=" - << ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata, - // kmdata_wordsize*sizeof(uint32_t)) << "]"; - - NtoHLA(p + offset, keydata, ra_size); + offset += ra_size + 1; + ra_size = fillHsExtKMRSP(p + offset - 1, kmdata, kmdata_wordsize); } else { m_RejectReason = SRT_REJ_IPE; - LOGC(mglog.Fatal, log << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd); + LOGC(cnlog.Fatal, log << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd); return false; } } + if (ra_size == 0) + { + // m_RejectReason is expected to be set by fillHsExtKMRSP(..) in this case. + return false; + } + // ra_size + offset has a value in element unit. // Switch it again to byte unit. - pkt.setLength((ra_size + offset) * sizeof(int32_t)); + w_pkt.setLength((ra_size + offset) * sizeof(int32_t)); - HLOGC(mglog.Debug, - log << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(hs.m_iType) - << " length: " << pkt.getLength() << " bytes"); + HLOGC(cnlog.Debug, + log << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(w_hs.m_iType) + << " length: " << w_pkt.getLength() << " bytes"); return true; } -static int -FindExtensionBlock(uint32_t *begin, size_t total_length, ref_t r_out_len, ref_t r_next_block) +template +static inline int FindExtensionBlock(Integer* begin, size_t total_length, + size_t& w_out_len, Integer*& w_next_block) { // Check if there's anything to process if (total_length == 0) { - *r_next_block = NULL; - *r_out_len = 0; + w_next_block = NULL; + w_out_len = 0; return SRT_CMD_NONE; } - size_t & out_len = *r_out_len; - uint32_t *&next_block = *r_next_block; // This function extracts the block command from the block and its length. // The command value is returned as a function result. - // The size of that command block is stored into out_len. - // The beginning of the prospective next block is stored in next_block. + // The size of that command block is stored into w_out_len. + // The beginning of the prospective next block is stored in w_next_block. // The caller must be aware that: // - exactly one element holds the block header (cmd+size), so the actual data are after this one. // - the returned size is the number of uint32_t elements since that first data element - // - the remaining size should be manually calculated as total_length - 1 - out_len, or - // simply, as next_block - begin. + // - the remaining size should be manually calculated as total_length - 1 - w_out_len, or + // simply, as w_next_block - begin. // Note that if the total_length is too short to extract the whole block, it will return // SRT_CMD_NONE. Note that total_length includes this first CMDSPEC word. @@ -2158,27 +1819,130 @@ FindExtensionBlock(uint32_t *begin, size_t total_length, ref_t r_out_len if (size + 1 > total_length) return SRT_CMD_NONE; - out_len = size; + w_out_len = size; if (total_length == size + 1) - next_block = NULL; + w_next_block = NULL; else - next_block = begin + 1 + size; + w_next_block = begin + 1 + size; return cmd; } -static inline bool NextExtensionBlock(ref_t begin, uint32_t *next, ref_t length) +// NOTE: the rule of order of arguments is broken here because this order +// serves better the logics and readability. +template +static inline bool NextExtensionBlock(Integer*& w_begin, Integer* next, size_t& w_length) { if (!next) return false; - *length = *length - (next - *begin); - *begin = next; + w_length = w_length - (next - w_begin); + w_begin = next; return true; } -bool CUDT::processSrtMsg(const CPacket *ctrlpkt) +void SrtExtractHandshakeExtensions(const char* bufbegin, size_t buflength, + vector& w_output) +{ + const uint32_t *begin = reinterpret_cast(bufbegin + CHandShake::m_iContentSize); + size_t size = buflength - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 + const uint32_t *next = 0; + size_t length = size / sizeof(uint32_t); + size_t blocklen = 0; + + for (;;) // ONE SHOT, but continuable loop + { + const int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); + + if (cmd == SRT_CMD_NONE) + { + // End of blocks + break; + } + + w_output.push_back(SrtHandshakeExtension(cmd)); + + SrtHandshakeExtension& ext = w_output.back(); + + std::copy(begin+1, begin+blocklen+1, back_inserter(ext.contents)); + + // Any other kind of message extracted. Search on. + if (!NextExtensionBlock((begin), next, (length))) + break; + } +} + +#if SRT_DEBUG_RTT +class RttTracer +{ +public: + RttTracer() + { + } + + ~RttTracer() + { + srt::sync::ScopedLock lck(m_mtx); + m_fout.close(); + } + + void trace(const srt::sync::steady_clock::time_point& currtime, + const std::string& event, int rtt_sample, int rttvar_sample, + bool is_smoothed_rtt_reset, int64_t recvTotal, + int smoothed_rtt, int rttvar) + { + srt::sync::ScopedLock lck(m_mtx); + create_file(); + + m_fout << srt::sync::FormatTimeSys(currtime) << ","; + m_fout << srt::sync::FormatTime(currtime) << ","; + m_fout << event << ","; + m_fout << rtt_sample << ","; + m_fout << rttvar_sample << ","; + m_fout << is_smoothed_rtt_reset << ","; + m_fout << recvTotal << ","; + m_fout << smoothed_rtt << ","; + m_fout << rttvar << "\n"; + m_fout.flush(); + } + +private: + void print_header() + { + m_fout << "Timepoint_SYST,Timepoint_STDY,Event,usRTTSample," + "usRTTVarSample,IsSmoothedRTTReset,pktsRecvTotal," + "usSmoothedRTT,usRTTVar\n"; + } + + void create_file() + { + if (m_fout.is_open()) + return; + + std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); + str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part + while (str_tnow.find(':') != std::string::npos) { + str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); + } + const std::string fname = "rtt_trace_" + str_tnow + "_" + SRT_SYNC_CLOCK_STR + ".csv"; + m_fout.open(fname, std::ofstream::out); + if (!m_fout) + std::cerr << "IPE: Failed to open " << fname << "!!!\n"; + + print_header(); + } + +private: + srt::sync::Mutex m_mtx; + std::ofstream m_fout; +}; + +RttTracer s_rtt_trace; +#endif + + +bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData; size_t len = ctrlpkt->getLength(); @@ -2187,7 +1951,7 @@ bool CUDT::processSrtMsg(const CPacket *ctrlpkt) int res = SRT_CMD_NONE; - HLOGC(mglog.Debug, log << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t))); + HLOGC(cnlog.Debug, log << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t))); switch (etype) { case SRT_CMD_HSREQ: @@ -2207,24 +1971,26 @@ bool CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t srtdata_out[SRTDATA_MAXSIZE]; size_t len_out = 0; - res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, srtdata_out, Ref(len_out), CUDT::HS_VERSION_UDT4); + res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, + (srtdata_out), (len_out)); if (res == SRT_CMD_KMRSP) { if (len_out == 1) { - if (m_bOPT_StrictEncryption) + if (m_config.bEnforcedEnc) { - LOGC(mglog.Error, + LOGC(cnlog.Warn, log << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])) - << " - rejecting per strict encryption"); - return false; + << " - rejecting per enforced encryption"); + res = SRT_CMD_NONE; + break; } - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))); } else { - HLOGC(mglog.Debug, log << "KMREQ -> requested to send KMRSP length=" << len_out); + HLOGC(cnlog.Debug, log << "KMREQ -> requested to send KMRSP length=" << len_out); } sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); } @@ -2232,7 +1998,7 @@ bool CUDT::processSrtMsg(const CPacket *ctrlpkt) // Please review later. else { - LOGC(mglog.Error, log << "KMREQ failed to process the request - ignoring"); + LOGC(cnlog.Warn, log << "KMREQ failed to process the request - ignoring"); } return true; // already done what's necessary @@ -2258,7 +2024,7 @@ bool CUDT::processSrtMsg(const CPacket *ctrlpkt) return true; } -int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, int hsv) +int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) { // Set this start time in the beginning, regardless as to whether TSBPD is being // used or not. This must be done in the Initiator as well as Responder. @@ -2268,50 +2034,40 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, * This takes time zone, time drift into account. * Also includes current packet transit time (rtt/2) */ -#if 0 // Debug PeerStartTime if not 1st HS packet - { - uint64_t oldPeerStartTime = m_ullRcvPeerStartTime; - m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts); - if (oldPeerStartTime) { - LOGC(mglog.Note, log << "rcvSrtMsg: 2nd PeerStartTime diff=" << - (m_ullRcvPeerStartTime - oldPeerStartTime) << " usec"); - - } - } -#else - m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts); -#endif + m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts); + // (in case of bonding group, this value will be OVERWRITTEN + // later in CUDT::interpretGroup). // Prepare the initial runtime values of latency basing on the option values. // They are going to get the value fixed HERE. - m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay; - m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay; + m_iTsbPdDelay_ms = m_config.iRcvLatency; + m_iPeerTsbPdDelay_ms = m_config.iPeerLatency; - if (len < SRT_CMD_HSREQ_MINSZ) + if (bytelen < SRT_CMD_HSREQ_MINSZ) { m_RejectReason = SRT_REJ_ROGUE; /* Packet smaller than minimum compatible packet size */ - LOGF(mglog.Error, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " invalid", SRT_CMD_HSREQ, len); + LOGF(cnlog.Error, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " invalid", SRT_CMD_HSREQ, bytelen); return SRT_CMD_NONE; } - LOGF(mglog.Note, + LOGF(cnlog.Note, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " vers=0x%x opts=0x%x delay=%d", SRT_CMD_HSREQ, - len, + bytelen, srtdata[SRT_HS_VERSION], srtdata[SRT_HS_FLAGS], SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); - m_lPeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS]; + m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; + m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; if (hsv == CUDT::HS_VERSION_UDT4) { - if (m_lPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) + if (m_uPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " is not acceptable."); return SRT_CMD_REJECT; @@ -2319,71 +2075,85 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, } else { - if (m_lPeerSrtVersion < SRT_VERSION_FEAT_HSv5) + if (m_uPeerSrtVersion < SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "HSREQ/rcv: With HSv5 version must be >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " ."); return SRT_CMD_REJECT; } } // Check also if the version satisfies the minimum required version - if (m_lPeerSrtVersion < m_lMinimumPeerSrtVersion) + if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) { m_RejectReason = SRT_REJ_VERSION; - LOGC(mglog.Error, - log << "HSREQ/rcv: Peer version: " << SrtVersionString(m_lPeerSrtVersion) - << " is too old for requested: " << SrtVersionString(m_lMinimumPeerSrtVersion) << " - REJECTING"); + LOGC(cnlog.Error, + log << "HSREQ/rcv: Peer version: " << SrtVersionString(m_uPeerSrtVersion) + << " is too old for requested: " << SrtVersionString(m_config.uMinimumPeerSrtVersion) + << " - REJECTING"); return SRT_CMD_REJECT; } - HLOGC(mglog.Debug, - log << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_lPeerSrtVersion) << " Flags: " << m_lPeerSrtFlags - << "(" << SrtFlagString(m_lPeerSrtFlags) << ")"); + HLOGC(cnlog.Debug, + log << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_uPeerSrtVersion) << " Flags: " << m_uPeerSrtFlags + << "(" << SrtFlagString(m_uPeerSrtFlags) + << ") Min req version:" << SrtVersionString(m_config.uMinimumPeerSrtVersion)); - m_bPeerRexmitFlag = IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG); - HLOGF(mglog.Debug, "HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND"); + m_bPeerRexmitFlag = IsSet(m_uPeerSrtFlags, SRT_OPT_REXMITFLG); + HLOGF(cnlog.Debug, "HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND"); // Check if both use the same API type. Reject if not. - bool peer_message_api = !IsSet(m_lPeerSrtFlags, SRT_OPT_STREAM); - if (peer_message_api != m_bMessageAPI) + bool peer_message_api = !IsSet(m_uPeerSrtFlags, SRT_OPT_STREAM); + if (peer_message_api != m_config.bMessageAPI) { m_RejectReason = SRT_REJ_MESSAGEAPI; - LOGC(mglog.Error, - log << "HSREQ/rcv: Agent uses " << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, but the Peer declares " - << (peer_message_api ? "MESSAGE" : "STREAM") << " API. Not compatible transmission type, rejecting."); + LOGC(cnlog.Error, + log << "HSREQ/rcv: Agent uses " << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") + << " API, but the Peer declares " << (peer_message_api ? "MESSAGE" : "STREAM") + << " API. Not compatible transmission type, rejecting."); return SRT_CMD_REJECT; } - if (len < SRT_HS_LATENCY + 1) + SRT_STATIC_ASSERT(SRT_HS_E_SIZE == SRT_HS_LATENCY + 1, "Assuming latency is the last field"); + if (bytelen < (SRT_HS_E_SIZE * sizeof(uint32_t))) { - // 3 is the size when containing VERSION, FLAGS and LATENCY. Less size - // makes it contain only the first two. Let's make it acceptable, as long - // as the latency flags aren't set. - if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV)) + // Handshake extension message includes VERSION, FLAGS and LATENCY + // (3 x 32 bits). SRT v1.2.0 and earlier might supply shorter extension message, + // without LATENCY fields. + // It is acceptable, as long as the latency flags are not set on our side. + // + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | SRT Version | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | SRT Flags | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Receiver TSBPD Delay | Sender TSBPD Delay | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting."); return SRT_CMD_REJECT; } - LOGC(mglog.Warn, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, not getting any TSBPD settings."); + LOGC(cnlog.Warn, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, not getting any TSBPD settings."); // Don't process any further settings in this case. Turn off TSBPD, just for a case. m_bTsbPd = false; m_bPeerTsbPd = false; return SRT_CMD_HSRSP; } - uint32_t latencystr = srtdata[SRT_HS_LATENCY]; + const uint32_t latencystr = srtdata[SRT_HS_LATENCY]; - if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND)) + if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND)) { // TimeStamp-based Packet Delivery feature enabled - if (!m_bTsbPd) + if (!isOPT_TsbPd()) { - LOGC(mglog.Warn, log << "HSREQ/rcv: Agent did not set rcv-TSBPD - ignoring proposed latency from peer"); + LOGC(cnlog.Warn, log << "HSREQ/rcv: Agent did not set rcv-TSBPD - ignoring proposed latency from peer"); // Note: also don't set the peer TSBPD flag HERE because // - in HSv4 it will be a sender, so it doesn't matter anyway @@ -2410,23 +2180,24 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, // Use the maximum latency out of latency from our settings and the latency // "proposed" by the peer. int maxdelay = std::max(m_iTsbPdDelay_ms, peer_decl_latency); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay); m_iTsbPdDelay_ms = maxdelay; + m_bTsbPd = true; } } else { - std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent"; - HLOGC(mglog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for sending - " << how_about_agent); + std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent"; + HLOGC(cnlog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for sending - " << how_about_agent); } // This happens when the HSv5 RESPONDER receives the HSREQ message; it declares // that the peer INITIATOR will receive the data and informs about its predefined // latency. We need to maximize this with our setting of the peer's latency and // record as peer's latency, which will be then sent back with HSRSP. - if (hsv > CUDT::HS_VERSION_UDT4 && IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV)) + if (hsv > CUDT::HS_VERSION_UDT4 && IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // So, PEER uses TSBPD, set the flag. // NOTE: it doesn't matter, if AGENT uses TSBPD. @@ -2437,27 +2208,27 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, // and select the maximum of this one and our proposed latency for the peer. int peer_decl_latency = SRT_HS_LATENCY_RCV::unwrap(latencystr); int maxdelay = std::max(m_iPeerTsbPdDelay_ms, peer_decl_latency); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay_ms << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay); m_iPeerTsbPdDelay_ms = maxdelay; } else { - std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent"; - HLOGC(mglog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for receiving - " << how_about_agent); + std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent"; + HLOGC(cnlog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for receiving - " << how_about_agent); } if (hsv > CUDT::HS_VERSION_UDT4) { // This is HSv5, do the same things as required for the sending party in HSv4, // as in HSv5 this can also be a sender. - if (IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP)) + if (IsSet(m_uPeerSrtFlags, SRT_OPT_TLPKTDROP)) { // Too late packets dropping feature supported m_bPeerTLPktDrop = true; } - if (IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT)) + if (IsSet(m_uPeerSrtFlags, SRT_OPT_NAKREPORT)) { // Peer will send Periodic NAK Reports m_bPeerNakReport = true; @@ -2467,20 +2238,20 @@ int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, return SRT_CMD_HSRSP; } -int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t len, uint32_t ts, int hsv) +int srt::CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) { // XXX Check for mis-version - // With HSv4 we accept only version less than 1.2.0 + // With HSv4 we accept only version less than 1.3.0 if (hsv == CUDT::HS_VERSION_UDT4 && srtdata[SRT_HS_VERSION] >= SRT_VERSION_FEAT_HSv5) { - LOGC(mglog.Error, log << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable."); + LOGC(cnlog.Error, log << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable."); return SRT_CMD_NONE; } - if (len < SRT_CMD_HSRSP_MINSZ) + if (bytelen < SRT_CMD_HSRSP_MINSZ) { /* Packet smaller than minimum compatible packet size */ - LOGF(mglog.Error, "HSRSP/rcv: cmd=%d(HSRSP) len=%" PRIzu " invalid", SRT_CMD_HSRSP, len); + LOGF(cnlog.Error, "HSRSP/rcv: cmd=%d(HSRSP) len=%" PRIzu " invalid", SRT_CMD_HSRSP, bytelen); return SRT_CMD_NONE; } @@ -2493,38 +2264,49 @@ int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t len, uint32_t ts, * This takes time zone, time drift into account. * Also includes current packet transit time (rtt/2) */ -#if 0 // Debug PeerStartTime if not 1st HS packet - { - uint64_t oldPeerStartTime = m_ullRcvPeerStartTime; - m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts); - if (oldPeerStartTime) { - LOGC(mglog.Note, log << "rcvSrtMsg: 2nd PeerStartTime diff=" << - (m_ullRcvPeerStartTime - oldPeerStartTime) << " usec"); - } + if (is_zero(m_tsRcvPeerStartTime)) + { + // Do not set this time when it's already set, which may be the case + // if the agent has this value already "borrowed" from a master socket + // that was in the group at the time when it was added. + m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts); + HLOGC(cnlog.Debug, log << "HSRSP/rcv: PEER START TIME not yet defined, setting: " << FormatTime(m_tsRcvPeerStartTime)); + } + else + { + HLOGC(cnlog.Debug, log << "HSRSP/rcv: PEER START TIME already set (derived): " << FormatTime(m_tsRcvPeerStartTime)); } -#else - m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts); -#endif - m_lPeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS]; + m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; + m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; - HLOGF(mglog.Debug, + HLOGF(cnlog.Debug, "HSRSP/rcv: Version: %s Flags: SND:%08X (%s)", - SrtVersionString(m_lPeerSrtVersion).c_str(), - m_lPeerSrtFlags, - SrtFlagString(m_lPeerSrtFlags).c_str()); + SrtVersionString(m_uPeerSrtVersion).c_str(), + m_uPeerSrtFlags, + SrtFlagString(m_uPeerSrtFlags).c_str()); + + // Basic version check + if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) + { + m_RejectReason = SRT_REJ_VERSION; + LOGC(cnlog.Error, + log << "HSRSP/rcv: Peer version: " << SrtVersionString(m_uPeerSrtVersion) + << " is too old for requested: " << SrtVersionString(m_config.uMinimumPeerSrtVersion) + << " - REJECTING"); + return SRT_CMD_REJECT; + } if (hsv == CUDT::HS_VERSION_UDT4) { // The old HSv4 way: extract just one value and put it under peer. - if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV)) + if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // TsbPd feature enabled m_bPeerTsbPd = true; m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << " (Agent: declared:" << m_iTsbPdDelay_ms << " rcv:" << m_iTsbPdDelay_ms << ")"); } @@ -2534,63 +2316,66 @@ int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t len, uint32_t ts, { // HSv5 way: extract the receiver latency and sender latency, if used. - if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV)) + // PEER WILL RECEIVE TSBPD == AGENT SHALL SEND TSBPD. + if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDRCV)) { // TsbPd feature enabled m_bPeerTsbPd = true; m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]); - HLOGC(mglog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms"); + HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms"); } else { - HLOGC(mglog.Debug, log << "HSRSP/rcv: Peer (responder) DOES NOT USE latency"); + HLOGC(cnlog.Debug, log << "HSRSP/rcv: Peer (responder) DOES NOT USE latency"); } - if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND)) + // PEER WILL SEND TSBPD == AGENT SHALL RECEIVE TSBPD. + if (IsSet(m_uPeerSrtFlags, SRT_OPT_TSBPDSND)) { - if (!m_bTsbPd) + if (!isOPT_TsbPd()) { - LOGC(mglog.Warn, + LOGC(cnlog.Warn, log << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD."); } else { + m_bTsbPd = true; // NOTE: in case of Group TSBPD receiving, this field will be SWITCHED TO m_bGroupTsbPd. // Take this value as a good deal. In case when the Peer did not "correct" the latency // because it has TSBPD turned off, just stay with the present value defined in options. m_iTsbPdDelay_ms = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); - HLOGC(mglog.Debug, log << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms"); + HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms"); } } } - if ((m_lSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP)) + if ((m_config.uSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_uPeerSrtFlags, SRT_OPT_TLPKTDROP)) { // Too late packets dropping feature supported m_bPeerTLPktDrop = true; } - if ((m_lSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT)) + if ((m_config.uSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_uPeerSrtFlags, SRT_OPT_NAKREPORT)) { // Peer will send Periodic NAK Reports m_bPeerNakReport = true; } - if (m_lSrtVersion >= SrtVersion(1, 2, 0)) + if (m_config.uSrtVersion >= SrtVersion(1, 2, 0)) { - if (IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG)) + if (IsSet(m_uPeerSrtFlags, SRT_OPT_REXMITFLG)) { // Peer will use REXMIT flag in packet retransmission. m_bPeerRexmitFlag = true; - HLOGP(mglog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer."); + HLOGP(cnlog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer."); } else { - HLOGP(mglog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT"); + HLOGP(cnlog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT"); } } else { - HLOGF(mglog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag"); + HLOGF(cnlog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag"); } handshakeDone(); @@ -2599,26 +2384,36 @@ int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t len, uint32_t ts, } // This function is called only when the URQ_CONCLUSION handshake has been received from the peer. -bool CUDT::interpretSrtHandshake(const CHandShake &hs, - const CPacket & hspkt, - uint32_t *out_data SRT_ATR_UNUSED, - size_t * out_len) +bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, + const CPacket& hspkt, + uint32_t* out_data SRT_ATR_UNUSED, + size_t* pw_len) { - // Initialize out_len to 0 to handle the unencrypted case - if (out_len) - *out_len = 0; + // Initialize pw_len to 0 to handle the unencrypted case + if (pw_len) + *pw_len = 0; // The version=0 statement as rejection is used only since HSv5. // The HSv4 sends the AGREEMENT handshake message with version=0, do not misinterpret it. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4 && hs.m_iVersion == 0) { m_RejectReason = SRT_REJ_PEER; - LOGC(mglog.Error, log << "HS VERSION = 0, meaning the handshake has been rejected."); + LOGC(cnlog.Error, log << "HS VERSION = 0, meaning the handshake has been rejected."); return false; } if (hs.m_iVersion < HS_VERSION_SRT1) + { + if (m_config.uMinimumPeerSrtVersion && m_config.uMinimumPeerSrtVersion >= SRT_VERSION_FEAT_HSv5) + { + m_RejectReason = SRT_REJ_VERSION; + // This means that a version with minimum 1.3.0 that features HSv5 is required, + // hence all HSv4 clients should be rejected. + LOGP(cnlog.Error, "interpretSrtHandshake: minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client"); + return false; + } return true; // do nothing + } // Anyway, check if the handshake contains any extra data. if (hspkt.getLength() <= CHandShake::m_iContentSize) @@ -2626,29 +2421,31 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, m_RejectReason = SRT_REJ_ROGUE; // This would mean that the handshake was at least HSv5, but somehow no extras were added. // Dismiss it then, however this has to be logged. - LOGC(mglog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!"); + LOGC(cnlog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!"); return false; } // We still believe it should work, let's check the flags. - int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); + const int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); if (ext_flags == 0) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!"); + LOGC(cnlog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!"); return false; } - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "HS VERSION=" << hs.m_iVersion << " EXTENSIONS: " << CHandShake::ExtensionFlagStr(ext_flags)); // Ok, now find the beginning of an int32_t array that follows the UDT handshake. - uint32_t *p = reinterpret_cast(hspkt.m_pcData + CHandShake::m_iContentSize); + uint32_t* p = reinterpret_cast(hspkt.m_pcData + CHandShake::m_iContentSize); size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0 + int hsreq_type_cmd SRT_ATR_UNUSED = SRT_CMD_NONE; + if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ)) { - HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting HSREQ/RSP type extension"); + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting HSREQ/RSP type extension"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); @@ -2656,19 +2453,20 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, for (;;) // this is ONE SHOT LOOP { - int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next)); + int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_HSREQ) { + hsreq_type_cmd = cmd; // Set is the size as it should, then give it for interpretation for // the proper function. - if (blocklen < SRT_HS__SIZE) + if (blocklen < SRT_HS_E_SIZE) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, - log << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS__SIZE + LOGC(cnlog.Error, + log << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } @@ -2678,22 +2476,23 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (rescmd != SRT_CMD_HSRSP) { // m_RejectReason already set - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd); return false; } handshakeDone(); - updateAfterSrtHandshake(SRT_CMD_HSREQ, HS_VERSION_SRT1); + // updateAfterSrtHandshake -> moved to postConnect and processRendezvous } else if (cmd == SRT_CMD_HSRSP) { + hsreq_type_cmd = cmd; // Set is the size as it should, then give it for interpretation for // the proper function. - if (blocklen < SRT_HS__SIZE) + if (blocklen < SRT_HS_E_SIZE) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, - log << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS__SIZE + LOGC(cnlog.Error, + log << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret @@ -2704,19 +2503,21 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, // (nothing to be responded for HSRSP, unless there was some kinda problem) if (rescmd != SRT_CMD_NONE) { - // Just formally; the current code doesn't seem to return anything else. - m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, + // Just formally; the current code doesn't seem to return anything else + // (unless it's already set) + if (m_RejectReason == SRT_REJ_UNKNOWN) + m_RejectReason = SRT_REJ_ROGUE; + LOGC(cnlog.Error, log << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd); return false; } handshakeDone(); - updateAfterSrtHandshake(SRT_CMD_HSRSP, HS_VERSION_SRT1); + // updateAfterSrtHandshake -> moved to postConnect and processRendezvous } else if (cmd == SRT_CMD_NONE) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, log << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!"); + LOGC(cnlog.Warn, log << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!"); // This means that there can be no more processing done by FindExtensionBlock(). // And we haven't found what we need - otherwise one of the above cases would pass // and lead to exit this loop immediately. @@ -2735,7 +2536,7 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, } } - HLOGC(mglog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ"); + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ"); // Now check the encrypted @@ -2743,21 +2544,20 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ)) { - HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting KMREQ/RSP type extension"); + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting KMREQ/RSP type extension"); #ifdef SRT_ENABLE_ENCRYPTION if (!m_pCryptoControl->hasPassphrase()) { - if (m_bOPT_StrictEncryption) + if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; - LOGC( - mglog.Error, - log << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per strict requirement"); + LOGC(cnlog.Error, + log << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per enforced encryption"); return false; } - LOGC(mglog.Error, + LOGC(cnlog.Warn, log << "HS KMREQ: Peer declares encryption, but agent does not - still allowing connection."); // Still allow for connection, and allow Agent to send unencrypted stream to the peer. @@ -2771,36 +2571,36 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, for (;;) // This is one shot loop, unless REPEATED by 'continue'. { - int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next)); + int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_KMREQ) { - if (!out_data || !out_len) + if (!out_data || !pw_len) { m_RejectReason = SRT_REJ_IPE; - LOGC(mglog.Fatal, log << "IPE: HS/KMREQ extracted without passing target buffer!"); + LOGC(cnlog.Fatal, log << "IPE: HS/KMREQ extracted without passing target buffer!"); return false; } - int res = - m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, out_data, Ref(*out_len), HS_VERSION_SRT1); + int res = m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, + (out_data), (*pw_len)); if (res != SRT_CMD_KMRSP) { m_RejectReason = SRT_REJ_IPE; // Something went wrong. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res); return false; } - if (*out_len == 1) + if (*pw_len == 1) { // This means that there was an abnormal encryption situation occurred. // This is inacceptable in case of strict encryption. - if (m_bOPT_StrictEncryption) + if (m_config.bEnforcedEnc) { if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADSECRET) { @@ -2810,8 +2610,8 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, { m_RejectReason = SRT_REJ_UNSECURE; } - LOGC(mglog.Error, - log << "interpretSrtHandshake: KMREQ result abnornal - rejecting per strict encryption"); + LOGC(cnlog.Error, + log << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption"); return false; } } @@ -2820,10 +2620,10 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, else if (cmd == SRT_CMD_KMRSP) { int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, HS_VERSION_SRT1); - if (m_bOPT_StrictEncryption && res == -1) + if (m_config.bEnforcedEnc && res == -1) { m_RejectReason = SRT_REJ_UNSECURE; - LOGC(mglog.Error, log << "KMRSP failed - rejecting connection as per strict encryption."); + LOGC(cnlog.Error, log << "KMRSP failed - rejecting connection as per enforced encryption."); return false; } encrypted = true; @@ -2831,13 +2631,13 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, else if (cmd == SRT_CMD_NONE) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, log << "HS KMREQ expected - none found!"); + LOGC(cnlog.Error, log << "HS KMREQ expected - none found!"); return false; } else { - HLOGC(mglog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); - if (NextExtensionBlock(Ref(begin), next, Ref(length))) + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); + if (NextExtensionBlock((begin), next, (length))) continue; } @@ -2847,16 +2647,16 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, // When encryption is not enabled at compile time, behave as if encryption wasn't set, // so accordingly to StrictEncryption flag. - if (m_bOPT_StrictEncryption) + if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting " - "per strict requirement"); + "per enforced encryption"); return false; } - LOGC(mglog.Error, + LOGC(cnlog.Warn, log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing " "connection."); encrypted = true; @@ -2865,16 +2665,18 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, bool have_congctl = false; bool have_filter = false; - string agsm = m_CongCtl.selected_name(); + string agsm = m_config.sCongestion.str(); if (agsm == "") { agsm = "live"; - m_CongCtl.select("live"); + m_config.sCongestion.set("live", 4); } + bool have_group SRT_ATR_UNUSED = false; + if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) { - HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting various CONFIG extensions"); + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting various CONFIG extensions"); uint32_t *begin = p; uint32_t *next = 0; @@ -2883,18 +2685,18 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, for (;;) // This is one shot loop, unless REPEATED by 'continue'. { - int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next)); + int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); const size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_SID) { - if (!bytelen || bytelen > MAX_SID_LENGTH) + if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { - LOGC(mglog.Error, - log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +MAX_SID_LENGTH + LOGC(cnlog.Error, + log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } @@ -2906,16 +2708,16 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, // padding zeros. In all these cases in the resulting array we should have all // subsequent characters of the string plus at least one '\0' at the end. This will // make it a perfect NUL-terminated string, to be used to initialize a string. - char target[MAX_SID_LENGTH + 1]; - memset(target, 0, MAX_SID_LENGTH + 1); - memcpy(target, begin + 1, bytelen); + char target[CSrtConfig::MAX_SID_LENGTH + 1]; + memset((target), 0, CSrtConfig::MAX_SID_LENGTH + 1); + memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); - m_sStreamName = target; - HLOGC(mglog.Debug, - log << "CONNECTOR'S REQUESTED SID [" << m_sStreamName << "] (bytelen=" << bytelen + m_config.sStreamName.set(target, strlen(target)); + HLOGC(cnlog.Debug, + log << "CONNECTOR'S REQUESTED SID [" << m_config.sStreamName.c_str() << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_CONGESTION) @@ -2923,23 +2725,23 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (have_congctl) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, log << "CONGCTL BLOCK REPEATED!"); + LOGC(cnlog.Error, log << "CONGCTL BLOCK REPEATED!"); return false; } - if (!bytelen || bytelen > MAX_SID_LENGTH) + if (!bytelen || bytelen > CSrtConfig::MAX_CONG_LENGTH) { - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > " - << +MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); + << +CSrtConfig::MAX_CONG_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Declare that congctl has been received have_congctl = true; - char target[MAX_SID_LENGTH + 1]; - memset(target, 0, MAX_SID_LENGTH + 1); - memcpy(target, begin + 1, bytelen); + char target[CSrtConfig::MAX_CONG_LENGTH + 1]; + memset((target), 0, CSrtConfig::MAX_CONG_LENGTH + 1); + memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); @@ -2951,12 +2753,12 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (sm != agsm) { m_RejectReason = SRT_REJ_CONGESTION; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'"); return false; } - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } @@ -2965,30 +2767,65 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (have_filter) { m_RejectReason = SRT_REJ_FILTER; - LOGC(mglog.Error, log << "FILTER BLOCK REPEATED!"); + LOGC(cnlog.Error, log << "FILTER BLOCK REPEATED!"); + return false; + } + + if (!bytelen || bytelen > CSrtConfig::MAX_PFILTER_LENGTH) + { + LOGC(cnlog.Error, + log << "interpretSrtHandshake: packet-filter type length " << bytelen << " is 0 or > " + << +CSrtConfig::MAX_PFILTER_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Declare that filter has been received have_filter = true; - // XXX This is the maximum string, but filter config - // shall be normally limited somehow, especially if used - // together with SID! - char target[MAX_SID_LENGTH + 1]; - memset(target, 0, MAX_SID_LENGTH + 1); - memcpy(target, begin + 1, bytelen); + char target[CSrtConfig::MAX_PFILTER_LENGTH + 1]; + memset((target), 0, CSrtConfig::MAX_PFILTER_LENGTH + 1); + memcpy((target), begin + 1, bytelen); string fltcfg = target; - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); if (!checkApplyFilterConfig(fltcfg)) { - LOGC(mglog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected"); + m_RejectReason = SRT_REJ_FILTER; + LOGC(cnlog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected"); + return false; + } + } +#if ENABLE_BONDING + else if ( cmd == SRT_CMD_GROUP ) + { + // Note that this will fire in both cases: + // - When receiving HS request from the Initiator, which belongs to a group, and agent must + // create the mirror group on his side (or join the existing one, if there's already + // a mirror group for that group ID). + // - When receiving HS response from the Responder, with its mirror group ID, so the agent + // must put the group into his peer group data + int32_t groupdata[GRPD_E_SIZE] = {}; + if (bytelen < GRPD_MIN_SIZE * GRPD_FIELD_SIZE || bytelen % GRPD_FIELD_SIZE) + { + m_RejectReason = SRT_REJ_ROGUE; + LOGC(cnlog.Error, log << "PEER'S GROUP wrong size: " << (bytelen/GRPD_FIELD_SIZE)); + return false; + } + size_t groupdata_size = bytelen / GRPD_FIELD_SIZE; + + memcpy(groupdata, begin+1, bytelen); + if (!interpretGroup(groupdata, groupdata_size, hsreq_type_cmd) ) + { + // m_RejectReason handled inside interpretGroup(). return false; } + + have_group = true; + HLOGC(cnlog.Debug, log << "CONNECTOR'S PEER GROUP [" << groupdata[0] << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } +#endif else if (cmd == SRT_CMD_NONE) { break; @@ -2996,28 +2833,28 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, else { // Found some block that is not interesting here. Skip this and get the next one. - HLOGC(mglog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); + HLOGC(cnlog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); } - if (!NextExtensionBlock(Ref(begin), next, Ref(length))) + if (!NextExtensionBlock((begin), next, (length))) break; } } // Post-checks // Check if peer declared encryption - if (!encrypted && m_CryptoSecret.len > 0) + if (!encrypted && m_config.CryptoSecret.len > 0) { - if (m_bOPT_StrictEncryption) + if (m_config.bEnforcedEnc) { m_RejectReason = SRT_REJ_UNSECURE; - LOGC(mglog.Error, - log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per strict " - "requirement."); + LOGC(cnlog.Error, + log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per " + "enforced encryption."); return false; } - LOGC(mglog.Error, + LOGC(cnlog.Warn, log << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets " "from Peer)."); @@ -3033,19 +2870,37 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (agsm != "live" && !have_congctl) { m_RejectReason = SRT_REJ_CONGESTION; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live')."); return false; } +#if ENABLE_BONDING + // m_GroupOf and locking info: NULL check won't hurt here. If the group + // was deleted in the meantime, it will be found out later anyway and result with error. + if (m_SrtHsSide == HSD_INITIATOR && m_parent->m_GroupOf) + { + // XXX Later probably needs to check if this group REQUIRES the group + // response. Currently this implements the bonding-category group, and this + // always requires that the listener respond with the group id, otherwise + // it probably DID NOT UNDERSTAND THE GROUP, so the connection should be rejected. + if (!have_group) + { + m_RejectReason = SRT_REJ_GROUP; + LOGC(cnlog.Error, log << "HS EXT: agent is a group member, but the listener did not respond with group ID. Rejecting."); + return false; + } + } +#endif + // Ok, finished, for now. return true; } -bool CUDT::checkApplyFilterConfig(const std::string &confstr) +bool srt::CUDT::checkApplyFilterConfig(const std::string &confstr) { SrtFilterConfig cfg; - if (!ParseFilterConfig(confstr, cfg)) + if (!ParseFilterConfig(confstr, (cfg))) return false; // Now extract the type, if present, and @@ -3053,17 +2908,19 @@ bool CUDT::checkApplyFilterConfig(const std::string &confstr) if (!PacketFilter::correctConfig(cfg)) return false; + string thisconf = m_config.sPacketFilterConfig.str(); + // Now parse your own string, if you have it. - if (m_OPT_PktFilterConfigString != "") + if (thisconf != "") { - // - for rendezvous, both must be exactly the same, or only one side specified. - if (m_bRendezvous && m_OPT_PktFilterConfigString != confstr) + // - for rendezvous, both must be exactly the same (it's unspecified, which will be the first one) + if (m_config.bRendezvous && thisconf != confstr) { return false; } SrtFilterConfig mycfg; - if (!ParseFilterConfig(m_OPT_PktFilterConfigString, mycfg)) + if (!ParseFilterConfig(thisconf, (mycfg))) return false; // Check only if both have set a filter of the same type. @@ -3083,53 +2940,423 @@ bool CUDT::checkApplyFilterConfig(const std::string &confstr) } else { - // On a listener, only apply those that you haven't set - for (map::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x) - { - if (!mycfg.parameters.count(x->first)) - mycfg.parameters[x->first] = x->second; - } + if (!CheckFilterCompat((mycfg), cfg)) + return false; + } + + HLOGC(cnlog.Debug, + log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters) + << " FORGN: " << Printable(cfg.parameters)); + + ostringstream myos; + myos << mycfg.type; + for (map::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x) + { + myos << "," << x->first << ":" << x->second; + } + + m_config.sPacketFilterConfig.set(myos.str()); + + HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: Effective config: " << thisconf); + } + else + { + // Take the foreign configuration as a good deal. + HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: Good deal config: " << thisconf); + m_config.sPacketFilterConfig.set(confstr); + } + + size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size; + if (m_config.zExpPayloadSize > efc_max_payload_size) + { + LOGC(cnlog.Warn, + log << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " + << efc_max_payload_size << " bytes"); + m_config.zExpPayloadSize = efc_max_payload_size; + } + + return true; +} + +#if ENABLE_BONDING +bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_ATR_UNUSED, int hsreq_type_cmd SRT_ATR_UNUSED) +{ + // `data_size` isn't checked because we believe it's checked earlier. + // Also this code doesn't predict to get any other format than the official one, + // so there are only data in two fields. Passing this argument is only left + // for consistency and possibly changes in future. + + // We are granted these two fields do exist + SRTSOCKET grpid = groupdata[GRPD_GROUPID]; + uint32_t gd = groupdata[GRPD_GROUPDATA]; + + SRT_GROUP_TYPE gtp = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); + int link_weight = SrtHSRequest::HS_GROUP_WEIGHT::unwrap(gd); + uint32_t link_flags = SrtHSRequest::HS_GROUP_FLAGS::unwrap(gd); + + if (m_config.iGroupConnect == 0) + { + m_RejectReason = SRT_REJ_GROUP; + LOGC(cnlog.Error, log << "HS/GROUP: this socket is not allowed for group connect."); + return false; + } + + // This is called when the group type has come in the handshake is invalid. + if (gtp >= SRT_GTYPE_E_END) + { + m_RejectReason = SRT_REJ_GROUP; + LOGC(cnlog.Error, log << "HS/GROUP: incorrect group type value " << gtp << " (max is " << SRT_GTYPE_E_END << ")"); + return false; + } + + if ((grpid & SRTGROUP_MASK) == 0) + { + m_RejectReason = SRT_REJ_ROGUE; + LOGC(cnlog.Error, log << "HS/GROUP: socket ID passed as a group ID is not a group ID"); + return false; + } + + // We have the group, now take appropriate action. + // The redundancy group requires to make a mirror group + // on this side, and the newly created socket should + // be made belong to it. + +#if ENABLE_HEAVY_LOGGING + static const char* hs_side_name[] = {"draw", "initiator", "responder"}; + HLOGC(cnlog.Debug, log << "interpretGroup: STATE: HsSide=" << hs_side_name[m_SrtHsSide] << " HS MSG: " << MessageTypeStr(UMSG_EXT, hsreq_type_cmd) + << " $" << grpid << " type=" << gtp << " weight=" << link_weight << " flags=0x" << std::hex << link_flags); +#endif + + // XXX Here are two separate possibilities: + // + // 1. This is a HS request and this is a newly created socket not yet part of any group. + // 2. This is a HS response and the group is the mirror group for the group to which the agent belongs; we need to pin the mirror group as peer group + // + // These two situations can be only distinguished by the HS side. + if (m_SrtHsSide == HSD_DRAW) + { + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Error, log << "IPE: interpretGroup: The HS side should have been already decided; it's still DRAW. Grouping rejected."); + return false; + } + + ScopedLock guard_group_existence (uglobal().m_GlobControlLock); + + if (m_SrtHsSide == HSD_INITIATOR) + { + // This is a connection initiator that has requested the peer to make a + // mirror group and join it, then respond its mirror group id. The + // `grpid` variable contains this group ID; map this as your peer + // group. If your group already has a peer group set, check if this is + // the same id, otherwise the connection should be rejected. + + // So, first check the group of the current socket and see if a peer is set. + CUDTGroup* pg = m_parent->m_GroupOf; + if (!pg) + { + // This means that the responder has responded with a group membership, + // but the initiator did not request any group membership presence. + // Currently impossible situation. + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Error, log << "IPE: HS/RSP: group membership responded, while not requested."); + return false; + } + + // Group existence is guarded, so we can now lock the group as well. + ScopedLock gl(*pg->exp_groupLock()); + + // Now we know the group exists, but it might still be closed + if (pg->closing()) + { + LOGC(cnlog.Error, log << "HS/RSP: group was closed in the process, can't continue connecting"); + m_RejectReason = SRT_REJ_IPE; + return false; + } + + SRTSOCKET peer = pg->peerid(); + if (peer == -1) + { + // This is the first connection within this group, so this group + // has just been informed about the peer membership. Accept it. + pg->set_peerid(grpid); + HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() << ", copying characteristic data"); + + // The call to syncWithSocket is copying + // some interesting data from the first connected + // socket. This should be only done for the first successful connection. + pg->syncWithSocket(*this, HSD_INITIATOR); + } + // Otherwise the peer id must be the same as existing, otherwise + // this group is considered already bound to another peer group. + // (Note that the peer group is peer-specific, and peer id numbers + // may repeat among sockets connected to groups established on + // different peers). + else if (peer != grpid) + { + LOGC(cnlog.Error, log << "IPE: HS/RSP: group membership responded for peer $" << grpid + << " but the current socket's group $" << pg->id() << " has already a peer $" << peer); + m_RejectReason = SRT_REJ_GROUP; + return false; + } + else + { + HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $" << pg->peerid()); + } + } + else + { + // This is a connection responder that has been requested to make a + // mirror group and join it. Later on, the HS response will be sent + // and its group ID will be added to the HS extensions as mirror group + // ID to the peer. + + SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags); + if (!lgid) + return true; // already done + + if (lgid == -1) + { + // NOTE: This error currently isn't reported by makeMePeerOf, + // so this is left to handle a possible error introduced in future. + m_RejectReason = SRT_REJ_GROUP; + return false; // error occurred + } + + if (!m_parent->m_GroupOf) + { + // Strange, we just added it... + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Fatal, log << "IPE: socket not in group after adding to it"); + return false; + } + + groups::SocketData* f = m_parent->m_GroupMemberData; + + f->weight = link_weight; + f->agent = m_parent->m_SelfAddr; + f->peer = m_PeerAddr; + } + + m_parent->m_GroupOf->debugGroup(); + + // That's all. For specific things concerning group + // types, this will be later. + return true; +} +#endif + +#if ENABLE_BONDING +// NOTE: This function is called only in one place and it's done +// exclusively on the listener side (HSD_RESPONDER, HSv5+). + +// [[using locked(s_UDTUnited.m_GlobControlLock)]] +SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags) +{ + // Note: This function will lock pg->m_GroupLock! + + CUDTSocket* s = m_parent; + + // Note that the socket being worked out here is about to be returned + // from `srt_accept` call, and until this moment it will be inaccessible + // for any other thread. It is then assumed that no other thread is accessing + // it right now so there's no need to lock s->m_ControlLock. + + // Check if there exists a group that this one is a peer of. + CUDTGroup* gp = uglobal().findPeerGroup_LOCKED(peergroup); + bool was_empty = true; + if (gp) + { + if (gp->type() != gtp) + { + LOGC(gmlog.Error, log << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp + << " agent group=$" << gp->id() << " type" << gp->type()); + return -1; + } + + HLOGC(gmlog.Debug, log << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id()); + + if (!gp->groupEmpty()) + was_empty = false; + } + else + { + try + { + gp = &newGroup(gtp); + } + catch (...) + { + // Expected exceptions are only those referring to system resources + return -1; + } + + if (!gp->applyFlags(link_flags, m_SrtHsSide)) + { + // Wrong settings. Must reject. Delete group. + uglobal().deleteGroup_LOCKED(gp); + return -1; } - HLOGC(mglog.Debug, - log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters) - << " FORGN: " << Printable(cfg.parameters)); + gp->set_peerid(peergroup); + gp->deriveSettings(this); - ostringstream myos; - myos << mycfg.type; - for (map::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x) + // This can only happen on a listener (it's only called on a site that is + // HSD_RESPONDER), so it was a response for a groupwise connection. + // Therefore such a group shall always be considered opened. + gp->setOpen(); + + HLOGC(gmlog.Debug, log << "makeMePeerOf: no group has peer=$" << peergroup << " - creating new mirror group $" << gp->id()); + } + + + { + ScopedLock glock (*gp->exp_groupLock()); + if (gp->closing()) { - myos << "," << x->first << ":" << x->second; + HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group $" << gp->id() << " is being closed, can't process"); + } + + if (was_empty) + { + gp->syncWithSocket(s->core(), HSD_RESPONDER); } + } + + // Setting non-blocking reading for group socket. + s->core().m_config.bSynRecving = false; + s->core().m_config.bSynSending = false; + + // Copy of addSocketToGroup. No idea how many parts could be common, not much. + + // Check if the socket already is in the group + groups::SocketData* f; + if (gp->contains(m_SocketID, (f))) + { + // XXX This is internal error. Report it, but continue + // (A newly created socket from acceptAndRespond should not have any group membership yet) + LOGC(gmlog.Error, log << "IPE (non-fatal): the socket is in the group, but has no clue about it!"); + s->m_GroupOf = gp; + s->m_GroupMemberData = f; + return 0; + } + + s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); + s->m_GroupOf = gp; + m_HSGroupType = gtp; + + // Record the remote address in the group data. + + return gp->id(); +} + +void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) +{ + ScopedLock gl (*gp->exp_groupLock()); + + // We have blocked here the process of connecting a new + // socket and adding anything new to the group, so no such + // thing may happen in the meantime. + steady_clock::time_point start_time, peer_start_time; - m_OPT_PktFilterConfigString = myos.str(); + start_time = m_stats.tsStartTime; + peer_start_time = m_tsRcvPeerStartTime; - HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Effective config: " << m_OPT_PktFilterConfigString); + if (!gp->applyGroupTime((start_time), (peer_start_time))) + { + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID + << " DERIVED: ST=" + << FormatTime(m_stats.tsStartTime) << " -> " + << FormatTime(start_time) << " PST=" + << FormatTime(m_tsRcvPeerStartTime) << " -> " + << FormatTime(peer_start_time)); + m_stats.tsStartTime = start_time; + m_tsRcvPeerStartTime = peer_start_time; } else { - // Take the foreign configuration as a good deal. - HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Good deal config: " << m_OPT_PktFilterConfigString); - m_OPT_PktFilterConfigString = confstr; + // This was the first connected socket and it defined start time. + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID + << " DEFINED: ST=" + << FormatTime(m_stats.tsStartTime) + << " PST=" << FormatTime(m_tsRcvPeerStartTime)); } - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size; - if (m_zOPT_ExpPayloadSize > efc_max_payload_size) + steady_clock::time_point rcv_buffer_time_base; + bool rcv_buffer_wrap_period = false; + steady_clock::duration rcv_buffer_udrift(0); + if (m_bTsbPd && gp->getBufferTimeBase(this, (rcv_buffer_time_base), (rcv_buffer_wrap_period), (rcv_buffer_udrift))) { - LOGC(mglog.Warn, - log << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " - << efc_max_payload_size << " bytes"); - m_zOPT_ExpPayloadSize = efc_max_payload_size; + // We have at least one socket in the group, each socket should have + // the value of the timebase set exactly THE SAME. + + // In case when we have the following situation: + + // - the existing link is before [LAST30] (so wrap period is off) + // - the new link gets the timestamp from [LAST30] range + // --> this will be recognized as entering the wrap period, next + // timebase will get added a segment to this value + // + // The only dangerous situations could be when one link gets + // timestamps from the [FOLLOWING30] and the other in [FIRST30], + // but between them there's a 30s distance, considered large enough + // time to not fill a network window. + enterCS(m_RecvLock); + m_pRcvBuffer->applyGroupTime(rcv_buffer_time_base, rcv_buffer_wrap_period, m_iTsbPdDelay_ms * 1000, rcv_buffer_udrift); +#if ENABLE_NEW_RCVBUFFER + m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); +#endif + leaveCS(m_RecvLock); + + HLOGF(gmlog.Debug, "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03us GROUP TIME BASE: %s%s", + m_iTsbPdDelay_ms/1000, + m_iTsbPdDelay_ms%1000, + FormatTime(rcv_buffer_time_base).c_str(), + rcv_buffer_wrap_period ? " (WRAP PERIOD)" : " (NOT WRAP PERIOD)"); + } + else + { + HLOGC(gmlog.Debug, log << "AFTER HS: (GROUP, but " << (m_bTsbPd ? "FIRST SOCKET is initialized normally)" : "no TSBPD set)")); + updateSrtRcvSettings(); } - return true; + // This function currently does nothing, just left for consistency + // with updateAfterSrtHandshake(). + updateSrtSndSettings(); + + if (gp->synconmsgno()) + { + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << ": NOT synchronizing sequence numbers."); + } + else + { + // These are the values that are normally set initially by setters. + int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; + if (!gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn))) + { + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID + << " DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn + << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) + << ") SND=%" << m_iSndLastAck << " -> %" << snd_isn + << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); + setInitialRcvSeq(rcv_isn); + setInitialSndSeq(snd_isn); + } + else + { + HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID + << " DEFINED ISN: RCV=%" << m_iRcvLastAck + << " SND=%" << m_iSndLastAck); + } + } } +#endif -void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) +void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) { - CGuard cg(m_ConnectionLock); + ScopedLock cg (m_ConnectionLock); - HLOGC(mglog.Debug, log << "startConnect: -> " << SockaddrToString(serv_addr) << "..."); + HLOGC(aclog.Debug, log << CONID() << "startConnect: -> " << serv_addr.str() + << (m_config.bSynRecving ? " (SYNCHRONOUS)" : " (ASYNCHRONOUS)") << "..."); if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); @@ -3141,25 +3368,18 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // record peer/server address - delete m_pPeerAddr; - m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6; - memcpy(m_pPeerAddr, serv_addr, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); + m_PeerAddr = serv_addr; // register this socket in the rendezvous queue // RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this // function -#ifdef SRT_ENABLE_CONNTIMEO - uint64_t ttl = m_iConnTimeOut * uint64_t(1000); -#else - uint64_t ttl = 3000000; -#endif - // XXX DEBUG - // ttl = 0x1000000000000000; - // XXX - if (m_bRendezvous) + steady_clock::duration ttl = m_config.tdConnTimeOut; + + if (m_config.bRendezvous) ttl *= 10; - ttl += CTimer::getTime(); - m_pRcvQueue->registerConnector(m_SocketID, this, m_iIPversion, serv_addr, ttl); + + const steady_clock::time_point ttl_time = steady_clock::now() + ttl; + m_pRcvQueue->registerConnector(m_SocketID, this, serv_addr, ttl_time); // The m_iType is used in the INDUCTION for nothing. This value is only regarded // in CONCLUSION handshake, however this must be created after the handshake version @@ -3169,7 +3389,7 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) m_ConnReq.m_iType = UDT_DGRAM; // This is my current configuration - if (m_bRendezvous) + if (m_config.bRendezvous) { // For rendezvous, use version 5 in the waveahand and the cookie. // In case when you get the version 4 waveahand, simply switch to @@ -3186,11 +3406,11 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // This will be also passed to a HSv4 rendezvous, but fortunately the old // SRT didn't read this field from URQ_WAVEAHAND message, only URQ_CONCLUSION. - m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_iSndCryptoKeyLen); - bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0; - HLOGC(mglog.Debug, + m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_config.iSndCryptoKeyLen); + bool whether SRT_ATR_UNUSED = m_config.iSndCryptoKeyLen != 0; + HLOGC(aclog.Debug, log << "startConnect (rnd): " << (whether ? "" : "NOT ") - << " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen); + << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); m_RdvState = CHandShake::RDV_WAVING; m_SrtHsSide = HSD_DRAW; // initially not resolved. } @@ -3211,30 +3431,27 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) m_RdvState = CHandShake::RDV_INVALID; } - m_ConnReq.m_iMSS = m_iMSS; - m_ConnReq.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize; + m_ConnReq.m_iMSS = m_config.iMSS; + // Defined as the size of the receiver buffer in packets, unless + // SRTO_FC has been set to a less value. + m_ConnReq.m_iFlightFlagSize = m_config.flightCapacity(); m_ConnReq.m_iID = m_SocketID; - CIPAddress::ntop(serv_addr, m_ConnReq.m_piPeerIP, m_iIPversion); + CIPAddress::ntop(serv_addr, (m_ConnReq.m_piPeerIP)); - if (forced_isn == 0) + if (forced_isn == SRT_SEQNO_NONE) { - // Random Initial Sequence Number (normal mode) - srand((unsigned int)CTimer::getTime()); - m_iISN = m_ConnReq.m_iISN = (int32_t)(CSeqNo::m_iMaxSeqNo * (double(rand()) / RAND_MAX)); + forced_isn = generateISN(); + HLOGC(aclog.Debug, log << "startConnect: ISN generated = " << forced_isn); } else { - // Predefined ISN (for debug purposes) - m_iISN = m_ConnReq.m_iISN = forced_isn; + HLOGC(aclog.Debug, log << "startConnect: ISN forced = " << forced_isn); } - m_iLastDecSeq = m_iISN - 1; - m_iSndLastAck = m_iISN; - m_iSndLastDataAck = m_iISN; - m_iSndLastFullAck = m_iISN; - m_iSndCurrSeqNo = m_iISN - 1; - m_iSndLastAck2 = m_iISN; - m_ullSndLastAck2Time = CTimer::getTime(); + m_iISN = m_ConnReq.m_iISN = forced_isn; + + setInitialSndSeq(m_iISN); + m_SndLastAck2Time = steady_clock::now(); // Inform the server my configurations. CPacket reqpkt; @@ -3255,26 +3472,26 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) reqpkt.m_iID = 0; size_t hs_size = m_iMaxSRTPayloadSize; - m_ConnReq.store_to(reqpkt.m_pcData, Ref(hs_size)); + m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); // Note that CPacket::allocate() sets also the size // to the size of the allocated buffer, which not // necessarily is to be the size of the data. reqpkt.setLength(hs_size); - uint64_t now = CTimer::getTime(); - reqpkt.m_iTimeStamp = int32_t(now - m_stats.startTime); + steady_clock::time_point now = steady_clock::now(); + setPacketTS(reqpkt, now); - HLOGC(mglog.Debug, - log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (" << now << "). SENDING HS: " << m_ConnReq.show()); + HLOGC(cnlog.Debug, + log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (TimeStamp: " << reqpkt.m_iTimeStamp << "). SENDING HS: " << m_ConnReq.show()); /* * Race condition if non-block connect response thread scheduled before we set m_bConnecting to true? * Connect response will be ignored and connecting will wait until timeout. * Maybe m_ConnectionLock handling problem? Not used in CUDT::connect(const CPacket& response) */ - m_llLastReqTime = now; - m_bConnecting = true; + m_tsLastReqTime = now; + m_bConnecting = true; m_pSndQueue->sendto(serv_addr, reqpkt); // @@ -3287,13 +3504,18 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) /// // - // asynchronous connect, return immediately - if (!m_bSynRecving) + ////////////////////////////////////////////////////// + // SYNCHRO BAR + ////////////////////////////////////////////////////// + if (!m_config.bSynRecving) { - HLOGC(mglog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker"); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker"); return; } + // Below this bar, rest of function maintains only and exclusively + // the SYNCHRONOUS (blocking) connection process. + // Wait for the negotiated configurations from the peer side. // This packet only prepares the storage where we will read the @@ -3307,52 +3529,52 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) while (!m_bClosing) { - int64_t tdiff = CTimer::getTime() - m_llLastReqTime; + const steady_clock::duration tdiff = steady_clock::now() - m_tsLastReqTime.load(); // avoid sending too many requests, at most 1 request per 250ms // SHORT VERSION: // The immediate first run of this loop WILL SKIP THIS PART, so // the processing really begins AFTER THIS CONDITION. // - // Note that some procedures inside may set m_llLastReqTime to 0, + // Note that some procedures inside may set m_tsLastReqTime to 0, // which will result of this condition to trigger immediately in // the next iteration. - if (tdiff > 250000) + if (count_milliseconds(tdiff) > 250) { - HLOGC(mglog.Debug, - log << "startConnect: LOOP: time to send (" << tdiff << " > 250000). size=" << reqpkt.getLength()); + HLOGC(cnlog.Debug, + log << "startConnect: LOOP: time to send (" << count_milliseconds(tdiff) << " > 250 ms). size=" << reqpkt.getLength()); - if (m_bRendezvous) + if (m_config.bRendezvous) reqpkt.m_iID = m_ConnRes.m_iID; - now = CTimer::getTime(); + now = steady_clock::now(); #if ENABLE_HEAVY_LOGGING { CHandShake debughs; debughs.load_from(reqpkt.m_pcData, reqpkt.getLength()); - HLOGC(mglog.Debug, - log << CONID() << "startConnect: REQ-TIME HIGH (" << now - << "). cont/sending HS to peer: " << debughs.show()); + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: REQ-TIME HIGH." + << " cont/sending HS to peer: " << debughs.show()); } #endif - m_llLastReqTime = now; - reqpkt.m_iTimeStamp = int32_t(now - m_stats.startTime); + m_tsLastReqTime = now; + setPacketTS(reqpkt, now); m_pSndQueue->sendto(serv_addr, reqpkt); } else { - HLOGC(mglog.Debug, log << "startConnect: LOOP: too early to send - " << tdiff << " < 250000"); + HLOGC(cnlog.Debug, log << "startConnect: LOOP: too early to send - " << count_milliseconds(tdiff) << " < 250ms"); } cst = CONN_CONTINUE; response.setLength(m_iMaxSRTPayloadSize); - if (m_pRcvQueue->recvfrom(m_SocketID, Ref(response)) > 0) + if (m_pRcvQueue->recvfrom(m_SocketID, (response)) > 0) { - HLOGC(mglog.Debug, log << CONID() << "startConnect: got response for connect request"); - cst = processConnectResponse(response, &e, true /*synchro*/); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: got response for connect request"); + cst = processConnectResponse(response, &e); - HLOGC(mglog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst)); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst)); // Expected is that: // - the peer responded with URQ_INDUCTION + cookie. This above function @@ -3383,7 +3605,7 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // it means that it has done all that was required, however none of the below // things has to be done (this function will do it by itself if needed). // Otherwise the handshake rolling can be interrupted and considered complete. - cst = processRendezvous(Ref(reqpkt), response, serv_addr, true /*synchro*/, RST_OK); + cst = processRendezvous(&response, serv_addr, RST_OK, (reqpkt)); if (cst == CONN_CONTINUE) continue; break; @@ -3399,9 +3621,9 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // [[using assert(m_pCryptoControl != nullptr)]]; // new request/response should be sent out immediately on receving a response - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) << ", REQ-TIME: LOW."); - m_llLastReqTime = 0; + m_tsLastReqTime = steady_clock::time_point(); // Now serialize the handshake again to the existing buffer so that it's // then sent later in this loop. @@ -3413,7 +3635,7 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // small to store the CONCLUSION handshake (with HSv5 extensions). reqpkt.setLength(m_iMaxSRTPayloadSize); - HLOGC(mglog.Debug, log << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength()); + HLOGC(cnlog.Debug, log << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength()); // NOTE: BUGFIX: SERIALIZE AGAIN. // The original UDT code didn't do it, so it was theoretically @@ -3429,9 +3651,9 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // // Now that this is fixed, the handshake messages from RendezvousQueue // are sent only when there is a rendezvous mode or non-blocking mode. - if (!createSrtHandshake(Ref(reqpkt), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0)) + if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (reqpkt), (m_ConnReq))) { - LOGC(mglog.Error, log << "createSrtHandshake failed - REJECTING."); + LOGC(cnlog.Warn, log << "createSrtHandshake failed - REJECTING."); cst = CONN_REJECT; break; } @@ -3442,23 +3664,25 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // listener should respond with HS_VERSION_SRT1, if it is HSv5 capable. } - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "startConnect: timeout from Q:recvfrom, looping again; cst=" << ConnectStatusStr(cst)); #if ENABLE_HEAVY_LOGGING // Non-fatal assertion if (cst == CONN_REJECT) // Might be returned by processRendezvous { - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!"); break; } #endif - if (CTimer::getTime() > ttl) + if (steady_clock::now() > ttl_time) { // timeout e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0); + m_RejectReason = SRT_REJ_TIMEOUT; + HLOGC(cnlog.Debug, log << "startConnect: TTL time " << FormatTime(ttl_time) << " exceeded, TIMEOUT."); break; } } @@ -3475,13 +3699,13 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) if (e.getErrorCode() == 0) { if (m_bClosing) // if the socket is closed before connection... - e = CUDTException(MJ_SETUP); // XXX NO MN ? + e = CUDTException(MJ_SETUP, MN_CLOSED, 0); else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected { m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); e = CUDTException(MJ_SETUP, MN_REJECTED, 0); } - else if ((!m_bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check + else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check e = CUDTException(MJ_SETUP, MN_SECURITY, 0); } @@ -3494,38 +3718,39 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) throw e; } - HLOGC(mglog.Debug, log << CONID() << "startConnect: handshake exchange succeeded"); + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: handshake exchange succeeded."); // Parameters at the end. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "startConnect: END. Parameters:" " mss=" - << m_iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize() - << " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iRTT << " bw=" << m_iBandwidth); + << m_config.iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize() + << " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iSRTT << " bw=" << m_iBandwidth); } // Asynchronous connection -EConnectStatus CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT +EConnectStatus srt::CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT { EConnectStatus cst = CONN_CONTINUE; CUDTException e; - CGuard cg(m_ConnectionLock); // FIX - HLOGC(mglog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing"); - cst = processConnectResponse(pkt, &e, false); + ScopedLock cg(m_ConnectionLock); + HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing"); + cst = processConnectResponse(pkt, &e); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: response processing result: " << ConnectStatusStr(cst) - << "REQ-TIME LOW to enforce immediate response"); - m_llLastReqTime = 0; + << "; REQ-TIME LOW to enforce immediate response"); + m_tsLastReqTime = steady_clock::time_point(); return cst; } -bool CUDT::processAsyncConnectRequest(EReadStatus rst, - EConnectStatus cst, - const CPacket & response, - const sockaddr *serv_addr) +bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, + EConnectStatus cst, + const CPacket* pResponse /*[[nullable]]*/, + const sockaddr_any& serv_addr) { // IMPORTANT! @@ -3537,24 +3762,28 @@ bool CUDT::processAsyncConnectRequest(EReadStatus rst, CPacket request; request.setControl(UMSG_HANDSHAKE); request.allocate(m_iMaxSRTPayloadSize); - uint64_t now = CTimer::getTime(); - request.m_iTimeStamp = int(now - m_stats.startTime); + const steady_clock::time_point now = steady_clock::now(); + setPacketTS(request, now); - HLOGC(mglog.Debug, - log << "processAsyncConnectRequest: REQ-TIME: HIGH (" << now << "). Should prevent too quick responses."); - m_llLastReqTime = now; + HLOGC(cnlog.Debug, + log << "processAsyncConnectRequest: REQ-TIME: HIGH. Should prevent too quick responses."); + m_tsLastReqTime = now; // ID = 0, connection request - request.m_iID = !m_bRendezvous ? 0 : m_ConnRes.m_iID; + request.m_iID = !m_config.bRendezvous ? 0 : m_ConnRes.m_iID; bool status = true; + ScopedLock cg(m_ConnectionLock); + if (!m_bOpened) // Check the socket has not been closed before already. + return false; + if (cst == CONN_RENDEZVOUS) { - HLOGC(mglog.Debug, log << "processAsyncConnectRequest: passing to processRendezvous"); - cst = processRendezvous(Ref(request), response, serv_addr, false /*asynchro*/, rst); + HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: passing to processRendezvous"); + cst = processRendezvous(pResponse, serv_addr, rst, (request)); if (cst == CONN_ACCEPT) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. " "Done."); return true; @@ -3563,7 +3792,7 @@ bool CUDT::processAsyncConnectRequest(EReadStatus rst, if (cst != CONN_CONTINUE) { // processRendezvous already set the reject reason - LOGC(mglog.Error, + LOGC(cnlog.Warn, log << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further."); status = false; } @@ -3571,23 +3800,26 @@ bool CUDT::processAsyncConnectRequest(EReadStatus rst, else if (cst == CONN_REJECT) { // m_RejectReason already set at worker_ProcessAddressedPacket. - LOGC(mglog.Error, - log << "processAsyncConnectRequest: REJECT reported from HS processing, not processing further."); + LOGC(cnlog.Warn, + log << "processAsyncConnectRequest: REJECT reported from HS processing: " + << srt_rejectreason_str(m_RejectReason) + << " - not processing further"); + // m_tsLastReqTime = steady_clock::time_point(); XXX ? return false; } else { // (this procedure will be also run for HSv4 rendezvous) - HLOGC(mglog.Debug, log << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength()); - if (!createSrtHandshake(Ref(request), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0)) + HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength()); + if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (request), (m_ConnReq))) { // All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired". - LOGC(mglog.Error, log << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing."); + LOGC(cnlog.Error, log << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing."); status = false; } else { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) << " to socket " << request.m_iID << " size=" << request.getLength()); } @@ -3600,24 +3832,26 @@ bool CUDT::processAsyncConnectRequest(EReadStatus rst, // Set the version to 0 as "handshake rejection" status and serialize it CHandShake zhs; size_t size = request.getLength(); - zhs.store_to(request.m_pcData, Ref(size)); + zhs.store_to((request.m_pcData), (size)); request.setLength(size); */ } - HLOGC(mglog.Debug, log << "processAsyncConnectRequest: sending request packet, setting REQ-TIME HIGH."); - m_llLastReqTime = CTimer::getTime(); + HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: setting REQ-TIME HIGH, SENDING HS:" << m_ConnReq.show()); + m_tsLastReqTime = steady_clock::now(); m_pSndQueue->sendto(serv_addr, request); return status; } -void CUDT::cookieContest() +void srt::CUDT::cookieContest() { if (m_SrtHsSide != HSD_DRAW) return; - HLOGC(mglog.Debug, log << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie); + LOGC(cnlog.Debug, log << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie); + // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. + // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0) { // Note that it's virtually impossible that Agent's cookie is not ready, this @@ -3630,45 +3864,145 @@ void CUDT::cookieContest() // // The cookie contest must be repeated every time because it // may change the state at some point. - int better_cookie = m_ConnReq.m_iCookie - m_ConnRes.m_iCookie; - - if (better_cookie > 0) - { - m_SrtHsSide = HSD_INITIATOR; + // + // In SRT v1.4.3 and prior the below subtraction was performed in 32-bit arithmetic. + // The result of subtraction can overflow 32-bits. + // Example + // m_ConnReq.m_iCookie = -1480577720; + // m_ConnRes.m_iCookie = 811599203; + // int64_t llBetterCookie = -1480577720 - 811599203 = -2292176923 (FFFF FFFF 7760 27E5); + // int32_t iBetterCookie = 2002790373 (7760 27E5); + // + // Now 64-bit arithmetic is used to calculate the actual result of subtraction. + // The 31-st bit is then checked to check if the resulting is negative in 32-bit aritmetics. + // This way the old contest behavior is preserved, and potential compiler optimisations are avoided. + const int64_t contest = int64_t(m_ConnReq.m_iCookie) - int64_t(m_ConnRes.m_iCookie); + + if ((contest & 0xFFFFFFFF) == 0) + { + // DRAW! The only way to continue would be to force the + // cookies to be regenerated and to start over. But it's + // not worth a shot - this is an extremely rare case. + // This can simply do reject so that it can be started again. + + // Pretend then that the cookie contest wasn't done so that + // it's done again. Cookies are baked every time anew, however + // the successful initial contest remains valid no matter how + // cookies will change. + + m_SrtHsSide = HSD_DRAW; return; } - if (better_cookie < 0) + if (contest & 0x80000000) { m_SrtHsSide = HSD_RESPONDER; return; } - // DRAW! The only way to continue would be to force the - // cookies to be regenerated and to start over. But it's - // not worth a shot - this is an extremely rare case. - // This can simply do reject so that it can be started again. + m_SrtHsSide = HSD_INITIATOR; +} + +// This function should complete the data for KMX needed for an out-of-band +// handshake response. Possibilities are: +// - There's no KMX (including first responder's handshake in rendezvous). This writes 0 to w_kmdatasize. +// - The encryption status is failure. Respond with fail code and w_kmdatasize = 1. +// - The last KMX was successful. Respond with the original kmdata and their size in w_kmdatasize. +EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatasize) +{ + // If the last CONCLUSION message didn't contain the KMX extension, there's + // no key recorded yet, so it can't be extracted. Mark this w_kmdatasize empty though. + int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); + if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ)) + { + // m_pCryptoControl can be NULL if the socket has been closed already. See issue #2231. + if (!m_pCryptoControl) + { + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Error, log << "IPE: craftKmResponse needs to send KM, but CryptoControl does not exist." + << " Socket state: connected=" << boolalpha << m_bConnected << ", connecting=" << m_bConnecting + << ", broken=" << m_bBroken << ", opened " << m_bOpened << ", closing=" << m_bClosing << "."); + return CONN_REJECT; + } + // This is a periodic handshake update, so you need to extract the KM data from the + // first message, provided that it is there. + size_t msgsize = m_pCryptoControl->getKmMsg_size(0); + if (msgsize == 0) + { + switch (m_pCryptoControl->m_RcvKmState) + { + // If the KMX process ended up with a failure, the KMX is not recorded. + // In this case as the KMRSP answer the "failure status" should be crafted. + case SRT_KM_S_NOSECRET: + case SRT_KM_S_BADSECRET: + { + HLOGC(cnlog.Debug, + log << "craftKmResponse: No KMX recorded, status = " + << KmStateStr(m_pCryptoControl->m_RcvKmState) << ". Respond it."); + + // Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case, + // that is, copy the NOSECRET code into KMX message. + memcpy((aw_kmdata), &m_pCryptoControl->m_RcvKmState, sizeof(int32_t)); + w_kmdatasize = 1; + } + break; // Treat as ACCEPT in general; might change to REJECT on enforced-encryption + + default: + // Remaining values: + // UNSECURED: should not fall here at all + // SECURING: should not happen in HSv5 + // SECURED: should have received the recorded KMX correctly (getKmMsg_size(0) > 0) + { + m_RejectReason = SRT_REJ_IPE; + // Remaining situations: + // - password only on this site: shouldn't be considered to be sent to a no-password site + LOGC(cnlog.Error, + log << "craftKmResponse: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV=" + << KmStateStr(m_pCryptoControl->m_RcvKmState) + << " SND=" << KmStateStr(m_pCryptoControl->m_SndKmState)); + return CONN_REJECT; + } + break; + } + } + else + { + w_kmdatasize = msgsize / 4; + if (msgsize > w_kmdatasize * 4) + { + // Sanity check + LOGC(cnlog.Error, log << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize); + memset((aw_kmdata + (w_kmdatasize * 4)), 0, msgsize - (w_kmdatasize * 4)); + ++w_kmdatasize; + } - // Pretend then that the cookie contest wasn't done so that - // it's done again. Cookies are baked every time anew, however - // the successful initial contest remains valid no matter how - // cookies will change. + HLOGC(cnlog.Debug, + log << "craftKmResponse: getting KM DATA from the fore-recorded KMX from KMREQ, size=" + << w_kmdatasize); + memcpy((aw_kmdata), m_pCryptoControl->getKmMsg_data(0), msgsize); + } + } + else + { + HLOGC(cnlog.Debug, log << "craftKmResponse: no KMX flag - not extracting KM data for KMRSP"); + w_kmdatasize = 0; + } - m_SrtHsSide = HSD_DRAW; + return CONN_ACCEPT; } -EConnectStatus CUDT::processRendezvous( - ref_t reqpkt, const CPacket &response, const sockaddr *serv_addr, bool synchro, EReadStatus rst) +EConnectStatus srt::CUDT::processRendezvous( + const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr, + EReadStatus rst, CPacket& w_reqpkt) { if (m_RdvState == CHandShake::RDV_CONNECTED) { - HLOGC(mglog.Debug, log << "processRendezvous: already in CONNECTED state."); + HLOGC(cnlog.Debug, log << "processRendezvous: already in CONNECTED state."); return CONN_ACCEPT; } uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; - CPacket &rpkt = *reqpkt; cookieContest(); @@ -3678,7 +4012,7 @@ EConnectStatus CUDT::processRendezvous( if (m_SrtHsSide == HSD_DRAW) { m_RejectReason = SRT_REJ_RDVCOOKIE; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute."); return CONN_REJECT; } @@ -3692,11 +4026,11 @@ EConnectStatus CUDT::processRendezvous( int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); bool needs_extension = ext_flags != 0; // Initial value: received HS has extensions. bool needs_hsrsp; - rendezvousSwitchState(Ref(rsp_type), Ref(needs_extension), Ref(needs_hsrsp)); + rendezvousSwitchState((rsp_type), (needs_extension), (needs_hsrsp)); if (rsp_type > URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(rsp_type); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type)); return CONN_REJECT; } @@ -3710,14 +4044,19 @@ EConnectStatus CUDT::processRendezvous( m_ConnReq.m_iReqType = rsp_type; m_ConnReq.m_extension = needs_extension; - // This must be done before prepareConnectionObjects(). - applyResponseSettings(); + // This must be done before prepareConnectionObjects(), because it sets ISN and m_iMaxSRTPayloadSize needed to create buffers. + if (!applyResponseSettings()) + { + LOGC(cnlog.Error, log << "processRendezvous: rogue peer"); + return CONN_REJECT; + } - // This must be done before interpreting and creating HSv5 extensions. - if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, 0)) + // The CryptoControl must be created by the prepareConnectionObjects() before interpreting and creating HSv5 extensions + // because the it will be used there. + if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, NULL)) { // m_RejectReason already handled - HLOGC(mglog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); + HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); return CONN_REJECT; } @@ -3730,109 +4069,51 @@ EConnectStatus CUDT::processRendezvous( { // We have JUST RECEIVED packet in this session (not that this is called as periodic update). // Sanity check - m_llLastReqTime = 0; - if (response.getLength() == size_t(-1)) + m_tsLastReqTime = steady_clock::time_point(); + if (!pResponse || pResponse->getLength() == size_t(-1)) { m_RejectReason = SRT_REJ_IPE; - LOGC(mglog.Fatal, + LOGC(cnlog.Fatal, log << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)"); return CONN_REJECT; } - if (!interpretSrtHandshake(m_ConnRes, response, kmdata, &kmdatasize)) + if (!interpretSrtHandshake(m_ConnRes, *pResponse, kmdata, &kmdatasize)) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW."); return CONN_REJECT; } + updateAfterSrtHandshake(HS_VERSION_SRT1); + // Pass on, inform about the shortened response-waiting period. - HLOGC(mglog.Debug, log << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately."); + HLOGC(cnlog.Debug, log << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately."); } else { - // If the last CONCLUSION message didn't contain the KMX extension, there's - // no key recorded yet, so it can't be extracted. Mark this kmdatasize empty though. - int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); - if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ)) - { - // This is a periodic handshake update, so you need to extract the KM data from the - // first message, provided that it is there. - size_t msgsize = m_pCryptoControl->getKmMsg_size(0); - if (msgsize == 0) - { - switch (m_pCryptoControl->m_RcvKmState) - { - // If the KMX process ended up with a failure, the KMX is not recorded. - // In this case as the KMRSP answer the "failure status" should be crafted. - case SRT_KM_S_NOSECRET: - case SRT_KM_S_BADSECRET: - { - HLOGC(mglog.Debug, - log << "processRendezvous: No KMX recorded, status = NOSECRET. Respond with NOSECRET."); - - // Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case, - // that is, copy the NOSECRET code into KMX message. - memcpy(kmdata, &m_pCryptoControl->m_RcvKmState, sizeof(int32_t)); - kmdatasize = 1; - } - break; - - default: - // Remaining values: - // UNSECURED: should not fall here at alll - // SECURING: should not happen in HSv5 - // SECURED: should have received the recorded KMX correctly (getKmMsg_size(0) > 0) - { - m_RejectReason = SRT_REJ_IPE; - // Remaining situations: - // - password only on this site: shouldn't be considered to be sent to a no-password site - LOGC(mglog.Error, - log << "processRendezvous: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV=" - << KmStateStr(m_pCryptoControl->m_RcvKmState) - << " SND=" << KmStateStr(m_pCryptoControl->m_SndKmState)); - return CONN_REJECT; - } - break; - } - } - else - { - kmdatasize = msgsize / 4; - if (msgsize > kmdatasize * 4) - { - // Sanity check - LOGC(mglog.Error, log << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize); - memset(kmdata + (kmdatasize * 4), 0, msgsize - (kmdatasize * 4)); - ++kmdatasize; - } - - HLOGC(mglog.Debug, - log << "processRendezvous: getting KM DATA from the fore-recorded KMX from KMREQ, size=" - << kmdatasize); - memcpy(kmdata, m_pCryptoControl->getKmMsg_data(0), msgsize); - } - } - else - { - HLOGC(mglog.Debug, log << "processRendezvous: no KMX flag - not extracting KM data for KMRSP"); - kmdatasize = 0; - } + // This is a repeated handshake, so you can't use the incoming data to + // prepare data for createSrtHandshake. They have to be extracted from inside. + EConnectStatus conn = craftKmResponse((kmdata), (kmdatasize)); + if (conn != CONN_ACCEPT) + return conn; } // No matter the value of needs_extension, the extension is always needed // when HSREQ was interpreted (to store HSRSP extension). m_ConnReq.m_extension = true; - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize); - rpkt.setLength(m_iMaxSRTPayloadSize); - if (!createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize)) + w_reqpkt.setLength(m_iMaxSRTPayloadSize); + if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, + kmdata, kmdatasize, + (w_reqpkt), (m_ConnReq))) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW"); - m_llLastReqTime = 0; + m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } @@ -3850,18 +4131,18 @@ EConnectStatus CUDT::processRendezvous( // not be done in case of rendezvous. The section in postConnect() is // predicted to run only in regular CALLER handling. - if (rst != RST_OK || response.getLength() == size_t(-1)) + if (rst != RST_OK || !pResponse || pResponse->getLength() == size_t(-1)) { // Actually the -1 length would be an IPE, but it's likely that this was reported already. HLOGC( - mglog.Debug, + cnlog.Debug, log << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)"); } else { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"); - if (!interpretSrtHandshake(m_ConnRes, response, 0, 0)) + if (!interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0)) { // m_RejectReason is already set, so set the reqtype accordingly m_ConnReq.m_iReqType = URQFailure(m_RejectReason); @@ -3870,23 +4151,24 @@ EConnectStatus CUDT::processRendezvous( // This should be false, make a kinda assert here. if (needs_extension) { - LOGC(mglog.Fatal, log << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS"); + LOGC(cnlog.Fatal, log << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS"); m_ConnReq.m_extension = false; } + updateAfterSrtHandshake(HS_VERSION_SRT1); } - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: COOKIES Agent/Peer: " << m_ConnReq.m_iCookie << "/" << m_ConnRes.m_iCookie << " HSD:" << (m_SrtHsSide == HSD_INITIATOR ? "initiator" : "responder") << " STATE:" << CHandShake::RdvStateStr(m_RdvState) << " ..."); if (rsp_type == URQ_DONE) { - HLOGC(mglog.Debug, log << "... WON'T SEND any response, both sides considered connected"); + HLOGC(cnlog.Debug, log << "... WON'T SEND any response, both sides considered connected"); } else { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "... WILL SEND " << RequestTypeStr(rsp_type) << " " << (m_ConnReq.m_extension ? "with" : "without") << " SRT HS extensions"); } @@ -3897,17 +4179,14 @@ EConnectStatus CUDT::processRendezvous( // serialization. m_ConnReq.m_extension = needs_extension; - rpkt.setLength(m_iMaxSRTPayloadSize); + w_reqpkt.setLength(m_iMaxSRTPayloadSize); if (m_RdvState == CHandShake::RDV_CONNECTED) { - // When synchro=false, don't lock a mutex for rendezvous queue. - // This is required when this function is called in the - // receive queue worker thread - it would lock itself. - int cst = postConnect(response, true, 0, synchro); + int cst = postConnect(pResponse, true, 0); if (cst == CONN_REJECT) { // m_RejectReason already set - HLOGC(mglog.Debug, log << "processRendezvous: rejecting due to problems in postConnect."); + HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in postConnect."); return CONN_REJECT; } } @@ -3918,7 +4197,7 @@ EConnectStatus CUDT::processRendezvous( // this time with URQ_AGREEMENT message, but still consider yourself connected. if (rsp_type == URQ_DONE) { - HLOGC(mglog.Debug, log << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)"); + HLOGC(cnlog.Debug, log << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)"); return CONN_ACCEPT; } @@ -3928,11 +4207,12 @@ EConnectStatus CUDT::processRendezvous( // needs_extension here distinguishes between cases 1 and 3. // NOTE: in case when interpretSrtHandshake was run under the conditions above (to interpret HSRSP), // then createSrtHandshake below will create only empty AGREEMENT message. - if (!createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0)) + if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, + (w_reqpkt), (m_ConnReq))) { // m_RejectReason already set - LOGC(mglog.Error, log << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW"); - m_llLastReqTime = 0; + LOGC(cnlog.Warn, log << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW"); + m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } @@ -3952,14 +4232,13 @@ EConnectStatus CUDT::processRendezvous( // catalyzer here and may turn the entity on the right track faster. When // AGREEMENT is missed, it may have kinda initial tearing. - const uint64_t now = CTimer::getTime(); - m_llLastReqTime = now; - rpkt.m_iTimeStamp = int32_t(now - m_stats.startTime); - HLOGC(mglog.Debug, - log << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH (" - << now << ")."); + const steady_clock::time_point now = steady_clock::now(); + m_tsLastReqTime = now; + setPacketTS(w_reqpkt, now); + HLOGC(cnlog.Debug, + log << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH."); - m_pSndQueue->sendto(serv_addr, rpkt); + m_pSndQueue->sendto(serv_addr, w_reqpkt); return CONN_ACCEPT; } @@ -3967,19 +4246,20 @@ EConnectStatus CUDT::processRendezvous( if (rst == RST_OK) { // the request time must be updated so that the next handshake can be sent out immediately - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType) << " REQ-TIME: LOW to send immediately, consider yourself conencted"); - m_llLastReqTime = 0; + m_tsLastReqTime = steady_clock::time_point(); } else { - HLOGC(mglog.Debug, log << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected"); + HLOGC(cnlog.Debug, log << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected"); } return CONN_CONTINUE; } -EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTException *eout, bool synchro) ATR_NOEXCEPT +// [[using locked(m_ConnectionLock)]]; +EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTException* eout) ATR_NOEXCEPT { // NOTE: ASSUMED LOCK ON: m_ConnectionLock. @@ -3994,7 +4274,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // This is required in HSv5 rendezvous, in which it should send the URQ_AGREEMENT message to // the peer, however switch to connected state. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processConnectResponse: TYPE:" << (response.isControl() ? MessageTypeStr(response.getType(), response.getExtendedType()) : string("DATA"))); @@ -4003,7 +4283,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // For HSv4, the data sender is INITIATOR, and the data receiver is RESPONDER, // regardless of the connecting side affiliation. This will be changed for HSv5. bool bidirectional = false; - HandshakeSide hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER; + HandshakeSide hsd = m_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; // (defined here due to 'goto' below). // SRT peer may send the SRT handshake private message (type 0x7fff) before a keep-alive. @@ -4017,7 +4297,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // For the initial form this value should not be checked. bool hsv5 = m_ConnRes.m_iVersion >= HS_VERSION_SRT1; - if (m_bRendezvous && + if (m_config.bRendezvous && (m_RdvState == CHandShake::RDV_CONNECTED // somehow Rendezvous-v5 switched it to CONNECTED. || !response.isControl() // WAS A PAYLOAD PACKET. || (response.getType() == UMSG_KEEPALIVE) // OR WAS A UMSG_KEEPALIVE message. @@ -4032,13 +4312,13 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // a data packet or a keep-alive packet comes, which means the peer side is already connected // in this situation, the previously recorded response will be used // In HSv5 this situation is theoretically possible if this party has missed the URQ_AGREEMENT message. - HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in"); + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in"); if (hsv5) { m_RdvState = CHandShake::RDV_CONNECTED; } - return postConnect(response, hsv5, eout, synchro); + return postConnect(&response, hsv5, eout); } if (!response.isControl(UMSG_HANDSHAKE)) @@ -4046,11 +4326,11 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti m_RejectReason = SRT_REJ_ROGUE; if (!response.isControl()) { - LOGC(mglog.Error, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected"); + LOGC(cnlog.Warn, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected"); } else { - LOGC(mglog.Error, + LOGC(cnlog.Error, log << CONID() << "processConnectResponse: CONFUSED: expected UMSG_HANDSHAKE as connection not yet established, " "got: " @@ -4063,13 +4343,13 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti { m_RejectReason = SRT_REJ_ROGUE; // Handshake data were too small to reach the Handshake structure. Reject. - LOGC(mglog.Error, + LOGC(cnlog.Error, log << CONID() << "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting."); return CONN_REJECT; } - HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show()); + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show()); if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); @@ -4081,7 +4361,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // Yes, we do abort to prevent buffer overrun. Set your MSS correctly // and you'll avoid problems. m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Fatal, log << "MSS size " << m_iMSS << "exceeds MTU size!"); + LOGC(cnlog.Fatal, log << "MSS size " << m_config.iMSS << "exceeds MTU size!"); return CONN_REJECT; } @@ -4090,13 +4370,13 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // The CCryptoControl attached object must be created early // because it will be required to create a conclusion handshake in HSv5 // - if (m_bRendezvous) + if (m_config.bRendezvous) { // SANITY CHECK: A rendezvous socket should reject any caller requests (it's not a listener) if (m_ConnRes.m_iReqType == URQ_INDUCTION) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, + LOGC(cnlog.Error, log << CONID() << "processConnectResponse: Rendezvous-point received INDUCTION handshake (expected WAVEAHAND). " "Rejecting."); @@ -4108,18 +4388,18 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti if (m_ConnRes.m_iVersion > HS_VERSION_UDT4) { - HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED."); return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous(). } - HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendsezvous HSv4 DETECTED."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendsezvous HSv4 DETECTED."); // So, here it has either received URQ_WAVEAHAND handshake message (while it should be in URQ_WAVEAHAND itself) // or it has received URQ_CONCLUSION/URQ_AGREEMENT message while this box has already sent URQ_WAVEAHAND to the // peer, and DID NOT send the URQ_CONCLUSION yet. if (m_ConnReq.m_iReqType == URQ_WAVEAHAND || m_ConnRes.m_iReqType == URQ_WAVEAHAND) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW. got HS RDV. Agent state:" << RequestTypeStr(m_ConnReq.m_iReqType) << " Peer HS:" << m_ConnRes.show()); @@ -4128,23 +4408,23 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // For HSv5, make the cookie contest and basing on this decide, which party // should provide the HSREQ/KMREQ attachment. - if (!createCrypter(hsd, false /* unidirectional */)) - { - m_RejectReason = SRT_REJ_RESOURCE; - m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE); - // the request time must be updated so that the next handshake can be sent out immediately. - m_llLastReqTime = 0; - return CONN_REJECT; - } + if (!createCrypter(hsd, false /* unidirectional */)) + { + m_RejectReason = SRT_REJ_RESOURCE; + m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE); + // the request time must be updated so that the next handshake can be sent out immediately. + m_tsLastReqTime = steady_clock::time_point(); + return CONN_REJECT; + } m_ConnReq.m_iReqType = URQ_CONCLUSION; // the request time must be updated so that the next handshake can be sent out immediately. - m_llLastReqTime = 0; + m_tsLastReqTime = steady_clock::time_point(); return CONN_CONTINUE; } else { - HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand"); + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand"); } } else @@ -4152,7 +4432,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // set cookie if (m_ConnRes.m_iReqType == URQ_INDUCTION) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" << hex << m_ConnRes.m_iCookie << " version:" << dec << m_ConnRes.m_iVersion << "), sending CONCLUSION HS with this cookie"); @@ -4168,7 +4448,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti if (hs_flags != SrtHSRequest::SRT_MAGIC_CODE) { - LOGC(mglog.Warn, log << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE"); + LOGC(cnlog.Warn, log << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE"); } checkUpdateCryptoKeyLen("processConnectResponse", m_ConnRes.m_iType); @@ -4189,12 +4469,14 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti m_ConnReq.m_extension = true; // For HSv5, the caller is INITIATOR and the listener is RESPONDER. - // The m_bDataSender value should be completely ignored and the + // The m_config.bDataSender value should be completely ignored and the // connection is always bidirectional. bidirectional = true; hsd = HSD_INITIATOR; + m_SrtHsSide = hsd; } - m_llLastReqTime = 0; + + m_tsLastReqTime = steady_clock::time_point(); if (!createCrypter(hsd, bidirectional)) { m_RejectReason = SRT_REJ_RESOURCE; @@ -4206,43 +4488,63 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti } } - return postConnect(response, false, eout, synchro); + return postConnect(&response, false, eout); } -void CUDT::applyResponseSettings() +bool srt::CUDT::applyResponseSettings() ATR_NOEXCEPT { + if (!m_ConnRes.valid()) + { + LOGC(cnlog.Error, log << "applyResponseSettings: ROGUE HANDSHAKE - rejecting"); + m_RejectReason = SRT_REJ_ROGUE; + return false; + } + // Re-configure according to the negotiated values. - m_iMSS = m_ConnRes.m_iMSS; + m_config.iMSS = m_ConnRes.m_iMSS; m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; - int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE; + const int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; m_iPeerISN = m_ConnRes.m_iISN; - m_iRcvLastAck = m_ConnRes.m_iISN; -#ifdef ENABLE_LOGGING - m_iDebugPrevLastAck = m_iRcvLastAck; -#endif - m_iRcvLastSkipAck = m_iRcvLastAck; - m_iRcvLastAckAck = m_ConnRes.m_iISN; - m_iRcvCurrSeqNo = m_ConnRes.m_iISN - 1; - m_iRcvCurrPhySeqNo = m_ConnRes.m_iISN - 1; + + setInitialRcvSeq(m_iPeerISN); + + m_iRcvCurrPhySeqNo = CSeqNo::decseq(m_ConnRes.m_iISN); m_PeerID = m_ConnRes.m_iID; - memcpy(m_piSelfIP, m_ConnRes.m_piPeerIP, 16); + memcpy((m_piSelfIP), m_ConnRes.m_piPeerIP, sizeof m_piSelfIP); - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " isn=" << m_ConnRes.m_iISN << " peerID=" << m_ConnRes.m_iID); + + return true; } -EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTException *eout, bool synchro) +EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, CUDTException *eout) ATR_NOEXCEPT { if (m_ConnRes.m_iVersion < HS_VERSION_SRT1) - m_ullRcvPeerStartTime = 0; // will be set correctly in SRT HS. + m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly in SRT HS. // This procedure isn't being executed in rendezvous because // in rendezvous it's completed before calling this function. if (!rendezvous) { + // The "local storage depleted" case shouldn't happen here, but + // this is a theoretical path that needs prevention. + bool ok = pResponse; + if (!ok) + { + m_RejectReason = SRT_REJ_IPE; + if (eout) + { + *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); + } + return CONN_REJECT; + } + + // [[assert (pResponse != NULL)]]; + // NOTE: THIS function must be called before calling prepareConnectionObjects. // The reason why it's not part of prepareConnectionObjects is that the activities // done there are done SIMILAR way in acceptAndRespond, which also calls this @@ -4254,35 +4556,71 @@ EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTE // // Currently just this function must be called always BEFORE prepareConnectionObjects // everywhere except acceptAndRespond(). - applyResponseSettings(); + ok = applyResponseSettings(); // This will actually be done also in rendezvous HSv4, // however in this case the HSREQ extension will not be attached, // so it will simply go the "old way". - bool ok = prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout); + // (&&: skip if failed already) + // Must be called before interpretSrtHandshake() to create the CryptoControl. + ok = ok && prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout); + // May happen that 'response' contains a data packet that was sent in rendezvous mode. // In this situation the interpretation of handshake was already done earlier. - if (ok && response.isControl()) + ok = ok && pResponse->isControl(); + ok = ok && interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0); + + if (!ok) { - ok = interpretSrtHandshake(m_ConnRes, response, 0, 0); - if (!ok && eout) + if (eout) { *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); } - } - if (!ok) // m_RejectReason already set + // m_RejectReason already set return CONN_REJECT; + } + } + + bool have_group = false; + + { +#if ENABLE_BONDING + ScopedLock cl (uglobal().m_GlobControlLock); + CUDTGroup* g = m_parent->m_GroupOf; + if (g) + { + // This is the last moment when this can be done. + // The updateAfterSrtHandshake call will copy the receiver + // start time to the receiver buffer data, so the correct + // value must be set before this happens. + synchronizeWithGroup(g); + have_group = true; + } +#endif + } + + if (!have_group) + { + // This function will be called internally inside + // synchronizeWithGroup(). This is just more complicated. + updateAfterSrtHandshake(m_ConnRes.m_iVersion); } CInfoBlock ib; - ib.m_iIPversion = m_iIPversion; - CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP); + ib.m_iIPversion = m_PeerAddr.family(); + CInfoBlock::convert(m_PeerAddr, ib.m_piIP); if (m_pCache->lookup(&ib) >= 0) { - m_iRTT = ib.m_iRTT; + m_iSRTT = ib.m_iSRTT; + m_iRTTVar = ib.m_iSRTT / 2; m_iBandwidth = ib.m_iBandwidth; } +#if SRT_DEBUG_RTT + s_rtt_trace.trace(steady_clock::now(), "Connect", -1, -1, + m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); +#endif + SRT_REJECT_REASON rr = setupCC(); if (rr != SRT_REJ_UNKNOWN) { @@ -4292,11 +4630,23 @@ EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTE // And, I am connected too. m_bConnecting = false; - m_bConnected = true; - // register this socket for receiving data packets - m_pRNode->m_bOnList = true; - m_pRcvQueue->setNewEntry(this); + // The lock on m_ConnectionLock should still be applied, but + // the socket could have been started removal before this function + // has started. Do a sanity check before you continue with the + // connection process. + CUDTSocket* s = uglobal().locateSocket(m_SocketID); + if (s) + { + // The socket could be closed at this very moment. + // Continue with removing the socket from the pending structures, + // but prevent it from setting it as connected. + m_bConnected = true; + + // register this socket for receiving data packets + m_pRNode->m_bOnList = true; + m_pRcvQueue->setNewEntry(this); + } // XXX Problem around CONN_CONFUSED! // If some too-eager packets were received from a listener @@ -4318,18 +4668,17 @@ EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTE // because otherwise the packets that are coming for this socket before the // connection process is complete will be rejected as "attack", instead of // being enqueued for later pickup from the queue. - m_pRcvQueue->removeConnector(m_SocketID, synchro); + m_pRcvQueue->removeConnector(m_SocketID); - // acknowledge the management module. - CUDTSocket* s = s_UDTUnited.locate(m_SocketID); + // Ok, no more things to be done as per "clear connecting state" if (!s) { + LOGC(cnlog.Error, log << "Connection broken in the process - socket @" << m_SocketID << " closed"); + m_RejectReason = SRT_REJ_CLOSE; if (eout) { - *eout = CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + *eout = CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - - m_RejectReason = SRT_REJ_CLOSE; return CONN_REJECT; } @@ -4337,20 +4686,66 @@ EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTE // the local port must be correctly assigned BEFORE CUDT::startConnect(), // otherwise if startConnect() fails, the multiplexer cannot be located // by garbage collection and will cause leak - s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr); - CIPAddress::pton(s->m_pSelfAddr, s->m_pUDT->m_piSelfIP, s->m_iIPversion); + s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); + CIPAddress::pton((s->m_SelfAddr), s->core().m_piSelfIP, m_PeerAddr); + + //int token = -1; +#if ENABLE_BONDING + { + ScopedLock cl (uglobal().m_GlobControlLock); + CUDTGroup* g = m_parent->m_GroupOf; + if (g) + { + // XXX this might require another check of group type. + // For redundancy group, at least, update the status in the group. + + // LEAVING as comment for historical reasons. Locking is here most + // likely not necessary because the socket cannot be removed from the + // group until the socket isn't removed, and this requires locking of + // m_GlobControlLock. This should ensure that when m_GroupOf is + // not NULL, m_GroupMemberData is also valid. + // ScopedLock glock(g->m_GroupLock); + + HLOGC(cnlog.Debug, log << "group: Socket @" << m_parent->m_SocketID << " fresh connected, setting IDLE"); + + groups::SocketData* gi = m_parent->m_GroupMemberData; + gi->sndstate = SRT_GST_IDLE; + gi->rcvstate = SRT_GST_IDLE; + gi->laststatus = SRTS_CONNECTED; + //token = gi->token; + g->setGroupConnected(); + } + } +#endif s->m_Status = SRTS_CONNECTED; // acknowledde any waiting epolls to write - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_CONNECT, true); + + CGlobEvent::triggerEvent(); + +/* XXX Likely it should NOT be called here for two reasons: + + - likely lots of mutexes are locked here so any + API call from here might cause a deadlock + - if called from an asynchronous connection process, it was + already called from inside updateConnStatus + - if called from startConnect (synchronous mode), it is even wrong. + + if (m_cbConnectHook) + { + CALLBACK_CALL(m_cbConnectHook, m_SocketID, SRT_SUCCESS, m_PeerAddr.get(), token); + } - LOGC(mglog.Note, log << "Connection established to: " << SockaddrToString(m_pPeerAddr)); + */ + + LOGC(cnlog.Note, log << CONID() << "Connection established to: " << m_PeerAddr.str()); return CONN_ACCEPT; } -void CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield) +void srt::CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield) { int enc_flags = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(typefield); @@ -4360,42 +4755,43 @@ void CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t ty if (enc_flags >= 2 && enc_flags <= 4) // 2 = 128, 3 = 192, 4 = 256 { int rcv_pbkeylen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(enc_flags); - if (m_iSndCryptoKeyLen == 0) + if (m_config.iSndCryptoKeyLen == 0) { - m_iSndCryptoKeyLen = rcv_pbkeylen; - HLOGC(mglog.Debug, log << loghdr << ": PBKEYLEN adopted from advertised value: " << m_iSndCryptoKeyLen); + m_config.iSndCryptoKeyLen = rcv_pbkeylen; + HLOGC(cnlog.Debug, log << loghdr << ": PBKEYLEN adopted from advertised value: " + << m_config.iSndCryptoKeyLen); } - else if (m_iSndCryptoKeyLen != rcv_pbkeylen) + else if (m_config.iSndCryptoKeyLen != rcv_pbkeylen) { // Conflict. Use SRTO_SENDER flag to check if this side should accept // the enforcement, otherwise simply let it win. - if (!m_bDataSender) + if (!m_config.bDataSender) { - LOGC(mglog.Warn, - log << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_iSndCryptoKeyLen << " by " + LOGC(cnlog.Warn, + log << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_config.iSndCryptoKeyLen << " by " << rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)"); - m_iSndCryptoKeyLen = rcv_pbkeylen; + m_config.iSndCryptoKeyLen = rcv_pbkeylen; } else { - LOGC(mglog.Warn, - log << loghdr << ": PBKEYLEN conflict - keep " << m_iSndCryptoKeyLen + LOGC(cnlog.Warn, + log << loghdr << ": PBKEYLEN conflict - keep " << m_config.iSndCryptoKeyLen << "; peer-advertised PBKEYLEN " << rcv_pbkeylen << " rejected because Agent is SRTO_SENDER"); } } } else if (enc_flags != 0) { - LOGC(mglog.Error, log << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags); + LOGC(cnlog.Error, log << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags); } else { - HLOGC(mglog.Debug, log << loghdr << ": No encryption flags found in type field: " << typefield); + HLOGC(cnlog.Debug, log << loghdr << ": No encryption flags found in type field: " << typefield); } } // Rendezvous -void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t needs_extension, ref_t needs_hsrsp) +void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_extension, bool& w_needs_hsrsp) { UDTRequestType req = m_ConnRes.m_iReqType; int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); @@ -4434,14 +4830,14 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // (actually to RDV_ATTENTION). There's also no exit to RDV_FINE from RDV_ATTENTION. // DEFAULT STATEMENT: don't attach extensions to URQ_CONCLUSION, neither HSREQ nor HSRSP. - *needs_extension = false; - *needs_hsrsp = false; + w_needs_extension = false; + w_needs_hsrsp = false; string reason; #if ENABLE_HEAVY_LOGGING - HLOGC(mglog.Debug, log << "rendezvousSwitchState: HS: " << m_ConnRes.show()); + HLOGC(cnlog.Debug, log << "rendezvousSwitchState: HS: " << m_ConnRes.show()); struct LogAtTheEnd { @@ -4455,14 +4851,14 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need ~LogAtTheEnd() { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "rendezvousSwitchState: STATE[" << CHandShake::RdvStateStr(ost) << "->" << CHandShake::RdvStateStr(nst) << "] REQTYPE[" << RequestTypeStr(orq) << "->" << RequestTypeStr(nrq) << "] " << "ext:" << (needext ? (needrsp ? "HSRSP" : "HSREQ") : "NONE") << (reason == "" ? string() : "reason:" + reason)); } - } l_logend = {m_RdvState, req, m_RdvState, *rsptype, *needs_extension, *needs_hsrsp, reason}; + } l_logend = {m_RdvState, req, m_RdvState, w_rsptype, w_needs_extension, w_needs_hsrsp, reason}; #endif @@ -4478,22 +4874,22 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need m_RdvState = CHandShake::RDV_ATTENTION; // NOTE: if this->isWinner(), attach HSREQ - *rsptype = URQ_CONCLUSION; + w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) - *needs_extension = true; + w_needs_extension = true; return; } if (req == URQ_CONCLUSION) { m_RdvState = CHandShake::RDV_FINE; - *rsptype = URQ_CONCLUSION; + w_rsptype = URQ_CONCLUSION; - *needs_extension = true; // (see below - this needs to craft either HSREQ or HSRSP) + w_needs_extension = true; // (see below - this needs to craft either HSREQ or HSRSP) // if this->isWinner(), then craft HSREQ for that response. // if this->isLoser(), then this packet should bring HSREQ, so craft HSRSP for the response. if (hsd == HSD_RESPONDER) - *needs_hsrsp = true; + w_needs_hsrsp = true; return; } } @@ -4509,9 +4905,9 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // agent has switched to ATTENTION state and continues sending // waveahands. In this case, just remain in ATTENTION state and // retry with URQ_CONCLUSION, as normally. - *rsptype = URQ_CONCLUSION; + w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) - *needs_extension = true; + w_needs_extension = true; return; } @@ -4527,15 +4923,15 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need if (hs_flags == 0) { HLOGC( - mglog.Debug, + cnlog.Debug, log << "rendezvousSwitchState: " "{INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got CONCLUSION, remain in [ATTENTION]"); - *rsptype = URQ_CONCLUSION; - *needs_extension = true; // If you expect to receive HSRSP, continue sending HSREQ + w_rsptype = URQ_CONCLUSION; + w_needs_extension = true; // If you expect to receive HSRSP, continue sending HSREQ return; } m_RdvState = CHandShake::RDV_CONNECTED; - *rsptype = URQ_AGREEMENT; + w_rsptype = URQ_AGREEMENT; return; } @@ -4547,25 +4943,25 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need if (hs_flags == 0) { LOGC( - mglog.Warn, + cnlog.Warn, log << "rendezvousSwitchState: (IPE!)" "{RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got CONCLUSION, remain in [ATTENTION]"); - *rsptype = URQ_CONCLUSION; - *needs_extension = false; // If you received WITHOUT extensions, respond WITHOUT extensions (wait - // for the right message) + w_rsptype = URQ_CONCLUSION; + w_needs_extension = false; // If you received WITHOUT extensions, respond WITHOUT extensions (wait + // for the right message) return; } m_RdvState = CHandShake::RDV_INITIATED; - *rsptype = URQ_CONCLUSION; - *needs_extension = true; - *needs_hsrsp = true; + w_rsptype = URQ_CONCLUSION; + w_needs_extension = true; + w_needs_hsrsp = true; return; } - LOGC(mglog.Error, log << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state."); + LOGC(cnlog.Error, log << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state."); // Fallback for cookie draw m_RdvState = CHandShake::RDV_INVALID; - *rsptype = URQFailure(SRT_REJ_RDVCOOKIE); + w_rsptype = URQFailure(SRT_REJ_RDVCOOKIE); return; } @@ -4584,7 +4980,7 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need m_RdvState = CHandShake::RDV_CONNECTED; // Both sides are connected, no need to send anything anymore. - *rsptype = URQ_DONE; + w_rsptype = URQ_DONE; return; } @@ -4594,15 +4990,15 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // we have to request this once again. Send URQ_CONCLUSION in order to // inform the other party that we need the conclusion message once again. // The ATTENTION state should be maintained. - *rsptype = URQ_CONCLUSION; - *needs_extension = true; - *needs_hsrsp = true; + w_rsptype = URQ_CONCLUSION; + w_needs_extension = true; + w_needs_hsrsp = true; return; } } } - reason = "ATTENTION -> WAVEAHAND(conclusion), CONCLUSION(agreement/conclusion), AGREEMENT (done/conclusion)"; - break; + reason = "ATTENTION -> WAVEAHAND(conclusion), CONCLUSION(agreement/conclusion), AGREEMENT (done/conclusion)"; + break; case CHandShake::RDV_FINE: { @@ -4626,7 +5022,7 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need { // Received REPEATED empty conclusion that has initially switched it into FINE state. // To exit FINE state we need the CONCLUSION message with HSRSP. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "rendezvousSwitchState: {INITIATOR}[FINE] rsptype, ref_t need // it to FINE state. That CONCLUSION message should have contained extension, // so if this is a repeated CONCLUSION+HSREQ, it should be responded with // CONCLUSION+HSRSP. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "rendezvousSwitchState: {RESPONDER}[FINE] rsptype, ref_t need if (!correct_switch) { - *rsptype = URQ_CONCLUSION; + w_rsptype = URQ_CONCLUSION; // initiator should send HSREQ, responder HSRSP, // in both cases extension is needed - *needs_extension = true; - *needs_hsrsp = hsd == HSD_RESPONDER; + w_needs_extension = true; + w_needs_hsrsp = hsd == HSD_RESPONDER; return; } m_RdvState = CHandShake::RDV_CONNECTED; - *rsptype = URQ_AGREEMENT; + w_rsptype = URQ_AGREEMENT; return; } @@ -4670,7 +5066,7 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // This will be dispatched in the main loop and discarded. m_RdvState = CHandShake::RDV_CONNECTED; - *rsptype = URQ_DONE; + w_rsptype = URQ_DONE; return; } } @@ -4686,14 +5082,14 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // No matter in which state we'd be, just switch to connected. if (m_RdvState == CHandShake::RDV_CONNECTED) { - HLOGC(mglog.Debug, log << "<-- AGREEMENT: already connected"); + HLOGC(cnlog.Debug, log << "<-- AGREEMENT: already connected"); } else { - HLOGC(mglog.Debug, log << "<-- AGREEMENT: switched to connected"); + HLOGC(cnlog.Debug, log << "<-- AGREEMENT: switched to connected"); } m_RdvState = CHandShake::RDV_CONNECTED; - *rsptype = URQ_DONE; + w_rsptype = URQ_DONE; return; } @@ -4702,15 +5098,15 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // Receiving conclusion in this state means that the other party // didn't get our conclusion, so send it again, the same as when // exiting the ATTENTION state. - *rsptype = URQ_CONCLUSION; + w_rsptype = URQ_CONCLUSION; if (hsd == HSD_RESPONDER) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "rendezvousSwitchState: " "{RESPONDER}[INITIATED] awaits AGREEMENT, " "got CONCLUSION, sending CONCLUSION+HSRSP"); - *needs_extension = true; - *needs_hsrsp = true; + w_needs_extension = true; + w_needs_hsrsp = true; return; } @@ -4720,72 +5116,293 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // HSREQ, and set responding HSRSP in that case. if (hs_flags == 0) { - HLOGC(mglog.Debug, - log << "rendezvousSwitchState: " - "{INITIATOR}[INITIATED] awaits AGREEMENT, " - "got empty CONCLUSION, STILL RESPONDING CONCLUSION+HSRSP"); + HLOGC(cnlog.Debug, + log << "rendezvousSwitchState: " + "{INITIATOR}[INITIATED] awaits AGREEMENT, " + "got empty CONCLUSION, STILL RESPONDING CONCLUSION+HSRSP"); + } + else + { + + HLOGC(cnlog.Debug, + log << "rendezvousSwitchState: " + "{INITIATOR}[INITIATED] awaits AGREEMENT, " + "got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP"); + } + w_needs_extension = true; + w_needs_hsrsp = true; + return; + } + } + + reason = "INITIATED -> AGREEMENT(done)"; + break; + + case CHandShake::RDV_CONNECTED: + // Do nothing. This theoretically should never happen. + w_rsptype = URQ_DONE; + return; + } + + HLOGC(cnlog.Debug, log << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID"); + // All others are treated as errors + m_RdvState = CHandShake::RDV_WAVING; + w_rsptype = URQFailure(SRT_REJ_ROGUE); +} + +/* + * Timestamp-based Packet Delivery (TsbPd) thread + * This thread runs only if TsbPd mode is enabled + * Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay. + */ +#if ENABLE_NEW_RCVBUFFER +void * srt::CUDT::tsbpd(void* param) +{ + CUDT* self = (CUDT*)param; + + THREAD_STATE_INIT("SRT:TsbPd"); + +#if ENABLE_BONDING + // Make the TSBPD thread a "client" of the group, + // which will ensure that the group will not be physically + // deleted until this thread exits. + // NOTE: DO NOT LEAD TO EVER CANCEL THE THREAD!!! + CUDTUnited::GroupKeeper gkeeper(self->uglobal(), self->m_parent); +#endif + + CUniqueSync recvdata_lcc (self->m_RecvLock, self->m_RecvDataCond); + CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); + + self->m_bTsbPdAckWakeup = true; + while (!self->m_bClosing) + { + steady_clock::time_point tsNextDelivery; // Next packet delivery time + bool rxready = false; +#if ENABLE_BONDING + bool shall_update_group = false; +#endif + + enterCS(self->m_RcvBufferLock); + const steady_clock::time_point tnow = steady_clock::now(); + + self->m_pRcvBuffer->updRcvAvgDataSize(tnow); + const srt::CRcvBufferNew::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); + + const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); + tsNextDelivery = info.tsbpd_time; + + if (!self->m_bTLPktDrop) + { + rxready = !info.seq_gap && is_time_to_deliver; + } + else if (is_time_to_deliver) + { + rxready = true; + if (info.seq_gap) + { + const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); +#if ENABLE_BONDING + shall_update_group = true; +#endif + +#if ENABLE_LOGGING + const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); +#if ENABLE_HEAVY_LOGGING + HLOGC(tslog.Debug, + log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" + << iDropCnt << " packets) playable at " << FormatTime(info.tsbpd_time) << " delayed " + << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); +#endif + LOGC(brlog.Warn, log << self->CONID() << "RCV-DROPPED " << iDropCnt << " packet(s). Packet seqno %" << info.seqno + << " delayed for " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') + << (timediff_us % 1000) << " ms"); +#endif + + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } + } + leaveCS(self->m_RcvBufferLock); + + if (rxready) + { + HLOGC(tslog.Debug, + log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " + << (count_milliseconds(steady_clock::now() - info.tsbpd_time)) << "ms)"); + /* + * There are packets ready to be delivered + * signal a waiting "recv" call if there is any data available + */ + if (self->m_config.bSynRecving) + { + recvdata_lcc.notify_one(); } - else + /* + * Set EPOLL_IN to wakeup any thread waiting on epoll + */ + self->uglobal().m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); +#if ENABLE_BONDING + // If this is NULL, it means: + // - the socket never was a group member + // - the socket was a group member, but: + // - was just removed as a part of closure + // - and will never be member of the group anymore + + // If this is not NULL, it means: + // - This socket is currently member of the group + // - This socket WAS a member of the group, though possibly removed from it already, BUT: + // - the group that this socket IS OR WAS member of is in the GroupKeeper + // - the GroupKeeper prevents the group from being deleted + // - it is then completely safe to access the group here, + // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. + + // It is ensured that the group object exists here because GroupKeeper + // keeps it busy, even if you just closed the socket, remove it as a member + // or even the group is empty and was explicitly closed. + if (gkeeper.group) { - - HLOGC(mglog.Debug, - log << "rendezvousSwitchState: " - "{INITIATOR}[INITIATED] awaits AGREEMENT, " - "got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP"); + // Functions called below will lock m_GroupLock, which in hierarchy + // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock + // m_GroupLock inside the calls. + InvertedLock unrecv(self->m_RecvLock); + // The current "APP reader" needs to simply decide as to whether + // the next CUDTGroup::recv() call should return with no blocking or not. + // When the group is read-ready, it should update its pollers as it sees fit. + + // NOTE: this call will set lock to m_IncludedGroup->m_GroupLock + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: GROUP: checking if %" << info.seqno << " makes group readable"); + gkeeper.group->updateReadState(self->m_SocketID, info.seqno); + + if (shall_update_group) + { + // A group may need to update the parallelly used idle links, + // should it have any. Pass the current socket position in order + // to skip it from the group loop. + // NOTE: SELF LOCKING. + gkeeper.group->updateLatestRcv(self->m_parent); + } } - *needs_extension = true; - *needs_hsrsp = true; - return; +#endif + CGlobEvent::triggerEvent(); + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } + + if (!is_zero(tsNextDelivery)) + { + IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); + /* + * Buffer at head of queue is not ready to play. + * Schedule wakeup when it will be. + */ + self->m_bTsbPdAckWakeup = false; + HLOGC(tslog.Debug, + log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); + THREAD_PAUSED(); + tsbpd_cc.wait_until(tsNextDelivery); + THREAD_RESUMED(); + } + else + { + /* + * We have just signaled epoll; or + * receive queue is empty; or + * next buffer to deliver is not in receive queue (missing packet in sequence). + * + * Block until woken up by one of the following event: + * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time + * if any) + * - New buffers ACKed + * - Closing the connection + */ + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); + self->m_bTsbPdAckWakeup = true; + THREAD_PAUSED(); + tsbpd_cc.wait(); + THREAD_RESUMED(); } + + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); } + THREAD_EXIT(); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); + return NULL; +} - reason = "INITIATED -> AGREEMENT(done)"; - break; +int srt::CUDT::rcvDropTooLateUpTo(int seqno) +{ + // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. + if (CSeqNo::seqcmp(seqno, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) + seqno = CSeqNo::incseq(m_iRcvCurrSeqNo); - case CHandShake::RDV_CONNECTED: - // Do nothing. This theoretically should never happen. - *rsptype = URQ_DONE; - return; + const int seq_gap_len = CSeqNo::seqoff(m_iRcvLastSkipAck, seqno); + + // seq_gap_len can be <= 0 if a packet has been dropped by the sender. + if (seq_gap_len > 0) + { + // Remove [from,to-inclusive] + dropFromLossLists(m_iRcvLastSkipAck, CSeqNo::decseq(seqno)); + m_iRcvLastSkipAck = seqno; } - HLOGC(mglog.Debug, log << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID"); - // All others are treated as errors - m_RdvState = CHandShake::RDV_WAVING; - *rsptype = URQFailure(SRT_REJ_ROGUE); + const int iDropCnt = m_pRcvBuffer->dropUpTo(seqno); + if (iDropCnt > 0) + { + enterCS(m_StatsLock); + // Estimate dropped bytes from average payload size. + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); + leaveCS(m_StatsLock); + } + return iDropCnt; } -/* - * Timestamp-based Packet Delivery (TsbPd) thread - * This thread runs only if TsbPd mode is enabled - * Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay. - */ -void *CUDT::tsbpd(void *param) +#else +void * srt::CUDT::tsbpd(void *param) { CUDT *self = (CUDT *)param; THREAD_STATE_INIT("SRT:TsbPd"); - CGuard::enterCS(self->m_RecvLock); +#if ENABLE_BONDING + // Make the TSBPD thread a "client" of the group, + // which will ensure that the group will not be physically + // deleted until this thread exits. + // NOTE: DO NOT LEAD TO EVER CANCEL THE THREAD!!! + CUDTUnited::GroupKeeper gkeeper (self->uglobal(), self->m_parent); +#endif + + UniqueLock recv_lock (self->m_RecvLock); + CSync recvdata_cc (self->m_RecvDataCond, recv_lock); + CSync tsbpd_cc (self->m_RcvTsbPdCond, recv_lock); + self->m_bTsbPdAckWakeup = true; while (!self->m_bClosing) { - int32_t current_pkt_seq = 0; - uint64_t tsbpdtime = 0; - bool rxready = false; + int32_t current_pkt_seq = 0; + steady_clock::time_point tsbpdtime; + bool rxready = false; + int32_t rcv_base_seq = SRT_SEQNO_NONE; +#if ENABLE_BONDING + bool shall_update_group = false; + if (gkeeper.group) + { + // Functions called below will lock m_GroupLock, which in hierarchy + // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock + // m_GroupLock inside the calls. + InvertedLock unrecv(self->m_RecvLock); + rcv_base_seq = gkeeper.group->getRcvBaseSeqNo(); + } +#endif - CGuard::enterCS(self->m_RcvBufferLock); + enterCS(self->m_RcvBufferLock); -#ifdef SRT_ENABLE_RCVBUFSZ_MAVG - self->m_pRcvBuffer->updRcvAvgDataSize(CTimer::getTime()); -#endif + self->m_pRcvBuffer->updRcvAvgDataSize(steady_clock::now()); if (self->m_bTLPktDrop) { - int32_t skiptoseqno = -1; - bool passack = true; // Get next packet to wait for even if not acked - - rxready = self->m_pRcvBuffer->getRcvFirstMsg( - Ref(tsbpdtime), Ref(passack), Ref(skiptoseqno), Ref(current_pkt_seq)); + int32_t skiptoseqno = SRT_SEQNO_NONE; + bool passack = true; // Get next packet to wait for even if not acked + rxready = self->m_pRcvBuffer->getRcvFirstMsg((tsbpdtime), (passack), (skiptoseqno), (current_pkt_seq), rcv_base_seq); HLOGC(tslog.Debug, log << boolalpha << "NEXT PKT CHECK: rdy=" << rxready << " passack=" << passack << " skipto=%" @@ -4804,93 +5421,130 @@ void *CUDT::tsbpd(void *param) /* Packet ready to play according to time stamp but... */ int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno); - if (skiptoseqno != -1 && seqlen > 0) + if (skiptoseqno != SRT_SEQNO_NONE && seqlen > 0) { /* - * skiptoseqno != -1, + * skiptoseqno != SRT_SEQNO_NONE, * packet ready to play but preceeded by missing packets (hole). */ - /* Update drop/skip stats */ - CGuard::enterCS(self->m_StatsLock); - self->m_stats.rcvDropTotal += seqlen; - self->m_stats.traceRcvDrop += seqlen; - /* Estimate dropped/skipped bytes from average payload */ - int avgpayloadsz = self->m_pRcvBuffer->getRcvAvgPayloadSize(); - self->m_stats.rcvBytesDropTotal += seqlen * avgpayloadsz; - self->m_stats.traceRcvBytesDrop += seqlen * avgpayloadsz; - CGuard::leaveCS(self->m_StatsLock); - - self->dropFromLossLists(self->m_iRcvLastSkipAck, - CSeqNo::decseq(skiptoseqno)); // remove(from,to-inclusive) + self->updateForgotten(seqlen, self->m_iRcvLastSkipAck, skiptoseqno); self->m_pRcvBuffer->skipData(seqlen); self->m_iRcvLastSkipAck = skiptoseqno; +#if ENABLE_BONDING + shall_update_group = true; +#endif #if ENABLE_LOGGING - int64_t timediff = 0; - if (tsbpdtime) - timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime()); + int64_t timediff_us = 0; + if (!is_zero(tsbpdtime)) + timediff_us = count_microseconds(steady_clock::now() - tsbpdtime); #if ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: DROPSEQ: up to seq=" << CSeqNo::decseq(skiptoseqno) << " (" + log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(skiptoseqno) << " (" << seqlen << " packets) playable at " << FormatTime(tsbpdtime) << " delayed " - << (timediff / 1000) << "." << (timediff % 1000) << " ms"); + << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); #endif - LOGC(dlog.Debug, log << "RCV-DROPPED packet delay=" << (timediff / 1000) << "ms"); + LOGC(brlog.Warn, + log << self->CONID() << "RCV-DROPPED " << seqlen << " packet(s), packet seqno %" << skiptoseqno + << " delayed for " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') + << (timediff_us % 1000) << " ms"); #endif - tsbpdtime = 0; // Next sent ack will unblock + tsbpdtime = steady_clock::time_point(); //Next sent ack will unblock rxready = false; } else if (passack) { /* Packets ready to play but not yet acknowledged (should happen within 10ms) */ rxready = false; - tsbpdtime = 0; // Next sent ack will unblock + tsbpdtime = steady_clock::time_point(); // Next sent ack will unblock } /* else packet ready to play */ } /* else packets not ready to play */ } else { - rxready = self->m_pRcvBuffer->isRcvDataReady(Ref(tsbpdtime), Ref(current_pkt_seq)); + rxready = self->m_pRcvBuffer->isRcvDataReady((tsbpdtime), (current_pkt_seq), -1 /*get first ready*/); } - CGuard::leaveCS(self->m_RcvBufferLock); + leaveCS(self->m_RcvBufferLock); if (rxready) { HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << current_pkt_seq << " (belated " - << ((CTimer::getTime() - tsbpdtime) / 1000.0) << "ms)"); + << (count_milliseconds(steady_clock::now() - tsbpdtime)) << "ms)"); /* * There are packets ready to be delivered * signal a waiting "recv" call if there is any data available */ - if (self->m_bSynRecving) + if (self->m_config.bSynRecving) { - pthread_cond_signal(&self->m_RecvDataCond); + recvdata_cc.notify_one_locked(recv_lock); } /* * Set EPOLL_IN to wakeup any thread waiting on epoll */ - self->s_UDTUnited.m_EPoll.update_events(self->m_SocketID, self->m_sPollID, UDT_EPOLL_IN, true); - CTimer::triggerEvent(); - tsbpdtime = 0; + self->uglobal().m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); +#if ENABLE_BONDING + // If this is NULL, it means: + // - the socket never was a group member + // - the socket was a group member, but: + // - was just removed as a part of closure + // - and will never be member of the group anymore + + // If this is not NULL, it means: + // - This socket is currently member of the group + // - This socket WAS a member of the group, though possibly removed from it already, BUT: + // - the group that this socket IS OR WAS member of is in the GroupKeeper + // - the GroupKeeper prevents the group from being deleted + // - it is then completely safe to access the group here, + // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. + + // It is ensured that the group object exists here because GroupKeeper + // keeps it busy, even if you just closed the socket, remove it as a member + // or even the group is empty and was explicitly closed. + if (gkeeper.group) + { + // Functions called below will lock m_GroupLock, which in hierarchy + // lies after m_RecvLock. Must unlock m_RecvLock to be able to lock + // m_GroupLock inside the calls. + InvertedLock unrecv(self->m_RecvLock); + // The current "APP reader" needs to simply decide as to whether + // the next CUDTGroup::recv() call should return with no blocking or not. + // When the group is read-ready, it should update its pollers as it sees fit. + + // NOTE: this call will set lock to m_GroupOf->m_GroupLock + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: GROUP: checking if %" << current_pkt_seq << " makes group readable"); + gkeeper.group->updateReadState(self->m_SocketID, current_pkt_seq); + + if (shall_update_group) + { + // A group may need to update the parallelly used idle links, + // should it have any. Pass the current socket position in order + // to skip it from the group loop. + // NOTE: SELF LOCKING. + gkeeper.group->updateLatestRcv(self->m_parent); + } + } +#endif + CGlobEvent::triggerEvent(); + tsbpdtime = steady_clock::time_point(); } - if (tsbpdtime != 0) + if (!is_zero(tsbpdtime)) { - int64_t timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime()); + IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsbpdtime - steady_clock::now()); /* * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ self->m_bTsbPdAckWakeup = false; - THREAD_PAUSED(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << current_pkt_seq - << " T=" << FormatTime(tsbpdtime) << " - waiting " << (timediff / 1000.0) << "ms"); - CTimer::condTimedWaitUS(&self->m_RcvTsbPdCond, &self->m_RecvLock, timediff); + << " T=" << FormatTime(tsbpdtime) << " - waiting " << count_milliseconds(timediff) << "ms"); + THREAD_PAUSED(); + tsbpd_cc.wait_until(tsbpdtime); THREAD_RESUMED(); } else @@ -4909,17 +5563,58 @@ void *CUDT::tsbpd(void *param) HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); self->m_bTsbPdAckWakeup = true; THREAD_PAUSED(); - pthread_cond_wait(&self->m_RcvTsbPdCond, &self->m_RecvLock); + tsbpd_cc.wait(); THREAD_RESUMED(); } + + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); } - CGuard::leaveCS(self->m_RecvLock); THREAD_EXIT(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); return NULL; } +#endif // ENABLE_NEW_RCVBUFFER + +void srt::CUDT::setInitialRcvSeq(int32_t isn) +{ + m_iRcvLastAck = isn; +#ifdef ENABLE_LOGGING + m_iDebugPrevLastAck = m_iRcvLastAck; +#endif + m_iRcvLastSkipAck = m_iRcvLastAck; + m_iRcvLastAckAck = isn; + m_iRcvCurrSeqNo = CSeqNo::decseq(isn); + +#if ENABLE_NEW_RCVBUFFER + sync::ScopedLock rb(m_RcvBufferLock); + if (m_pRcvBuffer) + { + if (!m_pRcvBuffer->empty()) + { + LOGC(cnlog.Error, log << "IPE: setInitialRcvSeq expected empty RCV buffer. Dropping all."); + const int iDropCnt = m_pRcvBuffer->dropAll(); + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + sync::ScopedLock sl(m_StatsLock); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); + } + + m_pRcvBuffer->setStartSeqNo(m_iRcvLastSkipAck); + } +#endif +} + +void srt::CUDT::updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno) +{ + enterCS(m_StatsLock); + // Estimate dropped bytes from average payload size. + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + m_stats.rcvr.dropped.count(stats::BytesPackets(seqlen * avgpayloadsz, (uint32_t) seqlen)); + leaveCS(m_StatsLock); + + dropFromLossLists(lastack, CSeqNo::decseq(skiptoseqno)); //remove(from,to-inclusive) +} -bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout) +bool srt::CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout) { // This will be lazily created due to being the common // code with HSv5 rendezvous, in which this will be run @@ -4927,15 +5622,12 @@ bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUD // be run once in the whole connection process. if (m_pSndBuffer) { - HLOGC(mglog.Debug, log << "prepareConnectionObjects: (lazy) already created."); + HLOGC(rslog.Debug, log << "prepareConnectionObjects: (lazy) already created."); return true; } - bool bidirectional = false; - if (hs.m_iVersion > HS_VERSION_UDT4) - { - bidirectional = true; // HSv5 is always bidirectional - } + // HSv5 is always bidirectional + const bool bidirectional = (hs.m_iVersion > HS_VERSION_UDT4); // HSD_DRAW is received only if this side is listener. // If this side is caller with HSv5, HSD_INITIATOR should be passed. @@ -4949,17 +5641,22 @@ bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUD } else { - hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER; + hsd = m_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; } } try { m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize); - m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_iRcvBufSize); +#if ENABLE_NEW_RCVBUFFER + SRT_ASSERT(m_iISN != -1); + m_pRcvBuffer = new srt::CRcvBufferNew(m_iISN, m_config.iRcvBufSize, m_pRcvQueue->m_pUnitQueue, m_config.bMessageAPI); +#else + m_pRcvBuffer = new CRcvBuffer(m_pRcvQueue->m_pUnitQueue, m_config.iRcvBufSize); +#endif // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); - m_pRcvLossList = new CRcvLossList(m_iFlightFlagSize); + m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) { @@ -4981,133 +5678,162 @@ bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUD return true; } -void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket &hspkt) +void srt::CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) { - HLOGC(mglog.Debug, log << "acceptAndRespond: setting up data according to handshake"); - - CGuard cg(m_ConnectionLock); + // this is a reponse handshake + w_hs.m_iReqType = URQ_CONCLUSION; + w_hs.m_iMSS = m_config.iMSS; + w_hs.m_iFlightFlagSize = m_config.flightCapacity(); + w_hs.m_iID = m_SocketID; - m_ullRcvPeerStartTime = 0; // will be set correctly at SRT HS + if (w_hs.m_iVersion > HS_VERSION_UDT4) + { + // The version is agreed; this code is executed only in case + // when AGENT is listener. In this case, conclusion response + // must always contain HSv5 handshake extensions. + w_hs.m_extension = true; + } - // Uses the smaller MSS between the peers - if (hs->m_iMSS > m_iMSS) - hs->m_iMSS = m_iMSS; - else - m_iMSS = hs->m_iMSS; + CIPAddress::ntop(peer, (w_hs.m_piPeerIP)); +} - // exchange info for maximum flow window size - m_iFlowWindowSize = hs->m_iFlightFlagSize; - hs->m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize; +void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) +{ + HLOGC(cnlog.Debug, log << "acceptAndRespond: setting up data according to handshake"); - m_iPeerISN = hs->m_iISN; + ScopedLock cg(m_ConnectionLock); - m_iRcvLastAck = hs->m_iISN; -#ifdef ENABLE_LOGGING - m_iDebugPrevLastAck = m_iRcvLastAck; -#endif - m_iRcvLastSkipAck = m_iRcvLastAck; - m_iRcvLastAckAck = hs->m_iISN; - m_iRcvCurrSeqNo = hs->m_iISN - 1; - m_iRcvCurrPhySeqNo = hs->m_iISN - 1; + m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS - m_PeerID = hs->m_iID; - hs->m_iID = m_SocketID; + // Uses the smaller MSS between the peers + m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); - // use peer's ISN and send it back for security check - m_iISN = hs->m_iISN; + // exchange info for maximum flow window size + m_iFlowWindowSize = w_hs.m_iFlightFlagSize; + m_iPeerISN = w_hs.m_iISN; + setInitialRcvSeq(m_iPeerISN); + m_iRcvCurrPhySeqNo = CSeqNo::decseq(w_hs.m_iISN); - m_iLastDecSeq = m_iISN - 1; - m_iSndLastAck = m_iISN; - m_iSndLastDataAck = m_iISN; - m_iSndLastFullAck = m_iISN; - m_iSndCurrSeqNo = m_iISN - 1; - m_iSndLastAck2 = m_iISN; - m_ullSndLastAck2Time = CTimer::getTime(); + m_PeerID = w_hs.m_iID; - // this is a reponse handshake - hs->m_iReqType = URQ_CONCLUSION; + // use peer's ISN and send it back for security check + m_iISN = w_hs.m_iISN; - if (hs->m_iVersion > HS_VERSION_UDT4) - { - // The version is agreed; this code is executed only in case - // when AGENT is listener. In this case, conclusion response - // must always contain HSv5 handshake extensions. - hs->m_extension = true; - } + setInitialSndSeq(m_iISN); + m_SndLastAck2Time = steady_clock::now(); // get local IP address and send the peer its IP address (because UDP cannot get local IP address) - memcpy(m_piSelfIP, hs->m_piPeerIP, 16); - CIPAddress::ntop(peer, hs->m_piPeerIP, m_iIPversion); + memcpy((m_piSelfIP), w_hs.m_piPeerIP, sizeof m_piSelfIP); + m_parent->m_SelfAddr = agent; + CIPAddress::pton((m_parent->m_SelfAddr), m_piSelfIP, peer); + + rewriteHandshakeData(peer, (w_hs)); - int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE; + int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; - HLOGC(mglog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + HLOGC(cnlog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); // Prepare all structures - if (!prepareConnectionObjects(*hs, HSD_DRAW, 0)) + if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) { - HLOGC(mglog.Debug, log << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); + HLOGC(cnlog.Debug, log << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // // Respond with the rejection message and exit with exception // so that the caller will know that this new socket should be deleted. - hs->m_iReqType = URQFailure(m_RejectReason); + w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } // Since now you can use m_pCryptoControl CInfoBlock ib; - ib.m_iIPversion = m_iIPversion; - CInfoBlock::convert(peer, m_iIPversion, ib.m_piIP); + ib.m_iIPversion = peer.family(); + CInfoBlock::convert(peer, ib.m_piIP); if (m_pCache->lookup(&ib) >= 0) { - m_iRTT = ib.m_iRTT; + m_iSRTT = ib.m_iSRTT; + m_iRTTVar = ib.m_iSRTT / 2; m_iBandwidth = ib.m_iBandwidth; } +#if SRT_DEBUG_RTT + s_rtt_trace.trace(steady_clock::now(), "Accept", -1, -1, + m_bIsFirstRTTReceived, -1, m_iSRTT, m_iRTTVar); +#endif + + m_PeerAddr = peer; + // This should extract the HSREQ and KMREQ portion in the handshake packet. // This could still be a HSv4 packet and contain no such parts, which will leave // this entity as "non-SRT-handshaken", and await further HSREQ and KMREQ sent // as UMSG_EXT. uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; - if (!interpretSrtHandshake(*hs, hspkt, kmdata, &kmdatasize)) + if (!interpretSrtHandshake(w_hs, hspkt, (kmdata), (&kmdatasize))) { - HLOGC(mglog.Debug, log << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT."); + HLOGC(cnlog.Debug, log << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT."); // If the SRT Handshake extension was provided and wasn't interpreted // correctly, the connection should be rejected. // // Respond with the rejection message and return false from // this function so that the caller will know that this new // socket should be deleted. - hs->m_iReqType = URQFailure(m_RejectReason); + w_hs.m_iReqType = URQFailure(m_RejectReason); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } + // Synchronize the time NOW because the following function is about + // to use the start time to pass it to the receiver buffer data. + bool have_group = false; + + { +#if ENABLE_BONDING + ScopedLock cl (uglobal().m_GlobControlLock); + CUDTGroup* g = m_parent->m_GroupOf; + if (g) + { + // This is the last moment when this can be done. + // The updateAfterSrtHandshake call will copy the receiver + // start time to the receiver buffer data, so the correct + // value must be set before this happens. + synchronizeWithGroup(g); + have_group = true; + } +#endif + } + + if (!have_group) + { + // This function will be called internally inside + // synchronizeWithGroup(). This is just more complicated. + updateAfterSrtHandshake(w_hs.m_iVersion); + } + SRT_REJECT_REASON rr = setupCC(); // UNKNOWN used as a "no error" value if (rr != SRT_REJ_UNKNOWN) { - hs->m_iReqType = URQFailure(rr); + w_hs.m_iReqType = URQFailure(rr); m_RejectReason = rr; throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } - m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6; - memcpy(m_pPeerAddr, peer, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); - // And of course, it is connected. m_bConnected = true; - // register this socket for receiving data packets + // Register this socket for receiving data packets. m_pRNode->m_bOnList = true; m_pRcvQueue->setNewEntry(this); - // send the response to the peer, see listen() for more discussions about this - // XXX Here create CONCLUSION RESPONSE with: + // Save the handshake in m_ConnRes in case when needs repeating. + m_ConnRes = w_hs; + + // Send the response to the peer, see listen() for more discussions + // about this. + // TODO: Here create CONCLUSION RESPONSE with: // - just the UDT handshake, if HS_VERSION_UDT4, - // - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP + // - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP. size_t size = m_iMaxSRTPayloadSize; // Allocate the maximum possible memory for an SRT payload. // This is a maximum you can send once. @@ -5116,27 +5842,24 @@ void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket response.allocate(size); // This will serialize the handshake according to its current form. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); - if (!createSrtHandshake(Ref(response), Ref(*hs), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize)) + if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (response), (w_hs))) { - LOGC(mglog.Error, log << "acceptAndRespond: error creating handshake response"); + LOGC(cnlog.Error, log << "acceptAndRespond: error creating handshake response"); throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } - // Set target socket ID to the value from received handshake's source ID. - response.m_iID = m_PeerID; - #if ENABLE_HEAVY_LOGGING { // To make sure what REALLY is being sent, parse back the handshake // data that have been just written into the buffer. CHandShake debughs; debughs.load_from(response.m_pcData, response.getLength()); - HLOGC(mglog.Debug, - log << CONID() << "acceptAndRespond: sending HS to peer, reqtype=" << RequestTypeStr(debughs.m_iReqType) - << " version=" << debughs.m_iVersion << " (connreq:" << RequestTypeStr(m_ConnReq.m_iReqType) - << "), target_socket=" << response.m_iID << ", my_socket=" << debughs.m_iID); + HLOGC(cnlog.Debug, + log << CONID() << "acceptAndRespond: sending HS from agent @" + << debughs.m_iID << " to peer @" << response.m_iID + << "HS:" << debughs.show()); } #endif @@ -5145,7 +5868,7 @@ void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket // When missed this message, the caller should not accept packets // coming as connected, but continue repeated handshake until finally // received the listener's handshake. - m_pSndQueue->sendto(peer, response); + addressAndSend((response)); } // This function is required to be called when a caller receives an INDUCTION @@ -5156,7 +5879,7 @@ void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket // be created, as this happens before the completion of the connection (and // therefore configuration of the crypter object), which can only take place upon // reception of CONCLUSION response from the listener. -bool CUDT::createCrypter(HandshakeSide side, bool bidirectional) +bool srt::CUDT::createCrypter(HandshakeSide side, bool bidirectional) { // Lazy initialization if (m_pCryptoControl) @@ -5165,24 +5888,24 @@ bool CUDT::createCrypter(HandshakeSide side, bool bidirectional) // Write back this value, when it was just determined. m_SrtHsSide = side; - m_pCryptoControl.reset(new CCryptoControl(this, m_SocketID)); + m_pCryptoControl.reset(new CCryptoControl(m_SocketID)); // XXX These below are a little bit controversial. // These data should probably be filled only upon // reception of the conclusion handshake - otherwise // they have outdated values. - m_pCryptoControl->setCryptoSecret(m_CryptoSecret); + m_pCryptoControl->setCryptoSecret(m_config.CryptoSecret); - if (bidirectional || m_bDataSender) + if (bidirectional || m_config.bDataSender) { - HLOGC(mglog.Debug, log << "createCrypter: setting RCV/SND KeyLen=" << m_iSndCryptoKeyLen); - m_pCryptoControl->setCryptoKeylen(m_iSndCryptoKeyLen); + HLOGC(rslog.Debug, log << "createCrypter: setting RCV/SND KeyLen=" << m_config.iSndCryptoKeyLen); + m_pCryptoControl->setCryptoKeylen(m_config.iSndCryptoKeyLen); } - return m_pCryptoControl->init(side, bidirectional); + return m_pCryptoControl->init(side, m_config, bidirectional); } -SRT_REJECT_REASON CUDT::setupCC() +SRT_REJECT_REASON srt::CUDT::setupCC() { // Prepare configuration object, // Create the CCC object and configure it. @@ -5193,30 +5916,42 @@ SRT_REJECT_REASON CUDT::setupCC() // XXX Not sure about that. May happen that AGENT wants // tsbpd mode, but PEER doesn't, even in bidirectional mode. // This way, the reception side should get precedense. - // if (bidirectional || m_bDataSender || m_bTwoWayData) - // m_bPeerTsbPd = m_bOPT_TsbPd; + // if (bidirectional || m_config.bDataSender || m_bTwoWayData) + // m_bPeerTsbPd = m_bTSBPD; // SrtCongestion will retrieve whatever parameters it needs // from *this. - if (!m_CongCtl.configure(this)) + + bool res = m_CongCtl.select(m_config.sCongestion.str()); + if (!res || !m_CongCtl.configure(this)) { return SRT_REJ_CONGESTION; } // Configure filter module - if (m_OPT_PktFilterConfigString != "") + if (!m_config.sPacketFilterConfig.empty()) { // This string, when nonempty, defines that the corrector shall be // configured. Otherwise it's left uninitialized. // At this point we state everything is checked and the appropriate // corrector type is already selected, so now create it. - HLOGC(mglog.Debug, log << "filter: Configuring Corrector: " << m_OPT_PktFilterConfigString); - if (!m_PacketFilter.configure(this, m_pRcvBuffer->getUnitQueue(), m_OPT_PktFilterConfigString)) + HLOGC(pflog.Debug, log << "filter: Configuring: " << m_config.sPacketFilterConfig.c_str()); + bool status = true; + try { - return SRT_REJ_FILTER; + // The filter configurer is build the way that allows to quit immediately + // exit by exception, but the exception is meant for the filter only. + status = m_PacketFilter.configure(this, m_pRcvQueue->m_pUnitQueue, m_config.sPacketFilterConfig.str()); + } + catch (CUDTException& ) + { + status = false; } + if (!status) + return SRT_REJ_FILTER; + m_PktFilterRexmitLevel = m_PacketFilter.arqLevel(); } else @@ -5228,44 +5963,47 @@ SRT_REJECT_REASON CUDT::setupCC() // Override the value of minimum NAK interval, per SrtCongestion's wish. // When default 0 value is returned, the current value set by CUDT // is preserved. - uint64_t min_nak_tk = m_CongCtl->minNAKInterval(); - if (min_nak_tk) - m_ullMinNakInt_tk = min_nak_tk; + const steady_clock::duration min_nak = microseconds_from(m_CongCtl->minNAKInterval()); + if (min_nak != steady_clock::duration::zero()) + m_tdMinNakInterval = min_nak; // Update timers - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - m_ullLastRspTime_tk = currtime_tk; - m_ullNextACKTime_tk = currtime_tk + m_ullACKInt_tk; - m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk; - m_ullLastRspAckTime_tk = currtime_tk; - m_ullLastSndTime_tk = currtime_tk; - - HLOGC(mglog.Debug, - log << "setupCC: setting parameters: mss=" << m_iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize + const steady_clock::time_point currtime = steady_clock::now(); + m_tsLastRspTime.store(currtime); + m_tsNextACKTime.store(currtime + m_tdACKInterval); + m_tsNextNAKTime.store(currtime + m_tdNAKInterval); + m_tsLastRspAckTime = currtime; + m_tsLastSndTime.store(currtime); + + HLOGC(rslog.Debug, + log << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize << " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)" - << " rtt=" << m_iRTT << " bw=" << m_iBandwidth); + << " rtt=" << m_iSRTT << " bw=" << m_iBandwidth); - updateCC(TEV_INIT, TEV_INIT_RESET); + if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET))) + { + LOGC(rslog.Error, log << "setupCC: IPE: resrouces not yet initialized!"); + return SRT_REJ_IPE; + } return SRT_REJ_UNKNOWN; } -void CUDT::considerLegacySrtHandshake(uint64_t timebase) +void srt::CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timebase) { // Do a fast pre-check first - this simply declares that agent uses HSv5 // and the legacy SRT Handshake is not to be done. Second check is whether // agent is sender (=initiator in HSv4). - if (!isTsbPd() || !m_bDataSender) + if (!isOPT_TsbPd() || !m_config.bDataSender) return; if (m_iSndHsRetryCnt <= 0) { - HLOGC(mglog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); + HLOGC(cnlog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); return; } - uint64_t now = CTimer::getTime(); - if (timebase != 0) + const steady_clock::time_point now = steady_clock::now(); + if (!is_zero(timebase)) { // Then this should be done only if it's the right time, // the TSBPD mode is on, and when the counter is "still rolling". @@ -5280,7 +6018,7 @@ void CUDT::considerLegacySrtHandshake(uint64_t timebase) */ if (timebase > now) // too early { - HLOGC(mglog.Debug, log << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times"); + HLOGC(cnlog.Debug, log << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times"); return; } } @@ -5288,29 +6026,29 @@ void CUDT::considerLegacySrtHandshake(uint64_t timebase) // payload packet sent. Send only if this is still set to maximum+1 value. else if (m_iSndHsRetryCnt < SRT_MAX_HSRETRY + 1) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt << " times"); return; } - HLOGC(mglog.Debug, log << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response"); + HLOGC(cnlog.Debug, log << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response"); m_iSndHsRetryCnt--; - m_ullSndHsLastTime_us = now; + m_tsSndHsLastTime = now; sendSrtMsg(SRT_CMD_HSREQ); } -void CUDT::checkSndTimers(Whether2RegenKm regen) +void srt::CUDT::checkSndTimers(Whether2RegenKm regen) { if (m_SrtHsSide == HSD_INITIATOR) { - HLOGC(mglog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase"); + HLOGC(cnlog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase"); // Legacy method for HSREQ, only if initiator. - considerLegacySrtHandshake(m_ullSndHsLastTime_us + m_iRTT * 3 / 2); + considerLegacySrtHandshake(m_tsSndHsLastTime + microseconds_from(m_iSRTT * 3 / 2)); } else { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)") << " - not considering legacy handshake"); } @@ -5324,19 +6062,25 @@ void CUDT::checkSndTimers(Whether2RegenKm regen) // if this side is RESPONDER. This shall be called only with // regeneration request, which is required by the sender. if (m_pCryptoControl) - m_pCryptoControl->sendKeysToPeer(regen); + m_pCryptoControl->sendKeysToPeer(this, SRTT(), regen); } } -void CUDT::addressAndSend(CPacket &pkt) +void srt::CUDT::addressAndSend(CPacket& w_pkt) { - pkt.m_iID = m_PeerID; - pkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - - m_pSndQueue->sendto(m_pPeerAddr, pkt); + w_pkt.m_iID = m_PeerID; + setPacketTS(w_pkt, steady_clock::now()); + + // NOTE: w_pkt isn't modified in this call, + // just in CChannel::sendto it's modified in place + // before sending for performance purposes, + // and then modification is undone. Logically then + // there's no modification here. + m_pSndQueue->sendto(m_PeerAddr, w_pkt); } -bool CUDT::close() +// [[using maybe_locked(m_GlobControlLock, if called from GC)]] +bool srt::CUDT::closeInternal() { // NOTE: this function is called from within the garbage collector thread. @@ -5345,29 +6089,34 @@ bool CUDT::close() return false; } - HLOGC(mglog.Debug, log << CONID() << " - closing socket:"); + // IMPORTANT: + // This function may block indefinitely, if called for a socket + // that has m_bBroken == false or m_bConnected == true. + // If it is intended to forcefully close the socket, make sure + // that it's in response to a broken connection. + HLOGC(smlog.Debug, log << CONID() << " - closing socket:"); - if (m_Linger.l_onoff != 0) + if (m_config.Linger.l_onoff != 0) { - uint64_t entertime = CTimer::getTime(); + const steady_clock::time_point entertime = steady_clock::now(); - HLOGC(mglog.Debug, log << CONID() << " ... (linger)"); + HLOGC(smlog.Debug, log << CONID() << " ... (linger)"); while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) && - (CTimer::getTime() - entertime < m_Linger.l_linger * uint64_t(1000000))) + (steady_clock::now() - entertime < seconds_from(m_config.Linger.l_linger))) { // linger has been checked by previous close() call and has expired - if (m_ullLingerExpiration >= entertime) + if (m_tsLingerExpiration >= entertime) break; - if (!m_bSynSending) + if (!m_config.bSynSending) { // if this socket enables asynchronous sending, return immediately and let GC to close it later - if (m_ullLingerExpiration == 0) - m_ullLingerExpiration = entertime + m_Linger.l_linger * uint64_t(1000000); + if (is_zero(m_tsLingerExpiration)) + m_tsLingerExpiration = entertime + seconds_from(m_config.Linger.l_linger); - HLOGC(mglog.Debug, + HLOGC(smlog.Debug, log << "CUDT::close: linger-nonblocking, setting expire time T=" - << FormatTime(m_ullLingerExpiration)); + << FormatTime(m_tsLingerExpiration)); return false; } @@ -5389,23 +6138,49 @@ bool CUDT::close() /* * update_events below useless - * removing usock for EPolls right after (remove_usocks) clears it (in other HAI patch). + * removing usock for EPolls right after (update_usocks) clears it (in other HAI patch). * * What is in EPoll shall be the responsibility of the application, if it want local close event, * it would remove the socket from the EPoll after close. */ + + // Make a copy under a lock because other thread might access it + // at the same time. + enterCS(uglobal().m_EPoll.m_EPollLock); + set epollid = m_sPollID; + leaveCS(uglobal().m_EPoll.m_EPollLock); + // trigger any pending IO events. - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true); + HLOGC(smlog.Debug, log << "close: SETTING ERR readiness on E" << Printable(epollid) << " of @" << m_SocketID); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true); // then remove itself from all epoll monitoring - try - { - for (set::iterator i = m_sPollID.begin(); i != m_sPollID.end(); ++i) - s_UDTUnited.m_EPoll.remove_usock(*i, m_SocketID); - } - catch (...) + int no_events = 0; + for (set::iterator i = epollid.begin(); i != epollid.end(); ++i) { + HLOGC(smlog.Debug, log << "close: CLEARING subscription on E" << (*i) << " of @" << m_SocketID); + try + { + uglobal().m_EPoll.update_usock(*i, m_SocketID, &no_events); + } + catch (...) + { + // The goal of this loop is to remove all subscriptions in + // the epoll system to this socket. If it's unsubscribed already, + // that's even better. + } + HLOGC(smlog.Debug, log << "close: removing E" << (*i) << " from back-subscribers of @" << m_SocketID); } + // Not deleting elements from m_sPollID inside the loop because it invalidates + // the control iterator of the loop. Instead, all will be removed at once. + + // IMPORTANT: there's theoretically little time between setting ERR readiness + // and unsubscribing, however if there's an application waiting on this event, + // it should be informed before this below instruction locks the epoll mutex. + enterCS(uglobal().m_EPoll.m_EPollLock); + m_sPollID.clear(); + leaveCS(uglobal().m_EPoll.m_EPollLock); + // XXX What's this, could any of the above actions make it !m_bOpened? if (!m_bOpened) { @@ -5415,14 +6190,14 @@ bool CUDT::close() // Inform the threads handler to stop. m_bClosing = true; - HLOGC(mglog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock"); + HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock"); - CGuard cg(m_ConnectionLock); + ScopedLock connectguard(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. releaseSynch(); - HLOGC(mglog.Debug, log << CONID() << "CLOSING, removing from listener/connector"); + HLOGC(smlog.Debug, log << CONID() << "CLOSING, removing from listener/connector"); if (m_bListening) { @@ -5438,161 +6213,63 @@ bool CUDT::close() { if (!m_bShutdown) { - HLOGC(mglog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer"); + HLOGC(smlog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer @" << m_PeerID); sendCtrl(UMSG_SHUTDOWN); } - m_pCryptoControl->close(); - // Store current connection information. CInfoBlock ib; - ib.m_iIPversion = m_iIPversion; - CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP); - ib.m_iRTT = m_iRTT; + ib.m_iIPversion = m_PeerAddr.family(); + CInfoBlock::convert(m_PeerAddr, ib.m_piIP); + ib.m_iSRTT = m_iSRTT; ib.m_iBandwidth = m_iBandwidth; m_pCache->update(&ib); - m_bConnected = false; - } +#if SRT_DEBUG_RTT + s_rtt_trace.trace(steady_clock::now(), "Cache", -1, -1, + m_bIsFirstRTTReceived, -1, m_iSRTT, -1); +#endif - if (m_bTsbPd && !pthread_equal(m_RcvTsbPdThread, pthread_t())) - { - HLOGC(mglog.Debug, log << "CLOSING, joining TSBPD thread..."); - void *retval; - int ret SRT_ATR_UNUSED = pthread_join(m_RcvTsbPdThread, &retval); - HLOGC(mglog.Debug, log << "... " << (ret == 0 ? "SUCCEEDED" : "FAILED")); + m_bConnected = false; } - HLOGC(mglog.Debug, log << "CLOSING, joining send/receive threads"); + HLOGC(smlog.Debug, log << "CLOSING, joining send/receive threads"); // waiting all send and recv calls to stop - CGuard sendguard(m_SendLock); - CGuard recvguard(m_RecvLock); + ScopedLock sendguard(m_SendLock); + ScopedLock recvguard(m_RecvLock); - // Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt(Ref(packet)) + // Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt((packet)) // from the processData(...) function while resetting Crypto Control. - CGuard::enterCS(m_RcvBufferLock); + enterCS(m_RcvBufferLock); + if (m_pCryptoControl) + m_pCryptoControl->close(); + m_pCryptoControl.reset(); - CGuard::leaveCS(m_RcvBufferLock); + leaveCS(m_RcvBufferLock); - m_lSrtVersion = SRT_DEF_VERSION; - m_lPeerSrtVersion = SRT_VERSION_UNK; - m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1; - m_ullRcvPeerStartTime = 0; + m_uPeerSrtVersion = SRT_VERSION_UNK; + m_tsRcvPeerStartTime = steady_clock::time_point(); m_bOpened = false; return true; } -/* - Old, mostly original UDT based version of CUDT::send. - Left for historical reasons. - -int CUDT::send(const char* data, int len) +int srt::CUDT::receiveBuffer(char *data, int len) { - // throw an exception if not connected - if (m_bBroken || m_bClosing) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - else if (!m_bConnected || !m_CongCtl.ready()) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - - if (len <= 0) - return 0; - - // Check if the current congctl accepts the call with given parameters. - if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_SEND, data, len, -1, false)) - throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); - - CGuard sendguard(m_SendLock); - - if (m_pSndBuffer->getCurrBufSize() == 0) - { - // delay the EXP timer to avoid mis-fired timeout - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - // (fix keepalive) m_ullLastRspTime_tk = currtime_tk; - m_ullLastRspAckTime_tk = currtime_tk; - m_iReXmitCount = 1; - } - if (sndBuffersLeft() <= 0) - { - if (!m_bSynSending) - throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); - else - { - { - // wait here during a blocking sending - CGuard sendblock_lock(m_SendBlockLock); - if (m_iSndTimeOut < 0) - { - while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth) - pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock); - } - else - { - uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * uint64_t(1000); - timespec locktime; - - locktime.tv_sec = exptime / 1000000; - locktime.tv_nsec = (exptime % 1000000) * 1000; - - while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth && (CTimer::getTime() < exptime)) - pthread_cond_timedwait(&m_SendBlockCond, &m_SendBlockLock, &locktime); - } - } - - // check the connection status - if (m_bBroken || m_bClosing) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - else if (!m_bConnected) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - else if (!m_bPeerHealth) - { - m_bPeerHealth = true; - throw CUDTException(MJ_PEERERROR); - } - } - } - - if (sndBuffersLeft() <= 0) - { - if (m_iSndTimeOut >= 0) - throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); - - return 0; - } - - int size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize); - - // record total time used for sending - if (m_pSndBuffer->getCurrBufSize() == 0) - m_llSndDurationCounter = CTimer::getTime(); - - // insert the user buffer into the sending list - m_pSndBuffer->addBuffer(data, size); // inorder=false, ttl=-1 - - // insert this socket to snd list if it is not on the list yet - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); - - if (sndBuffersLeft() <= 0) - { - // write is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false); - } - - return size; -} -*/ + if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) + throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); -int CUDT::receiveBuffer(char *data, int len) -{ - if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, -1, false)) + if (isOPT_TsbPd()) + { + LOGP(arlog.Error, "recv: This function is not intended to be used in Live mode with TSBPD."); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); + } - CGuard recvguard(m_RecvLock); + UniqueLock recvguard(m_RecvLock); - if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { if (m_bShutdown) { @@ -5613,42 +6290,46 @@ int CUDT::receiveBuffer(char *data, int len) // make this function return 0, potentially also without breaking // the connection and potentially also with losing no ability to // send some larger portion of data next time. - HLOGC(mglog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF"); + HLOGC(arlog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } - HLOGC(mglog.Debug, - log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") - << " SHUTDOWN. Reporting as BROKEN."); + HLOGC(arlog.Debug, + log << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") + << " SHUTDOWN. Reporting as BROKEN."); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - if (!m_pRcvBuffer->isRcvDataReady()) + CSync rcond (m_RecvDataCond, recvguard); + CSync tscond (m_RcvTsbPdCond, recvguard); + if (!isRcvBufferReady()) { - if (!m_bSynRecving) + if (!m_config.bSynRecving) { throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } - else + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_config.iRcvTimeOut < 0) { - /* Kick TsbPd thread to schedule next wakeup (if running) */ - if (m_iRcvTimeOut < 0) + THREAD_PAUSED(); + while (stillConnected() && !isRcvBufferReady()) { - while (stillConnected() && !m_pRcvBuffer->isRcvDataReady()) - { - // Do not block forever, check connection status each 1 sec. - CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, 1000000); - } + // Do not block forever, check connection status each 1 sec. + rcond.wait_for(seconds_from(1)); } - else + THREAD_RESUMED(); + } + else + { + const steady_clock::time_point exptime = + steady_clock::now() + milliseconds_from(m_config.iRcvTimeOut); + THREAD_PAUSED(); + while (stillConnected() && !isRcvBufferReady()) { - uint64_t exptime = CTimer::getTime() + m_iRcvTimeOut * 1000; - while (stillConnected() && !m_pRcvBuffer->isRcvDataReady()) - { - CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, m_iRcvTimeOut * 1000); - if (CTimer::getTime() >= exptime) - break; - } + if (!rcond.wait_until(exptime)) // NOT means "not received a signal" + break; // timeout } + THREAD_RESUMED(); } } @@ -5656,56 +6337,63 @@ int CUDT::receiveBuffer(char *data, int len) if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { // See at the beginning - if (!m_bMessageAPI && m_bShutdown) + if (!m_config.bMessageAPI && m_bShutdown) { - HLOGC(mglog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF"); + HLOGC(arlog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } - HLOGC(mglog.Debug, - log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") + HLOGC(arlog.Debug, + log << (m_config.bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") << " SHUTDOWN. Reporting as BROKEN."); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } + enterCS(m_RcvBufferLock); const int res = m_pRcvBuffer->readBuffer(data, len); + leaveCS(m_RcvBufferLock); /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) { HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); - pthread_cond_signal(&m_RcvTsbPdCond); + tscond.notify_one_locked(recvguard); + } + else + { + HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); } - if (!m_pRcvBuffer->isRcvDataReady()) + if (!isRcvBufferReady()) { // read is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } - if ((res <= 0) && (m_iRcvTimeOut >= 0)) + if ((res <= 0) && (m_config.iRcvTimeOut >= 0)) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); return res; } -void CUDT::checkNeedDrop(ref_t bCongestion) +// [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]]; +// [[using locked(m_SendLock)]]; +int srt::CUDT::sndDropTooLate() { if (!m_bPeerTLPktDrop) - return; + return 0; - if (!m_bMessageAPI) + if (!m_config.bMessageAPI) { - LOGC(dlog.Error, log << "The SRTO_TLPKTDROP flag can only be used with message API."); + LOGC(aslog.Error, log << "The SRTO_TLPKTDROP flag can only be used with message API."); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } - int bytes, timespan_ms; - // (returns buffer size in buffer units, ignored) - m_pSndBuffer->getCurrBufSize(Ref(bytes), Ref(timespan_ms)); + const time_point tnow = steady_clock::now(); + const int buffdelay_ms = (int) count_milliseconds(m_pSndBuffer->getBufferingDelay(tnow)); // high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms) // Minimum value must accomodate an I-Frame (~8 x average frame size) @@ -5713,76 +6401,85 @@ void CUDT::checkNeedDrop(ref_t bCongestion) // >>using 1 sec for worse case 1 frame using all bit budget. // picture rate would be useful in auto SRT setting for min latency // XXX Make SRT_TLPKTDROP_MINTHRESHOLD_MS option-configurable - int threshold_ms = 0; - if (m_iOPT_SndDropDelay >= 0) - { - threshold_ms = std::max(m_iPeerTsbPdDelay_ms + m_iOPT_SndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) + - (2 * COMM_SYN_INTERVAL_US / 1000); - } + const int threshold_ms = (m_config.iSndDropDelay >= 0) + ? std::max(m_iPeerTsbPdDelay_ms + m_config.iSndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) + + (2 * COMM_SYN_INTERVAL_US / 1000) + : 0; - if (threshold_ms && timespan_ms > threshold_ms) - { - // protect packet retransmission - CGuard::enterCS(m_RecvAckLock); - int dbytes; - int dpkts = m_pSndBuffer->dropLateData(dbytes, CTimer::getTime() - (threshold_ms * 1000)); - if (dpkts > 0) - { - CGuard::enterCS(m_StatsLock); - m_stats.traceSndDrop += dpkts; - m_stats.sndDropTotal += dpkts; - m_stats.traceSndBytesDrop += dbytes; - m_stats.sndBytesDropTotal += dbytes; - CGuard::leaveCS(m_StatsLock); + if (threshold_ms == 0 || buffdelay_ms <= threshold_ms) + return 0; -#if ENABLE_HEAVY_LOGGING - int32_t realack = m_iSndLastDataAck; -#endif - int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); + // protect packet retransmission + ScopedLock rcvlck(m_RecvAckLock); + int dbytes; + int32_t first_msgno; + const int dpkts = m_pSndBuffer->dropLateData((dbytes), (first_msgno), tnow - milliseconds_from(threshold_ms)); + if (dpkts <= 0) + return 0; - m_iSndLastAck = fakeack; - m_iSndLastDataAck = fakeack; + // If some packets were dropped update stats, socket state, loss list and the parent group if any. + enterCS(m_StatsLock); + m_stats.sndr.dropped.count(dbytes);; + leaveCS(m_StatsLock); - int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); - m_pSndLossList->remove(minlastack); - /* If we dropped packets not yet sent, advance current position */ - // THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1) - if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0) - { - m_iSndCurrSeqNo = minlastack; - } - LOGC(dlog.Error, log << "SND-DROPPED " << dpkts << " packets - lost delaying for " << timespan_ms << "ms"); + IF_HEAVY_LOGGING(const int32_t realack = m_iSndLastDataAck); + const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); - HLOGC(dlog.Debug, - log << "drop,now " << CTimer::getTime() << "us," << realack << "-" << m_iSndCurrSeqNo << " seqs," - << dpkts << " pkts," << dbytes << " bytes," << timespan_ms << " ms"); - } - *bCongestion = true; - CGuard::leaveCS(m_RecvAckLock); - } - else if (timespan_ms > (m_iPeerTsbPdDelay_ms / 2)) + m_iSndLastAck = fakeack; + m_iSndLastDataAck = fakeack; + + const int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck); + m_pSndLossList->removeUpTo(minlastack); + /* If we dropped packets not yet sent, advance current position */ + // THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1) + if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0) { - HLOGC(mglog.Debug, - log << "cong, NOW: " << CTimer::getTime() << "us, BYTES " << bytes << ", TMSPAN " << timespan_ms << "ms"); + m_iSndCurrSeqNo = minlastack; + } + + HLOGC(aslog.Debug, log << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo << ") n=" + << dpkts << "pkt " << dbytes << "B, span=" << buffdelay_ms << " ms, FIRST #" << first_msgno); - *bCongestion = true; +#if ENABLE_BONDING + // This is done with a presumption that the group + // exists and if this is not NULL, it means that this + // function was called with locked m_GroupLock, as sendmsg2 + // function was called from inside CUDTGroup::send, which + // locks the whole function. + // + // XXX This is true only because all existing groups are managed + // groups, that is, sockets cannot be added or removed from group + // manually, nor can send/recv operation be done on a single socket + // from the API call directly. This should be extra verified, if that + // changes in the future. + // + if (m_parent->m_GroupOf) + { + // What's important is that the lock on GroupLock cannot be applied + // here, both because it might be applied already, that is, according + // to the condition defined at this function's header, it is applied + // under this condition. Hence ackMessage can be defined as 100% locked. + m_parent->m_GroupOf->ackMessage(first_msgno); } +#endif + + return dpkts; } -int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, uint64_t srctime) +int srt::CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64_t srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; mctrl.msgttl = msttl; mctrl.inorder = inorder; mctrl.srctime = srctime; - return this->sendmsg2(data, len, Ref(mctrl)); + return this->sendmsg2(data, len, (mctrl)); } -int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) +// [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] +// GroupLock is applied when this function is called from inside CUDTGroup::send, +// which is the only case when the m_parent->m_GroupOf is not NULL. +int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) { - SRT_MSGCTRL &mctrl = *r_mctrl; - bool bCongestion = false; - // throw an exception if not connected if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -5791,12 +6488,21 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) if (len <= 0) { - LOGC(dlog.Error, log << "INVALID: Data size for sending declared with length: " << len); + LOGC(aslog.Error, log << "INVALID: Data size for sending declared with length: " << len); return 0; } - int msttl = mctrl.msgttl; - bool inorder = mctrl.inorder; + if (w_mctrl.msgno != -1) // most unlikely, unless you use balancing groups + { + if (w_mctrl.msgno < 1 || w_mctrl.msgno > MSGNO_SEQ_MAX) + { + LOGC(aslog.Error, log << "INVALID forced msgno " << w_mctrl.msgno << ": can be -1 (trap) or <1..." << MSGNO_SEQ_MAX << ">"); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + } + + int msttl = w_mctrl.msgttl; + bool inorder = w_mctrl.inorder; // Sendmsg isn't restricted to the congctl type, however the congctl // may want to have something to say here. @@ -5804,7 +6510,7 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) { SrtCongestion::TransAPI api = SrtCongestion::STA_MESSAGE; CodeMinor mn = MN_INVALMSGAPI; - if (!m_bMessageAPI) + if (!m_config.bMessageAPI) { api = SrtCongestion::STA_BUFFER; mn = MN_INVALBUFFERAPI; @@ -5832,11 +6538,11 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) // out a message of a length that exceeds the total size of the sending // buffer (configurable by SRTO_SNDBUF). - if (m_bMessageAPI && len > int(m_iSndBufSize * m_iMaxSRTPayloadSize)) + if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxSRTPayloadSize)) { - LOGC(dlog.Error, + LOGC(aslog.Error, log << "Message length (" << len << ") exceeds the size of sending buffer: " - << (m_iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); + << (m_config.iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } @@ -5845,30 +6551,27 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) must be at least conditional because it breaks backward compat. if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK()) { - LOGC(dlog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending + LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } */ - CGuard sendguard(m_SendLock); + UniqueLock sendguard(m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) { // delay the EXP timer to avoid mis-fired timeout - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - - CGuard ack_lock(m_RecvAckLock); - m_ullLastRspAckTime_tk = currtime_tk; // (fix keepalive) - m_iReXmitCount = 1; // can be modified in checkRexmitTimer and processCtrlAck (receiver's thread) + ScopedLock ack_lock(m_RecvAckLock); + m_tsLastRspAckTime = steady_clock::now(); + m_iReXmitCount = 1; } - // checkNeedDrop(...) may lock m_RecvAckLock + // sndDropTooLate(...) may lock m_RecvAckLock // to modify m_pSndBuffer and m_pSndLossList - checkNeedDrop(Ref(bCongestion)); + const int iPktsTLDropped SRT_ATR_UNUSED = sndDropTooLate(); int minlen = 1; // Minimum sender buffer space required for STREAM API - if (m_bMessageAPI) + if (m_config.bMessageAPI) { // For MESSAGE API the minimum outgoing buffer space required is // the size that can carry over the whole message as passed here. @@ -5879,24 +6582,29 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) { //>>We should not get here if SRT_ENABLE_TLPKTDROP // XXX Check if this needs to be removed, or put to an 'else' condition for m_bTLPktDrop. - if (!m_bSynSending) + if (!m_config.bSynSending) throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); { // wait here during a blocking sending - CGuard sendblock_lock(m_SendBlockLock); + UniqueLock sendblock_lock (m_SendBlockLock); - if (m_iSndTimeOut < 0) + if (m_config.iSndTimeOut < 0) { while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth) - pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock); + m_SendBlockCond.wait(sendblock_lock); } else { - const uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * uint64_t(1000); - - while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth && exptime > CTimer::getTime()) - CTimer::condTimedWaitUS(&m_SendBlockCond, &m_SendBlockLock, m_iSndTimeOut * uint64_t(1000)); + const steady_clock::time_point exptime = + steady_clock::now() + milliseconds_from(m_config.iSndTimeOut); + THREAD_PAUSED(); + while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth) + { + if (!m_SendBlockCond.wait_until(sendblock_lock, exptime)) + break; + } + THREAD_RESUMED(); } } @@ -5919,7 +6627,7 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) */ if (sndBuffersLeft() < minlen) { - if (m_iSndTimeOut >= 0) + if (m_config.iSndTimeOut >= 0) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); // XXX This looks very weird here, however most likely @@ -5936,7 +6644,7 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) // - m_bPeerHealth condition is checked and responded with PEERERROR // // ERGO: never happens? - LOGC(mglog.Fatal, + LOGC(aslog.Fatal, log << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. " "Impossible."); @@ -5948,13 +6656,12 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) // record total time used for sending if (m_pSndBuffer->getCurrBufSize() == 0) { - CGuard::enterCS(m_StatsLock); - m_stats.sndDurationCounter = CTimer::getTime(); - CGuard::leaveCS(m_StatsLock); + ScopedLock lock(m_StatsLock); + m_stats.sndDurationCounter = steady_clock::now(); } int size = len; - if (!m_bMessageAPI) + if (!m_config.bMessageAPI) { // For STREAM API it's allowed to send less bytes than the given buffer. // Just return how many bytes were actually scheduled for writing. @@ -5964,99 +6671,191 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) } { - CGuard recvAckLock(m_RecvAckLock); + ScopedLock recvAckLock(m_RecvAckLock); // insert the user buffer into the sending list - // This should be protected by a mutex. m_SendLock does this. - m_pSndBuffer->addBuffer(data, size, mctrl.msgttl, mctrl.inorder, mctrl.srctime, Ref(mctrl.msgno)); - HLOGC(dlog.Debug, log << CONID() << "sock:SENDING srctime: " << mctrl.srctime << "us DATA SIZE: " << size); + + int32_t seqno = m_iSndNextSeqNo; + IF_HEAVY_LOGGING(int32_t orig_seqno = seqno); + IF_HEAVY_LOGGING(steady_clock::time_point ts_srctime = + steady_clock::time_point() + microseconds_from(w_mctrl.srctime)); + +#if ENABLE_BONDING + // Check if seqno has been set, in case when this is a group sender. + // If the sequence is from the past towards the "next sequence", + // simply return the size, pretending that it has been sent. + + // NOTE: it's assumed that if this is a group member, then + // an attempt to call srt_sendmsg2 has been rejected, and so + // the pktseq field has been set by the internal group sender function. + if (m_parent->m_GroupOf + && w_mctrl.pktseq != SRT_SEQNO_NONE + && m_iSndNextSeqNo != SRT_SEQNO_NONE) + { + if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0) + { + HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq + << " OLDER THAN next expected %" << seqno << " - FAKE-SENDING."); + return size; + } + } +#endif + + // Set this predicted next sequence to the control information. + // It's the sequence of the FIRST (!) packet from all packets used to send + // this buffer. Values from this field will be monotonic only if you always + // have one packet per buffer (as it's in live mode). + w_mctrl.pktseq = seqno; + + // Now seqno is the sequence to which it was scheduled + // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurrate. + HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:" + << (w_mctrl.srctime ? FormatTime(ts_srctime) : "none") + << " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno + << " STAMP: " << BufferStamp(data, size)); + + if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(m_stats.tsStartTime.time_since_epoch())) + { + LOGC(aslog.Error, + log << "Wrong source time was provided. Sending is rejected."); + throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); + } + + if (w_mctrl.srctime && (!m_config.bMessageAPI || !m_bTsbPd)) + { + HLOGC(aslog.Warn, + log << "Source time can only be used with TSBPD and Message API enabled. Using default time instead."); + w_mctrl.srctime = 0; + } + + // w_mctrl.seqno is INPUT-OUTPUT value: + // - INPUT: the current sequence number to be placed for the next scheduled packet + // - OUTPUT: value of the sequence number to be put on the first packet at the next sendmsg2 call. + // We need to supply to the output the value that was STAMPED ON THE PACKET, + // which is seqno. In the output we'll get the next sequence number. + m_pSndBuffer->addBuffer(data, size, (w_mctrl)); + m_iSndNextSeqNo = w_mctrl.pktseq; + w_mctrl.pktseq = seqno; + + HLOGC(aslog.Debug, log << CONID() << "buf:SENDING srctime:" << FormatTime(ts_srctime) + << " size=" << size << " #" << w_mctrl.msgno << " SCHED %" << orig_seqno + << "(>> %" << seqno << ") !" << BufferStamp(data, size)); if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried. { // write is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); } } - // insert this socket to the snd list if it is not on the list yet + // Insert this socket to the snd list if it is not on the list already. // m_pSndUList->pop may lock CSndUList::m_ListLock and then m_RecvAckLock - m_pSndQueue->m_pSndUList->update(this, CSndUList::rescheduleIf(bCongestion)); + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); #ifdef SRT_ENABLE_ECN - if (bCongestion) + // IF there was a packet drop on the sender side, report congestion to the app. + if (iPktsTLDropped > 0) + { + LOGC(aslog.Error, log << "sendmsg2: CONGESTION; reporting error"); throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0); + } #endif /* SRT_ENABLE_ECN */ + + HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (END): success, size=" << size); return size; } -int CUDT::recv(char *data, int len) +int srt::CUDT::recv(char* data, int len) { - if (!m_bConnected || !m_CongCtl.ready()) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + SRT_MSGCTRL mctrl = srt_msgctrl_default; + return recvmsg2(data, len, (mctrl)); +} - if (len <= 0) - { - LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recv."); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } +int srt::CUDT::recvmsg(char* data, int len, int64_t& srctime) +{ + SRT_MSGCTRL mctrl = srt_msgctrl_default; + int res = recvmsg2(data, len, (mctrl)); + srctime = mctrl.srctime; + return res; +} + +// [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]] +// GroupLock is applied when this function is called from inside CUDTGroup::recv, +// which is the only case when the m_parent->m_GroupOf is not NULL. +int srt::CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + // Check if the socket is a member of a receiver group. + // If so, then reading by receiveMessage is disallowed. - if (m_bMessageAPI) +#if ENABLE_BONDING + if (m_parent->m_GroupOf && m_parent->m_GroupOf->isGroupReceiver()) { - SRT_MSGCTRL mctrl = srt_msgctrl_default; - return receiveMessage(data, len, Ref(mctrl)); + LOGP(arlog.Error, "recv*: This socket is a receiver group member. Use group ID, NOT socket ID."); + throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); } +#endif - return receiveBuffer(data, len); -} - -int CUDT::recvmsg(char *data, int len, uint64_t &srctime) -{ if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (len <= 0) { - LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg."); + LOGC(arlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - if (m_bMessageAPI) - { - SRT_MSGCTRL mctrl = srt_msgctrl_default; - int ret = receiveMessage(data, len, Ref(mctrl)); - srctime = mctrl.srctime; - return ret; - } + if (m_config.bMessageAPI) + return receiveMessage(data, len, (w_mctrl)); return receiveBuffer(data, len); } -int CUDT::recvmsg2(char *data, int len, ref_t mctrl) +size_t srt::CUDT::getAvailRcvBufferSizeLock() const { - if (!m_bConnected || !m_CongCtl.ready()) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - - if (len <= 0) - { - LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg."); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } + ScopedLock lck(m_RcvBufferLock); + return getAvailRcvBufferSizeNoLock(); +} - if (m_bMessageAPI) - return receiveMessage(data, len, mctrl); +size_t srt::CUDT::getAvailRcvBufferSizeNoLock() const +{ +#if ENABLE_NEW_RCVBUFFER + return m_pRcvBuffer->getAvailSize(m_iRcvLastAck); +#else + return m_pRcvBuffer->getAvailBufSize(); +#endif +} - return receiveBuffer(data, len); +bool srt::CUDT::isRcvBufferReady() const +{ + ScopedLock lck(m_RcvBufferLock); +#if ENABLE_NEW_RCVBUFFER + return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); +#else + return m_pRcvBuffer->isRcvDataReady(); +#endif } -int CUDT::receiveMessage(char *data, int len, ref_t r_mctrl) +// int by_exception: accepts values of CUDTUnited::ErrorHandling: +// - 0 - by return value +// - 1 - by exception +// - 2 - by abort (unused) +int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_exception) { - SRT_MSGCTRL &mctrl = *r_mctrl; // Recvmsg isn't restricted to the congctl type, it's the most // basic method of passing the data. You can retrieve data as // they come in, however you need to match the size of the buffer. - if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, -1, false)) + + // Note: if by_exception = ERH_RETURN, this would still break it + // by exception. The intention of by_exception isn't to prevent + // exceptions here, but to intercept the erroneous situation should + // it be handled by the caller in a less than general way. As this + // is only used internally, we state that the problem that would be + // handled by exception here should not happen, and in case if it does, + // it's a bug to fix, so the exception is nothing wrong. + if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); - CGuard recvguard(m_RecvLock); + UniqueLock recvguard (m_RecvLock); + CSync tscond (m_RcvTsbPdCond, recvguard); /* XXX DEBUG STUFF - enable when required char charbool[2] = {'0', '1'}; @@ -6065,7 +6864,7 @@ int CUDT::receiveMessage(char *data, int len, ref_t r_mctrl) ptrn[pos[0]] = charbool[m_bBroken]; ptrn[pos[1]] = charbool[m_bConnected]; ptrn[pos[2]] = charbool[m_bClosing]; - ptrn[pos[3]] = charbool[m_bSynRecving]; + ptrn[pos[3]] = charbool[m_config.m_bSynRecving]; int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum()); strcpy(ptrn + pos[4] + wrtlen, "\n"); fputs(ptrn, stderr); @@ -6073,116 +6872,213 @@ int CUDT::receiveMessage(char *data, int len, ref_t r_mctrl) if (m_bBroken || m_bClosing) { - int res = m_pRcvBuffer->readMsg(data, len); - mctrl.srctime = 0; + HLOGC(arlog.Debug, log << CONID() << "receiveMessage: CONNECTION BROKEN - reading from recv buffer just for formality"); + enterCS(m_RcvBufferLock); +#if ENABLE_NEW_RCVBUFFER + const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + : 0; +#else + const int res = m_pRcvBuffer->readMsg(data, len); +#endif + leaveCS(m_RcvBufferLock); + w_mctrl.srctime = 0; - /* Kick TsbPd thread to schedule next wakeup (if running) */ + // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) - pthread_cond_signal(&m_RcvTsbPdCond); + { + HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); + tscond.notify_one_locked(recvguard); + } + else + { + HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); + } - if (!m_pRcvBuffer->isRcvDataReady()) + if (!isRcvBufferReady()) { // read is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } if (res == 0) { - if (!m_bMessageAPI && m_bShutdown) + if (!m_config.bMessageAPI && m_bShutdown) return 0; + // Forced to return error instead of throwing exception. + if (!by_exception) + return APIError(MJ_CONNECTION, MN_CONNLOST, 0); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else return res; } - if (!m_bSynRecving) +#if !ENABLE_NEW_RCVBUFFER + const int seqdistance = -1; +#endif + + if (!m_config.bSynRecving) { + HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN ASYNC MODE. Going to extract payload size=" << len); + enterCS(m_RcvBufferLock); +#if ENABLE_NEW_RCVBUFFER + const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + : 0; +#else + const int res = m_pRcvBuffer->readMsg(data, len, (w_mctrl), seqdistance); +#endif + leaveCS(m_RcvBufferLock); + HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); - int res = m_pRcvBuffer->readMsg(data, len, r_mctrl); if (res == 0) { // read is not available any more - // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) - pthread_cond_signal(&m_RcvTsbPdCond); + { + HLOGP(arlog.Debug, "receiveMessage: nothing to read, kicking TSBPD, return AGAIN"); + tscond.notify_one_locked(recvguard); + } + else + { + HLOGP(arlog.Debug, "receiveMessage: nothing to read, return AGAIN"); + } // Shut up EPoll if no more messages in non-blocking mode - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); + // Forced to return 0 instead of throwing exception, in case of AGAIN/READ + if (!by_exception) + return 0; throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } - else + + if (!isRcvBufferReady()) { - if (!m_pRcvBuffer->isRcvDataReady()) + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) { - // Kick TsbPd thread to schedule next wakeup (if running) - if (m_bTsbPd) - pthread_cond_signal(&m_RcvTsbPdCond); + HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more - kicking TSBPD."); + tscond.notify_one_locked(recvguard); + } + else + { + HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more"); + } - // Shut up EPoll if no more messages in non-blocking mode - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + // Shut up EPoll if no more messages in non-blocking mode + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); - // After signaling the tsbpd for ready data, report the bandwidth. - double bw SRT_ATR_UNUSED = Bps2Mbps(m_iBandwidth * m_iMaxSRTPayloadSize); - HLOGC(mglog.Debug, - log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth - << " buffers per second)"); - } - return res; + // After signaling the tsbpd for ready data, report the bandwidth. +#if ENABLE_HEAVY_LOGGING + double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxSRTPayloadSize ); + HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth << " buffers per second)"); +#endif } + return res; } + HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN SYNC MODE. Going to extract payload size max=" << len); + int res = 0; bool timeout = false; // Do not block forever, check connection status each 1 sec. - uint64_t recvtmo = m_iRcvTimeOut < 0 ? 1000 : m_iRcvTimeOut; + const steady_clock::duration recv_timeout = m_config.iRcvTimeOut < 0 ? seconds_from(1) : milliseconds_from(m_config.iRcvTimeOut); + + CSync recv_cond (m_RecvDataCond, recvguard); do { - if (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady())) +#if ENABLE_NEW_RCVBUFFER + if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady(steady_clock::now())) +#else + steady_clock::time_point tstime SRT_ATR_UNUSED; + int32_t seqno; + if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady((tstime), (seqno), seqdistance)) +#endif { /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) { - HLOGP(tslog.Debug, "recvmsg: KICK tsbpd()"); - pthread_cond_signal(&m_RcvTsbPdCond); + // XXX Experimental, so just inform: + // Check if the last check of isRcvDataReady has returned any "next time for a packet". + // If so, then it means that TSBPD has fallen asleep only up to this time, so waking it up + // would be "spurious". If a new packet comes ahead of the packet which's time is returned + // in tstime (as TSBPD sleeps up to then), the procedure that receives it is responsible + // of kicking TSBPD. + // bool spurious = (tstime != 0); + + HLOGC(tslog.Debug, log << CONID() << "receiveMessage: KICK tsbpd"); + tscond.notify_one_locked(recvguard); } + THREAD_PAUSED(); do { - if (CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, recvtmo * 1000) == ETIMEDOUT) + // `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be + // only until the time that is now + timeout since the first moment + // when this started, or sliced-waiting for 1 second, if timtout is + // higher than this. + const steady_clock::time_point exptime = steady_clock::now() + recv_timeout; + + HLOGC(tslog.Debug, + log << CONID() << "receiveMessage: fall asleep up to TS=" << FormatTime(exptime) + << " lock=" << (&m_RecvLock) << " cond=" << (&m_RecvDataCond)); + + if (!recv_cond.wait_until(exptime)) { - if (!(m_iRcvTimeOut < 0)) + if (m_config.iRcvTimeOut >= 0) // otherwise it's "no timeout set" timeout = true; - HLOGP(tslog.Debug, "recvmsg: DATA COND: EXPIRED -- trying to get data anyway"); + HLOGP(tslog.Debug, + "receiveMessage: DATA COND: EXPIRED -- checking connection conditions and rolling again"); } else { - HLOGP(tslog.Debug, "recvmsg: DATA COND: KICKED."); + HLOGP(tslog.Debug, "receiveMessage: DATA COND: KICKED."); } - } while (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady())); + } while (stillConnected() && !timeout && (!isRcvBufferReady())); + THREAD_RESUMED(); + + HLOGC(tslog.Debug, + log << CONID() << "receiveMessage: lock-waiting loop exited: stillConntected=" << stillConnected() + << " timeout=" << timeout << " data-ready=" << isRcvBufferReady()); } /* XXX DEBUG STUFF - enable when required - LOGC(dlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected + LOGC(arlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected << " CLOSING " << m_bClosing << " TMOUT " << timeout << " NMSG " << m_pRcvBuffer->getRcvMsgNum()); */ - res = m_pRcvBuffer->readMsg(data, len, r_mctrl); + enterCS(m_RcvBufferLock); +#if ENABLE_NEW_RCVBUFFER + res = m_pRcvBuffer->readMessage((data), len, &w_mctrl); +#else + res = m_pRcvBuffer->readMsg((data), len, (w_mctrl), seqdistance); +#endif + leaveCS(m_RcvBufferLock); + HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); if (m_bBroken || m_bClosing) { - if (!m_bMessageAPI && m_bShutdown) + // Forced to return 0 instead of throwing exception. + if (!by_exception) + return APIError(MJ_CONNECTION, MN_CONNLOST, 0); + if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else if (!m_bConnected) + { + // Forced to return -1 instead of throwing exception. + if (!by_exception) + return APIError(MJ_CONNECTION, MN_NOCONN, 0); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } } while ((res == 0) && !timeout); - if (!m_pRcvBuffer->isRcvDataReady()) + if (!isRcvBufferReady()) { // Falling here means usually that res == 0 && timeout == true. // res == 0 would repeat the above loop, unless there was also a timeout. @@ -6195,23 +7091,28 @@ int CUDT::receiveMessage(char *data, int len, ref_t r_mctrl) if (m_bTsbPd) { HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)"); - pthread_cond_signal(&m_RcvTsbPdCond); + tscond.notify_one_locked(recvguard); } // Shut up EPoll if no more messages in non-blocking mode - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } // Unblock when required // LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT"); - if ((res <= 0) && (m_iRcvTimeOut >= 0)) + if ((res <= 0) && (m_config.iRcvTimeOut >= 0)) + { + // Forced to return -1 instead of throwing exception. + if (!by_exception) + return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + } return res; } -int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) +int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -6221,26 +7122,24 @@ int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) if (size <= 0 && size != -1) return 0; - if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_SEND, 0, size, -1, false)) + if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_SEND, 0, size, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK()) { - LOGC(dlog.Error, + LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } - CGuard sendguard(m_SendLock); + ScopedLock sendguard (m_SendLock); if (m_pSndBuffer->getCurrBufSize() == 0) { // delay the EXP timer to avoid mis-fired timeout - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - // (fix keepalive) m_ullLastRspTime_tk = currtime_tk; - m_ullLastRspAckTime_tk = currtime_tk; - m_iReXmitCount = 1; + // XXX Lock ??? ScopedLock ack_lock(m_RecvAckLock); + m_tsLastRspAckTime = steady_clock::now(); + m_iReXmitCount = 1; } // positioning... @@ -6286,10 +7185,12 @@ int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) unitsize = int((tosend >= block) ? block : tosend); { - CGuard lk(m_SendBlockLock); + UniqueLock lock(m_SendBlockLock); + THREAD_PAUSED(); while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth) - pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock); + m_SendBlockCond.wait(lock); + THREAD_RESUMED(); } if (m_bBroken || m_bClosing) @@ -6306,13 +7207,12 @@ int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) // record total time used for sending if (m_pSndBuffer->getCurrBufSize() == 0) { - CGuard::enterCS(m_StatsLock); - m_stats.sndDurationCounter = CTimer::getTime(); - CGuard::leaveCS(m_StatsLock); + ScopedLock lock(m_StatsLock); + m_stats.sndDurationCounter = steady_clock::now(); } { - CGuard recvAckLock(m_RecvAckLock); + ScopedLock recvAckLock(m_RecvAckLock); const int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize); if (sentsize > 0) @@ -6324,7 +7224,7 @@ int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) if (sndBuffersLeft() <= 0) { // write is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); } } @@ -6335,13 +7235,13 @@ int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) return size - tosend; } -int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) +int64_t srt::CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) { if (!m_bConnected || !m_CongCtl.ready()) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + else if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { - if (!m_bMessageAPI && m_bShutdown) + if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } @@ -6349,16 +7249,16 @@ int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) if (size <= 0) return 0; - if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_RECV, 0, size, -1, false)) + if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_RECV, 0, size, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); - if (m_bTsbPd) + if (isOPT_TsbPd()) { - LOGC(dlog.Error, log << "Reading from file is incompatible with TSBPD mode and would cause a deadlock\n"); + LOGC(arlog.Error, log << "Reading from file is incompatible with TSBPD mode and would cause a deadlock\n"); throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); } - CGuard recvguard(m_RecvLock); + UniqueLock recvguard(m_RecvLock); // Well, actually as this works over a FILE (fstream), not just a stream, // the size can be measured anyway and predicted if setting the offset might @@ -6417,23 +7317,28 @@ int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); } - pthread_mutex_lock(&m_RecvDataLock); - while (stillConnected() && !m_pRcvBuffer->isRcvDataReady()) - pthread_cond_wait(&m_RecvDataCond, &m_RecvDataLock); - pthread_mutex_unlock(&m_RecvDataLock); + { + CSync rcond (m_RecvDataCond, recvguard); + + THREAD_PAUSED(); + while (stillConnected() && !isRcvBufferReady()) + rcond.wait(); + THREAD_RESUMED(); + } if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + else if ((m_bBroken || m_bClosing) && !isRcvBufferReady()) { - - if (!m_bMessageAPI && m_bShutdown) + if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - unitsize = int((torecv == -1 || torecv >= block) ? block : torecv); + unitsize = int((torecv > block) ? block : torecv); + enterCS(m_RcvBufferLock); recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize); + leaveCS(m_RcvBufferLock); if (recvsize > 0) { @@ -6442,157 +7347,160 @@ int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) } } - if (!m_pRcvBuffer->isRcvDataReady()) + if (!isRcvBufferReady()) { // read is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false); } return size - torecv; } -void CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) +void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) { if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - CGuard statsguard(m_StatsLock); - - uint64_t currtime = CTimer::getTime(); - perf->msTimeStamp = (currtime - m_stats.startTime) / 1000; - - perf->pktSent = m_stats.traceSent; - perf->pktRecv = m_stats.traceRecv; - perf->pktSndLoss = m_stats.traceSndLoss; - perf->pktRcvLoss = m_stats.traceRcvLoss; - perf->pktRetrans = m_stats.traceRetrans; - perf->pktRcvRetrans = m_stats.traceRcvRetrans; - perf->pktSentACK = m_stats.sentACK; - perf->pktRecvACK = m_stats.recvACK; - perf->pktSentNAK = m_stats.sentNAK; - perf->pktRecvNAK = m_stats.recvNAK; - perf->usSndDuration = m_stats.sndDuration; - perf->pktReorderDistance = m_stats.traceReorderDistance; - perf->pktReorderTolerance = m_iReorderTolerance; - perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime; - perf->pktRcvBelated = m_stats.traceRcvBelated; - - perf->pktSndFilterExtra = m_stats.sndFilterExtra; - perf->pktRcvFilterExtra = m_stats.rcvFilterExtra; - perf->pktRcvFilterSupply = m_stats.rcvFilterSupply; - perf->pktRcvFilterLoss = m_stats.rcvFilterLoss; - - /* perf byte counters include all headers (SRT+UDP+IP) */ const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; - perf->byteSent = m_stats.traceBytesSent + (m_stats.traceSent * pktHdrSize); - perf->byteRecv = m_stats.traceBytesRecv + (m_stats.traceRecv * pktHdrSize); - perf->byteRetrans = m_stats.traceBytesRetrans + (m_stats.traceRetrans * pktHdrSize); -#ifdef SRT_ENABLE_LOSTBYTESCOUNT - perf->byteRcvLoss = m_stats.traceRcvBytesLoss + (m_stats.traceRcvLoss * pktHdrSize); -#endif - - perf->pktSndDrop = m_stats.traceSndDrop; - perf->pktRcvDrop = m_stats.traceRcvDrop + m_stats.traceRcvUndecrypt; - perf->byteSndDrop = m_stats.traceSndBytesDrop + (m_stats.traceSndDrop * pktHdrSize); - perf->byteRcvDrop = - m_stats.traceRcvBytesDrop + (m_stats.traceRcvDrop * pktHdrSize) + m_stats.traceRcvBytesUndecrypt; - perf->pktRcvUndecrypt = m_stats.traceRcvUndecrypt; - perf->byteRcvUndecrypt = m_stats.traceRcvBytesUndecrypt; - - perf->pktSentTotal = m_stats.sentTotal; - perf->pktRecvTotal = m_stats.recvTotal; - perf->pktSndLossTotal = m_stats.sndLossTotal; - perf->pktRcvLossTotal = m_stats.rcvLossTotal; - perf->pktRetransTotal = m_stats.retransTotal; - perf->pktSentACKTotal = m_stats.sentACKTotal; - perf->pktRecvACKTotal = m_stats.recvACKTotal; - perf->pktSentNAKTotal = m_stats.sentNAKTotal; - perf->pktRecvNAKTotal = m_stats.recvNAKTotal; - perf->usSndDurationTotal = m_stats.m_sndDurationTotal; - - perf->byteSentTotal = m_stats.bytesSentTotal + (m_stats.sentTotal * pktHdrSize); - perf->byteRecvTotal = m_stats.bytesRecvTotal + (m_stats.recvTotal * pktHdrSize); - perf->byteRetransTotal = m_stats.bytesRetransTotal + (m_stats.retransTotal * pktHdrSize); - perf->pktSndFilterExtraTotal = m_stats.sndFilterExtraTotal; - perf->pktRcvFilterExtraTotal = m_stats.rcvFilterExtraTotal; - perf->pktRcvFilterSupplyTotal = m_stats.rcvFilterSupplyTotal; - perf->pktRcvFilterLossTotal = m_stats.rcvFilterLossTotal; - -#ifdef SRT_ENABLE_LOSTBYTESCOUNT - perf->byteRcvLossTotal = m_stats.rcvBytesLossTotal + (m_stats.rcvLossTotal * pktHdrSize); -#endif - perf->pktSndDropTotal = m_stats.sndDropTotal; - perf->pktRcvDropTotal = m_stats.rcvDropTotal + m_stats.m_rcvUndecryptTotal; - perf->byteSndDropTotal = m_stats.sndBytesDropTotal + (m_stats.sndDropTotal * pktHdrSize); - perf->byteRcvDropTotal = - m_stats.rcvBytesDropTotal + (m_stats.rcvDropTotal * pktHdrSize) + m_stats.m_rcvBytesUndecryptTotal; - perf->pktRcvUndecryptTotal = m_stats.m_rcvUndecryptTotal; - perf->byteRcvUndecryptTotal = m_stats.m_rcvBytesUndecryptTotal; - //< - - double interval = double(currtime - m_stats.lastSampleTime); - - //>mod - perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; - perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; - //< - - perf->usPktSndPeriod = m_ullInterval_tk / double(m_ullCPUFrequency); - perf->pktFlowWindow = m_iFlowWindowSize; - perf->pktCongestionWindow = (int)m_dCongestionWindow; - perf->pktFlightSize = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) - 1; - perf->msRTT = (double)m_iRTT / 1000.0; - //>new - perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0; - perf->msRcvTsbPdDelay = m_bTsbPd ? m_iTsbPdDelay_ms : 0; - perf->byteMSS = m_iMSS; - - perf->mbpsMaxBW = m_llMaxBW > 0 ? Bps2Mbps(m_llMaxBW) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0; - - //< - uint32_t availbw = (uint64_t)(m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth); + { + ScopedLock statsguard(m_StatsLock); + + const steady_clock::time_point currtime = steady_clock::now(); + + perf->msTimeStamp = count_milliseconds(currtime - m_stats.tsStartTime); + perf->pktSent = m_stats.sndr.sent.trace.count(); + perf->pktSentUnique = m_stats.sndr.sentUnique.trace.count(); + perf->pktRecv = m_stats.rcvr.recvd.trace.count(); + perf->pktRecvUnique = m_stats.rcvr.recvdUnique.trace.count(); + + perf->pktSndLoss = m_stats.sndr.lost.trace.count(); + perf->pktRcvLoss = m_stats.rcvr.lost.trace.count(); + perf->pktRetrans = m_stats.sndr.sentRetrans.trace.count(); + perf->pktRcvRetrans = m_stats.rcvr.recvdRetrans.trace.count(); + perf->pktSentACK = m_stats.rcvr.sentAck.trace.count(); + perf->pktRecvACK = m_stats.sndr.recvdAck.trace.count(); + perf->pktSentNAK = m_stats.rcvr.sentNak.trace.count(); + perf->pktRecvNAK = m_stats.sndr.recvdNak.trace.count(); + perf->usSndDuration = m_stats.sndDuration; + perf->pktReorderDistance = m_stats.traceReorderDistance; + perf->pktReorderTolerance = m_iReorderTolerance; + perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime; + perf->pktRcvBelated = m_stats.rcvr.recvdBelated.trace.count(); + + perf->pktSndFilterExtra = m_stats.sndr.sentFilterExtra.trace.count(); + perf->pktRcvFilterExtra = m_stats.rcvr.recvdFilterExtra.trace.count(); + perf->pktRcvFilterSupply = m_stats.rcvr.suppliedByFilter.trace.count(); + perf->pktRcvFilterLoss = m_stats.rcvr.lossFilter.trace.count(); + + /* perf byte counters include all headers (SRT+UDP+IP) */ + perf->byteSent = m_stats.sndr.sent.trace.bytesWithHdr(); + perf->byteSentUnique = m_stats.sndr.sentUnique.trace.bytesWithHdr(); + perf->byteRecv = m_stats.rcvr.recvd.trace.bytesWithHdr(); + perf->byteRecvUnique = m_stats.rcvr.recvdUnique.trace.bytesWithHdr(); + perf->byteRetrans = m_stats.sndr.sentRetrans.trace.bytesWithHdr(); + perf->byteRcvLoss = m_stats.rcvr.lost.trace.bytesWithHdr(); + + perf->pktSndDrop = m_stats.sndr.dropped.trace.count(); + perf->pktRcvDrop = m_stats.rcvr.dropped.trace.count() + m_stats.rcvr.undecrypted.trace.count(); + perf->byteSndDrop = m_stats.sndr.dropped.trace.bytesWithHdr(); + perf->byteRcvDrop = m_stats.rcvr.dropped.trace.bytesWithHdr(); + perf->pktRcvUndecrypt = m_stats.rcvr.undecrypted.trace.count(); + perf->byteRcvUndecrypt = m_stats.rcvr.undecrypted.trace.bytes(); + + perf->pktSentTotal = m_stats.sndr.sent.total.count(); + perf->pktSentUniqueTotal = m_stats.sndr.sentUnique.total.count(); + perf->pktRecvTotal = m_stats.rcvr.recvd.total.count(); + perf->pktRecvUniqueTotal = m_stats.rcvr.recvdUnique.total.count(); + perf->pktSndLossTotal = m_stats.sndr.lost.total.count(); + perf->pktRcvLossTotal = m_stats.rcvr.lost.total.count(); + perf->pktRetransTotal = m_stats.sndr.sentRetrans.total.count(); + perf->pktSentACKTotal = m_stats.rcvr.sentAck.total.count(); + perf->pktRecvACKTotal = m_stats.sndr.recvdAck.total.count(); + perf->pktSentNAKTotal = m_stats.rcvr.sentNak.total.count(); + perf->pktRecvNAKTotal = m_stats.sndr.recvdNak.total.count(); + perf->usSndDurationTotal = m_stats.m_sndDurationTotal; + + perf->byteSentTotal = m_stats.sndr.sent.total.bytesWithHdr(); + perf->byteSentUniqueTotal = m_stats.sndr.sentUnique.total.bytesWithHdr(); + perf->byteRecvTotal = m_stats.rcvr.recvd.total.bytesWithHdr(); + perf->byteRecvUniqueTotal = m_stats.rcvr.recvdUnique.total.bytesWithHdr(); + perf->byteRetransTotal = m_stats.sndr.sentRetrans.total.bytesWithHdr(); + perf->pktSndFilterExtraTotal = m_stats.sndr.sentFilterExtra.total.count(); + perf->pktRcvFilterExtraTotal = m_stats.rcvr.recvdFilterExtra.total.count(); + perf->pktRcvFilterSupplyTotal = m_stats.rcvr.suppliedByFilter.total.count(); + perf->pktRcvFilterLossTotal = m_stats.rcvr.lossFilter.total.count(); + + perf->byteRcvLossTotal = m_stats.rcvr.lost.total.bytesWithHdr(); + perf->pktSndDropTotal = m_stats.sndr.dropped.total.count(); + perf->pktRcvDropTotal = m_stats.rcvr.dropped.total.count() + m_stats.rcvr.undecrypted.total.count(); + // TODO: The payload is dropped. Probably header sizes should not be counted? + perf->byteSndDropTotal = m_stats.sndr.dropped.total.bytesWithHdr(); + perf->byteRcvDropTotal = m_stats.rcvr.dropped.total.bytesWithHdr() + m_stats.rcvr.undecrypted.total.bytesWithHdr(); + perf->pktRcvUndecryptTotal = m_stats.rcvr.undecrypted.total.count(); + perf->byteRcvUndecryptTotal = m_stats.rcvr.undecrypted.total.bytes(); + + // TODO: The following class members must be protected with a different mutex, not the m_StatsLock. + const double interval = (double) count_microseconds(currtime - m_stats.tsLastSampleTime); + perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; + perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; + perf->usPktSndPeriod = (double) count_microseconds(m_tdSendInterval.load()); + perf->pktFlowWindow = m_iFlowWindowSize.load(); + perf->pktCongestionWindow = (int)m_dCongestionWindow; + perf->pktFlightSize = getFlightSpan(); + perf->msRTT = (double)m_iSRTT / 1000.0; + perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0; + perf->msRcvTsbPdDelay = isOPT_TsbPd() ? m_iTsbPdDelay_ms : 0; + perf->byteMSS = m_config.iMSS; + + perf->mbpsMaxBW = m_config.llMaxBW > 0 ? Bps2Mbps(m_config.llMaxBW) + : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) + : 0; + + if (clear) + { + m_stats.sndr.resetTrace(); + m_stats.rcvr.resetTrace(); + + m_stats.sndDuration = 0; + m_stats.tsLastSampleTime = currtime; + } + } + + const int64_t availbw = m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth.load(); perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize)); - if (pthread_mutex_trylock(&m_ConnectionLock) == 0) + if (tryEnterCS(m_ConnectionLock)) { if (m_pSndBuffer) { -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG if (instantaneous) { /* Get instant SndBuf instead of moving average for application-based Algorithm (such as NAE) in need of fast reaction to network condition changes. */ - perf->pktSndBuf = m_pSndBuffer->getCurrBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf)); + perf->pktSndBuf = m_pSndBuffer->getCurrBufSize((perf->byteSndBuf), (perf->msSndBuf)); } else { - perf->pktSndBuf = m_pSndBuffer->getAvgBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf)); + perf->pktSndBuf = m_pSndBuffer->getAvgBufSize((perf->byteSndBuf), (perf->msSndBuf)); } -#else - perf->pktSndBuf = m_pSndBuffer->getCurrBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf)); -#endif perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize); - //< - perf->byteAvailSndBuf = (m_iSndBufSize - perf->pktSndBuf) * m_iMSS; + perf->byteAvailSndBuf = (m_config.iSndBufSize - perf->pktSndBuf) * m_config.iMSS; } else { perf->byteAvailSndBuf = 0; - // new> perf->pktSndBuf = 0; perf->byteSndBuf = 0; perf->msSndBuf = 0; - //< } if (m_pRcvBuffer) { - perf->byteAvailRcvBuf = m_pRcvBuffer->getAvailBufSize() * m_iMSS; - // new> -#ifdef SRT_ENABLE_RCVBUFSZ_MAVG + ScopedLock lck(m_RcvBufferLock); + perf->byteAvailRcvBuf = (int) getAvailRcvBufferSizeNoLock() * m_config.iMSS; if (instantaneous) // no need for historical API for Rcv side { perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf); @@ -6601,68 +7509,30 @@ void CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) { perf->pktRcvBuf = m_pRcvBuffer->getRcvAvgDataSize(perf->byteRcvBuf, perf->msRcvBuf); } -#else - perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf); -#endif - //< } else { perf->byteAvailRcvBuf = 0; - // new> perf->pktRcvBuf = 0; perf->byteRcvBuf = 0; perf->msRcvBuf = 0; - //< } - pthread_mutex_unlock(&m_ConnectionLock); + leaveCS(m_ConnectionLock); } else { perf->byteAvailSndBuf = 0; perf->byteAvailRcvBuf = 0; - // new> perf->pktSndBuf = 0; perf->byteSndBuf = 0; perf->msSndBuf = 0; - perf->byteRcvBuf = 0; perf->msRcvBuf = 0; - //< - } - - if (clear) - { - m_stats.traceSndDrop = 0; - m_stats.traceRcvDrop = 0; - m_stats.traceSndBytesDrop = 0; - m_stats.traceRcvBytesDrop = 0; - m_stats.traceRcvUndecrypt = 0; - m_stats.traceRcvBytesUndecrypt = 0; - // new> - m_stats.traceBytesSent = m_stats.traceBytesRecv = m_stats.traceBytesRetrans = 0; - //< - m_stats.traceSent = m_stats.traceRecv = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans = - m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0; - m_stats.sndDuration = 0; - m_stats.traceRcvRetrans = 0; - m_stats.traceRcvBelated = 0; -#ifdef SRT_ENABLE_LOSTBYTESCOUNT - m_stats.traceRcvBytesLoss = 0; -#endif - - m_stats.sndFilterExtra = 0; - m_stats.rcvFilterExtra = 0; - - m_stats.rcvFilterSupply = 0; - m_stats.rcvFilterLoss = 0; - - m_stats.lastSampleTime = currtime; } } -void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) +bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) { // Special things that must be done HERE, not in SrtCongestion, // because it involves the input buffer in CUDT. It would be @@ -6672,14 +7542,14 @@ void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) // time when the sending buffer. For sanity check, check both first. if (!m_CongCtl.ready() || !m_pSndBuffer) { - LOGC(mglog.Error, - log << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY") - << "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created")); + LOGC(rslog.Error, + log << CONID() << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY") + << "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created")); - return; + return false; } - HLOGC(mglog.Debug, log << "updateCC: EVENT:" << TransmissionEventStr(evt)); + HLOGC(rslog.Debug, log << "updateCC: EVENT:" << TransmissionEventStr(evt)); if (evt == TEV_INIT) { @@ -6690,24 +7560,24 @@ void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) EInitEvent only_input = arg.get(); // false = TEV_INIT_RESET: in the beginning, or when MAXBW was changed. - if (only_input && m_llMaxBW) + if (only_input != TEV_INIT_RESET && m_config.llMaxBW) { - HLOGC(mglog.Debug, log << "updateCC/TEV_INIT: non-RESET stage and m_llMaxBW already set to " << m_llMaxBW); + HLOGC(rslog.Debug, log << CONID() << "updateCC/TEV_INIT: non-RESET stage and m_config.llMaxBW already set to " << m_config.llMaxBW); // Don't change } - else // either m_llMaxBW == 0 or only_input == TEV_INIT_RESET + else // either m_config.llMaxBW == 0 or only_input == TEV_INIT_RESET { // Use the values: // - if SRTO_MAXBW is >0, use it. // - if SRTO_MAXBW == 0, use SRTO_INPUTBW + SRTO_OHEADBW // - if SRTO_INPUTBW == 0, pass 0 to requst in-buffer sampling // Bytes/s - int bw = m_llMaxBW != 0 ? m_llMaxBW : // When used SRTO_MAXBW - m_llInputBW != 0 ? withOverhead(m_llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW - 0; // When both MAXBW and INPUTBW are 0, request in-buffer sampling + const int64_t bw = m_config.llMaxBW != 0 ? m_config.llMaxBW : // When used SRTO_MAXBW + m_config.llInputBW != 0 ? withOverhead(m_config.llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW + 0; // When both MAXBW and INPUTBW are 0, request in-buffer sampling // Note: setting bw == 0 uses BW_INFINITE value in LiveCC - m_CongCtl->updateBandwidth(m_llMaxBW, bw); + m_CongCtl->updateBandwidth(m_config.llMaxBW, bw); if (only_input == TEV_INIT_OHEADBW) { @@ -6716,13 +7586,13 @@ void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) } else { - // No need to calculate input reate if the bandwidth is set + // No need to calculate input rate if the bandwidth is set const bool disable_in_rate_calc = (bw != 0); m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc); } - HLOGC(mglog.Debug, - log << "updateCC/TEV_INIT: updating BW=" << m_llMaxBW + HLOGC(rslog.Debug, + log << CONID() << "updateCC/TEV_INIT: updating BW=" << m_config.llMaxBW << (only_input == TEV_INIT_RESET ? " (UNCHANGED)" : only_input == TEV_INIT_OHEADBW ? " (only Overhead)" : " (updated sampling rate)")); @@ -6731,11 +7601,11 @@ void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) // This part is also required only by LiveCC, however not // moved there due to that it needs access to CSndBuffer. - if (evt == TEV_ACK || evt == TEV_LOSSREPORT || evt == TEV_CHECKTIMER) + if (evt == TEV_ACK || evt == TEV_LOSSREPORT || evt == TEV_CHECKTIMER || evt == TEV_SYNC) { // Specific part done when MaxBW is set to 0 (auto) and InputBW is 0. // This requests internal input rate sampling. - if (m_llMaxBW == 0 && m_llInputBW == 0) + if (m_config.llMaxBW == 0 && m_config.llInputBW == 0) { // Get auto-calculated input rate, Bytes per second const int64_t inputbw = m_pSndBuffer->getInputRate(); @@ -6747,12 +7617,12 @@ void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) * and sendrate skyrockets for retransmission. * Keep previously set maximum in that case (inputbw == 0). */ - if (inputbw != 0) - m_CongCtl->updateBandwidth(0, withOverhead(inputbw)); // Bytes/sec + if (inputbw >= 0) + m_CongCtl->updateBandwidth(0, withOverhead(std::max(m_config.llMinInputBW, inputbw))); // Bytes/sec } } - HLOGC(mglog.Debug, log << "udpateCC: emitting signal for EVENT:" << TransmissionEventStr(evt)); + HLOGC(rslog.Debug, log << CONID() << "updateCC: emitting signal for EVENT:" << TransmissionEventStr(evt)); // Now execute a congctl-defined action for that event. EmitSignal(evt, arg); @@ -6765,94 +7635,151 @@ void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg) // NOTE: THESE things come from CCC class: // - m_dPktSndPeriod // - m_dCWndSize - m_ullInterval_tk = (uint64_t)(m_CongCtl->pktSndPeriod_us() * m_ullCPUFrequency); + m_tdSendInterval = microseconds_from((int64_t)m_CongCtl->pktSndPeriod_us()); m_dCongestionWindow = m_CongCtl->cgWindowSize(); #if ENABLE_HEAVY_LOGGING - HLOGC(mglog.Debug, - log << "updateCC: updated values from congctl: interval=" << m_ullInterval_tk << "tk (" - << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << m_dCongestionWindow); + HLOGC(rslog.Debug, + log << CONID() << "updateCC: updated values from congctl: interval=" << count_microseconds(m_tdSendInterval) << " us (" + << "tk (" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" + << std::setprecision(3) << m_dCongestionWindow); #endif } - HLOGC(mglog.Debug, log << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt)); - -#if 0 // debug - static int callcnt = 0; - if (!(callcnt++ % 250)) cerr << "SndPeriod=" << (m_ullInterval_tk/m_ullCPUFrequency) << "\n"); + HLOGC(rslog.Debug, log << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt)); -#endif + return true; } -void CUDT::initSynch() +void srt::CUDT::initSynch() { - pthread_mutex_init(&m_SendBlockLock, NULL); - pthread_cond_init(&m_SendBlockCond, NULL); - pthread_mutex_init(&m_RecvDataLock, NULL); - pthread_cond_init(&m_RecvDataCond, NULL); - pthread_mutex_init(&m_SendLock, NULL); - pthread_mutex_init(&m_RecvLock, NULL); - pthread_mutex_init(&m_RcvLossLock, NULL); - pthread_mutex_init(&m_RecvAckLock, NULL); - pthread_mutex_init(&m_RcvBufferLock, NULL); - pthread_mutex_init(&m_ConnectionLock, NULL); - pthread_mutex_init(&m_StatsLock, NULL); - - memset(&m_RcvTsbPdThread, 0, sizeof m_RcvTsbPdThread); - pthread_cond_init(&m_RcvTsbPdCond, NULL); + setupMutex(m_SendBlockLock, "SendBlock"); + setupCond(m_SendBlockCond, "SendBlock"); + setupCond(m_RecvDataCond, "RecvData"); + setupMutex(m_SendLock, "Send"); + setupMutex(m_RecvLock, "Recv"); + setupMutex(m_RcvLossLock, "RcvLoss"); + setupMutex(m_RecvAckLock, "RecvAck"); + setupMutex(m_RcvBufferLock, "RcvBuffer"); + setupMutex(m_ConnectionLock, "Connection"); + setupMutex(m_StatsLock, "Stats"); + setupCond(m_RcvTsbPdCond, "RcvTsbPd"); } -void CUDT::destroySynch() +void srt::CUDT::destroySynch() { - pthread_mutex_destroy(&m_SendBlockLock); - pthread_cond_destroy(&m_SendBlockCond); - pthread_mutex_destroy(&m_RecvDataLock); - pthread_cond_destroy(&m_RecvDataCond); - pthread_mutex_destroy(&m_SendLock); - pthread_mutex_destroy(&m_RecvLock); - pthread_mutex_destroy(&m_RcvLossLock); - pthread_mutex_destroy(&m_RecvAckLock); - pthread_mutex_destroy(&m_RcvBufferLock); - pthread_mutex_destroy(&m_ConnectionLock); - pthread_mutex_destroy(&m_StatsLock); - pthread_cond_destroy(&m_RcvTsbPdCond); + releaseMutex(m_SendBlockLock); + + // Just in case, signal the CV, on which some + // other thread is possibly waiting, because a + // process hanging on a pthread_cond_wait would + // cause the call to destroy a CV hang up. + m_SendBlockCond.notify_all(); + releaseCond(m_SendBlockCond); + + m_RecvDataCond.notify_all(); + releaseCond(m_RecvDataCond); + releaseMutex(m_SendLock); + releaseMutex(m_RecvLock); + releaseMutex(m_RcvLossLock); + releaseMutex(m_RecvAckLock); + releaseMutex(m_RcvBufferLock); + releaseMutex(m_ConnectionLock); + releaseMutex(m_StatsLock); + + m_RcvTsbPdCond.notify_all(); + releaseCond(m_RcvTsbPdCond); } -void CUDT::releaseSynch() +void srt::CUDT::releaseSynch() { + SRT_ASSERT(m_bClosing); // wake up user calls - pthread_mutex_lock(&m_SendBlockLock); - pthread_cond_signal(&m_SendBlockCond); - pthread_mutex_unlock(&m_SendBlockLock); + CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); + + enterCS(m_SendLock); + leaveCS(m_SendLock); + + // Awake tsbpd() and srt_recv*(..) threads for them to check m_bClosing. + CSync::lock_notify_one(m_RecvDataCond, m_RecvLock); + CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); + + // Azquiring m_RcvTsbPdStartupLock protects race in starting + // the tsbpd() thread in CUDT::processData(). + // Wait for tsbpd() thread to finish. + enterCS(m_RcvTsbPdStartupLock); + if (m_RcvTsbPdThread.joinable()) + { + m_RcvTsbPdThread.join(); + } + leaveCS(m_RcvTsbPdStartupLock); + + // Acquiring the m_RecvLock it is assumed that both tsbpd() + // and srt_recv*(..) threads will be aware about the state of m_bClosing. + enterCS(m_RecvLock); + leaveCS(m_RecvLock); +} - pthread_mutex_lock(&m_SendLock); - pthread_mutex_unlock(&m_SendLock); +// [[using locked(m_RcvBufferLock)]]; +void srt::CUDT::ackDataUpTo(int32_t ack) +{ + const int acksize SRT_ATR_UNUSED = CSeqNo::seqoff(m_iRcvLastSkipAck, ack); + + HLOGC(xtlog.Debug, log << "ackDataUpTo: %" << m_iRcvLastSkipAck << " -> %" << ack + << " (" << acksize << " packets)"); - pthread_mutex_lock(&m_RecvDataLock); - pthread_cond_signal(&m_RecvDataCond); - pthread_mutex_unlock(&m_RecvDataLock); + m_iRcvLastAck = ack; + m_iRcvLastSkipAck = ack; - pthread_mutex_lock(&m_RecvLock); - pthread_cond_signal(&m_RcvTsbPdCond); - pthread_mutex_unlock(&m_RecvLock); +#if !ENABLE_NEW_RCVBUFFER + // NOTE: This is new towards UDT and prevents spurious + // wakeup of select/epoll functions when no new packets + // were signed off for extraction. + if (acksize > 0) + { + m_pRcvBuffer->ackData(acksize); + } +#endif +} - pthread_mutex_lock(&m_RecvDataLock); - if (!pthread_equal(m_RcvTsbPdThread, pthread_t())) +#if ENABLE_BONDING && ENABLE_NEW_RCVBUFFER +void srt::CUDT::dropToGroupRecvBase() { + int32_t group_recv_base = SRT_SEQNO_NONE; + if (m_parent->m_GroupOf) { - pthread_join(m_RcvTsbPdThread, NULL); - m_RcvTsbPdThread = pthread_t(); + // Check is first done before locking to avoid unnecessary + // mutex locking. The condition for this field is that it + // can be either never set, already reset, or ever set + // and possibly dangling. The re-check after lock eliminates + // the dangling case. + ScopedLock glock (uglobal().m_GlobControlLock); + + // Note that getRcvBaseSeqNo() will lock m_GroupOf->m_GroupLock, + // but this is an intended order. + if (m_parent->m_GroupOf) + group_recv_base = m_parent->m_GroupOf->getRcvBaseSeqNo(); } - pthread_mutex_unlock(&m_RecvDataLock); + if (group_recv_base == SRT_SEQNO_NONE) + return; - pthread_mutex_lock(&m_RecvLock); - pthread_mutex_unlock(&m_RecvLock); + ScopedLock lck(m_RcvBufferLock); + int cnt = rcvDropTooLateUpTo(CSeqNo::incseq(group_recv_base)); + if (cnt > 0) + { + HLOGC(grlog.Debug, + log << "dropToGroupRecvBase: " << CONID() << " dropped " << cnt << " packets before ACK: group_recv_base=" + << group_recv_base << " m_iRcvLastSkipAck=" << m_iRcvLastSkipAck + << " m_iRcvCurrSeqNo=" << m_iRcvCurrSeqNo << " m_bTsbPd=" << m_bTsbPd); + } } +#endif +namespace srt { #if ENABLE_HEAVY_LOGGING static void DebugAck(string hdr, int prev, int ack) { if (!prev) { - HLOGC(mglog.Debug, log << hdr << "ACK " << ack); + HLOGC(xtlog.Debug, log << hdr << "ACK " << ack); return; } @@ -6860,7 +7787,7 @@ static void DebugAck(string hdr, int prev, int ack) int diff = CSeqNo::seqoff(prev, ack); if (diff < 0) { - HLOGC(mglog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")"); + HLOGC(xtlog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")"); return; } @@ -6870,210 +7797,35 @@ static void DebugAck(string hdr, int prev, int ack) ostringstream ackv; for (; prev != ack; prev = CSeqNo::incseq(prev)) - ackv << prev << " "; - if (shorted) - ackv << "..."; - HLOGC(mglog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack); -} -#else -static inline void DebugAck(string, int, int) {} -#endif - -void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, int size) -{ - CPacket ctrlpkt; - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - - ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - - int nbsent = 0; - int local_prevack = 0; - -#if ENABLE_HEAVY_LOGGING - struct SaveBack - { - int & target; - const int &source; - - ~SaveBack() { target = source; } - } l_saveback = {m_iDebugPrevLastAck, m_iRcvLastAck}; - (void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable] - - local_prevack = m_iDebugPrevLastAck; -#endif - - switch (pkttype) - { - case UMSG_ACK: // 010 - Acknowledgement - { - int32_t ack; - - // If there is no loss, the ACK is the current largest sequence number plus 1; - // Otherwise it is the smallest sequence number in the receiver loss list. - if (m_pRcvLossList->getLossLength() == 0) - ack = CSeqNo::incseq(m_iRcvCurrSeqNo); - else - ack = m_pRcvLossList->getFirstLostSeq(); - - if (m_iRcvLastAckAck == ack) - break; - - // send out a lite ACK - // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number - if (size == SEND_LITE_ACK) - { - ctrlpkt.pack(pkttype, NULL, &ack, size); - ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); - DebugAck("sendCtrl(lite):" + CONID(), local_prevack, ack); - break; - } - - // There are new received packets to acknowledge, update related information. - /* tsbpd thread may also call ackData when skipping packet so protect code */ - CGuard::enterCS(m_RcvBufferLock); - - // IF ack > m_iRcvLastAck - if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) - { - int acksize = CSeqNo::seqoff(m_iRcvLastSkipAck, ack); - - IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck); - m_iRcvLastAck = ack; - m_iRcvLastSkipAck = ack; - - // XXX Unknown as to whether it matters. - // This if (acksize) causes that ackData() won't be called. - // With size == 0 it wouldn't do anything except calling CTimer::triggerEvent(). - // This, again, signals the condition, CTimer::m_EventCond. - // This releases CTimer::waitForEvent() call used in CUDTUnited::selectEx(). - // Preventing to call this on zero size makes sense, if it prevents false alerts. - if (acksize > 0) - m_pRcvBuffer->ackData(acksize); - CGuard::leaveCS(m_RcvBufferLock); - - // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, - // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which - // will signal m_RecvDataCond when there's time to play for particular - // data packet. - HLOGC(dlog.Debug, - log << "ACK: clip %" << oldack << "-%" << ack << ", REVOKED " << acksize << " from RCV buffer"); - - if (m_bTsbPd) - { - /* Newly acknowledged data, signal TsbPD thread */ - pthread_mutex_lock(&m_RecvLock); - if (m_bTsbPdAckWakeup) - pthread_cond_signal(&m_RcvTsbPdCond); - pthread_mutex_unlock(&m_RecvLock); - } - else - { - if (m_bSynRecving) - { - // signal a waiting "recv" call if there is any data available - pthread_mutex_lock(&m_RecvDataLock); - pthread_cond_signal(&m_RecvDataCond); - pthread_mutex_unlock(&m_RecvDataLock); - } - // acknowledge any waiting epolls to read - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true); - CTimer::triggerEvent(); - } - CGuard::enterCS(m_RcvBufferLock); - } - else if (ack == m_iRcvLastAck) - { - // If the ACK was just sent already AND elapsed time did not exceed RTT, - if ((currtime_tk - m_ullLastAckTime_tk) < ((m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency)) - { - CGuard::leaveCS(m_RcvBufferLock); - break; - } - } - else - { - // Not possible (m_iRcvCurrSeqNo+1 < m_iRcvLastAck ?) - CGuard::leaveCS(m_RcvBufferLock); - break; - } - - // [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]] - - // Send out the ACK only if has not been received by the sender before - if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0) - { - // NOTE: The BSTATS feature turns on extra fields above size 6 - // also known as ACKD_TOTAL_SIZE_VER100. - int32_t data[ACKD_TOTAL_SIZE]; - - // Case you care, CAckNo::incack does exactly the same thing as - // CSeqNo::incseq. Logically the ACK number is a different thing - // than sequence number (it's a "journal" for ACK request-response, - // and starts from 0, unlike sequence, which starts from a random - // number), but still the numbers are from exactly the same domain. - m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo); - data[ACKD_RCVLASTACK] = m_iRcvLastAck; - data[ACKD_RTT] = m_iRTT; - data[ACKD_RTTVAR] = m_iRTTVar; - data[ACKD_BUFFERLEFT] = m_pRcvBuffer->getAvailBufSize(); - // a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock - if (data[ACKD_BUFFERLEFT] < 2) - data[ACKD_BUFFERLEFT] = 2; - - // NOTE: m_CongCtl->ACKTimeout_us() should be taken into account. - if (currtime_tk - m_ullLastAckTime_tk > m_ullACKInt_tk) - { - int rcvRate; - int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size - - data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed(Ref(rcvRate)); - data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); - - //>>Patch while incompatible (1.0.2) receiver floating around - if (m_lPeerSrtVersion == SrtVersion(1, 0, 2)) - { - data[ACKD_RCVRATE] = rcvRate; // bytes/sec - data[ACKD_XMRATE] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec - ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102; - } - else if (m_lPeerSrtVersion >= SrtVersion(1, 0, 3)) - { - // Normal, currently expected version. - data[ACKD_RCVRATE] = rcvRate; // bytes/sec - ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101; - } - // ELSE: leave the buffer with ...UDTBASE size. - - ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ctrlsz); - CTimer::rdtsc(m_ullLastAckTime_tk); - } - else - { - ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); - } + ackv << prev << " "; + if (shorted) + ackv << "..."; + HLOGC(xtlog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack); +} +#else +static inline void DebugAck(string, int, int) {} +#endif +} - ctrlpkt.m_iID = m_PeerID; - ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); - DebugAck("sendCtrl: " + CONID(), local_prevack, ack); +void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size) +{ + CPacket ctrlpkt; + setPacketTS(ctrlpkt, steady_clock::now()); - m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); + int nbsent = 0; - CGuard::enterCS(m_StatsLock); - ++m_stats.sentACK; - ++m_stats.sentACKTotal; - CGuard::leaveCS(m_StatsLock); - } - CGuard::leaveCS(m_RcvBufferLock); + switch (pkttype) + { + case UMSG_ACK: // 010 - Acknowledgement + { + nbsent = sendCtrlAck(ctrlpkt, size); break; } case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement ctrlpkt.pack(pkttype, lparam); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; @@ -7088,16 +7840,16 @@ void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, in ctrlpkt.pack(pkttype, NULL, lossdata, bytes); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); - CGuard::enterCS(m_StatsLock); - ++m_stats.sentNAK; - ++m_stats.sentNAKTotal; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.rcvr.sentNak.count(1); + leaveCS(m_StatsLock); } // Call with no arguments - get loss list from internal data. else if (m_pRcvLossList->getLossLength() > 0) { + ScopedLock lock(m_RcvLossLock); // this is periodically NAK report; make sure NAK cannot be sent back too often // read loss list from the local receiver loss list @@ -7109,28 +7861,29 @@ void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, in { ctrlpkt.pack(pkttype, NULL, data, losslen * 4); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); - CGuard::enterCS(m_StatsLock); - ++m_stats.sentNAK; - ++m_stats.sentNAKTotal; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.rcvr.sentNak.count(1); + leaveCS(m_StatsLock); } delete[] data; } // update next NAK time, which should wait enough time for the retansmission, but not too long - m_ullNAKInt_tk = (m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency; + m_tdNAKInterval = microseconds_from(m_iSRTT + 4 * m_iRTTVar); // Fix the NAKreport period according to the congctl - m_ullNAKInt_tk = m_CongCtl->updateNAKInterval( - m_ullNAKInt_tk, m_RcvTimeWindow.getPktRcvSpeed(), m_pRcvLossList->getLossLength()); + m_tdNAKInterval = + microseconds_from(m_CongCtl->updateNAKInterval(count_microseconds(m_tdNAKInterval), + m_RcvTimeWindow.getPktRcvSpeed(), + m_pRcvLossList->getLossLength())); // This is necessary because a congctl need not wish to define // its own minimum interval, in which case the default one is used. - if (m_ullNAKInt_tk < m_ullMinNakInt_tk) - m_ullNAKInt_tk = m_ullMinNakInt_tk; + if (m_tdNAKInterval < m_tdMinNakInterval) + m_tdNAKInterval = m_tdMinNakInterval; break; } @@ -7138,44 +7891,46 @@ void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, in case UMSG_CGWARNING: // 100 - Congestion Warning ctrlpkt.pack(pkttype); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); - CTimer::rdtsc(m_ullLastWarningTime); + m_tsLastWarningTime = steady_clock::now(); break; case UMSG_KEEPALIVE: // 001 - Keep-alive ctrlpkt.pack(pkttype); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; case UMSG_HANDSHAKE: // 000 - Handshake ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake)); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; case UMSG_SHUTDOWN: // 101 - Shutdown + if (m_PeerID == 0) // Dont't send SHUTDOWN if we don't know peer ID. + break; ctrlpkt.pack(pkttype); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; case UMSG_DROPREQ: // 111 - Msg drop request ctrlpkt.pack(pkttype, lparam, rparam, 8); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error ctrlpkt.pack(pkttype, lparam); ctrlpkt.m_iID = m_PeerID; - nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); break; @@ -7188,15 +7943,291 @@ void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, in // Fix keepalive if (nbsent) - m_ullLastSndTime_tk = currtime_tk; + m_tsLastSndTime.store(steady_clock::now()); +} + +int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) +{ + SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); + int32_t ack; // First unacknowledged packet seqnuence number (acknowledge up to ack). + int nbsent = 0; + int local_prevack = 0; + +#if ENABLE_HEAVY_LOGGING + struct SaveBack + { + int& target; + const int& source; + + ~SaveBack() { target = source; } + } l_saveback = { m_iDebugPrevLastAck, m_iRcvLastAck }; + (void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable] + + local_prevack = m_iDebugPrevLastAck; + + string reason = "first lost"; // just for "a reason" of giving particular % for ACK +#endif + +#if ENABLE_BONDING && ENABLE_NEW_RCVBUFFER + dropToGroupRecvBase(); +#endif + + { + // If there is no loss, the ACK is the current largest sequence number plus 1; + // Otherwise it is the smallest sequence number in the receiver loss list. + ScopedLock lock(m_RcvLossLock); + // TODO: Consider the Fresh Loss list as well!!! + ack = m_pRcvLossList->getFirstLostSeq(); + } + + // We don't need to check the length prematurely, + // if length is 0, this will return SRT_SEQNO_NONE. + // If so happened, simply use the latest received pkt + 1. + if (ack == SRT_SEQNO_NONE) + { + ack = CSeqNo::incseq(m_iRcvCurrSeqNo); + IF_HEAVY_LOGGING(reason = "expected next"); + } + + if (m_iRcvLastAckAck == ack) + { + HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); + return nbsent; + } + + // send out a lite ACK + // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number + if (size == SEND_LITE_ACK) + { + ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); + DebugAck("sendCtrl(lite):" + CONID(), local_prevack, ack); + return nbsent; + } + + // There are new received packets to acknowledge, update related information. + /* tsbpd thread may also call ackData when skipping packet so protect code */ + UniqueLock bufflock(m_RcvBufferLock); + + // IF ack %> m_iRcvLastAck + if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) + { + ackDataUpTo(ack); + +#if ENABLE_BONDING +#if ENABLE_NEW_RCVBUFFER + const int32_t group_read_seq = m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()).seqno; +#else + const int32_t group_read_seq = CSeqNo::decseq(ack); +#endif +#endif + + InvertedLock un_bufflock (m_RcvBufferLock); + +#if ENABLE_BONDING + // This actually should be done immediately after the ACK pointers were + // updated in this socket, but it can't be done inside this function due + // to being run under a lock. + + // At this moment no locks are applied. The only lock used so far + // was m_RcvBufferLock, but this was lifed above. At this moment + // it is safe to apply any locks here. This function is affined + // to CRcvQueue::worker thread, so it is free to apply locks as + // required in the defined order. At present we only need the lock + // on m_GlobControlLock to prevent the group from being deleted + // in the meantime + if (m_parent->m_GroupOf) + { + // Check is first done before locking to avoid unnecessary + // mutex locking. The condition for this field is that it + // can be either never set, already reset, or ever set + // and possibly dangling. The re-check after lock eliminates + // the dangling case. + ScopedLock glock (uglobal().m_GlobControlLock); + + // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, + // but this is an intended order. + if (m_parent->m_GroupOf) + { + // A group may need to update the parallelly used idle links, + // should it have any. Pass the current socket position in order + // to skip it from the group loop. + m_parent->m_GroupOf->updateLatestRcv(m_parent); + } + } +#endif + IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck); + + // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, + // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which + // will signal m_RecvDataCond when there's time to play for particular + // data packet. + HLOGC(xtlog.Debug, log << "ACK: clip %" << oldack << "-%" << ack + << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); + + if (m_bTsbPd) + { + /* Newly acknowledged data, signal TsbPD thread */ + CUniqueSync tslcc (m_RecvLock, m_RcvTsbPdCond); + // m_bTsbPdAckWakeup is protected by m_RecvLock in the tsbpd() thread + if (m_bTsbPdAckWakeup) + tslcc.notify_one(); + } + else + { + { + CUniqueSync rdcc (m_RecvLock, m_RecvDataCond); + +#if ENABLE_NEW_RCVBUFFER + // Locks m_RcvBufferLock, which is unlocked above by InvertedLock un_bufflock. + // Must check read-readiness under m_RecvLock to protect the epoll from concurrent changes in readBuffer() + if (isRcvBufferReady()) +#endif + { + if (m_config.bSynRecving) + { + // signal a waiting "recv" call if there is any data available + rdcc.notify_one(); + } + // acknowledge any waiting epolls to read + // fix SRT_EPOLL_IN event loss but rcvbuffer still have data: + // 1. user call receive/receivemessage(about line number:6482) + // 2. after read/receive, if rcvbuffer is empty, will set SRT_EPOLL_IN event to false + // 3. but if we do not do some lock work here, will cause some sync problems between threads: + // (1) user thread: call receive/receivemessage + // (2) user thread: read data + // (3) user thread: no data in rcvbuffer, set SRT_EPOLL_IN event to false + // (4) receive thread: receive data and set SRT_EPOLL_IN to true + // (5) user thread: set SRT_EPOLL_IN to false + // 4. so , m_RecvLock must be used here to protect epoll event + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); + } + } +#if ENABLE_BONDING + if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) + { + // See above explanation for double-checking + ScopedLock glock (uglobal().m_GlobControlLock); + + if (m_parent->m_GroupOf) + { + // The current "APP reader" needs to simply decide as to whether + // the next CUDTGroup::recv() call should return with no blocking or not. + // When the group is read-ready, it should update its pollers as it sees fit. + m_parent->m_GroupOf->updateReadState(m_SocketID, group_read_seq); + } + } +#endif + CGlobEvent::triggerEvent(); + } + } + else if (ack == m_iRcvLastAck) + { + // If the ACK was just sent already AND elapsed time did not exceed RTT, + if ((steady_clock::now() - m_tsLastAckTime) < + (microseconds_from(m_iSRTT + 4 * m_iRTTVar))) + { + HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat"); + return nbsent; + } + } + else + { + // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) + LOGC(xtlog.Error, log << "sendCtrl(UMSG_ACK): IPE: curr %" << ack + << " <% last %" << m_iRcvLastAck); + return nbsent; + } + + // [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]]; + // [[using locked(m_RcvBufferLock)]]; + + // Send out the ACK only if has not been received by the sender before + if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0) + { + // NOTE: The BSTATS feature turns on extra fields above size 6 + // also known as ACKD_TOTAL_SIZE_VER100. + int32_t data[ACKD_TOTAL_SIZE]; + + // Case you care, CAckNo::incack does exactly the same thing as + // CSeqNo::incseq. Logically the ACK number is a different thing + // than sequence number (it's a "journal" for ACK request-response, + // and starts from 0, unlike sequence, which starts from a random + // number), but still the numbers are from exactly the same domain. + m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo); + data[ACKD_RCVLASTACK] = m_iRcvLastAck; + data[ACKD_RTT] = m_iSRTT; + data[ACKD_RTTVAR] = m_iRTTVar; + data[ACKD_BUFFERLEFT] = (int) getAvailRcvBufferSizeNoLock(); + // a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock + if (data[ACKD_BUFFERLEFT] < 2) + data[ACKD_BUFFERLEFT] = 2; + + if (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval) + { + int rcvRate; + int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size + + data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed((rcvRate)); + data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); + + //>>Patch while incompatible (1.0.2) receiver floating around + if (m_uPeerSrtVersion == SrtVersion(1, 0, 2)) + { + data[ACKD_RCVRATE] = rcvRate; // bytes/sec + data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec + ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102_ONLY; + } + else if (m_uPeerSrtVersion >= SrtVersion(1, 0, 3)) + { + // Normal, currently expected version. + data[ACKD_RCVRATE] = rcvRate; // bytes/sec + ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101; + } + // ELSE: leave the buffer with ...UDTBASE size. + + ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ctrlsz); + m_tsLastAckTime = steady_clock::now(); + } + else + { + ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); + } + + ctrlpkt.m_iID = m_PeerID; + setPacketTS(ctrlpkt, steady_clock::now()); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt); + DebugAck("sendCtrl(UMSG_ACK): " + CONID(), local_prevack, ack); + + m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); + + enterCS(m_StatsLock); + m_stats.rcvr.sentAck.count(1); + leaveCS(m_StatsLock); + } + else + { + HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): " << CONID() << "ACK %" << m_iRcvLastAck + << " <=% ACKACK %" << m_iRcvLastAckAck << " - NOT SENDING ACK"); + } + + return nbsent; } -void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) +void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) { +#if ENABLE_BONDING + // This is for the call of CSndBuffer::getMsgNoAt that returns + // this value as a notfound-trap. + int32_t msgno_at_last_acked_seq = SRT_MSGNO_CONTROL; + bool is_group = m_parent->m_GroupOf; +#endif + // Update sender's loss list and acknowledge packets in the sender's buffer { // m_RecvAckLock protects sender's loss list and epoll - CGuard ack_lock(m_RecvAckLock); + ScopedLock ack_lock(m_RecvAckLock); const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno); // IF distance between m_iSndLastDataAck and ack is nonempty... @@ -7206,41 +8237,90 @@ void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) // update sending variables m_iSndLastDataAck = ackdata_seqno; +#if ENABLE_BONDING + if (is_group) + { + // Get offset-1 because 'offset' points actually to past-the-end + // of the sender buffer. We have already checked that offset is + // at least 1. + msgno_at_last_acked_seq = m_pSndBuffer->getMsgNoAt(offset-1); + // Just keep this value prepared; it can't be updated exactly right + // now because accessing the group needs some locks to be applied + // with preserved the right locking order. + } +#endif + // remove any loss that predates 'ack' (not to be considered loss anymore) - m_pSndLossList->remove(CSeqNo::decseq(m_iSndLastDataAck)); + m_pSndLossList->removeUpTo(CSeqNo::decseq(m_iSndLastDataAck)); // acknowledge the sending buffer (remove data that predate 'ack') m_pSndBuffer->ackData(offset); // acknowledde any waiting epolls to write - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); + CGlobEvent::triggerEvent(); + } + +#if ENABLE_BONDING + if (is_group) + { + // m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only + // be done now that m_RecvAckLock is unlocked. + ScopedLock glock (uglobal().m_GlobControlLock); + if (m_parent->m_GroupOf) + { + HLOGC(inlog.Debug, log << "ACK: acking group sender buffer for #" << msgno_at_last_acked_seq); + + // Guard access to m_iSndAckedMsgNo field + // Note: This can't be done inside CUDTGroup::ackMessage + // because this function is also called from CUDT::sndDropTooLate + // called from CUDT::sendmsg2 called from CUDTGroup::send, which + // applies the lock on m_GroupLock already. + ScopedLock glk (*m_parent->m_GroupOf->exp_groupLock()); + + // NOTE: ackMessage also accepts and ignores the trap representation + // which is SRT_MSGNO_CONTROL. + m_parent->m_GroupOf->ackMessage(msgno_at_last_acked_seq); + } } +#endif // insert this socket to snd list if it is not on the list yet - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + const steady_clock::time_point currtime = steady_clock::now(); + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE, currtime); - if (m_bSynSending) + if (m_config.bSynSending) { - CGuard lk(m_SendBlockLock); - pthread_cond_signal(&m_SendBlockCond); + CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); } - const int64_t currtime = CTimer::getTime(); // record total time used for sending - CGuard::enterCS(m_StatsLock); - m_stats.sndDuration += currtime - m_stats.sndDurationCounter; - m_stats.m_sndDurationTotal += currtime - m_stats.sndDurationCounter; + enterCS(m_StatsLock); + m_stats.sndDuration += count_microseconds(currtime - m_stats.sndDurationCounter); + m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.sndDurationCounter = currtime; - CGuard::leaveCS(m_StatsLock); + leaveCS(m_StatsLock); } -void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) +void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) { - const int32_t *ackdata = (const int32_t *)ctrlpkt.m_pcData; + const int32_t* ackdata = (const int32_t*)ctrlpkt.m_pcData; const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK]; + // Check the value of ACK in case when it was some rogue peer + if (ackdata_seqno < 0) + { + // This embraces all cases when the most significant bit is set, + // as the variable is of a signed type. So, SRT_SEQNO_NONE is + // included, but it also triggers for any other kind of invalid value. + // This check MUST BE DONE before making any operation on this number. + LOGC(inlog.Error, log << CONID() << "ACK: IPE/EPE: received invalid ACK value: " << ackdata_seqno + << " " << std::hex << ackdata_seqno << " (IGNORED)"); + return; + } + const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; - HLOGC(mglog.Debug, + HLOGC(inlog.Debug, log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); @@ -7251,13 +8331,13 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) { if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { - CGuard ack_lock(m_RecvAckLock); - m_iFlowWindowSize -= CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); + ScopedLock ack_lock(m_RecvAckLock); + m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; - // TODO: m_ullLastRspAckTime_tk should be protected with m_RecvAckLock + // TODO: m_tsLastRspAckTime should be protected with m_RecvAckLock // because the sendmsg2 may want to change it at the same time. - m_ullLastRspAckTime_tk = currtime_tk; + m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } @@ -7273,12 +8353,11 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) // There can be less ACKACK packets in the stream, than the number of ACK packets. // Only send ACKACK every syn interval or if ACK packet with the sequence number // already acknowledged (with ACKACK) has come again, which probably means ACKACK was lost. - const uint64_t now = CTimer::getTime(); - if ((now - m_ullSndLastAck2Time > (uint64_t)COMM_SYN_INTERVAL_US) || (ack_seqno == m_iSndLastAck2)) + if ((currtime - m_SndLastAck2Time > microseconds_from(COMM_SYN_INTERVAL_US)) || (ack_seqno == m_iSndLastAck2)) { sendCtrl(UMSG_ACKACK, &ack_seqno); m_iSndLastAck2 = ack_seqno; - m_ullSndLastAck2Time = now; + m_SndLastAck2Time = currtime; } } @@ -7287,16 +8366,16 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) // // Protect packet retransmission - CGuard::enterCS(m_RecvAckLock); + enterCS(m_RecvAckLock); // Check the validation of the ack if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) { - CGuard::leaveCS(m_RecvAckLock); + leaveCS(m_RecvAckLock); // this should not happen: attack or bug - LOGC(glog.Error, - log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current " - << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); + LOGC(gglog.Error, + log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current " + << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); m_bBroken = true; m_iBrokenCounter = 0; return; @@ -7305,10 +8384,10 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { // Update Flow Window Size, must update before and together with m_iSndLastAck - m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; - m_iSndLastAck = ackdata_seqno; - m_ullLastRspAckTime_tk = currtime_tk; // Should be protected with m_RecvAckLock - m_iReXmitCount = 1; // Reset re-transmit count since last ACK + m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ackdata_seqno; + m_tsLastRspAckTime = currtime; + m_iReXmitCount = 1; // Reset re-transmit count since last ACK } /* @@ -7324,7 +8403,7 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) { // discard it if it is a repeated ACK - CGuard::leaveCS(m_RecvAckLock); + leaveCS(m_RecvAckLock); return; } m_iSndLastFullAck = ackdata_seqno; @@ -7332,7 +8411,19 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) // // END of the new code with TLPKTDROP // - CGuard::leaveCS(m_RecvAckLock); + leaveCS(m_RecvAckLock); +#if ENABLE_BONDING + if (m_parent->m_GroupOf) + { + ScopedLock glock (uglobal().m_GlobControlLock); + if (m_parent->m_GroupOf) + { + // Will apply m_GroupLock, ordered after m_GlobControlLock. + // m_GlobControlLock is necessary for group existence. + m_parent->m_GroupOf->updateWriteState(); + } + } +#endif size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING bool wrongsize = 0 != (acksize % ACKD_FIELD_SIZE); @@ -7341,7 +8432,7 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) if (wrongsize) { // Issue a log, but don't do anything but skipping the "odd" bytes from the payload. - LOGC(mglog.Error, + LOGC(inlog.Warn, log << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to " << acksize << " fields"); } @@ -7349,21 +8440,71 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) // Start with checking the base size. if (acksize < ACKD_TOTAL_SIZE_SMALL) { - LOGC(mglog.Error, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!"); + LOGC(inlog.Warn, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!"); // Ack is already interpreted, just skip further parts. return; } // This check covers fields up to ACKD_BUFFERLEFT. - // Update RTT - // m_iRTT = ackdata[ACKD_RTT]; - // m_iRTTVar = ackdata[ACKD_RTTVAR]; - // XXX These ^^^ commented-out were blocked in UDT; - // the current RTT calculations are exactly the same as in UDT4. - const int rtt = ackdata[ACKD_RTT]; + // Extract RTT estimate and RTTVar from the ACK packet. + const int rtt = ackdata[ACKD_RTT]; + const int rttvar = ackdata[ACKD_RTTVAR]; + + // Update the values of smoothed RTT and the variation in RTT samples + // on subsequent RTT estimates extracted from the ACK packets + // (during transmission). + if (m_bIsFirstRTTReceived) + { + // Suppose transmission is bidirectional if sender is also receiving + // data packets. + enterCS(m_StatsLock); + const bool bPktsReceived = m_stats.rcvr.recvd.total.count() != 0; + leaveCS(m_StatsLock); + + if (bPktsReceived) // Transmission is bidirectional. + { + // RTT value extracted from the ACK packet (rtt) is already smoothed + // RTT obtained at the receiver side. Apply EWMA anyway for the second + // time on the sender side. Ignore initial values which might arrive + // after the smoothed RTT on the sender side has been + // reset to the very first RTT sample received from the receiver. + // TODO: The case of bidirectional transmission requires further + // improvements and testing. Double smoothing is applied here to be + // consistent with the previous behavior. + if (rtt != INITIAL_RTT || rttvar != INITIAL_RTTVAR) + { + int iSRTT = m_iSRTT.load(), iRTTVar = m_iRTTVar.load(); + iRTTVar = avg_iir<4>(iRTTVar, abs(rtt - iSRTT)); + iSRTT = avg_iir<8>(iSRTT, rtt); + m_iSRTT = iSRTT; + m_iRTTVar = iRTTVar; + } + } + else // Transmission is unidirectional. + { + // Simply take the values of smoothed RTT and RTT variance from + // the ACK packet. + m_iSRTT = rtt; + m_iRTTVar = rttvar; + } + } + // Reset the value of smoothed RTT to the first real RTT estimate extracted + // from an ACK after initialization (at the beginning of transmission). + // In case of resumed connection over the same network, the very first RTT + // value sent within an ACK will be taken from cache and equal to previous + // connection's final smoothed RTT value. The reception of such a value + // will also trigger the smoothed RTT reset at the sender side. + else if (rtt != INITIAL_RTT && rttvar != INITIAL_RTTVAR) + { + m_iSRTT = rtt; + m_iRTTVar = rttvar; + m_bIsFirstRTTReceived = true; + } - m_iRTTVar = avg_iir<4>(m_iRTTVar, abs(rtt - m_iRTT)); - m_iRTT = avg_iir<8>(m_iRTT, rtt); +#if SRT_DEBUG_RTT + s_rtt_trace.trace(currtime, "ACK", rtt, rttvar, m_bIsFirstRTTReceived, + m_stats.recvTotal, m_iSRTT, m_iRTTVar); +#endif /* Version-dependent fields: * Original UDT (total size: ACKD_TOTAL_SIZE_SMALL): @@ -7374,10 +8515,10 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) * Additional UDT fields, not always attached: * ACKD_RCVSPEED * ACKD_BANDWIDTH - * SRT extension version 1.0.2 (bstats): + * SRT extension since v1.0.1: * ACKD_RCVRATE - * SRT extension version 1.0.4: - * ACKD_XMRATE + * SRT extension in v1.0.2 only: + * ACKD_XMRATE_VER102_ONLY */ if (acksize > ACKD_TOTAL_SIZE_SMALL) @@ -7387,7 +8528,7 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) int bandwidth = ackdata[ACKD_BANDWIDTH]; int bytesps; - /* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE]) and delivery rate (pcData[ACKD_RCVRATE]) in + /* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE_VER102_ONLY]) and delivery rate (pcData[ACKD_RCVRATE]) in * bytes/sec instead of pkts/sec */ /* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */ if (acksize > ACKD_TOTAL_SIZE_UDTBASE) @@ -7395,11 +8536,9 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) else bytesps = pktps * m_iMaxSRTPayloadSize; - m_iBandwidth = avg_iir<8>(m_iBandwidth, bandwidth); - m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate, pktps); - m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate, bytesps); - // XXX not sure if ACKD_XMRATE is of any use. This is simply - // calculated as ACKD_BANDWIDTH * m_iMaxSRTPayloadSize. + m_iBandwidth = avg_iir<8>(m_iBandwidth.load(), bandwidth); + m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate.load(), pktps); + m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate.load(), bytesps); // Update Estimated Bandwidth and packet delivery rate // m_iRcvRate = m_iDeliveryRate; @@ -7409,85 +8548,115 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) } checkSndTimers(REGEN_KM); - updateCC(TEV_ACK, ackdata_seqno); + updateCC(TEV_ACK, EventVariant(ackdata_seqno)); - CGuard::enterCS(m_StatsLock); - ++m_stats.recvACK; - ++m_stats.recvACKTotal; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.sndr.recvdAck.count(1); + leaveCS(m_StatsLock); } -void CUDT::processCtrl(CPacket &ctrlpkt) +void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) { - // Just heard from the peer, reset the expiration count. - m_iEXPCount = 1; - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - m_ullLastRspTime_tk = currtime_tk; - bool using_rexmit_flag = m_bPeerRexmitFlag; - - HLOGC(mglog.Debug, - log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID); + int32_t ack = 0; - switch (ctrlpkt.getType()) - { - case UMSG_ACK: // 010 - Acknowledgement - processCtrlAck(ctrlpkt, currtime_tk); - break; + // Calculate RTT estimate on the receiver side based on ACK/ACKACK pair. + const int rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack, tsArrival); - case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement + if (rtt == -1) { - int32_t ack = 0; - int rtt = -1; - - // update RTT - rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack); - if (rtt <= 0) + if (ctrlpkt.getAckSeqNo() > (m_iAckSeqNo - static_cast(ACK_WND_SIZE)) && ctrlpkt.getAckSeqNo() <= m_iAckSeqNo) { - LOGC(mglog.Error, - log << "IPE: ACK node overwritten when acknowledging " << ctrlpkt.getAckSeqNo() - << " (ack extracted: " << ack << ")"); - break; + LOGC(inlog.Note, + log << CONID() << "ACKACK out of order, skipping RTT calculation " + << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo + << ", RTT (EWMA): " << m_iSRTT << ")"); + return; } - // if increasing delay detected... - // sendCtrl(UMSG_CGWARNING); + LOGC(inlog.Error, + log << CONID() << "IPE: ACK record not found, can't estimate RTT " + << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo + << ", RTT (EWMA): " << m_iSRTT << ")"); + return; + } - // RTT EWMA - m_iRTTVar = (m_iRTTVar * 3 + abs(rtt - m_iRTT)) >> 2; - m_iRTT = (m_iRTT * 7 + rtt) >> 3; + if (rtt <= 0) + { + LOGC(inlog.Error, + log << CONID() << "IPE: invalid RTT estimate " << rtt + << ", possible time shift. Clock: " << SRT_SYNC_CLOCK_STR); + return; + } - updateCC(TEV_ACKACK, ack); + // If increasing delay is detected. + // sendCtrl(UMSG_CGWARNING); - // This function will put a lock on m_RecvLock by itself, as needed. - // It must be done inside because this function reads the current time - // and if waiting for the lock has caused a delay, the time will be - // inaccurate. Additionally it won't lock if TSBPD mode is off, and - // won't update anything. Note that if you set TSBPD mode and use - // srt_recvfile (which doesn't make any sense), you'll have a deadlock. - m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), m_RecvLock); + // Update the values of smoothed RTT and the variation in RTT samples + // on subsequent RTT samples (during transmission). + if (m_bIsFirstRTTReceived) + { + m_iRTTVar = avg_iir<4>(m_iRTTVar.load(), abs(rtt - m_iSRTT.load())); + m_iSRTT = avg_iir<8>(m_iSRTT.load(), rtt); + } + // Reset the value of smoothed RTT on the first RTT sample after initialization + // (at the beginning of transmission). + // In case of resumed connection over the same network, the initial RTT + // value will be taken from cache and equal to previous connection's + // final smoothed RTT value. + else + { + m_iSRTT = rtt; + m_iRTTVar = rtt / 2; + m_bIsFirstRTTReceived = true; + } - // update last ACK that has been received by the sender - if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) - m_iRcvLastAckAck = ack; +#if SRT_DEBUG_RTT + s_rtt_trace.trace(tsArrival, "ACKACK", rtt, -1, m_bIsFirstRTTReceived, + -1, m_iSRTT, m_iRTTVar); +#endif - break; - } + updateCC(TEV_ACKACK, EventVariant(ack)); - case UMSG_LOSSREPORT: // 011 - Loss Report + // This function will put a lock on m_RecvLock by itself, as needed. + // It must be done inside because this function reads the current time + // and if waiting for the lock has caused a delay, the time will be + // inaccurate. Additionally it won't lock if TSBPD mode is off, and + // won't update anything. Note that if you set TSBPD mode and use + // srt_recvfile (which doesn't make any sense), you'll have a deadlock. + if (m_config.bDriftTracer) { - int32_t *losslist = (int32_t *)(ctrlpkt.m_pcData); - size_t losslist_len = ctrlpkt.getLength() / 4; + const bool drift_updated SRT_ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); +#if ENABLE_BONDING + if (drift_updated && m_parent->m_GroupOf) + { + ScopedLock glock(uglobal().m_GlobControlLock); + if (m_parent->m_GroupOf) + { + m_parent->m_GroupOf->synchronizeDrift(this); + } + } +#endif + } + + // Update last ACK that has been received by the sender + if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) + m_iRcvLastAckAck = ack; +} - bool secure = true; +void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) +{ + const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData); + const size_t losslist_len = ctrlpkt.getLength() / 4; + + bool secure = true; - // protect packet retransmission - CGuard::enterCS(m_RecvAckLock); + // This variable is used in "normal" logs, so it may cause a warning + // when logging is forcefully off. + int32_t wrong_loss SRT_ATR_UNUSED = CSeqNo::m_iMaxSeqNo; - // This variable is used in "normal" logs, so it may cause a warning - // when logging is forcefully off. - int32_t wrong_loss SRT_ATR_UNUSED = CSeqNo::m_iMaxSeqNo; + // protect packet retransmission + { + ScopedLock ack_lock(m_RecvAckLock); // decode loss list message and insert loss into the sender loss list for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++i) @@ -7495,32 +8664,37 @@ void CUDT::processCtrl(CPacket &ctrlpkt) if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) { // Then it's this is a specification with HI in a consecutive cell. - int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]); - int32_t losslist_hi = losslist[i + 1]; + const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]); + const int32_t losslist_hi = losslist[i + 1]; // specification means that the consecutive cell has been already interpreted. ++i; - HLOGF(mglog.Debug, - "received UMSG_LOSSREPORT: %d-%d (%d packets)...", - losslist_lo, - losslist_hi, - CSeqNo::seqoff(losslist_lo, losslist_hi) + 1); + HLOGF(inlog.Debug, + "%sreceived UMSG_LOSSREPORT: %d-%d (%d packets)...", CONID().c_str(), + losslist_lo, + losslist_hi, + CSeqNo::seqoff(losslist_lo, losslist_hi) + 1); if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) || (CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0)) { + LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT rng " << losslist_lo << " - " << losslist_hi + << " with last sent " << m_iSndCurrSeqNo << " - DISCARDING"); // seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq - secure = false; + secure = false; wrong_loss = losslist_hi; - // XXX leaveCS: really necessary? 'break' will break the 'for' loop, not the 'switch' statement. - // and the leaveCS is done again next to the 'for' loop end. - CGuard::leaveCS(m_RecvAckLock); break; } int num = 0; + // IF losslist_lo %>= m_iSndLastAck if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0) + { + HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " + << losslist_lo << " - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(losslist_lo, losslist_hi); + } + // ELSE IF losslist_hi %>= m_iSndLastAck else if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0) { // This should be theoretically impossible because this would mean @@ -7529,65 +8703,330 @@ void CUDT::processCtrl(CPacket &ctrlpkt) // However, this can happen if the packet reordering has caused the earlier sent // LOSSREPORT will be delivered after later sent ACK. Whatever, ACK should be // more important, so simply drop the part that predates ACK. + HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " + << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); } + else + { + // This should be treated as IPE, but this may happen in one situtation: + // - redundancy second link (ISN was screwed up initially, but late towards last sent) + // - initial DROPREQ was lost + // This just causes repeating DROPREQ, as when the receiver continues sending + // LOSSREPORT, it's probably UNAWARE OF THE SITUATION. + // + // When this DROPREQ gets lost in UDP again, the receiver will do one of these: + // - repeatedly send LOSSREPORT (as per NAKREPORT), so this will happen again + // - finally give up rexmit request as per TLPKTDROP (DROPREQ should make + // TSBPD wake up should it still wait for new packets to get ACK-ed) + + HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: IGNORED with SndLastAck=%" + << m_iSndLastAck << ": %" << losslist_lo << "-" << losslist_hi + << " - sending DROPREQ (IPE or DROPREQ lost with ISN screw)"); + + // This means that the loss touches upon a range that wasn't ever sent. + // Normally this should never happen, but this might be a case when the + // ISN FIX for redundant connection was missed. + + // In distinction to losslist, DROPREQ has always a range + // always just one range, and the data are , with no range bit. + int32_t seqpair[2] = { losslist_lo, losslist_hi }; + const int32_t no_msgno = 0; // We don't know - this wasn't ever sent + + sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); + } - CGuard::enterCS(m_StatsLock); - m_stats.traceSndLoss += num; - m_stats.sndLossTotal += num; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.sndr.lost.count(num); + leaveCS(m_StatsLock); } else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0) { - HLOGF(mglog.Debug, "received UMSG_LOSSREPORT: %d (1 packet)...", losslist[i]); - if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0) { + LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT pkt %" << losslist[i] + << " with last sent %" << m_iSndCurrSeqNo << " - DISCARDING"); // seq_a must not be greater than the most recent sent seq - secure = false; + secure = false; wrong_loss = losslist[i]; - CGuard::leaveCS(m_RecvAckLock); break; } - int num = m_pSndLossList->insert(losslist[i], losslist[i]); + HLOGC(inlog.Debug, log << CONID() << "rcv LOSSREPORT: %" + << losslist[i] << " (1 packet)"); + const int num = m_pSndLossList->insert(losslist[i], losslist[i]); + + enterCS(m_StatsLock); + m_stats.sndr.lost.count(num); + leaveCS(m_StatsLock); + } + } + } + + updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); + + if (!secure) + { + LOGC(inlog.Warn, + log << CONID() << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo + << " vs loss %" << wrong_loss); + // this should not happen: attack or bug + m_bBroken = true; + m_iBrokenCounter = 0; + return; + } + + // the lost packet (retransmission) should be sent out immediately + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + + enterCS(m_StatsLock); + m_stats.sndr.recvdNak.count(1); + leaveCS(m_StatsLock); +} + +void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) +{ + CHandShake req; + req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); + + HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show()); + + if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...??? + || (m_config.bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION + { + // The peer side has not received the handshake message, so it keeps querying + // resend the handshake packet + + // This condition embraces cases when: + // - this is normal accept() and URQ_INDUCTION was received + // - this is rendezvous accept() and there's coming any kind of URQ except AGREEMENT (should be RENDEZVOUS + // or CONCLUSION) + // - this is any of URQ_ERROR_* - well... + CHandShake initdata; + initdata.m_iISN = m_iISN; + initdata.m_iMSS = m_config.iMSS; + initdata.m_iFlightFlagSize = m_config.iFlightFlagSize; + + // For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT. + // For client-server we do URQ_INDUCTION --> URQ_CONCLUSION. + initdata.m_iReqType = (!m_config.bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT; + initdata.m_iID = m_SocketID; + + uint32_t kmdata[SRTDATA_MAXSIZE]; + size_t kmdatasize = SRTDATA_MAXSIZE; + bool have_hsreq = false; + if (req.m_iVersion > HS_VERSION_UDT4) + { + initdata.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener... + const int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); + if (hs_flags != 0) // has SRT extensions + { + HLOGC(inlog.Debug, + log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType) + << " WITH SRT ext"); + have_hsreq = interpretSrtHandshake(req, ctrlpkt, (kmdata), (&kmdatasize)); + if (!have_hsreq) + { + initdata.m_iVersion = 0; + m_RejectReason = SRT_REJ_ROGUE; + initdata.m_iReqType = URQFailure(m_RejectReason); + } + else + { + // Extensions are added only in case of CONCLUSION (not AGREEMENT). + // Actually what is expected here is that this may either process the + // belated-repeated handshake from a caller (and then it's CONCLUSION, + // and should be added with HSRSP/KMRSP), or it's a belated handshake + // of Rendezvous when it has already considered itself connected. + // Sanity check - according to the rules, there should be no such situation + if (m_config.bRendezvous && m_SrtHsSide == HSD_RESPONDER) + { + LOGC(inlog.Error, + log << CONID() << "processCtrl/HS: IPE???: RESPONDER should receive all its handshakes in " + "handshake phase."); + } + + // The 'extension' flag will be set from this variable; set it to false + // in case when the AGREEMENT response is to be sent. + have_hsreq = initdata.m_iReqType == URQ_CONCLUSION; + HLOGC(inlog.Debug, + log << CONID() << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType) + << " kmdatasize=" << kmdatasize); + } + } + else + { + HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)); + } + } + else + { + initdata.m_iVersion = HS_VERSION_UDT4; + kmdatasize = 0; // HSv4 doesn't add any extensions, no KMX + } + + initdata.m_extension = have_hsreq; + + HLOGC(inlog.Debug, + log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType) + << (have_hsreq ? " WITH SRT HS response extensions" : "")); + + CPacket response; + response.setControl(UMSG_HANDSHAKE); + response.allocate(m_iMaxSRTPayloadSize); + + // If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE. + // There is also no possible IPE condition in case of HSv4 - for this version it will always return true. + if (createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, + (response), (initdata))) + { + response.m_iID = m_PeerID; + setPacketTS(response, steady_clock::now()); + const int nbsent = m_pSndQueue->sendto(m_PeerAddr, response); + if (nbsent) + { + m_tsLastSndTime.store(steady_clock::now()); + } + } + } + else + { + HLOGC(inlog.Debug, log << CONID() << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."); + } +} + +void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) +{ + const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; + + { + CUniqueSync rcvtscc (m_RecvLock, m_RcvTsbPdCond); + // With both TLPktDrop and TsbPd enabled, a message always consists only of one packet. + // It will be dropped as too late anyway. Not dropping it from the receiver buffer + // in advance reduces false drops if the packet somehow manages to arrive. + // Still remove the record from the loss list to cease further retransmission requests. + if (!m_bTLPktDrop || !m_bTsbPd) + { + const bool using_rexmit_flag = m_bPeerRexmitFlag; + ScopedLock rblock(m_RcvBufferLock); +#if ENABLE_NEW_RCVBUFFER + const int iDropCnt = m_pRcvBuffer->dropMessage(dropdata[0], dropdata[1], ctrlpkt.getMsgSeq(using_rexmit_flag)); + + if (iDropCnt > 0) + { + LOGC(brlog.Warn, log << CONID() << "RCV-DROPPED " << iDropCnt << " packet(s), seqno range %" + << dropdata[0] << "-%" << dropdata[1] << ", msgno " << ctrlpkt.getMsgSeq(using_rexmit_flag) + << " (SND DROP REQUEST)."); + + enterCS(m_StatsLock); + // Estimate dropped bytes from average payload size. + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); + leaveCS(m_StatsLock); + } +#else + m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag); +#endif + } + // When the drop request was received, it means that there are + // packets for which there will never be ACK sent; if the TSBPD thread + // is currently in the ACK-waiting state, it may never exit. + if (m_bTsbPd) + { + HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); + rcvtscc.notify_one(); + } + } + + dropFromLossLists(dropdata[0], dropdata[1]); - CGuard::enterCS(m_StatsLock); - m_stats.traceSndLoss += num; - m_stats.sndLossTotal += num; - CGuard::leaveCS(m_StatsLock); - } - } - CGuard::leaveCS(m_RecvAckLock); + // If dropping ahead of the current largest sequence number, + // move the recv seq number forward. + if ((CSeqNo::seqcmp(dropdata[0], CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) + && (CSeqNo::seqcmp(dropdata[1], m_iRcvCurrSeqNo) > 0)) + { + HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" + << dropdata[0] << "-" << dropdata[1] << " <-- set as current seq"); + m_iRcvCurrSeqNo = dropdata[1]; + } + else + { + HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" + << dropdata[0] << "-" << dropdata[1] << " current %" << m_iRcvCurrSeqNo); + } +} + +void srt::CUDT::processCtrlShutdown() +{ + m_bShutdown = true; + m_bClosing = true; + m_bBroken = true; + m_iBrokenCounter = 60; - updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); + // This does the same as it would happen on connection timeout, + // just we know about this state prematurely thanks to this message. + updateBrokenConnection(); + completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! +} - if (!secure) +void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) +{ + HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:" + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) + << ", value=" << ctrlpkt.getExtendedType()); + + // This has currently two roles in SRT: + // - HSv4 (legacy) handshake + // - refreshed KMX (initial KMX is done still in the HS process in HSv5) + const bool understood = processSrtMsg(&ctrlpkt); + // CAREFUL HERE! This only means that this update comes from the UMSG_EXT + // message received, REGARDLESS OF WHAT IT IS. This version doesn't mean + // the handshake version, but the reason of calling this function. + // + // Fortunately, the only messages taken into account in this function + // are HSREQ and HSRSP, which should *never* be interchanged when both + // parties are HSv5. + if (understood) + { + if (ctrlpkt.getExtendedType() == SRT_CMD_HSREQ || ctrlpkt.getExtendedType() == SRT_CMD_HSRSP) { - LOGC(mglog.Warn, - log << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo - << " vs loss %" << wrong_loss); - // this should not happen: attack or bug - m_bBroken = true; - m_iBrokenCounter = 0; - break; + updateAfterSrtHandshake(HS_VERSION_UDT4); } + } + else + { + updateCC(TEV_CUSTOM, EventVariant(&ctrlpkt)); + } +} - // the lost packet (retransmission) should be sent out immediately - m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); +void srt::CUDT::processCtrl(const CPacket &ctrlpkt) +{ + // Just heard from the peer, reset the expiration count. + m_iEXPCount = 1; + const steady_clock::time_point currtime = steady_clock::now(); + m_tsLastRspTime = currtime; + + HLOGC(inlog.Debug, + log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID); + + switch (ctrlpkt.getType()) + { + case UMSG_ACK: // 010 - Acknowledgement + processCtrlAck(ctrlpkt, currtime); + break; - CGuard::enterCS(m_StatsLock); - ++m_stats.recvNAK; - ++m_stats.recvNAKTotal; - CGuard::leaveCS(m_StatsLock); + case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement + processCtrlAckAck(ctrlpkt, currtime); + break; + case UMSG_LOSSREPORT: // 011 - Loss Report + processCtrlLossReport(ctrlpkt); break; - } case UMSG_CGWARNING: // 100 - Delay Warning // One way packet delay is increasing, so decrease the sending rate - m_ullInterval_tk = (uint64_t)ceil(m_ullInterval_tk * 1.125); - m_iLastDecSeq = m_iSndCurrSeqNo; + m_tdSendInterval = (m_tdSendInterval.load() * 1125) / 1000; // XXX Note as interesting fact: this is only prepared for handling, // but nothing in the code is sending this message. Probably predicted // for a custom congctl. There's a predicted place to call it under @@ -7596,154 +9035,19 @@ void CUDT::processCtrl(CPacket &ctrlpkt) break; case UMSG_KEEPALIVE: // 001 - Keep-alive - // The only purpose of keep-alive packet is to tell that the peer is still alive - // nothing needs to be done. - + processKeepalive(ctrlpkt, currtime); break; case UMSG_HANDSHAKE: // 000 - Handshake - { - CHandShake req; - req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); - - HLOGC(mglog.Debug, log << "processCtrl: got HS: " << req.show()); - - if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...??? - || (m_bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION - { - // The peer side has not received the handshake message, so it keeps querying - // resend the handshake packet - - // This condition embraces cases when: - // - this is normal accept() and URQ_INDUCTION was received - // - this is rendezvous accept() and there's coming any kind of URQ except AGREEMENT (should be RENDEZVOUS - // or CONCLUSION) - // - this is any of URQ_ERROR_* - well... - CHandShake initdata; - initdata.m_iISN = m_iISN; - initdata.m_iMSS = m_iMSS; - initdata.m_iFlightFlagSize = m_iFlightFlagSize; - - // For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT. - // For client-server we do URQ_INDUCTION --> URQ_CONCLUSION. - initdata.m_iReqType = (!m_bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT; - initdata.m_iID = m_SocketID; - - uint32_t kmdata[SRTDATA_MAXSIZE]; - size_t kmdatasize = SRTDATA_MAXSIZE; - bool have_hsreq = false; - if (req.m_iVersion > HS_VERSION_UDT4) - { - initdata.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener... - int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); - if (hs_flags != 0) // has SRT extensions - { - HLOGC(mglog.Debug, - log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType) - << " WITH SRT ext"); - have_hsreq = interpretSrtHandshake(req, ctrlpkt, kmdata, &kmdatasize); - if (!have_hsreq) - { - initdata.m_iVersion = 0; - m_RejectReason = SRT_REJ_ROGUE; - initdata.m_iReqType = URQFailure(m_RejectReason); - } - else - { - // Extensions are added only in case of CONCLUSION (not AGREEMENT). - // Actually what is expected here is that this may either process the - // belated-repeated handshake from a caller (and then it's CONCLUSION, - // and should be added with HSRSP/KMRSP), or it's a belated handshake - // of Rendezvous when it has already considered itself connected. - // Sanity check - according to the rules, there should be no such situation - if (m_bRendezvous && m_SrtHsSide == HSD_RESPONDER) - { - LOGC(mglog.Error, - log << "processCtrl/HS: IPE???: RESPONDER should receive all its handshakes in " - "handshake phase."); - } - - // The 'extension' flag will be set from this variable; set it to false - // in case when the AGREEMENT response is to be sent. - have_hsreq = initdata.m_iReqType == URQ_CONCLUSION; - HLOGC(mglog.Debug, - log << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType) - << " kmdatasize=" << kmdatasize); - } - } - else - { - HLOGC(mglog.Debug, log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)); - } - } - else - { - initdata.m_iVersion = HS_VERSION_UDT4; - } - - initdata.m_extension = have_hsreq; - - HLOGC(mglog.Debug, - log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType) - << (have_hsreq ? " WITH SRT HS response extensions" : "")); - - // XXX here interpret SRT handshake extension - CPacket response; - response.setControl(UMSG_HANDSHAKE); - response.allocate(m_iMaxSRTPayloadSize); - - // If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE. - // There is also no possible IPE condition in case of HSv4 - for this version it will always return true. - if (createSrtHandshake(Ref(response), Ref(initdata), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize)) - { - response.m_iID = m_PeerID; - response.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - int nbsent = m_pSndQueue->sendto(m_pPeerAddr, response); - if (nbsent) - { - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - m_ullLastSndTime_tk = currtime_tk; - } - } - } - else - { - HLOGC(mglog.Debug, log << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."); - } - + processCtrlHS(ctrlpkt); break; - } case UMSG_SHUTDOWN: // 101 - Shutdown - m_bShutdown = true; - m_bClosing = true; - m_bBroken = true; - m_iBrokenCounter = 60; - - // Signal the sender and recver if they are waiting for data. - releaseSynch(); - // Unblock any call so they learn the connection_broken error - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true); - - CTimer::triggerEvent(); - + processCtrlShutdown(); break; case UMSG_DROPREQ: // 111 - Msg drop request - CGuard::enterCS(m_RecvLock); - m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag); - CGuard::leaveCS(m_RecvLock); - - dropFromLossLists(*(int32_t *)ctrlpkt.m_pcData, *(int32_t *)(ctrlpkt.m_pcData + 4)); - - // move forward with current recv seq no. - if ((CSeqNo::seqcmp(*(int32_t *)ctrlpkt.m_pcData, CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) && - (CSeqNo::seqcmp(*(int32_t *)(ctrlpkt.m_pcData + 4), m_iRcvCurrSeqNo) > 0)) - { - m_iRcvCurrSeqNo = *(int32_t *)(ctrlpkt.m_pcData + 4); - } - + processCtrlDropReq(ctrlpkt); break; case UMSG_PEERERROR: // 1000 - An error has happened to the peer side @@ -7752,34 +9056,12 @@ void CUDT::processCtrl(CPacket &ctrlpkt) // currently only this error is signalled from the peer side // if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately // giving the app a chance to fix the issue - m_bPeerHealth = false; break; case UMSG_EXT: // 0x7FFF - reserved and user defined messages - HLOGF(mglog.Debug, "CONTROL EXT MSG RECEIVED: %08X\n", ctrlpkt.getExtendedType()); - { - // This has currently two roles in SRT: - // - HSv4 (legacy) handshake - // - refreshed KMX (initial KMX is done still in the HS process in HSv5) - bool understood = processSrtMsg(&ctrlpkt); - // CAREFUL HERE! This only means that this update comes from the UMSG_EXT - // message received, REGARDLESS OF WHAT IT IS. This version doesn't mean - // the handshake version, but the reason of calling this function. - // - // Fortunately, the only messages taken into account in this function - // are HSREQ and HSRSP, which should *never* be interchanged when both - // parties are HSv5. - if (understood) - { - updateAfterSrtHandshake(ctrlpkt.getExtendedType(), HS_VERSION_UDT4); - } - else - { - updateCC(TEV_CUSTOM, &ctrlpkt); - } - } + processCtrlUserDefined(ctrlpkt); break; default: @@ -7787,60 +9069,74 @@ void CUDT::processCtrl(CPacket &ctrlpkt) } } -void CUDT::updateSrtRcvSettings() +void srt::CUDT::updateSrtRcvSettings() { - if (m_bTsbPd) + // CHANGED: we need to apply the tsbpd delay only for socket TSBPD. + // For Group TSBPD the buffer will have to deliver packets always on request + // by sequence number, although the buffer will have to solve all the TSBPD + // things internally anyway. Extracting by sequence number means only that + // the packet can be retrieved from the buffer before its time to play comes + // (unlike in normal situation when reading directly from socket), however + // its time to play shall be properly defined. + ScopedLock lock(m_RecvLock); + + // NOTE: remember to also update synchronizeWithGroup() if more settings are updated here. +#if ENABLE_NEW_RCVBUFFER + m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); +#endif + + // XXX m_bGroupTsbPd is ignored with SRT_ENABLE_APP_READER + if (m_bTsbPd || m_bGroupTsbPd) { - /* We are TsbPd receiver */ - CGuard::enterCS(m_RecvLock); - m_pRcvBuffer->setRcvTsbPdMode(m_ullRcvPeerStartTime, m_iTsbPdDelay_ms * 1000); - CGuard::leaveCS(m_RecvLock); +#if ENABLE_NEW_RCVBUFFER + m_pRcvBuffer->setTsbPdMode(m_tsRcvPeerStartTime, false, milliseconds_from(m_iTsbPdDelay_ms)); +#else + m_pRcvBuffer->setRcvTsbPdMode(m_tsRcvPeerStartTime, milliseconds_from(m_iTsbPdDelay_ms)); +#endif - HLOGF(mglog.Debug, - "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03u secs", + HLOGF(cnlog.Debug, + "AFTER HS: Set Rcv TsbPd mode%s: delay=%u.%03us RCV START: %s", + (m_bGroupTsbPd ? " (AS GROUP MEMBER)" : ""), m_iTsbPdDelay_ms / 1000, - m_iTsbPdDelay_ms % 1000); + m_iTsbPdDelay_ms % 1000, + FormatTime(m_tsRcvPeerStartTime).c_str()); } else { - HLOGC(mglog.Debug, log << "AFTER HS: Rcv TsbPd mode not set"); + HLOGC(cnlog.Debug, log << "AFTER HS: Rcv TsbPd mode not set"); } } -void CUDT::updateSrtSndSettings() +void srt::CUDT::updateSrtSndSettings() { if (m_bPeerTsbPd) { /* We are TsbPd sender */ // XXX Check what happened here. - // m_iPeerTsbPdDelay_ms = m_CongCtl->getSndPeerTsbPdDelay();// + ((m_iRTT + (4 * m_iRTTVar)) / 1000); + // m_iPeerTsbPdDelay_ms = m_CongCtl->getSndPeerTsbPdDelay();// + ((m_iSRTT + (4 * m_iRTTVar)) / 1000); /* * For sender to apply Too-Late Packet Drop * option (m_bTLPktDrop) must be enabled and receiving peer shall support it */ - HLOGF(mglog.Debug, - "AFTER HS: Set Snd TsbPd mode %s: delay=%d.%03d secs", - m_bPeerTLPktDrop ? "with TLPktDrop" : "without TLPktDrop", - m_iPeerTsbPdDelay_ms / 1000, - m_iPeerTsbPdDelay_ms % 1000); + HLOGF(cnlog.Debug, + "AFTER HS: Set Snd TsbPd mode %s TLPktDrop: delay=%d.%03ds START TIME: %s", + m_bPeerTLPktDrop ? "with" : "without", + m_iPeerTsbPdDelay_ms/1000, m_iPeerTsbPdDelay_ms%1000, + FormatTime(m_stats.tsStartTime).c_str()); } else { - HLOGC(mglog.Debug, log << "AFTER HS: Snd TsbPd mode not set"); + HLOGC(cnlog.Debug, log << "AFTER HS: Snd TsbPd mode not set"); } } -void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv) +void srt::CUDT::updateAfterSrtHandshake(int hsv) { - - switch (srt_cmd) - { - case SRT_CMD_HSREQ: - case SRT_CMD_HSRSP: - break; - default: - return; - } + HLOGC(cnlog.Debug, log << "updateAfterSrtHandshake: HS version " << hsv); + // This is blocked from being run in the "app reader" version because here + // every socket does its TsbPd independently, just the sequence screwup is + // done and the application reader sorts out packets by sequence numbers, + // but only when they are signed off by TsbPd. // The only possibility here is one of these two: // - Agent is RESPONDER and it receives HSREQ. @@ -7851,13 +9147,33 @@ void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv) // // This function will be called only ONCE in this // instance, through either HSREQ or HSRSP. +#if ENABLE_HEAVY_LOGGING + const char* hs_side[] = { "DRAW", "INITIATOR", "RESPONDER" }; +#if ENABLE_BONDING + string grpspec; + + if (m_parent->m_GroupOf) + { + ScopedLock glock (uglobal().m_GlobControlLock); + grpspec = m_parent->m_GroupOf + ? " group=$" + Sprint(m_parent->m_GroupOf->id()) + : string(); + } +#else + const char* grpspec = ""; +#endif + + HLOGC(cnlog.Debug, log << "updateAfterSrtHandshake: version=" + << m_ConnRes.m_iVersion << " side=" << hs_side[m_SrtHsSide] + << grpspec); +#endif if (hsv > HS_VERSION_UDT4) { updateSrtRcvSettings(); updateSrtSndSettings(); } - else if (srt_cmd == SRT_CMD_HSRSP) + else if (m_SrtHsSide == HSD_INITIATOR) { // HSv4 INITIATOR is sender updateSrtSndSettings(); @@ -7869,62 +9185,90 @@ void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv) } } -int CUDT::packLostData(CPacket &packet, uint64_t &origintime) +int srt::CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origintime) { // protect m_iSndLastDataAck from updating by ACK processing - CGuard ackguard(m_RecvAckLock); + UniqueLock ackguard(m_RecvAckLock); + const steady_clock::time_point time_now = steady_clock::now(); + const steady_clock::time_point time_nak = time_now - microseconds_from(m_iSRTT - 4 * m_iRTTVar); - while ((packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0) + while ((w_packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0) { - const int offset = CSeqNo::seqoff(m_iSndLastDataAck, packet.m_iSeqNo); + // XXX See the note above the m_iSndLastDataAck declaration in core.h + // This is the place where the important sequence numbers for + // sender buffer are actually managed by this field here. + const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.m_iSeqNo); if (offset < 0) { - LOGC(dlog.Error, - log << "IPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " << packet.m_iSeqNo - << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset << ". Continue"); + // XXX Likely that this will never be executed because if the upper + // sequence is not in the sender buffer, then most likely the loss + // was completely ignored. + LOGC(qrlog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " + << w_packet.m_iSeqNo << ", m_iSndLastDataAck " << m_iSndLastDataAck + << ")=" << offset << ". Continue"); + + // No matter whether this is right or not (maybe the attack case should be + // considered, and some LOSSREPORT flood prevention), send the drop request + // to the peer. + int32_t seqpair[2] = { + w_packet.m_iSeqNo, + CSeqNo::decseq(m_iSndLastDataAck) + }; + w_packet.m_iMsgNo = 0; // Message number is not known, setting all 32 bits to 0. + + HLOGC(qrlog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: " + << "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" + << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); + + sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); continue; } + if (m_bPeerNakReport && m_config.iRetransmitAlgo != 0) + { + const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); + if (tsLastRexmit >= time_nak) + { + HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno " + << w_packet.m_iSeqNo << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit)) + << " RTT=" << m_iSRTT << " RTTVar=" << m_iRTTVar + << " now=" << FormatTime(time_now)); + continue; + } + } + int msglen; - const int payload = m_pSndBuffer->readData(&(packet.m_pcData), offset, packet.m_iMsgNo, origintime, msglen); - SRT_ASSERT(payload != 0); + const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_origintime), (msglen)); if (payload == -1) { int32_t seqpair[2]; - seqpair[0] = packet.m_iSeqNo; - seqpair[1] = CSeqNo::incseq(seqpair[0], msglen); - sendCtrl(UMSG_DROPREQ, &packet.m_iMsgNo, seqpair, 8); + seqpair[0] = w_packet.m_iSeqNo; + SRT_ASSERT(msglen >= 1); + seqpair[1] = CSeqNo::incseq(seqpair[0], msglen - 1); - // only one msg drop request is necessary - m_pSndLossList->remove(seqpair[1]); + HLOGC(qrlog.Debug, + log << "loss-reported packets expired in SndBuf - requesting DROP: " + << "msgno=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " msglen=" << msglen + << " SEQ:" << seqpair[0] << " - " << seqpair[1]); + sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); // skip all dropped packets - if (CSeqNo::seqcmp(m_iSndCurrSeqNo, CSeqNo::incseq(seqpair[1])) < 0) - m_iSndCurrSeqNo = CSeqNo::incseq(seqpair[1]); - + m_pSndLossList->removeUpTo(seqpair[1]); + m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, seqpair[1]); continue; } - // NOTE: This is just a sanity check. Returning 0 is impossible to happen - // in case of retransmission. If the offset was a positive value, then the - // block must exist in the old blocks because it wasn't yet cut off by ACK - // and has been already recorded as sent (otherwise the peer wouldn't send - // back the loss report). May something happen here in case when the send - // loss record has been updated by the FASTREXMIT. else if (payload == 0) continue; // At this point we no longer need the ACK lock, // because we are going to return from the function. // Therefore unlocking in order not to block other threads. - ackguard.forceUnlock(); + ackguard.unlock(); - CGuard::enterCS(m_StatsLock); - ++m_stats.traceRetrans; - ++m_stats.retransTotal; - m_stats.traceBytesRetrans += payload; - m_stats.bytesRetransTotal += payload; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.sndr.sentRetrans.count(payload); + leaveCS(m_StatsLock); // Despite the contextual interpretation of packet.m_iMsgNo around // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular @@ -7932,7 +9276,7 @@ int CUDT::packLostData(CPacket &packet, uint64_t &origintime) // So, set here the rexmit flag if the peer understands it. if (m_bPeerRexmitFlag) { - packet.m_iMsgNo |= PACKET_SND_REXMIT; + w_packet.m_iMsgNo |= PACKET_SND_REXMIT; } return payload; @@ -7941,122 +9285,199 @@ int CUDT::packLostData(CPacket &packet, uint64_t &origintime) return 0; } -int CUDT::packData(CPacket &packet, uint64_t &ts_tk) +#if SRT_DEBUG_TRACE_SND +class snd_logger { - int payload = 0; - bool probe = false; - uint64_t origintime = 0; - bool new_packet_packed = false; - bool filter_ctl_pkt = false; + typedef srt::sync::steady_clock steady_clock; - int kflg = EK_NOENC; +public: + snd_logger() {} - uint64_t entertime_tk; - CTimer::rdtsc(entertime_tk); + ~snd_logger() + { + ScopedLock lck(m_mtx); + m_fout.close(); + } -#if 0 // debug: TimeDiff histogram - static int lldiffhisto[23] = {0}; - static int llnodiff = 0; - if (m_ullTargetTime_tk != 0) - { - int ofs = 11 + ((entertime_tk - m_ullTargetTime_tk)/(int64_t)m_ullCPUFrequency)/1000; - if (ofs < 0) ofs = 0; - else if (ofs > 22) ofs = 22; - lldiffhisto[ofs]++; - } - else if(m_ullTargetTime_tk == 0) - { - llnodiff++; - } - static int callcnt = 0; - if (!(callcnt++ % 5000)) { - fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n", - lldiffhisto[0],lldiffhisto[1],lldiffhisto[2],lldiffhisto[3],lldiffhisto[4],lldiffhisto[5], - lldiffhisto[6],lldiffhisto[7],lldiffhisto[8],lldiffhisto[9],lldiffhisto[10],lldiffhisto[11]); - fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n", - lldiffhisto[12],lldiffhisto[13],lldiffhisto[14],lldiffhisto[15],lldiffhisto[16],lldiffhisto[17], - lldiffhisto[18],lldiffhisto[19],lldiffhisto[20],lldiffhisto[21],lldiffhisto[21],llnodiff); - } + struct + { + typedef srt::sync::steady_clock steady_clock; + long long usElapsed; + steady_clock::time_point tsNow; + int usSRTT; + int usRTTVar; + int msSndBuffSpan; + int msTimespanTh; + int msNextUniqueToSend; + long long usElapsedLastDrop; + bool canRexmit; + int iPktSeqno; + bool isRetransmitted; + } state; + + void trace() + { + using namespace srt::sync; + ScopedLock lck(m_mtx); + create_file(); + + m_fout << state.usElapsed << ","; + m_fout << state.usSRTT << ","; + m_fout << state.usRTTVar << ","; + m_fout << state.msSndBuffSpan << ","; + m_fout << state.msTimespanTh << ","; + m_fout << state.msNextUniqueToSend << ","; + m_fout << state.usElapsedLastDrop << ","; + m_fout << state.canRexmit << ","; + m_fout << state.iPktSeqno << ','; + m_fout << state.isRetransmitted << '\n'; + + m_fout.flush(); + } + +private: + void print_header() + { + m_fout << "usElapsed,usSRTT,usRTTVar,msSndBuffTimespan,msTimespanTh,msNextUniqueToSend,usDLastDrop,canRexmit,sndPktSeqno,isRexmit"; + m_fout << "\n"; + } + + void create_file() + { + if (m_fout.is_open()) + return; + + m_start_time = srt::sync::steady_clock::now(); + std::string str_tnow = srt::sync::FormatTimeSys(m_start_time); + str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part + while (str_tnow.find(':') != std::string::npos) + { + str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); + } + const std::string fname = "snd_trace_" + str_tnow + ".csv"; + m_fout.open(fname, std::ofstream::out); + if (!m_fout) + std::cerr << "IPE: Failed to open " << fname << "!!!\n"; + + print_header(); + } + +private: + srt::sync::Mutex m_mtx; + std::ofstream m_fout; + srt::sync::steady_clock::time_point m_start_time; +}; + +snd_logger g_snd_logger; +#endif // SRT_DEBUG_TRACE_SND + +bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) +{ + // Prioritization of original packets only applies to Live CC. + if (!m_bPeerTLPktDrop || !m_config.bMessageAPI) + return true; + + // TODO: lock sender buffer? + const time_point tsNextPacket = m_pSndBuffer->peekNextOriginal(); + +#if SRT_DEBUG_TRACE_SND + const int buffdelay_ms = count_milliseconds(m_pSndBuffer->getBufferingDelay(tnow)); + // If there is a small loss, still better to retransmit. If timespan is already big, + // then consider sending original packets. + const int threshold_ms_min = (2 * m_iSRTT + 4 * m_iRTTVar + COMM_SYN_INTERVAL_US) / 1000; + const int msNextUniqueToSend = count_milliseconds(tnow - tsNextPacket) + m_iPeerTsbPdDelay_ms; + + g_snd_logger.state.tsNow = tnow; + g_snd_logger.state.usElapsed = count_microseconds(tnow - m_stats.tsStartTime); + g_snd_logger.state.usSRTT = m_iSRTT; + g_snd_logger.state.usRTTVar = m_iRTTVar; + g_snd_logger.state.msSndBuffSpan = buffdelay_ms; + g_snd_logger.state.msTimespanTh = threshold_ms_min; + g_snd_logger.state.msNextUniqueToSend = msNextUniqueToSend; + g_snd_logger.state.usElapsedLastDrop = count_microseconds(tnow - m_tsLastTLDrop); + g_snd_logger.state.canRexmit = false; #endif - if ((0 != m_ullTargetTime_tk) && (entertime_tk > m_ullTargetTime_tk)) - m_ullTimeDiff_tk += entertime_tk - m_ullTargetTime_tk; - string reason; + if (tsNextPacket != time_point()) + { + // Can send original packet, so just send it + return false; + } + +#if SRT_DEBUG_TRACE_SND + g_snd_logger.state.canRexmit = true; +#endif + return true; +} + +std::pair srt::CUDT::packData(CPacket& w_packet) +{ + int payload = 0; + bool probe = false; + steady_clock::time_point origintime; + bool new_packet_packed = false; + bool filter_ctl_pkt = false; + + const steady_clock::time_point enter_time = steady_clock::now(); + + if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime) + { + m_tdSendTimeDiff = m_tdSendTimeDiff.load() + (enter_time - m_tsNextSendTime); + } + + string reason = "reXmit"; + + ScopedLock connectguard(m_ConnectionLock); + // If a closing action is done simultaneously, then + // m_bOpened should already be false, and it's set + // just before releasing this lock. + // + // If this lock is caught BEFORE the closing could + // start the dissolving process, this process will + // not be started until this function is finished. + if (!m_bOpened) + return std::make_pair(false, enter_time); + + payload = isRetransmissionAllowed(enter_time) + ? packLostData((w_packet), (origintime)) + : 0; - payload = packLostData(packet, origintime); if (payload > 0) { reason = "reXmit"; } else if (m_PacketFilter && - m_PacketFilter.packControlPacket(Ref(packet), m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags())) + m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags(), (w_packet))) { - HLOGC(mglog.Debug, log << "filter: filter/CTL packet ready - packing instead of data."); - payload = packet.getLength(); + HLOGC(qslog.Debug, log << "filter: filter/CTL packet ready - packing instead of data."); + payload = (int) w_packet.getLength(); reason = "filter"; filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set // Stats - - { - CGuard lg(m_StatsLock); - ++m_stats.sndFilterExtra; - ++m_stats.sndFilterExtraTotal; - } + ScopedLock lg(m_StatsLock); + m_stats.sndr.sentFilterExtra.count(1); } else { - // If no loss, and no packetfilter control packet, pack a new packet. - - // check congestion/flow window limit - int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); - int seqdiff = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)); - if (cwnd >= seqdiff) - { - // XXX Here it's needed to set kflg to msgno_bitset in the block stored in the - // send buffer. This should be somehow avoided, the crypto flags should be set - // together with encrypting, and the packet should be sent as is, when rexmitting. - // It would be nice to research as to whether CSndBuffer::Block::m_iMsgNoBitset field - // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. - kflg = m_pCryptoControl->getSndCryptoFlags(); - payload = m_pSndBuffer->readData(&(packet.m_pcData), packet.m_iMsgNo, origintime, kflg); - if (payload) - { - m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo); - // m_pCryptoControl->m_iSndCurrSeqNo = m_iSndCurrSeqNo; - - packet.m_iSeqNo = m_iSndCurrSeqNo; - - // every 16 (0xF) packets, a packet pair is sent - if ((packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) - probe = true; - - new_packet_packed = true; - } - else - { - m_ullTargetTime_tk = 0; - m_ullTimeDiff_tk = 0; - ts_tk = 0; - return 0; - } - } - else + if (!packUniqueData(w_packet, origintime)) { - HLOGC(dlog.Debug, - log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow - << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << seqdiff); - m_ullTargetTime_tk = 0; - m_ullTimeDiff_tk = 0; - ts_tk = 0; - return 0; + m_tsNextSendTime = steady_clock::time_point(); + m_tdSendTimeDiff = steady_clock::duration(); + return std::make_pair(false, enter_time); } + new_packet_packed = true; + + // every 16 (0xF) packets, a packet pair is sent + if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) + probe = true; + payload = (int) w_packet.getLength(); reason = "normal"; } // Normally packet.m_iTimeStamp field is set exactly here, - // usually as taken from m_StartTime and current time, unless live + // usually as taken from m_stats.tsStartTime and current time, unless live // mode in which case it is based on 'origintime' as set during scheduling. // In case when this is a filter control packet, the m_iTimeStamp field already // contains the exactly needed value, and it's a timestamp clip, not a real @@ -8069,107 +9490,237 @@ int CUDT::packData(CPacket &packet, uint64_t &ts_tk) * When timestamp is carried over in this sending stream from a received stream, * it may be older than the session start time causing a negative packet time * that may block the receiver's Timestamp-based Packet Delivery. - * XXX Isn't it then better to not decrease it by m_StartTime? As long as it + * XXX Isn't it then better to not decrease it by m_stats.tsStartTime? As long as it * doesn't screw up the start time on the other side. */ - if (origintime >= m_stats.startTime) - packet.m_iTimeStamp = int(origintime - m_stats.startTime); + if (origintime >= m_stats.tsStartTime) + { + setPacketTS(w_packet, origintime); + } else - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); + { + setPacketTS(w_packet, steady_clock::now()); + LOGC(qslog.Warn, log << "packData: reference time=" << FormatTime(origintime) + << " is in the past towards start time=" << FormatTime(m_stats.tsStartTime) + << " - setting NOW as reference time for the data packet"); + } } else { - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); + setPacketTS(w_packet, steady_clock::now()); } } - packet.m_iID = m_PeerID; - packet.setLength(payload); - - /* Encrypt if 1st time this packet is sent and crypto is enabled */ - if (kflg) - { - // XXX Encryption flags are already set on the packet before calling this. - // See readData() above. - if (m_pCryptoControl->encrypt(Ref(packet))) - { - // Encryption failed - //>>Add stats for crypto failure - ts_tk = 0; - LOGC(dlog.Error, log << "ENCRYPT FAILED - packet won't be sent, size=" << payload); - return -1; // Encryption failed - } - payload = packet.getLength(); /* Cipher may change length */ - reason += " (encrypted)"; - } + w_packet.m_iID = m_PeerID; if (new_packet_packed && m_PacketFilter) { - HLOGC(mglog.Debug, log << "filter: Feeding packet for source clip"); - m_PacketFilter.feedSource(Ref(packet)); + HLOGC(qslog.Debug, log << "filter: Feeding packet for source clip"); + m_PacketFilter.feedSource((w_packet)); } #if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() - HLOGC(mglog.Debug, - log << CONID() << "packData: " << reason << " packet seq=" << packet.m_iSeqNo << " (ACK=" << m_iSndLastAck - << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << packet.MessageFlagStr() << ")"); + HLOGC(qslog.Debug, + log << CONID() << "packData: " << reason << " packet seq=" << w_packet.m_iSeqNo << " (ACK=" << m_iSndLastAck + << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif // Fix keepalive - m_ullLastSndTime_tk = entertime_tk; + m_tsLastSndTime.store(enter_time); - considerLegacySrtHandshake(0); + considerLegacySrtHandshake(steady_clock::time_point()); // WARNING: TEV_SEND is the only event that is reported from // the CSndQueue::worker thread. All others are reported from // CRcvQueue::worker. If you connect to this signal, make sure // that you are aware of prospective simultaneous access. - updateCC(TEV_SEND, &packet); + updateCC(TEV_SEND, EventVariant(&w_packet)); // XXX This was a blocked code also originally in UDT. Probably not required. // Left untouched for historical reasons. // Might be possible that it was because of that this is send from // different thread than the rest of the signals. - // m_pSndTimeWindow->onPktSent(packet.m_iTimeStamp); + // m_pSndTimeWindow->onPktSent(w_packet.m_iTimeStamp); - CGuard::enterCS(m_StatsLock); - m_stats.traceBytesSent += payload; - m_stats.bytesSentTotal += payload; - ++m_stats.traceSent; - ++m_stats.sentTotal; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.sndr.sent.count(payload); + if (new_packet_packed) + m_stats.sndr.sentUnique.count(payload); + leaveCS(m_StatsLock); + const duration sendint = m_tdSendInterval; if (probe) { // sends out probing packet pair - ts_tk = entertime_tk; - probe = false; + m_tsNextSendTime = enter_time; + // Sending earlier, need to adjust the pace later on. + m_tdSendTimeDiff = m_tdSendTimeDiff.load() - sendint; + probe = false; } else { #if USE_BUSY_WAITING - ts_tk = entertime_tk + m_ullInterval_tk; + m_tsNextSendTime = enter_time + m_tdSendInterval.load(); #else - if (m_ullTimeDiff_tk >= m_ullInterval_tk) + const duration sendbrw = m_tdSendTimeDiff; + + if (sendbrw >= sendint) { - ts_tk = entertime_tk; - m_ullTimeDiff_tk -= m_ullInterval_tk; + // Send immidiately + m_tsNextSendTime = enter_time; + + // ATOMIC NOTE: this is the only thread that + // modifies this field + m_tdSendTimeDiff = sendbrw - sendint; } else { - ts_tk = entertime_tk + m_ullInterval_tk - m_ullTimeDiff_tk; - m_ullTimeDiff_tk = 0; + m_tsNextSendTime = enter_time + (sendint - sendbrw); + m_tdSendTimeDiff = duration(); + } +#endif + } + + return std::make_pair(payload >= 0, m_tsNextSendTime); +} + +bool srt::CUDT::packUniqueData(CPacket& w_packet, time_point& w_origintime) +{ + // Check the congestion/flow window limit + const int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); + const int flightspan = getFlightSpan(); + if (cwnd <= flightspan) + { + HLOGC(qslog.Debug, log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow + << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); + return false; + } + + // XXX Here it's needed to set kflg to msgno_bitset in the block stored in the + // send buffer. This should be somehow avoided, the crypto flags should be set + // together with encrypting, and the packet should be sent as is, when rexmitting. + // It would be nice to research as to whether CSndBuffer::Block::m_iMsgNoBitset field + // isn't a useless redundant state copy. If it is, then taking the flags here can be removed. + const int kflg = m_pCryptoControl->getSndCryptoFlags(); + int pktskipseqno = 0; + const int pld_size = m_pSndBuffer->readData((w_packet), (w_origintime), kflg, (pktskipseqno)); + if (pktskipseqno) + { + // Some packets were skipped due to TTL expiry. + m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo, pktskipseqno); + } + + if (pld_size == 0) + { + return false; + } + + // A CHANGE. The sequence number is currently added to the packet + // when scheduling, not when extracting. This is a inter-migration form, + // only override extraction sequence with scheduling sequence in group mode. + m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo); + +#if ENABLE_BONDING + // Fortunately the group itself isn't being accessed. + if (m_parent->m_GroupOf) + { + const int packetspan = CSeqNo::seqoff(m_iSndCurrSeqNo, w_packet.m_iSeqNo); + if (packetspan > 0) + { + // After increasing by 1, but being previously set as ISN-1, this should be == ISN, + // if this is the very first packet to send. + if (m_iSndCurrSeqNo == m_iISN) + { + // This is the very first packet to be sent; so there's nothing in + // the sending buffer yet, and therefore we are in a situation as just + // after connection. No packets in the buffer, no packets are sent, + // no ACK to be awaited. We can screw up all the variables that are + // initialized from ISN just after connection. + LOGC(qslog.Note, + log << CONID() << "packData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo + << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " for the first packet: DIFF=" + << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + } + else + { + // There will be a serious data discrepancy between the agent and the peer. + LOGC(qslog.Error, + log << CONID() << "IPE: packData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo + << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " in the middle of transition: DIFF=" + << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + } + + // Additionally send the drop request to the peer so that it + // won't stupidly request the packets to be retransmitted. + // Don't do it if the difference isn't positive or exceeds the threshold. + int32_t seqpair[2]; + seqpair[0] = m_iSndCurrSeqNo; + seqpair[1] = CSeqNo::decseq(w_packet.m_iSeqNo); + const int32_t no_msgno = 0; + LOGC(qslog.Debug, + log << CONID() << "packData: Sending DROPREQ: SEQ: " << seqpair[0] << " - " << seqpair[1] << " (" + << packetspan << " packets)"); + sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); + // In case when this message is lost, the peer will still get the + // UMSG_DROPREQ message when the agent realizes that the requested + // packet are not present in the buffer (preadte the send buffer). + + // Override extraction sequence with scheduling sequence. + m_iSndCurrSeqNo = w_packet.m_iSeqNo; + ScopedLock ackguard(m_RecvAckLock); + m_iSndLastAck = w_packet.m_iSeqNo; + m_iSndLastDataAck = w_packet.m_iSeqNo; + m_iSndLastFullAck = w_packet.m_iSeqNo; + m_iSndLastAck2 = w_packet.m_iSeqNo; + } + else if (packetspan < 0) + { + LOGC(qslog.Error, + log << CONID() << "IPE: packData: SCHEDULING sequence " << w_packet.m_iSeqNo + << " is behind of EXTRACTION sequence " << m_iSndCurrSeqNo << ", dropping this packet: DIFF=" + << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + // XXX: Probably also change the socket state to broken? + return false; } + } + else #endif + { + HLOGC(qslog.Debug, + log << CONID() << "packData: Applying EXTRACTION sequence " << m_iSndCurrSeqNo + << " over SCHEDULING sequence " << w_packet.m_iSeqNo << " for socket not in group:" + << " DIFF=" << CSeqNo::seqcmp(m_iSndCurrSeqNo, w_packet.m_iSeqNo) + << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + // Do this always when not in a group. + w_packet.m_iSeqNo = m_iSndCurrSeqNo; + } + + // Encrypt if 1st time this packet is sent and crypto is enabled + if (kflg != EK_NOENC) + { + // Note that the packet header must have a valid seqno set, as it is used as a counter for encryption. + // Other fields of the data packet header (e.g. timestamp, destination socket ID) are not used for the counter. + // Cypher may change packet length! + if (m_pCryptoControl->encrypt((w_packet)) != ENCS_CLEAR) + { + // Encryption failed + //>>Add stats for crypto failure + LOGC(qslog.Warn, log << "ENCRYPT FAILED - packet won't be sent, size=" << pld_size); + return false; + } } - m_ullTargetTime_tk = ts_tk; +#if SRT_DEBUG_TRACE_SND + g_snd_logger.state.iPktSeqno = w_packet.m_iSeqNo; + g_snd_logger.state.isRetransmitted = w_packet.getRexmitFlag(); + g_snd_logger.trace(); +#endif - return payload; + return true; } // This is a close request, but called from the -void CUDT::processClose() +void srt::CUDT::processClose() { sendCtrl(UMSG_SHUTDOWN); @@ -8178,25 +9729,24 @@ void CUDT::processClose() m_bBroken = true; m_iBrokenCounter = 60; - HLOGP(mglog.Debug, "processClose: sent message and set flags"); + HLOGP(smlog.Debug, "processClose: sent message and set flags"); if (m_bTsbPd) { - HLOGP(mglog.Debug, "processClose: lock-and-signal TSBPD"); - CGuard rl(m_RecvLock); - pthread_cond_signal(&m_RcvTsbPdCond); + HLOGP(smlog.Debug, "processClose: lock-and-signal TSBPD"); + CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); } // Signal the sender and recver if they are waiting for data. releaseSynch(); // Unblock any call so they learn the connection_broken error - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true); - HLOGP(mglog.Debug, "processClose: triggering timer event to spread the bad news"); - CTimer::triggerEvent(); + HLOGP(smlog.Debug, "processClose: triggering timer event to spread the bad news"); + CGlobEvent::triggerEvent(); } -void CUDT::sendLossReport(const std::vector > &loss_seqs) +void srt::CUDT::sendLossReport(const std::vector > &loss_seqs) { typedef vector > loss_seqs_t; @@ -8207,13 +9757,13 @@ void CUDT::sendLossReport(const std::vector > &loss_ if (i->first == i->second) { seqbuffer.push_back(i->first); - HLOGF(mglog.Debug, "lost packet %d: sending LOSSREPORT", i->first); + HLOGF(qrlog.Debug, "lost packet %d: sending LOSSREPORT", i->first); } else { seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST); seqbuffer.push_back(i->second); - HLOGF(mglog.Debug, + HLOGF(qrlog.Debug, "lost packets %d-%d (%d packets): sending LOSSREPORT", i->first, i->second, @@ -8223,32 +9773,99 @@ void CUDT::sendLossReport(const std::vector > &loss_ if (!seqbuffer.empty()) { - sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], seqbuffer.size()); + sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], (int) seqbuffer.size()); + } +} + + +bool srt::CUDT::overrideSndSeqNo(int32_t seq) +{ + // This function is intended to be called from the socket + // group managmenet functions to synchronize the sequnece in + // all sockes in the bonding group. THIS sequence given + // here is the sequence TO BE STAMPED AT THE EXACTLY NEXT + // sent payload. Therefore, screw up the ISN to exactly this + // value, and the send sequence to the value one less - because + // the m_iSndCurrSeqNo is increased by one immediately before + // stamping it to the packet. + + // This function can only be called: + // - from the operation on an idle socket in the socket group + // - IMMEDIATELY after connection established and BEFORE the first payload + // - The corresponding socket at the peer side must be also + // in this idle state! + + ScopedLock cg (m_RecvAckLock); + + // Both the scheduling and sending sequences should be fixed. + // The new sequence normally should jump over several sequence numbers + // towards what is currently in m_iSndCurrSeqNo. + // Therefore it's not allowed that: + // - the jump go backward: backward packets should be already there + // - the jump go forward by a value larger than half the period: DISCREPANCY. + const int diff = CSeqNo(seq) - CSeqNo(m_iSndCurrSeqNo); + if (diff < 0 || diff > CSeqNo::m_iSeqNoTH) + { + LOGC(gslog.Error, log << CONID() << "IPE: Overriding with seq %" << seq << " DISCREPANCY against current %" + << m_iSndCurrSeqNo << " and next sched %" << m_iSndNextSeqNo << " - diff=" << diff); + return false; } + + // + // The peer will have to do the same, as a reaction on perceived + // packet loss. When it recognizes that this initial screwing up + // has happened, it should simply ignore the loss and go on. + // ISN isn't being changed here - it doesn't make much sense now. + + setInitialSndSeq(seq); + + // m_iSndCurrSeqNo will be most likely lower than m_iSndNextSeqNo because + // the latter is ahead with the number of packets already scheduled, but + // not yet sent. + + HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo + << " (unchanged)" + ); + return true; } -int CUDT::processData(CUnit *in_unit) +int srt::CUDT::processData(CUnit* in_unit) { + if (m_bClosing) + return -1; + CPacket &packet = in_unit->m_Packet; - // XXX This should be called (exclusively) here: - // m_pRcvBuffer->addLocalTsbPdDriftSample(packet.getMsgTimeStamp()); // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); - m_ullLastRspTime_tk = currtime_tk; + m_tsLastRspTime.store(steady_clock::now()); + + const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; // We are receiving data, start tsbpd thread if TsbPd is enabled - if (m_bTsbPd && pthread_equal(m_RcvTsbPdThread, pthread_t())) + if (need_tsbpd && !m_RcvTsbPdThread.joinable()) { - HLOGP(mglog.Debug, "Spawning TSBPD thread"); - int st = 0; - { - ThreadName tn("SRT:TsbPd"); - st = pthread_create(&m_RcvTsbPdThread, NULL, CUDT::tsbpd, this); - } - if (st != 0) + ScopedLock lock(m_RcvTsbPdStartupLock); + + if (m_bClosing) // Check again to protect join() in CUDT::releaseSync() + return -1; + + HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread"); +#if ENABLE_HEAVY_LOGGING + std::ostringstream tns1, tns2; + // Take the last 2 ciphers from the socket ID. + tns1 << m_SocketID; + std::string s = tns1.str(); + tns2 << "SRT:TsbPd:@" << s.substr(s.size()-2, 2); + + const string& tn = tns2.str(); + + ThreadName tnkeep(tn); + const string& thname = tn; +#else + const string thname = "SRT:TsbPd"; +#endif + if (!StartThread(m_RcvTsbPdThread, CUDT::tsbpd, this, thname)) return -1; } @@ -8261,42 +9878,57 @@ int CUDT::processData(CUnit *in_unit) if (pktrexmitflag == 1) { // This packet was retransmitted - CGuard::enterCS(m_StatsLock); - m_stats.traceRcvRetrans++; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.rcvr.recvdRetrans.count(packet.getLength()); + leaveCS(m_StatsLock); #if ENABLE_HEAVY_LOGGING // Check if packet was retransmitted on request or on ack timeout // Search the sequence in the loss record. rexmit_reason = " by "; if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo)) - rexmit_reason += "REQUEST"; + rexmit_reason += "BLIND"; else - rexmit_reason += "ACK-TMOUT"; + rexmit_reason += "NAKREPORT"; #endif } - HLOGC(dlog.Debug, - log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo()); +#if ENABLE_HEAVY_LOGGING + { + steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) + + // It's easier to remove the latency factor from this value than to add a function + // that exposes the details basing on which this value is calculated. + steady_clock::time_point pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); + steady_clock::time_point ets = pts - tsbpddelay; + + HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() + << " seq=" << packet.getSeqNo() + // XXX FIX IT. OTS should represent the original sending time, but it's relative. + //<< " OTS=" << FormatTime(packet.getMsgTimeStamp()) + << " ETS=" << FormatTime(ets) + << " PTS=" << FormatTime(pts)); + } +#endif - updateCC(TEV_RECEIVE, &packet); + updateCC(TEV_RECEIVE, EventVariant(&packet)); ++m_iPktCount; - const int pktsz = packet.getLength(); + const int pktsz = (int) packet.getLength(); // Update time information - // XXX Note that this adds the byte size of a packet - // of which we don't yet know as to whether this has - // carried out some useful data or some excessive data - // that will be later discarded. - // FIXME: before adding this on the rcv time window, - // make sure that this packet isn't going to be - // effectively discarded, as repeated retransmission, - // for example, burdens the link, but doesn't better the speed. + // XXX Note that this adds the byte size of a packet + // of which we don't yet know as to whether this has + // carried out some useful data or some excessive data + // that will be later discarded. + // FIXME: before adding this on the rcv time window, + // make sure that this packet isn't going to be + // effectively discarded, as repeated retransmission, + // for example, burdens the link, but doesn't better the speed. m_RcvTimeWindow.onPktArrival(pktsz); - // Probe the packet pair if needed. - // Conditions and any extra data required for the packet - // this function will extract and test as needed. + // Probe the packet pair if needed. + // Conditions and any extra data required for the packet + // this function will extract and test as needed. const bool unordered = CSeqNo::seqcmp(packet.m_iSeqNo, m_iRcvCurrSeqNo) <= 0; const bool retransmitted = m_bPeerRexmitFlag && packet.getRexmitFlag(); @@ -8306,14 +9938,10 @@ int CUDT::processData(CUnit *in_unit) // otherwise measurement must be rejected. m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); - CGuard::enterCS(m_StatsLock); - m_stats.traceBytesRecv += pktsz; - m_stats.bytesRecvTotal += pktsz; - ++m_stats.traceRecv; - ++m_stats.recvTotal; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.rcvr.recvd.count(pktsz); + leaveCS(m_StatsLock); - typedef vector > loss_seqs_t; loss_seqs_t filter_loss_seqs; loss_seqs_t srt_loss_seqs; vector incoming; @@ -8340,19 +9968,22 @@ int CUDT::processData(CUnit *in_unit) // to the filter and before the filter could recover the packet before anyone // notices :) - if (packet.getMsgSeq() != 0) // disregard filter-control packets, their seq may mean nothing + if (packet.getMsgSeq() != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing { int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); + // Difference between these two sequence numbers is expected to be: + // 0 - duplicated last packet (theory only) + // 1 - subsequent packet (alright) + // <0 - belated or recovered packet + // >1 - jump over a packet loss (loss = seqdiff-1) if (diff > 1) { - CGuard lg(m_StatsLock); - int loss = diff - 1; // loss is all that is above diff == 1 - m_stats.traceRcvLoss += loss; - m_stats.rcvLossTotal += loss; - uint64_t lossbytes = loss * m_pRcvBuffer->getRcvAvgPayloadSize(); - m_stats.traceRcvBytesLoss += lossbytes; - m_stats.rcvBytesLossTotal += lossbytes; - HLOGC(mglog.Debug, + const int loss = diff - 1; // loss is all that is above diff == 1 + ScopedLock lg(m_StatsLock); + const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); + m_stats.rcvr.lost.count(stats::BytesPackets(loss * avgpayloadsz, (uint32_t) loss)); + + HLOGC(qrlog.Debug, log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " << CSeqNo::decseq(packet.m_iSeqNo) << "]"); } @@ -8364,18 +9995,55 @@ int CUDT::processData(CUnit *in_unit) } } + // [[using locked()]]; // (NOTHING locked) + +#if ENABLE_BONDING + // Switch to RUNNING even if there was a discrepancy, unless + // it was long way forward. + // XXX Important: This code is in the dead function defaultPacketArrival + // but normally it should be called here regardless if the packet was + // accepted or rejected because if it was belated it may result in a + // "runaway train" problem as the IDLE links are being updated the base + // reception sequence pointer stating that this link is not receiving. + if (m_parent->m_GroupOf) + { + ScopedLock protect_group_existence (uglobal().m_GlobControlLock); + groups::SocketData* gi = m_parent->m_GroupMemberData; + + // This check is needed as after getting the lock the socket + // could be potentially removed. It is however granted that as long + // as gi is non-NULL iterator, the group does exist and it does contain + // this socket as member (that is, 'gi' cannot be a dangling iterator). + if (gi != NULL) + { + if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely + { + HLOGC(qrlog.Debug, + log << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] + << " -> RUNNING."); + gi->rcvstate = SRT_GST_RUNNING; + } + else + { + HLOGC(qrlog.Debug, log << "processData: IN-GROUP rcv state transition NOT DONE - state:" + << srt_log_grp_state[gi->rcvstate]); + } + } + } +#endif + { // Start of offset protected section // Prevent TsbPd thread from modifying Ack position while adding data // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() - CGuard recvbuf_acklock(m_RcvBufferLock); + UniqueLock recvbuf_acklock(m_RcvBufferLock); // vector undec_units; if (m_PacketFilter) { // Stuff this data into the filter - m_PacketFilter.receive(in_unit, Ref(incoming), Ref(filter_loss_seqs)); - HLOGC(mglog.Debug, + m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); + HLOGC(qrlog.Debug, log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) << " loss to report, " << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" @@ -8395,9 +10063,9 @@ int CUDT::processData(CUnit *in_unit) // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', // the one that came in the input of this function. - for (vector::iterator i = incoming.begin(); i != incoming.end(); ++i) + for (vector::iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) { - CUnit * u = *i; + CUnit * u = *unitIt; CPacket &rpkt = u->m_Packet; // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. @@ -8412,21 +10080,22 @@ int CUDT::processData(CUnit *in_unit) if (offset < 0) { IF_HEAVY_LOGGING(exc_type = "BELATED"); - uint64_t tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp()); - uint64_t bltime = - CountIIR(uint64_t(m_stats.traceBelatedTime) * 1000, CTimer::getTime() - tsbpdtime, 0.2); - - CGuard::enterCS(m_StatsLock); - m_stats.traceBelatedTime = double(bltime) / 1000.0; - m_stats.traceRcvBelated++; - CGuard::leaveCS(m_StatsLock); - HLOGC(mglog.Debug, + steady_clock::time_point tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp()); + const double bltime = (double) CountIIR( + uint64_t(m_stats.traceBelatedTime) * 1000, + count_microseconds(steady_clock::now() - tsbpdtime), 0.2); + + enterCS(m_StatsLock); + m_stats.traceBelatedTime = bltime / 1000.0; + m_stats.rcvr.recvdBelated.count(rpkt.getLength()); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/" << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr()); continue; } - const int avail_bufsize = m_pRcvBuffer->getAvailBufSize(); + const int avail_bufsize = (int) getAvailRcvBufferSizeNoLock(); if (offset >= avail_bufsize) { // This is already a sequence discrepancy. Probably there could be found @@ -8440,31 +10109,48 @@ int CUDT::processData(CUnit *in_unit) // lowest sequence in the empty buffer and the incoming sequence // that exceeds the buffer size. Receiving data in this situation // is no longer possible and this is a point of no return. - LOGC(mglog.Error, - log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION. offset=" << offset - << " avail=" << avail_bufsize << " ack.seq=" << m_iRcvLastSkipAck - << " pkt.seq=" << rpkt.m_iSeqNo << " rcv-remain=" << m_pRcvBuffer->debugGetSize()); + + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION." + " seq=" << rpkt.m_iSeqNo + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, int(m_pRcvBuffer->capacity()) - 1) + << "), " << (offset-avail_bufsize+1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); // This is a scoped lock with AckLock, but for the moment // when processClose() is called this lock must be taken out, // otherwise this will cause a deadlock. We don't need this // lock anymore, and at 'return' it will be unlocked anyway. - recvbuf_acklock.forceUnlock(); + recvbuf_acklock.unlock(); processClose(); return -1; } else { - LOGC(mglog.Error, - log << CONID() << "No room to store incoming packet: offset=" << offset - << " avail=" << avail_bufsize << " ack.seq=" << m_iRcvLastSkipAck - << " pkt.seq=" << rpkt.m_iSeqNo << " rcv-remain=" << m_pRcvBuffer->debugGetSize()); +#if ENABLE_NEW_RCVBUFFER + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) + ); +#else + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << offset << ". " + << m_pRcvBuffer->strFullnessState(steady_clock::now()) + ); +#endif + return -1; } } bool adding_successful = true; - if (m_pRcvBuffer->addData(*i, offset) < 0) +#if ENABLE_NEW_RCVBUFFER + if (m_pRcvBuffer->insert(u) < 0) +#else + if (m_pRcvBuffer->addData(u, offset) < 0) +#endif { // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. // So this packet is "redundant". @@ -8475,43 +10161,59 @@ int CUDT::processData(CUnit *in_unit) { IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); excessive = false; - if (u->m_Packet.getMsgCryptoFlags()) + if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) { - EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt(Ref(u->m_Packet)) : ENCS_NOTSUP; + EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; if (rc != ENCS_CLEAR) { - // Could not decrypt - // Keep packet in received buffer - // Crypto flags are still set - // It will be acknowledged - { - CGuard lg(m_StatsLock); - m_stats.traceRcvUndecrypt += 1; - m_stats.traceRcvBytesUndecrypt += pktsz; - m_stats.m_rcvUndecryptTotal += 1; - m_stats.m_rcvBytesUndecryptTotal += pktsz; - } - - // Log message degraded to debug because it may happen very often - HLOGC(dlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); + // Heavy log message because if seen once the message may happen very often. + HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); adding_successful = false; IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + + ScopedLock lg(m_StatsLock); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(pktsz, 1)); } } } - HLOGC(mglog.Debug, - log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " offset=" << offset - << " BUFr=" << avail_bufsize - << " (" << exc_type << "/" << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " - << packet.MessageFlagStr()); + if (adding_successful) + { + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + } + +#if ENABLE_HEAVY_LOGGING + std::ostringstream expectspec; + if (excessive) + expectspec << "EXCESSIVE(" << exc_type << rexmit_reason << ")"; + else + expectspec << "ACCEPTED"; + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo + << " offset=" << offset + << " BUFr=" << avail_bufsize + << " avail=" << getAvailRcvBufferSizeNoLock() + << " buffer=(" << m_iRcvLastSkipAck + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1) + << ") " + << " RSL=" << expectspec.str() + << " SN=" << rexmitstat[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif // Decryption should have made the crypto flags EK_NOENC. // Otherwise it's an error. if (adding_successful) { - HLOGC(dlog.Debug, + // XXX move this code do CUDT::defaultPacketArrival and call it from here: + // srt_loss_seqs = CALLBACK_CALL(m_cbPacketArrival, rpkt); + + HLOGC(qrlog.Debug, log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); @@ -8528,7 +10230,7 @@ int CUDT::processData(CUnit *in_unit) { m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); } - HLOGC(mglog.Debug, + HLOGC(qrlog.Debug, log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs) << " tolerance: " << initial_loss_ttl); reorder_prevent_lossreport = true; @@ -8563,7 +10265,7 @@ int CUDT::processData(CUnit *in_unit) // a given period). if (m_CongCtl->needsQuickACK(packet)) { - CTimer::rdtsc(m_ullNextACKTime_tk); + m_tsNextACKTime.store(steady_clock::now()); } } @@ -8571,7 +10273,6 @@ int CUDT::processData(CUnit *in_unit) { return -1; } - } // End of recvbuf_acklock if (m_bClosing) @@ -8599,10 +10300,10 @@ int CUDT::processData(CUnit *in_unit) // TODO: Can unlock rcvloss after m_pRcvLossList->insert(...)? // And probably protect m_FreshLoss as well. - HLOGC(mglog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING."); + HLOGC(qrlog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING."); // if record_loss == false, nothing will be contained here // Insert lost sequence numbers to the receiver loss list - CGuard lg(m_RcvLossLock); + ScopedLock lg(m_RcvLossLock); for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) { // If loss found, insert them to the receiver loss list @@ -8613,15 +10314,18 @@ int CUDT::processData(CUnit *in_unit) const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS; if (!reorder_prevent_lossreport && report_recorded_loss) { - HLOGC(mglog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); + HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } if (m_bTsbPd) { - pthread_mutex_lock(&m_RecvLock); - pthread_cond_signal(&m_RcvTsbPdCond); - pthread_mutex_unlock(&m_RecvLock); + HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); + CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); + } + else + { + HLOGC(qrlog.Debug, log << "loss: socket is not TSBPD, not signaling"); } } @@ -8632,14 +10336,13 @@ int CUDT::processData(CUnit *in_unit) // With NEVER, nothing is to be reported. if (!filter_loss_seqs.empty()) { - HLOGC(mglog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); + HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); if (m_bTsbPd) { - pthread_mutex_lock(&m_RecvLock); - pthread_cond_signal(&m_RcvTsbPdCond); - pthread_mutex_unlock(&m_RecvLock); + HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond"); + CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); } } @@ -8650,7 +10353,7 @@ int CUDT::processData(CUnit *in_unit) // is linear time. On the other hand, there are some special cases that are important for performance: // - only the first (plus some following) could have had TTL drown to 0 // - the only (little likely) possibility that the next-to-first record has TTL=0 is when there was - // a loss range split (due to unlose() of one sequence) + // a loss range split (due to dropFromLossLists() of one sequence) // - first found record with TTL>0 means end of "ready to LOSSREPORT" records // So: // All you have to do is: @@ -8663,7 +10366,7 @@ int CUDT::processData(CUnit *in_unit) vector lossdata; { - CGuard lg(m_RcvLossLock); + ScopedLock lg(m_RcvLossLock); // XXX There was a mysterious crash around m_FreshLoss. When the initial_loss_ttl is 0 // (that is, "belated loss report" feature is off), don't even touch m_FreshLoss. @@ -8673,11 +10376,11 @@ int CUDT::processData(CUnit *in_unit) // Phase 1: take while TTL <= 0. // There can be more than one record with the same TTL, if it has happened before - // that there was an 'unlost' (@c unlose) sequence that has split one detected loss + // that there was an 'unlost' (@c dropFromLossLists) sequence that has split one detected loss // into two records. for (; i != m_FreshLoss.end() && i->ttl <= 0; ++i) { - HLOGF(mglog.Debug, + HLOGF(qrlog.Debug, "Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT", i->seq[0], i->seq[1], @@ -8694,11 +10397,11 @@ int CUDT::processData(CUnit *in_unit) if (m_FreshLoss.empty()) { - HLOGP(mglog.Debug, "NO MORE FRESH LOSS RECORDS."); + HLOGP(qrlog.Debug, "NO MORE FRESH LOSS RECORDS."); } else { - HLOGF(mglog.Debug, + HLOGF(qrlog.Debug, "STILL %" PRIzu " FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d", m_FreshLoss.size(), i->seq[0], @@ -8714,7 +10417,7 @@ int CUDT::processData(CUnit *in_unit) } if (!lossdata.empty()) { - sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], lossdata.size()); + sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int) lossdata.size()); } // was_sent_in_order means either of: @@ -8730,10 +10433,10 @@ int CUDT::processData(CUnit *in_unit) if (m_iReorderTolerance > 0) { m_iReorderTolerance--; - CGuard::enterCS(m_StatsLock); + enterCS(m_StatsLock); m_stats.traceReorderDistance--; - CGuard::leaveCS(m_StatsLock); - HLOGF(mglog.Debug, + leaveCS(m_StatsLock); + HLOGF(qrlog.Debug, "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d", m_iReorderTolerance); } @@ -8743,6 +10446,101 @@ int CUDT::processData(CUnit *in_unit) return 0; } +#if ENABLE_BONDING +void srt::CUDT::updateIdleLinkFrom(CUDT* source) +{ + ScopedLock lg (m_RecvLock); + + if (!m_pRcvBuffer->empty()) + { + HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": receiver buffer not empty"); + return; + } + + // XXX Try to optimize this. Note that here happens: + // - decseq just to have a value to compare directly + // - seqcmp with that value + // - if passed, in setInitialRcvSeq there's the same decseq again + int32_t new_last_rcv = CSeqNo::decseq(source->m_iRcvLastSkipAck); + + // if (new_last_rcv <% m_iRcvCurrSeqNo) + if (CSeqNo::seqcmp(new_last_rcv, m_iRcvCurrSeqNo) < 0) + { + // Reject the change because that would shift the reception pointer backwards. + HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID + << ": backward setting rejected: %" << m_iRcvCurrSeqNo + << " -> %" << new_last_rcv); + return; + } + + HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID + << " from @" << source->m_SocketID << ": %" << source->m_iRcvLastSkipAck); + setInitialRcvSeq(source->m_iRcvLastSkipAck); +} + +// XXX This function is currently unused. It should be fixed and put into use. +// See the blocked call in CUDT::processData(). +// XXX REVIEW LOCKS WHEN REACTIVATING! +srt::CUDT::loss_seqs_t srt::CUDT::defaultPacketArrival(void* vself, CPacket& pkt) +{ +// [[using affinity(m_pRcvBuffer->workerThread())]]; + CUDT* self = (CUDT*)vself; + loss_seqs_t output; + + // XXX When an alternative packet arrival callback is installed + // in case of groups, move this part to the groupwise version. + + if (self->m_parent->m_GroupOf) + { + groups::SocketData* gi = self->m_parent->m_GroupMemberData; + if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely + { + HLOGC(qrlog.Debug, log << "defaultPacketArrival: IN-GROUP rcv state transition to RUNNING. NOT checking for loss"); + gi->rcvstate = SRT_GST_RUNNING; + return output; + } + } + + const int initial_loss_ttl = (self->m_bPeerRexmitFlag) ? self->m_iReorderTolerance : 0; + + int seqdiff = CSeqNo::seqcmp(pkt.m_iSeqNo, self->m_iRcvCurrSeqNo); + + HLOGC(qrlog.Debug, log << "defaultPacketArrival: checking sequence " << pkt.m_iSeqNo + << " against latest " << self->m_iRcvCurrSeqNo << " (distance: " << seqdiff << ")"); + + // Loss detection. + if (seqdiff > 1) // packet is later than the very subsequent packet + { + const int32_t seqlo = CSeqNo::incseq(self->m_iRcvCurrSeqNo); + const int32_t seqhi = CSeqNo::decseq(pkt.m_iSeqNo); + + { + // If loss found, insert them to the receiver loss list + ScopedLock lg (self->m_RcvLossLock); + self->m_pRcvLossList->insert(seqlo, seqhi); + + if (initial_loss_ttl) + { + // pack loss list for (possibly belated) NAK + // The LOSSREPORT will be sent in a while. + self->m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl)); + HLOGF(qrlog.Debug, "defaultPacketArrival: added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi, + 1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl); + } + } + + if (!initial_loss_ttl) + { + // old code; run immediately when tolerance = 0 + // or this feature isn't used because of the peer + output.push_back(make_pair(seqlo, seqhi)); + } + } + + return output; +} +#endif + /// This function is called when a packet has arrived, which was behind the current /// received sequence - that is, belated or retransmitted. Try to remove the packet /// from both loss records: the general loss record and the fresh loss record. @@ -8756,11 +10554,11 @@ int CUDT::processData(CUnit *in_unit) /// will be set to the tolerance value, which means that later packet retransmission /// will not be required immediately, but only after receiving N next packets that /// do not include the lacking packet. -/// The tolerance is not increased infinitely - it's bordered by m_iMaxReorderTolerance. +/// The tolerance is not increased infinitely - it's bordered by iMaxReorderTolerance. /// This value can be set in options - SRT_LOSSMAXTTL. -void CUDT::unlose(const CPacket &packet) +void srt::CUDT::unlose(const CPacket &packet) { - CGuard lg(m_RcvLossLock); + ScopedLock lg(m_RcvLossLock); int32_t sequence = packet.m_iSeqNo; m_pRcvLossList->remove(sequence); @@ -8779,16 +10577,16 @@ void CUDT::unlose(const CPacket &packet) was_reordered = !packet.getRexmitFlag(); if (was_reordered) { - HLOGF(mglog.Debug, "received out-of-band packet seq %d", sequence); + HLOGF(qrlog.Debug, "received out-of-band packet seq %d", sequence); const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo)); - CGuard::enterCS(m_StatsLock); + enterCS(m_StatsLock); m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance); - CGuard::leaveCS(m_StatsLock); + leaveCS(m_StatsLock); if (seqdiff > m_iReorderTolerance) { - const int new_tolerance = min(seqdiff, m_iMaxReorderTolerance); - HLOGF(mglog.Debug, + const int new_tolerance = min(seqdiff, m_config.iMaxReorderTolerance); + HLOGF(qrlog.Debug, "Belated by %d seqs - Reorder tolerance %s %d", seqdiff, (new_tolerance == m_iReorderTolerance) ? "REMAINS with" : "increased to", @@ -8800,12 +10598,12 @@ void CUDT::unlose(const CPacket &packet) } else { - HLOGC(mglog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence); + HLOGC(qrlog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence); } } else { - HLOGF(mglog.Debug, "received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence); + HLOGF(qrlog.Debug, "received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence); } // Don't do anything if "belated loss report" feature is not used. @@ -8867,7 +10665,7 @@ breakbreak:; if (i != m_FreshLoss.size()) { - HLOGF(mglog.Debug, "sequence %d removed from belated lossreport record", sequence); + HLOGF(qrlog.Debug, "sequence %d removed from belated lossreport record", sequence); } if (was_reordered) @@ -8880,7 +10678,7 @@ breakbreak:; else if (had_ttl > 2) { ++m_iConsecEarlyDelivery; // otherwise, and if it arrived quite earlier, increase counter - HLOGF(mglog.Debug, "... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery); + HLOGF(qrlog.Debug, "... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery); // After 10 consecutive if (m_iConsecEarlyDelivery >= 10) @@ -8889,10 +10687,10 @@ breakbreak:; if (m_iReorderTolerance > 0) { m_iReorderTolerance--; - CGuard::enterCS(m_StatsLock); + enterCS(m_StatsLock); m_stats.traceReorderDistance--; - CGuard::leaveCS(m_StatsLock); - HLOGF(mglog.Debug, + leaveCS(m_StatsLock); + HLOGF(qrlog.Debug, "... reached %d times - decreasing tolerance to %d", m_iConsecEarlyDelivery, m_iReorderTolerance); @@ -8903,12 +10701,12 @@ breakbreak:; } } -void CUDT::dropFromLossLists(int32_t from, int32_t to) +void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) { - CGuard lg(m_RcvLossLock); + ScopedLock lg(m_RcvLossLock); m_pRcvLossList->remove(from, to); - HLOGF(mglog.Debug, "TLPKTDROP seq %d-%d (%d packets)", from, to, CSeqNo::seqoff(from, to)); + HLOGF(qrlog.Debug, "%sTLPKTDROP seq %d-%d (%d packets)", CONID().c_str(), from, to, CSeqNo::seqoff(from, to)); if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; @@ -8946,7 +10744,7 @@ void CUDT::dropFromLossLists(int32_t from, int32_t to) } // This function, as the name states, should bake a new cookie. -int32_t CUDT::bake(const sockaddr *addr, int32_t current_cookie, int correction) +int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int correction) { static unsigned int distractor = 0; unsigned int rollover = distractor + 10; @@ -8956,14 +10754,14 @@ int32_t CUDT::bake(const sockaddr *addr, int32_t current_cookie, int correction) // SYN cookie char clienthost[NI_MAXHOST]; char clientport[NI_MAXSERV]; - getnameinfo(addr, - (m_iIPversion == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6), + getnameinfo(addr.get(), + addr.size(), clienthost, sizeof(clienthost), clientport, sizeof(clientport), NI_NUMERICHOST | NI_NUMERICSERV); - int64_t timestamp = ((CTimer::getTime() - m_stats.startTime) / 60000000) + distractor - + int64_t timestamp = (count_microseconds(steady_clock::now() - m_stats.tsStartTime) / 60000000) + distractor + correction; // secret changes every one minute stringstream cookiestr; cookiestr << clienthost << ":" << clientport << ":" << timestamp; @@ -9003,17 +10801,19 @@ int32_t CUDT::bake(const sockaddr *addr, int32_t current_cookie, int correction) // // XXX Make this function return EConnectStatus enum type (extend if needed), // and this will be directly passed to the caller. -SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &packet) + +// [[using locked(m_pRcvQueue->m_LSLock)]]; +int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) { // XXX ASSUMPTIONS: // [[using assert(packet.m_iID == 0)]] - HLOGC(mglog.Debug, log << "processConnectRequest: received a connection request"); + HLOGC(cnlog.Debug, log << "processConnectRequest: received a connection request"); if (m_bClosing) { m_RejectReason = SRT_REJ_CLOSE; - HLOGC(mglog.Debug, log << "processConnectRequest: ... NOT. Rejecting because closing."); + HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because closing."); return m_RejectReason; } @@ -9025,11 +10825,11 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (m_bBroken) { m_RejectReason = SRT_REJ_CLOSE; - HLOGC(mglog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken."); + HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken."); return m_RejectReason; } - size_t exp_len = - CHandShake::m_iContentSize; // When CHandShake::m_iContentSize is used in log, the file fails to link! + // When CHandShake::m_iContentSize is used in log, the file fails to link! + size_t exp_len = CHandShake::m_iContentSize; // NOTE!!! Old version of SRT code checks if the size of the HS packet // is EQUAL to the above CHandShake::m_iContentSize. @@ -9040,7 +10840,7 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (packet.getLength() < exp_len) { m_RejectReason = SRT_REJ_ROGUE; - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len << ")"); return m_RejectReason; @@ -9052,7 +10852,7 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (!packet.isControl(UMSG_HANDSHAKE)) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, log << "processConnectRequest: the packet received as handshake is not a handshake message"); + LOGC(cnlog.Error, log << "processConnectRequest: the packet received as handshake is not a handshake message"); return m_RejectReason; } @@ -9070,14 +10870,14 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac int32_t cookie_val = bake(addr); - HLOGC(mglog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val); + HLOGC(cnlog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val); // REQUEST:INDUCTION. // Set a cookie, a target ID, and send back the same as // RESPONSE:INDUCTION. if (hs.m_iReqType == URQ_INDUCTION) { - HLOGC(mglog.Debug, log << "processConnectRequest: received type=induction, sending back with cookie+socket"); + HLOGC(cnlog.Debug, log << "processConnectRequest: received type=induction, sending back with cookie+socket"); // XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp // is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies @@ -9103,15 +10903,19 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac // Additionally, set this field to a MAGIC value. This field isn't used during INDUCTION // by HSv4 client, HSv5 client can use it to additionally verify that this is a HSv5 listener. // In this field we also advertise the PBKEYLEN value. When 0, it's considered not advertised. - hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_iSndCryptoKeyLen); - bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0; - HLOGC(mglog.Debug, + hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_config.iSndCryptoKeyLen); + bool whether SRT_ATR_UNUSED = m_config.iSndCryptoKeyLen != 0; + HLOGC(cnlog.Debug, log << "processConnectRequest: " << (whether ? "" : "NOT ") - << " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen); + << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); size_t size = packet.getLength(); - hs.store_to(packet.m_pcData, Ref(size)); - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); + hs.store_to((packet.m_pcData), (size)); + setPacketTS(packet, steady_clock::now()); + + // Display the HS before sending it to peer + HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (i): " << hs.show()); + m_pSndQueue->sendto(addr, packet); return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code. } @@ -9121,7 +10925,14 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac // set in the above INDUCTION, in the HS_VERSION_SRT1 // should also contain extra data. - HLOGC(mglog.Debug, + if (!hs.valid()) + { + LOGC(cnlog.Error, log << "processConnectRequest: ROGUE HS RECEIVED. Rejecting"); + m_RejectReason = SRT_REJ_ROGUE; + return SRT_REJ_ROGUE; + } + + HLOGC(cnlog.Debug, log << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie..."); if (hs.m_iCookie != cookie_val) { @@ -9130,15 +10941,15 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (hs.m_iCookie != cookie_val) { m_RejectReason = SRT_REJ_RDVCOOKIE; - HLOGC(mglog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); + HLOGC(cnlog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); return m_RejectReason; } - HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); + HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); } else { - HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); + HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); } int32_t id = hs.m_iID; @@ -9182,21 +10993,28 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (!accepted_hs) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason - << " MSG: " << srt_rejectreason_str(m_RejectReason)); + << " MSG: " << srt_rejectreason_str(m_RejectReason)); // mismatch, reject the request hs.m_iReqType = URQFailure(m_RejectReason); size_t size = CHandShake::m_iContentSize; - hs.store_to(packet.m_pcData, Ref(size)); + hs.store_to((packet.m_pcData), (size)); packet.m_iID = id; - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); + setPacketTS(packet, steady_clock::now()); + HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (e): " << hs.show()); m_pSndQueue->sendto(addr, packet); } else { - SRT_REJECT_REASON error = SRT_REJ_UNKNOWN; - int result = s_UDTUnited.newConnection(m_SocketID, addr, &hs, packet, Ref(error)); + // IMPORTANT!!! + // If the newConnection() detects there is already a socket connection associated with the remote peer, + // it returns the socket via `acpu`, and the `result` returned is 0. + // Else if a new connection is successfully created, the conclusion handshake response + // is sent by the function itself (it calls the acceptAndRespond(..)), the `acpu` remains null, the `result` is 1. + int error = SRT_REJ_UNKNOWN; + CUDT* acpu = NULL; + int result = uglobal().newConnection(m_SocketID, addr, packet, (hs), (error), (acpu)); // This is listener - m_RejectReason need not be set // because listener has no functionality of giving the app @@ -9208,59 +11026,116 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (result == -1) { hs.m_iReqType = URQFailure(error); - LOGF(mglog.Error, "UU:newConnection: rsp(REJECT): %d - %s", hs.m_iReqType, srt_rejectreason_str(error)); - } - - // CONFUSION WARNING! - // - // The newConnection() will call acceptAndRespond() if the processing - // was successful - IN WHICH CASE THIS PROCEDURE SHOULD DO NOTHING. - // Ok, almost nothing - see update_events below. - // - // If newConnection() failed, acceptAndRespond() will not be called. - // Ok, more precisely, the thing that acceptAndRespond() is expected to do - // will not be done (this includes sending any response to the peer). - // - // Now read CAREFULLY. The newConnection() will return: - // - // - -1: The connection processing failed due to errors like: - // - memory alloation error - // - listen backlog exceeded - // - any error propagated from CUDT::open and CUDT::acceptAndRespond - // - 0: The connection already exists - // - 1: Connection accepted. - // - // So, update_events is called only if the connection is established. - // Both 0 (repeated) and -1 (error) require that a response be sent. - // The CPacket object that has arrived as a connection request is here - // reused for the connection rejection response (see URQ_ERROR_REJECT set - // as m_iReqType). - - // send back a response if connection failed or connection already existed - // new connection response should be sent in acceptAndRespond() - if (result != 1) - { - HLOGC(mglog.Debug, - log << CONID() << "processConnectRequest: sending ABNORMAL handshake info req=" + LOGF(cnlog.Warn, "processConnectRequest: rsp(REJECT): %d - %s", hs.m_iReqType, srt_rejectreason_str(error)); + } + + // The `acpu` not NULL means connection exists, the `result` should be 0. It is not checked here though. + // The `newConnection(..)` only sends reponse for newly created connection. + // The connection already exists (no new connection has been created, no response sent). + // Send the conclusion response manually here in case the peer has missed the first one. + // The value `result` here should be 0. + if (acpu) + { + // This is an existing connection, so the handshake is only needed + // because of the rule that every handshake request must be covered + // by the handshake response. It wouldn't be good to call interpretSrtHandshake + // here because the data from the handshake have been already interpreted + // and recorded. We just need to craft a response. + HLOGC(cnlog.Debug, + log << CONID() << "processConnectRequest: sending REPEATED handshake response req=" << RequestTypeStr(hs.m_iReqType)); - size_t size = CHandShake::m_iContentSize; - hs.store_to(packet.m_pcData, Ref(size)); - packet.m_iID = id; - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - m_pSndQueue->sendto(addr, packet); + + // Rewrite already updated previously data in acceptAndRespond + acpu->rewriteHandshakeData(acpu->m_PeerAddr, (hs)); + + uint32_t kmdata[SRTDATA_MAXSIZE]; + size_t kmdatasize = SRTDATA_MAXSIZE; + EConnectStatus conn = CONN_ACCEPT; + + if (hs.m_iVersion >= HS_VERSION_SRT1) + { + // Always attach extension. + hs.m_extension = true; + conn = acpu->craftKmResponse((kmdata), (kmdatasize)); + } + else + { + kmdatasize = 0; + } + + if (conn != CONN_ACCEPT) + return conn; + + packet.setLength(m_iMaxSRTPayloadSize); + if (!acpu->createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, + kmdata, kmdatasize, + (packet), (hs))) + { + HLOGC(cnlog.Debug, + log << "processConnectRequest: rejecting due to problems in createSrtHandshake."); + result = -1; // enforce fallthrough for the below condition! + hs.m_iReqType = URQFailure(m_RejectReason == SRT_REJ_UNKNOWN ? int(SRT_REJ_IPE) : m_RejectReason.load()); + } + else + { + // Send the crafted handshake + HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING (repeated) HS (a): " << hs.show()); + acpu->addressAndSend((packet)); + } } - else + + if (result == 1) { + // BUG! There is no need to update write-readiness on the listener socket once new connection is accepted. + // Only read-readiness has to be updated, but it is done so in the newConnection(..) function. + // See PR #1831 and issue #1667. + HLOGC(cnlog.Debug, log << "processConnectRequest: @" << m_SocketID + << " accepted connection, updating epoll to write-ready"); + + // New connection has been accepted or an existing one has been found. Update epoll write-readiness. // a new connection has been created, enable epoll for write - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + // Note: not using SRT_EPOLL_CONNECT symbol because this is a procedure + // executed for the accepted socket. + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); + } + else if (result == -1) + { + // The new connection failed + // or the connection already existed, but manually sending the HS response above has failed. + // HSv4: Send the SHUTDOWN message to the peer (see PR #2010) in order to disallow the peer to connect. + // The HSv4 clients do not interpret the error handshake response correctly. + // HSv5: Send a handshake with an error code (hs.m_iReqType set earlier) to the peer. + if (hs.m_iVersion < HS_VERSION_SRT1) + { + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: HSv4 caller, sending SHUTDOWN after rejection with " + << RequestTypeStr(hs.m_iReqType)); + CPacket rsp; + setPacketTS((rsp), steady_clock::now()); + rsp.pack(UMSG_SHUTDOWN); + rsp.m_iID = m_PeerID; + m_pSndQueue->sendto(addr, rsp); + } + else + { + HLOGC(cnlog.Debug, + log << CONID() << "processConnectRequest: sending ABNORMAL handshake info req=" + << RequestTypeStr(hs.m_iReqType)); + size_t size = CHandShake::m_iContentSize; + hs.store_to((packet.m_pcData), (size)); + packet.setLength(size); + packet.m_iID = id; + setPacketTS(packet, steady_clock::now()); + HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (a): " << hs.show()); + m_pSndQueue->sendto(addr, packet); + } } } - LOGC(mglog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); + LOGC(cnlog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); return RejectReasonForURQ(hs.m_iReqType); } -void CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) +void srt::CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) { if (lo == hi) lr.push_back(lo); @@ -9271,28 +11146,31 @@ void CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) } } -void CUDT::checkACKTimer(uint64_t currtime_tk) +int srt::CUDT::checkACKTimer(const steady_clock::time_point &currtime) { - if (currtime_tk > m_ullNextACKTime_tk // ACK time has come - // OR the number of sent packets since last ACK has reached - // the congctl-defined value of ACK Interval - // (note that none of the builtin congctls defines ACK Interval) + int because_decision = BECAUSE_NO_REASON; + if (currtime > m_tsNextACKTime.load() // ACK time has come + // OR the number of sent packets since last ACK has reached + // the congctl-defined value of ACK Interval + // (note that none of the builtin congctls defines ACK Interval) || (m_CongCtl->ACKMaxPackets() > 0 && m_iPktCount >= m_CongCtl->ACKMaxPackets())) { // ACK timer expired or ACK interval is reached sendCtrl(UMSG_ACK); - CTimer::rdtsc(currtime_tk); - const int ack_interval_tk = - m_CongCtl->ACKTimeout_us() > 0 ? m_CongCtl->ACKTimeout_us() * m_ullCPUFrequency : m_ullACKInt_tk; - m_ullNextACKTime_tk = currtime_tk + ack_interval_tk; + const steady_clock::duration ack_interval = m_CongCtl->ACKTimeout_us() > 0 + ? microseconds_from(m_CongCtl->ACKTimeout_us()) + : m_tdACKInterval; + m_tsNextACKTime.store(currtime + ack_interval); m_iPktCount = 0; m_iLightACKCount = 1; + because_decision = BECAUSE_ACK; } + // Or the transfer rate is so high that the number of packets // have reached the value of SelfClockInterval * LightACKCount before - // the time has come according to m_ullNextACKTime_tk. In this case a "lite ACK" + // the time has come according to m_tsNextACKTime. In this case a "lite ACK" // is sent, which doesn't contain statistical data and nothing more // than just the ACK number. The "fat ACK" packets will be still sent // normally according to the timely rules. @@ -9301,10 +11179,13 @@ void CUDT::checkACKTimer(uint64_t currtime_tk) // send a "light" ACK sendCtrl(UMSG_ACK, NULL, NULL, SEND_LITE_ACK); ++m_iLightACKCount; + because_decision = BECAUSE_LITEACK; } + + return because_decision; } -void CUDT::checkNAKTimer(uint64_t currtime_tk) +int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) { // XXX The problem with working NAKREPORT with SRT_ARQ_ONREQ // is not that it would be inappropriate, but because it's not @@ -9317,64 +11198,93 @@ void CUDT::checkNAKTimer(uint64_t currtime_tk) // by the filter. By this reason they appear often out of order // and for adding them properly the loss list container wasn't // prepared. This then requires some more effort to implement. - if (!m_bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) - return; + if (!m_config.bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) + return BECAUSE_NO_REASON; /* - * m_bRcvNakReport enables NAK reports for SRT. + * m_config.bRcvNakReport enables NAK reports for SRT. * Retransmission based on timeout is bandwidth consuming, * not knowing what to retransmit when the only NAK sent by receiver is lost, * all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT). */ const int loss_len = m_pRcvLossList->getLossLength(); SRT_ASSERT(loss_len >= 0); + int debug_decision = BECAUSE_NO_REASON; if (loss_len > 0) { - if (currtime_tk <= m_ullNextNAKTime_tk) - return; // wait for next NAK time + if (currtime <= m_tsNextNAKTime.load()) + return BECAUSE_NO_REASON; // wait for next NAK time sendCtrl(UMSG_LOSSREPORT); + debug_decision = BECAUSE_NAKREPORT; } - m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk; + m_tsNextNAKTime.store(currtime + m_tdNAKInterval); + return debug_decision; } -bool CUDT::checkExpTimer(uint64_t currtime_tk) +bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int check_reason SRT_ATR_UNUSED) { + // VERY HEAVY LOGGING +#if ENABLE_HEAVY_LOGGING & 1 + static const char* const decisions [] = { + "ACK", + "LITE-ACK", + "NAKREPORT" + }; + + string decision = "NOTHING"; + if (check_reason) + { + ostringstream decd; + decision = ""; + for (int i = 0; i < LAST_BECAUSE_BIT; ++i) + { + int flag = 1 << i; + if (check_reason & flag) + decd << decisions[i] << " "; + } + decision = decd.str(); + } + HLOGC(xtlog.Debug, log << CONID() << "checkTimer: ACTIVITIES PERFORMED: " << decision); +#endif + // In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class. // There's nothing in the original code that alters these values. - uint64_t next_exp_time_tk; + steady_clock::time_point next_exp_time; if (m_CongCtl->RTO()) { - next_exp_time_tk = m_ullLastRspTime_tk + m_CongCtl->RTO() * m_ullCPUFrequency; + next_exp_time = m_tsLastRspTime.load() + microseconds_from(m_CongCtl->RTO()); } else { - uint64_t exp_int_tk = (m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency; - if (exp_int_tk < m_iEXPCount * m_ullMinExpInt_tk) - exp_int_tk = m_iEXPCount * m_ullMinExpInt_tk; - next_exp_time_tk = m_ullLastRspTime_tk + exp_int_tk; + steady_clock::duration exp_timeout = + microseconds_from(m_iEXPCount * (m_iSRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US); + if (exp_timeout < (m_iEXPCount * m_tdMinExpInterval)) + exp_timeout = m_iEXPCount * m_tdMinExpInterval; + next_exp_time = m_tsLastRspTime.load() + exp_timeout; } - if (currtime_tk <= next_exp_time_tk) + if (currtime <= next_exp_time && !m_bBreakAsUnstable) return false; // ms -> us - const int PEER_IDLE_TMO_US = m_iOPT_PeerIdleTimeout * 1000; + const int PEER_IDLE_TMO_US = m_config.iPeerIdleTimeout_ms * 1000; // Haven't received any information from the peer, is it dead?! // timeout: at least 16 expirations and must be greater than 5 seconds - if ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) && - (currtime_tk - m_ullLastRspTime_tk > PEER_IDLE_TMO_US * m_ullCPUFrequency)) + time_point last_rsp_time = m_tsLastRspTime.load(); + if (m_bBreakAsUnstable || ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) && + (currtime - last_rsp_time > microseconds_from(PEER_IDLE_TMO_US)))) { // // Connection is broken. // UDT does not signal any information about this instead of to stop quietly. // Application will detect this when it calls any UDT methods next time. // - HLOGC(mglog.Debug, - log << "CONNECTION EXPIRED after " << ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "ms"); + HLOGC(xtlog.Debug, + log << "CONNECTION EXPIRED after " << count_milliseconds(currtime - last_rsp_time) << "ms"); m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 30; @@ -9382,19 +11292,15 @@ bool CUDT::checkExpTimer(uint64_t currtime_tk) // update snd U list to remove this socket m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); - releaseSynch(); - - // app can call any UDT API to learn the connection_broken error - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR, true); - - CTimer::triggerEvent(); + updateBrokenConnection(); + completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! return true; } - HLOGC(mglog.Debug, + HLOGC(xtlog.Debug, log << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed=" - << ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "/" << (+PEER_IDLE_TMO_US) << "us"); + << (count_microseconds(currtime - last_rsp_time)) << "/" << (+PEER_IDLE_TMO_US) << "us"); ++m_iEXPCount; @@ -9402,40 +11308,40 @@ bool CUDT::checkExpTimer(uint64_t currtime_tk) * (keepalive fix) * duB: * It seems there is confusion of the direction of the Response here. - * LastRspTime is supposed to be when receiving (data/ctrl) from peer + * lastRspTime is supposed to be when receiving (data/ctrl) from peer * as shown in processCtrl and processData, * Here we set because we sent something? * * Disabling this code that prevent quick reconnection when peer disappear */ // Reset last response time since we've just sent a heart-beat. - // (fixed) m_ullLastRspTime_tk = currtime_tk; + // (fixed) m_tsLastRspTime = currtime_tk; return false; } -void CUDT::checkRexmitTimer(uint64_t currtime_tk) +void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) { - /* There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT. - * - * LATEREXMIT is only used with FileCC. - * The mode is triggered when some time has passed since the last ACK from - * the receiver, while there is still some unacknowledged data in the sender's buffer, - * and the loss list is empty. - * - * FASTREXMIT is only used with LiveCC. - * The mode is triggered if the receiver does not send periodic NAK reports, - * when some time has passed since the last ACK from the receiver, - * while there is still some unacknowledged data in the sender's buffer. - * - * In case the above conditions are met, the unacknowledged packets - * in the sender's buffer will be added to loss list and retransmitted. - */ + // There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT. + // + // LATEREXMIT is only used with FileCC. + // The RTO is triggered when some time has passed since the last ACK from + // the receiver, while there is still some unacknowledged data in the sender's buffer, + // and the loss list is empty at the moment of RTO (nothing to retransmit yet). + // + // FASTREXMIT is only used with LiveCC. + // The RTO is triggered if the receiver is not configured to send periodic NAK reports, + // when some time has passed since the last ACK from the receiver, + // while there is still some unacknowledged data in the sender's buffer. + // + // In case the above conditions are met, the unacknowledged packets + // in the sender's buffer will be added to the SND loss list and retransmitted. + // - const uint64_t rtt_syn = (m_iRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US); - const uint64_t exp_int = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency; + const uint64_t rtt_syn = (m_iSRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US); + const uint64_t exp_int_us = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US); - if (currtime_tk <= (m_ullLastRspAckTime_tk + exp_int)) + if (currtime <= (m_tsLastRspAckTime + microseconds_from(exp_int_us))) return; // If there is no unacknowledged data in the sending buffer, @@ -9443,39 +11349,34 @@ void CUDT::checkRexmitTimer(uint64_t currtime_tk) if (m_pSndBuffer->getCurrBufSize() <= 0) return; - const bool is_laterexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_LATEREXMIT; - const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT; + const bool is_laterexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_LATEREXMIT; // FileCC + const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT; // LiveCC - // If the receiver will send periodic NAK reports, then FASTREXMIT is inactive. - // MIND that probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off. + // If the receiver will send periodic NAK reports, then FASTREXMIT (live) is inactive. + // TODO: Probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off. if (is_fastrexmit && m_bPeerNakReport) return; - // We need to retransmit only when the data in the sender's buffer was already sent. - // Otherwise it might still be sent regulary. - bool retransmit = false; - // - the sender loss list is empty (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track) - if (is_laterexmit && (CSeqNo::incseq(m_iSndCurrSeqNo) != m_iSndLastAck) && m_pSndLossList->getLossLength() == 0) - retransmit = true; - - if (is_fastrexmit && (CSeqNo::seqoff(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0)) - retransmit = true; - - if (retransmit) + // Schedule a retransmission IF: + // - there are packets in flight (getFlightSpan() > 0); + // - in case of LATEREXMIT (File Mode): the sender loss list is empty + // (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track). + // - in case of FASTREXMIT (Live Mode): the RTO (rtt_syn) was triggered, therefore + // schedule unacknowledged packets for retransmission regardless of the loss list emptiness. + if (getFlightSpan() > 0 && (!is_laterexmit || m_pSndLossList->getLossLength() == 0)) { // Sender: Insert all the packets sent after last received acknowledgement into the sender loss list. - CGuard acklock(m_RecvAckLock); // Protect packet retransmission + ScopedLock acklock(m_RecvAckLock); // Protect packet retransmission // Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list const int32_t csn = m_iSndCurrSeqNo; const int num = m_pSndLossList->insert(m_iSndLastAck, csn); if (num > 0) { - CGuard::enterCS(m_StatsLock); - m_stats.traceSndLoss += num; - m_stats.sndLossTotal += num; - CGuard::leaveCS(m_StatsLock); + enterCS(m_StatsLock); + m_stats.sndr.lost.count(num); + leaveCS(m_StatsLock); - HLOGC(mglog.Debug, + HLOGC(xtlog.Debug, log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT") << " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " (" << CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)"); @@ -9486,104 +11387,183 @@ void CUDT::checkRexmitTimer(uint64_t currtime_tk) checkSndTimers(DONT_REGEN_KM); const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT; - updateCC(TEV_CHECKTIMER, stage); + updateCC(TEV_CHECKTIMER, EventVariant(stage)); - // immediately restart transmission - m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); + // schedule sending if not scheduled already + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); } -void CUDT::checkTimers() +void srt::CUDT::checkTimers() { // update CC parameters - updateCC(TEV_CHECKTIMER, TEV_CHT_INIT); - // uint64_t minint = (uint64_t)(m_ullCPUFrequency * m_pSndTimeWindow->getMinPktSndInt() * 0.9); - // if (m_ullInterval_tk < minint) - // m_ullInterval_tk = minint; - // NOTE: This commented-out ^^^ code was commented out in original UDT. Leaving for historical reasons + updateCC(TEV_CHECKTIMER, EventVariant(TEV_CHT_INIT)); - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); + const steady_clock::time_point currtime = steady_clock::now(); // This is a very heavy log, unblock only for temporary debugging! #if 0 - HLOGC(mglog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_ullNextACKTime_tk) + HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_tsNextACKTime) << " AckInterval=" << m_iACKInterval << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount); #endif // Check if it is time to send ACK - checkACKTimer(currtime_tk); + int debug_decision = checkACKTimer(currtime); // Check if it is time to send a loss report - checkNAKTimer(currtime_tk); + debug_decision |= checkNAKTimer(currtime); // Check if the connection is expired - if (checkExpTimer(currtime_tk)) + if (checkExpTimer(currtime, debug_decision)) return; // Check if FAST or LATE packet retransmission is required - checkRexmitTimer(currtime_tk); + checkRexmitTimer(currtime); - // uint64_t exp_int = (m_iRTT + 4 * m_iRTTVar + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency; - if (currtime_tk > m_ullLastSndTime_tk + (COMM_KEEPALIVE_PERIOD_US * m_ullCPUFrequency)) + if (currtime > m_tsLastSndTime.load() + microseconds_from(COMM_KEEPALIVE_PERIOD_US)) { sendCtrl(UMSG_KEEPALIVE); - HLOGP(mglog.Debug, "KEEPALIVE"); +#if ENABLE_BONDING + if (m_parent->m_GroupOf) + { + ScopedLock glock (uglobal().m_GlobControlLock); + if (m_parent->m_GroupOf) + { + // Pass socket ID because it's about changing group socket data + m_parent->m_GroupOf->internalKeepalive(m_parent->m_GroupMemberData); + // NOTE: GroupLock is unnecessary here because the only data read and + // modified is the target of the iterator from m_GroupMemberData. The + // iterator will be valid regardless of any container modifications. + } + } +#endif + HLOGP(xtlog.Debug, "KEEPALIVE"); + } +} + +void srt::CUDT::updateBrokenConnection() +{ + m_bClosing = true; + releaseSynch(); + // app can call any UDT API to learn the connection_broken error + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); + CGlobEvent::triggerEvent(); +} + +void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) +{ + int token = -1; + +#if ENABLE_BONDING + bool pending_broken = false; + { + ScopedLock guard_group_existence (uglobal().m_GlobControlLock); + if (m_parent->m_GroupOf) + { + token = m_parent->m_GroupMemberData->token; + if (m_parent->m_GroupMemberData->sndstate == SRT_GST_PENDING) + { + HLOGC(gmlog.Debug, log << "updateBrokenConnection: a pending link was broken - will be removed"); + pending_broken = true; + } + else + { + HLOGC(gmlog.Debug, log << "updateBrokenConnection: state=" << CUDTGroup::StateStr(m_parent->m_GroupMemberData->sndstate) << " a used link was broken - not closing automatically"); + } + + m_parent->m_GroupMemberData->sndstate = SRT_GST_BROKEN; + m_parent->m_GroupMemberData->rcvstate = SRT_GST_BROKEN; + } + } +#endif + + if (m_cbConnectHook) + { + CALLBACK_CALL(m_cbConnectHook, m_SocketID, errorcode, m_PeerAddr.get(), token); + } + +#if ENABLE_BONDING + { + // Lock GlobControlLock in order to make sure that + // the state if the socket having the group and the + // existence of the group will not be changed during + // the operation. The attempt of group deletion will + // have to wait until this operation completes. + ScopedLock lock(uglobal().m_GlobControlLock); + CUDTGroup* pg = m_parent->m_GroupOf; + if (pg) + { + // Bound to one call because this requires locking + pg->updateFailedLink(); + } + } + + // Sockets that never succeeded to connect must be deleted + // explicitly, otherwise they will never be deleted. + if (pending_broken) + { + // XXX This somehow can cause a deadlock + // uglobal()->close(m_parent); + m_parent->setBrokenClosed(); } +#endif } -void CUDT::addEPoll(const int eid) +void srt::CUDT::addEPoll(const int eid) { - CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock); + enterCS(uglobal().m_EPoll.m_EPollLock); m_sPollID.insert(eid); - CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); + leaveCS(uglobal().m_EPoll.m_EPollLock); if (!stillConnected()) return; - CGuard::enterCS(m_RecvLock); - if (m_pRcvBuffer->isRcvDataReady()) + enterCS(m_RecvLock); + if (isRcvBufferReady()) { - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } - CGuard::leaveCS(m_RecvLock); + leaveCS(m_RecvLock); - if (m_iSndBufSize > m_pSndBuffer->getCurrBufSize()) + if (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize()) { - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); } } -void CUDT::removeEPoll(const int eid) +void srt::CUDT::removeEPollEvents(const int eid) { // clear IO events notifications; // since this happens after the epoll ID has been removed, they cannot be set again set remove; remove.insert(eid); - s_UDTUnited.m_EPoll.update_events(m_SocketID, remove, UDT_EPOLL_IN | UDT_EPOLL_OUT, false); + uglobal().m_EPoll.update_events(m_SocketID, remove, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); +} - CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock); +void srt::CUDT::removeEPollID(const int eid) +{ + enterCS(uglobal().m_EPoll.m_EPollLock); m_sPollID.erase(eid); - CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); + leaveCS(uglobal().m_EPoll.m_EPollLock); } -void CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) +void srt::CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) { - if (evt >= TEV__SIZE) + if (evt >= TEV_E_SIZE) return; // sanity check m_Slots[evt].push_back(sl); } -void CUDT::DisconnectSignal(ETransmissionEvent evt) +void srt::CUDT::DisconnectSignal(ETransmissionEvent evt) { - if (evt >= TEV__SIZE) + if (evt >= TEV_E_SIZE) return; // sanity check m_Slots[evt].clear(); } -void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) +void srt::CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) { for (std::vector::iterator i = m_Slots[tev].begin(); i != m_Slots[tev].end(); ++i) { @@ -9591,19 +11571,19 @@ void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) } } -int CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) +int srt::CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) { - CUDTSocket *s = s_UDTUnited.locate(u); - if (!s || !s->m_pUDT) + CUDTSocket *s = uglobal().locateSocket(u); + if (!s) return -1; - CSndBuffer *b = s->m_pUDT->m_pSndBuffer; + CSndBuffer *b = s->core().m_pSndBuffer; if (!b) return -1; int bytecount, timespan; - int count = b->getCurrBufSize(Ref(bytecount), Ref(timespan)); + int count = b->getCurrBufSize((bytecount), (timespan)); if (blocks) *blocks = count; @@ -9614,29 +11594,56 @@ int CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) return std::abs(timespan); } -SRT_REJECT_REASON CUDT::rejectReason(SRTSOCKET u) +int srt::CUDT::rejectReason(SRTSOCKET u) { - CUDTSocket *s = s_UDTUnited.locate(u); - if (!s || !s->m_pUDT) + CUDTSocket* s = uglobal().locateSocket(u); + if (!s) return SRT_REJ_UNKNOWN; - return s->m_pUDT->m_RejectReason; + return s->core().m_RejectReason; +} + +int srt::CUDT::rejectReason(SRTSOCKET u, int value) +{ + CUDTSocket* s = uglobal().locateSocket(u); + if (!s) + return APIError(MJ_NOTSUP, MN_SIDINVAL); + + if (value < SRT_REJC_PREDEFINED) + return APIError(MJ_NOTSUP, MN_INVAL); + + s->core().m_RejectReason = value; + return 0; +} + +int64_t srt::CUDT::socketStartTime(SRTSOCKET u) +{ + CUDTSocket* s = uglobal().locateSocket(u); + if (!s) + return APIError(MJ_NOTSUP, MN_SIDINVAL); + + return count_microseconds(s->core().m_stats.tsStartTime.time_since_epoch()); } -bool CUDT::runAcceptHook(CUDT *acore, const sockaddr *peer, const CHandShake *hs, const CPacket &hspkt) +bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) { // Prepare the information for the hook. // We need streamid. - char target[MAX_SID_LENGTH + 1]; - memset(target, 0, MAX_SID_LENGTH + 1); + char target[CSrtConfig::MAX_SID_LENGTH + 1]; + memset((target), 0, CSrtConfig::MAX_SID_LENGTH + 1); // Just for a case, check the length. // This wasn't done before, and we could risk memory crash. // In case of error, this will remain unset and the empty // string will be passed as streamid. - int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs->m_iType); + int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); + +#if ENABLE_BONDING + bool have_group = false; + SRT_GROUP_TYPE gt = SRT_GTYPE_UNDEFINED; +#endif // This tests if there are any extensions. if (hspkt.getLength() > CHandShake::m_iContentSize + 4 && IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) @@ -9649,57 +11656,105 @@ bool CUDT::runAcceptHook(CUDT *acore, const sockaddr *peer, const CHandShake *hs for (;;) // ONE SHOT, but continuable loop { - int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next)); + int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); const size_t bytelen = blocklen * sizeof(uint32_t); if (cmd == SRT_CMD_SID) { - if (!bytelen || bytelen > MAX_SID_LENGTH) + if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { - LOGC(mglog.Error, - log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +MAX_SID_LENGTH + LOGC(cnlog.Error, + log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // See comment at CUDT::interpretSrtHandshake(). - memcpy(target, begin + 1, bytelen); + memcpy((target), begin + 1, bytelen); // Un-swap on big endian machines - ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); - - // Nothing more expected from connection block. - break; + ItoHLA(((uint32_t *)target), (uint32_t *)target, blocklen); } +#if ENABLE_BONDING + else if (cmd == SRT_CMD_GROUP) + { + uint32_t* groupdata = begin + 1; + have_group = true; // Even if parse error happes + if (bytelen / sizeof(int32_t) >= GRPD_E_SIZE) + { + uint32_t gd = groupdata[GRPD_GROUPDATA]; + gt = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); + } + } +#endif else if (cmd == SRT_CMD_NONE) { // End of blocks break; } - else - { - // Any other kind of message extracted. Search on. - length -= (next - begin); - begin = next; - if (begin) - continue; - } - break; + // Any other kind of message extracted. Search on. + if (!NextExtensionBlock((begin), next, (length))) + break; } } +#if ENABLE_BONDING + if (have_group && acore->m_config.iGroupConnect == 0) + { + HLOGC(cnlog.Debug, log << "runAcceptHook: REJECTING connection WITHOUT calling the hook - groups not allowed"); + return false; + } + + // Update the groupconnect flag + acore->m_config.iGroupConnect = have_group ? 1 : 0; + acore->m_HSGroupType = gt; +#endif + try { - int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs->m_iVersion, peer, target); + int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs.m_iVersion, peer, target); if (result == -1) return false; } catch (...) { - LOGP(mglog.Error, "runAcceptHook: hook interrupted by exception"); + LOGP(cnlog.Warn, "runAcceptHook: hook interrupted by exception"); return false; } return true; } + +void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) +{ + // Here can be handled some protocol definition + // for extra data sent through keepalive. + +#if ENABLE_BONDING + if (m_parent->m_GroupOf) + { + // Lock GlobControlLock in order to make sure that + // the state if the socket having the group and the + // existence of the group will not be changed during + // the operation. The attempt of group deletion will + // have to wait until this operation completes. + ScopedLock lock(uglobal().m_GlobControlLock); + CUDTGroup* pg = m_parent->m_GroupOf; + if (pg) + { + // Whether anything is to be done with this socket + // about the fact that keepalive arrived, let the + // group handle it + pg->processKeepalive(m_parent->m_GroupMemberData); + } + } +#endif + +#if ENABLE_NEW_RCVBUFFER + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); + if (m_config.bDriftTracer) + m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); +#endif +} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/core.h b/trunk/3rdparty/srt-1-fit/srtcore/core.h index 2b0a787413..fa58ca7c2e 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/core.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/core.h @@ -51,45 +51,34 @@ modified by *****************************************************************************/ -#ifndef __UDT_CORE_H__ -#define __UDT_CORE_H__ +#ifndef INC_SRT_CORE_H +#define INC_SRT_CORE_H #include #include - #include "srt.h" #include "common.h" #include "list.h" #include "buffer.h" +#include "buffer_rcv.h" #include "window.h" #include "packet.h" #include "channel.h" -#include "api.h" #include "cache.h" #include "queue.h" #include "handshake.h" #include "congctl.h" #include "packetfilter.h" +#include "socketconfig.h" #include "utilities.h" +#include "logger_defs.h" -#include - -namespace srt_logging -{ - -extern Logger - glog, -// blog, - mglog, - dlog, - tslog, - rxlog, - cclog; +#include "stats.h" -} +#include -// XXX Utility function - to be moved to utilities.h? +// TODO: Utility function - to be moved to utilities.h? template inline T CountIIR(T base, T newval, double factor) { @@ -100,35 +89,48 @@ inline T CountIIR(T base, T newval, double factor) return base+T(diff*factor); } -// XXX Probably a better rework for that can be done - this can be -// turned into a serializable structure, just like it's for CHandShake. +// TODO: Probably a better rework for that can be done - this can be +// turned into a serializable structure, just like it's done for CHandShake. enum AckDataItem { - ACKD_RCVLASTACK = 0, - ACKD_RTT = 1, - ACKD_RTTVAR = 2, - ACKD_BUFFERLEFT = 3, - ACKD_TOTAL_SIZE_SMALL = 4, - - // Extra fields existing in UDT (not always sent) - - ACKD_RCVSPEED = 4, // length would be 16 - ACKD_BANDWIDTH = 5, - ACKD_TOTAL_SIZE_UDTBASE = 6, // length = 24 - // Extra stats for SRT - - ACKD_RCVRATE = 6, - ACKD_TOTAL_SIZE_VER101 = 7, // length = 28 - ACKD_XMRATE = 7, // XXX This is a weird compat stuff. Version 1.1.3 defines it as ACKD_BANDWIDTH*m_iMaxSRTPayloadSize when set. Never got. - // XXX NOTE: field number 7 may be used for something in future, need to confirm destruction of all !compat 1.0.2 version - - ACKD_TOTAL_SIZE_VER102 = 8, // 32 -// FEATURE BLOCKED. Probably not to be restored. -// ACKD_ACKBITMAP = 8, - ACKD_TOTAL_SIZE = ACKD_TOTAL_SIZE_VER102 // length = 32 (or more) + ACKD_RCVLASTACK = 0, + ACKD_RTT = 1, + ACKD_RTTVAR = 2, + ACKD_BUFFERLEFT = 3, + ACKD_TOTAL_SIZE_SMALL = 4, // Size of the Small ACK, packet length = 16. + + // Extra fields for Full ACK. + ACKD_RCVSPEED = 4, + ACKD_BANDWIDTH = 5, + ACKD_TOTAL_SIZE_UDTBASE = 6, // Packet length = 24. + + // Extra stats since SRT v1.0.1. + ACKD_RCVRATE = 6, + ACKD_TOTAL_SIZE_VER101 = 7, // Packet length = 28. + + // Only in SRT v1.0.2. + ACKD_XMRATE_VER102_ONLY = 7, + ACKD_TOTAL_SIZE_VER102_ONLY = 8, // Packet length = 32. + + ACKD_TOTAL_SIZE = ACKD_TOTAL_SIZE_VER102_ONLY // The maximum known ACK length is 32 bytes. }; const size_t ACKD_FIELD_SIZE = sizeof(int32_t); +static const size_t SRT_SOCKOPT_NPOST = 12; +extern const SRT_SOCKOPT srt_post_opt_list []; + +enum GroupDataItem +{ + GRPD_GROUPID, + GRPD_GROUPDATA, + + GRPD_E_SIZE +}; + +const size_t GRPD_MIN_SIZE = 2; // ID and GROUPTYPE as backward compat + +const size_t GRPD_FIELD_SIZE = sizeof(int32_t); + // For HSv4 legacy handshake #define SRT_MAX_HSRETRY 10 /* Maximum SRT handshake retry */ @@ -137,9 +139,17 @@ enum SeqPairItems SEQ_BEGIN = 0, SEQ_END = 1, SEQ_SIZE = 2 }; + // Extended SRT Congestion control class - only an incomplete definition required class CCryptoControl; +namespace srt { +class CUDTUnited; +class CUDTSocket; +#if ENABLE_BONDING +class CUDTGroup; +#endif + // XXX REFACTOR: The 'CUDT' class is to be merged with 'CUDTSocket'. // There's no reason for separating them, there's no case of having them // anyhow managed separately. After this is done, with a small help with @@ -159,25 +169,42 @@ class CUDT friend class CSndUList; friend class CRcvUList; friend class PacketFilter; + friend class CUDTGroup; + friend class TestMockCUDT; // unit tests -private: // constructor and desctructor + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; + typedef sync::AtomicClock atomic_time_point; + typedef sync::AtomicDuration atomic_duration; +private: // constructor and desctructor void construct(); void clearData(); - CUDT(); - CUDT(const CUDT& ancestor); - const CUDT& operator=(const CUDT&) {return *this;} + CUDT(CUDTSocket* parent); + CUDT(CUDTSocket* parent, const CUDT& ancestor); + const CUDT& operator=(const CUDT&) {return *this;} // = delete ? ~CUDT(); public: //API static int startup(); static int cleanup(); - static SRTSOCKET socket(int af, int type = SOCK_STREAM, int protocol = 0); + static SRTSOCKET socket(); +#if ENABLE_BONDING + static SRTSOCKET createGroup(SRT_GROUP_TYPE); + static SRTSOCKET getGroupOfSocket(SRTSOCKET socket); + static int getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize); + static bool isgroup(SRTSOCKET sock) { return (sock & SRTGROUP_MASK) != 0; } +#endif static int bind(SRTSOCKET u, const sockaddr* name, int namelen); static int bind(SRTSOCKET u, UDPSOCKET udpsock); static int listen(SRTSOCKET u, int backlog); static SRTSOCKET accept(SRTSOCKET u, sockaddr* addr, int* addrlen); + static SRTSOCKET accept_bond(const SRTSOCKET listeners [], int lsize, int64_t msTimeOut); static int connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + static int connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen); +#if ENABLE_BONDING + static int connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG links [], int arraysize); +#endif static int close(SRTSOCKET u); static int getpeername(SRTSOCKET u, sockaddr* name, int* namelen); static int getsockname(SRTSOCKET u, sockaddr* name, int* namelen); @@ -185,56 +212,72 @@ class CUDT static int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); static int send(SRTSOCKET u, const char* buf, int len, int flags); static int recv(SRTSOCKET u, char* buf, int len, int flags); - static int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, uint64_t srctime = 0); - static int recvmsg(SRTSOCKET u, char* buf, int len, uint64_t& srctime); - static int sendmsg2(SRTSOCKET u, const char* buf, int len, ref_t mctrl); - static int recvmsg2(SRTSOCKET u, char* buf, int len, ref_t mctrl); + static int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl = SRT_MSGTTL_INF, bool inorder = false, int64_t srctime = 0); + static int recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime); + static int sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& mctrl); + static int recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_mctrl); static int64_t sendfile(SRTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = SRT_DEFAULT_SENDFILE_BLOCK); static int64_t recvfile(SRTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = SRT_DEFAULT_RECVFILE_BLOCK); - static int select(int nfds, ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout); + static int select(int nfds, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout); static int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); static int epoll_create(); + static int epoll_clear_usocks(int eid); static int epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); static int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); static int epoll_remove_usock(const int eid, const SRTSOCKET u); static int epoll_remove_ssock(const int eid, const SYSSOCKET s); static int epoll_update_usock(const int eid, const SRTSOCKET u, const int* events = NULL); static int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - static int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); + static int epoll_wait(const int eid, std::set* readfds, std::set* writefds, + int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); static int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); static int32_t epoll_set(const int eid, int32_t flags); static int epoll_release(const int eid); static CUDTException& getlasterror(); static int bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true, bool instantaneous = false); +#if ENABLE_BONDING + static int groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true); +#endif static SRT_SOCKSTATUS getsockstate(SRTSOCKET u); static bool setstreamid(SRTSOCKET u, const std::string& sid); static std::string getstreamid(SRTSOCKET u); static int getsndbuffer(SRTSOCKET u, size_t* blocks, size_t* bytes); - static SRT_REJECT_REASON rejectReason(SRTSOCKET s); + static int rejectReason(SRTSOCKET s); + static int rejectReason(SRTSOCKET s, int value); + static int64_t socketStartTime(SRTSOCKET s); - static int setError(const CUDTException& e) +public: // internal API + // This is public so that it can be used directly in API implementation functions. + struct APIError { - s_UDTUnited.setError(new CUDTException(e)); - return SRT_ERROR; - } + APIError(const CUDTException&); + APIError(CodeMajor, CodeMinor, int = 0); -public: // internal API - static const SRTSOCKET INVALID_SOCK = -1; // invalid socket descriptor - static const int ERROR = -1; // socket api error returned value + operator int() const + { + return SRT_ERROR; + } + }; + + static const SRTSOCKET INVALID_SOCK = -1; // Invalid socket descriptor + static const int ERROR = -1; // Socket api error returned value static const int HS_VERSION_UDT4 = 4; static const int HS_VERSION_SRT1 = 5; // Parameters // - // Note: use notation with X*1000*1000* ... instead of million zeros in a row. - // In C++17 there is a possible notation of 5'000'000 for convenience, but that's - // something only for a far future. - static const int COMM_RESPONSE_TIMEOUT_MS = 5*1000; // 5 seconds - static const int COMM_RESPONSE_MAX_EXP = 16; - static const int SRT_TLPKTDROP_MINTHRESHOLD_MS = 1000; - static const uint64_t COMM_KEEPALIVE_PERIOD_US = 1*1000*1000; - static const int32_t COMM_SYN_INTERVAL_US = 10*1000; + // NOTE: Use notation with X*1000*1000*... instead of + // million zeros in a row. + static const int COMM_RESPONSE_MAX_EXP = 16; + static const int SRT_TLPKTDROP_MINTHRESHOLD_MS = 1000; + static const uint64_t COMM_KEEPALIVE_PERIOD_US = 1*1000*1000; + static const int32_t COMM_SYN_INTERVAL_US = 10*1000; + static const int COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS = 3000; + static const uint16_t MAX_WEIGHT = 32767; + static const size_t ACK_WND_SIZE = 1024; + static const int INITIAL_RTT = 10 * COMM_SYN_INTERVAL_US; + static const int INITIAL_RTTVAR = INITIAL_RTT / 2; int handshakeVersion() { @@ -245,47 +288,157 @@ class CUDT { #if ENABLE_LOGGING std::ostringstream os; - os << "%" << m_SocketID << ":"; + os << "@" << m_SocketID << ":"; return os.str(); #else return ""; #endif } - SRTSOCKET socketID() { return m_SocketID; } + SRTSOCKET socketID() const { return m_SocketID; } - static CUDT* getUDTHandle(SRTSOCKET u); - static std::vector existingSockets(); + static CUDT* getUDTHandle(SRTSOCKET u); + static std::vector existingSockets(); void addressAndSend(CPacket& pkt); - void sendSrtMsg(int cmd, uint32_t *srtdata_in = NULL, int srtlen_in = 0); - - bool isTsbPd() { return m_bOPT_TsbPd; } - int RTT() { return m_iRTT; } - int32_t sndSeqNo() { return m_iSndCurrSeqNo; } - int32_t rcvSeqNo() { return m_iRcvCurrSeqNo; } - int flowWindowSize() { return m_iFlowWindowSize; } - int32_t deliveryRate() { return m_iDeliveryRate; } - int bandwidth() { return m_iBandwidth; } - int64_t maxBandwidth() { return m_llMaxBW; } - int MSS() { return m_iMSS; } - size_t maxPayloadSize() { return m_iMaxSRTPayloadSize; } - size_t OPT_PayloadSize() { return m_zOPT_ExpPayloadSize; } - uint64_t minNAKInterval() { return m_ullMinNakInt_tk; } - int32_t ISN() { return m_iISN; } - int sndLossLength() { return m_pSndLossList->getLossLength(); } - - // XXX See CUDT::tsbpd() to see how to implement it. This should - // do the same as TLPKTDROP feature when skipping packets that are agreed - // to be lost. Note that this is predicted to be called with TSBPD off. - // This is to be exposed for the application so that it can require this - // sequence to be skipped, if that packet has been otherwise arrived through - // a different channel. - void skipIncoming(int32_t seq); + + SRT_ATTR_REQUIRES(m_ConnectionLock) + void sendSrtMsg(int cmd, uint32_t *srtdata_in = NULL, size_t srtlen_in = 0); + + bool isOPT_TsbPd() const { return m_config.bTSBPD; } + int SRTT() const { return m_iSRTT; } + int RTTVar() const { return m_iRTTVar; } + int32_t sndSeqNo() const { return m_iSndCurrSeqNo; } + int32_t schedSeqNo() const { return m_iSndNextSeqNo; } + bool overrideSndSeqNo(int32_t seq); + + sync::steady_clock::time_point lastRspTime() const { return m_tsLastRspTime.load(); } + sync::steady_clock::time_point freshActivationStart() const { return m_tsFreshActivation; } + + int32_t rcvSeqNo() const { return m_iRcvCurrSeqNo; } + int flowWindowSize() const { return m_iFlowWindowSize; } + int32_t deliveryRate() const { return m_iDeliveryRate; } + int bandwidth() const { return m_iBandwidth; } + int64_t maxBandwidth() const { return m_config.llMaxBW; } + int MSS() const { return m_config.iMSS; } + + uint32_t peerLatency_us() const { return m_iPeerTsbPdDelay_ms * 1000; } + int peerIdleTimeout_ms() const { return m_config.iPeerIdleTimeout_ms; } + size_t maxPayloadSize() const { return m_iMaxSRTPayloadSize; } + size_t OPT_PayloadSize() const { return m_config.zExpPayloadSize; } + int sndLossLength() { return m_pSndLossList->getLossLength(); } + int32_t ISN() const { return m_iISN; } + int32_t peerISN() const { return m_iPeerISN; } + duration minNAKInterval() const { return m_tdMinNakInterval; } + sockaddr_any peerAddr() const { return m_PeerAddr; } + + /// Returns the number of packets in flight (sent, but not yet acknowledged). + /// @param lastack is the sequence number of the first unacknowledged packet. + /// @param curseq is the sequence number of the latest original packet sent + /// + /// @note When there are no packets in flight, lastack = incseq(curseq). + /// + /// @returns The number of packets in flight belonging to the interval [0; ...) + static int32_t getFlightSpan(int32_t lastack, int32_t curseq) + { + // Packets sent: + // | 1 | 2 | 3 | 4 | 5 | + // ^ ^ + // | | + // lastack | + // curseq + // + // In Flight: [lastack; curseq] + // + // Normally 'lastack' should be PAST the 'curseq', + // however in a case when the sending stopped and all packets were + // ACKed, the 'lastack' is one sequence ahead of 'curseq'. + // Therefore we increase 'curseq' by 1 forward and then + // get the distance towards the last ACK. This way this value may + // be only positive as seqlen() includes endpoints. + // Finally, we subtract 1 to exclude the increment added earlier. + + return CSeqNo::seqlen(lastack, CSeqNo::incseq(curseq)) - 1; + } + + /// Returns the number of packets in flight (sent, but not yet acknowledged). + /// @returns The number of packets in flight belonging to the interval [0; ...) + int32_t getFlightSpan() const + { + return getFlightSpan(m_iSndLastAck, m_iSndCurrSeqNo); + } + + int minSndSize(int len = 0) const + { + const int ps = (int) maxPayloadSize(); + if (len == 0) // wierd, can't use non-static data member as default argument! + len = ps; + return m_config.bMessageAPI ? (len+ps-1)/ps : 1; + } + + int32_t makeTS(const time_point& from_time) const + { + // NOTE: + // - This calculates first the time difference towards start time. + // - This difference value is also CUT OFF THE SEGMENT information + // (a multiple of MAX_TIMESTAMP+1) + // So, this can be simply defined as: TS = (RTS - STS) % (MAX_TIMESTAMP+1) + // XXX Would be nice to check if local_time > m_tsStartTime, + // otherwise it may go unnoticed with clock skew. + return (int32_t) sync::count_microseconds(from_time - m_stats.tsStartTime); + } + + void setPacketTS(CPacket& p, const time_point& local_time) + { + p.m_iTimeStamp = makeTS(local_time); + } + + // Utility used for closing a listening socket + // immediately to free the socket + void notListening() + { + sync::ScopedLock cg(m_ConnectionLock); + m_bListening = false; + m_pRcvQueue->removeListener(this); + } + + static int32_t generateISN() + { + using namespace sync; + return genRandomInt(0, CSeqNo::m_iMaxSeqNo); + } + + static CUDTUnited& uglobal(); // UDT global management base + + std::set& pollset() { return m_sPollID; } + + CSrtConfig m_config; + + SRTU_PROPERTY_RO(SRTSOCKET, id, m_SocketID); + SRTU_PROPERTY_RO(bool, isClosing, m_bClosing); +#if ENABLE_NEW_RCVBUFFER + SRTU_PROPERTY_RO(srt::CRcvBufferNew*, rcvBuffer, m_pRcvBuffer); +#else + SRTU_PROPERTY_RO(CRcvBuffer*, rcvBuffer, m_pRcvBuffer); +#endif + SRTU_PROPERTY_RO(bool, isTLPktDrop, m_bTLPktDrop); + SRTU_PROPERTY_RO(bool, isSynReceiving, m_config.bSynRecving); + SRTU_PROPERTY_RR(sync::Condition*, recvDataCond, &m_RecvDataCond); + SRTU_PROPERTY_RR(sync::Condition*, recvTsbPdCond, &m_RcvTsbPdCond); + + /// @brief Request a socket to be broken due to too long instability (normally by a group). + void breakAsUnstable() { m_bBreakAsUnstable = true; } void ConnectSignal(ETransmissionEvent tev, EventSlot sl); void DisconnectSignal(ETransmissionEvent tev); + // This is in public section so prospective overriding it can be + // done by directly assigning to a field. + + typedef std::vector< std::pair > loss_seqs_t; + typedef loss_seqs_t packetArrival_cb(void*, CPacket&); + CallbackHolder m_cbPacketArrival; + private: /// initialize a UDT entity and bind to a local address. @@ -298,7 +451,7 @@ class CUDT /// Connect to a UDT entity listening at address "peer". /// @param peer [in] The address of the listening UDT entity. - void startConnect(const sockaddr* peer, int32_t forced_isn); + void startConnect(const sockaddr_any& peer, int32_t forced_isn); /// Process the response handshake packet. Failure reasons can be: /// * Socket is not in connecting state @@ -309,8 +462,8 @@ class CUDT /// @retval 1 Connection in progress (m_ConnReq turned into RESPONSE) /// @retval -1 Connection failed - SRT_ATR_NODISCARD EConnectStatus processConnectResponse(const CPacket& pkt, CUDTException* eout, bool synchro) ATR_NOEXCEPT; - + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + EConnectStatus processConnectResponse(const CPacket& pkt, CUDTException* eout) ATR_NOEXCEPT; // This function works in case of HSv5 rendezvous. It changes the state // according to the present state and received message type, as well as the @@ -319,7 +472,7 @@ class CUDT // - rsptype: handshake message type that should be sent back to the peer (nothing if URQ_DONE) // - needs_extension: the HSREQ/KMREQ or HSRSP/KMRSP extensions should be attached to the handshake message. // - RETURNED VALUE: if true, it means a URQ_CONCLUSION message was received with HSRSP/KMRSP extensions and needs HSRSP/KMRSP. - void rendezvousSwitchState(ref_t rsptype, ref_t needs_extension, ref_t needs_hsrsp); + void rendezvousSwitchState(UDTRequestType& rsptype, bool& needs_extension, bool& needs_hsrsp); void cookieContest(); /// Interpret the incoming handshake packet in order to perform appropriate @@ -328,14 +481,21 @@ class CUDT /// @param reqpkt Packet to be written with handshake data /// @param response incoming handshake response packet to be interpreted /// @param serv_addr incoming packet's address - /// @param synchro True when this function was called in blocking mode /// @param rst Current read status to know if the HS packet was freshly received from the peer, or this is only a periodic update (RST_AGAIN) - SRT_ATR_NODISCARD EConnectStatus processRendezvous(ref_t reqpkt, const CPacket &response, const sockaddr* serv_addr, bool synchro, EReadStatus); - SRT_ATR_NODISCARD bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout); - SRT_ATR_NODISCARD EConnectStatus postConnect(const CPacket& response, bool rendezvous, CUDTException* eout, bool synchro); - void applyResponseSettings(); + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + EConnectStatus processRendezvous(const CPacket* response, const sockaddr_any& serv_addr, EReadStatus, CPacket& reqpkt); + + /// Create the CryptoControl object based on the HS packet. Allocates sender and receiver buffers and loss lists. + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout); + + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + EConnectStatus postConnect(const CPacket* response, bool rendezvous, CUDTException* eout) ATR_NOEXCEPT; + + SRT_ATR_NODISCARD bool applyResponseSettings() ATR_NOEXCEPT; SRT_ATR_NODISCARD EConnectStatus processAsyncConnectResponse(const CPacket& pkt) ATR_NOEXCEPT; - SRT_ATR_NODISCARD bool processAsyncConnectRequest(EReadStatus rst, EConnectStatus cst, const CPacket& response, const sockaddr* serv_addr); + SRT_ATR_NODISCARD bool processAsyncConnectRequest(EReadStatus rst, EConnectStatus cst, const CPacket* response, const sockaddr_any& serv_addr); + SRT_ATR_NODISCARD EConnectStatus craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatasize); void checkUpdateCryptoKeyLen(const char* loghdr, int32_t typefield); @@ -343,34 +503,73 @@ class CUDT SRT_ATR_NODISCARD size_t fillSrtHandshake_HSRSP(uint32_t* srtdata, size_t srtlen, int hs_version); SRT_ATR_NODISCARD size_t fillSrtHandshake(uint32_t* srtdata, size_t srtlen, int msgtype, int hs_version); - SRT_ATR_NODISCARD bool createSrtHandshake(ref_t reqpkt, ref_t hs, - int srths_cmd, int srtkm_cmd, const uint32_t* data, size_t datalen); + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + bool createSrtHandshake(int srths_cmd, int srtkm_cmd, const uint32_t* data, size_t datalen, + CPacket& w_reqpkt, CHandShake& w_hs); + + SRT_ATR_NODISCARD size_t fillHsExtConfigString(uint32_t *pcmdspec, int cmd, const std::string &str); +#if ENABLE_BONDING + SRT_ATR_NODISCARD size_t fillHsExtGroup(uint32_t *pcmdspec); +#endif + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + size_t fillHsExtKMREQ(uint32_t *pcmdspec, size_t ki); + + SRT_ATR_NODISCARD size_t fillHsExtKMRSP(uint32_t *pcmdspec, const uint32_t *kmdata, size_t kmdata_wordsize); SRT_ATR_NODISCARD size_t prepareSrtHsMsg(int cmd, uint32_t* srtdata, size_t size); SRT_ATR_NODISCARD bool processSrtMsg(const CPacket *ctrlpkt); - SRT_ATR_NODISCARD int processSrtMsg_HSREQ(const uint32_t* srtdata, size_t len, uint32_t ts, int hsv); - SRT_ATR_NODISCARD int processSrtMsg_HSRSP(const uint32_t* srtdata, size_t len, uint32_t ts, int hsv); + SRT_ATR_NODISCARD int processSrtMsg_HSREQ(const uint32_t* srtdata, size_t bytelen, uint32_t ts, int hsv); + SRT_ATR_NODISCARD int processSrtMsg_HSRSP(const uint32_t* srtdata, size_t bytelen, uint32_t ts, int hsv); SRT_ATR_NODISCARD bool interpretSrtHandshake(const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data, size_t* out_len); SRT_ATR_NODISCARD bool checkApplyFilterConfig(const std::string& cs); - void updateAfterSrtHandshake(int srt_cmd, int hsv); +#if ENABLE_BONDING + static CUDTGroup& newGroup(const int); // defined EXCEPTIONALLY in api.cpp for convenience reasons + // Note: This is an "interpret" function, which should treat the tp as + // "possibly group type" that might be out of the existing values. + SRT_ATR_NODISCARD bool interpretGroup(const int32_t grpdata[], size_t data_size, int hsreq_type_cmd); + SRT_ATR_NODISCARD SRTSOCKET makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE tp, uint32_t link_flags); + void synchronizeWithGroup(CUDTGroup* grp); +#endif + + void updateAfterSrtHandshake(int hsv); void updateSrtRcvSettings(); void updateSrtSndSettings(); - void checkNeedDrop(ref_t bCongestion); + void updateIdleLinkFrom(CUDT* source); + + /// @brief Drop packets too late to be delivered if any. + /// @returns the number of packets actually dropped. + SRT_ATTR_REQUIRES2(m_RecvAckLock, m_StatsLock) + int sndDropTooLate(); - /// Connect to a UDT entity listening at address "peer", which has sent "hs" request. + /// @bried Allow packet retransmission. + /// Depending on the configuration mode (live / file), retransmission + /// can be blocked if e.g. there are original packets pending to be sent. + /// @return true if retransmission is allowed; false otherwise. + bool isRetransmissionAllowed(const time_point& tnow); + + /// Connect to a UDT entity as per hs request. This will update + /// required data in the entity, then update them also in the hs structure, + /// and then send the response back to the caller. + /// @param agent [in] The address to which the UDT entity is bound. /// @param peer [in] The address of the listening UDT entity. + /// @param hspkt [in] The original packet that brought the handshake. /// @param hs [in/out] The handshake information sent by the peer side (in), negotiated value (out). + void acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& hs); - void acceptAndRespond(const sockaddr* peer, CHandShake* hs, const CPacket& hspkt); - bool runAcceptHook(CUDT* acore, const sockaddr* peer, const CHandShake* hs, const CPacket& hspkt); + /// Write back to the hs structure the data after they have been + /// negotiated by acceptAndRespond. + void rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs); + bool runAcceptHook(CUDT* acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt); /// Close the opened UDT entity. - bool close(); + bool closeInternal(); + void updateBrokenConnection(); + void completeBrokenConnectionDependencies(int errorcode); /// Request UDT to send out a data block "data" with size of "len". /// @param data [in] The address of the application data to be sent. @@ -379,7 +578,7 @@ class CUDT SRT_ATR_NODISCARD int send(const char* data, int len) { - return sendmsg(data, len, -1, false, 0); + return sendmsg(data, len, SRT_MSGTTL_INF, false, 0); } /// Request UDT to receive data to a memory block "data" with size of "len". @@ -397,21 +596,21 @@ class CUDT /// @param srctime [in] Time when the data were ready to send. /// @return Actual size of data sent. - SRT_ATR_NODISCARD int sendmsg(const char* data, int len, int ttl, bool inorder, uint64_t srctime); + SRT_ATR_NODISCARD int sendmsg(const char* data, int len, int ttl, bool inorder, int64_t srctime); /// Receive a message to buffer "data". /// @param data [out] data received. /// @param len [in] size of the buffer. /// @return Actual size of data received. - SRT_ATR_NODISCARD int sendmsg2(const char* data, int len, ref_t m); - - SRT_ATR_NODISCARD int recvmsg(char* data, int len, uint64_t& srctime); + SRT_ATR_NODISCARD int sendmsg2(const char* data, int len, SRT_MSGCTRL& w_m); - SRT_ATR_NODISCARD int recvmsg2(char* data, int len, ref_t m); - - SRT_ATR_NODISCARD int receiveMessage(char* data, int len, ref_t m); + SRT_ATR_NODISCARD int recvmsg(char* data, int len, int64_t& srctime); + SRT_ATR_NODISCARD int recvmsg2(char* data, int len, SRT_MSGCTRL& w_m); + SRT_ATR_NODISCARD int receiveMessage(char* data, int len, SRT_MSGCTRL& w_m, int erh = 1 /*throw exception*/); SRT_ATR_NODISCARD int receiveBuffer(char* data, int len); + size_t dropMessage(int32_t seqtoskip); + /// Request UDT to send out a file described as "fd", starting from "offset", with size of "size". /// @param ifs [in] The input file stream. /// @param offset [in, out] From where to read and send data; output is the new offset when the call returns. @@ -442,7 +641,13 @@ class CUDT /// @param optval [in] The value to be returned. /// @param optlen [out] size of "optval". - void getOpt(SRT_SOCKOPT optName, void* optval, int& optlen); + void getOpt(SRT_SOCKOPT optName, void* optval, int& w_optlen); + +#if ENABLE_BONDING + /// Applies the configuration set on the socket. + /// Any errors in this process are reported by exception. + SRT_ERRNO applyMemberConfigObject(const SRT_SocketOptionObject& opt); +#endif /// read the performance data with bytes counters since bstats() /// @@ -458,7 +663,6 @@ class CUDT void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); - void considerLegacySrtHandshake(uint64_t timebase); void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); void handshakeDone() { @@ -467,7 +671,7 @@ class CUDT int64_t withOverhead(int64_t basebw) { - return (basebw * (100 + m_iOverheadBW))/100; + return (basebw * (100 + m_config.iOverheadBW))/100; } static double Bps2Mbps(int64_t basebw) @@ -488,247 +692,346 @@ class CUDT int sndSpaceLeft() { - return sndBuffersLeft() * m_iMaxSRTPayloadSize; + return static_cast(sndBuffersLeft() * maxPayloadSize()); } int sndBuffersLeft() { - return m_iSndBufSize - m_pSndBuffer->getCurrBufSize(); + return m_config.iSndBufSize - m_pSndBuffer->getCurrBufSize(); + } + + time_point socketStartTime() + { + return m_stats.tsStartTime; } + SRT_ATTR_EXCLUDES(m_RcvBufferLock) + bool isRcvBufferReady() const; // TSBPD thread main function. static void* tsbpd(void* param); - static CUDTUnited s_UDTUnited; // UDT global management base - -private: // Identification - SRTSOCKET m_SocketID; // UDT socket number - - // XXX Deprecated field. In any place where it's used, UDT_DGRAM is - // the only allowed value. The functionality of distinguishing the transmission - // method is now in m_CongCtl. - UDTSockType m_iSockType; // Type of the UDT connection (SOCK_STREAM or SOCK_DGRAM) - SRTSOCKET m_PeerID; // peer id, for multiplexer - - int m_iMaxSRTPayloadSize; // Maximum/regular payload size, in bytes - size_t m_zOPT_ExpPayloadSize; // Expected average payload size (user option) - - // Options - int m_iMSS; // Maximum Segment Size, in bytes - bool m_bSynSending; // Sending syncronization mode - bool m_bSynRecving; // Receiving syncronization mode - int m_iFlightFlagSize; // Maximum number of packets in flight from the peer side - int m_iSndBufSize; // Maximum UDT sender buffer size - int m_iRcvBufSize; // Maximum UDT receiver buffer size - linger m_Linger; // Linger information on close - int m_iUDPSndBufSize; // UDP sending buffer size - int m_iUDPRcvBufSize; // UDP receiving buffer size - int m_iIPversion; // IP version - bool m_bRendezvous; // Rendezvous connection mode -#ifdef SRT_ENABLE_CONNTIMEO - int m_iConnTimeOut; // connect timeout in milliseconds +#if ENABLE_NEW_RCVBUFFER + /// Drop too late packets (receiver side). Updaet loss lists and ACK positions. + /// The @a seqno packet itself is not dropped. + /// @param seqno [in] The sequence number of the first packets following those to be dropped. + /// @return The number of packets dropped. + int rcvDropTooLateUpTo(int seqno); #endif - int m_iSndTimeOut; // sending timeout in milliseconds - int m_iRcvTimeOut; // receiving timeout in milliseconds - bool m_bReuseAddr; // reuse an exiting port or not, for UDP multiplexer - int64_t m_llMaxBW; // maximum data transfer rate (threshold) -#ifdef SRT_ENABLE_IPOPTS - int m_iIpTTL; - int m_iIpToS; -#endif - // These fields keep the options for encryption - // (SRTO_PASSPHRASE, SRTO_PBKEYLEN). Crypto object is - // created later and takes values from these. - HaiCrypt_Secret m_CryptoSecret; - int m_iSndCryptoKeyLen; - // XXX Consider removing. The m_bDataSender stays here - // in order to maintain the HS side selection in HSv4. - bool m_bDataSender; + void updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno); + + static loss_seqs_t defaultPacketArrival(void* vself, CPacket& pkt); + static loss_seqs_t groupPacketArrival(void* vself, CPacket& pkt); + + CRateEstimator getRateEstimator() const + { + if (!m_pSndBuffer) + return CRateEstimator(); + return m_pSndBuffer->getRateEstimator(); + } + + void setRateEstimator(const CRateEstimator& rate) + { + if (!m_pSndBuffer) + return; + + m_pSndBuffer->setRateEstimator(rate); + updateCC(TEV_SYNC, EventVariant(0)); + } + + +private: // Identification + CUDTSocket* const m_parent; // Temporary, until the CUDTSocket class is merged with CUDT + SRTSOCKET m_SocketID; // UDT socket number + SRTSOCKET m_PeerID; // Peer ID, for multiplexer // HSv4 (legacy handshake) support) - uint64_t m_ullSndHsLastTime_us; //Last SRT handshake request time - int m_iSndHsRetryCnt; //SRT handshake retries left - - bool m_bMessageAPI; - bool m_bOPT_TsbPd; // Whether AGENT will do TSBPD Rx (whether peer does, is not agent's problem) - int m_iOPT_TsbPdDelay; // Agent's Rx latency - int m_iOPT_PeerTsbPdDelay; // Peer's Rx latency for the traffic made by Agent's Tx. - bool m_bOPT_TLPktDrop; // Whether Agent WILL DO TLPKTDROP on Rx. - int m_iOPT_SndDropDelay; // Extra delay when deciding to snd-drop for TLPKTDROP, -1 to off - bool m_bOPT_StrictEncryption; // Off by default. When on, any connection other than nopw-nopw & pw1-pw1 is rejected. - std::string m_sStreamName; - int m_iOPT_PeerIdleTimeout; // Timeout for hearing anything from the peer. - - int m_iTsbPdDelay_ms; // Rx delay to absorb burst in milliseconds - int m_iPeerTsbPdDelay_ms; // Tx delay that the peer uses to absorb burst in milliseconds - bool m_bTLPktDrop; // Enable Too-late Packet Drop - int64_t m_llInputBW; // Input stream rate (bytes/sec) - int m_iOverheadBW; // Percent above input stream rate (applies if m_llMaxBW == 0) - bool m_bRcvNakReport; // Enable Receiver Periodic NAK Reports - int m_iIpV6Only; // IPV6_V6ONLY option (-1 if not set) + time_point m_tsSndHsLastTime; // Last SRT handshake request time + int m_iSndHsRetryCnt; // SRT handshake retries left + +#if ENABLE_BONDING + SRT_GROUP_TYPE m_HSGroupType; // Group type about-to-be-set in the handshake +#endif + private: - UniquePtr m_pCryptoControl; // congestion control SRT class (small data extension) - CCache* m_pCache; // network information cache + int m_iMaxSRTPayloadSize; // Maximum/regular payload size, in bytes + int m_iTsbPdDelay_ms; // Rx delay to absorb burst, in milliseconds + int m_iPeerTsbPdDelay_ms; // Tx delay that the peer uses to absorb burst, in milliseconds + bool m_bTLPktDrop; // Enable Too-late Packet Drop + SRT_ATTR_PT_GUARDED_BY(m_ConnectionLock) + UniquePtr m_pCryptoControl; // Crypto control module + CCache* m_pCache; // Network information cache // Congestion control - std::vector m_Slots[TEV__SIZE]; - SrtCongestion m_CongCtl; + std::vector m_Slots[TEV_E_SIZE]; + SrtCongestion m_CongCtl; // Packet filtering PacketFilter m_PacketFilter; - std::string m_OPT_PktFilterConfigString; SRT_ARQLevel m_PktFilterRexmitLevel; - std::string m_sPeerPktFilterConfigString; + std::string m_sPeerPktFilterConfigString; // Attached tool function void EmitSignal(ETransmissionEvent tev, EventVariant var); // Internal state - volatile bool m_bListening; // If the UDT entit is listening to connection - volatile bool m_bConnecting; // The short phase when connect() is called but not yet completed - volatile bool m_bConnected; // Whether the connection is on or off - volatile bool m_bClosing; // If the UDT entity is closing - volatile bool m_bShutdown; // If the peer side has shutdown the connection - volatile bool m_bBroken; // If the connection has been broken - volatile bool m_bPeerHealth; // If the peer status is normal - volatile SRT_REJECT_REASON m_RejectReason; + sync::atomic m_bListening; // If the UDT entity is listening to connection + sync::atomic m_bConnecting; // The short phase when connect() is called but not yet completed + sync::atomic m_bConnected; // Whether the connection is on or off + sync::atomic m_bClosing; // If the UDT entity is closing + sync::atomic m_bShutdown; // If the peer side has shutdown the connection + sync::atomic m_bBroken; // If the connection has been broken + sync::atomic m_bBreakAsUnstable; // A flag indicating that the socket should become broken because it has been unstable for too long. + sync::atomic m_bPeerHealth; // If the peer status is normal + sync::atomic m_RejectReason; bool m_bOpened; // If the UDT entity has been opened - int m_iBrokenCounter; // a counter (number of GC checks) to let the GC tag this socket as disconnected + // A counter (number of GC checks happening every 1s) to let the GC tag this socket as closed. + sync::atomic m_iBrokenCounter; // If a broken socket still has data in the receiver buffer, it is not marked closed until the counter is 0. int m_iEXPCount; // Expiration counter - int m_iBandwidth; // Estimated bandwidth, number of packets per second - int m_iRTT; // RTT, in microseconds - int m_iRTTVar; // RTT variance - int m_iDeliveryRate; // Packet arrival rate at the receiver side - int m_iByteDeliveryRate; // Byte arrival rate at the receiver side - - uint64_t m_ullLingerExpiration; // Linger expiration time (for GC to close a socket with data in sending buffer) - - CHandShake m_ConnReq; // connection request - CHandShake m_ConnRes; // connection response + sync::atomic m_iBandwidth; // Estimated bandwidth, number of packets per second + sync::atomic m_iSRTT; // Smoothed RTT (an exponentially-weighted moving average (EWMA) + // of an endpoint's RTT samples), in microseconds + sync::atomic m_iRTTVar; // The variation in the RTT samples (RTT variance), in microseconds + sync::atomic m_bIsFirstRTTReceived; // True if the first RTT sample was obtained from the ACK/ACKACK pair + // at the receiver side or received by the sender from an ACK packet. + // It's used to reset the initial value of smoothed RTT (m_iSRTT) + // at the beginning of transmission (including the one taken from + // cache). False by default. + sync::atomic m_iDeliveryRate; // Packet arrival rate at the receiver side + sync::atomic m_iByteDeliveryRate; // Byte arrival rate at the receiver side + + CHandShake m_ConnReq; // Connection request + CHandShake m_ConnRes; // Connection response CHandShake::RendezvousState m_RdvState; // HSv5 rendezvous state HandshakeSide m_SrtHsSide; // HSv5 rendezvous handshake side resolved from cookie contest (DRAW if not yet resolved) - int64_t m_llLastReqTime; // last time when a connection request is sent private: // Sending related data CSndBuffer* m_pSndBuffer; // Sender buffer CSndLossList* m_pSndLossList; // Sender loss list - CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window + CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window + + atomic_duration m_tdSendInterval; // Inter-packet time, in CPU clock cycles - volatile uint64_t m_ullInterval_tk; // Inter-packet time, in CPU clock cycles - uint64_t m_ullTimeDiff_tk; // aggregate difference in inter-packet time + atomic_duration m_tdSendTimeDiff; // Aggregate difference in inter-packet sending time - volatile int m_iFlowWindowSize; // Flow control window size - volatile double m_dCongestionWindow; // congestion window size + SRT_ATTR_GUARDED_BY(m_RecvAckLock) + sync::atomic m_iFlowWindowSize; // Flow control window size + double m_dCongestionWindow; // Congestion window size + +private: // Timers + atomic_time_point m_tsNextACKTime; // Next ACK time, in CPU clock cycles, same below + atomic_time_point m_tsNextNAKTime; // Next NAK time + + duration m_tdACKInterval; // ACK interval + duration m_tdNAKInterval; // NAK interval + SRT_ATTR_GUARDED_BY(m_RecvAckLock) + atomic_time_point m_tsLastRspTime; // Timestamp of last response from the peer + time_point m_tsLastRspAckTime; // (SND) Timestamp of last ACK from the peer + atomic_time_point m_tsLastSndTime; // Timestamp of last data/ctrl sent (in system ticks) + time_point m_tsLastWarningTime; // Last time that a warning message is sent + atomic_time_point m_tsLastReqTime; // last time when a connection request is sent + time_point m_tsRcvPeerStartTime; + time_point m_tsLingerExpiration; // Linger expiration time (for GC to close a socket with data in sending buffer) + time_point m_tsLastAckTime; // (RCV) Timestamp of last ACK + duration m_tdMinNakInterval; // NAK timeout lower bound; too small value can cause unnecessary retransmission + duration m_tdMinExpInterval; // Timeout lower bound threshold: too small timeout can cause problem + + int m_iPktCount; // Packet counter for ACK + int m_iLightACKCount; // Light ACK counter + + time_point m_tsNextSendTime; // Scheduled time of next packet sending + + sync::atomic m_iSndLastFullAck; // Last full ACK received + SRT_ATTR_GUARDED_BY(m_RecvAckLock) + sync::atomic m_iSndLastAck; // Last ACK received + + // NOTE: m_iSndLastDataAck is the value strictly bound to the CSndBufer object (m_pSndBuffer) + // and this is the sequence number that refers to the block at position [0]. Upon acknowledgement, + // this value is shifted to the acknowledged position, and the blocks are removed from the + // m_pSndBuffer buffer up to excluding this sequence number. + // XXX CONSIDER removing this field and give up the maintenance of this sequence number + // to the sending buffer. This way, extraction of an old packet for retransmission should + // require only the lost sequence number, and how to find the packet with this sequence + // will be up to the sending buffer. + sync::atomic m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list + sync::atomic m_iSndCurrSeqNo; // The largest sequence number that HAS BEEN SENT + sync::atomic m_iSndNextSeqNo; // The sequence number predicted to be placed at the currently scheduled packet + + // Note important differences between Curr and Next fields: + // - m_iSndCurrSeqNo: this is used by SRT:SndQ:worker thread and it's operated from CUDT::packData + // function only. This value represents the sequence number that has been stamped on a packet directly + // before it is sent over the network. + // - m_iSndNextSeqNo: this is used by the user's thread and it's operated from CUDT::sendmsg2 + // function only. This value represents the sequence number that is PREDICTED to be stamped on the + // first block out of the block series that will be scheduled for later sending over the network + // out of the data passed in this function. For a special case when the length of the data is + // short enough to be passed in one UDP packet (always the case for live mode), this value is + // always increased by one in this call, otherwise it will be increased by the number of blocks + // scheduled for sending. - volatile int32_t m_iSndLastFullAck; // Last full ACK received - volatile int32_t m_iSndLastAck; // Last ACK received - volatile int32_t m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list - volatile int32_t m_iSndCurrSeqNo; // The largest sequence number that has been sent - int32_t m_iLastDecSeq; // Sequence number sent last decrease occurs int32_t m_iSndLastAck2; // Last ACK2 sent back - uint64_t m_ullSndLastAck2Time; // The time when last ACK2 was sent back + time_point m_SndLastAck2Time; // The time when last ACK2 was sent back + void setInitialSndSeq(int32_t isn) + { + m_iSndLastAck = isn; + m_iSndLastDataAck = isn; + m_iSndLastFullAck = isn; + m_iSndCurrSeqNo = CSeqNo::decseq(isn); + m_iSndNextSeqNo = isn; + m_iSndLastAck2 = isn; + } + + void setInitialRcvSeq(int32_t isn); + int32_t m_iISN; // Initial Sequence Number bool m_bPeerTsbPd; // Peer accept TimeStamp-Based Rx mode bool m_bPeerTLPktDrop; // Enable sender late packet dropping bool m_bPeerNakReport; // Sender's peer (receiver) issues Periodic NAK Reports bool m_bPeerRexmitFlag; // Receiver supports rexmit flag in payload packets + + SRT_ATTR_GUARDED_BY(m_RecvAckLock) int32_t m_iReXmitCount; // Re-Transmit Count since last ACK private: // Receiving related data +#if ENABLE_NEW_RCVBUFFER + CRcvBufferNew* m_pRcvBuffer; //< Receiver buffer +#else CRcvBuffer* m_pRcvBuffer; //< Receiver buffer +#endif CRcvLossList* m_pRcvLossList; //< Receiver loss list std::deque m_FreshLoss; //< Lost sequence already added to m_pRcvLossList, but not yet sent UMSG_LOSSREPORT for. int m_iReorderTolerance; //< Current value of dynamic reorder tolerance - int m_iMaxReorderTolerance; //< Maximum allowed value for dynamic reorder tolerance int m_iConsecEarlyDelivery; //< Increases with every OOO packet that came m_ACKWindow; //< ACK history window - CPktTimeWindow<16, 64> m_RcvTimeWindow; //< Packet arrival time window + CACKWindow m_ACKWindow; // ACK history window + CPktTimeWindow<16, 64> m_RcvTimeWindow; // Packet arrival time window - int32_t m_iRcvLastAck; //< Last sent ACK + int32_t m_iRcvLastAck; // First unacknowledged packet seqno sent in the latest ACK. #ifdef ENABLE_LOGGING int32_t m_iDebugPrevLastAck; #endif int32_t m_iRcvLastSkipAck; // Last dropped sequence ACK - uint64_t m_ullLastAckTime_tk; // Timestamp of last ACK - int32_t m_iRcvLastAckAck; // Last sent ACK that has been acknowledged + int32_t m_iRcvLastAckAck; // (RCV) Latest packet seqno in a sent ACK acknowledged by ACKACK. RcvQTh (sendCtrlAck {r}, processCtrlAckAck {r}, processCtrlAck {r}, connection {w}). int32_t m_iAckSeqNo; // Last ACK sequence number - int32_t m_iRcvCurrSeqNo; // Largest received sequence number + sync::atomic m_iRcvCurrSeqNo; // (RCV) Largest received sequence number. RcvQTh, TSBPDTh. int32_t m_iRcvCurrPhySeqNo; // Same as m_iRcvCurrSeqNo, but physical only (disregarding a filter) - uint64_t m_ullLastWarningTime; // Last time that a warning message is sent - int32_t m_iPeerISN; // Initial Sequence Number of the peer side - uint64_t m_ullRcvPeerStartTime; - uint32_t m_lSrtVersion; - uint32_t m_lMinimumPeerSrtVersion; - uint32_t m_lPeerSrtVersion; - uint32_t m_lPeerSrtFlags; + uint32_t m_uPeerSrtVersion; + uint32_t m_uPeerSrtFlags; bool m_bTsbPd; // Peer sends TimeStamp-Based Packet Delivery Packets - pthread_t m_RcvTsbPdThread; // Rcv TsbPD Thread handle - pthread_cond_t m_RcvTsbPdCond; + bool m_bGroupTsbPd; // TSBPD should be used for GROUP RECEIVER instead + + sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle + sync::Condition m_RcvTsbPdCond; // TSBPD signals if reading is ready. Use together with m_RecvLock bool m_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent + sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining CallbackHolder m_cbAcceptHook; + CallbackHolder m_cbConnectHook; // FORWARDER public: - static int installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) - { - return s_UDTUnited.installAcceptHook(lsn, hook, opaq); - } + static int installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); + static int installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); private: void installAcceptHook(srt_listen_callback_fn* hook, void* opaq) { m_cbAcceptHook.set(opaq, hook); } + void installConnectHook(srt_connect_callback_fn* hook, void* opaq) + { + m_cbConnectHook.set(opaq, hook); + } -private: // synchronization: mutexes and conditions - pthread_mutex_t m_ConnectionLock; // used to synchronize connection operation - pthread_cond_t m_SendBlockCond; // used to block "send" call - pthread_mutex_t m_SendBlockLock; // lock associated to m_SendBlockCond +private: // synchronization: mutexes and conditions + sync::Mutex m_ConnectionLock; // used to synchronize connection operation - pthread_mutex_t m_RcvBufferLock; // Protects the state of the m_pRcvBuffer + sync::Condition m_SendBlockCond; // used to block "send" call + sync::Mutex m_SendBlockLock; // lock associated to m_SendBlockCond + mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer // Protects access to m_iSndCurrSeqNo, m_iSndLastAck - pthread_mutex_t m_RecvAckLock; // Protects the state changes while processing incomming ACK (UDT_EPOLL_OUT) - - - pthread_cond_t m_RecvDataCond; // used to block "recv" when there is no data - pthread_mutex_t m_RecvDataLock; // lock associated to m_RecvDataCond - - pthread_mutex_t m_SendLock; // used to synchronize "send" call - pthread_mutex_t m_RecvLock; // used to synchronize "recv" call + sync::Mutex m_RecvAckLock; // Protects the state changes while processing incomming ACK (SRT_EPOLL_OUT) - pthread_mutex_t m_RcvLossLock; // Protects the receiver loss list (access: CRcvQueue::worker, CUDT::tsbpd) + sync::Condition m_RecvDataCond; // used to block "srt_recv*" when there is no data. Use together with m_RecvLock + sync::Mutex m_RecvLock; // used to synchronize "srt_recv*" call, protects TSBPD drift updates (CRcvBuffer::isRcvDataReady()) - pthread_mutex_t m_StatsLock; // used to synchronize access to trace statistics + sync::Mutex m_SendLock; // used to synchronize "send" call + sync::Mutex m_RcvLossLock; // Protects the receiver loss list (access: CRcvQueue::worker, CUDT::tsbpd) + mutable sync::Mutex m_StatsLock; // used to synchronize access to trace statistics void initSynch(); void destroySynch(); void releaseSynch(); private: // Common connection Congestion Control setup + // This can fail only when it failed to create a congctl + // which only may happen when the congctl list is extended + // with user-supplied congctl modules, not a case so far. + SRT_ATR_NODISCARD SRT_REJECT_REASON setupCC(); - void updateCC(ETransmissionEvent, EventVariant arg); + + // for updateCC it's ok to discard the value. This returns false only if + // the congctl isn't created, and this can be prevented from. + bool updateCC(ETransmissionEvent, const EventVariant arg); + + // Failure to create the crypter means that an encrypted + // connection should be rejected if ENFORCEDENCRYPTION is on. + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) bool createCrypter(HandshakeSide side, bool bidi); private: // Generation and processing of packets - void sendCtrl(UDTMessageType pkttype, const void* lparam = NULL, void* rparam = NULL, int size = 0); + void sendCtrl(UDTMessageType pkttype, const int32_t* lparam = NULL, void* rparam = NULL, int size = 0); - void processCtrl(CPacket& ctrlpkt); + /// Forms and sends ACK packet + /// @note Assumes @ctrlpkt already has a timestamp. + /// + /// @param ctrlpkt A control packet structure to fill. It must have a timestemp already set. + /// @param size Sends lite ACK if size is SEND_LITE_ACK, Full ACK otherwise + /// + /// @returns the nmber of packets sent. + int sendCtrlAck(CPacket& ctrlpkt, int size); void sendLossReport(const std::vector< std::pair >& losslist); - void processCtrlAck(const CPacket& ctrlpkt, const uint64_t currtime_tk); - /// + void processCtrl(const CPacket& ctrlpkt); + + /// @brief Process incoming control ACK packet. + /// @param ctrlpkt incoming ACK packet + /// @param currtime current clock time + void processCtrlAck(const CPacket& ctrlpkt, const time_point& currtime); + + /// @brief Process incoming control ACKACK packet. + /// @param ctrlpkt incoming ACKACK packet + /// @param tsArrival time when packet has arrived (used to calculate RTT) + void processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival); + + /// @brief Process incoming loss report (NAK) packet. + /// @param ctrlpkt incoming NAK packet + void processCtrlLossReport(const CPacket& ctrlpkt); + + /// @brief Process incoming handshake control packet + /// @param ctrlpkt incoming HS packet + void processCtrlHS(const CPacket& ctrlpkt); + + /// @brief Process incoming drop request control packet + /// @param ctrlpkt incoming drop request packet + void processCtrlDropReq(const CPacket& ctrlpkt); + + /// @brief Process incoming shutdown control packet + void processCtrlShutdown(); + /// @brief Process incoming user defined control packet + /// @param ctrlpkt incoming user defined packet + void processCtrlUserDefined(const CPacket& ctrlpkt); + + /// @brief Update sender's loss list on an incoming acknowledgement. /// @param ackdata_seqno sequence number of a data packet being acknowledged void updateSndLossListOnACK(int32_t ackdata_seqno); @@ -738,126 +1041,110 @@ class CUDT /// @param origintime [in, out] origin timestamp of the packet /// /// @return payload size on success, <=0 on failure - int packLostData(CPacket& packet, uint64_t& origintime); + int packLostData(CPacket &packet, time_point &origintime); + + /// Pack a unique data packet (never sent so far) in CPacket for sending. + /// + /// @param packet [in, out] a CPacket structure to fill. + /// @param origintime [in, out] origin timestamp of the packet. + /// + /// @return true if a packet has been packets; false otherwise. + bool packUniqueData(CPacket& packet, time_point& origintime); + + /// Pack in CPacket the next data to be send. + /// + /// @param packet [in, out] a CPacket structure to fill + /// + /// @return A pair of values is returned (is_payload_valid, timestamp). + /// If is_payload_valid is false, there was nothing packed for sending, + /// and the timestamp value should be ignored. + /// The timestamp is the full source/origin timestamp of the data. + std::pair packData(CPacket& packet); - int packData(CPacket& packet, uint64_t& ts); int processData(CUnit* unit); void processClose(); - SRT_REJECT_REASON processConnectRequest(const sockaddr* addr, CPacket& packet); + + /// Process the request after receiving the handshake from caller. + /// The @a packet param is passed here as non-const because this function + /// will need to make a temporary back-and-forth endian swap; it doesn't intend to + /// modify the object permanently. + /// @param addr source address from where the request came + /// @param packet contents of the packet + /// @return URQ code, possibly containing reject reason + int processConnectRequest(const sockaddr_any& addr, CPacket& packet); static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi); - int32_t bake(const sockaddr* addr, int32_t previous_cookie = 0, int correction = 0); + int32_t bake(const sockaddr_any& addr, int32_t previous_cookie = 0, int correction = 0); -private: // Trace + /// @brief Acknowledge reading position up to the @p seq. + /// Updates m_iRcvLastAck and m_iRcvLastSkipAck to @p seq. + /// @param seq first unacknowledged packet sequence number. + void ackDataUpTo(int32_t seq); + +#if ENABLE_BONDING && ENABLE_NEW_RCVBUFFER + /// @brief Drop packets in the recv buffer behind group_recv_base. + /// Updates m_iRcvLastSkipAck if it's behind group_recv_base. + void dropToGroupRecvBase(); +#endif + + void processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival); + + /// Locks m_RcvBufferLock and retrieves the available size of the receiver buffer. + SRT_ATTR_EXCLUDES(m_RcvBufferLock) + size_t getAvailRcvBufferSizeLock() const; + + /// Retrieves the available size of the receiver buffer. + /// Expects that m_RcvBufferLock is locked. + SRT_ATTR_REQUIRES(m_RcvBufferLock) + size_t getAvailRcvBufferSizeNoLock() const; +private: // Trace struct CoreStats { - uint64_t startTime; // timestamp when the UDT entity is started - int64_t sentTotal; // total number of sent data packets, including retransmissions - int64_t recvTotal; // total number of received packets - int sndLossTotal; // total number of lost packets (sender side) - int rcvLossTotal; // total number of lost packets (receiver side) - int retransTotal; // total number of retransmitted packets - int sentACKTotal; // total number of sent ACK packets - int recvACKTotal; // total number of received ACK packets - int sentNAKTotal; // total number of sent NAK packets - int recvNAKTotal; // total number of received NAK packets - int sndDropTotal; - int rcvDropTotal; - uint64_t bytesSentTotal; // total number of bytes sent, including retransmissions - uint64_t bytesRecvTotal; // total number of received bytes - uint64_t rcvBytesLossTotal; // total number of loss bytes (estimate) - uint64_t bytesRetransTotal; // total number of retransmitted bytes - uint64_t sndBytesDropTotal; - uint64_t rcvBytesDropTotal; - int m_rcvUndecryptTotal; - uint64_t m_rcvBytesUndecryptTotal; - - int sndFilterExtraTotal; - int rcvFilterExtraTotal; - int rcvFilterSupplyTotal; - int rcvFilterLossTotal; + time_point tsStartTime; // timestamp when the UDT entity is started + stats::Sender sndr; // sender statistics + stats::Receiver rcvr; // receiver statistics int64_t m_sndDurationTotal; // total real time for sending - uint64_t lastSampleTime; // last performance sample time - int64_t traceSent; // number of packets sent in the last trace interval - int64_t traceRecv; // number of packets received in the last trace interval - int traceSndLoss; // number of lost packets in the last trace interval (sender side) - int traceRcvLoss; // number of lost packets in the last trace interval (receiver side) - int traceRetrans; // number of retransmitted packets in the last trace interval - int sentACK; // number of ACKs sent in the last trace interval - int recvACK; // number of ACKs received in the last trace interval - int sentNAK; // number of NAKs sent in the last trace interval - int recvNAK; // number of NAKs received in the last trace interval - int traceSndDrop; - int traceRcvDrop; - int traceRcvRetrans; + time_point tsLastSampleTime; // last performance sample time int traceReorderDistance; double traceBelatedTime; - int64_t traceRcvBelated; - uint64_t traceBytesSent; // number of bytes sent in the last trace interval - uint64_t traceBytesRecv; // number of bytes sent in the last trace interval - uint64_t traceRcvBytesLoss; // number of bytes bytes lost in the last trace interval (estimate) - uint64_t traceBytesRetrans; // number of bytes retransmitted in the last trace interval - uint64_t traceSndBytesDrop; - uint64_t traceRcvBytesDrop; - int traceRcvUndecrypt; - uint64_t traceRcvBytesUndecrypt; - - int sndFilterExtra; - int rcvFilterExtra; - int rcvFilterSupply; - int rcvFilterLoss; - + int64_t sndDuration; // real time for sending - int64_t sndDurationCounter; // timers to record the sending duration + time_point sndDurationCounter; // timers to record the sending Duration } m_stats; public: - static const int SELF_CLOCK_INTERVAL = 64; // ACK interval for self-clocking static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq static const int PACKETPAIR_MASK = 0xF; - static const size_t MAX_SID_LENGTH = 512; - -private: // Timers - uint64_t m_ullCPUFrequency; // CPU clock frequency, used for Timer, ticks per microsecond - uint64_t m_ullNextACKTime_tk; // Next ACK time, in CPU clock cycles, same below - uint64_t m_ullNextNAKTime_tk; // Next NAK time - - volatile uint64_t m_ullACKInt_tk; // ACK interval - volatile uint64_t m_ullNAKInt_tk; // NAK interval - volatile uint64_t m_ullLastRspTime_tk; // time stamp of last response from the peer - volatile uint64_t m_ullLastRspAckTime_tk; // time stamp of last ACK from the peer, protect with m_RecvAckLock - volatile uint64_t m_ullLastSndTime_tk; // time stamp of last data/ctrl sent (in system ticks) - uint64_t m_ullMinNakInt_tk; // NAK timeout lower bound; too small value can cause unnecessary retransmission - uint64_t m_ullMinExpInt_tk; // timeout lower bound threshold: too small timeout can cause problem - - int m_iPktCount; // packet counter for ACK - int m_iLightACKCount; // light ACK counter - - uint64_t m_ullTargetTime_tk; // scheduled time of next packet sending +private: // Timers functions + time_point m_tsFreshActivation; // GROUPS: time of fresh activation of the link, or 0 if past the activation phase or idle + time_point m_tsUnstableSince; // GROUPS: time since unexpected ACK delay experienced, or 0 if link seems healthy + time_point m_tsWarySince; // GROUPS: time since an unstable link has first some response + + static const int BECAUSE_NO_REASON = 0, // NO BITS + BECAUSE_ACK = 1 << 0, + BECAUSE_LITEACK = 1 << 1, + BECAUSE_NAKREPORT = 1 << 2, + LAST_BECAUSE_BIT = 3; void checkTimers(); - void checkACKTimer (uint64_t currtime_tk); - void checkNAKTimer(uint64_t currtime_tk); - bool checkExpTimer (uint64_t currtime_tk); // returns true if the connection is expired - void checkRexmitTimer(uint64_t currtime_tk); - -public: // For the use of CCryptoControl - // HaiCrypt configuration - unsigned int m_uKmRefreshRatePkt; - unsigned int m_uKmPreAnnouncePkt; + void considerLegacySrtHandshake(const time_point &timebase); + int checkACKTimer (const time_point& currtime); + int checkNAKTimer(const time_point& currtime); + bool checkExpTimer (const time_point& currtime, int check_reason); // returns true if the connection is expired + void checkRexmitTimer(const time_point& currtime); private: // for UDP multiplexer - CSndQueue* m_pSndQueue; // packet sending queue - CRcvQueue* m_pRcvQueue; // packet receiving queue - sockaddr* m_pPeerAddr; // peer address - uint32_t m_piSelfIP[4]; // local UDP IP address - CSNode* m_pSNode; // node information for UDT list used in snd queue - CRNode* m_pRNode; // node information for UDT list used in rcv queue + CSndQueue* m_pSndQueue; // packet sending queue + CRcvQueue* m_pRcvQueue; // packet receiving queue + sockaddr_any m_PeerAddr; // peer address + uint32_t m_piSelfIP[4]; // local UDP IP address + CSNode* m_pSNode; // node information for UDT list used in snd queue + CRNode* m_pRNode; // node information for UDT list used in rcv queue public: // For SrtCongestion const CSndQueue* sndQueue() { return m_pSndQueue; } @@ -866,8 +1153,10 @@ class CUDT private: // for epoll std::set m_sPollID; // set of epoll ID to trigger void addEPoll(const int eid); - void removeEPoll(const int eid); + void removeEPollEvents(const int eid); + void removeEPollID(const int eid); }; +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/crypto.cpp b/trunk/3rdparty/srt-1-fit/srtcore/crypto.cpp index 3c09570a1f..4740102980 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/crypto.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/crypto.cpp @@ -13,6 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include #include #include @@ -24,6 +26,7 @@ written by #include "crypto.h" #include "logging.h" #include "core.h" +#include "api.h" using namespace srt_logging; @@ -40,9 +43,10 @@ using namespace srt_logging; */ // 10* HAICRYPT_DEF_KM_PRE_ANNOUNCE -const int SRT_CRYPT_KM_PRE_ANNOUNCE = 0x10000; +const int SRT_CRYPT_KM_PRE_ANNOUNCE SRT_ATR_UNUSED = 0x10000; -#if ENABLE_LOGGING +namespace srt_logging +{ std::string KmStateStr(SRT_KM_STATE state) { switch (state) @@ -62,9 +66,21 @@ std::string KmStateStr(SRT_KM_STATE state) } } } +} // namespace +using srt_logging::KmStateStr; + +void srt::CCryptoControl::globalInit() +{ +#ifdef SRT_ENABLE_ENCRYPTION + // We need to force the Cryspr to be initialized during startup to avoid the + // possibility of multiple threads initialzing the same static data later on. + HaiCryptCryspr_Get_Instance(); +#endif +} -std::string CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen) +#if ENABLE_LOGGING +std::string srt::CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen) { std::ostringstream os; os << hdr << ": cmd=" << cmd << "(" << (cmd == SRT_CMD_KMREQ ? "KMREQ":"KMRSP") <<") len=" @@ -75,7 +91,7 @@ std::string CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srt } #endif -void CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) +void srt::CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) { if (cmd == SRT_CMD_KMREQ) { @@ -83,43 +99,41 @@ void CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) { m_SndKmState = SRT_KM_S_SECURING; } - LOGP(mglog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen)); + LOGP(cnlog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen)); } else { - LOGP(mglog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen)); + LOGP(cnlog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen)); } } -void CCryptoControl::createFakeSndContext() +void srt::CCryptoControl::createFakeSndContext() { if (!m_iSndKmKeyLen) m_iSndKmKeyLen = 16; - if (!createCryptoCtx(Ref(m_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX)) + if (!createCryptoCtx(m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, (m_hSndCrypto))) { - HLOGC(mglog.Debug, log << "Error: Can't create fake crypto context for sending - sending will return ERROR!"); + HLOGC(cnlog.Debug, log << "Error: Can't create fake crypto context for sending - sending will return ERROR!"); m_hSndCrypto = 0; } } -int CCryptoControl::processSrtMsg_KMREQ( +int srt::CCryptoControl::processSrtMsg_KMREQ( const uint32_t* srtdata SRT_ATR_UNUSED, size_t bytelen SRT_ATR_UNUSED, - uint32_t* srtdata_out, ref_t r_srtlen, int hsv SRT_ATR_UNUSED) + int hsv SRT_ATR_UNUSED, + uint32_t pw_srtdata_out[], size_t& w_srtlen) { - size_t& srtlen = *r_srtlen; //Receiver /* All 32-bit msg fields swapped on reception * But HaiCrypt expect network order message * Re-swap to cancel it. */ #ifdef SRT_ENABLE_ENCRYPTION - srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]); - HtoNLA(srtdata_out, srtdata, srtlen); - unsigned char* kmdata = reinterpret_cast(srtdata_out); - - std::vector kmcopy(kmdata, kmdata + bytelen); + w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]); + HtoNLA((pw_srtdata_out), srtdata, w_srtlen); + unsigned char* kmdata = reinterpret_cast(pw_srtdata_out); // The side that has received KMREQ is always an HSD_RESPONDER, regardless of // what has called this function. The HSv5 handshake only enforces bidirectional @@ -131,11 +145,11 @@ int CCryptoControl::processSrtMsg_KMREQ( // CHANGED. The first version made HSv5 reject the connection. // This isn't well handled by applications, so the connection is // still established, but unable to handle any transport. -//#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { srtlen = 1; goto HSv4_ErrorReport; } -#define KMREQ_RESULT_REJECTION() { srtlen = 1; goto HSv4_ErrorReport; } +//#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { w_srtlen = 1; goto HSv4_ErrorReport; } +#define KMREQ_RESULT_REJECTION() { w_srtlen = 1; goto HSv4_ErrorReport; } int rc = HAICRYPT_OK; // needed before 'goto' run from KMREQ_RESULT_REJECTION macro - bool SRT_ATR_UNUSED wasb4 = false; + bool wasb4 SRT_ATR_UNUSED = false; size_t sek_len = 0; // What we have to do: @@ -147,16 +161,16 @@ int CCryptoControl::processSrtMsg_KMREQ( // function normally return SRT_CMD_KMRSP. if ( bytelen <= HCRYPT_MSG_KM_OFS_SALT ) //Sanity on message { - LOGC(mglog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT); + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT); m_RcvKmState = SRT_KM_S_BADSECRET; KMREQ_RESULT_REJECTION(); } - HLOGC(mglog.Debug, log << "KMREQ: getting SEK and creating receiver crypto"); + HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto"); sek_len = hcryptMsg_KM_GetSekLen(kmdata); if ( sek_len == 0 ) { - LOGC(mglog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!"); + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!"); m_RcvKmState = SRT_KM_S_BADSECRET; KMREQ_RESULT_REJECTION(); } @@ -168,7 +182,7 @@ int CCryptoControl::processSrtMsg_KMREQ( #if ENABLE_HEAVY_LOGGING if (m_iSndKmKeyLen != m_iRcvKmKeyLen) { - LOGC(mglog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen + LOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen << " overwritten by Peer's PBKEYLEN=" << m_iRcvKmKeyLen); } #endif @@ -179,22 +193,22 @@ int CCryptoControl::processSrtMsg_KMREQ( // a wrong password. if (m_KmSecret.len == 0) //We have a shared secret <==> encryption is on { - LOGC(mglog.Error, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!"); + LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!"); m_RcvKmState = SRT_KM_S_NOSECRET; KMREQ_RESULT_REJECTION(); } wasb4 = m_hRcvCrypto; - if (!createCryptoCtx(Ref(m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX)) + if (!createCryptoCtx(m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, (m_hRcvCrypto))) { - LOGC(mglog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."); + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."); m_RcvKmState = SRT_KM_S_NOSECRET; KMREQ_RESULT_REJECTION(); } if (!wasb4) { - HLOGC(mglog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen); + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen); } // We have both sides set with password, so both are pending for security m_RcvKmState = SRT_KM_S_SECURING; @@ -207,36 +221,36 @@ int CCryptoControl::processSrtMsg_KMREQ( { case HAICRYPT_OK: m_RcvKmState = SRT_KM_S_SECURED; - HLOGC(mglog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED."); + HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED."); //Send back the whole message to confirm break; case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; //Send status KMRSP message to tel error - srtlen = 1; - LOGC(mglog.Error, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"); + w_srtlen = 1; + LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"); break; case HAICRYPT_ERROR: //Other errors default: m_RcvKmState = m_SndKmState = SRT_KM_S_NOSECRET; - srtlen = 1; - LOGC(mglog.Error, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET"); + w_srtlen = 1; + LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET"); break; } - LOGP(mglog.Note, FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)); + LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)); // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE, // until the next KMREQ is received as a key regeneration. m_bErrorReported = false; - if (srtlen == 1) + if (w_srtlen == 1) goto HSv4_ErrorReport; // Configure the sender context also, if it succeeded to configure the // receiver context and we are using bidirectional mode. - if ( bidirectional ) + if (bidirectional) { // Note: 'bidirectional' means that we want a bidirectional key update, // which happens only and exclusively with HSv5 handshake - not when the @@ -249,7 +263,7 @@ int CCryptoControl::processSrtMsg_KMREQ( m_iSndKmKeyLen = m_iRcvKmKeyLen; if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK) { - LOGC(mglog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!"); + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!"); if (hasPassphrase()) m_SndKmState = SRT_KM_S_BADSECRET; else @@ -260,30 +274,30 @@ int CCryptoControl::processSrtMsg_KMREQ( m_SndKmState = SRT_KM_S_SECURED; } - LOGC(mglog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) + LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) << " SndKeyLen=" << m_iSndKmKeyLen << " TX CRYPTO CTX CLONED FROM RX" ); // Write the KM message into the field from which it will be next sent. - memcpy(m_SndKmMsg[0].Msg, kmdata, bytelen); + memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen); m_SndKmMsg[0].MsgLen = bytelen; m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :) } else { - HLOGC(mglog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " << KmStateStr(m_SndKmState) << " state"); } } else { - HLOGP(mglog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); + HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); } } else { - HLOGC(mglog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX."); + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX."); } return SRT_CMD_KMRSP; @@ -304,17 +318,17 @@ int CCryptoControl::processSrtMsg_KMREQ( // It's ok that this is reported as error because this happens in a scenario, // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT // application which tries to make a security association. - LOGC(mglog.Error, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject..."); + LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject..."); m_RcvKmState = SRT_KM_S_NOSECRET; #endif - srtlen = 1; + w_srtlen = 1; - srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState; + pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState; return SRT_CMD_KMRSP; } -int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int /* XXX unused? hsv*/) +int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int /* XXX unused? hsv*/) { /* All 32-bit msg fields (if present) swapped on reception * But HaiCrypt expect network order message @@ -367,7 +381,7 @@ int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int break; default: - LOGC(mglog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer error state: " + LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer error state: " << KmStateStr(peerstate) << " (" << int(peerstate) << ")"); m_RcvKmState = SRT_KM_S_NOSECRET; m_SndKmState = SRT_KM_S_NOSECRET; @@ -375,11 +389,11 @@ int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int break; } - LOGC(mglog.Error, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState)); + LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState)); } else { - HLOGC(mglog.Debug, log << "processSrtMsg_KMRSP: received key response len=" << len); + HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: received key response len=" << len); // XXX INSECURE << ": [" << FormatBinaryString((uint8_t*)srtd, len) << "]"; bool key1 = getKmMsg_acceptResponse(0, srtd, len); bool key2 = true; @@ -389,40 +403,40 @@ int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int if (key1 || key2) { m_SndKmState = m_RcvKmState = SRT_KM_S_SECURED; - HLOGC(mglog.Debug, log << "processSrtMsg_KMRSP: KM response matches " << (key1 ? "EVEN" : "ODD") << " key"); + HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: KM response matches " << (key1 ? "EVEN" : "ODD") << " key"); retstatus = 1; } else { retstatus = -1; - LOGC(mglog.Error, log << "processSrtMsg_KMRSP: IPE??? KM response key matches no key"); + LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE??? KM response key matches no key"); /* XXX INSECURE - LOGC(mglog.Error, log << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len) + LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len) << "] matches no key 0=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[0].Msg, m_SndKmMsg[0].MsgLen) << "] 1=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[1].Msg, m_SndKmMsg[1].MsgLen) << "]"); */ m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET; } - HLOGC(mglog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry + HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); } - LOGP(mglog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len)); + LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len)); return retstatus; } -void CCryptoControl::sendKeysToPeer(Whether2RegenKm regen SRT_ATR_UNUSED) +void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SRT_ATR_UNUSED, Whether2RegenKm regen SRT_ATR_UNUSED) { - if ( !m_hSndCrypto || m_SndKmState == SRT_KM_S_UNSECURED) + if (!m_hSndCrypto || m_SndKmState == SRT_KM_S_UNSECURED) { - HLOGC(mglog.Debug, log << "sendKeysToPeer: NOT sending/regenerating keys: " + HLOGC(cnlog.Debug, log << "sendKeysToPeer: NOT sending/regenerating keys: " << (m_hSndCrypto ? "CONNECTION UNSECURED" : "NO TX CRYPTO CTX created")); return; } #ifdef SRT_ENABLE_ENCRYPTION - uint64_t now = 0; + srt::sync::steady_clock::time_point now = srt::sync::steady_clock::now(); /* * Crypto Key Distribution to peer: * If... @@ -432,38 +446,35 @@ void CCryptoControl::sendKeysToPeer(Whether2RegenKm regen SRT_ATR_UNUSED) * - last sent Keying Material req should have been replied (RTT*1.5 elapsed); * then (re-)send handshake request. */ - if ( ((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0)) - && ((m_SndKmLastTime + ((m_parent->RTT() * 3)/2)) <= (now = CTimer::getTime()))) + if (((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0)) + && ((m_SndKmLastTime + srt::sync::microseconds_from((iSRTT * 3)/2)) <= now)) { for (int ki = 0; ki < 2; ki++) { if (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0) { m_SndKmMsg[ki].iPeerRetry--; - HLOGC(mglog.Debug, log << "sendKeysToPeer: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen + HLOGC(cnlog.Debug, log << "sendKeysToPeer: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry); m_SndKmLastTime = now; - m_parent->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen/sizeof(uint32_t)); + sock->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t)); } } } - if (now == 0) - { - HLOGC(mglog.Debug, log << "sendKeysToPeer: NO KEYS RESENT, will " << - (regen ? "" : "NOT ") << "regenerate."); - } if (regen) + { regenCryptoKm( - true, // send UMSG_EXT + SRT_CMD_KMREQ to the peer, if regenerated the key - false // Do not apply the regenerated key to the to the receiver context - ); // regenerate and send + sock, // send UMSG_EXT + SRT_CMD_KMREQ to the peer using this socket + false // Do not apply the regenerated key to the to the receiver context + ); // regenerate and send + } #endif } #ifdef SRT_ENABLE_ENCRYPTION -void CCryptoControl::regenCryptoKm(bool sendit, bool bidirectional) +void srt::CCryptoControl::regenCryptoKm(CUDT* sock, bool bidirectional) { if (!m_hSndCrypto) return; @@ -473,8 +484,8 @@ void CCryptoControl::regenCryptoKm(bool sendit, bool bidirectional) int nbo = HaiCrypt_Tx_ManageKeys(m_hSndCrypto, out_p, out_len_p, 2); int sent = 0; - HLOGC(mglog.Debug, log << "regenCryptoKm: regenerating crypto keys nbo=" << nbo << - " THEN=" << (sendit ? "SEND" : "KEEP") << " DIR=" << (bidirectional ? "BOTH" : "SENDER")); + HLOGC(cnlog.Debug, log << "regenCryptoKm: regenerating crypto keys nbo=" << nbo << + " THEN=" << (sock ? "SEND" : "KEEP") << " DIR=" << (bidirectional ? "BOTH" : "SENDER")); for (int i = 0; i < nbo && i < 2; i++) { @@ -491,71 +502,69 @@ void CCryptoControl::regenCryptoKm(bool sendit, bool bidirectional) { uint8_t* oldkey SRT_ATR_UNUSED = m_SndKmMsg[ki].Msg; - HLOGC(mglog.Debug, log << "new key[" << ki << "] index=" << kix + HLOGC(cnlog.Debug, log << "new key[" << ki << "] index=" << kix << " OLD=[" << m_SndKmMsg[ki].MsgLen << "]" << FormatBinaryString(m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen) << " NEW=[" << out_len_p[i] << "]" << FormatBinaryString((const uint8_t*)out_p[i], out_len_p[i])); /* New Keying material, send to peer */ - memcpy(m_SndKmMsg[ki].Msg, out_p[i], out_len_p[i]); + memcpy((m_SndKmMsg[ki].Msg), out_p[i], out_len_p[i]); m_SndKmMsg[ki].MsgLen = out_len_p[i]; m_SndKmMsg[ki].iPeerRetry = SRT_MAX_KMRETRY; - if (bidirectional && !sendit) + if (bidirectional && !sock) { // "Send" this key also to myself, just to be applied to the receiver crypto, // exactly the same way how this key is interpreted on the peer side into its receiver crypto int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen, NULL, NULL, 0); if ( rc < 0 ) { - LOGC(mglog.Fatal, log << "regenCryptoKm: IPE: applying key generated in snd crypto into rcv crypto: failed code=" << rc); + LOGC(cnlog.Fatal, log << "regenCryptoKm: IPE: applying key generated in snd crypto into rcv crypto: failed code=" << rc); // The party won't be able to decrypt incoming data! // Not sure if anything has to be reported. } } - if (sendit) + if (sock) { - HLOGC(mglog.Debug, log << "regenCryptoKm: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen + HLOGC(cnlog.Debug, log << "regenCryptoKm: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry); - m_parent->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen/sizeof(uint32_t)); + sock->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t)); sent++; } } else if (out_len_p[i] == 0) { - HLOGC(mglog.Debug, log << "no key[" << ki << "] index=" << kix << ": not generated"); + HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": not generated"); } else { - HLOGC(mglog.Debug, log << "no key[" << ki << "] index=" << kix << ": key unchanged"); + HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": key unchanged"); } } - HLOGC(mglog.Debug, log << "regenCryptoKm: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry + HLOGC(cnlog.Debug, log << "regenCryptoKm: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); if (sent) - m_SndKmLastTime = CTimer::getTime(); + m_SndKmLastTime = srt::sync::steady_clock::now(); } #endif -CCryptoControl::CCryptoControl(CUDT* parent, SRTSOCKET id): -m_parent(parent), // should be initialized in createCC() -m_SocketID(id), -m_iSndKmKeyLen(0), -m_iRcvKmKeyLen(0), -m_SndKmState(SRT_KM_S_UNSECURED), -m_RcvKmState(SRT_KM_S_UNSECURED), -m_KmRefreshRatePkt(0), -m_KmPreAnnouncePkt(0), -m_bErrorReported(false) +srt::CCryptoControl::CCryptoControl(SRTSOCKET id) + : m_SocketID(id) + , m_iSndKmKeyLen(0) + , m_iRcvKmKeyLen(0) + , m_SndKmState(SRT_KM_S_UNSECURED) + , m_RcvKmState(SRT_KM_S_UNSECURED) + , m_KmRefreshRatePkt(0) + , m_KmPreAnnouncePkt(0) + , m_bErrorReported(false) { m_KmSecret.len = 0; //send - m_SndKmLastTime = 0; m_SndKmMsg[0].MsgLen = 0; m_SndKmMsg[0].iPeerRetry = 0; m_SndKmMsg[1].MsgLen = 0; @@ -565,14 +574,14 @@ m_bErrorReported(false) m_hRcvCrypto = NULL; } -bool CCryptoControl::init(HandshakeSide side, bool bidirectional SRT_ATR_UNUSED) +bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool bidirectional SRT_ATR_UNUSED) { // NOTE: initiator creates m_hSndCrypto. When bidirectional, // it creates also m_hRcvCrypto with the same key length. // Acceptor creates nothing - it will create appropriate // contexts when receiving KMREQ from the initiator. - HLOGC(mglog.Debug, log << "CCryptoControl::init: HS SIDE:" + HLOGC(cnlog.Debug, log << "CCryptoControl::init: HS SIDE:" << (side == HSD_INITIATOR ? "INITIATOR" : "RESPONDER") << " DIRECTION:" << (bidirectional ? "BOTH" : (side == HSD_INITIATOR) ? "SENDER" : "RECEIVER")); @@ -582,8 +591,8 @@ bool CCryptoControl::init(HandshakeSide side, bool bidirectional SRT_ATR_UNUSED) // Set security-pending state, if a password was set. m_SndKmState = hasPassphrase() ? SRT_KM_S_SECURING : SRT_KM_S_UNSECURED; - m_KmPreAnnouncePkt = m_parent->m_uKmPreAnnouncePkt; - m_KmRefreshRatePkt = m_parent->m_uKmRefreshRatePkt; + m_KmPreAnnouncePkt = cfg.uKmPreAnnouncePkt; + m_KmRefreshRatePkt = cfg.uKmRefreshRatePkt; if ( side == HSD_INITIATOR ) { @@ -592,18 +601,18 @@ bool CCryptoControl::init(HandshakeSide side, bool bidirectional SRT_ATR_UNUSED) #ifdef SRT_ENABLE_ENCRYPTION if (m_iSndKmKeyLen == 0) { - HLOGC(mglog.Debug, log << "CCryptoControl::init: PBKEYLEN still 0, setting default 16"); + HLOGC(cnlog.Debug, log << "CCryptoControl::init: PBKEYLEN still 0, setting default 16"); m_iSndKmKeyLen = 16; } - bool ok = createCryptoCtx(Ref(m_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX); - HLOGC(mglog.Debug, log << "CCryptoControl::init: creating SND crypto context: " << ok); + bool ok = createCryptoCtx(m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, (m_hSndCrypto)); + HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating SND crypto context: " << ok); if (ok && bidirectional) { m_iRcvKmKeyLen = m_iSndKmKeyLen; int st = HaiCrypt_Clone(m_hSndCrypto, HAICRYPT_CRYPTO_DIR_RX, &m_hRcvCrypto); - HLOGC(mglog.Debug, log << "CCryptoControl::init: creating CLONED RCV crypto context: status=" << st); + HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating CLONED RCV crypto context: status=" << st); ok = st == 0; } @@ -618,45 +627,51 @@ bool CCryptoControl::init(HandshakeSide side, bool bidirectional SRT_ATR_UNUSED) } regenCryptoKm( - false, // Do not send the key (will be attached it to the HSv5 handshake) - bidirectional // replicate the key to the receiver context, if bidirectional - ); + NULL, // Do not send the key (the KM msg will be attached to the HSv5 handshake) + bidirectional // replicate the key to the receiver context, if bidirectional + ); #else - LOGC(mglog.Error, log << "CCryptoControl::init: encryption not supported"); + // This error would be a consequence of setting the passphrase, while encryption + // is turned off at compile time. Setting the password itself should be not allowed + // so this could only happen as a consequence of an IPE. + LOGC(cnlog.Error, log << "CCryptoControl::init: IPE: encryption not supported"); return true; #endif } else { - HLOGC(mglog.Debug, log << "CCryptoControl::init: CAN'T CREATE crypto: key length for SND = " << m_iSndKmKeyLen); + HLOGC(cnlog.Debug, log << "CCryptoControl::init: CAN'T CREATE crypto: key length for SND = " << m_iSndKmKeyLen); } } else { - HLOGC(mglog.Debug, log << "CCryptoControl::init: NOT creating crypto contexts - will be created upon reception of KMREQ"); + HLOGC(cnlog.Debug, log << "CCryptoControl::init: NOT creating crypto contexts - will be created upon reception of KMREQ"); } return true; } -void CCryptoControl::close() +void srt::CCryptoControl::close() { /* Wipeout secrets */ memset(&m_KmSecret, 0, sizeof(m_KmSecret)); } -std::string CCryptoControl::CONID() const +std::string srt::CCryptoControl::CONID() const { - if ( m_SocketID == 0 ) + if (m_SocketID == 0) return ""; std::ostringstream os; - os << "%" << m_SocketID << ":"; + os << "@" << m_SocketID << ":"; return os.str(); } +#ifdef SRT_ENABLE_ENCRYPTION + #if ENABLE_HEAVY_LOGGING +namespace srt { static std::string CryptoFlags(int flg) { using namespace std; @@ -673,13 +688,13 @@ static std::string CryptoFlags(int flg) copy(f.begin(), f.end(), ostream_iterator(os, "|")); return os.str(); } -#endif +} // namespace srt +#endif // ENABLE_HEAVY_LOGGING -#ifdef SRT_ENABLE_ENCRYPTION -bool CCryptoControl::createCryptoCtx(ref_t hCrypto, size_t keylen, HaiCrypt_CryptoDir cdir) +bool srt::CCryptoControl::createCryptoCtx(size_t keylen, HaiCrypt_CryptoDir cdir, HaiCrypt_Handle& w_hCrypto) { - if (*hCrypto) + if (w_hCrypto) { // XXX You can check here if the existing handle represents // a correctly defined crypto. But this doesn't seem to be @@ -690,7 +705,7 @@ bool CCryptoControl::createCryptoCtx(ref_t hCrypto, size_t keyl if ((m_KmSecret.len <= 0) || (keylen <= 0)) { - LOGC(mglog.Error, log << CONID() << "cryptoCtx: missing secret (" << m_KmSecret.len << ") or key length (" << keylen << ")"); + LOGC(cnlog.Error, log << CONID() << "cryptoCtx: IPE missing secret (" << m_KmSecret.len << ") or key length (" << keylen << ")"); return false; } @@ -711,36 +726,35 @@ bool CCryptoControl::createCryptoCtx(ref_t hCrypto, size_t keyl crypto_cfg.secret = m_KmSecret; //memcpy(&crypto_cfg.secret, &m_KmSecret, sizeof(crypto_cfg.secret)); - HLOGC(mglog.Debug, log << "CRYPTO CFG: flags=" << CryptoFlags(crypto_cfg.flags) << " xport=" << crypto_cfg.xport << " cryspr=" << crypto_cfg.cryspr + HLOGC(cnlog.Debug, log << "CRYPTO CFG: flags=" << CryptoFlags(crypto_cfg.flags) << " xport=" << crypto_cfg.xport << " cryspr=" << crypto_cfg.cryspr << " keylen=" << crypto_cfg.key_len << " passphrase_length=" << crypto_cfg.secret.len); - if (HaiCrypt_Create(&crypto_cfg, &hCrypto.get()) != HAICRYPT_OK) + if (HaiCrypt_Create(&crypto_cfg, (&w_hCrypto)) != HAICRYPT_OK) { - LOGC(mglog.Error, log << CONID() << "cryptoCtx: could not create " << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " crypto ctx"); + LOGC(cnlog.Error, log << CONID() << "cryptoCtx: could not create " << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " crypto ctx"); return false; } - HLOGC(mglog.Debug, log << CONID() << "cryptoCtx: CREATED crypto for dir=" << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " keylen=" << keylen); + HLOGC(cnlog.Debug, log << CONID() << "cryptoCtx: CREATED crypto for dir=" << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " keylen=" << keylen); return true; } #else -bool CCryptoControl::createCryptoCtx(ref_t, size_t, HaiCrypt_CryptoDir) +bool srt::CCryptoControl::createCryptoCtx(size_t, HaiCrypt_CryptoDir, HaiCrypt_Handle&) { return false; } -#endif +#endif // SRT_ENABLE_ENCRYPTION -EncryptionStatus CCryptoControl::encrypt(ref_t r_packet SRT_ATR_UNUSED) +srt::EncryptionStatus srt::CCryptoControl::encrypt(CPacket& w_packet SRT_ATR_UNUSED) { #ifdef SRT_ENABLE_ENCRYPTION // Encryption not enabled - do nothing. if ( getSndCryptoFlags() == EK_NOENC ) return ENCS_CLEAR; - CPacket& packet = *r_packet; - int rc = HaiCrypt_Tx_Data(m_hSndCrypto, (uint8_t*)packet.getHeader(), (uint8_t*)packet.m_pcData, packet.getLength()); + int rc = HaiCrypt_Tx_Data(m_hSndCrypto, ((uint8_t*)w_packet.getHeader()), ((uint8_t*)w_packet.m_pcData), w_packet.getLength()); if (rc < 0) { return ENCS_FAILED; @@ -749,7 +763,7 @@ EncryptionStatus CCryptoControl::encrypt(ref_t r_packet SRT_ATR_UNUSED) { // XXX what happens if the encryption is said to be "succeeded", // but the length is 0? Shouldn't this be treated as unwanted? - packet.setLength(rc); + w_packet.setLength(rc); } return ENCS_CLEAR; @@ -758,14 +772,12 @@ EncryptionStatus CCryptoControl::encrypt(ref_t r_packet SRT_ATR_UNUSED) #endif } -EncryptionStatus CCryptoControl::decrypt(ref_t r_packet SRT_ATR_UNUSED) +srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNUSED) { #ifdef SRT_ENABLE_ENCRYPTION - CPacket& packet = *r_packet; - - if (packet.getMsgCryptoFlags() == EK_NOENC) + if (w_packet.getMsgCryptoFlags() == EK_NOENC) { - HLOGC(mglog.Debug, log << "CPacket::decrypt: packet not encrypted"); + HLOGC(cnlog.Debug, log << "CPacket::decrypt: packet not encrypted"); return ENCS_CLEAR; // not encrypted, no need do decrypt, no flags to be modified } @@ -776,8 +788,8 @@ EncryptionStatus CCryptoControl::decrypt(ref_t r_packet SRT_ATR_UNUSED) // We were unaware that the peer has set password, // but now here we are. m_RcvKmState = SRT_KM_S_SECURING; - LOGC(mglog.Note, log << "SECURITY UPDATE: Peer has surprised Agent with encryption, but KMX is pending - current packet size=" - << packet.getLength() << " dropped"); + LOGC(cnlog.Note, log << "SECURITY UPDATE: Peer has surprised Agent with encryption, but KMX is pending - current packet size=" + << w_packet.getLength() << " dropped"); return ENCS_FAILED; } else @@ -786,7 +798,7 @@ EncryptionStatus CCryptoControl::decrypt(ref_t r_packet SRT_ATR_UNUSED) // which means that it will be unable to decrypt // sent payloads anyway. m_RcvKmState = SRT_KM_S_NOSECRET; - LOGP(mglog.Error, "SECURITY FAILURE: Agent has no PW, but Peer sender has declared one, can't decrypt"); + LOGP(cnlog.Warn, "SECURITY FAILURE: Agent has no PW, but Peer sender has declared one, can't decrypt"); // This only informs about the state change; it will be also caught by the condition below } } @@ -807,28 +819,28 @@ EncryptionStatus CCryptoControl::decrypt(ref_t r_packet SRT_ATR_UNUSED) if (!m_bErrorReported) { m_bErrorReported = true; - LOGC(mglog.Error, log << "SECURITY STATUS: " << KmStateStr(m_RcvKmState) << " - can't decrypt packet."); + LOGC(cnlog.Error, log << "SECURITY STATUS: " << KmStateStr(m_RcvKmState) << " - can't decrypt w_packet."); } - HLOGC(mglog.Debug, log << "Packet still not decrypted, status=" << KmStateStr(m_RcvKmState) - << " - dropping size=" << packet.getLength()); + HLOGC(cnlog.Debug, log << "Packet still not decrypted, status=" << KmStateStr(m_RcvKmState) + << " - dropping size=" << w_packet.getLength()); return ENCS_FAILED; } - int rc = HaiCrypt_Rx_Data(m_hRcvCrypto, (uint8_t *)packet.getHeader(), (uint8_t *)packet.m_pcData, packet.getLength()); + int rc = HaiCrypt_Rx_Data(m_hRcvCrypto, ((uint8_t *)w_packet.getHeader()), ((uint8_t *)w_packet.m_pcData), w_packet.getLength()); if ( rc <= 0 ) { - LOGC(mglog.Error, log << "decrypt ERROR (IPE): HaiCrypt_Rx_Data failure=" << rc << " - returning failed decryption"); + LOGC(cnlog.Error, log << "decrypt ERROR (IPE): HaiCrypt_Rx_Data failure=" << rc << " - returning failed decryption"); // -1: decryption failure // 0: key not received yet return ENCS_FAILED; } // Otherwise: rc == decrypted text length. - packet.setLength(rc); /* In case clr txt size is different from cipher txt */ + w_packet.setLength(rc); /* In case clr txt size is different from cipher txt */ // Decryption succeeded. Update flags. - packet.setMsgCryptoFlags(EK_NOENC); + w_packet.setMsgCryptoFlags(EK_NOENC); - HLOGC(mglog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc); + HLOGC(cnlog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc); return ENCS_CLEAR; #else return ENCS_NOTSUP; @@ -836,9 +848,10 @@ EncryptionStatus CCryptoControl::decrypt(ref_t r_packet SRT_ATR_UNUSED) } -CCryptoControl::~CCryptoControl() +srt::CCryptoControl::~CCryptoControl() { #ifdef SRT_ENABLE_ENCRYPTION + close(); if (m_hSndCrypto) { HaiCrypt_Close(m_hSndCrypto); @@ -850,38 +863,3 @@ CCryptoControl::~CCryptoControl() } #endif } - - -std::string SrtFlagString(int32_t flags) -{ -#define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) - - std::string output; - static std::string namera[] = { "TSBPD-snd", "TSBPD-rcv", "haicrypt", "TLPktDrop", "NAKReport", "ReXmitFlag", "StreamAPI" }; - - size_t i = 0; - for ( ; i < LEN(namera); ++i ) - { - if ( (flags & 1) == 1 ) - { - output += "+" + namera[i] + " "; - } - else - { - output += "-" + namera[i] + " "; - } - - flags >>= 1; - //if ( flags == 0 ) - // break; - } - -#undef LEN - - if ( flags != 0 ) - { - output += "+unknown"; - } - - return output; -} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/crypto.h b/trunk/3rdparty/srt-1-fit/srtcore/crypto.h index 31744e663d..2c2b352502 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/crypto.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/crypto.h @@ -13,8 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__CRYPTO_H -#define INC__CRYPTO_H +#ifndef INC_SRT_CRYPTO_H +#define INC_SRT_CRYPTO_H #include #include @@ -28,33 +28,36 @@ written by #include #include -#if ENABLE_LOGGING -std::string KmStateStr(SRT_KM_STATE state); namespace srt_logging { -extern Logger mglog; +std::string KmStateStr(SRT_KM_STATE state); +#if ENABLE_LOGGING +extern Logger cnlog; +#endif } -#endif +namespace srt +{ +class CUDT; +struct CSrtConfig; + // For KMREQ/KMRSP. Only one field is used. const size_t SRT_KMR_KMSTATE = 0; #define SRT_CMD_MAXSZ HCRYPT_MSG_KM_MAX_SZ /* Maximum SRT custom messages payload size (bytes) */ -const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ/sizeof(int32_t); +const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ/sizeof(uint32_t); enum Whether2RegenKm {DONT_REGEN_KM = 0, REGEN_KM = 1}; class CCryptoControl { -//public: - class CUDT* m_parent; - SRTSOCKET m_SocketID; + SRTSOCKET m_SocketID; - size_t m_iSndKmKeyLen; //Key length - size_t m_iRcvKmKeyLen; //Key length from rx KM + size_t m_iSndKmKeyLen; //Key length + size_t m_iRcvKmKeyLen; //Key length from rx KM // Temporarily allow these to be accessed. public: @@ -69,7 +72,7 @@ class CCryptoControl HaiCrypt_Secret m_KmSecret; //Key material shared secret // Sender - uint64_t m_SndKmLastTime; + sync::steady_clock::time_point m_SndKmLastTime; struct { unsigned char Msg[HCRYPT_MSG_KM_MAX_SZ]; size_t MsgLen; @@ -82,6 +85,7 @@ class CCryptoControl bool m_bErrorReported; public: + static void globalInit(); bool sendingAllowed() { @@ -106,9 +110,11 @@ class CCryptoControl } private: - #ifdef SRT_ENABLE_ENCRYPTION - void regenCryptoKm(bool sendit, bool bidirectional); + /// Regenerate cryptographic key material. + /// @param[in] sock If not null, the socket will be used to send the KM message to the peer (e.g. KM refresh). + /// @param[in] bidirectional If true, the key material will be regenerated for both directions (receiver and sender). + void regenCryptoKm(CUDT* sock, bool bidirectional); #endif public: @@ -119,7 +125,8 @@ class CCryptoControl void updateKmState(int cmd, size_t srtlen); // Detailed processing - int processSrtMsg_KMREQ(const uint32_t* srtdata, size_t len, uint32_t* srtdata_out, ref_t r_srtlen, int hsv); + int processSrtMsg_KMREQ(const uint32_t* srtdata, size_t len, int hsv, + uint32_t srtdata_out[], size_t&); // This returns: // 1 - the given payload is the same as the currently used key @@ -158,18 +165,18 @@ class CCryptoControl void getKmMsg_markSent(size_t ki, bool runtime) { #if ENABLE_LOGGING - using srt_logging::mglog; + using srt_logging::cnlog; #endif - m_SndKmLastTime = CTimer::getTime(); + m_SndKmLastTime = sync::steady_clock::now(); if (runtime) { m_SndKmMsg[ki].iPeerRetry--; - HLOGC(mglog.Debug, log << "getKmMsg_markSent: key[" << ki << "]: len=" << m_SndKmMsg[ki].MsgLen << " retry=" << m_SndKmMsg[ki].iPeerRetry); + HLOGC(cnlog.Debug, log << "getKmMsg_markSent: key[" << ki << "]: len=" << m_SndKmMsg[ki].MsgLen << " retry=" << m_SndKmMsg[ki].iPeerRetry); } else { - HLOGC(mglog.Debug, log << "getKmMsg_markSent: key[" << ki << "]: len=" << m_SndKmMsg[ki].MsgLen << " STILL IN USE."); + HLOGC(cnlog.Debug, log << "getKmMsg_markSent: key[" << ki << "]: len=" << m_SndKmMsg[ki].MsgLen << " STILL IN USE."); } } @@ -191,25 +198,24 @@ class CCryptoControl return false; } - CCryptoControl(CUDT* parent, SRTSOCKET id); + CCryptoControl(SRTSOCKET id); // DEBUG PURPOSES: std::string CONID() const; std::string FormatKmMessage(std::string hdr, int cmd, size_t srtlen); - bool init(HandshakeSide, bool); + bool init(HandshakeSide, const CSrtConfig&, bool); void close(); - // This function is used in: - // - HSv4 (initial key material exchange - in HSv5 it's attached to handshake) - // - case of key regeneration, which should be then exchanged again - void sendKeysToPeer(Whether2RegenKm regen); - + /// @return True if the handshake is in progress. + /// This function is used in: + /// - HSv4 (initial key material exchange - in HSv5 it's attached to handshake) + /// - case of key regeneration, which should be then exchanged again. + void sendKeysToPeer(CUDT* sock, int iSRTT, Whether2RegenKm regen); void setCryptoSecret(const HaiCrypt_Secret& secret) { m_KmSecret = secret; - //memcpy(&m_KmSecret, &secret, sizeof(m_KmSecret)); } void setCryptoKeylen(size_t keylen) @@ -218,7 +224,7 @@ class CCryptoControl m_iRcvKmKeyLen = keylen; } - bool createCryptoCtx(ref_t rh, size_t keylen, HaiCrypt_CryptoDir tx); + bool createCryptoCtx(size_t keylen, HaiCrypt_CryptoDir tx, HaiCrypt_Handle& rh); int getSndCryptoFlags() const { @@ -253,16 +259,18 @@ class CCryptoControl /// the encryption will fail. /// XXX Encryption flags in the PH_MSGNO /// field in the header must be correctly set before calling. - EncryptionStatus encrypt(ref_t r_packet); + EncryptionStatus encrypt(CPacket& w_packet); /// Decrypts the packet. If the packet has ENCKEYSPEC part /// in PH_MSGNO set to EK_NOENC, it does nothing. It decrypts /// only if the encryption correctly configured, otherwise it /// fails. After successful decryption, the ENCKEYSPEC part // in PH_MSGNO is set to EK_NOENC. - EncryptionStatus decrypt(ref_t r_packet); + EncryptionStatus decrypt(CPacket& w_packet); ~CCryptoControl(); }; +} // namespace srt + #endif // SRT_CONGESTION_CONTROL_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp b/trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp index 84a63c854c..37d1d30dad 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp @@ -50,37 +50,35 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifdef LINUX - #include - #include -#endif -#if __APPLE__ - #include "TargetConditionals.h" -#endif -#if defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) - #include - #include - #include - #include -#endif -#if defined(__ANDROID__) || defined(ANDROID) - #include -#endif +#define SRT_IMPORT_EVENT +#include "platform_sys.h" + #include #include #include #include +#if defined(__FreeBSD_kernel__) +#include +#endif + #include "common.h" #include "epoll.h" #include "logging.h" #include "udt.h" using namespace std; +using namespace srt::sync; + +#if ENABLE_HEAVY_LOGGING +namespace srt { +static ostream& PrintEpollEvent(ostream& os, int events, int et_events = 0); +} +#endif namespace srt_logging { -extern Logger mglog; + extern Logger eilog, ealog; } using namespace srt_logging; @@ -89,20 +87,21 @@ using namespace srt_logging; #define IF_DIRNAME(tested, flag, name) (tested & flag ? name : "") #endif -CEPoll::CEPoll(): +srt::CEPoll::CEPoll(): m_iIDSeed(0) { - CGuard::createMutex(m_EPollLock); + // Exception -> CUDTUnited ctor. + setupMutex(m_EPollLock, "EPoll"); } -CEPoll::~CEPoll() +srt::CEPoll::~CEPoll() { - CGuard::releaseMutex(m_EPollLock); + releaseMutex(m_EPollLock); } -int CEPoll::create() +int srt::CEPoll::create(CEPollDesc** pout) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); if (++ m_iIDSeed >= 0x7FFFFFFF) m_iIDSeed = 0; @@ -114,7 +113,31 @@ int CEPoll::create() int localid = 0; #ifdef LINUX - localid = epoll_create(1024); + + // NOTE: epoll_create1() and EPOLL_CLOEXEC were introduced in GLIBC-2.9. + // So earlier versions of GLIBC, must use epoll_create() and set + // FD_CLOEXEC on the file descriptor returned by it after the fact. + #if defined(EPOLL_CLOEXEC) + int flags = 0; + #if ENABLE_SOCK_CLOEXEC + flags |= EPOLL_CLOEXEC; + #endif + localid = epoll_create1(flags); + #else + localid = epoll_create(1); + #if ENABLE_SOCK_CLOEXEC + if (localid != -1) + { + int fdFlags = fcntl(localid, F_GETFD); + if (fdFlags != -1) + { + fdFlags |= FD_CLOEXEC; + fcntl(localid, F_SETFD, fdFlags); + } + } + #endif + #endif + /* Possible reasons of -1 error: EMFILE: The per-user limit on the number of epoll instances imposed by /proc/sys/fs/epoll/max_user_instances was encountered. ENFILE: The system limit on the total number of open files has been reached. @@ -122,25 +145,82 @@ ENOMEM: There was insufficient memory to create the kernel object. */ if (localid < 0) throw CUDTException(MJ_SETUP, MN_NONE, errno); - #elif defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) + #elif defined(BSD) || TARGET_OS_MAC localid = kqueue(); if (localid < 0) throw CUDTException(MJ_SETUP, MN_NONE, errno); #else - // on Solaris, use /dev/poll + // TODO: Solaris, use port_getn() + // https://docs.oracle.com/cd/E86824_01/html/E54766/port-get-3c.html // on Windows, select #endif pair::iterator, bool> res = m_mPolls.insert(make_pair(m_iIDSeed, CEPollDesc(m_iIDSeed, localid))); if (!res.second) // Insertion failed (no memory?) throw CUDTException(MJ_SETUP, MN_NONE); + if (pout) + *pout = &res.first->second; return m_iIDSeed; } -int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) +int srt::CEPoll::clear_usocks(int eid) +{ + // This should remove all SRT sockets from given eid. + ScopedLock pg (m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + + CEPollDesc& d = p->second; + + d.clearAll(); + + return 0; +} + + +void srt::CEPoll::clear_ready_usocks(CEPollDesc& d, int direction) +{ + if ((direction & ~SRT_EPOLL_EVENTTYPES) != 0) + { + // This is internal function, so simply report an IPE on incorrect usage. + LOGC(eilog.Error, log << "CEPoll::clear_ready_usocks: IPE, event flags exceed event types: " << direction); + return; + } + ScopedLock pg (m_EPollLock); + + vector cleared; + + CEPollDesc::enotice_t::iterator i = d.enotice_begin(); + while (i != d.enotice_end()) + { + IF_HEAVY_LOGGING(SRTSOCKET subsock = i->fd); + SRTSOCKET rs = d.clearEventSub(i++, direction); + // This function returns: + // - a valid socket - if there are no other subscription after 'direction' was cleared + // - SRT_INVALID_SOCK otherwise + // Valid sockets should be collected as sockets that no longer + // have a subscribed event should be deleted from subscriptions. + if (rs != SRT_INVALID_SOCK) + { + HLOGC(eilog.Debug, log << "CEPoll::clear_ready_usocks: @" << rs << " got all subscription cleared"); + cleared.push_back(rs); + } + else + { + HLOGC(eilog.Debug, log << "CEPoll::clear_ready_usocks: @" << subsock << " is still subscribed"); + } + } + + for (size_t i = 0; i < cleared.size(); ++i) + d.removeSubscription(cleared[i]); +} + +int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) @@ -155,18 +235,18 @@ int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) else { ev.events = 0; - if (*events & UDT_EPOLL_IN) + if (*events & SRT_EPOLL_IN) ev.events |= EPOLLIN; - if (*events & UDT_EPOLL_OUT) + if (*events & SRT_EPOLL_OUT) ev.events |= EPOLLOUT; - if (*events & UDT_EPOLL_ERR) + if (*events & SRT_EPOLL_ERR) ev.events |= EPOLLERR; } ev.data.fd = s; if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_ADD, s, &ev) < 0) throw CUDTException(); -#elif defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) +#elif defined(BSD) || TARGET_OS_MAC struct kevent ke[2]; int num = 0; @@ -177,11 +257,11 @@ int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) } else { - if (*events & UDT_EPOLL_IN) + if (*events & SRT_EPOLL_IN) { EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); } - if (*events & UDT_EPOLL_OUT) + if (*events & SRT_EPOLL_OUT) { EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL); } @@ -190,6 +270,10 @@ int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) throw CUDTException(); #else + // fake use 'events' to prevent warning. Remove when implemented. + (void)events; + (void)s; + #ifdef _MSC_VER // Microsoft Visual Studio doesn't support the #warning directive - nonstandard anyway. // Use #pragma message with the same text. @@ -206,9 +290,9 @@ int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) return 0; } -int CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) +int srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) @@ -218,7 +302,7 @@ int CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) epoll_event ev; // ev is ignored, for compatibility with old Linux kernel only. if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_DEL, s, &ev) < 0) throw CUDTException(); -#elif defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) +#elif defined(BSD) || TARGET_OS_MAC struct kevent ke; // @@ -237,9 +321,10 @@ int CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) } // Need this to atomically modify polled events (ex: remove write/keep read) -int CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) +int srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); + IF_HEAVY_LOGGING(ostringstream evd); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) @@ -250,9 +335,12 @@ int CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) int32_t evts = events ? *events : uint32_t(SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR); bool edgeTriggered = evts & SRT_EPOLL_ET; evts &= ~SRT_EPOLL_ET; + + // et_evts = all events, if SRT_EPOLL_ET, or only those that are always ET otherwise. + int32_t et_evts = edgeTriggered ? evts : evts & SRT_EPOLL_ETONLY; if (evts) { - pair iter_new = d.addWatch(u, evts, edgeTriggered); + pair iter_new = d.addWatch(u, evts, et_evts); CEPollDesc::Wait& wait = iter_new.first->second; if (!iter_new.second) { @@ -260,6 +348,7 @@ int CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) // parameter, but others are probably unchanged. Change them // forcefully and take out notices that are no longer valid. const int removable = wait.watch & ~evts; + IF_HEAVY_LOGGING(PrintEpollEvent(evd, evts & (~wait.watch))); // Check if there are any events that would be removed. // If there are no removed events watched (for example, when @@ -272,11 +361,16 @@ int CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) // Update the watch configuration, including edge wait.watch = evts; - if (edgeTriggered) - wait.edge = evts; + wait.edge = et_evts; // Now it should look exactly like newly added // and the state is also updated + HLOGC(ealog.Debug, log << "srt_epoll_update_usock: UPDATED E" << eid << " for @" << u << " +" << evd.str()); + } + else + { + IF_HEAVY_LOGGING(PrintEpollEvent(evd, evts)); + HLOGC(ealog.Debug, log << "srt_epoll_update_usock: ADDED E" << eid << " for @" << u << " " << evd.str()); } const int newstate = wait.watch & wait.state; @@ -287,20 +381,21 @@ int CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) } else if (edgeTriggered) { - // Specified only SRT_EPOLL_ET flag, but no event flag. Error. + LOGC(ealog.Error, log << "srt_epoll_update_usock: Specified only SRT_EPOLL_ET flag, but no event flag. Error."); throw CUDTException(MJ_NOTSUP, MN_INVAL); } else { // Update with no events means to remove subscription + HLOGC(ealog.Debug, log << "srt_epoll_update_usock: REMOVED E" << eid << " socket @" << u); d.removeSubscription(u); } return 0; } -int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) +int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) @@ -315,18 +410,18 @@ int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) else { ev.events = 0; - if (*events & UDT_EPOLL_IN) + if (*events & SRT_EPOLL_IN) ev.events |= EPOLLIN; - if (*events & UDT_EPOLL_OUT) + if (*events & SRT_EPOLL_OUT) ev.events |= EPOLLOUT; - if (*events & UDT_EPOLL_ERR) + if (*events & SRT_EPOLL_ERR) ev.events |= EPOLLERR; } ev.data.fd = s; if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_MOD, s, &ev) < 0) throw CUDTException(); -#elif defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) +#elif defined(BSD) || TARGET_OS_MAC struct kevent ke[2]; int num = 0; @@ -345,17 +440,23 @@ int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) } else { - if (*events & UDT_EPOLL_IN) + if (*events & SRT_EPOLL_IN) { EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); } - if (*events & UDT_EPOLL_OUT) + if (*events & SRT_EPOLL_OUT) { EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL); } } if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0) throw CUDTException(); +#else + + // fake use 'events' to prevent warning. Remove when implemented. + (void)events; + (void)s; + #endif // Assuming add is used if not inserted // p->second.m_sLocals.insert(s); @@ -363,9 +464,9 @@ int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) return 0; } -int CEPoll::setflags(const int eid, int32_t flags) +int srt::CEPoll::setflags(const int eid, int32_t flags) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); @@ -388,7 +489,7 @@ int CEPoll::setflags(const int eid, int32_t flags) return oflags; } -int CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) +int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { // It is allowed to call this function witn fdsSize == 0 // and therefore also NULL fdsSet. This will then only report @@ -396,12 +497,12 @@ int CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t m if (fdsSize < 0 || (fdsSize > 0 && !fdsSet)) throw CUDTException(MJ_NOTSUP, MN_INVAL); - int64_t entertime = CTimer::getTime(); + steady_clock::time_point entertime = steady_clock::now(); while (true) { { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); @@ -410,7 +511,7 @@ int CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t m if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty()) { // Empty EID is not allowed, report error. - throw CUDTException(MJ_NOTSUP, MN_INVAL); + throw CUDTException(MJ_NOTSUP, MN_EEMPTY); } if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK) && (fdsSet == NULL || fdsSize == 0)) @@ -444,16 +545,16 @@ int CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t m return total; } - if ((msTimeOut >= 0) && (int64_t(CTimer::getTime() - entertime) >= msTimeOut * int64_t(1000))) + if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) break; // official wait does: throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); - CTimer::waitForEvent(); + CGlobEvent::waitForEvent(); } return 0; } -int CEPoll::wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) +int srt::CEPoll::wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) { // if all fields is NULL and waiting time is infinite, then this would be a deadlock if (!readfds && !writefds && !lrfds && !lwfds && (msTimeOut < 0)) @@ -467,18 +568,16 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd int total = 0; - int64_t entertime = CTimer::getTime(); - - HLOGC(mglog.Debug, log << "CEPoll::wait: START for eid=" << eid); - + srt::sync::steady_clock::time_point entertime = srt::sync::steady_clock::now(); while (true) { { - CGuard epollock(m_EPollLock); + ScopedLock epollock(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) { + LOGC(ealog.Error, log << "EID:" << eid << " INVALID."); throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); } @@ -487,7 +586,9 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty() && ed.m_sLocals.empty()) { // Empty EID is not allowed, report error. - throw CUDTException(MJ_NOTSUP, MN_INVAL); + //throw CUDTException(MJ_NOTSUP, MN_INVAL); + LOGC(ealog.Error, log << "EID:" << eid << " no sockets to check, this would deadlock"); + throw CUDTException(MJ_NOTSUP, MN_EEMPTY, 0); } if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK)) @@ -507,13 +608,13 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd { ++it_next; IF_HEAVY_LOGGING(++total_noticed); - if (readfds && ((it->events & UDT_EPOLL_IN) || (it->events & UDT_EPOLL_ERR))) + if (readfds && ((it->events & SRT_EPOLL_IN) || (it->events & SRT_EPOLL_ERR))) { if (readfds->insert(it->fd).second) ++total; } - if (writefds && ((it->events & UDT_EPOLL_OUT) || (it->events & UDT_EPOLL_ERR))) + if (writefds && ((it->events & SRT_EPOLL_OUT) || (it->events & SRT_EPOLL_ERR))) { if (writefds->insert(it->fd).second) ++total; @@ -530,13 +631,14 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd } } - HLOGC(mglog.Debug, log << "CEPoll::wait: REPORTED " << total << "/" << total_noticed + HLOGC(ealog.Debug, log << "CEPoll::wait: REPORTED " << total << "/" << total_noticed << debug_sockets.str()); - if (lrfds || lwfds) + if ((lrfds || lwfds) && !ed.m_sLocals.empty()) { #ifdef LINUX const int max_events = ed.m_sLocals.size(); + SRT_ASSERT(max_events > 0); epoll_event ev[max_events]; int nfds = ::epoll_wait(ed.m_iLocalID, ev, max_events, 0); @@ -554,11 +656,12 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd ++ total; } } - HLOGC(mglog.Debug, log << "CEPoll::wait: LINUX: picking up " << (total - prev_total) << " ready fds."); + HLOGC(ealog.Debug, log << "CEPoll::wait: LINUX: picking up " << (total - prev_total) << " ready fds."); -#elif defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) +#elif defined(BSD) || TARGET_OS_MAC struct timespec tmout = {0, 0}; const int max_events = ed.m_sLocals.size(); + SRT_ASSERT(max_events > 0); struct kevent ke[max_events]; int nfds = kevent(ed.m_iLocalID, NULL, 0, ke, max_events, &tmout); @@ -578,7 +681,7 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd } } - HLOGC(mglog.Debug, log << "CEPoll::wait: Darwin/BSD: picking up " << (total - prev_total) << " ready fds."); + HLOGC(ealog.Debug, log << "CEPoll::wait: Darwin/BSD: picking up " << (total - prev_total) << " ready fds."); #else //currently "select" is used for all non-Linux platforms. @@ -623,34 +726,127 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd } } - HLOGC(mglog.Debug, log << "CEPoll::wait: select(otherSYS): picking up " << (total - prev_total) << " ready fds."); + HLOGC(ealog.Debug, log << "CEPoll::wait: select(otherSYS): picking up " << (total - prev_total) << " ready fds."); #endif } } // END-LOCK: m_EPollLock - HLOGC(mglog.Debug, log << "CEPoll::wait: Total of " << total << " READY SOCKETS"); + HLOGC(ealog.Debug, log << "CEPoll::wait: Total of " << total << " READY SOCKETS"); if (total > 0) return total; - if ((msTimeOut >= 0) && (int64_t(CTimer::getTime() - entertime) >= msTimeOut * int64_t(1000))) + if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) { - HLOGP(mglog.Debug, "... not waiting longer - timeout"); + HLOGC(ealog.Debug, log << "EID:" << eid << ": TIMEOUT."); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } - CTimer::EWait wt ATR_UNUSED = CTimer::waitForEvent(); - HLOGC(mglog.Debug, log << "CEPoll::wait: EVENT WAITING: " - << (wt == CTimer::WT_TIMEOUT ? "CHECKPOINT" : wt == CTimer::WT_EVENT ? "TRIGGERED" : "ERROR")); + const bool wait_signaled SRT_ATR_UNUSED = CGlobEvent::waitForEvent(); + HLOGC(ealog.Debug, log << "CEPoll::wait: EVENT WAITING: " + << (wait_signaled ? "TRIGGERED" : "CHECKPOINT")); } return 0; } -int CEPoll::release(const int eid) +int srt::CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut, bool report_by_exception) { - CGuard pg(m_EPollLock); + { + ScopedLock lg (m_EPollLock); + if (!d.flags(SRT_EPOLL_ENABLE_EMPTY) && d.watch_empty() && msTimeOut < 0) + { + // no socket is being monitored, this may be a deadlock + LOGC(ealog.Error, log << "EID:" << d.m_iID << " no sockets to check, this would deadlock"); + if (report_by_exception) + throw CUDTException(MJ_NOTSUP, MN_EEMPTY, 0); + return -1; + } + } + + st.clear(); + + steady_clock::time_point entertime = steady_clock::now(); + while (true) + { + { + // Not extracting separately because this function is + // for internal use only and we state that the eid could + // not be deleted or changed the target CEPollDesc in the + // meantime. + + // Here we only prevent the pollset be updated simultaneously + // with unstable reading. + ScopedLock lg (m_EPollLock); + + if (!d.flags(SRT_EPOLL_ENABLE_EMPTY) && d.watch_empty()) + { + // Empty EID is not allowed, report error. + throw CUDTException(MJ_NOTSUP, MN_EEMPTY); + } + + if (!d.m_sLocals.empty()) + { + // XXX Add error log + // uwait should not be used with EIDs subscribed to system sockets + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + + bool empty = d.enotice_empty(); + + if (!empty || msTimeOut == 0) + { + IF_HEAVY_LOGGING(ostringstream singles); + // If msTimeOut == 0, it means that we need the information + // immediately, we don't want to wait. Therefore in this case + // report also when none is ready. + int total = 0; // This is a list, so count it during iteration + CEPollDesc::enotice_t::iterator i = d.enotice_begin(); + while (i != d.enotice_end()) + { + ++total; + st[i->fd] = i->events; + IF_HEAVY_LOGGING(singles << "@" << i->fd << ":"); + IF_HEAVY_LOGGING(PrintEpollEvent(singles, i->events, i->parent->edgeOnly())); + const bool edged SRT_ATR_UNUSED = d.checkEdge(i++); // NOTE: potentially deletes `i` + IF_HEAVY_LOGGING(singles << (edged ? "<^> " : " ")); + } + + // Logging into 'singles' because it notifies as to whether + // the edge-triggered event has been cleared + HLOGC(ealog.Debug, log << "E" << d.m_iID << " rdy=" << total << ": " + << singles.str() + << " TRACKED: " << d.DisplayEpollWatch()); + return total; + } + // Don't report any updates because this check happens + // extremely often. + } + + if ((msTimeOut >= 0) && ((steady_clock::now() - entertime) >= microseconds_from(msTimeOut * int64_t(1000)))) + { + HLOGC(ealog.Debug, log << "EID:" << d.m_iID << ": TIMEOUT."); + if (report_by_exception) + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + return 0; // meaning "none is ready" + } + + CGlobEvent::waitForEvent(); + } + + return 0; +} + +bool srt::CEPoll::empty(const CEPollDesc& d) const +{ + ScopedLock lg (m_EPollLock); + return d.watch_empty(); +} + +int srt::CEPoll::release(const int eid) +{ + ScopedLock pg(m_EPollLock); map::iterator i = m_mPolls.find(eid); if (i == m_mPolls.end()) @@ -659,7 +855,7 @@ int CEPoll::release(const int eid) #ifdef LINUX // release local/system epoll descriptor ::close(i->second.m_iLocalID); - #elif defined(BSD) || defined(OSX) || (TARGET_OS_IOS == 1) || (TARGET_OS_TV == 1) + #elif defined(BSD) || TARGET_OS_MAC ::close(i->second.m_iLocalID); #endif @@ -669,16 +865,29 @@ int CEPoll::release(const int eid) } -int CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int events, const bool enable) +int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int events, const bool enable) { + // As event flags no longer contain only event types, check now. + if ((events & ~SRT_EPOLL_EVENTTYPES) != 0) + { + LOGC(eilog.Fatal, log << "epoll/update: IPE: 'events' parameter shall not contain special flags!"); + return -1; // still, ignored. + } + + int nupdated = 0; vector lost; - CGuard pg(m_EPollLock); + IF_HEAVY_LOGGING(ostringstream debug); + IF_HEAVY_LOGGING(debug << "epoll/update: @" << uid << " " << (enable ? "+" : "-")); + IF_HEAVY_LOGGING(PrintEpollEvent(debug, events)); + + ScopedLock pg (m_EPollLock); for (set::iterator i = eids.begin(); i != eids.end(); ++ i) { map::iterator p = m_mPolls.find(*i); if (p == m_mPolls.end()) { + HLOGC(eilog.Note, log << "epoll/update: E" << *i << " was deleted in the meantime"); // EID invalid, though still present in the socket's subscriber list // (dangling in the socket). Postpone to fix the subscruption and continue. lost.push_back(*i); @@ -692,9 +901,12 @@ int CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int e if (!pwait) { // As this is mapped in the socket's data, it should be impossible. + LOGC(eilog.Error, log << "epoll/update: IPE: update struck E" + << (*i) << " which is NOT SUBSCRIBED to @" << uid); continue; } + IF_HEAVY_LOGGING(string tracking = " TRACKING: " + ed.DisplayEpollWatch()); // compute new states // New state to be set into the permanent state @@ -704,13 +916,21 @@ int CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int e // compute states changes! int changes = pwait->state ^ newstate; // oldState XOR newState if (!changes) + { + HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i) + << tracking << " NOT updated: no changes"); continue; // no changes! + } // assign new state pwait->state = newstate; // filter change relating what is watching changes &= pwait->watch; if (!changes) + { + HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i) + << tracking << " NOT updated: not subscribed"); continue; // no change watching + } // set events changes! // This function will update the notice object associated with @@ -718,10 +938,75 @@ int CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int e // - if enable, it will set event flags, possibly in a new notice object // - if !enable, it will clear event flags, possibly remove notice if resulted in 0 ed.updateEventNotice(*pwait, uid, events, enable); + ++nupdated; + + HLOGC(eilog.Debug, log << debug.str() << ": E" << (*i) + << " TRACKING: " << ed.DisplayEpollWatch()); } for (vector::iterator i = lost.begin(); i != lost.end(); ++ i) eids.erase(*i); - return 0; + return nupdated; } + +// Debug use only. +#if ENABLE_HEAVY_LOGGING +namespace srt +{ + +static ostream& PrintEpollEvent(ostream& os, int events, int et_events) +{ + static pair const namemap [] = { + make_pair(SRT_EPOLL_IN, "R"), + make_pair(SRT_EPOLL_OUT, "W"), + make_pair(SRT_EPOLL_ERR, "E"), + make_pair(SRT_EPOLL_UPDATE, "U") + }; + + int N = Size(namemap); + + for (int i = 0; i < N; ++i) + { + if (events & namemap[i].first) + { + os << "["; + if (et_events & namemap[i].first) + os << "^"; + os << namemap[i].second << "]"; + } + } + + return os; +} + +string DisplayEpollResults(const std::map& sockset) +{ + typedef map fmap_t; + ostringstream os; + for (fmap_t::const_iterator i = sockset.begin(); i != sockset.end(); ++i) + { + os << "@" << i->first << ":"; + PrintEpollEvent(os, i->second); + os << " "; + } + + return os.str(); +} + +string CEPollDesc::DisplayEpollWatch() +{ + ostringstream os; + for (ewatch_t::const_iterator i = m_USockWatchState.begin(); i != m_USockWatchState.end(); ++i) + { + os << "@" << i->first << ":"; + PrintEpollEvent(os, i->second.watch, i->second.edge); + os << " "; + } + + return os.str(); +} + +} // namespace srt + +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/epoll.h b/trunk/3rdparty/srt-1-fit/srtcore/epoll.h old mode 100755 new mode 100644 index 1d0463ffcb..7b0d941c8d --- a/trunk/3rdparty/srt-1-fit/srtcore/epoll.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/epoll.h @@ -50,8 +50,8 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_EPOLL_H__ -#define __UDT_EPOLL_H__ +#ifndef INC_SRT_EPOLL_H +#define INC_SRT_EPOLL_H #include @@ -59,8 +59,15 @@ modified by #include #include "udt.h" +namespace srt +{ + +class CUDT; +class CRendezvousQueue; +class CUDTGroup; + -struct CEPollDesc +class CEPollDesc { const int m_iID; // epoll ID @@ -86,40 +93,62 @@ struct CEPollDesc { /// Events the subscriber is interested with. Only those will be /// regarded when updating event flags. - int watch; + int32_t watch; /// Which events should be edge-triggered. When the event isn't /// mentioned in `watch`, this bit flag is disregarded. Otherwise /// it means that the event is to be waited for persistent state /// if this flag is not present here, and for edge trigger, if /// the flag is present here. - int edge; + int32_t edge; /// The current persistent state. This is usually duplicated in /// a dedicated state object in `m_USockEventNotice`, however the state /// here will stay forever as is, regardless of the edge/persistent /// subscription mode for the event. - int state; + int32_t state; /// The iterator to `m_USockEventNotice` container that contains the /// event notice object for this subscription, or the value from /// `nullNotice()` if there is no such object. enotice_t::iterator notit; - Wait(int sub, bool etr, enotice_t::iterator i) + Wait(explicit_t sub, explicit_t etr, enotice_t::iterator i) :watch(sub) - ,edge(etr ? sub : 0) + ,edge(etr) ,state(0) ,notit(i) { } int edgeOnly() { return edge & watch; } + + /// Clear all flags for given direction from the notices + /// and subscriptions, and checks if this made the event list + /// for this watch completely empty. + /// @param direction event type that has to be cleared + /// @return true, if this cleared the last event (the caller + /// want to remove the subscription for this socket) + bool clear(int32_t direction) + { + if (watch & direction) + { + watch &= ~direction; + edge &= ~direction; + state &= ~direction; + + return watch == 0; + } + + return false; + } }; typedef std::map ewatch_t; -private: +#if ENABLE_HEAVY_LOGGING +std::string DisplayEpollWatch(); +#endif /// Sockets that are subscribed for events in this eid. ewatch_t m_USockWatchState; @@ -135,7 +164,10 @@ struct CEPollDesc enotice_t::iterator nullNotice() { return m_USockEventNotice.end(); } -public: + // Only CEPoll class should have access to it. + // Guarding private access to the class is not necessary + // within the epoll module. + friend class CEPoll; CEPollDesc(int id, int localID) : m_iID(id) @@ -147,13 +179,13 @@ struct CEPollDesc static const int32_t EF_NOCHECK_EMPTY = 1 << 0; static const int32_t EF_CHECK_REP = 1 << 1; - int32_t flags() { return m_Flags; } - bool flags(int32_t f) { return (m_Flags & f) != 0; } + int32_t flags() const { return m_Flags; } + bool flags(int32_t f) const { return (m_Flags & f) != 0; } void set_flags(int32_t flg) { m_Flags |= flg; } void clr_flags(int32_t flg) { m_Flags &= ~flg; } // Container accessors for ewatch_t. - bool watch_empty() { return m_USockWatchState.empty(); } + bool watch_empty() const { return m_USockWatchState.empty(); } Wait* watch_find(SRTSOCKET sock) { ewatch_t::iterator i = m_USockWatchState.find(sock); @@ -165,13 +197,16 @@ struct CEPollDesc // Container accessors for enotice_t. enotice_t::iterator enotice_begin() { return m_USockEventNotice.begin(); } enotice_t::iterator enotice_end() { return m_USockEventNotice.end(); } + enotice_t::const_iterator enotice_begin() const { return m_USockEventNotice.begin(); } + enotice_t::const_iterator enotice_end() const { return m_USockEventNotice.end(); } + bool enotice_empty() const { return m_USockEventNotice.empty(); } const int m_iLocalID; // local system epoll ID std::set m_sLocals; // set of local (non-UDT) descriptors - std::pair addWatch(SRTSOCKET sock, int32_t events, bool edgeTrg) + std::pair addWatch(SRTSOCKET sock, explicit_t events, explicit_t et_events) { - return m_USockWatchState.insert(std::make_pair(sock, Wait(events, edgeTrg, nullNotice()))); + return m_USockWatchState.insert(std::make_pair(sock, Wait(events, et_events, nullNotice()))); } void addEventNotice(Wait& wait, SRTSOCKET sock, int events) @@ -224,6 +259,12 @@ struct CEPollDesc m_USockWatchState.erase(i); } + void clearAll() + { + m_USockEventNotice.clear(); + m_USockWatchState.clear(); + } + void removeExistingNotices(Wait& wait) { m_USockEventNotice.erase(wait.notit); @@ -281,12 +322,44 @@ struct CEPollDesc } return false; } + + /// This should work in a loop around the notice container of + /// the given eid container and clear out the notice for + /// particular event type. If this has cleared effectively the + /// last existing event, it should return the socket id + /// so that the caller knows to remove it also from subscribers. + /// + /// @param i iterator in the notice container + /// @param event event type to be cleared + /// @retval (socket) Socket to be removed from subscriptions + /// @retval SRT_INVALID_SOCK Nothing to be done (associated socket + /// still has other subscriptions) + SRTSOCKET clearEventSub(enotice_t::iterator i, int event) + { + // We need to remove the notice and subscription + // for this event. The 'i' iterator is safe to + // delete, even indirectly. + + // This works merely like checkEdge, just on request to clear the + // identified event, if found. + if (i->events & event) + { + // The notice has a readiness flag on this event. + // This means that there exists also a subscription. + Wait* w = i->parent; + if (w->clear(event)) + return i->fd; + } + + return SRT_INVALID_SOCK; + } }; class CEPoll { -friend class CUDT; -friend class CRendezvousQueue; +friend class srt::CUDT; +friend class srt::CUDTGroup; +friend class srt::CRendezvousQueue; public: CEPoll(); @@ -294,90 +367,122 @@ friend class CRendezvousQueue; public: // for CUDTUnited API - /// create a new EPoll. - /// @return new EPoll ID if success, otherwise an error number. + /// create a new EPoll. + /// @return new EPoll ID if success, otherwise an error number. + int create(CEPollDesc** ppd = 0); - int create(); - /// add a UDT socket to an EPoll. - /// @param [in] eid EPoll ID. - /// @param [in] u UDT Socket ID. - /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. + /// delete all user sockets (SRT sockets) from an EPoll + /// @param [in] eid EPoll ID. + /// @return 0 + int clear_usocks(int eid); - int add_usock(const int eid, const SRTSOCKET& u, const int* events = NULL) { return update_usock(eid, u, events); } - - /// add a system socket to an EPoll. - /// @param [in] eid EPoll ID. - /// @param [in] s system Socket ID. - /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. + /// add a system socket to an EPoll. + /// @param [in] eid EPoll ID. + /// @param [in] s system Socket ID. + /// @param [in] events events to watch. + /// @return 0 if success, otherwise an error number. int add_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); - /// remove a UDT socket event from an EPoll; socket will be removed if no events to watch. - /// @param [in] eid EPoll ID. - /// @param [in] u UDT socket ID. - /// @return 0 if success, otherwise an error number. - - int remove_usock(const int eid, const SRTSOCKET& u) { static const int Null(0); return update_usock(eid, u, &Null);} - - /// remove a system socket event from an EPoll; socket will be removed if no events to watch. - /// @param [in] eid EPoll ID. - /// @param [in] s system socket ID. - /// @return 0 if success, otherwise an error number. + /// remove a system socket event from an EPoll; socket will be removed if no events to watch. + /// @param [in] eid EPoll ID. + /// @param [in] s system socket ID. + /// @return 0 if success, otherwise an error number. int remove_ssock(const int eid, const SYSSOCKET& s); - /// update a UDT socket events from an EPoll. - /// @param [in] eid EPoll ID. - /// @param [in] u UDT socket ID. - /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. + /// update a UDT socket events from an EPoll. + /// @param [in] eid EPoll ID. + /// @param [in] u UDT socket ID. + /// @param [in] events events to watch. + /// @return 0 if success, otherwise an error number. int update_usock(const int eid, const SRTSOCKET& u, const int* events); - /// update a system socket events from an EPoll. - /// @param [in] eid EPoll ID. - /// @param [in] u UDT socket ID. - /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. + /// update a system socket events from an EPoll. + /// @param [in] eid EPoll ID. + /// @param [in] u UDT socket ID. + /// @param [in] events events to watch. + /// @return 0 if success, otherwise an error number. int update_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); - /// wait for EPoll events or timeout. - /// @param [in] eid EPoll ID. - /// @param [out] readfds UDT sockets available for reading. - /// @param [out] writefds UDT sockets available for writing. - /// @param [in] msTimeOut timeout threshold, in milliseconds. - /// @param [out] lrfds system file descriptors for reading. - /// @param [out] lwfds system file descriptors for writing. - /// @return number of sockets available for IO. + /// wait for EPoll events or timeout. + /// @param [in] eid EPoll ID. + /// @param [out] readfds UDT sockets available for reading. + /// @param [out] writefds UDT sockets available for writing. + /// @param [in] msTimeOut timeout threshold, in milliseconds. + /// @param [out] lrfds system file descriptors for reading. + /// @param [out] lwfds system file descriptors for writing. + /// @return number of sockets available for IO. int wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds, std::set* lwfds); - /// wait for EPoll events or timeout optimized with explicit EPOLL_ERR event and the edge mode option. - /// @param [in] eid EPoll ID. - /// @param [out] fdsSet array of user socket events (SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR). - /// @param [int] fdsSize of fds array - /// @param [in] msTimeOut timeout threshold, in milliseconds. - /// @return total of available events in the epoll system (can be greater than fdsSize) + typedef std::map fmap_t; + + /// Lightweit and more internal-reaching version of `uwait` for internal use only. + /// This function wait for sockets to be ready and reports them in `st` map. + /// + /// @param d the internal structure of the epoll container + /// @param st output container for the results: { socket_type, event } + /// @param msTimeOut timeout after which return with empty output is allowed + /// @param report_by_exception if true, errors will result in exception intead of returning -1 + /// @retval -1 error occurred + /// @retval >=0 number of ready sockets (actually size of `st`) + int swait(CEPollDesc& d, fmap_t& st, int64_t msTimeOut, bool report_by_exception = true); + + /// Empty subscription check - for internal use only. + bool empty(const CEPollDesc& d) const; + + /// Reports which events are ready on the given socket. + /// @param mp socket event map retirned by `swait` + /// @param sock which socket to ask + /// @return event flags for given socket, or 0 if none + static int ready(const fmap_t& mp, SRTSOCKET sock) + { + fmap_t::const_iterator y = mp.find(sock); + if (y == mp.end()) + return 0; + return y->second; + } + + /// Reports whether socket is ready for given event. + /// @param mp socket event map retirned by `swait` + /// @param sock which socket to ask + /// @param event which events it should be ready for + /// @return true if the given socket is ready for given event + static bool isready(const fmap_t& mp, SRTSOCKET sock, SRT_EPOLL_OPT event) + { + return (ready(mp, sock) & event) != 0; + } + + // Could be a template directly, but it's now hidden in the imp file. + void clear_ready_usocks(CEPollDesc& d, int direction); + + /// wait for EPoll events or timeout optimized with explicit EPOLL_ERR event and the edge mode option. + /// @param [in] eid EPoll ID. + /// @param [out] fdsSet array of user socket events (SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR). + /// @param [int] fdsSize of fds array + /// @param [in] msTimeOut timeout threshold, in milliseconds. + /// @return total of available events in the epoll system (can be greater than fdsSize) int uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); - - /// close and release an EPoll. - /// @param [in] eid EPoll ID. - /// @return 0 if success, otherwise an error number. + + /// close and release an EPoll. + /// @param [in] eid EPoll ID. + /// @return 0 if success, otherwise an error number. int release(const int eid); public: // for CUDT to acknowledge IO status - /// Update events available for a UDT socket. - /// @param [in] uid UDT socket ID. - /// @param [in] eids EPoll IDs to be set - /// @param [in] events Combination of events to update - /// @param [in] enable true -> enable, otherwise disable - /// @return 0 if success, otherwise an error number + /// Update events available for a UDT socket. At the end this function + /// counts the number of updated EIDs with given events. + /// @param [in] uid UDT socket ID. + /// @param [in] eids EPoll IDs to be set + /// @param [in] events Combination of events to update + /// @param [in] enable true -> enable, otherwise disable + /// @return -1 if invalid events, otherwise the number of changes int update_events(const SRTSOCKET& uid, std::set& eids, int events, bool enable); @@ -385,11 +490,17 @@ friend class CRendezvousQueue; private: int m_iIDSeed; // seed to generate a new ID - pthread_mutex_t m_SeedLock; + srt::sync::Mutex m_SeedLock; std::map m_mPolls; // all epolls - pthread_mutex_t m_EPollLock; + mutable srt::sync::Mutex m_EPollLock; }; +#if ENABLE_HEAVY_LOGGING +std::string DisplayEpollResults(const std::map& sockset); +#endif + +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/fec.cpp b/trunk/3rdparty/srt-1-fit/srtcore/fec.cpp index b1e810e525..6b9981abbc 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/fec.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/fec.cpp @@ -8,11 +8,13 @@ * */ +#include "platform_sys.h" #include #include #include #include +#include #include "packetfilter.h" #include "core.h" @@ -21,9 +23,106 @@ #include "fec.h" +// Maximum allowed "history" remembered in the receiver groups. +// This is calculated in series, that is, this number will be +// multiplied by sizeRow() and sizeCol() to get the value being +// a maximum distance between the FEC group base sequence and +// the sequence to which a request comes in. + +// XXX Might be that this parameter should be configurable +#define SRT_FEC_MAX_RCV_HISTORY 10 + using namespace std; using namespace srt_logging; +namespace srt { + +const char FECFilterBuiltin::defaultConfig [] = "fec,rows:1,layout:staircase,arq:onreq"; + +struct StringKeys +{ + string operator()(const pair item) + { + return item.first; + } +}; + +bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error) +{ + string arspec = map_get(cfg.parameters, "layout"); + + if (arspec != "" && arspec != "even" && arspec != "staircase") + { + w_error = "value for 'layout' must be 'even' or 'staircase'"; + return false; + } + + string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows"); + + int out_rows = 1; + + if (colspec != "") + { + int out_cols = atoi(colspec.c_str()); + if (out_cols < 2) + { + w_error = "at least 'cols' must be specified and > 1"; + return false; + } + } + + if (rowspec != "") + { + out_rows = atoi(rowspec.c_str()); + if (out_rows >= -1 && out_rows < 1) + { + w_error = "'rows' must be >=1 or negative < -1"; + return false; + } + } + + // Extra interpret level, if found, default never. + // Check only those that are managed. + string level = map_get(cfg.parameters, "arq"); + if (level != "") + { + static const char* const levelnames [] = {"never", "onreq", "always"}; + size_t i = 0; + for (i = 0; i < Size(levelnames); ++i) + { + if (strcmp(level.c_str(), levelnames[i]) == 0) + break; + } + + if (i == Size(levelnames)) + { + w_error = "'arq' value '" + level + "' invalid. Allowed: never, onreq, always"; + return false; + } + } + + set keys; + transform(cfg.parameters.begin(), cfg.parameters.end(), inserter(keys, keys.begin()), StringKeys()); + + // Delete all default parameters + SrtFilterConfig defconf; + ParseFilterConfig(defaultConfig, (defconf)); + for (map::const_iterator i = defconf.parameters.begin(); + i != defconf.parameters.end(); ++i) + keys.erase(i->first); + + // Delete mandatory parameters + keys.erase("cols"); + + if (!keys.empty()) + { + w_error = "Extra parameters. Allowed only: cols, rows, layout, arq"; + return false; + } + + return true; +} + FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector &provided, const string &confstr) : SrtPacketFilterBase(init) , m_fallback_level(SRT_ARQ_ONREQ) @@ -33,6 +132,13 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector if (!ParseFilterConfig(confstr, cfg)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + string ermsg; + if (!verifyConfig(cfg, (ermsg))) + { + LOGC(pflog.Error, log << "IPE: Filter config failed: " << ermsg); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + // Configuration supported: // - row only (number_rows == 1) // - columns only, no row FEC/CTL (number_rows < -1) @@ -47,33 +153,23 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector string shorter = arspec.size() > 5 ? arspec.substr(0, 5) : arspec; if (shorter == "even") m_arrangement_staircase = false; - else if (shorter != "" && shorter != "stair") - { - LOGC(mglog.Error, log << "FILTER/FEC: CONFIG: value for 'layout' must be 'even' or 'staircase'"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows"); - int out_rows = 1; - int out_cols = atoi(colspec.c_str()); - - if (colspec == "" || out_cols < 2) + if (colspec == "") { - LOGC(mglog.Error, log << "FILTER/FEC: CONFIG: at least 'cols' must be specified and > 1"); + LOGC(pflog.Error, log << "FEC filter config: parameter 'cols' is mandatory"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } + int out_rows = 1; + int out_cols = atoi(colspec.c_str()); + m_number_cols = out_cols; if (rowspec != "") { out_rows = atoi(rowspec.c_str()); - if (out_rows >= -1 && out_rows < 1) - { - LOGC(mglog.Error, log << "FILTER/FEC: CONFIG: 'rows' must be >=1 or negative < -1"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } } if (out_rows < 0) @@ -99,17 +195,14 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector { if (level == levelnames[i]) { - lv = i; + lv = int(i); break; } } + } - if (lv == -1) - { - LOGC(mglog.Error, log << "FILTER/FEC: CONFIG: 'arq': value '" << level << "' unknown"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - + if (lv != -1) + { m_fallback_level = SRT_ARQLevel(lv); } else @@ -117,7 +210,6 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector m_fallback_level = SRT_ARQ_ONREQ; } - // Required to store in the header when rebuilding rcv.id = socketID(); @@ -163,7 +255,7 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector // Size: rows // Step: 1 (next packet in group is 1 past the previous one) // Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows') - HLOGC(mglog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row"); + HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row"); ConfigureGroup(snd.row, snd_isn, 1, sizeRow()); // In the beginning we need just one reception group. New reception @@ -171,7 +263,7 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector // The value of rcv.row[0].base will be used as an absolute base for calculating // the index of the group for a given received packet. rcv.rowq.resize(1); - HLOGP(mglog.Debug, "FEC: INIT: receiver first row"); + HLOGP(pflog.Debug, "FEC: INIT: receiver first row"); ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow()); if (sizeCol() > 1) @@ -180,9 +272,9 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector // Step: rows (the next packet in the group is one row later) // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down) - HLOGP(mglog.Debug, "FEC: INIT: sender first N columns"); + HLOGP(pflog.Debug, "FEC: INIT: sender first N columns"); ConfigureColumns(snd.cols, snd_isn); - HLOGP(mglog.Debug, "FEC: INIT: receiver first N columns"); + HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns"); ConfigureColumns(rcv.colq, rcv_isn); } @@ -211,7 +303,7 @@ void FECFilterBuiltin::ConfigureColumns(Container& which, int32_t isn) if (!m_arrangement_staircase) { - HLOGC(mglog.Debug, log << "ConfigureColumns: new " + HLOGC(pflog.Debug, log << "ConfigureColumns: new " << numberCols() << " columns, START AT: " << zero); // With even arrangement, just use a plain loop. // Initialize straight way all groups in the size. @@ -238,27 +330,27 @@ void FECFilterBuiltin::ConfigureColumns(Container& which, int32_t isn) // Start here. The 'isn' is still the absolute base sequence value. size_t offset = 0; - HLOGC(mglog.Debug, log << "ConfigureColumns: " << (which.size() - zero) + HLOGC(pflog.Debug, log << "ConfigureColumns: " << (which.size() - zero) << " columns, START AT: " << zero); for (size_t i = zero; i < which.size(); ++i) { - int32_t seq = CSeqNo::incseq(isn, offset); + int32_t seq = CSeqNo::incseq(isn, int(offset)); size_t col = i - zero; - HLOGC(mglog.Debug, log << "ConfigureColumns: [" << col << "]: -> ConfigureGroup..."); + HLOGC(pflog.Debug, log << "ConfigureColumns: [" << col << "]: -> ConfigureGroup..."); ConfigureGroup(which[i], seq, sizeRow(), sizeCol() * numberCols()); if (col % numberRows() == numberRows() - 1) { offset = col + 1; // +1 because we want it for the next column - HLOGC(mglog.Debug, log << "ConfigureColumns: [" << (col+1) << "]... (resetting to row 0: +" + HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "]... (resetting to row 0: +" << offset << " %" << CSeqNo::incseq(isn, offset) << ")"); } else { offset += 1 + sizeRow(); - HLOGC(mglog.Debug, log << "ConfigureColumns: [" << (col+1) << "] ... (continue +" + HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "] ... (continue +" << offset << " %" << CSeqNo::incseq(isn, offset) << ")"); } } @@ -281,7 +373,7 @@ void FECFilterBuiltin::ConfigureGroup(Group& g, int32_t seqno, size_t gstep, siz g.flag_clip = 0; g.timestamp_clip = 0; - HLOGC(mglog.Debug, log << "FEC: ConfigureGroup: base %" << seqno << " step=" << gstep << " drop=" << drop); + HLOGC(pflog.Debug, log << "FEC: ConfigureGroup: base %" << seqno << " step=" << gstep << " drop=" << drop); // Preallocate the buffer that will be used for storing it for // the needs of passing the data through the network. @@ -292,9 +384,9 @@ void FECFilterBuiltin::ConfigureGroup(Group& g, int32_t seqno, size_t gstep, siz void FECFilterBuiltin::ResetGroup(Group& g) { - int32_t new_seq_base = CSeqNo::incseq(g.base, g.drop); + const int32_t new_seq_base = CSeqNo::incseq(g.base, int(g.drop)); - HLOGC(mglog.Debug, log << "FEC: ResetGroup (step=" << g.step << "): base %" << g.base << " -> %" << new_seq_base); + HLOGC(pflog.Debug, log << "FEC: ResetGroup (step=" << g.step << "): base %" << g.base << " -> %" << new_seq_base); g.base = new_seq_base; g.collected = 0; @@ -323,7 +415,7 @@ void FECFilterBuiltin::feedSource(CPacket& packet) if (CheckGroupClose(snd.row, horiz_pos, sizeRow())) { - HLOGC(mglog.Debug, log << "FEC:... HORIZ group closed, B=%" << snd.row.base); + HLOGC(pflog.Debug, log << "FEC:... HORIZ group closed, B=%" << snd.row.base); } ClipPacket(snd.row, packet); snd.row.collected++; @@ -332,12 +424,12 @@ void FECFilterBuiltin::feedSource(CPacket& packet) if (sizeCol() < 2) { // The above logging instruction in case of no columns - HLOGC(mglog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() + HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " B:%" << baseoff << " H:*[" << horiz_pos << "]" << " size=" << packet.size() << " TS=" << packet.getMsgTimeStamp() << " !" << BufferStamp(packet.data(), packet.size())); - HLOGC(mglog.Debug, log << "FEC collected: H: " << snd.row.collected); + HLOGC(pflog.Debug, log << "FEC collected: H: " << snd.row.collected); return; } @@ -358,7 +450,7 @@ void FECFilterBuiltin::feedSource(CPacket& packet) // the future, and "this sequence" is in a group that is already closed. // In this case simply can't clip the packet in the column group. - HLOGC(mglog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " rowoff=" << baseoff + HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " rowoff=" << baseoff << " column=" << vert_gx << " .base=%" << vert_base << " coloff=" << vert_off); if (vert_off >= 0 && sizeCol() > 1) @@ -368,7 +460,7 @@ void FECFilterBuiltin::feedSource(CPacket& packet) // SANITY: check if the rule applies on the group if (vert_off % sizeRow()) { - LOGC(mglog.Fatal, log << "FEC:feedSource: IPE: VGroup #" << vert_gx << " base=%" << vert_base + LOGC(pflog.Fatal, log << "FEC:feedSource: IPE: VGroup #" << vert_gx << " base=%" << vert_base << " WRONG with horiz base=%" << base << "coloff(" << vert_off << ") % sizeRow(" << sizeRow() << ") = " << (vert_off % sizeRow())); @@ -376,9 +468,10 @@ void FECFilterBuiltin::feedSource(CPacket& packet) return; } - int vert_pos = vert_off / sizeRow(); + SRT_ASSERT(vert_off >= 0); + int vert_pos = vert_off / int(sizeRow()); - HLOGC(mglog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() + HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " B:%" << baseoff << " H:*[" << horiz_pos << "] V(B=%" << vert_base << ")[col=" << vert_gx << "][" << vert_pos << "/" << sizeCol() << "] " << " size=" << packet.size() @@ -396,7 +489,7 @@ void FECFilterBuiltin::feedSource(CPacket& packet) if (CheckGroupClose(snd.cols[vert_gx], vert_pos, sizeCol())) { - HLOGC(mglog.Debug, log << "FEC:... VERT group closed, B=%" << snd.cols[vert_gx].base); + HLOGC(pflog.Debug, log << "FEC:... VERT group closed, B=%" << snd.cols[vert_gx].base); } ClipPacket(snd.cols[vert_gx], packet); snd.cols[vert_gx].collected++; @@ -404,14 +497,14 @@ void FECFilterBuiltin::feedSource(CPacket& packet) else { - HLOGC(mglog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() + HLOGC(pflog.Debug, log << "FEC:feedSource: %" << packet.getSeqNo() << " B:%" << baseoff << " H:*[" << horiz_pos << "] V(B=%" << vert_base << ")[col=" << vert_gx << "]" << " size=" << packet.size() << " TS=" << packet.getMsgTimeStamp() << " !" << BufferStamp(packet.data(), packet.size())); } - HLOGC(mglog.Debug, log << "FEC collected: H: " << snd.row.collected << " V[" << vert_gx << "]: " << snd.cols[vert_gx].collected); + HLOGC(pflog.Debug, log << "FEC collected: H: " << snd.row.collected << " V[" << vert_gx << "]: " << snd.cols[vert_gx].collected); } bool FECFilterBuiltin::CheckGroupClose(Group& g, size_t pos, size_t size) @@ -428,7 +521,7 @@ void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt) // Both length and timestamp must be taken as NETWORK ORDER // before applying the clip. - uint16_t length_net = htons(pkt.size()); + uint16_t length_net = htons(uint16_t(pkt.size())); uint8_t kflg = uint8_t(pkt.getMsgCryptoFlags()); // NOTE: Unlike length, the TIMESTAMP is NOT endian-reordered @@ -439,7 +532,7 @@ void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt) ClipData(g, length_net, kflg, timestamp_hw, pkt.data(), pkt.size()); - HLOGC(mglog.Debug, log << "FEC DATA PKT CLIP: " << hex + HLOGC(pflog.Debug, log << "FEC DATA PKT CLIP: " << hex << "FLAGS=" << unsigned(kflg) << " LENGTH[ne]=" << (length_net) << " TS[he]=" << timestamp_hw << " CLIP STATE: FLAGS=" << unsigned(g.flag_clip) @@ -466,7 +559,7 @@ void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) ClipData(g, *length_clip, *flag_clip, timestamp_hw, payload, payload_clip_len); - HLOGC(mglog.Debug, log << "FEC/CTL CLIP: " << hex + HLOGC(pflog.Debug, log << "FEC/CTL CLIP: " << hex << "FLAGS=" << unsigned(*flag_clip) << " LENGTH[ne]=" << (*length_clip) << " TS[he]=" << timestamp_hw << " CLIP STATE: FLAGS=" << unsigned(g.flag_clip) @@ -477,7 +570,7 @@ void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) { - uint16_t length_net = htons(pkt.length); + uint16_t length_net = htons(uint16_t(pkt.length)); uint8_t kflg = MSGNO_ENCKEYSPEC::unwrap(pkt.hdr[SRT_PH_MSGNO]); // NOTE: Unlike length, the TIMESTAMP is NOT endian-reordered @@ -488,7 +581,7 @@ void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) ClipData(g, length_net, kflg, timestamp_hw, pkt.buffer, pkt.length); - HLOGC(mglog.Debug, log << "FEC REBUILT DATA CLIP: " << hex + HLOGC(pflog.Debug, log << "FEC REBUILT DATA CLIP: " << hex << "FLAGS=" << unsigned(kflg) << " LENGTH[ne]=" << (length_net) << " TS[he]=" << timestamp_hw << " CLIP STATE: FLAGS=" << unsigned(g.flag_clip) @@ -551,20 +644,20 @@ bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq) // means we don't use columns. if (m_number_rows <= 1) { - HLOGC(mglog.Debug, log << "FEC/CTL not checking VERT group - rows only config"); + HLOGC(pflog.Debug, log << "FEC/CTL not checking VERT group - rows only config"); // PASS ON to Horizontal group check } else { int offset_to_row_base = CSeqNo::seqoff(snd.row.base, seq); - int vert_gx = (offset_to_row_base + m_number_cols) % m_number_cols; + int vert_gx = (offset_to_row_base + int(m_number_cols)) % int(m_number_cols); // This can actually happen only for the very first sent packet. // It looks like "following the last packet from the previous group", // however there was no previous group because this is the first packet. if (offset_to_row_base < 0) { - HLOGC(mglog.Debug, log << "FEC/CTL not checking VERT group [" << vert_gx << "] - negative offset_to_row_base %" + HLOGC(pflog.Debug, log << "FEC/CTL not checking VERT group [" << vert_gx << "] - negative offset_to_row_base %" << snd.row.base << " -> %" << seq << " (" << offset_to_row_base << ") (collected " << snd.cols[abs(vert_gx)].collected << "/" << sizeCol() << ")"); // PASS ON to Horizontal group check @@ -573,7 +666,7 @@ bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq) { if (snd.cols[vert_gx].collected >= m_number_rows) { - HLOGC(mglog.Debug, log << "FEC/CTL ready for VERT group [" << vert_gx << "]: %" << seq + HLOGC(pflog.Debug, log << "FEC/CTL ready for VERT group [" << vert_gx << "]: %" << seq << " (base %" << snd.cols[vert_gx].base << ")"); // SHIP THE VERTICAL FEC packet. PackControl(snd.cols[vert_gx], vert_gx, rpkt, seq); @@ -583,7 +676,7 @@ bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq) return true; } - HLOGC(mglog.Debug, log << "FEC/CTL NOT ready for VERT group [" << vert_gx << "]: %" << seq + HLOGC(pflog.Debug, log << "FEC/CTL NOT ready for VERT group [" << vert_gx << "]: %" << seq << " (base %" << snd.cols[vert_gx].base << ")" << " - collected " << snd.cols[vert_gx].collected << "/" << m_number_rows); } @@ -593,11 +686,11 @@ bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq) { if (!m_cols_only) { - HLOGC(mglog.Debug, log << "FEC/CTL ready for HORIZ group: %" << seq << " (base %" << snd.row.base << ")"); + HLOGC(pflog.Debug, log << "FEC/CTL ready for HORIZ group: %" << seq << " (base %" << snd.row.base << ")"); // SHIP THE HORIZONTAL FEC packet. PackControl(snd.row, -1, rpkt, seq); - HLOGC(mglog.Debug, log << "...PACKET size=" << rpkt.length + HLOGC(pflog.Debug, log << "...PACKET size=" << rpkt.length << " TS=" << rpkt.hdr[SRT_PH_TIMESTAMP] << " !" << BufferStamp(rpkt.buffer, rpkt.length)); @@ -616,7 +709,7 @@ bool FECFilterBuiltin::packControlPacket(SrtPacket& rpkt, int32_t seq) } else { - HLOGC(mglog.Debug, log << "FEC/CTL NOT ready for HORIZ group: %" << seq + HLOGC(pflog.Debug, log << "FEC/CTL NOT ready for HORIZ group: %" << seq << " (base %" << snd.row.base << ")" << " - collected " << snd.row.collected << "/" << m_number_cols); } @@ -640,7 +733,7 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& #if ENABLE_DEBUG if (g.output_buffer.size() < total_size) { - LOGC(mglog.Fatal, log << "OUTPUT BUFFER TOO SMALL!"); + LOGC(pflog.Fatal, log << "OUTPUT BUFFER TOO SMALL!"); abort(); } #endif @@ -654,11 +747,11 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& out[off++] = g.flag_clip; // Ok, now the length clip - memcpy(out+off, &g.length_clip, sizeof g.length_clip); + memcpy((out + off), &g.length_clip, sizeof g.length_clip); off += sizeof g.length_clip; // And finally the payload clip - memcpy(out+off, &g.payload_clip[0], g.payload_clip.size()); + memcpy((out + off), &g.payload_clip[0], g.payload_clip.size()); // Ready. Now fill the header and finalize other data. pkt.length = total_size; @@ -666,7 +759,7 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& pkt.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip; pkt.hdr[SRT_PH_SEQNO] = seq; - HLOGC(mglog.Debug, log << "FEC: PackControl: hdr(" + HLOGC(pflog.Debug, log << "FEC: PackControl: hdr(" << (total_size - g.payload_clip.size()) << "): INDEX=" << int(index) << " LENGTH[ne]=" << hex << g.length_clip << " FLAGS=" << int(g.flag_clip) << " TS=" << g.timestamp_clip @@ -703,7 +796,7 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) // matrix dismissal FIRST before this packet is going to be handled. CheckLargeDrop(rpkt.getSeqNo()); - if (rpkt.getMsgSeq() == 0) + if (rpkt.getMsgSeq() == SRT_MSGNO_CONTROL) { // Interpret the first byte of the contents. const char* payload = rpkt.data(); @@ -717,7 +810,12 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) isfec.col = true; } - HLOGC(mglog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=0, FEC/CTL packet. INDEX=" << int(payload[0])); + HLOGC(pflog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=0, FEC/CTL packet. INDEX=" << int(payload[0])); + + // This marks the cell as NOT received, but still does extend the + // cell container up to this sequence. The HangHorizontal and HangVertical + // functions that would also do cell dismissal, RELY ON IT. + MarkCellReceived(rpkt.getSeqNo(), CELL_EXTEND); } else { @@ -731,7 +829,7 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) if (past || exists) { - HLOGC(mglog.Debug, log << "FEC: packet %" << rpkt.getSeqNo() << " " + HLOGC(pflog.Debug, log << "FEC: packet %" << rpkt.getSeqNo() << " " << (past ? "in the PAST" : "already known") << ", IGNORING."); return true; @@ -739,49 +837,72 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) want_packet = true; - HLOGC(mglog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " DATA PACKET."); + HLOGC(pflog.Debug, log << "FEC: RECEIVED %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " DATA PACKET."); MarkCellReceived(rpkt.getSeqNo()); - } - // Remember this simply every time a packet comes in. In live mode usually - // this flag is ORD_RELAXED (false), but some earlier versions used ORD_REQUIRED. - // Even though this flag is now usually ORD_RELAXED, it's fate in live mode - // isn't completely decided yet, so stay flexible. We believe at least that this - // flag will stay unchanged during whole connection. - rcv.order_required = rpkt.getMsgOrderFlag(); + // Remember this simply every time a packet comes in. In live mode usually + // this flag is ORD_RELAXED (false), but some earlier versions used ORD_REQUIRED. + // Even though this flag is now usually ORD_RELAXED, it's fate in live mode + // isn't completely decided yet, so stay flexible. We believe at least that this + // flag will stay unchanged during whole connection. + rcv.order_required = rpkt.getMsgOrderFlag(); + } loss_seqs_t irrecover_row, irrecover_col; - bool ok = true; +#if ENABLE_HEAVY_LOGGING + static string hangname [] = {"SUCCESS", "PAST", "CRAZY", "NOT-DONE"}; +#endif + + // Required for EHangStatus + using namespace std::rel_ops; + + EHangStatus okh = HANG_NOTDONE; if (!isfec.col) // == regular packet or FEC/ROW { // Don't manage this packet for horizontal group, // if it was a vertical FEC/CTL packet. - ok = HangHorizontal(rpkt, isfec.row, irrecover_row); - HLOGC(mglog.Debug, log << "FEC: HangHorizontal %" << rpkt.getSeqNo() + okh = HangHorizontal(rpkt, isfec.row, irrecover_row); + HLOGC(pflog.Debug, log << "FEC: HangHorizontal %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() - << " RESULT=" << boolalpha << ok << " IRRECOVERABLE: " << Printable(irrecover_row)); + << " RESULT=" << hangname[okh] << " IRRECOVERABLE: " << Printable(irrecover_row)); } - if (!ok) + if (okh > HANG_SUCCESS) { // Just informative. - LOGC(mglog.Warn, log << "FEC/H: rebuilding/hanging FAILED."); + LOGC(pflog.Warn, log << "FEC/H: rebuilding/hanging FAILED."); } + EHangStatus okv = HANG_NOTDONE; // Don't do HangVertical in case of row-only configuration if (!isfec.row && m_number_rows > 1) // == regular packet or FEC/COL { - ok = HangVertical(rpkt, isfec.colx, irrecover_col); - HLOGC(mglog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo() + // NOTE FOR IPE REPORTING: + // It is allowed that + // - Both HangVertical and HangHorizontal + + okv = HangVertical(rpkt, isfec.colx, irrecover_col); + IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? int(okh) < HANG_CRAZY : false); + HLOGC(pflog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() - << " RESULT=" << boolalpha << ok << " IRRECOVERABLE: " << Printable(irrecover_col)); + << " RESULT=" << hangname[okh] + << (discrep ? " IPE: H successul and V failed!" : "") + << " IRRECOVERABLE: " << Printable(irrecover_col)); } - if (!ok) + if (okv > HANG_SUCCESS) { // Just informative. - LOGC(mglog.Warn, log << "FEC/V: rebuilding/hanging FAILED."); + LOGC(pflog.Warn, log << "FEC/V: rebuilding/hanging FAILED."); + } + + if (okv == HANG_CRAZY || okh == HANG_CRAZY) + { + // Mark the cell not received, if it was rejected by the + // FEC group facility, otherwise it will deny to try to rebuild an + // allegedly existing packet. + MarkCellReceived(rpkt.getSeqNo(), CELL_REMOVE); } // Pack the following packets as irrecoverable: @@ -844,16 +965,16 @@ void FECFilterBuiltin::CheckLargeDrop(int32_t seqno) int32_t oldbase = rcv.rowq[0].base; size_t rowdist = offset / sizeRow(); - int32_t newbase = CSeqNo::incseq(oldbase, rowdist * sizeRow()); + int32_t newbase = CSeqNo::incseq(oldbase, int(rowdist * sizeRow())); - LOGC(mglog.Warn, log << "FEC: LARGE DROP detected! Resetting row groups. Base: %" << oldbase + LOGC(pflog.Warn, log << "FEC: LARGE DROP detected! Resetting row groups. Base: %" << oldbase << " -> %" << newbase << "(shift by " << CSeqNo::seqoff(oldbase, newbase) << ")"); rcv.rowq.clear(); rcv.cells.clear(); rcv.rowq.resize(1); - HLOGP(mglog.Debug, "FEC: RE-INIT: receiver first row"); + HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first row"); ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); } @@ -864,7 +985,7 @@ void FECFilterBuiltin::CheckLargeDrop(int32_t seqno) if (offset != CSeqNo::seqoff(rcv.colq[0].base, seqno)) { reset_anyway = true; - HLOGC(mglog.Debug, log << "FEC: IPE: row.base %" << rcv.rowq[0].base << " != %" << rcv.colq[0].base << " - resetting"); + HLOGC(pflog.Debug, log << "FEC: IPE: row.base %" << rcv.rowq[0].base << " != %" << rcv.colq[0].base << " - resetting"); } // Number of column - regardless of series. @@ -885,18 +1006,19 @@ void FECFilterBuiltin::CheckLargeDrop(int32_t seqno) return; } - size_t matrix = numberRows() * numberCols(); + const size_t size_in_packets = colx * numberRows(); + const size_t matrix = numberRows() * numberCols(); - int colseries = coloff / matrix; + const int colseries = coloff / int(matrix); - if (colseries > 2 || reset_anyway) + if (size_in_packets > rcvBufferSize()/2 || colseries > SRT_FEC_MAX_RCV_HISTORY || reset_anyway) { // Ok, now define the new ABSOLUTE BASE. This is the base of the column 0 // column group from the series previous towards this one. int32_t oldbase = rcv.colq[0].base; - int32_t newbase = CSeqNo::incseq(oldbase, (colseries-1) * matrix); + int32_t newbase = CSeqNo::incseq(oldbase, (colseries-1) * int(matrix)); - LOGC(mglog.Warn, log << "FEC: LARGE DROP detected! Resetting all groups. Base: %" << oldbase + LOGC(pflog.Warn, log << "FEC: LARGE DROP detected! Resetting all groups. Base: %" << oldbase << " -> %" << newbase << "(shift by " << CSeqNo::seqoff(oldbase, newbase) << ")"); rcv.rowq.clear(); @@ -904,13 +1026,13 @@ void FECFilterBuiltin::CheckLargeDrop(int32_t seqno) rcv.cells.clear(); rcv.rowq.resize(1); - HLOGP(mglog.Debug, "FEC: RE-INIT: receiver first row"); + HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first row"); ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); // Size: cols // Step: rows (the next packet in the group is one row later) // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down) - HLOGP(mglog.Debug, "FEC: RE-INIT: receiver first N columns"); + HLOGP(pflog.Debug, "FEC: RE-INIT: receiver first N columns"); ConfigureColumns(rcv.colq, newbase); rcv.cell_base = newbase; @@ -928,7 +1050,7 @@ void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) int offset = CSeqNo::seqoff(base, g.base); if (offset < 0) { - LOGC(mglog.Error, log << "FEC: IPE: row base %" << g.base << " is PAST to cell base %" << base); + LOGC(pflog.Error, log << "FEC: IPE: row base %" << g.base << " is PAST to cell base %" << base); return; } @@ -936,9 +1058,9 @@ void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) // Sanity check, if all cells are really filled. if (maxoff > rcv.cells.size()) { - LOGC(mglog.Error, log << "FEC: IPE: Collecting loss from row %" + LOGC(pflog.Error, log << "FEC: IPE: Collecting loss from row %" << g.base << "+" << m_number_cols << " while cells <= %" - << CSeqNo::seqoff(rcv.cell_base, rcv.cells.size()-1)); + << CSeqNo::seqoff(rcv.cell_base, int(rcv.cells.size())-1)); return; } @@ -952,11 +1074,11 @@ void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) if (gone && !last) { // Switch full -> loss. Store the sequence, as single (for now) - val.first = val.second = CSeqNo::incseq(base, i); + val.first = val.second = CSeqNo::incseq(base, int(i)); } else if (last && !gone) { - val.second = CSeqNo::incseq(base, i); + val.second = CSeqNo::incseq(base, int(i)); irrecover.push_back(val); } } @@ -973,6 +1095,7 @@ void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) g.dismissed = true; } +#if ENABLE_HEAVY_LOGGING static inline char CellMark(const std::deque& cells, int index) { if (index >= int(cells.size())) @@ -981,48 +1104,50 @@ static inline char CellMark(const std::deque& cells, int index) return cells[index] ? '#' : '.'; } -#if ENABLE_HEAVY_LOGGING -static void DebugPrintCells(int32_t base, const std::deque& cells, int row_size) +static void DebugPrintCells(int32_t base, const std::deque& cells, size_t row_size) { - int i = 0; + size_t i = 0; // Shift to the first empty cell - for ( ; i < int(cells.size()); ++i) + for ( ; i < cells.size(); ++i) if (cells[i] == false) break; - if (i == int(cells.size())) + if (i == cells.size()) { - LOGC(mglog.Debug, log << "FEC: ... cell[0-" << (cells.size()-1) << "]: ALL CELLS EXIST"); + LOGC(pflog.Debug, log << "FEC: ... cell[0-" << (cells.size()-1) << "]: ALL CELLS EXIST"); return; } // Ok, we have some empty cells, so just adjust to the start of a row. - i -= i % row_size; - if (i < 0) - i = 0; // you never know... - - for ( ; i < int(cells.size()); i += row_size ) + size_t bstep = i % row_size; + if (i < bstep) // you never know... + i = 0; + else + i -= bstep; + + for ( ; i < cells.size(); i += row_size ) { std::ostringstream os; os << "cell[" << i << "-" << (i+row_size-1) << "] %" << CSeqNo::incseq(base, i) << ":"; - for (int y = 0; y < row_size; ++y) + for (size_t y = 0; y < row_size; ++y) { os << " " << CellMark(cells, i+y); } - LOGP(mglog.Debug, os.str()); + LOGP(pflog.Debug, os.str()); } } #else -static void DebugPrintCells(int32_t /*base*/, const std::deque& /*cells*/, int /*row_size*/) {} +static void DebugPrintCells(int32_t /*base*/, const std::deque& /*cells*/, size_t /*row_size*/) {} #endif -bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs_t& irrecover) +FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs_t& irrecover) { - int32_t seq = rpkt.getSeqNo(); + const int32_t seq = rpkt.getSeqNo(); - int rowx = RcvGetRowGroupIndex(seq); + EHangStatus stat; + const int rowx = RcvGetRowGroupIndex(seq, (stat)); if (rowx == -1) - return false; + return stat; RcvGroup& rowg = rcv.rowq[rowx]; // Clip the packet into the horizontal group. @@ -1035,25 +1160,25 @@ bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs { ClipControlPacket(rowg, rpkt); rowg.fec = true; - HLOGC(mglog.Debug, log << "FEC/H: FEC/CTL packet clipped, %" << seq << " base=%" << rowg.base); + HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL packet clipped, %" << seq << " base=%" << rowg.base); } else { - HLOGC(mglog.Debug, log << "FEC/H: FEC/CTL at %" << seq << " DUPLICATED, skipping."); + HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL at %" << seq << " DUPLICATED, skipping."); } } else { ClipPacket(rowg, rpkt); rowg.collected++; - HLOGC(mglog.Debug, log << "FEC/H: DATA packet clipped, %" << seq + HLOGC(pflog.Debug, log << "FEC/H: DATA packet clipped, %" << seq << ", received " << rowg.collected << "/" << sizeRow() << " base=%" << rowg.base); } if (rowg.fec && rowg.collected == m_number_cols - 1) { - HLOGC(mglog.Debug, log << "FEC/H: HAVE " << rowg.collected << " collected & FEC; REBUILDING..."); + HLOGC(pflog.Debug, log << "FEC/H: HAVE " << rowg.collected << " collected & FEC; REBUILDING..."); // The group will provide the information for rebuilding. // The sequence of the lost packet can be checked in cells. // With the condition of 'collected == m_number_cols - 1', there @@ -1068,7 +1193,7 @@ bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs os << " " << rcv.rebuilt[i].hdr[SRT_PH_SEQNO]; } - LOGC(mglog.Debug, log << "FEC: ... cached rebuilt packets (" << rcv.rebuilt.size() << "):" << os.str()); + LOGC(pflog.Debug, log << "FEC: ... cached rebuilt packets (" << rcv.rebuilt.size() << "):" << os.str()); #endif } @@ -1093,9 +1218,9 @@ bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs } } - if (want_collect_irrecover) + if (want_collect_irrecover) // AND rcv.rowq.size() > 1 { - int current = rcv.rowq.size() - 2; + int current = int(rcv.rowq.size()) - 2; // We know we have at least 2 rows. // This value is then 0 or more. int past = current - 1; @@ -1134,7 +1259,7 @@ bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs // If want_remove_cells, also remove these rows and corresponding cells. int nrowremove = 1 + past; - HLOGC(mglog.Debug, log << "Collecting irrecoverable packets from " << nrowremove << " ROWS per offset " + HLOGC(pflog.Debug, log << "Collecting irrecoverable packets from " << nrowremove << " ROWS per offset " << CSeqNo::seqoff(rcv.rowq[1].base, seq) << " vs. " << m_number_cols << "/3"); for (int i = 0; i <= past; ++i) @@ -1142,17 +1267,21 @@ bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs CollectIrrecoverRow(rcv.rowq[i], irrecover); } - if (want_remove_cells) + // Sanity check condition - rcv.rowq must be of size + // greater than the number of rows to remove so that + // the rcv.rowq[0] exists after the operation. + if (want_remove_cells && rcv.rowq.size() > size_t(nrowremove)) { + // nrowremove >= 1 size_t npktremove = sizeRow() * nrowremove; - size_t ersize = min(npktremove, rcv.cells.size()); + size_t ersize = min(npktremove, rcv.cells.size()); // ersize <= rcv.cells.size() - HLOGC(mglog.Debug, log << "FEC/H: Dismissing rows n=" << nrowremove + HLOGC(pflog.Debug, log << "FEC/H: Dismissing rows n=" << nrowremove << ", starting at %" << rcv.rowq[0].base << " AND " << npktremove << " CELLS, base switch %" << rcv.cell_base << " -> %" << rcv.rowq[past].base); - rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + 1 + past); + rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + nrowremove); rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + ersize); // We state that we have removed as many cells as for the removed @@ -1165,13 +1294,13 @@ bool FECFilterBuiltin::HangHorizontal(const CPacket& rpkt, bool isfec, loss_seqs } else { - HLOGC(mglog.Debug, log << "FEC: NOT collecting irrecover from rows: distance=" + HLOGC(pflog.Debug, log << "FEC: NOT collecting irrecover from rows: distance=" << CSeqNo::seqoff(rcv.rowq[0].base, seq)); } } - return true; + return HANG_SUCCESS; } int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) @@ -1179,7 +1308,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) int baseoff = CSeqNo::seqoff(rcv.cell_base, g.base); if (baseoff < 0) { - LOGC(mglog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD"); + LOGC(pflog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD"); return -1; } @@ -1192,10 +1321,10 @@ int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) { if (!rcv.CellAt(cix)) { - offset = cix; + offset = int(cix); #if ENABLE_HEAVY_LOGGING // For heavy logging case, show all cells in the range - LOGC(mglog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, cix) + LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): MISSING"); #else @@ -1209,7 +1338,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) #if ENABLE_HEAVY_LOGGING else { - LOGC(mglog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, cix) + LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): exists"); } #endif @@ -1217,7 +1346,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) if (offset == -1) { - LOGC(mglog.Fatal, log << "FEC/H: IPE: rebuilding attempt, but no lost packet found"); + LOGC(pflog.Fatal, log << "FEC/H: IPE: rebuilding attempt, but no lost packet found"); return -1; // sanity, shouldn't happen } @@ -1231,7 +1360,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) int baseoff = CSeqNo::seqoff(rcv.cell_base, g.base); if (baseoff < 0) { - LOGC(mglog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD"); + LOGC(pflog.Error, log << "FEC: IPE: negative cell offset, cell_base=%" << rcv.cell_base << " Group's base: %" << g.base << " - NOT ATTEMPTING TO REBUILD"); return -1; } @@ -1245,10 +1374,10 @@ int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) size_t cix = baseoff + (col * sizeRow()); if (!rcv.CellAt(cix)) { - offset = cix; + offset = int(cix); #if ENABLE_HEAVY_LOGGING // For heavy logging case, show all cells in the range - LOGC(mglog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, cix) + LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): MISSING"); #else @@ -1262,7 +1391,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) #if ENABLE_HEAVY_LOGGING else { - LOGC(mglog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, cix) + LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): exists"); } #endif @@ -1270,7 +1399,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) if (offset == -1) { - LOGC(mglog.Fatal, log << "FEC/V: IPE: rebuilding attempt, but no lost packet found"); + LOGC(pflog.Fatal, log << "FEC/V: IPE: rebuilding attempt, but no lost packet found"); return -1; // sanity, shouldn't happen } @@ -1287,7 +1416,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) uint16_t length_hw = ntohs(g.length_clip); if (length_hw > payloadSize()) { - LOGC(mglog.Error, log << "FEC: DECLIPPED length '" << length_hw << "' exceeds payload size. NOT REBUILDING."); + LOGC(pflog.Warn, log << "FEC: DECLIPPED length '" << length_hw << "' exceeds payload size. NOT REBUILDING."); return; } @@ -1328,28 +1457,28 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) // contains only trailing zeros for completion, which are skipped. copy(g.payload_clip.begin(), g.payload_clip.end(), p.buffer); - HLOGC(mglog.Debug, log << "FEC: REBUILT: %" << seqno + HLOGC(pflog.Debug, log << "FEC: REBUILT: %" << seqno << " msgno=" << MSGNO_SEQ::unwrap(p.hdr[SRT_PH_MSGNO]) << " flags=" << PacketMessageFlagStr(p.hdr[SRT_PH_MSGNO]) << " TS=" << p.hdr[SRT_PH_TIMESTAMP] << " ID=" << dec << p.hdr[SRT_PH_ID] << " size=" << length_hw << " !" << BufferStamp(p.buffer, p.length)); + // Mark this packet received + MarkCellReceived(seqno); + // If this is a single request (filled from row and m_number_cols == 1), // do not attempt recursive rebuilding if (tp == Group::SINGLE) return; - // Mark this packet received - MarkCellReceived(seqno); - - // This flips HORIZ/VERT - Group::Type crosstype = Group::Type(!tp); + Group::Type crosstype = Group::FlipType(tp); + EHangStatus stat; if (crosstype == Group::HORIZ) { // Find this packet in the horizontal group - int rowx = RcvGetRowGroupIndex(seqno); + const int rowx = RcvGetRowGroupIndex(seqno, (stat)); if (rowx == -1) return; // can't access any group to rebuild RcvGroup& rowg = rcv.rowq[rowx]; @@ -1365,7 +1494,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) // is extracting the data directly from the rebuilt one. ClipRebuiltPacket(rowg, p); rowg.collected++; - HLOGC(mglog.Debug, log << "FEC/H: REBUILT packet clipped, %" << seqno + HLOGC(pflog.Debug, log << "FEC/H: REBUILT packet clipped, %" << seqno << ", received " << rowg.collected << "/" << m_number_cols << " FOR base=%" << rowg.base); @@ -1373,7 +1502,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) // They are already known when the packets were collected. if (rowg.fec && rowg.collected == m_number_cols - 1) { - HLOGC(mglog.Debug, log << "FEC/H: with FEC-rebuilt HAVE " << rowg.collected << " collected & FEC; REBUILDING"); + HLOGC(pflog.Debug, log << "FEC/H: with FEC-rebuilt HAVE " << rowg.collected << " collected & FEC; REBUILDING"); // The group will provide the information for rebuilding. // The sequence of the lost packet can be checked in cells. // With the condition of 'collected == m_number_cols - 1', there @@ -1386,7 +1515,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) else // crosstype == Group::VERT { // Find this packet in the vertical group - int colx = RcvGetColumnGroupIndex(seqno); + const int colx = RcvGetColumnGroupIndex(seqno, (stat)); if (colx == -1) return; // can't access any group to rebuild RcvGroup& colg = rcv.colq[colx]; @@ -1402,7 +1531,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) // is extracting the data directly from the rebuilt one. ClipRebuiltPacket(colg, p); colg.collected++; - HLOGC(mglog.Debug, log << "FEC/V: REBUILT packet clipped, %" << seqno + HLOGC(pflog.Debug, log << "FEC/V: REBUILT packet clipped, %" << seqno << ", received " << colg.collected << "/" << m_number_rows << " FOR base=%" << colg.base); @@ -1410,7 +1539,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) // They are already known when the packets were collected. if (colg.fec && colg.collected == m_number_rows - 1) { - HLOGC(mglog.Debug, log << "FEC/V: with FEC-rebuilt HAVE " << colg.collected << " collected & FEC; REBUILDING"); + HLOGC(pflog.Debug, log << "FEC/V: with FEC-rebuilt HAVE " << colg.collected << " collected & FEC; REBUILDING"); // The group will provide the information for rebuilding. // The sequence of the lost packet can be checked in cells. // With the condition of 'collected == m_number_rows - 1', there @@ -1423,30 +1552,27 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) } -int FECFilterBuiltin::ExtendRows(int rowx) +size_t FECFilterBuiltin::ExtendRows(size_t rowx) { // Check if oversize. Oversize is when the // index is > 2*m_number_cols. If so, shrink // the container first. #if ENABLE_HEAVY_LOGGING - LOGC(mglog.Debug, log << "FEC: ROW STATS BEFORE: n=" << rcv.rowq.size()); + LOGC(pflog.Debug, log << "FEC: ROW STATS BEFORE: n=" << rcv.rowq.size()); for (size_t i = 0; i < rcv.rowq.size(); ++i) - LOGC(mglog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); #endif - if (rowx > int(m_number_cols*3)) - { - LOGC(mglog.Error, log << "FEC/H: OFFSET=" << rowx << " exceeds maximum row container size, SHRINKING rows and cells"); - - rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + m_number_cols); - rowx -= m_number_cols; + const size_t size_in_packets = rowx * numberCols(); + const int n_series = int(rowx / numberRows()); - // With rows, delete also an appropriate number of cells. - int nerase = min(int(rcv.cells.size()), CSeqNo::seqoff(rcv.cell_base, rcv.rowq[0].base)); - rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + nerase); - rcv.cell_base = rcv.rowq[0].base; + if (size_in_packets > rcvBufferSize() && n_series > 2) + { + HLOGC(pflog.Debug, log << "FEC: Emergency resize, rowx=" << rowx << " series=" << n_series + << "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize()); + EmergencyShrink(n_series); } // Create and configure next groups. @@ -1459,31 +1585,32 @@ int FECFilterBuiltin::ExtendRows(int rowx) for (size_t i = old; i < rcv.rowq.size(); ++i) { // Initialize the base for the row group - int32_t ibase = CSeqNo::incseq(rcv.rowq[0].base, i*m_number_cols); + int32_t ibase = CSeqNo::incseq(rcv.rowq[0].base, int(i*m_number_cols)); ConfigureGroup(rcv.rowq[i], ibase, 1, m_number_cols); } #if ENABLE_HEAVY_LOGGING - LOGC(mglog.Debug, log << "FEC: ROW STATS AFTER: n=" << rcv.rowq.size()); + LOGC(pflog.Debug, log << "FEC: ROW STATS AFTER: n=" << rcv.rowq.size()); for (size_t i = 0; i < rcv.rowq.size(); ++i) - LOGC(mglog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); #endif return rowx; } -int FECFilterBuiltin::RcvGetRowGroupIndex(int32_t seq) +int FECFilterBuiltin::RcvGetRowGroupIndex(int32_t seq, EHangStatus& w_status) { RcvGroup& head = rcv.rowq[0]; - int32_t base = head.base; + const int32_t base = head.base; - int offset = CSeqNo::seqoff(base, seq); + const int offset = CSeqNo::seqoff(base, seq); // Discard the packet, if older than base. if (offset < 0) { - HLOGC(mglog.Debug, log << "FEC/H: Packet %" << seq << " is in the past, ignoring"); + HLOGC(pflog.Debug, log << "FEC/H: Packet %" << seq << " is in the past, ignoring"); + w_status = HANG_PAST; return -1; } @@ -1497,7 +1624,7 @@ int FECFilterBuiltin::RcvGetRowGroupIndex(int32_t seq) so simply TRUST THIS SEQUENCE, no matter what. After the check it won't do any harm. if (rowx > numberRows()*2) // past twice the matrix { - LOGC(mglog.Error, log << "FEC/H: Packet %" << seq << " is in the far future, ignoring"); + LOGC(pflog.Error, log << "FEC/H: Packet %" << seq << " is in the far future, ignoring"); return -1; } */ @@ -1509,18 +1636,20 @@ int FECFilterBuiltin::RcvGetRowGroupIndex(int32_t seq) // First, possibly extend the row container if (rowx >= rcv.rowq.size()) { + // Never returns -1 rowx = ExtendRows(rowx); } - return rowx; + w_status = HANG_SUCCESS; + return int(rowx); } -void FECFilterBuiltin::MarkCellReceived(int32_t seq) +void FECFilterBuiltin::MarkCellReceived(int32_t seq, ECellReceived is_received) { // Mark the packet as received. This will allow later to // determine, which exactly packet is lost and needs rebuilding. - int cellsize = rcv.cells.size(); - int cell_offset = CSeqNo::seqoff(rcv.cell_base, seq); + const int cellsize = int(rcv.cells.size()); + const int cell_offset = CSeqNo::seqoff(rcv.cell_base, seq); bool resized SRT_ATR_UNUSED = false; if (cell_offset >= cellsize) { @@ -1530,48 +1659,179 @@ void FECFilterBuiltin::MarkCellReceived(int32_t seq) resized = true; rcv.cells.resize(cell_offset+1, false); } - rcv.cells[cell_offset] = true; - HLOGC(mglog.Debug, log << "FEC: MARK CELL RECEIVED: %" << seq << " - cells base=%" + if (resized || is_received != CELL_EXTEND) + { + // In both RECEIVED and REMOVE cases, forcefully set the value always. + // In EXTEND, only if it was received + // Value set should be true only if RECEIVED, false otherwise + rcv.cells[cell_offset] = (is_received == CELL_RECEIVED); + } + +#if ENABLE_HEAVY_LOGGING + static string const cellop [] = { "RECEIVED", "EXTEND", "REMOVE" }; + LOGC(pflog.Debug, log << "FEC: MARK CELL " << cellop[is_received] + << "(" << (rcv.cells[cell_offset] ? "SET" : "CLR") << ")" + << ": %" << seq << " - cells base=%" << rcv.cell_base << "[" << cell_offset << "]+" << rcv.cells.size() << (resized ? "(resized)":"") << " :"); +#endif DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow()); } bool FECFilterBuiltin::IsLost(int32_t seq) const { - int offset = CSeqNo::seqoff(rcv.cell_base, seq); + const int offset = CSeqNo::seqoff(rcv.cell_base, seq); if (offset < 0) { - LOGC(mglog.Error, log << "FEC: IsLost: IPE: %" << seq + LOGC(pflog.Error, log << "FEC: IsLost: IPE: %" << seq << " is earlier than the cell base %" << rcv.cell_base); - return true; // fake we have the packet - this is to collect losses only + return true; // This might be due to emergency shrinking; pretend the packet is lost } if (offset >= int(rcv.cells.size())) { // XXX IPE! - LOGC(mglog.Error, log << "FEC: IsLost: IPE: %" << seq << " is past the cells %" + LOGC(pflog.Error, log << "FEC: IsLost: IPE: %" << seq << " is past the cells %" << rcv.cell_base << " + " << rcv.cells.size()); - return true; + return false; // Don't notify it yet } return rcv.cells[offset]; } -bool FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, loss_seqs_t& irrecover) +void FECFilterBuiltin::EmergencyShrink(size_t n_series) +{ + // Shrink is required in order to prepare place for + // either vertical or horizontal group in series `n_series`. + + // The n_series can be calculated as: + // n_series = colgx / numberCols() + // n_series = rowgx / numberRows() + // + // The (Column or Row) Group Index value is calculated as + // the number of column where the desired sequence number + // should be located towards the very first container item + // (row/column 0). + + // The task for this function is to leave only one series + // of groups and therefore initialize the containers. Likely + // the part that contains the last series should be already + // there, so in this case just remove some initial items from + // the container so that only those remain that are intended + // to remain. However, by various reasons (like e.g. that all + // packets from the whole series have been lost) particular + // container (colq, rowq, cell) doesn't contain this last + // series at all. In that case clear the container completely + // and just add an initial configuration for the first part + // (which will be then dynamically extended as packets come in). + + const int32_t oldbase = rcv.colq[0].base; + const size_t shift_series = n_series - 1; + + // This is simply a situation when the size is so excessive + // that it couldn't be withstood by the receiver buffer, so + // even if this isn't an extremely big size for allocation for + // FEC, it doesn't make sense anyway. + // + // Minimum of 2 series must remain in the group container, + // otherwise there's no need to guard the size. + + // This requires simply resetting all group containers to + // the very initial state, just take the calculated base seq + // from the value of colgx reset to column 0. + + // As colgx is calculated by stating that colgx == 0 represents + // the very first cell in the column groups, take this, shift + // by the number of series. + + // SHIFT BY: n_series * matrix size + // n_series is at least 2 (see condition) + const size_t shift = shift_series * numberCols() * numberRows(); + + // Always positive: colgx, and so n_series, and so shift + const int32_t newbase = CSeqNo::incseq(oldbase, int(shift)); + + const size_t shift_rows = shift_series * numberRows(); + + bool need_reset = rcv.rowq.size() < shift_rows; + if (!need_reset) + { + // Sanity check - you should have the exact value + // of `newbase` at the next series beginning position + if (rcv.rowq[numberRows()].base != newbase) + { + LOGC(pflog.Error, log << "FEC: IPE: row start at %" << rcv.rowq[0].base << " next series %" << rcv.rowq[numberRows()].base + << " (expected %" << newbase << "). RESETTING ROWS."); + need_reset = true; + } + } + + if (need_reset) + { + rcv.rowq.clear(); + // This n_series is the number rounded downwards, + // So you just need to prepare place for ONE series. + // The procedure below will extend them to the required + // size for the received colgx. + rcv.rowq.resize(1); + + HLOGC(pflog.Debug, log << "FEC: Reset recv row %" << oldbase << " -> %" << newbase << ", INIT ROWS:"); + ConfigureGroup(rcv.rowq[0], newbase, 1, sizeRow()); + } + else + { + HLOGC(pflog.Debug, log << "FEC: Shifting rcv row %" << oldbase << " -> %" << newbase); + rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.end() + shift_rows); + } + + const size_t shift_cols = shift_series * numberCols(); + need_reset = rcv.colq.size() < shift_cols; + if (!need_reset) + { + // Sanity check - you should have the exact value + // of `newbase` at the next series beginning position + if (rcv.colq[numberCols()].base != newbase) + { + LOGC(pflog.Error, log << "FEC: IPE: col start at %" << rcv.colq[0].base << " next series %" << rcv.colq[numberCols()].base + << " (expected %" << newbase << "). RESETTING ROWS."); + need_reset = true; + } + } + + if (need_reset) + { + rcv.colq.clear(); + HLOGC(pflog.Debug, log << "FEC: Reset recv row %" << oldbase << " -> %" << newbase << ", INIT first " << numberCols() << ":"); + ConfigureColumns(rcv.colq, newbase); + } + + if (rcv.cells.size() > shift) + { + rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + shift); + } + else + { + rcv.cells.clear(); + rcv.cells.push_back(false); + } + rcv.cell_base = newbase; +} + +FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, loss_seqs_t& irrecover) { bool fec_ctl = (fec_col != -1); // Now hang the packet in the vertical group - int32_t seq = rpkt.getSeqNo(); + const int32_t seq = rpkt.getSeqNo(); // Ok, now we have the column index, we know it exists. // Apply the packet. - int colgx = RcvGetColumnGroupIndex(seq); + EHangStatus stat; + const int colgx = RcvGetColumnGroupIndex(seq, (stat)); if (colgx == -1) - return false; + return stat; RcvGroup& colg = rcv.colq[colgx]; @@ -1581,12 +1841,12 @@ bool FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, lo { ClipControlPacket(colg, rpkt); colg.fec = true; - HLOGC(mglog.Debug, log << "FEC/V: FEC/CTL packet clipped, %" << seq << " FOR COLUMN " << int(fec_col) + HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL packet clipped, %" << seq << " FOR COLUMN " << int(fec_col) << " base=%" << colg.base); } else { - HLOGC(mglog.Debug, log << "FEC/V: FEC/CTL at %" << seq << " COLUMN " << int(fec_col) << " DUPLICATED, skipping."); + HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL at %" << seq << " COLUMN " << int(fec_col) << " DUPLICATED, skipping."); } } else @@ -1594,14 +1854,14 @@ bool FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, lo // Data packet, clip it as data ClipPacket(colg, rpkt); colg.collected++; - HLOGC(mglog.Debug, log << "FEC/V: DATA packet clipped, %" << seq + HLOGC(pflog.Debug, log << "FEC/V: DATA packet clipped, %" << seq << ", received " << colg.collected << "/" << sizeCol() << " base=%" << colg.base); } if (colg.fec && colg.collected == m_number_rows - 1) { - HLOGC(mglog.Debug, log << "FEC/V: HAVE " << colg.collected << " collected & FEC; REBUILDING"); + HLOGC(pflog.Debug, log << "FEC/V: HAVE " << colg.collected << " collected & FEC; REBUILDING"); RcvRebuild(colg, RcvGetLossSeqVert(colg), Group::VERT); } @@ -1611,13 +1871,13 @@ bool FECFilterBuiltin::HangVertical(const CPacket& rpkt, signed char fec_col, lo RcvCheckDismissColumn(rpkt.getSeqNo(), colgx, irrecover); #if ENABLE_HEAVY_LOGGING - LOGC(mglog.Debug, log << "FEC: COL STATS ATM: n=" << rcv.colq.size()); + LOGC(pflog.Debug, log << "FEC: COL STATS ATM: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) - LOGC(mglog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif - return true; + return HANG_SUCCESS; } void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t& irrecover) @@ -1628,7 +1888,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t // - get the series for this column // - if series is 0, just return - int series = colgx / numberCols(); + const size_t series = colgx / numberCols(); if (series == 0) return; @@ -1639,9 +1899,9 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t set loss; - int colx SRT_ATR_UNUSED = colgx % numberCols(); + size_t colx SRT_ATR_UNUSED = colgx % numberCols(); - HLOGC(mglog.Debug, log << "FEC/V: going to DISMISS cols past %" << seq + HLOGC(pflog.Debug, log << "FEC/V: going to DISMISS cols past %" << seq << " at INDEX=" << colgx << " col=" << colx << " series=" << series << " - looking up candidates..."); @@ -1652,7 +1912,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t RcvGroup& pg = rcv.colq[i]; if (pg.dismissed) { - HLOGC(mglog.Debug, log << "FEC/V: ... [" << i << "] base=%" + HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%" << pg.base << " ALREADY DISMISSED, skipping."); continue; } @@ -1666,11 +1926,11 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t // because they can't be dismissed yet. Jump them over, so maybe // they can be dismissed in future. int this_col_offset = CSeqNo::seqoff(pg.base, seq); - int last_seq_offset = this_col_offset - (sizeCol()-1)*sizeRow(); + int last_seq_offset = this_col_offset - int((sizeCol()-1)*sizeRow()); if (last_seq_offset < 0) { - HLOGC(mglog.Debug, log << "FEC/V: ... [" << i << "] base=%" + HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%" << pg.base << " TOO EARLY (last=%" << CSeqNo::incseq(pg.base, (sizeCol()-1)*sizeRow()) << ")"); @@ -1681,7 +1941,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t // still a chance that it hits the staircase top of the first // staircase and will dismiss it as well. - HLOGC(mglog.Debug, log << "FEC/V: ... [" << i << "] base=%" + HLOGC(pflog.Debug, log << "FEC/V: ... [" << i << "] base=%" << pg.base << " - PAST last=%" << CSeqNo::incseq(pg.base, (sizeCol()-1)*sizeRow()) << " - collecting losses."); @@ -1689,16 +1949,16 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t pg.dismissed = true; // mark irrecover already collected for (size_t sof = 0; sof < pg.step * sizeCol(); sof += pg.step) { - int32_t lseq = CSeqNo::incseq(pg.base, sof); + int32_t lseq = CSeqNo::incseq(pg.base, int(sof)); if (!IsLost(lseq)) { loss.insert(lseq); - HLOGC(mglog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq + HLOGC(pflog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq << " lost"); } else { - HLOGC(mglog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq + HLOGC(pflog.Debug, log << "FEC: ... cell +" << sof << " %" << lseq << " EXISTS"); } } @@ -1720,31 +1980,46 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t int32_t base0 = rcv.colq[0].base; int this_off = CSeqNo::seqoff(base0, seq); - int mindist = + int mindist = int( m_arrangement_staircase ? (numberCols() * numberRows() * 2) : - (numberCols() * numberRows()); + (numberCols() * numberRows())); bool any_dismiss SRT_ATR_UNUSED = false; + // Here's a change. + // The number of existing column groups is supposed to always cover + // at least one full series, whereas the number of row groups are + // created always one per necessity, so the number of existing row + // groups may be less than required for a full series, whereas here + // it is intended to simply dismiss groups for full series. This may + // cause that it is aiming for removing more row groups than currently + // exist. This is completely ok, as the sequence that triggered removal + // is long past these series anyway, so the groups for packets that will + // never be received makes no sense. Simply accept this state and delete + // all row groups and reinitialize them into the new base, where the base + // is the current base for column 0 group. + // + // Therefore dismissal is triggered whenever you have a cover of one column + // series. If the number of row groups doesn't cover it, simply delete all + // row groups, that's all. + // if (base0 +% mindist) <% seq - if (this_off < mindist) + if (this_off < mindist) // COND 1: minimum remaining { - HLOGC(mglog.Debug, log << "FEC/V: NOT dismissing any columns at %" << seq + HLOGC(pflog.Debug, log << "FEC/V: NOT dismissing any columns at %" << seq << ", need to pass %" << CSeqNo::incseq(base0, mindist)); } - else if (rcv.colq.size() < numberCols()) + else if (rcv.colq.size() - 1 < numberCols()) // COND 2: full matrix in columns { - HLOGC(mglog.Debug, log << "FEC/V: IPE: about to dismiss past %" << seq - << " with required %" << CSeqNo::incseq(base0, mindist) - << " but col container size still " << rcv.colq.size()); - } - else if (rcv.rowq.size() < numberRows()) - { - HLOGC(mglog.Debug, log << "FEC/V: IPE: about to dismiss past %" << seq +#if ENABLE_HEAVY_LOGGING + LOGC(pflog.Debug, log << "FEC/V: IPE: about to dismiss past %" << seq << " with required %" << CSeqNo::incseq(base0, mindist) - << " but row container size still " << rcv.rowq.size()); + << " but col container size still " << rcv.colq.size() << "; COL STATS:"); + for (size_t i = 0; i < rcv.colq.size(); ++i) + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); +#endif } else { @@ -1752,31 +2027,66 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t // is numberCols(), regardless of the required 'mindinst'. any_dismiss = true; - int32_t newbase = rcv.colq[numberCols()].base; - int32_t newbase_row = rcv.rowq[numberRows()].base; - int matrix_size = numberCols() * numberRows(); + const int32_t newbase = rcv.colq[numberCols()].base; + int32_t newbase_row SRT_ATR_UNUSED; // For logging only, but including FATAL. + // Sanity check + // If sanity check failed OR if the number of existing row + // groups doesn't enclose those that need to be dismissed, + // clear row groups completely - these packets are lost and + // irrecoverable anyway. + bool insane = false; + bool undercounted = false; + + if (rcv.rowq.size() - 1 < numberRows()) // COND 3: full matrix in rows + { + // Do not reach to index=numberRows() because it doesn't exist. + // Take the value from the columns as a good deal - actually + // row base and col base shall be always in sync. + newbase_row = newbase; + undercounted = true; + } + else + { + newbase_row = rcv.rowq[numberRows()].base; + insane = newbase_row != newbase; + } + const size_t matrix_size = numberCols() * numberRows(); - HLOGC(mglog.Debug, log << "FEC/V: DISMISSING " << numberCols() << " COLS. Base %" + HLOGC(pflog.Debug, log << "FEC/V: DISMISSING " << numberCols() << " COLS. Base %" << rcv.colq[0].base << " -> %" << newbase << " AND " << numberRows() << " ROWS Base %" << rcv.rowq[0].base << " -> %" << newbase_row << " AND " << matrix_size << " cells"); + // ensured existence of the removed range: see COND 2 above. rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols()); #if ENABLE_HEAVY_LOGGING - LOGC(mglog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); + LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) - LOGC(mglog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif // Now erase accordingly one matrix of rows. - // Sanity check - if (newbase_row != newbase) + if (insane || undercounted) { - LOGC(mglog.Fatal, log << "FEC/V: IPE: DISCREPANCY in base0 col=%" - << newbase << " row=%" << newbase_row << " - DELETING ALL ROWS"); + if (insane) + { + LOGC(pflog.Fatal, log << "FEC/V: IPE: DISCREPANCY in new base0 col=%" + << newbase << " row=%" << newbase_row << " - DELETING ALL ROWS"); + } + else + { + +#if ENABLE_HEAVY_LOGGING + LOGC(pflog.Debug, log << "FEC/V: about to dismiss past %" << seq + << " with required %" << CSeqNo::incseq(base0, mindist) + << " but row container size still " << rcv.rowq.size() << " (will clear to %" << newbase << " instead); ROW STATS:"); + for (size_t i = 0; i < rcv.rowq.size(); ++i) + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.rowq[i].DisplayStats()); +#endif + } // Delete all rows and reinitialize them. rcv.rowq.clear(); @@ -1786,27 +2096,31 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t else { // Remove "legally" a matrix of rows. + // ensured existence of the removed range: see COND 3 above rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + numberRows()); } // And now accordingly remove cells. Exactly one matrix of cells. // Sanity check first. - int32_t newbase_cell = CSeqNo::incseq(rcv.cell_base, matrix_size); + int32_t newbase_cell = CSeqNo::incseq(rcv.cell_base, int32_t(matrix_size)); if (newbase != newbase_cell) { - LOGC(mglog.Fatal, log << "FEC/V: IPE: DISCREPANCY in base0 col=%" - << newbase << " row=%" << newbase_row << " - DELETING ALL ROWS"); + LOGC(pflog.Fatal, log << "FEC/V: IPE: DISCREPANCY in new base0 col=%" + << newbase << " cell_base=%" << newbase_cell << " - DELETING ALL CELLS"); // Try to shift it gently first. Find the cell that matches the base. int shift = CSeqNo::seqoff(rcv.cell_base, newbase); - if (shift < 0) + if (shift < 0 || size_t(shift) > rcv.cells.size()) rcv.cells.clear(); else rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + shift); } else { - rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + matrix_size); + if (rcv.cells.size() <= size_t(matrix_size)) + rcv.cells.clear(); + else + rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + matrix_size); } rcv.cell_base = newbase; DebugPrintCells(rcv.cell_base, rcv.cells, sizeRow()); @@ -1864,7 +2178,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t int32_t newrowbase = rcv.rowq[numberRows()].base; if (newbase != newrowbase) { - LOGC(mglog.Error, log << "FEC: IPE: ROW/COL base DISCREPANCY: Looking up lineraly for the right row."); + LOGC(pflog.Error, log << "FEC: IPE: ROW/COL base DISCREPANCY: Looking up lineraly for the right row."); // Fallback implementation in order not to break everything for (size_t r = 0; r < rcv.rowq.size(); ++r) @@ -1895,11 +2209,11 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t if (oldrowbase != rcv.cell_base) { - LOGC(mglog.Error, log << "FEC: CELL/ROW base discrepancy, calculating and resynchronizing"); + LOGC(pflog.Error, log << "FEC: CELL/ROW base discrepancy, calculating and resynchronizing"); } else { - HLOGC(mglog.Debug, log << "FEC: will remove " << nrem << " cells, SHOULD BE = " + HLOGC(pflog.Debug, log << "FEC: will remove " << nrem << " cells, SHOULD BE = " << (nrowrem * sizeRow())); } @@ -1913,7 +2227,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t loss.insert(lseq); } - HLOGC(mglog.Debug, log << "FEC: ERASING unused cells (" << nrem << "): %" + HLOGC(pflog.Debug, log << "FEC: ERASING unused cells (" << nrem << "): %" << rcv.cell_base << " - %" << newbase << ", losses collected: " << Printable(loss)); @@ -1924,12 +2238,12 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t } else { - HLOGC(mglog.Debug, log << "FEC: NOT ERASING cells, base %" << rcv.cell_base + HLOGC(pflog.Debug, log << "FEC: NOT ERASING cells, base %" << rcv.cell_base << " vs row base %" << rcv.rowq[0].base); } } - HLOGC(mglog.Debug, log << "FEC/V: updated g=" << colgx << " -> " << newcolgx << " %" + HLOGC(pflog.Debug, log << "FEC/V: updated g=" << colgx << " -> " << newcolgx << " %" << rcv.colq[newcolgx].base << ", DISMISS up to g=" << numberCols() << " base=%" << lastbase << " ROW=%" << rcv.rowq[0].base << "+" << nrowrem); @@ -1942,7 +2256,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t // Now all collected lost packets translate into the range list format TranslateLossRecords(loss, irrecover); -HLOGC(mglog.Debug, log << "FEC: ... COLLECTED IRRECOVER: " << Printable(loss) << (any_dismiss ? " CELLS DISMISSED" : " nothing dismissed")); +HLOGC(pflog.Debug, log << "FEC: ... COLLECTED IRRECOVER: " << Printable(loss) << (any_dismiss ? " CELLS DISMISSED" : " nothing dismissed")); } void FECFilterBuiltin::TranslateLossRecords(const set& loss, loss_seqs_t& irrecover) @@ -1973,7 +2287,7 @@ void FECFilterBuiltin::TranslateLossRecords(const set& loss, loss_seqs_ irrecover.push_back(make_pair(fi_start, fi_end)); } -int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno) +int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno, EHangStatus& w_status) { // The column is only the column, not yet // exactly the index of the column group in the container. @@ -2047,43 +2361,47 @@ int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno) // // GROUP_INDEX = COLUMN_INDEX + (COLUMN_SERIES * m_number_cols) - int offset = CSeqNo::seqoff(rcv.colq[0].base, seqno); + const int offset = CSeqNo::seqoff(rcv.colq[0].base, seqno); if (offset < 0) { - HLOGC(mglog.Debug, log << "FEC/V: %" << seqno << " in the past of col ABSOLUTE base %" << rcv.colq[0].base); + HLOGC(pflog.Debug, log << "FEC/V: %" << seqno << " in the past of col ABSOLUTE base %" << rcv.colq[0].base); + w_status = HANG_PAST; return -1; } if (offset > CSeqNo::m_iSeqNoTH/2) { - LOGC(mglog.Error, log << "FEC/V: IPE/ATTACK: pkt %" << seqno << " has CRAZY OFFSET towards the base %" << rcv.colq[0].base); + LOGC(pflog.Error, log << "FEC/V: IPE/ATTACK: pkt %" << seqno << " has CRAZY OFFSET towards the base %" << rcv.colq[0].base); + w_status = HANG_CRAZY; return -1; } - int colx = offset % m_number_cols; - int32_t colbase = rcv.colq[colx].base; - int coloff = CSeqNo::seqoff(colbase, seqno); + const int colx = offset % m_number_cols; + const int32_t colbase = rcv.colq[colx].base; + const int coloff = CSeqNo::seqoff(colbase, seqno); if (coloff < 0) { - HLOGC(mglog.Debug, log << "FEC/V: %" << seqno << " in the past of col #" << colx << " base %" << colbase); + HLOGC(pflog.Debug, log << "FEC/V: %" << seqno << " in the past of col #" << colx << " base %" << colbase); // This means that this sequence number predates the earliest // sequence number supported by the very first column. + w_status = HANG_PAST; return -1; } - int colseries = coloff / (m_number_cols * m_number_rows); - size_t colgx = colx + (colseries * m_number_cols); + const int colseries = coloff / int(m_number_cols * m_number_rows); + size_t colgx = colx + int(colseries * m_number_cols); - HLOGC(mglog.Debug, log << "FEC/V: Lookup group for %" << seqno << ": cg_base=%" << rcv.colq[0].base + HLOGC(pflog.Debug, log << "FEC/V: Lookup group for %" << seqno << ": cg_base=%" << rcv.colq[0].base << " column=" << colx << " with base %" << colbase << ": SERIES=" << colseries << " INDEX:" << colgx); // Check oversize. Dismiss some earlier items if it exceeds the size. // before you extend the size enormously. - if (colgx > m_number_rows * m_number_cols * 2) + if (colgx > m_number_rows * m_number_cols * SRT_FEC_MAX_RCV_HISTORY) { // That's too much - LOGC(mglog.Error, log << "FEC/V: IPE or ATTACK: offset " << colgx << " is too crazy, ABORTING lookup"); + LOGC(pflog.Error, log << "FEC/V: IPE or ATTACK: offset " << colgx << " is too crazy, ABORTING lookup"); + w_status = HANG_CRAZY; return -1; } @@ -2091,8 +2409,8 @@ int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno) { colgx = ExtendColumns(colgx); } - - return colgx; + w_status = HANG_SUCCESS; + return int(colgx); // // Even though column groups are arranged in a "staircase", it only means @@ -2145,69 +2463,58 @@ int FECFilterBuiltin::RcvGetColumnGroupIndex(int32_t seqno) // gmax = SHIFT(g.base, m_number_cols * m_number_rows) // IF ( gs %> gmax ) // DISMISS COLUMNS from 0 to GROUP_INDEX - i; break - } -int FECFilterBuiltin::ExtendColumns(int colgx) +size_t FECFilterBuiltin::ExtendColumns(size_t colgx) { - if (colgx > int(sizeRow() * 2)) + // This isn't safe to allow the group container to get expanded to any + // size, however with some very tolerant settings, such as 10 seconds of + // latency and very large receiver buffer, this might be tolerable. + // + // Therefore put only two conditions here: + // + // 1. The group containers must keep at most place for so many + // packets as it is intended for the receiver buffer. Keeping + // group cells for more packets doesn't make sense anyway. + // + // 2. Existing group containers should contain at least size + // for two series. If they don't contain that much, there's no + // need to do any emergency shrinking. Unknown whether this is + // physically possible, although it may also happen in case when + // you have very large FEC matrix size not coordinated with the + // receiver buffer size. + + // colgx is the number of column + NSERIES * numberCols(). + // We can state that for every column we should have a number + // of packets as many as the number of rows, so simply multiply this. + const size_t size_in_packets = colgx * numberRows(); + const size_t n_series = colgx / numberCols(); + if (size_in_packets > rcvBufferSize()/2 || n_series > SRT_FEC_MAX_RCV_HISTORY) + { + HLOGC(pflog.Debug, log << "FEC: Emergency resize, colgx=" << colgx << " series=" << n_series + << "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize()); + EmergencyShrink(n_series); + } + else { - // This shouldn't happen because columns should be dismissed - // once the last row of the first series is closed. - LOGC(mglog.Error, log << "FEC/V: OFFSET=" << colgx << " exceeds maximum col container size, SHRINKING container by " << sizeRow()); - - // Delete one series of columns. - int32_t oldbase SRT_ATR_UNUSED = rcv.colq[0].base; - rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols()); - colgx -= numberCols(); - int32_t newbase = rcv.colq[0].base; - - // Delete also appropriate number of rows for one series - rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + numberRows()); - - // Sanity-check if the resulting row absolute base is equal to column - if (rcv.rowq[0].base != newbase) - { - LOGC(mglog.Error, log << "FEC/V: IPE: removal of " << numberRows() - << " rows ships no same seq: rowbase=%" - << rcv.rowq[0].base - << " colbase=%" << oldbase << " -> %" << newbase << " - RESETTING ROWS"); - - // How much you need, depends on the columns. - size_t nseries = rcv.colq.size() / numberCols() + 1; - size_t needrows = nseries * numberRows(); - - rcv.rowq.clear(); - rcv.rowq.resize(needrows); - int32_t rowbase = newbase; - for (size_t i = 0; i < rcv.rowq.size(); ++i) - { - ConfigureGroup(rcv.rowq[i], rowbase, 1, sizeRow()); - rowbase = CSeqNo::incseq(newbase, sizeRow()); - } - } - - size_t ncellrem = CSeqNo::seqoff(rcv.cell_base, newbase); - rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + ncellrem); - rcv.cell_base = newbase; - - // Note that after this shift, column groups that were - // in particular column, remain in that column. + HLOGC(pflog.Debug, log << "FEC: Will extend up to colgx=" << colgx << " series=" << n_series + << " for npackets=" << size_in_packets); } + #if ENABLE_HEAVY_LOGGING - LOGC(mglog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); + LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) - LOGC(mglog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif // First, obtain the "series" of columns, possibly fixed. - int series = colgx / numberCols(); + const int series = int(colgx / numberCols()); // Now, the base of the series is the base increased by one matrix size. - int32_t base = rcv.colq[0].base; + const int32_t base = rcv.colq[0].base; // This is the base for series 0, but this procedure must be prepared // for that the series will not necessarily be 1, may be greater. @@ -2217,7 +2524,7 @@ int FECFilterBuiltin::ExtendColumns(int colgx) // Check, up to which series the columns are initialized. // Start with the series that doesn't exist - int old_series = rcv.colq.size() / numberCols(); + const int old_series = int(rcv.colq.size() / numberCols()); // Each iteration of this loop adds one series of columns. // One series count numberCols() columns. @@ -2230,8 +2537,8 @@ int FECFilterBuiltin::ExtendColumns(int colgx) // Every base sequence for a series of columns is the series 0 // base increased by one matrix size times series number. // THIS REMAINS TRUE NO MATTER IF WE USE STRAIGNT OR STAIRCASE ARRANGEMENT. - int32_t sbase = CSeqNo::incseq(base, (numberCols()*numberRows()) * s); - HLOGC(mglog.Debug, log << "FEC/V: EXTENDING column groups series " << s + const int32_t sbase = CSeqNo::incseq(base, int(numberCols()*numberRows()) * s); + HLOGC(pflog.Debug, log << "FEC/V: EXTENDING column groups series " << s << ", size " << rcv.colq.size() << " -> " << (rcv.colq.size() + numberCols()) << ", base=%" << base << " -> %" << sbase); @@ -2242,12 +2549,13 @@ int FECFilterBuiltin::ExtendColumns(int colgx) } #if ENABLE_HEAVY_LOGGING - LOGC(mglog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); + LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) - LOGC(mglog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); + LOGC(pflog.Debug, log << "... [" << i << "] " << rcv.colq[i].DisplayStats()); #endif return colgx; } +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/fec.h b/trunk/3rdparty/srt-1-fit/srtcore/fec.h index 5912a19677..71a6adfa7d 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/fec.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/fec.h @@ -9,8 +9,8 @@ */ -#ifndef INC__SRT_FEC_H -#define INC__SRT_FEC_H +#ifndef INC_SRT_FEC_H +#define INC_SRT_FEC_H #include #include @@ -19,6 +19,8 @@ #include "packetfilter_api.h" +namespace srt { + class FECFilterBuiltin: public SrtPacketFilterBase { SrtFilterConfig cfg; @@ -68,6 +70,12 @@ class FECFilterBuiltin: public SrtPacketFilterBase SINGLE // Horizontal-only with no recursion }; + static Type FlipType(Type t) + { + SRT_ASSERT(t != SINGLE); + return (t == HORIZ) ? VERT : HORIZ; + } + }; struct RcvGroup: Group @@ -183,21 +191,43 @@ class FECFilterBuiltin: public SrtPacketFilterBase // Receiving void CheckLargeDrop(int32_t seqno); - int ExtendRows(int rowx); - int ExtendColumns(int colgx); - void MarkCellReceived(int32_t seq); - bool HangHorizontal(const CPacket& pkt, bool fec_ctl, loss_seqs_t& irrecover); - bool HangVertical(const CPacket& pkt, signed char fec_colx, loss_seqs_t& irrecover); + size_t ExtendRows(size_t rowx); + size_t ExtendColumns(size_t colgx); + + enum ECellReceived + { + CELL_RECEIVED, //< mark cell for a received packet (no matter current value) + CELL_EXTEND, //< just make sure there's a place for a packet, set false if not + CELL_REMOVE //< even if a packet was marked true, remove the cell existence confirmation + }; + void MarkCellReceived(int32_t seq, ECellReceived recv = CELL_RECEIVED); + + enum EHangStatus + { + HANG_NOTDONE, + HANG_SUCCESS, + HANG_PAST, + HANG_CRAZY + }; + + friend bool operator <(FECFilterBuiltin::EHangStatus a, FECFilterBuiltin::EHangStatus b) + { + return int(a) < int(b); + } + + EHangStatus HangHorizontal(const CPacket& pkt, bool fec_ctl, loss_seqs_t& irrecover); + EHangStatus HangVertical(const CPacket& pkt, signed char fec_colx, loss_seqs_t& irrecover); void ClipControlPacket(Group& g, const CPacket& pkt); void ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt); void RcvRebuild(Group& g, int32_t seqno, Group::Type tp); int32_t RcvGetLossSeqHoriz(Group& g); int32_t RcvGetLossSeqVert(Group& g); + void EmergencyShrink(size_t n_series); static void TranslateLossRecords(const std::set& loss, loss_seqs_t& irrecover); void RcvCheckDismissColumn(int32_t seqno, int colgx, loss_seqs_t& irrecover); - int RcvGetRowGroupIndex(int32_t seq); - int RcvGetColumnGroupIndex(int32_t seq); + int RcvGetRowGroupIndex(int32_t seq, EHangStatus& w_status); + int RcvGetColumnGroupIndex(int32_t seqno, EHangStatus& w_status); void CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) const; bool IsLost(int32_t seq) const; @@ -243,6 +273,11 @@ class FECFilterBuiltin: public SrtPacketFilterBase static const size_t EXTRA_SIZE = 4; virtual SRT_ARQLevel arqLevel() ATR_OVERRIDE { return m_fallback_level; } + + static const char defaultConfig []; + static bool verifyConfig(const SrtFilterConfig& config, std::string& w_errormsg); }; +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/filelist.maf b/trunk/3rdparty/srt-1-fit/srtcore/filelist.maf index e2a6983c4a..560a0463b3 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/filelist.maf +++ b/trunk/3rdparty/srt-1-fit/srtcore/filelist.maf @@ -3,6 +3,7 @@ SOURCES api.cpp buffer.cpp +buffer_rcv.cpp cache.cpp channel.cpp common.cpp @@ -12,27 +13,48 @@ epoll.cpp fec.cpp handshake.cpp list.cpp +logger_default.cpp +logger_defs.cpp md5.cpp packet.cpp packetfilter.cpp queue.cpp congctl.cpp +socketconfig.cpp srt_c_api.cpp -window.cpp srt_compat.c +strerror_defs.cpp +sync.cpp +tsbpd_time.cpp +window.cpp + +SOURCES - ENABLE_BONDING +group.cpp +group_backup.cpp +group_common.cpp + +SOURCES - !ENABLE_STDCXX_SYNC +sync_posix.cpp + +SOURCES - ENABLE_STDCXX_SYNC +sync_cxx11.cpp + +SOURCES - EXTRA_WIN32_SHARED +srt_shared.rc PUBLIC HEADERS srt.h logging_api.h +access_control.h PROTECTED HEADERS platform_sys.h udt.h -srt4udt.h PRIVATE HEADERS api.h buffer.h +buffer_rcv.h cache.h channel.h common.h @@ -45,13 +67,18 @@ logging.h md5.h netinet_any.h packet.h +sync.h queue.h congctl.h -srt4udt.h +socketconfig.h srt_compat.h +stats.h threadname.h +tsbpd_time.h utilities.h window.h -SOURCES WIN32 SHARED -srt_shared.rc +PRIVATE HEADERS - ENABLE_BONDING +group.h +group_backup.h +group_common.h diff --git a/trunk/3rdparty/srt-1-fit/srtcore/group.cpp b/trunk/3rdparty/srt-1-fit/srtcore/group.cpp new file mode 100644 index 0000000000..5975cc9ae2 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group.cpp @@ -0,0 +1,4717 @@ +#include "platform_sys.h" + +#include + +#include "api.h" +#include "group.h" + +using namespace std; +using namespace srt::sync; +using namespace srt::groups; +using namespace srt_logging; + +// The SRT_DEF_VERSION is defined in core.cpp. +extern const int32_t SRT_DEF_VERSION; + +namespace srt { + +int32_t CUDTGroup::s_tokenGen = 0; + +// [[using locked(this->m_GroupLock)]]; +bool CUDTGroup::getBufferTimeBase(CUDT* forthesakeof, + steady_clock::time_point& w_tb, + bool& w_wp, + steady_clock::duration& w_dr) +{ + CUDT* master = 0; + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + CUDT* u = &gi->ps->core(); + if (gi->laststatus != SRTS_CONNECTED) + { + HLOGC(gmlog.Debug, + log << "getBufferTimeBase: skipping @" << u->m_SocketID + << ": not connected, state=" << SockStatusStr(gi->laststatus)); + continue; + } + + if (u == forthesakeof) + continue; // skip the member if it's the target itself + + if (!u->m_pRcvBuffer) + continue; // Not initialized yet + + master = u; + break; // found + } + + // We don't have any sockets in the group, so can't get + // the buffer timebase. This should be then initialized + // the usual way. + if (!master) + return false; + + master->m_pRcvBuffer->getInternalTimeBase((w_tb), (w_wp), (w_dr)); + + // Sanity check + if (is_zero(w_tb)) + { + LOGC(gmlog.Error, log << "IPE: existing previously socket has no time base set yet!"); + return false; // this will enforce initializing the time base normal way + } + return true; +} + +// [[using locked(this->m_GroupLock)]]; +bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_t& w_rcv_isn) +{ + if (m_bConnected) // You are the first one, no need to change. + { + IF_HEAVY_LOGGING(string update_reason = "what?"); + // Find a socket that is declared connected and is not + // the socket that caused the call. + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->id == target) + continue; + + CUDT& se = gi->ps->core(); + if (!se.m_bConnected) + continue; + + // Found it. Get the following sequences: + // For sending, the sequence that is about to be sent next. + // For receiving, the sequence of the latest received packet. + + // SndCurrSeqNo is initially set to ISN-1, this next one is + // the sequence that is about to be stamped on the next sent packet + // over that socket. Using this field is safer because it is atomic + // and its affinity is to the same thread as the sending function. + + // NOTE: the groupwise scheduling sequence might have been set + // already. If so, it means that it was set by either: + // - the call of this function on the very first conencted socket (see below) + // - the call to `sendBroadcast` or `sendBackup` + // In both cases, we want THIS EXACTLY value to be reported + if (m_iLastSchedSeqNo != -1) + { + w_snd_isn = m_iLastSchedSeqNo; + IF_HEAVY_LOGGING(update_reason = "GROUPWISE snd-seq"); + } + else + { + w_snd_isn = se.m_iSndNextSeqNo; + + // Write it back to the groupwise scheduling sequence so that + // any next connected socket will take this value as well. + m_iLastSchedSeqNo = w_snd_isn; + IF_HEAVY_LOGGING(update_reason = "existing socket not yet sending"); + } + + // RcvCurrSeqNo is increased by one because it happens that at the + // synchronization moment it's already past reading and delivery. + // This is redundancy, so the redundant socket is connected at the moment + // when the other one is already transmitting, so skipping one packet + // even if later transmitted is less troublesome than requesting a + // "mistakenly seen as lost" packet. + w_rcv_isn = CSeqNo::incseq(se.m_iRcvCurrSeqNo); + + HLOGC(gmlog.Debug, + log << "applyGroupSequences: @" << target << " gets seq from @" << gi->id << " rcv %" << (w_rcv_isn) + << " snd %" << (w_snd_isn) << " as " << update_reason); + return false; + } + } + + // If the GROUP (!) is not connected, or no running/pending socket has been found. + // // That is, given socket is the first one. + // The group data should be set up with its own data. They should already be passed here + // in the variables. + // + // Override the schedule sequence of the group in this case because whatever is set now, + // it's not valid. + + HLOGC(gmlog.Debug, + log << "applyGroupSequences: no socket found connected and transmitting, @" << target + << " not changing sequences, storing snd-seq %" << (w_snd_isn)); + + set_currentSchedSequence(w_snd_isn); + + return true; +} + +// NOTE: This function is now for DEBUG PURPOSES ONLY. +// Except for presenting the extracted data in the logs, there's no use of it now. +void CUDTGroup::debugMasterData(SRTSOCKET slave) +{ + // Find at least one connection, which is running. Note that this function is called + // from within a handshake process, so the socket that undergoes this process is at best + // currently in SRT_GST_PENDING state and it's going to be in SRT_GST_IDLE state at the + // time when the connection process is done, until the first reading/writing happens. + ScopedLock cg(m_GroupLock); + + IF_LOGGING(SRTSOCKET mpeer = SRT_INVALID_SOCK); + IF_LOGGING(steady_clock::time_point start_time); + + bool found = false; + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->sndstate == SRT_GST_RUNNING) + { + // Found it. Get the socket's peer's ID and this socket's + // Start Time. Once it's delivered, this can be used to calculate + // the Master-to-Slave start time difference. + IF_LOGGING(mpeer = gi->ps->m_PeerID); + IF_LOGGING(start_time = gi->ps->core().socketStartTime()); + HLOGC(gmlog.Debug, + log << "getMasterData: found RUNNING master @" << gi->id << " - reporting master's peer $" << mpeer + << " starting at " << FormatTime(start_time)); + found = true; + break; + } + } + + if (!found) + { + // If no running one found, then take the first socket in any other + // state than broken, except the slave. This is for a case when a user + // has prepared one link already, but hasn't sent anything through it yet. + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->sndstate == SRT_GST_BROKEN) + continue; + + if (gi->id == slave) + continue; + + // Found it. Get the socket's peer's ID and this socket's + // Start Time. Once it's delivered, this can be used to calculate + // the Master-to-Slave start time difference. + IF_LOGGING(mpeer = gi->ps->core().m_PeerID); + IF_LOGGING(start_time = gi->ps->core().socketStartTime()); + HLOGC(gmlog.Debug, + log << "getMasterData: found IDLE/PENDING master @" << gi->id << " - reporting master's peer $" << mpeer + << " starting at " << FormatTime(start_time)); + found = true; + break; + } + } + + if (!found) + { + LOGC(cnlog.Debug, log << CONID() << "NO GROUP MASTER LINK found for group: $" << id()); + } + else + { + // The returned master_st is the master's start time. Calculate the + // differene time. + IF_LOGGING(steady_clock::duration master_tdiff = m_tsStartTime - start_time); + LOGC(cnlog.Debug, log << CONID() << "FOUND GROUP MASTER LINK: peer=$" << mpeer + << " - start time diff: " << FormatDuration(master_tdiff)); + } +} + +// GROUP + +CUDTGroup::SocketData* CUDTGroup::add(SocketData data) +{ + ScopedLock g(m_GroupLock); + + // Change the snd/rcv state of the group member to PENDING. + // Default for SocketData after creation is BROKEN, which just + // after releasing the m_GroupLock could be read and interpreted + // as broken connection and removed before the handshake process + // is done. + data.sndstate = SRT_GST_PENDING; + data.rcvstate = SRT_GST_PENDING; + + HLOGC(gmlog.Debug, log << "CUDTGroup::add: adding new member @" << data.id); + m_Group.push_back(data); + gli_t end = m_Group.end(); + if (m_iMaxPayloadSize == -1) + { + int plsize = data.ps->core().OPT_PayloadSize(); + HLOGC(gmlog.Debug, + log << "CUDTGroup::add: taking MAX payload size from socket @" << data.ps->m_SocketID << ": " << plsize + << " " << (plsize ? "(explicit)" : "(unspecified = fallback to 1456)")); + if (plsize == 0) + plsize = SRT_LIVE_MAX_PLSIZE; + // It is stated that the payload size + // is taken from first, and every next one + // will get the same. + m_iMaxPayloadSize = plsize; + } + + --end; + return &*end; +} + +CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) + : m_Global(CUDT::uglobal()) + , m_GroupID(-1) + , m_PeerGroupID(-1) + , m_bSyncOnMsgNo(false) + , m_type(gtype) + , m_listener() + , m_iBusy() + , m_iSndOldestMsgNo(SRT_MSGNO_NONE) + , m_iSndAckedMsgNo(SRT_MSGNO_NONE) + , m_uOPT_MinStabilityTimeout_us(1000 * CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS) + // -1 = "undefined"; will become defined with first added socket + , m_iMaxPayloadSize(-1) + , m_bSynRecving(true) + , m_bSynSending(true) + , m_bTsbPd(true) + , m_bTLPktDrop(true) + , m_iTsbPdDelay_us(0) + // m_*EID and m_*Epolld fields will be initialized + // in the constructor body. + , m_iSndTimeOut(-1) + , m_iRcvTimeOut(-1) + , m_tsStartTime() + , m_tsRcvPeerStartTime() + , m_RcvBaseSeqNo(SRT_SEQNO_NONE) + , m_bOpened(false) + , m_bConnected(false) + , m_bClosing(false) + , m_iLastSchedSeqNo(SRT_SEQNO_NONE) + , m_iLastSchedMsgNo(SRT_MSGNO_NONE) +{ + setupMutex(m_GroupLock, "Group"); + setupMutex(m_RcvDataLock, "RcvData"); + setupCond(m_RcvDataCond, "RcvData"); + m_RcvEID = m_Global.m_EPoll.create(&m_RcvEpolld); + m_SndEID = m_Global.m_EPoll.create(&m_SndEpolld); + + m_stats.init(); + + // Set this data immediately during creation before + // two or more sockets start arguing about it. + m_iLastSchedSeqNo = CUDT::generateISN(); +} + +CUDTGroup::~CUDTGroup() +{ + srt_epoll_release(m_RcvEID); + srt_epoll_release(m_SndEID); + releaseMutex(m_GroupLock); + releaseMutex(m_RcvDataLock); + releaseCond(m_RcvDataCond); +} + +void CUDTGroup::GroupContainer::erase(CUDTGroup::gli_t it) +{ + if (it == m_LastActiveLink) + { + if (m_List.empty()) + { + LOGC(gmlog.Error, log << "IPE: GroupContainer is empty and 'erase' is called on it."); + m_LastActiveLink = m_List.end(); + return; // this avoids any misunderstandings in iterator checks + } + + gli_t bb = m_List.begin(); + ++bb; + if (bb == m_List.end()) // means: m_List.size() == 1 + { + // One element, this one being deleted, nothing to point to. + m_LastActiveLink = m_List.end(); + } + else + { + // Set the link to the previous element IN THE RING. + // We have the position pointer. + // Reverse iterator is automatically decremented. + std::reverse_iterator rt(m_LastActiveLink); + if (rt == m_List.rend()) + rt = m_List.rbegin(); + + m_LastActiveLink = rt.base(); + + // This operation is safe because we know that: + // - the size of the container is at least 2 (0 and 1 cases are handled above) + // - if m_LastActiveLink == m_List.begin(), `rt` is shifted to the opposite end. + --m_LastActiveLink; + } + } + m_List.erase(it); +} + +void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) +{ + HLOGC(gmlog.Debug, + log << "GROUP $" << id() << " OPTION: #" << optName + << " value:" << FormatBinaryString((uint8_t*)optval, optlen)); + + switch (optName) + { + case SRTO_RCVSYN: + m_bSynRecving = cast_optval(optval, optlen); + return; + + case SRTO_SNDSYN: + m_bSynSending = cast_optval(optval, optlen); + return; + + case SRTO_SNDTIMEO: + m_iSndTimeOut = cast_optval(optval, optlen); + break; + + case SRTO_RCVTIMEO: + m_iRcvTimeOut = cast_optval(optval, optlen); + break; + + case SRTO_GROUPMINSTABLETIMEO: + { + const int val_ms = cast_optval(optval, optlen); + const int min_timeo_ms = (int) CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS; + if (val_ms < min_timeo_ms) + { + LOGC(qmlog.Error, + log << "group option: SRTO_GROUPMINSTABLETIMEO min allowed value is " << min_timeo_ms << " ms."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // Search if you already have SRTO_PEERIDLETIMEO set + int idletmo = CSrtConfig::COMM_RESPONSE_TIMEOUT_MS; + vector::iterator f = + find_if(m_config.begin(), m_config.end(), ConfigItem::OfType(SRTO_PEERIDLETIMEO)); + if (f != m_config.end()) + { + f->get(idletmo); // worst case, it will leave it unchanged. + } + + if (val_ms > idletmo) + { + LOGC(qmlog.Error, + log << "group option: SRTO_GROUPMINSTABLETIMEO=" << val_ms << " exceeds SRTO_PEERIDLETIMEO=" << idletmo); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + m_uOPT_MinStabilityTimeout_us = 1000 * val_ms; + } + + break; + + case SRTO_CONGESTION: + // Currently no socket groups allow any other + // congestion control mode other than live. + LOGP(gmlog.Error, "group option: SRTO_CONGESTION is only allowed as 'live' and cannot be changed"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + default: + break; + } + + // All others must be simply stored for setting on a socket. + // If the group is already open and any post-option is about + // to be modified, it must be allowed and applied on all sockets. + if (m_bOpened) + { + // There's at least one socket in the group, so only + // post-options are allowed. + if (!binary_search(srt_post_opt_list, srt_post_opt_list + SRT_SOCKOPT_NPOST, optName)) + { + LOGC(gmlog.Error, log << "setsockopt(group): Group is connected, this option can't be altered"); + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + } + + HLOGC(gmlog.Debug, log << "... SPREADING to existing sockets."); + // This means that there are sockets already, so apply + // this option on them. + std::vector ps_vec; + { + // Do copy to avoid deadlock. CUDT::setOpt() cannot be called directly inside this loop, because + // CUDT::setOpt() will lock m_ConnectionLock, which should be locked before m_GroupLock. + ScopedLock gg(m_GroupLock); + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + ps_vec.push_back(gi->ps); + } + } + for (std::vector::iterator it = ps_vec.begin(); it != ps_vec.end(); ++it) + { + (*it)->core().setOpt(optName, optval, optlen); + } + } + + // Store the option regardless if pre or post. This will apply + m_config.push_back(ConfigItem(optName, optval, optlen)); +} + +static bool getOptDefault(SRT_SOCKOPT optname, void* optval, int& w_optlen); + +// unfortunately this is required to properly handle th 'default_opt != opt' +// operation in the below importOption. Not required simultaneously operator==. +static bool operator!=(const struct linger& l1, const struct linger& l2) +{ + return l1.l_onoff != l2.l_onoff || l1.l_linger != l2.l_linger; +} + +template +static void importOption(vector& storage, SRT_SOCKOPT optname, const ValueType& field) +{ + ValueType default_opt = ValueType(); + int default_opt_size = sizeof(ValueType); + ValueType opt = field; + if (!getOptDefault(optname, (&default_opt), (default_opt_size)) || default_opt != opt) + { + // Store the option when: + // - no default for this option is found + // - the option value retrieved from the field is different than default + storage.push_back(CUDTGroup::ConfigItem(optname, &opt, default_opt_size)); + } +} + +// This function is called by the same premises as the CUDT::CUDT(const CUDT&) (copy constructor). +// The intention is to rewrite the part that comprises settings from the socket +// into the group. Note that some of the settings concern group, some others concern +// only target socket, and there are also options that can't be set on a socket. +void CUDTGroup::deriveSettings(CUDT* u) +{ + // !!! IMPORTANT !!! + // + // This function shall ONLY be called on a newly created group + // for the sake of the newly accepted socket from the group-enabled listener, + // which is lazy-created for the first ever accepted socket. + // Once the group is created, it should stay with the options + // state as initialized here, and be changeable only in case when + // the option is altered on the group. + + // SRTO_RCVSYN + m_bSynRecving = u->m_config.bSynRecving; + + // SRTO_SNDSYN + m_bSynSending = u->m_config.bSynSending; + + // SRTO_RCVTIMEO + m_iRcvTimeOut = u->m_config.iRcvTimeOut; + + // SRTO_SNDTIMEO + m_iSndTimeOut = u->m_config.iSndTimeOut; + + // SRTO_GROUPMINSTABLETIMEO + m_uOPT_MinStabilityTimeout_us = 1000 * u->m_config.uMinStabilityTimeout_ms; + + // Ok, this really is disgusting, but there's only one way + // to properly do it. Would be nice to have some more universal + // connection between an option symbolic name and the internals + // in CUDT class, but until this is done, since now every new + // option will have to be handled both in the CUDT::setOpt/getOpt + // functions, and here as well. + + // This is about moving options from listener to the group, + // to be potentially replicated on the socket. So both pre + // and post options apply. + +#define IM(option, field) importOption(m_config, option, u->m_config.field) +#define IMF(option, field) importOption(m_config, option, u->field) + + IM(SRTO_MSS, iMSS); + IM(SRTO_FC, iFlightFlagSize); + + // Nonstandard + importOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); + importOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); + + IM(SRTO_LINGER, Linger); + IM(SRTO_UDP_SNDBUF, iUDPSndBufSize); + IM(SRTO_UDP_RCVBUF, iUDPRcvBufSize); + // SRTO_RENDEZVOUS: impossible to have it set on a listener socket. + // SRTO_SNDTIMEO/RCVTIMEO: groupwise setting + IM(SRTO_CONNTIMEO, tdConnTimeOut); + IM(SRTO_DRIFTTRACER, bDriftTracer); + // Reuseaddr: true by default and should only be true. + IM(SRTO_MAXBW, llMaxBW); + IM(SRTO_INPUTBW, llInputBW); + IM(SRTO_MININPUTBW, llMinInputBW); + IM(SRTO_OHEADBW, iOverheadBW); + IM(SRTO_IPTOS, iIpToS); + IM(SRTO_IPTTL, iIpTTL); + IM(SRTO_TSBPDMODE, bTSBPD); + IM(SRTO_RCVLATENCY, iRcvLatency); + IM(SRTO_PEERLATENCY, iPeerLatency); + IM(SRTO_SNDDROPDELAY, iSndDropDelay); + IM(SRTO_PAYLOADSIZE, zExpPayloadSize); + IMF(SRTO_TLPKTDROP, m_bTLPktDrop); + + importOption(m_config, SRTO_STREAMID, u->m_config.sStreamName.str()); + + IM(SRTO_MESSAGEAPI, bMessageAPI); + IM(SRTO_NAKREPORT, bRcvNakReport); + IM(SRTO_MINVERSION, uMinimumPeerSrtVersion); + IM(SRTO_ENFORCEDENCRYPTION, bEnforcedEnc); + IM(SRTO_IPV6ONLY, iIpV6Only); + IM(SRTO_PEERIDLETIMEO, iPeerIdleTimeout_ms); + + importOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig.str()); + + importOption(m_config, SRTO_PBKEYLEN, u->m_pCryptoControl->KeyLen()); + + // Passphrase is empty by default. Decipher the passphrase and + // store as passphrase option + if (u->m_config.CryptoSecret.len) + { + string password((const char*)u->m_config.CryptoSecret.str, u->m_config.CryptoSecret.len); + m_config.push_back(ConfigItem(SRTO_PASSPHRASE, password.c_str(), password.size())); + } + + IM(SRTO_KMREFRESHRATE, uKmRefreshRatePkt); + IM(SRTO_KMPREANNOUNCE, uKmPreAnnouncePkt); + + string cc = u->m_CongCtl.selected_name(); + if (cc != "live") + { + m_config.push_back(ConfigItem(SRTO_CONGESTION, cc.c_str(), cc.size())); + } + + // NOTE: This is based on information extracted from the "semi-copy-constructor" of CUDT class. + // Here should be handled all things that are options that modify the socket, but not all options + // are assigned to configurable items. + +#undef IM +#undef IMF +} + +bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide) +{ + const bool synconmsg = IsSet(flags, SRT_GFLAG_SYNCONMSG); + if (synconmsg) + { + LOGP(gmlog.Error, "GROUP: requested sync on msgno - not supported."); + return false; + } + + return true; +} + +template +struct Value +{ + static int fill(void* optval, int, Type value) + { + // XXX assert size >= sizeof(Type) ? + *(Type*)optval = value; + return sizeof(Type); + } +}; + +template <> +inline int Value::fill(void* optval, int len, std::string value) +{ + if (size_t(len) < value.size()) + return 0; + memcpy(optval, value.c_str(), value.size()); + return (int) value.size(); +} + +template +inline int fillValue(void* optval, int len, V value) +{ + return Value::fill(optval, len, value); +} + +static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) +{ + static const linger def_linger = {1, CSrtConfig::DEF_LINGER_S}; + switch (optname) + { + default: + return false; + +#define RD(value) \ + w_optlen = fillValue((pw_optval), w_optlen, value); \ + break + + case SRTO_KMSTATE: + case SRTO_SNDKMSTATE: + case SRTO_RCVKMSTATE: + RD(SRT_KM_S_UNSECURED); + case SRTO_PBKEYLEN: + RD(16); + + case SRTO_MSS: + RD(CSrtConfig::DEF_MSS); + + case SRTO_SNDSYN: + RD(true); + case SRTO_RCVSYN: + RD(true); + case SRTO_ISN: + RD(SRT_SEQNO_NONE); + case SRTO_FC: + RD(CSrtConfig::DEF_FLIGHT_SIZE); + + case SRTO_SNDBUF: + case SRTO_RCVBUF: + w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::UDP_HDR_SIZE)); + break; + + case SRTO_LINGER: + RD(def_linger); + case SRTO_UDP_SNDBUF: + case SRTO_UDP_RCVBUF: + RD(CSrtConfig::DEF_UDP_BUFFER_SIZE); + case SRTO_RENDEZVOUS: + RD(false); + case SRTO_SNDTIMEO: + RD(-1); + case SRTO_RCVTIMEO: + RD(-1); + case SRTO_REUSEADDR: + RD(true); + case SRTO_MAXBW: + RD(int64_t(-1)); + case SRTO_INPUTBW: + RD(int64_t(-1)); + case SRTO_OHEADBW: + RD(0); + case SRTO_STATE: + RD(SRTS_INIT); + case SRTO_EVENT: + RD(0); + case SRTO_SNDDATA: + RD(0); + case SRTO_RCVDATA: + RD(0); + + case SRTO_IPTTL: + RD(0); + case SRTO_IPTOS: + RD(0); + + case SRTO_SENDER: + RD(false); + case SRTO_TSBPDMODE: + RD(false); + case SRTO_LATENCY: + case SRTO_RCVLATENCY: + case SRTO_PEERLATENCY: + RD(SRT_LIVE_DEF_LATENCY_MS); + case SRTO_TLPKTDROP: + RD(true); + case SRTO_SNDDROPDELAY: + RD(-1); + case SRTO_NAKREPORT: + RD(true); + case SRTO_VERSION: + RD(SRT_DEF_VERSION); + case SRTO_PEERVERSION: + RD(0); + + case SRTO_CONNTIMEO: + RD(-1); + case SRTO_DRIFTTRACER: + RD(true); + + case SRTO_MINVERSION: + RD(0); + case SRTO_STREAMID: + RD(std::string()); + case SRTO_CONGESTION: + RD(std::string()); + case SRTO_MESSAGEAPI: + RD(true); + case SRTO_PAYLOADSIZE: + RD(0); + case SRTO_GROUPMINSTABLETIMEO: + RD(CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS); + } + +#undef RD + return true; +} + +void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) +{ + // Options handled in group + switch (optname) + { + case SRTO_RCVSYN: + *(bool*)pw_optval = m_bSynRecving; + w_optlen = sizeof(bool); + return; + + case SRTO_SNDSYN: + *(bool*)pw_optval = m_bSynSending; + w_optlen = sizeof(bool); + return; + + default:; // pass on + } + + // XXX Suspicous: may require locking of GlobControlLock + // to prevent from deleting a socket in the meantime. + // Deleting a socket requires removing from the group first, + // so after GroupLock this will be either already NULL or + // a valid socket that will only be closed after time in + // the GC, so this is likely safe like all other API functions. + CUDTSocket* ps = 0; + + { + // In sockets. All sockets should have all options + // set the same and should represent the group state + // well enough. If there are no sockets, just use default. + + // Group lock to protect the container itself. + // Once a socket is extracted, we state it cannot be + // closed without the group send/recv function or closing + // being involved. + ScopedLock lg(m_GroupLock); + if (m_Group.empty()) + { + if (!getOptDefault(optname, (pw_optval), (w_optlen))) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + return; + } + + ps = m_Group.begin()->ps; + + // Release the lock on the group, as it's not necessary, + // as well as it might cause a deadlock when combined + // with the others. + } + + if (!ps) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + return ps->core().getOpt(optname, (pw_optval), (w_optlen)); +} + +SRT_SOCKSTATUS CUDTGroup::getStatus() +{ + typedef vector > states_t; + states_t states; + + { + ScopedLock cg(m_GroupLock); + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + switch (gi->sndstate) + { + // Check only sndstate. If this machine is ONLY receiving, + // then rcvstate will turn into SRT_GST_RUNNING, while + // sndstate will remain SRT_GST_IDLE, but still this may only + // happen if the socket is connected. + case SRT_GST_IDLE: + case SRT_GST_RUNNING: + states.push_back(make_pair(gi->id, SRTS_CONNECTED)); + break; + + case SRT_GST_BROKEN: + states.push_back(make_pair(gi->id, SRTS_BROKEN)); + break; + + default: // (pending, or whatever will be added in future) + { + // TEMPORARY make a node to note a socket to be checked afterwards + states.push_back(make_pair(gi->id, SRTS_NONEXIST)); + } + } + } + } + + SRT_SOCKSTATUS pending_state = SRTS_NONEXIST; + + for (states_t::iterator i = states.begin(); i != states.end(); ++i) + { + // If at least one socket is connected, the state is connected. + if (i->second == SRTS_CONNECTED) + return SRTS_CONNECTED; + + // Second level - pick up the state + if (i->second == SRTS_NONEXIST) + { + // Otherwise find at least one socket, which's state isn't broken. + i->second = m_Global.getStatus(i->first); + if (pending_state == SRTS_NONEXIST) + pending_state = i->second; + } + } + + // Return that state as group state + if (pending_state != SRTS_NONEXIST) // did call getStatus at least once and it didn't return NOEXIST + return pending_state; + + // If none found, return SRTS_BROKEN. + return SRTS_BROKEN; +} + +// [[using locked(m_GroupLock)]]; +void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) +{ + if (side == HSD_RESPONDER) + { + // On the listener side you should synchronize ISN with the incoming + // socket, which is done immediately after creating the socket and + // adding it to the group. On the caller side the ISN is defined in + // the group directly, before any member socket is created. + set_currentSchedSequence(core.ISN()); + } + + // XXX + // Might need further investigation as to whether this isn't + // wrong for some cases. By having this -1 here the value will be + // laziliy set from the first reading one. It is believed that + // it covers all possible scenarios, that is: + // + // - no readers - no problem! + // - have some readers and a new is attached - this is set already + // - connect multiple links, but none has read yet - you'll be the first. + // + // Previous implementation used setting to: core.m_iPeerISN + resetInitialRxSequence(); + + // Get the latency (possibly fixed against the opposite side) + // from the first socket (core.m_iTsbPdDelay_ms), + // and set it on the current socket. + set_latency(core.m_iTsbPdDelay_ms * int64_t(1000)); +} + +void CUDTGroup::close() +{ + // Close all descriptors, then delete the group. + vector ids; + + { + ScopedLock glob(CUDT::uglobal().m_GlobControlLock); + ScopedLock g(m_GroupLock); + + m_bClosing = true; + + // Copy the list of IDs into the array. + for (gli_t ig = m_Group.begin(); ig != m_Group.end(); ++ig) + { + ids.push_back(ig->id); + // Immediately cut ties to this group. + // Just for a case, redispatch the socket, to stay safe. + CUDTSocket* s = CUDT::uglobal().locateSocket_LOCKED(ig->id); + if (!s) + { + HLOGC(smlog.Debug, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); + continue; + } + s->m_GroupOf = NULL; + s->m_GroupMemberData = NULL; + HLOGC(smlog.Debug, log << "group/close: CUTTING OFF @" << ig->id << " (found as @" << s->m_SocketID << ") from the group"); + } + + // After all sockets that were group members have their ties cut, + // the container can be cleared. Note that sockets won't be now + // removing themselves from the group when closing because they + // are unaware of being group members. + m_Group.clear(); + m_PeerGroupID = -1; + + set epollid; + { + // Global EPOLL lock must be applied to access any socket's epoll set. + // This is a set of all epoll ids subscribed to it. + ScopedLock elock (CUDT::uglobal().m_EPoll.m_EPollLock); + epollid = m_sPollID; // use move() in C++11 + m_sPollID.clear(); + } + + int no_events = 0; + for (set::iterator i = epollid.begin(); i != epollid.end(); ++i) + { + HLOGC(smlog.Debug, log << "close: CLEARING subscription on E" << (*i) << " of $" << id()); + try + { + CUDT::uglobal().m_EPoll.update_usock(*i, id(), &no_events); + } + catch (...) + { + // May catch an API exception, but this isn't an API call to be interrupted. + } + HLOGC(smlog.Debug, log << "close: removing E" << (*i) << " from back-subscribers of $" << id()); + } + + // NOW, the m_GroupLock is released, then m_GlobControlLock. + // The below code should work with no locks and execute socket + // closing. + } + + HLOGC(gmlog.Debug, log << "grp/close: closing $" << m_GroupID << ", closing first " << ids.size() << " sockets:"); + // Close all sockets with unlocked GroupLock + for (vector::iterator i = ids.begin(); i != ids.end(); ++i) + { + try + { + CUDT::uglobal().close(*i); + } + catch (CUDTException&) + { + HLOGC(gmlog.Debug, log << "grp/close: socket @" << *i << " is likely closed already, ignoring"); + } + } + + HLOGC(gmlog.Debug, log << "grp/close: closing $" << m_GroupID << ": sockets closed, clearing the group:"); + + // Lock the group again to clear the group data + { + ScopedLock g(m_GroupLock); + + if (!m_Group.empty()) + { + LOGC(gmlog.Error, log << "grp/close: IPE - after requesting to close all members, still " << m_Group.size() + << " lingering members!"); + m_Group.clear(); + } + + // This takes care of the internal part. + // The external part will be done in Global (CUDTUnited) + } + + // Release blocked clients + // XXX This looks like a dead code. Group receiver functions + // do not use any lock on m_RcvDataLock, it is likely a remainder + // of the old, internal impementation. + // CSync::lock_notify_one(m_RcvDataCond, m_RcvDataLock); +} + +// [[using locked(m_Global->m_GlobControlLock)]] +// [[using locked(m_GroupLock)]] +void CUDTGroup::send_CheckValidSockets() +{ + vector toremove; + + for (gli_t d = m_Group.begin(), d_next = d; d != m_Group.end(); d = d_next) + { + ++d_next; // it's now safe to erase d + CUDTSocket* revps = m_Global.locateSocket_LOCKED(d->id); + if (revps != d->ps) + { + // Note: the socket might STILL EXIST, just in the trash, so + // it can't be found by locateSocket. But it can still be bound + // to the group. Just mark it broken from upside so that the + // internal sending procedures will skip it. Removal from the + // group will happen in GC, which will both remove from + // group container and cut backward links to the group. + + HLOGC(gmlog.Debug, log << "group/send_CheckValidSockets: socket @" << d->id << " is no longer valid, setting BROKEN in $" << id()); + d->sndstate = SRT_GST_BROKEN; + d->rcvstate = SRT_GST_BROKEN; + } + } +} + +int CUDTGroup::send(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + switch (m_type) + { + default: + LOGC(gslog.Error, log << "CUDTGroup::send: not implemented for type #" << m_type); + throw CUDTException(MJ_SETUP, MN_INVAL, 0); + + case SRT_GTYPE_BROADCAST: + return sendBroadcast(buf, len, (w_mc)); + + case SRT_GTYPE_BACKUP: + return sendBackup(buf, len, (w_mc)); + + /* to be implemented + + case SRT_GTYPE_BALANCING: + return sendBalancing(buf, len, (w_mc)); + + case SRT_GTYPE_MULTICAST: + return sendMulticast(buf, len, (w_mc)); + */ + } +} + +int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + // Avoid stupid errors in the beginning. + if (len <= 0) + { + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // NOTE: This is a "vector of list iterators". Every element here + // is an iterator to another container. + // Note that "list" is THE ONLY container in standard C++ library, + // for which NO ITERATORS ARE INVALIDATED after a node at particular + // iterator has been removed, except for that iterator itself. + vector wipeme; + vector idleLinks; + vector pendingSockets; // need sock ids as it will be checked out of lock + + int32_t curseq = SRT_SEQNO_NONE; // The seqno of the first packet of this message. + int32_t nextseq = SRT_SEQNO_NONE; // The seqno of the first packet of next message. + + int rstat = -1; + + int stat = 0; + SRT_ATR_UNUSED CUDTException cx(MJ_SUCCESS, MN_NONE, 0); + + vector activeLinks; + + // First, acquire GlobControlLock to make sure all member sockets still exist + enterCS(m_Global.m_GlobControlLock); + ScopedLock guard(m_GroupLock); + + if (m_bClosing) + { + leaveCS(m_Global.m_GlobControlLock); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Now, still under lock, check if all sockets still can be dispatched + + // LOCKED: GlobControlLock, GroupLock (RIGHT ORDER!) + send_CheckValidSockets(); + leaveCS(m_Global.m_GlobControlLock); + // LOCKED: GroupLock (only) + // Since this moment GlobControlLock may only be locked if GroupLock is unlocked first. + + if (m_bClosing) + { + // No temporary locks here. The group lock is scoped. + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // This simply requires the payload to be sent through every socket in the group + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) + { + if (d->sndstate != SRT_GST_BROKEN) + { + // Check the socket state prematurely in order not to uselessly + // send over a socket that is broken. + CUDT* const pu = (d->ps) + ? &d->ps->core() + : NULL; + + if (!pu || pu->m_bBroken) + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: socket @" << d->id << " detected +Broken - transit to BROKEN"); + d->sndstate = SRT_GST_BROKEN; + d->rcvstate = SRT_GST_BROKEN; + } + } + + // Check socket sndstate before sending + if (d->sndstate == SRT_GST_BROKEN) + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: socket in BROKEN state: @" << d->id + << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); + wipeme.push_back(d->id); + continue; + } + + if (d->sndstate == SRT_GST_IDLE) + { + SRT_SOCKSTATUS st = SRTS_NONEXIST; + if (d->ps) + st = d->ps->getStatus(); + // If the socket is already broken, move it to broken. + if (int(st) >= int(SRTS_BROKEN)) + { + HLOGC(gslog.Debug, + log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " << SockStatusStr(st) + << ", WILL BE CLOSED."); + wipeme.push_back(d->id); + continue; + } + + if (st != SRTS_CONNECTED) + { + HLOGC(gslog.Debug, + log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); + pendingSockets.push_back(d->id); + continue; + } + + HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket in IDLE state: @" << d->id << " - will activate it"); + // This is idle, we'll take care of them next time + // Might be that: + // - this socket is idle, while some NEXT socket is running + // - we need at least one running socket to work BEFORE activating the idle one. + // - if ALL SOCKETS ARE IDLE, then we simply activate the first from the list, + // and all others will be activated using the ISN from the first one. + idleLinks.push_back(d); + continue; + } + + if (d->sndstate == SRT_GST_RUNNING) + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: socket in RUNNING state: @" << d->id << " - will send a payload"); + activeLinks.push_back(d); + continue; + } + + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" + << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); + + pendingSockets.push_back(d->id); + } + + vector sendstates; + if (w_mc.srctime == 0) + w_mc.srctime = count_microseconds(steady_clock::now().time_since_epoch()); + + for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) + { + gli_t d = *snd; + int erc = 0; // success + // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. + try + { + // This must be wrapped in try-catch because on error it throws an exception. + // Possible return values are only 0, in case when len was passed 0, or a positive + // >0 value that defines the size of the data that it has sent, that is, in case + // of Live mode, equal to 'len'. + stat = d->ps->core().sendmsg2(buf, len, (w_mc)); + } + catch (CUDTException& e) + { + cx = e; + stat = -1; + erc = e.getErrorCode(); + } + + if (stat != -1) + { + curseq = w_mc.pktseq; + nextseq = d->ps->core().schedSeqNo(); + } + + const Sendstate cstate = {d->id, &*d, stat, erc}; + sendstates.push_back(cstate); + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); + } + + // Ok, we have attempted to send a payload over all links + // that are currently in the RUNNING state. We know that at + // least one is successful if we have non-default curseq value. + + // Here we need to activate all links that are found as IDLE. + // Some portion of logical exclusions: + // + // - sockets that were broken in the beginning are already wiped out + // - broken sockets are checked first, so they can't be simultaneously idle + // - idle sockets can't get broken because there's no operation done on them + // - running sockets are the only one that could change sndstate here + // - running sockets can either remain running or turn to broken + // In short: Running and Broken sockets can't become idle, + // although Running sockets can become Broken. + + // There's no certainty here as to whether at least one link was + // running and it has successfully performed the operation. + // Might have even happened that we had 2 running links that + // got broken and 3 other links so far in idle sndstate that just connected + // at that very moment. In this case we have 3 idle links to activate, + // but there is no sequence base to overwrite their ISN with. If this + // happens, then the first link that should be activated goes with + // whatever ISN it has, whereas every next idle link should use that + // exactly ISN. + // + // If it has additionally happened that the first link got broken at + // that very moment of sending, the second one has a chance to succeed + // and therefore take over the leading role in setting the ISN. If the + // second one fails, too, then the only remaining idle link will simply + // go with its own original sequence. + // + // On the opposite side the reader should know that the link is inactive + // so the first received payload activates it. Activation of an idle link + // means that the very first packet arriving is TAKEN AS A GOOD DEAL, that is, + // no LOSSREPORT is sent even if the sequence looks like a "jumped over". + // Only for activated links is the LOSSREPORT sent upon seqhole detection. + + // Now we can go to the idle links and attempt to send the payload + // also over them. + + // TODO: { sendBroadcast_ActivateIdleLinks + for (vector::iterator i = idleLinks.begin(); i != idleLinks.end(); ++i) + { + gli_t d = *i; + if (!d->ps->m_GroupOf) + continue; + + int erc = 0; + int lastseq = d->ps->core().schedSeqNo(); + if (curseq != SRT_SEQNO_NONE && curseq != lastseq) + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: socket @" << d->id << ": override snd sequence %" << lastseq << " with %" + << curseq << " (diff by " << CSeqNo::seqcmp(curseq, lastseq) + << "); SENDING PAYLOAD: " << BufferStamp(buf, len)); + d->ps->core().overrideSndSeqNo(curseq); + } + else + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: socket @" << d->id << ": sequence remains with original value: %" + << lastseq << "; SENDING PAYLOAD " << BufferStamp(buf, len)); + } + + // Now send and check the status + // The link could have got broken + + try + { + stat = d->ps->core().sendmsg2(buf, len, (w_mc)); + } + catch (CUDTException& e) + { + cx = e; + stat = -1; + erc = e.getErrorCode(); + } + + if (stat != -1) + { + d->sndstate = SRT_GST_RUNNING; + + // Note: this will override the sequence number + // for all next iterations in this loop. + curseq = w_mc.pktseq; + nextseq = d->ps->core().schedSeqNo(); + HLOGC(gslog.Debug, + log << "@" << d->id << ":... sending SUCCESSFUL %" << curseq << " MEMBER STATUS: RUNNING"); + } + + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); + + const Sendstate cstate = {d->id, &*d, stat, erc}; + sendstates.push_back(cstate); + } + + if (nextseq != SRT_SEQNO_NONE) + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: $" << id() << ": updating current scheduling sequence %" << nextseq); + m_iLastSchedSeqNo = nextseq; + } + + // } + + // { send_CheckBrokenSockets() + + if (!pendingSockets.empty()) + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets, polling them."); + + // These sockets if they are in pending state, they should be added to m_SndEID + // at the connecting stage. + CEPoll::fmap_t sready; + + if (m_Global.m_EPoll.empty(*m_SndEpolld)) + { + // Sanity check - weird pending reported. + LOGC(gslog.Error, + log << "grp/sendBroadcast: IPE: reported pending sockets, but EID is empty - wiping pending!"); + copy(pendingSockets.begin(), pendingSockets.end(), back_inserter(wipeme)); + } + else + { + { + InvertedLock ug(m_GroupLock); + + THREAD_PAUSED(); + m_Global.m_EPoll.swait( + *m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything happened + THREAD_RESUMED(); + } + + if (m_bClosing) + { + // No temporary locks here. The group lock is scoped. + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + HLOGC(gslog.Debug, log << "grp/sendBroadcast: RDY: " << DisplayEpollResults(sready)); + + // sockets in EX: should be moved to wipeme. + for (vector::iterator i = pendingSockets.begin(); i != pendingSockets.end(); ++i) + { + if (CEPoll::isready(sready, *i, SRT_EPOLL_ERR)) + { + HLOGC(gslog.Debug, + log << "grp/sendBroadcast: Socket @" << (*i) << " reported FAILURE - moved to wiped."); + // Failed socket. Move d to wipeme. Remove from eid. + wipeme.push_back(*i); + int no_events = 0; + m_Global.m_EPoll.update_usock(m_SndEID, *i, &no_events); + } + } + + // After that, all sockets that have been reported + // as ready to write should be removed from EID. This + // will also remove those sockets that have been added + // as redundant links at the connecting stage and became + // writable (connected) before this function had a chance + // to check them. + m_Global.m_EPoll.clear_ready_usocks(*m_SndEpolld, SRT_EPOLL_CONNECT); + } + } + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + send_CloseBrokenSockets(wipeme); + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + // } + + // { sendBroadcast_CheckBlockedLinks() + + // Alright, we've made an attempt to send a packet over every link. + // Every operation was done through a non-blocking attempt, so + // links where sending was blocked have SRT_EASYNCSND error. + // Links that were successful, have the len value in state. + + // First thing then, find out if at least one link was successful. + // The first successful link sets the sequence value, + // the following links derive it. This might be also the first idle + // link with its random-generated ISN, if there were no active links. + + vector successful, blocked; + + // This iteration of the state will simply + // qualify the remaining sockets into three categories: + // + // - successful (we only need to know if at least one did) + // - blocked - if none succeeded, but some blocked, POLL & RETRY. + // - wipeme - sending failed by any other reason than blocking, remove. + + // Now - sendstates contain directly sockets. + // In order to update members, you need to have locked: + // - GlobControlLock to prevent sockets from disappearing or being closed + // - then GroupLock to latch the validity of m_GroupMemberData field. + + { + { + InvertedLock ung (m_GroupLock); + enterCS(CUDT::uglobal().m_GlobControlLock); + HLOGC(gslog.Debug, log << "grp/sendBroadcast: Locked GlobControlLock, locking back GroupLock"); + } + + // Under this condition, as an unlock-lock cycle was done on m_GroupLock, + // the Sendstate::it field shall not be used here! + for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is) + { + CUDTSocket* ps = CUDT::uglobal().locateSocket_LOCKED(is->id); + + // Is the socket valid? If not, simply SKIP IT. Nothing to be done with it, + // it's already deleted. + if (!ps) + continue; + + // Is the socket still group member? If not, SKIP IT. It could only be taken ownership + // by being explicitly closed and so it's deleted from the container. + if (!ps->m_GroupOf) + continue; + + // Now we are certain that m_GroupMemberData is valid. + SocketData* d = ps->m_GroupMemberData; + + if (is->stat == len) + { + HLOGC(gslog.Debug, + log << "SEND STATE link [" << (is - sendstates.begin()) << "]: SUCCESSFULLY sent " << len + << " bytes"); + // Successful. + successful.push_back(d); + rstat = is->stat; + continue; + } + + // Remaining are only failed. Check if again. + if (is->code == SRT_EASYNCSND) + { + blocked.push_back(d); + continue; + } + +#if ENABLE_HEAVY_LOGGING + string errmsg = cx.getErrorString(); + LOGC(gslog.Debug, + log << "SEND STATE link [" << (is - sendstates.begin()) << "]: FAILURE (result:" << is->stat + << "): " << errmsg << ". Setting this socket broken status."); +#endif + // Turn this link broken + d->sndstate = SRT_GST_BROKEN; + } + + // Now you can leave GlobControlLock, while GroupLock is still locked. + leaveCS(CUDT::uglobal().m_GlobControlLock); + } + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: GROUP CLOSED, ABANDONING"); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Good, now let's realize the situation. + // First, check the most optimistic scenario: at least one link succeeded. + + bool was_blocked = false; + bool none_succeeded = false; + + if (!successful.empty()) + { + // Good. All blocked links are now qualified as broken. + // You had your chance, but I can't leave you here, + // there will be no further chance to reattempt sending. + for (vector::iterator b = blocked.begin(); b != blocked.end(); ++b) + { + (*b)->sndstate = SRT_GST_BROKEN; + } + blocked.clear(); + } + else + { + none_succeeded = true; + was_blocked = !blocked.empty(); + } + + int ercode = 0; + + if (was_blocked) + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + if (!m_bSynSending) + { + throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); + } + + HLOGC(gslog.Debug, log << "grp/sendBroadcast: all blocked, trying to common-block on epoll..."); + + // XXX TO BE REMOVED. Sockets should be subscribed in m_SndEID at connecting time + // (both srt_connect and srt_accept). + + // None was successful, but some were blocked. It means that we + // haven't sent the payload over any link so far, so we still have + // a chance to retry. + int modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; + for (vector::iterator b = blocked.begin(); b != blocked.end(); ++b) + { + HLOGC(gslog.Debug, + log << "Will block on blocked socket @" << (*b)->id << " as only blocked socket remained"); + CUDT::uglobal().epoll_add_usock_INTERNAL(m_SndEID, (*b)->ps, &modes); + } + + int blst = 0; + CEPoll::fmap_t sready; + + { + // Lift the group lock for a while, to avoid possible deadlocks. + InvertedLock ug(m_GroupLock); + HLOGC(gslog.Debug, log << "grp/sendBroadcast: blocking on any of blocked sockets to allow sending"); + + // m_iSndTimeOut is -1 by default, which matches the meaning of waiting forever + THREAD_PAUSED(); + blst = m_Global.m_EPoll.swait(*m_SndEpolld, sready, m_iSndTimeOut); + THREAD_RESUMED(); + + // NOTE EXCEPTIONS: + // - EEMPTY: won't happen, we have explicitly added sockets to EID here. + // - XTIMEOUT: will be propagated as this what should be reported to API + // This is the only reason why here the errors are allowed to be handled + // by exceptions. + } + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + if (blst == -1) + { + int rno; + ercode = srt_getlasterror(&rno); + } + else + { + activeLinks.clear(); + sendstates.clear(); + // Extract gli's from the whole group that have id found in the array. + + // LOCKING INFO: + // For the moment of lifting m_GroupLock, some sockets could have been closed. + // But then, we believe they have been also removed from the group container, + // and this requires locking on GroupLock. We can then stafely state that the + // group container contains only existing sockets, at worst broken. + + for (gli_t dd = m_Group.begin(); dd != m_Group.end(); ++dd) + { + int rdev = CEPoll::ready(sready, dd->id); + if (rdev & SRT_EPOLL_ERR) + { + dd->sndstate = SRT_GST_BROKEN; + } + else if (rdev & SRT_EPOLL_OUT) + activeLinks.push_back(dd); + } + + for (vector::iterator snd = activeLinks.begin(); snd != activeLinks.end(); ++snd) + { + gli_t d = *snd; + + int erc = 0; // success + // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. + try + { + // This must be wrapped in try-catch because on error it throws an exception. + // Possible return values are only 0, in case when len was passed 0, or a positive + // >0 value that defines the size of the data that it has sent, that is, in case + // of Live mode, equal to 'len'. + stat = d->ps->core().sendmsg2(buf, len, (w_mc)); + } + catch (CUDTException& e) + { + cx = e; + stat = -1; + erc = e.getErrorCode(); + } + if (stat != -1) + curseq = w_mc.pktseq; + + const Sendstate cstate = {d->id, &*d, stat, erc}; + sendstates.push_back(cstate); + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); + } + + // This time only check if any were successful. + // All others are wipeme. + // NOTE: m_GroupLock is continuously locked - you can safely use Sendstate::it field. + for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is) + { + if (is->stat == len) + { + // Successful. + successful.push_back(is->mb); + rstat = is->stat; + was_blocked = false; + none_succeeded = false; + continue; + } +#if ENABLE_HEAVY_LOGGING + string errmsg = cx.getErrorString(); + HLOGC(gslog.Debug, + log << "... (repeat-waited) sending FAILED (" << errmsg + << "). Setting this socket broken status."); +#endif + // Turn this link broken + is->mb->sndstate = SRT_GST_BROKEN; + } + } + } + + // } + + if (none_succeeded) + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)"); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + // Reparse error code, if set. + // It might be set, if the last operation was failed. + // If any operation succeeded, this will not be executed anyway. + CodeMajor major = CodeMajor(ercode ? ercode / 1000 : MJ_CONNECTION); + CodeMinor minor = CodeMinor(ercode ? ercode % 1000 : MN_CONNLOST); + + throw CUDTException(major, minor, 0); + } + + // Now that at least one link has succeeded, update sending stats. + m_stats.sent.count(len); + + // Pity that the blocking mode only determines as to whether this function should + // block or not, but the epoll flags must be updated regardless of the mode. + + // Now fill in the socket table. Check if the size is enough, if not, + // then set the pointer to NULL and set the correct size. + + // Note that list::size() is linear time, however this shouldn't matter, + // as with the increased number of links in the redundancy group the + // impossibility of using that many of them grows exponentally. + size_t grpsize = m_Group.size(); + + if (w_mc.grpdata_size < grpsize) + { + w_mc.grpdata = NULL; + } + + size_t i = 0; + + bool ready_again = false; + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) + { + if (w_mc.grpdata) + { + // Enough space to fill + copyGroupData(*d, (w_mc.grpdata[i])); + } + + // We perform this loop anyway because we still need to check if any + // socket is writable. Note that the group lock will hold any write ready + // updates that are performed just after a single socket update for the + // group, so if any socket is actually ready at the moment when this + // is performed, and this one will result in none-write-ready, this will + // be fixed just after returning from this function. + + ready_again = ready_again || d->ps->writeReady(); + } + w_mc.grpdata_size = i; + + if (!ready_again) + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + } + + return rstat; +} + +int CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) +{ + if (!psize) + return CUDT::APIError(MJ_NOTSUP, MN_INVAL); + + ScopedLock gl(m_GroupLock); + + return getGroupData_LOCKED(pdata, psize); +} + +// [[using locked(this->m_GroupLock)]] +int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) +{ + SRT_ASSERT(psize != NULL); + const size_t size = *psize; + // Rewrite correct size + *psize = m_Group.size(); + + if (!pdata) + { + return 0; + } + + if (m_Group.size() > size) + { + // Not enough space to retrieve the data. + return CUDT::APIError(MJ_NOTSUP, MN_XSIZE); + } + + size_t i = 0; + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) + { + copyGroupData(*d, (pdata[i])); + } + + return m_Group.size(); +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUPDATA& w_target) +{ + w_target.id = source.id; + memcpy((&w_target.peeraddr), &source.peer, source.peer.size()); + + w_target.sockstate = source.laststatus; + w_target.token = source.token; + + // In the internal structure the member state + // is one per direction. From the user perspective + // however it is used either in one direction only, + // in which case the one direction that is active + // matters, or in both directions, in which case + // it will be always either both active or both idle. + + if (source.sndstate == SRT_GST_RUNNING || source.rcvstate == SRT_GST_RUNNING) + { + w_target.result = 0; + w_target.memberstate = SRT_GST_RUNNING; + } + // Stats can differ per direction only + // when at least in one direction it's ACTIVE. + else if (source.sndstate == SRT_GST_BROKEN || source.rcvstate == SRT_GST_BROKEN) + { + w_target.result = -1; + w_target.memberstate = SRT_GST_BROKEN; + } + else + { + // IDLE or PENDING + w_target.result = 0; + w_target.memberstate = source.sndstate; + } + + w_target.weight = source.weight; +} + +void CUDTGroup::getGroupCount(size_t& w_size, bool& w_still_alive) +{ + ScopedLock gg(m_GroupLock); + + // Note: linear time, but no way to avoid it. + // Fortunately the size of the redundancy group is even + // in the craziest possible implementation at worst 4 members long. + size_t group_list_size = 0; + + // In managed group, if all sockets made a failure, all + // were removed, so the loop won't even run once. In + // non-managed, simply no socket found here would have a + // connected status. + bool still_alive = false; + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->laststatus == SRTS_CONNECTED) + { + still_alive = true; + } + ++group_list_size; + } + + // If no socket is found connected, don't update any status. + w_size = group_list_size; + w_still_alive = still_alive; +} + +// [[using locked(m_GroupLock)]] +void CUDTGroup::fillGroupData(SRT_MSGCTRL& w_out, // MSGCTRL to be written + const SRT_MSGCTRL& in // MSGCTRL read from the data-providing socket +) +{ + // Preserve the data that will be overwritten by assignment + SRT_SOCKGROUPDATA* grpdata = w_out.grpdata; + size_t grpdata_size = w_out.grpdata_size; + + w_out = in; // NOTE: This will write NULL to grpdata and 0 to grpdata_size! + + w_out.grpdata = NULL; // Make sure it's done, for any case + w_out.grpdata_size = 0; + + // User did not wish to read the group data at all. + if (!grpdata) + { + return; + } + + int st = getGroupData_LOCKED((grpdata), (&grpdata_size)); + + // Always write back the size, no matter if the data were filled. + w_out.grpdata_size = grpdata_size; + + if (st == SRT_ERROR) + { + // Keep NULL in grpdata + return; + } + + // Write back original data + w_out.grpdata = grpdata; +} + +// [[using locked(CUDT::uglobal()->m_GlobControLock)]] +// [[using locked(m_GroupLock)]] +struct FLookupSocketWithEvent_LOCKED +{ + CUDTUnited* glob; + int evtype; + FLookupSocketWithEvent_LOCKED(CUDTUnited* g, int event_type) + : glob(g) + , evtype(event_type) + { + } + + typedef CUDTSocket* result_type; + + pair operator()(const pair& es) + { + CUDTSocket* so = NULL; + if ((es.second & evtype) == 0) + return make_pair(so, false); + + so = glob->locateSocket_LOCKED(es.first); + return make_pair(so, !!so); + } +}; + +void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) +{ +#if ENABLE_HEAVY_LOGGING + std::ostringstream ds; + ds << "E(" << m_RcvEID << ") "; +#define HCLOG(expr) expr +#else +#define HCLOG(x) if (false) {} +#endif + + alive.reserve(m_Group.size()); + + HLOGC(grlog.Debug, log << "group/recv: Reviewing member sockets for polling"); + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->laststatus == SRTS_CONNECTING) + { + HCLOG(ds << "@" << gi->id << " "); + continue; // don't read over a failed or pending socket + } + + if (gi->laststatus >= SRTS_BROKEN) + { + broken.insert(gi->ps); + } + + if (broken.count(gi->ps)) + { + HCLOG(ds << "@" << gi->id << " "); + continue; + } + + if (gi->laststatus != SRTS_CONNECTED) + { + HCLOG(ds << "@" << gi->id << "laststatus) << "> "); + // Sockets in this state are ignored. We are waiting until it + // achieves CONNECTING state, then it's added to write. + // Or gets broken and closed in the next step. + continue; + } + + // Don't skip packets that are ahead because if we have a situation + // that all links are either "elephants" (do not report read readiness) + // and "kangaroos" (have already delivered an ahead packet) then + // omiting kangaroos will result in only elephants to be polled for + // reading. Due to the strict timing requirements and ensurance that + // TSBPD on every link will result in exactly the same delivery time + // for a packet of given sequence, having an elephant and kangaroo in + // one cage means that the elephant is simply a broken or half-broken + // link (the data are not delivered, but it will get repaired soon, + // enough for SRT to maintain the connection, but it will still drop + // packets that didn't arrive in time), in both cases it may + // potentially block the reading for an indefinite time, while + // simultaneously a kangaroo might be a link that got some packets + // dropped, but then it's still capable to deliver packets on time. + + // Note that gi->id might be a socket that was previously being polled + // on write, when it's attempting to connect, but now it's connected. + // This will update the socket with the new event set. + + alive.push_back(gi->ps); + HCLOG(ds << "@" << gi->id << "[READ] "); + } + + HLOGC(grlog.Debug, log << "group/recv: " << ds.str() << " --> EPOLL/SWAIT"); +#undef HCLOG +} + +vector CUDTGroup::recv_WaitForReadReady(const vector& aliveMembers, set& w_broken) +{ + if (aliveMembers.empty()) + { + LOGC(grlog.Error, log << "group/recv: all links broken"); + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } + + for (vector::const_iterator i = aliveMembers.begin(); i != aliveMembers.end(); ++i) + { + // NOT using the official srt_epoll_add_usock because this will do socket dispatching, + // which requires lock on m_GlobControlLock, while this lock cannot be applied without + // first unlocking m_GroupLock. + const int read_modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; + CUDT::uglobal().epoll_add_usock_INTERNAL(m_RcvEID, *i, &read_modes); + } + + // Here we need to make an additional check. + // There might be a possibility that all sockets that + // were added to the reader group, are ahead. At least + // surely we don't have a situation that any link contains + // an ahead-read subsequent packet, because GroupCheckPacketAhead + // already handled that case. + // + // What we can have is that every link has: + // - no known seq position yet (is not registered in the position map yet) + // - the position equal to the latest delivered sequence + // - the ahead position + + // Now the situation is that we don't have any packets + // waiting for delivery so we need to wait for any to report one. + + // The non-blocking mode would need to simply check the readiness + // with only immediate report, and read-readiness would have to + // be done in background. + + // In blocking mode, use m_iRcvTimeOut, which's default value -1 + // means to block indefinitely, also in swait(). + // In non-blocking mode use 0, which means to always return immediately. + int timeout = m_bSynRecving ? m_iRcvTimeOut : 0; + int nready = 0; + // Poll on this descriptor until reading is available, indefinitely. + CEPoll::fmap_t sready; + + // GlobControlLock is required for dispatching the sockets. + // Therefore it must be applied only when GroupLock is off. + { + // This call may wait indefinite time, so GroupLock must be unlocked. + InvertedLock ung (m_GroupLock); + THREAD_PAUSED(); + nready = m_Global.m_EPoll.swait(*m_RcvEpolld, sready, timeout, false /*report by retval*/); + THREAD_RESUMED(); + + // HERE GlobControlLock is locked first, then GroupLock is applied back + enterCS(CUDT::uglobal().m_GlobControlLock); + } + // BOTH m_GlobControlLock AND m_GroupLock are locked here. + + HLOGC(grlog.Debug, log << "group/recv: " << nready << " RDY: " << DisplayEpollResults(sready)); + + if (nready == 0) + { + // GlobControlLock is applied manually, so unlock manually. + // GroupLock will be unlocked as per scope. + leaveCS(CUDT::uglobal().m_GlobControlLock); + // This can only happen when 0 is passed as timeout and none is ready. + // And 0 is passed only in non-blocking mode. So this is none ready in + // non-blocking mode. + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + + // Handle sockets of pending connection and with errors. + + // Nice to have something like: + + // broken = FilterIf(sready, [] (auto s) + // { return s.second == SRT_EPOLL_ERR && (auto cs = g->locateSocket(s.first, ERH_RETURN)) + // ? {cs, true} + // : {nullptr, false} + // }); + + FilterIf( + /*FROM*/ sready.begin(), + sready.end(), + /*TO*/ std::inserter(w_broken, w_broken.begin()), + /*VIA*/ FLookupSocketWithEvent_LOCKED(&m_Global, SRT_EPOLL_ERR)); + + + // If this set is empty, it won't roll even once, therefore output + // will be surely empty. This will be checked then same way as when + // reading from every socket resulted in error. + vector readReady; + readReady.reserve(aliveMembers.size()); + for (vector::const_iterator sockiter = aliveMembers.begin(); sockiter != aliveMembers.end(); ++sockiter) + { + CUDTSocket* sock = *sockiter; + const CEPoll::fmap_t::const_iterator ready_iter = sready.find(sock->m_SocketID); + if (ready_iter != sready.end()) + { + if (ready_iter->second & SRT_EPOLL_ERR) + continue; // broken already + + if ((ready_iter->second & SRT_EPOLL_IN) == 0) + continue; // not ready for reading + + readReady.push_back(*sockiter); + } + else + { + // No read-readiness reported by epoll, but probably missed or not yet handled + // as the receiver buffer is read-ready. + ScopedLock lg(sock->core().m_RcvBufferLock); + if (sock->core().m_pRcvBuffer && sock->core().m_pRcvBuffer->isRcvDataReady()) + readReady.push_back(sock); + } + } + + leaveCS(CUDT::uglobal().m_GlobControlLock); + + return readReady; +} + +void CUDTGroup::updateReadState(SRTSOCKET /* not sure if needed */, int32_t sequence) +{ + bool ready = false; + ScopedLock lg(m_GroupLock); + int seqdiff = 0; + + if (m_RcvBaseSeqNo == SRT_SEQNO_NONE) + { + // One socket reported readiness, while no reading operation + // has ever been done. Whatever the sequence number is, it will + // be taken as a good deal and reading will be accepted. + ready = true; + } + else if ((seqdiff = CSeqNo::seqcmp(sequence, m_RcvBaseSeqNo)) > 0) + { + // Case diff == 1: The very next. Surely read-ready. + + // Case diff > 1: + // We have an ahead packet. There's one strict condition in which + // we may believe it needs to be delivered - when KANGAROO->HORSE + // transition is allowed. Stating that the time calculation is done + // exactly the same way on every link in the redundancy group, when + // it came to a situation that a packet from one link is ready for + // extraction while it has jumped over some packet, it has surely + // happened due to TLPKTDROP, and if it happened on at least one link, + // we surely don't have this packet ready on any other link. + + // This might prove not exactly true, especially when at the moment + // when this happens another link may surprisinly receive this lacking + // packet, so the situation gets suddenly repaired after this function + // is called, the only result of it would be that it will really get + // the very next sequence, even though this function doesn't know it + // yet, but surely in both cases the situation is the same: the medium + // is ready for reading, no matter what packet will turn out to be + // returned when reading is done. + + ready = true; + } + + // When the sequence number is behind the current one, + // stating that the readines wasn't checked otherwise, the reading + // function will not retrieve anything ready to read just by this premise. + // Even though this packet would have to be eventually extracted (and discarded). + + if (ready) + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, true); + } +} + +int32_t CUDTGroup::getRcvBaseSeqNo() +{ + ScopedLock lg(m_GroupLock); + return m_RcvBaseSeqNo; +} + +void CUDTGroup::updateWriteState() +{ + ScopedLock lg(m_GroupLock); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); +} + +/// Validate iPktSeqno is in range +/// (iBaseSeqno - m_iSeqNoTH/2; iBaseSeqno + m_iSeqNoTH). +/// +/// EXPECT_EQ(isValidSeqno(125, 124), true); // behind +/// EXPECT_EQ(isValidSeqno(125, 125), true); // behind +/// EXPECT_EQ(isValidSeqno(125, 126), true); // the next in order +/// +/// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 2), true); // ahead, but ok. +/// EXPECT_EQ(isValidSeqno(0, 0x3FFFFFFF - 1), false); // too far ahead. +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 2, 0x7FFFFFFF), false); // too far ahead. +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF + 3, 0x7FFFFFFF), true); // ahead, but ok. +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 2), false); // too far (behind) +/// EXPECT_EQ(isValidSeqno(0x3FFFFFFF, 0x1FFFFFFF + 3), true); // behind, but ok +/// EXPECT_EQ(isValidSeqno(0x70000000, 0x0FFFFFFF), true); // ahead, but ok +/// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 2), false); // too far ahead. +/// EXPECT_EQ(isValidSeqno(0x70000000, 0x30000000 - 3), true); // ahead, but ok +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0), true); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x7FFFFFFF), true); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000000), false); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000001), false); +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000002), true); // behind by 536870910 +/// EXPECT_EQ(isValidSeqno(0x0FFFFFFF, 0x70000003), true); +/// +/// @return false if @a iPktSeqno is not inside the valid range; otherwise true. +static bool isValidSeqno(int32_t iBaseSeqno, int32_t iPktSeqno) +{ + const int32_t iLenAhead = CSeqNo::seqlen(iBaseSeqno, iPktSeqno); + if (iLenAhead >= 0 && iLenAhead < CSeqNo::m_iSeqNoTH) + return true; + + const int32_t iLenBehind = CSeqNo::seqlen(iPktSeqno, iBaseSeqno); + if (iLenBehind >= 0 && iLenBehind < CSeqNo::m_iSeqNoTH / 2) + return true; + + return false; +} + +#ifdef ENABLE_NEW_RCVBUFFER +int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) +{ + // First, acquire GlobControlLock to make sure all member sockets still exist + enterCS(m_Global.m_GlobControlLock); + ScopedLock guard(m_GroupLock); + + if (m_bClosing) + { + // The group could be set closing in the meantime, but if + // this is only about to be set by another thread, this thread + // must fist wait for being able to acquire this lock. + // The group will not be deleted now because it is added usage counter + // by this call, but will be released once it exits. + leaveCS(m_Global.m_GlobControlLock); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Now, still under lock, check if all sockets still can be dispatched + send_CheckValidSockets(); + leaveCS(m_Global.m_GlobControlLock); + + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + // Later iteration over it might be less efficient than + // by vector, but we'll also often try to check a single id + // if it was ever seen broken, so that it's skipped. + set broken; + + for (;;) + { + if (!m_bOpened || !m_bConnected) + { + LOGC(grlog.Error, + log << boolalpha << "grp/recv: $" << id() << ": ABANDONING: opened=" << m_bOpened + << " connected=" << m_bConnected); + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } + + vector aliveMembers; + recv_CollectAliveAndBroken(aliveMembers, broken); + if (aliveMembers.empty()) + { + LOGC(grlog.Error, log << "grp/recv: ALL LINKS BROKEN, ABANDONING."); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } + + vector readySockets; + if (m_bSynRecving) + readySockets = recv_WaitForReadReady(aliveMembers, broken); + else + readySockets = aliveMembers; + + if (m_bClosing) + { + HLOGC(grlog.Debug, log << "grp/recv: $" << id() << ": GROUP CLOSED, ABANDONING."); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Find the first readable packet among all member sockets. + CUDTSocket* socketToRead = NULL; + CRcvBufferNew::PacketInfo infoToRead = {-1, false, time_point()}; + for (vector::const_iterator si = readySockets.begin(); si != readySockets.end(); ++si) + { + CUDTSocket* ps = *si; + + ScopedLock lg(ps->core().m_RcvBufferLock); + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) + { + // Drop here to make sure the getFirstReadablePacketInfo() below return fresher packet. + int cnt = ps->core().rcvDropTooLateUpTo(CSeqNo::incseq(m_RcvBaseSeqNo)); + if (cnt > 0) + { + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": dropped " << cnt + << " packets before reading: m_RcvBaseSeqNo=" << m_RcvBaseSeqNo); + } + } + + const CRcvBufferNew::PacketInfo info = + ps->core().m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()); + if (info.seqno == SRT_SEQNO_NONE) + { + HLOGC(grlog.Debug, log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": Nothing to read."); + continue; + } + // We need to qualify the sequence, just for a case. + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && !isValidSeqno(m_RcvBaseSeqNo, info.seqno)) + { + LOGC(grlog.Error, + log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": SEQUENCE DISCREPANCY: base=%" + << m_RcvBaseSeqNo << " vs pkt=%" << info.seqno << ", setting ESECFAIL"); + ps->core().m_bBroken = true; + broken.insert(ps); + continue; + } + if (socketToRead == NULL || CSeqNo::seqcmp(info.seqno, infoToRead.seqno) < 0) + { + socketToRead = ps; + infoToRead = info; + } + } + + if (socketToRead == NULL) + { + if (m_bSynRecving) + { + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": No links reported any fresher packet, re-polling."); + continue; + } + else + { + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": No links reported any fresher packet, clearing readiness."); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + } + else + { + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": Found first readable packet from @" << socketToRead->m_SocketID + << ": seq=" << infoToRead.seqno << " gap=" << infoToRead.seq_gap + << " time=" << FormatTime(infoToRead.tsbpd_time)); + } + + const int res = socketToRead->core().receiveMessage((buf), len, (w_mc), CUDTUnited::ERH_RETURN); + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": @" << socketToRead->m_SocketID << ": Extracted data with %" + << w_mc.pktseq << " #" << w_mc.msgno << ": " << (res <= 0 ? "(NOTHING)" : BufferStamp(buf, res))); + if (res == 0) + { + LOGC(grlog.Warn, + log << "grp/recv: $" << id() << ": @" << socketToRead->m_SocketID << ": Retrying next socket..."); + // This socket will not be socketToRead in the next turn because receiveMessage() return 0 here. + continue; + } + if (res == SRT_ERROR) + { + LOGC(grlog.Warn, + log << "grp/recv: $" << id() << ": @" << socketToRead->m_SocketID << ": " << srt_getlasterror_str() + << ". Retrying next socket..."); + broken.insert(socketToRead); + continue; + } + fillGroupData((w_mc), w_mc); + + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": Update m_RcvBaseSeqNo: %" << m_RcvBaseSeqNo << " -> %" << w_mc.pktseq); + m_RcvBaseSeqNo = w_mc.pktseq; + + // Update stats as per delivery + m_stats.recv.count(res); + updateAvgPayloadSize(res); + + for (vector::const_iterator si = aliveMembers.begin(); si != aliveMembers.end(); ++si) + { + CUDTSocket* ps = *si; + ScopedLock lg(ps->core().m_RcvBufferLock); + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) + { + int cnt = ps->core().rcvDropTooLateUpTo(CSeqNo::incseq(m_RcvBaseSeqNo)); + if (cnt > 0) + { + HLOGC(grlog.Debug, + log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": dropped " << cnt + << " packets after reading: m_RcvBaseSeqNo=" << m_RcvBaseSeqNo); + } + } + } + for (vector::const_iterator si = aliveMembers.begin(); si != aliveMembers.end(); ++si) + { + CUDTSocket* ps = *si; + if (!ps->core().isRcvBufferReady()) + m_Global.m_EPoll.update_events(ps->m_SocketID, ps->core().m_sPollID, SRT_EPOLL_IN, false); + } + + return res; + } + LOGC(grlog.Error, log << "grp/recv: UNEXPECTED RUN PATH, ABANDONING."); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); +} +#else +// The "app reader" version of the reading function. +// This reads the packets from every socket treating them as independent +// and prepared to work with the application. Then packets are sorted out +// by getting the sequence number. +int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) +{ + typedef map::iterator pit_t; + // Later iteration over it might be less efficient than + // by vector, but we'll also often try to check a single id + // if it was ever seen broken, so that it's skipped. + set broken; + size_t output_size = 0; + + // First, acquire GlobControlLock to make sure all member sockets still exist + enterCS(m_Global.m_GlobControlLock); + ScopedLock guard(m_GroupLock); + + if (m_bClosing) + { + // The group could be set closing in the meantime, but if + // this is only about to be set by another thread, this thread + // must fist wait for being able to acquire this lock. + // The group will not be deleted now because it is added usage counter + // by this call, but will be released once it exits. + leaveCS(m_Global.m_GlobControlLock); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Now, still under lock, check if all sockets still can be dispatched + send_CheckValidSockets(); + leaveCS(m_Global.m_GlobControlLock); + + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + for (;;) + { + if (!m_bOpened || !m_bConnected) + { + LOGC(grlog.Error, + log << boolalpha << "group/recv: ERROR opened=" << m_bOpened << " connected=" << m_bConnected); + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } + + // Check first the ahead packets if you have any to deliver. + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && !m_Positions.empty()) + { + // This function also updates the group sequence pointer. + ReadPos* pos = checkPacketAhead(); + if (pos) + { + if (size_t(len) < pos->packet.size()) + throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); + + HLOGC(grlog.Debug, + log << "group/recv: delivering AHEAD packet %" << pos->mctrl.pktseq << " #" << pos->mctrl.msgno + << ": " << BufferStamp(&pos->packet[0], pos->packet.size())); + memcpy(buf, &pos->packet[0], pos->packet.size()); + fillGroupData((w_mc), pos->mctrl); + m_RcvBaseSeqNo = pos->mctrl.pktseq; + len = pos->packet.size(); + pos->packet.clear(); + + // Update stats as per delivery + m_stats.recv.count(len); + updateAvgPayloadSize(len); + + // We predict to have only one packet ahead, others are pending to be reported by tsbpd. + // This will be "re-enabled" if the later check puts any new packet into ahead. + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + + return len; + } + } + + // LINK QUALIFICATION NAMES: + // + // HORSE: Correct link, which delivers the very next sequence. + // Not necessarily this link is currently active. + // + // KANGAROO: Got some packets dropped and the sequence number + // of the packet jumps over the very next sequence and delivers + // an ahead packet. + // + // ELEPHANT: Is not ready to read, while others are, or reading + // up to the current latest delivery sequence number does not + // reach this sequence and the link becomes non-readable earlier. + + // The above condition has ruled out one kangaroo and turned it + // into a horse. + + // Below there's a loop that will try to extract packets. Kangaroos + // will be among the polled ones because skipping them risks that + // the elephants will take over the reading. Links already known as + // elephants will be also polled in an attempt to revitalize the + // connection that experienced just a short living choking. + // + // After polling we attempt to read from every link that reported + // read-readiness and read at most up to the sequence equal to the + // current delivery sequence. + + // Links that deliver a packet below that sequence will be retried + // until they deliver no more packets or deliver the packet of + // expected sequence. Links that don't have a record in m_Positions + // and report readiness will be always read, at least to know what + // sequence they currently stand on. + // + // Links that are already known as kangaroos will be polled, but + // no reading attempt will be done. If after the reading series + // it will turn out that we have no more horses, the slowest kangaroo + // will be "upgraded to a horse" (the ahead link with a sequence + // closest to the current delivery sequence will get its sequence + // set as current delivered and its recorded ahead packet returned + // as the read packet). + + // If we find at least one horse, the packet read from that link + // will be delivered. All other link will be just ensured update + // up to this sequence number, or at worst all available packets + // will be read. In this case all kangaroos remain kangaroos, + // until the current delivery sequence m_RcvBaseSeqNo will be lifted + // to the sequence recorded for these links in m_Positions, + // during the next time ahead check, after which they will become + // horses. + + const size_t size = m_Group.size(); + + // Prepare first the list of sockets to be added as connect-pending + // and as read-ready, then unlock the group, and then add them to epoll. + vector aliveMembers; + recv_CollectAliveAndBroken(aliveMembers, broken); + + const vector ready_sockets = recv_WaitForReadReady(aliveMembers, broken); + // m_GlobControlLock lifted, m_GroupLock still locked. + // Now we can safely do this scoped way. + + if (!m_bSynRecving && ready_sockets.empty()) + { + HLOGC(grlog.Debug, + log << "group/rcv $" << m_GroupID << ": Not available AT THIS TIME, NOT READ-READY now."); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + + // Ok, now we need to have some extra qualifications: + // 1. If a socket has no registry yet, we read anyway, just + // to notify the current position. We read ONLY ONE PACKET this time, + // we'll worry later about adjusting it to the current group sequence + // position. + // 2. If a socket is already position ahead, DO NOT read from it, even + // if it is ready. + + // The state of things whether we were able to extract the very next + // sequence will be simply defined by the fact that `output` is nonempty. + + int32_t next_seq = m_RcvBaseSeqNo; + + if (m_bClosing) + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: GROUP CLOSED, ABANDONING"); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + // + // NOTE: Although m_GlobControlLock is lifted here so potentially sockets + // colected in ready_sockets could be closed at any time, all of them are member + // sockets of this group. Therefore the first socket attempted to be closed will + // have to remove the socket from the group, and this will require lock on GroupLock, + // which is still applied here. So this will have to wait for this function to finish + // (or block on swait, in which case the lock is lifted) anyway. + + for (vector::const_iterator si = ready_sockets.begin(); si != ready_sockets.end(); ++si) + { + CUDTSocket* ps = *si; + SRTSOCKET id = ps->m_SocketID; + ReadPos* p = NULL; + pit_t pe = m_Positions.find(id); + if (pe != m_Positions.end()) + { + p = &pe->second; + + // Possible results of comparison: + // x < 0: the sequence is in the past, the socket should be adjusted FIRST + // x = 0: the socket should be ready to get the exactly next packet + // x = 1: the case is already handled by GroupCheckPacketAhead. + // x > 1: AHEAD. DO NOT READ. + const int seqdiff = CSeqNo::seqcmp(p->mctrl.pktseq, m_RcvBaseSeqNo); + if (seqdiff > 1) + { + HLOGC(grlog.Debug, + log << "group/recv: EPOLL: @" << id << " %" << p->mctrl.pktseq << " AHEAD %" << m_RcvBaseSeqNo + << ", not reading."); + continue; + } + } + else + { + // The position is not known, so get the position on which + // the socket is currently standing. + pair ee = m_Positions.insert(make_pair(id, ReadPos(ps->core().m_iRcvLastSkipAck))); + p = &(ee.first->second); + HLOGC(grlog.Debug, + log << "group/recv: EPOLL: @" << id << " %" << p->mctrl.pktseq << " NEW SOCKET INSERTED"); + } + + // Read from this socket stubbornly, until: + // - reading is no longer possible (AGAIN) + // - the sequence difference is >= 1 + + for (;;) + { + SRT_MSGCTRL mctrl = srt_msgctrl_default; + + // Read the data into the user's buffer. This is an optimistic + // prediction that we'll read the right data. This will be overwritten + // by "more correct data" if found more appropriate later. But we have to + // copy these data anyway anywhere, even if they need to fall on the floor later. + int stat; + char extrabuf[SRT_LIVE_MAX_PLSIZE]; + char* msgbuf = NULL; + if (output_size) + { + // We already have the target data in `buf`. Now reading extra data potentially redundant (to be ignored) + // or AHEAD (to be buffered internally by the group) + msgbuf = extrabuf; + stat = ps->core().receiveMessage((extrabuf), SRT_LIVE_MAX_PLSIZE, (mctrl), CUDTUnited::ERH_RETURN); + HLOGC(grlog.Debug, + log << "group/recv: @" << id << " EXTRACTED EXTRA data with %" << mctrl.pktseq + << " #" << mctrl.msgno << ": " << (stat <= 0 ? "(NOTHING)" : BufferStamp(extrabuf, stat)) + << (CSeqNo::seqcmp(mctrl.pktseq, m_RcvBaseSeqNo) > 1 ? " - TO STORE" : " - TO IGNORE")); + } + else + { + msgbuf = buf; + stat = ps->core().receiveMessage((buf), len, (mctrl), CUDTUnited::ERH_RETURN); + HLOGC(grlog.Debug, + log << "group/recv: @" << id << " EXTRACTED data with %" << mctrl.pktseq << " #" + << mctrl.msgno << ": " << (stat <= 0 ? "(NOTHING)" : BufferStamp(buf, stat))); + } + if (stat == 0) + { + HLOGC(grlog.Debug, log << "group/recv @" << id << ": SPURIOUS epoll, ignoring"); + // This is returned in case of "again". In case of errors, we have SRT_ERROR. + // Do not treat this as spurious, just stop reading. + break; + } + + if (stat == SRT_ERROR) + { + HLOGC(grlog.Debug, log << "group/recv: @" << id << ": " << srt_getlasterror_str()); + broken.insert(ps); + break; + } + + // NOTE: checks against m_RcvBaseSeqNo and decisions based on it + // must NOT be done if m_RcvBaseSeqNo is SRT_SEQNO_NONE, which + // means that we are about to deliver the very first packet and we + // take its sequence number as a good deal. + + // The order must be: + // - check discrepancy + // - record the sequence + // - check ordering. + // The second one must be done always, but failed discrepancy + // check should exclude the socket from any further checks. + // That's why the common check for m_RcvBaseSeqNo != SRT_SEQNO_NONE can't + // embrace everything below. + + // We need to first qualify the sequence, just for a case + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && !isValidSeqno(m_RcvBaseSeqNo, mctrl.pktseq)) + { + // This error should be returned if the link turns out + // to be the only one, or set to the group data. + // err = SRT_ESECFAIL; + LOGC(grlog.Error, + log << "group/recv: @" << id << ": SEQUENCE DISCREPANCY: base=%" << m_RcvBaseSeqNo + << " vs pkt=%" << mctrl.pktseq << ", setting ESECFAIL"); + broken.insert(ps); + break; + } + + // Rewrite it to the state for a case when next reading + // would not succeed. Do not insert the buffer here because + // this is only required when the sequence is ahead; for that + // it will be fixed later. + p->mctrl.pktseq = mctrl.pktseq; + + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) + { + // Now we can safely check it. + const int seqdiff = CSeqNo::seqcmp(mctrl.pktseq, m_RcvBaseSeqNo); + + if (seqdiff <= 0) + { + HLOGC(grlog.Debug, + log << "group/recv: @" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno + << " BEHIND base=%" << m_RcvBaseSeqNo << " - discarding"); + // The sequence is recorded, the packet has to be discarded. + m_stats.recvDiscard.count(stat); + continue; + } + + // Now we have only two possibilities: + // seqdiff == 1: The very next sequence, we want to read and return the packet. + // seqdiff > 1: The packet is ahead - record the ahead packet, but continue with the others. + + if (seqdiff > 1) + { + HLOGC(grlog.Debug, + log << "@" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " AHEAD base=%" + << m_RcvBaseSeqNo); + p->packet.assign(msgbuf, msgbuf + stat); + p->mctrl = mctrl; + break; // Don't read from that socket anymore. + } + } + + // We have seqdiff = 1, or we simply have the very first packet + // which's sequence is taken as a good deal. Update the sequence + // and record output. + + if (output_size) + { + HLOGC(grlog.Debug, + log << "group/recv: @" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " REDUNDANT"); + break; + } + + HLOGC(grlog.Debug, + log << "group/recv: @" << id << " %" << mctrl.pktseq << " #" << mctrl.msgno << " DELIVERING"); + output_size = stat; + fillGroupData((w_mc), mctrl); + + // Update stats as per delivery + m_stats.recv.count(output_size); + updateAvgPayloadSize(output_size); + + // Record, but do not update yet, until all sockets are handled. + next_seq = mctrl.pktseq; + break; + } + } + +#if ENABLE_HEAVY_LOGGING + if (!broken.empty()) + { + std::ostringstream brks; + for (set::iterator b = broken.begin(); b != broken.end(); ++b) + brks << "@" << (*b)->m_SocketID << " "; + LOGC(grlog.Debug, log << "group/recv: REMOVING BROKEN: " << brks.str()); + } +#endif + + vector brokenid; + // Now remove all broken sockets from aheads, if any. + // Even if they have already delivered a packet. + for (set::iterator di = broken.begin(); di != broken.end(); ++di) + { + CUDTSocket* ps = *di; + m_Positions.erase(ps->m_SocketID); + //ps->setBrokenClosed(); + } + + // Force closing + { + InvertedLock ung (m_GroupLock); + for (set::iterator b = broken.begin(); b != broken.end(); ++b) + { + CUDT::uglobal().close(*b); + } + } + + if (broken.size() >= size) // This > is for sanity check + { + // All broken + HLOGC(grlog.Debug, log << "group/recv: All sockets broken"); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // May be required to be re-read. + broken.clear(); + + if (output_size) + { + // We have extracted something, meaning that we have the sequence shift. + // Update it now and don't do anything else with the sockets. + + // Sanity check + if (next_seq == SRT_SEQNO_NONE) + { + LOGP(grlog.Error, "IPE: next_seq not set after output extracted!"); + + // This should never happen, but the only way to keep the code + // safe an recoverable is to use the incremented sequence. By + // leaving the sequence as is there's a risk of hangup. + // Not doing it in case of SRT_SEQNO_NONE as it would make a valid %0. + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) + m_RcvBaseSeqNo = CSeqNo::incseq(m_RcvBaseSeqNo); + } + else + { + m_RcvBaseSeqNo = next_seq; + } + + const ReadPos* pos = checkPacketAhead(); + if (!pos) + { + // Don't clear the read-readinsess state if you have a packet ahead because + // if you have, the next read call will return it. + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + } + + HLOGC(grlog.Debug, + log << "group/recv: successfully extracted packet size=" << output_size << " - returning"); + return output_size; + } + + HLOGC(grlog.Debug, log << "group/recv: NOT extracted anything - checking for a need to kick kangaroos"); + + // Check if we have any sockets left :D + + // Here we surely don't have any more HORSES, + // only ELEPHANTS and KANGAROOS. Qualify them and + // attempt to at least take advantage of KANGAROOS. + + // In this position all links are either: + // - updated to the current position + // - updated to the newest possible possition available + // - not yet ready for extraction (not present in the group) + + // If we haven't extracted the very next sequence position, + // it means that we might only have the ahead packets read, + // that is, the next sequence has been dropped by all links. + + if (!m_Positions.empty()) + { + // This might notify both lingering links, which didn't + // deliver the required sequence yet, and links that have + // the sequence ahead. Review them, and if you find at + // least one packet behind, just wait for it to be ready. + // Use again the waiting function because we don't want + // the general waiting procedure to skip others. + set elephants; + + // const because it's `typename decltype(m_Positions)::value_type` + pair* slowest_kangaroo = 0; + + for (pit_t rp = m_Positions.begin(); rp != m_Positions.end(); ++rp) + { + // NOTE that m_RcvBaseSeqNo in this place wasn't updated + // because we haven't successfully extracted anything. + int seqdiff = CSeqNo::seqcmp(rp->second.mctrl.pktseq, m_RcvBaseSeqNo); + if (seqdiff < 0) + { + elephants.insert(rp->first); + } + // If seqdiff == 0, we have a socket ON TRACK. + else if (seqdiff > 0) + { + // If there's already a slowest_kangaroo, seqdiff decides if this one is slower. + // Otherwise it is always slower by having no competition. + seqdiff = slowest_kangaroo + ? CSeqNo::seqcmp(slowest_kangaroo->second.mctrl.pktseq, rp->second.mctrl.pktseq) + : 1; + if (seqdiff > 0) + { + slowest_kangaroo = &*rp; + } + } + } + + // Note that if no "slowest_kangaroo" was found, it means + // that we don't have kangaroos. + if (slowest_kangaroo) + { + // We have a slowest kangaroo. Elephants must be ignored. + // Best case, they will get revived, worst case they will be + // soon broken. + // + // As we already have the packet delivered by the slowest + // kangaroo, we can simply return it. + + // Check how many were skipped and add them to the stats + const int32_t jump = (CSeqNo(slowest_kangaroo->second.mctrl.pktseq) - CSeqNo(m_RcvBaseSeqNo)) - 1; + if (jump > 0) + { + m_stats.recvDrop.count(stats::BytesPackets(jump * static_cast(avgRcvPacketSize()), jump)); + LOGC(grlog.Warn, + log << "@" << m_GroupID << " GROUP RCV-DROPPED " << jump << " packet(s): seqno %" + << m_RcvBaseSeqNo << " to %" << slowest_kangaroo->second.mctrl.pktseq); + } + + m_RcvBaseSeqNo = slowest_kangaroo->second.mctrl.pktseq; + vector& pkt = slowest_kangaroo->second.packet; + if (size_t(len) < pkt.size()) + throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); + + HLOGC(grlog.Debug, + log << "@" << slowest_kangaroo->first << " KANGAROO->HORSE %" + << slowest_kangaroo->second.mctrl.pktseq << " #" << slowest_kangaroo->second.mctrl.msgno + << ": " << BufferStamp(&pkt[0], pkt.size())); + + memcpy(buf, &pkt[0], pkt.size()); + fillGroupData((w_mc), slowest_kangaroo->second.mctrl); + len = pkt.size(); + pkt.clear(); + + // Update stats as per delivery + m_stats.recv.count(len); + updateAvgPayloadSize(len); + + // It is unlikely to have a packet ahead because usually having one packet jumped-ahead + // clears the possibility of having aheads at all. + // XXX Research if this is possible at all; if it isn't, then don't waste time on + // looking for it. + const ReadPos* pos = checkPacketAhead(); + if (!pos) + { + // Don't clear the read-readinsess state if you have a packet ahead because + // if you have, the next read call will return it. + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + } + return len; + } + + HLOGC(grlog.Debug, + log << "group/recv: " + << (elephants.empty() ? "NO LINKS REPORTED ANY FRESHER PACKET." : "ALL LINKS ELEPHANTS.") + << " Re-polling."); + } + else + { + HLOGC(grlog.Debug, log << "group/recv: POSITIONS EMPTY - Re-polling."); + } + } +} +#endif + +// [[using locked(m_GroupLock)]] +CUDTGroup::ReadPos* CUDTGroup::checkPacketAhead() +{ + typedef map::iterator pit_t; + ReadPos* out = 0; + + // This map no longer maps only ahead links. + // Here are all links, and whether ahead, it's defined by the sequence. + for (pit_t i = m_Positions.begin(); i != m_Positions.end(); ++i) + { + // i->first: socket ID + // i->second: ReadPos { sequence, packet } + // We are not interested with the socket ID because we + // aren't going to read from it - we have the packet already. + ReadPos& a = i->second; + + const int seqdiff = CSeqNo::seqcmp(a.mctrl.pktseq, m_RcvBaseSeqNo); + if (seqdiff == 1) + { + // The very next packet. Return it. + HLOGC(grlog.Debug, + log << "group/recv: Base %" << m_RcvBaseSeqNo << " ahead delivery POSSIBLE %" << a.mctrl.pktseq + << " #" << a.mctrl.msgno << " from @" << i->first << ")"); + out = &a; + } + else if (seqdiff < 1 && !a.packet.empty()) + { + HLOGC(grlog.Debug, + log << "group/recv: @" << i->first << " dropping collected ahead %" << a.mctrl.pktseq << "#" + << a.mctrl.msgno << " with base %" << m_RcvBaseSeqNo); + a.packet.clear(); + } + // In case when it's >1, keep it in ahead + } + + return out; +} + +const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) +{ + static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; + static const size_t size = Size(states); + static const char* const unknown = "UNKNOWN"; + if (size_t(st) < size) + return states[st]; + return unknown; +} + +void CUDTGroup::synchronizeDrift(const srt::CUDT* srcMember) +{ + SRT_ASSERT(srcMember != NULL); + ScopedLock glock(m_GroupLock); + if (m_Group.size() <= 1) + { + HLOGC(grlog.Debug, log << "GROUP: synch uDRIFT NOT DONE, no other links"); + return; + } + + steady_clock::time_point timebase; + steady_clock::duration udrift(0); + bool wrap_period = false; + srcMember->m_pRcvBuffer->getInternalTimeBase((timebase), (wrap_period), (udrift)); + + HLOGC(grlog.Debug, + log << "GROUP: synch uDRIFT=" << FormatDuration(udrift) << " TB=" << FormatTime(timebase) << "(" + << (wrap_period ? "" : "NO ") << "wrap period)"); + + // Now that we have the minimum timebase and drift calculated, apply this to every link, + // INCLUDING THE REPORTER. + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + // Skip non-connected; these will be synchronized when ready + if (gi->laststatus != SRTS_CONNECTED) + continue; + CUDT& member = gi->ps->core(); + if (srcMember == &member) + continue; + + member.m_pRcvBuffer->applyGroupDrift(timebase, wrap_period, udrift); + } +} + +void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) +{ + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + const steady_clock::time_point currtime = steady_clock::now(); + + memset(perf, 0, sizeof *perf); + + ScopedLock gg(m_GroupLock); + + perf->msTimeStamp = count_milliseconds(currtime - m_tsStartTime); + + perf->pktSentUnique = m_stats.sent.trace.count(); + perf->pktRecvUnique = m_stats.recv.trace.count(); + perf->pktRcvDrop = m_stats.recvDrop.trace.count(); + + perf->byteSentUnique = m_stats.sent.trace.bytesWithHdr(); + perf->byteRecvUnique = m_stats.recv.trace.bytesWithHdr(); + perf->byteRcvDrop = m_stats.recvDrop.trace.bytesWithHdr(); + + perf->pktSentUniqueTotal = m_stats.sent.total.count(); + perf->pktRecvUniqueTotal = m_stats.recv.total.count(); + perf->pktRcvDropTotal = m_stats.recvDrop.total.count(); + + perf->byteSentUniqueTotal = m_stats.sent.total.bytesWithHdr(); + perf->byteRecvUniqueTotal = m_stats.recv.total.bytesWithHdr(); + perf->byteRcvDropTotal = m_stats.recvDrop.total.bytesWithHdr(); + + const double interval = static_cast(count_microseconds(currtime - m_stats.tsLastSampleTime)); + perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; + perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; + + if (clear) + { + m_stats.reset(); + } +} + +/// @brief Compares group members by their weight (higher weight comes first). +struct FCompareByWeight +{ + typedef CUDTGroup::gli_t gli_t; + + /// @returns true if the first argument is less than (i.e. is ordered before) the second. + bool operator()(const gli_t preceding, const gli_t succeeding) + { + return preceding->weight > succeeding->weight; + } +}; + +// [[using maybe_locked(this->m_GroupLock)]] +BackupMemberState CUDTGroup::sendBackup_QualifyIfStandBy(const gli_t d) +{ + if (!d->ps) + return BKUPST_BROKEN; + + const SRT_SOCKSTATUS st = d->ps->getStatus(); + // If the socket is already broken, move it to broken. + if (int(st) >= int(SRTS_BROKEN)) + { + HLOGC(gslog.Debug, + log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " << SockStatusStr(st) + << ", WILL BE CLOSED."); + return BKUPST_BROKEN; + } + + if (st != SRTS_CONNECTED) + { + HLOGC(gslog.Debug, log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); + return BKUPST_PENDING; + } + + return BKUPST_STANDBY; +} + +// [[using maybe_locked(this->m_GroupLock)]] +bool CUDTGroup::send_CheckIdle(const gli_t d, vector& w_wipeme, vector& w_pendingSockets) +{ + SRT_SOCKSTATUS st = SRTS_NONEXIST; + if (d->ps) + st = d->ps->getStatus(); + // If the socket is already broken, move it to broken. + if (int(st) >= int(SRTS_BROKEN)) + { + HLOGC(gslog.Debug, + log << "CUDTGroup::send.$" << id() << ": @" << d->id << " became " << SockStatusStr(st) + << ", WILL BE CLOSED."); + w_wipeme.push_back(d->id); + return false; + } + + if (st != SRTS_CONNECTED) + { + HLOGC(gslog.Debug, log << "CUDTGroup::send. @" << d->id << " is still " << SockStatusStr(st) << ", skipping."); + w_pendingSockets.push_back(d->id); + return false; + } + + return true; +} + + +#if SRT_DEBUG_BONDING_STATES +class StabilityTracer +{ +public: + StabilityTracer() + { + } + + ~StabilityTracer() + { + srt::sync::ScopedLock lck(m_mtx); + m_fout.close(); + } + + void trace(const CUDT& u, const srt::sync::steady_clock::time_point& currtime, uint32_t activation_period_us, + int64_t stability_tmo_us, const std::string& state, uint16_t weight) + { + srt::sync::ScopedLock lck(m_mtx); + create_file(); + + m_fout << srt::sync::FormatTime(currtime) << ","; + m_fout << u.id() << ","; + m_fout << weight << ","; + m_fout << u.peerLatency_us() << ","; + m_fout << u.SRTT() << ","; + m_fout << u.RTTVar() << ","; + m_fout << stability_tmo_us << ","; + m_fout << count_microseconds(currtime - u.lastRspTime()) << ","; + m_fout << state << ","; + m_fout << (srt::sync::is_zero(u.freshActivationStart()) ? -1 : (count_microseconds(currtime - u.freshActivationStart()))) << ","; + m_fout << activation_period_us << "\n"; + m_fout.flush(); + } + +private: + void print_header() + { + //srt::sync::ScopedLock lck(m_mtx); + m_fout << "Timepoint,SocketID,weight,usLatency,usRTT,usRTTVar,usStabilityTimeout,usSinceLastResp,State,usSinceActivation,usActivationPeriod\n"; + } + + void create_file() + { + if (m_fout.is_open()) + return; + + std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); + str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part + while (str_tnow.find(':') != std::string::npos) { + str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); + } + const std::string fname = "stability_trace_" + str_tnow + ".csv"; + m_fout.open(fname, std::ofstream::out); + if (!m_fout) + std::cerr << "IPE: Failed to open " << fname << "!!!\n"; + + print_header(); + } + +private: + srt::sync::Mutex m_mtx; + std::ofstream m_fout; +}; + +StabilityTracer s_stab_trace; +#endif + +void CUDTGroup::sendBackup_QualifyMemberStates(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) +{ + // First, check status of every link - no matter if idle or active. + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) + { + if (d->sndstate != SRT_GST_BROKEN) + { + // Check the socket state prematurely in order not to uselessly + // send over a socket that is broken. + CUDT* const pu = (d->ps) + ? &d->ps->core() + : NULL; + + if (!pu || pu->m_bBroken) + { + HLOGC(gslog.Debug, log << "grp/sendBackup: socket @" << d->id << " detected +Broken - transit to BROKEN"); + d->sndstate = SRT_GST_BROKEN; + d->rcvstate = SRT_GST_BROKEN; + } + } + + // Check socket sndstate before sending + if (d->sndstate == SRT_GST_BROKEN) + { + HLOGC(gslog.Debug, + log << "grp/sendBackup: socket in BROKEN state: @" << d->id + << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); + sendBackup_AssignBackupState(d->ps->core(), BKUPST_BROKEN, currtime); + w_sendBackupCtx.recordMemberState(&(*d), BKUPST_BROKEN); +#if SRT_DEBUG_BONDING_STATES + s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(BKUPST_BROKEN), d->weight); +#endif + continue; + } + + if (d->sndstate == SRT_GST_IDLE) + { + const BackupMemberState idle_state = sendBackup_QualifyIfStandBy(d); + sendBackup_AssignBackupState(d->ps->core(), idle_state, currtime); + w_sendBackupCtx.recordMemberState(&(*d), idle_state); + + if (idle_state == BKUPST_STANDBY) + { + // TODO: Check if this is some abandoned logic. + sendBackup_CheckIdleTime(d); + } +#if SRT_DEBUG_BONDING_STATES + s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(idle_state), d->weight); +#endif + continue; + } + + if (d->sndstate == SRT_GST_RUNNING) + { + const BackupMemberState active_state = sendBackup_QualifyActiveState(d, currtime); + sendBackup_AssignBackupState(d->ps->core(), active_state, currtime); + w_sendBackupCtx.recordMemberState(&(*d), active_state); +#if SRT_DEBUG_BONDING_STATES + s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(active_state), d->weight); +#endif + continue; + } + + HLOGC(gslog.Debug, + log << "grp/sendBackup: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" + << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); + + // Otherwise connection pending + sendBackup_AssignBackupState(d->ps->core(), BKUPST_PENDING, currtime); + w_sendBackupCtx.recordMemberState(&(*d), BKUPST_PENDING); +#if SRT_DEBUG_BONDING_STATES + s_stab_trace.trace(d->ps->core(), currtime, 0, 0, stateToStr(BKUPST_PENDING), d->weight); +#endif + } +} + + +void CUDTGroup::sendBackup_AssignBackupState(CUDT& sock, BackupMemberState state, const steady_clock::time_point& currtime) +{ + switch (state) + { + case BKUPST_PENDING: + case BKUPST_STANDBY: + case BKUPST_BROKEN: + sock.m_tsFreshActivation = steady_clock::time_point(); + sock.m_tsUnstableSince = steady_clock::time_point(); + sock.m_tsWarySince = steady_clock::time_point(); + break; + case BKUPST_ACTIVE_FRESH: + if (is_zero(sock.freshActivationStart())) + { + sock.m_tsFreshActivation = currtime; + } + sock.m_tsUnstableSince = steady_clock::time_point(); + sock.m_tsWarySince = steady_clock::time_point();; + break; + case BKUPST_ACTIVE_STABLE: + sock.m_tsFreshActivation = steady_clock::time_point(); + sock.m_tsUnstableSince = steady_clock::time_point(); + sock.m_tsWarySince = steady_clock::time_point(); + break; + case BKUPST_ACTIVE_UNSTABLE: + if (is_zero(sock.m_tsUnstableSince)) + { + sock.m_tsUnstableSince = currtime; + } + sock.m_tsFreshActivation = steady_clock::time_point(); + sock.m_tsWarySince = steady_clock::time_point(); + break; + case BKUPST_ACTIVE_UNSTABLE_WARY: + if (is_zero(sock.m_tsWarySince)) + { + sock.m_tsWarySince = currtime; + } + break; + default: + break; + } +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_CheckIdleTime(gli_t w_d) +{ + // Check if it was fresh set as idle, we had to wait until its sender + // buffer gets empty so that we can make sure that KEEPALIVE will be the + // really last sent for longer time. + CUDT& u = w_d->ps->core(); + if (is_zero(u.m_tsFreshActivation)) // TODO: Check if this condition is ever false + return; + + CSndBuffer* b = u.m_pSndBuffer; + if (b && b->getCurrBufSize() == 0) + { + HLOGC(gslog.Debug, + log << "grp/sendBackup: FRESH IDLE LINK reached empty buffer - setting permanent and KEEPALIVE"); + u.m_tsFreshActivation = steady_clock::time_point(); + + // Send first immediate keepalive. The link is to be turn to IDLE + // now so nothing will be sent to it over time and it will start + // getting KEEPALIVES since now. Send the first one now to increase + // probability that the link will be recognized as IDLE on the + // reception side ASAP. + int32_t arg = 1; + w_d->ps->core().sendCtrl(UMSG_KEEPALIVE, &arg); + } +} + +// [[using locked(this->m_GroupLock)]] +CUDTGroup::BackupMemberState CUDTGroup::sendBackup_QualifyActiveState(const gli_t d, const time_point currtime) +{ + const CUDT& u = d->ps->core(); + + const uint32_t latency_us = u.peerLatency_us(); + + const int32_t min_stability_us = m_uOPT_MinStabilityTimeout_us; + const int64_t initial_stabtout_us = max(min_stability_us, latency_us); + const int64_t probing_period_us = initial_stabtout_us + 5 * CUDT::COMM_SYN_INTERVAL_US; + + // RTT and RTTVar values are still being refined during the probing period, + // therefore the dymanic timeout should not be used during the probing period. + const bool is_activation_phase = !is_zero(u.freshActivationStart()) + && (count_microseconds(currtime - u.freshActivationStart()) <= probing_period_us); + + // Initial stability timeout is used only in activation phase. + // Otherwise runtime stability is used, including the WARY state. + const int64_t stability_tout_us = is_activation_phase + ? initial_stabtout_us // activation phase + : min(max(min_stability_us, 2 * u.SRTT() + 4 * u.RTTVar()), latency_us); + + const steady_clock::time_point last_rsp = max(u.freshActivationStart(), u.lastRspTime()); + const steady_clock::duration td_response = currtime - last_rsp; + + // No response for a long time + if (count_microseconds(td_response) > stability_tout_us) + { + return BKUPST_ACTIVE_UNSTABLE; + } + + enterCS(u.m_StatsLock); + const int64_t drop_total = u.m_stats.sndr.dropped.total.count(); + leaveCS(u.m_StatsLock); + + const bool have_new_drops = d->pktSndDropTotal != drop_total; + if (have_new_drops) + { + d->pktSndDropTotal = drop_total; + if (!is_activation_phase) + return BKUPST_ACTIVE_UNSTABLE; + } + + // Responsive: either stable, wary or still fresh activated. + if (is_activation_phase) + return BKUPST_ACTIVE_FRESH; + + const bool is_wary = !is_zero(u.m_tsWarySince); + const bool is_wary_probing = is_wary + && (count_microseconds(currtime - u.m_tsWarySince) <= 4 * u.peerLatency_us()); + + const bool is_unstable = !is_zero(u.m_tsUnstableSince); + + // If unstable and not in wary, become wary. + if (is_unstable && !is_wary) + return BKUPST_ACTIVE_UNSTABLE_WARY; + + // Still probing for stability. + if (is_wary_probing) + return BKUPST_ACTIVE_UNSTABLE_WARY; + + if (is_wary) + { + LOGC(gslog.Debug, + log << "grp/sendBackup: @" << u.id() << " wary->stable after " << count_milliseconds(currtime - u.m_tsWarySince) << " ms"); + } + + return BKUPST_ACTIVE_STABLE; +} + +// [[using locked(this->m_GroupLock)]] +bool CUDTGroup::sendBackup_CheckSendStatus(const steady_clock::time_point& currtime SRT_ATR_UNUSED, + const int send_status, + const int32_t lastseq, + const int32_t pktseq, + CUDT& w_u, + int32_t& w_curseq, + int& w_final_stat) +{ + if (send_status == -1) + return false; // Sending failed. + + + bool send_succeeded = false; + if (w_curseq == SRT_SEQNO_NONE) + { + w_curseq = pktseq; + } + else if (w_curseq != lastseq) + { + // We believe that all active links use the same seq. + // But we can do some sanity check. + LOGC(gslog.Error, + log << "grp/sendBackup: @" << w_u.m_SocketID << ": IPE: another running link seq discrepancy: %" + << lastseq << " vs. previous %" << w_curseq << " - fixing"); + + // Override must be done with a sequence number greater by one. + + // Example: + // + // Link 1 before sending: curr=1114, next=1115 + // After sending it reports pktseq=1115 + // + // Link 2 before sending: curr=1110, next=1111 (->lastseq before sending) + // THIS CHECK done after sending: + // -- w_curseq(1115) != lastseq(1111) + // + // NOW: Link 1 after sending is: + // curr=1115, next=1116 + // + // The value of w_curseq here = 1115, while overrideSndSeqNo + // calls setInitialSndSeq(seq), which sets: + // - curr = seq - 1 + // - next = seq + // + // So, in order to set curr=1115, next=1116 + // this must set to 1115+1. + + w_u.overrideSndSeqNo(CSeqNo::incseq(w_curseq)); + } + + // State it as succeeded, though. We don't know if the link + // is broken until we get the connection broken confirmation, + // and the instability state may wear off next time. + send_succeeded = true; + w_final_stat = send_status; + + return send_succeeded; +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_Buffering(const char* buf, const int len, int32_t& w_curseq, SRT_MSGCTRL& w_mc) +{ + // This is required to rewrite into currentSchedSequence() property + // as this value will be used as ISN when a new link is connected. + int32_t oldest_buffer_seq = SRT_SEQNO_NONE; + + if (w_curseq != SRT_SEQNO_NONE) + { + HLOGC(gslog.Debug, log << "grp/sendBackup: successfully sent over running link, ADDING TO BUFFER."); + + // Note: the sequence number that was used to send this packet should be + // recorded here. + oldest_buffer_seq = addMessageToBuffer(buf, len, (w_mc)); + } + else + { + // We have to predict, which sequence number would have + // to be placed on the packet about to be sent now. To + // maintain consistency: + + // 1. If there are any packets in the sender buffer, + // get the sequence of the last packet, increase it. + // This must be done even if this contradicts the ISN + // of all idle links because otherwise packets will get + // discrepancy. + if (!m_SenderBuffer.empty()) + { + BufferedMessage& m = m_SenderBuffer.back(); + w_curseq = CSeqNo::incseq(m.mc.pktseq); + + // Set also this sequence to the current w_mc + w_mc.pktseq = w_curseq; + + // XXX may need tighter revision when message mode is allowed + w_mc.msgno = ++MsgNo(m.mc.msgno); + oldest_buffer_seq = addMessageToBuffer(buf, len, (w_mc)); + } + + // Note that if buffer is empty and w_curseq is (still) SRT_SEQNO_NONE, + // it will have to try to send first in order to extract the data. + + // Note that if w_curseq is still SRT_SEQNO_NONE at this point, it means + // that we have the case of the very first packet sending. + // Otherwise there would be something in the buffer already. + } + + if (oldest_buffer_seq != SRT_SEQNO_NONE) + m_iLastSchedSeqNo = oldest_buffer_seq; +} + +size_t CUDTGroup::sendBackup_TryActivateStandbyIfNeeded( + const char* buf, + const int len, + bool& w_none_succeeded, + SRT_MSGCTRL& w_mc, + int32_t& w_curseq, + int32_t& w_final_stat, + SendBackupCtx& w_sendBackupCtx, + CUDTException& w_cx, + const steady_clock::time_point& currtime) +{ + const unsigned num_standby = w_sendBackupCtx.countMembersByState(BKUPST_STANDBY); + if (num_standby == 0) + { + return 0; + } + + const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); + const unsigned num_fresh = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_FRESH); + + if (num_stable + num_fresh == 0) + { + LOGC(gslog.Warn, + log << "grp/sendBackup: trying to activate a stand-by link (" << num_standby << " available). " + << "Reason: no stable links" + ); + } + else if (w_sendBackupCtx.maxActiveWeight() < w_sendBackupCtx.maxStandbyWeight()) + { + LOGC(gslog.Warn, + log << "grp/sendBackup: trying to activate a stand-by link (" << num_standby << " available). " + << "Reason: max active weight " << w_sendBackupCtx.maxActiveWeight() + << ", max stand by weight " << w_sendBackupCtx.maxStandbyWeight() + ); + } + else + { + /*LOGC(gslog.Warn, + log << "grp/sendBackup: no need to activate (" << num_standby << " available). " + << "Max active weight " << w_sendBackupCtx.maxActiveWeight() + << ", max stand by weight " << w_sendBackupCtx.maxStandbyWeight() + );*/ + return 0; + } + + int stat = -1; + + size_t num_activated = 0; + + w_sendBackupCtx.sortByWeightAndState(); + typedef vector::const_iterator const_iter_t; + for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) + { + if (member->state != BKUPST_STANDBY) + continue; + + int erc = 0; + SocketData* d = member->pSocketData; + // Now send and check the status + // The link could have got broken + + try + { + CUDT& cudt = d->ps->core(); + // Take source rate estimation from an active member (needed for the input rate estimation mode). + cudt.setRateEstimator(w_sendBackupCtx.getRateEstimate()); + + // TODO: At this point all packets that could be sent + // are located in m_SenderBuffer. So maybe just use sendBackupRexmit()? + if (w_curseq == SRT_SEQNO_NONE) + { + // This marks the fact that the given here packet + // could not be sent over any link. This includes the + // situation of sending the very first packet after connection. + + HLOGC(gslog.Debug, + log << "grp/sendBackup: ... trying @" << d->id << " - sending the VERY FIRST message"); + + stat = cudt.sendmsg2(buf, len, (w_mc)); + if (stat != -1) + { + // This will be no longer used, but let it stay here. + // It's because if this is successful, no other links + // will be tried. + w_curseq = w_mc.pktseq; + addMessageToBuffer(buf, len, (w_mc)); + } + } + else + { + HLOGC(gslog.Debug, + log << "grp/sendBackup: ... trying @" << d->id << " - resending " << m_SenderBuffer.size() + << " collected messages..."); + // Note: this will set the currently required packet + // because it has been just freshly added to the sender buffer + stat = sendBackupRexmit(cudt, (w_mc)); + } + ++num_activated; + } + catch (CUDTException& e) + { + // This will be propagated from internal sendmsg2 call, + // but that's ok - we want this sending interrupted even in half. + w_cx = e; + stat = -1; + erc = e.getErrorCode(); + } + + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); + + if (stat != -1) + { + d->sndstate = SRT_GST_RUNNING; + sendBackup_AssignBackupState(d->ps->core(), BKUPST_ACTIVE_FRESH, currtime); + w_sendBackupCtx.updateMemberState(d, BKUPST_ACTIVE_FRESH); + // Note: this will override the sequence number + // for all next iterations in this loop. + w_none_succeeded = false; + w_final_stat = stat; + + LOGC(gslog.Warn, + log << "@" << d->id << " FRESH-ACTIVATED"); + + // We've activated the link, so that's enough. + break; + } + + // Failure - move to broken those that could not be activated + bool isblocked SRT_ATR_UNUSED = true; + if (erc != SRT_EASYNCSND) + { + isblocked = false; + sendBackup_AssignBackupState(d->ps->core(), BKUPST_BROKEN, currtime); + w_sendBackupCtx.updateMemberState(d, BKUPST_BROKEN); + } + + // If we found a blocked link, leave it alone, however + // still try to send something over another link + + LOGC(gslog.Warn, + log << "@" << d->id << " FAILED (" << (isblocked ? "blocked" : "ERROR") + << "), trying to activate another link."); + } + + return num_activated; +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_CheckPendingSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) +{ + if (w_sendBackupCtx.countMembersByState(BKUPST_PENDING) == 0) + return; + + HLOGC(gslog.Debug, log << "grp/send*: checking pending sockets."); + + // These sockets if they are in pending state, should be added to m_SndEID + // at the connecting stage. + CEPoll::fmap_t sready; + + if (m_Global.m_EPoll.empty(*m_SndEpolld)) + { + // Sanity check - weird pending reported. + LOGC(gslog.Error, log << "grp/send*: IPE: reported pending sockets, but EID is empty - wiping pending!"); + return; + } + + { + InvertedLock ug(m_GroupLock); + m_Global.m_EPoll.swait( + *m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything has happened + } + + if (m_bClosing) + { + HLOGC(gslog.Debug, log << "grp/send...: GROUP CLOSED, ABANDONING"); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Some sockets could have been closed in the meantime. + if (m_Global.m_EPoll.empty(*m_SndEpolld)) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + HLOGC(gslog.Debug, log << "grp/send*: RDY: " << DisplayEpollResults(sready)); + + typedef vector::const_iterator const_iter_t; + for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) + { + if (member->state != BKUPST_PENDING) + continue; + + const SRTSOCKET sockid = member->pSocketData->id; + if (!CEPoll::isready(sready, sockid, SRT_EPOLL_ERR)) + continue; + + HLOGC(gslog.Debug, log << "grp/send*: Socket @" << sockid << " reported FAILURE - qualifying as broken."); + w_sendBackupCtx.updateMemberState(member->pSocketData, BKUPST_BROKEN); + if (member->pSocketData->ps) + sendBackup_AssignBackupState(member->pSocketData->ps->core(), BKUPST_BROKEN, currtime); + + const int no_events = 0; + m_Global.m_EPoll.update_usock(m_SndEID, sockid, &no_events); + } + + // After that, all sockets that have been reported + // as ready to write should be removed from EID. This + // will also remove those sockets that have been added + // as redundant links at the connecting stage and became + // writable (connected) before this function had a chance + // to check them. + m_Global.m_EPoll.clear_ready_usocks(*m_SndEpolld, SRT_EPOLL_OUT); +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_CheckUnstableSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) +{ + const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); + if (num_stable == 0) + return; + + const unsigned num_unstable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE); + const unsigned num_wary = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE_WARY); + if (num_unstable + num_wary == 0) + return; + + HLOGC(gslog.Debug, log << "grp/send*: checking unstable sockets."); + + + typedef vector::const_iterator const_iter_t; + for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) + { + if (member->state != BKUPST_ACTIVE_UNSTABLE && member->state != BKUPST_ACTIVE_UNSTABLE_WARY) + continue; + + CUDT& sock = member->pSocketData->ps->core(); + + if (is_zero(sock.m_tsUnstableSince)) + { + LOGC(gslog.Error, log << "grp/send* IPE: Socket @" << member->socketID + << " is qualified as unstable, but does not have the 'unstable since' timestamp. Still marking for closure."); + } + + const int unstable_for_ms = count_milliseconds(currtime - sock.m_tsUnstableSince); + if (unstable_for_ms < sock.peerIdleTimeout_ms()) + continue; + + // Requesting this socket to be broken with the next CUDT::checkExpTimer() call. + sock.breakAsUnstable(); + + LOGC(gslog.Warn, log << "grp/send*: Socket @" << member->socketID << " is unstable for " << unstable_for_ms + << "ms - requesting breakage."); + + //w_sendBackupCtx.updateMemberState(member->pSocketData, BKUPST_BROKEN); + //if (member->pSocketData->ps) + // sendBackup_AssignBackupState(member->pSocketData->ps->core(), BKUPST_BROKEN, currtime); + } +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::send_CloseBrokenSockets(vector& w_wipeme) +{ + if (!w_wipeme.empty()) + { + InvertedLock ug(m_GroupLock); + + // With unlocked GroupLock, we can now lock GlobControlLock. + // This is needed prevent any of them be deleted from the container + // at the same time. + ScopedLock globlock(CUDT::uglobal().m_GlobControlLock); + + for (vector::iterator p = w_wipeme.begin(); p != w_wipeme.end(); ++p) + { + CUDTSocket* s = CUDT::uglobal().locateSocket_LOCKED(*p); + + // If the socket has been just moved to ClosedSockets, it means that + // the object still exists, but it will be no longer findable. + if (!s) + continue; + + HLOGC(gslog.Debug, + log << "grp/send...: BROKEN SOCKET @" << (*p) << " - CLOSING, to be removed from group."); + + // As per sending, make it also broken so that scheduled + // packets will be also abandoned. + s->setClosed(); + } + } + + HLOGC(gslog.Debug, log << "grp/send...: - wiped " << w_wipeme.size() << " broken sockets"); + + // We'll need you again. + w_wipeme.clear(); +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_CloseBrokenSockets(SendBackupCtx& w_sendBackupCtx) +{ + if (w_sendBackupCtx.countMembersByState(BKUPST_BROKEN) == 0) + return; + + InvertedLock ug(m_GroupLock); + + // With unlocked GroupLock, we can now lock GlobControlLock. + // This is needed prevent any of them be deleted from the container + // at the same time. + ScopedLock globlock(CUDT::uglobal().m_GlobControlLock); + + typedef vector::const_iterator const_iter_t; + for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) + { + if (member->state != BKUPST_BROKEN) + continue; + + // m_GroupLock is unlocked, therefore member->pSocketData can't be used. + const SRTSOCKET sockid = member->socketID; + CUDTSocket* s = CUDT::uglobal().locateSocket_LOCKED(sockid); + + // If the socket has been just moved to ClosedSockets, it means that + // the object still exists, but it will be no longer findable. + if (!s) + continue; + + LOGC(gslog.Debug, + log << "grp/send...: BROKEN SOCKET @" << sockid << " - CLOSING, to be removed from group."); + + // As per sending, make it also broken so that scheduled + // packets will be also abandoned. + s->setBrokenClosed(); + } + + // TODO: all broken members are to be removed from the context now??? +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx, + int& w_final_stat, + bool& w_none_succeeded, + SRT_MSGCTRL& w_mc, + CUDTException& w_cx) +{ + // In contradiction to broadcast sending, backup sending must check + // the blocking state in total first. We need this information through + // epoll because we didn't use all sockets to send the data hence the + // blocked socket information would not be complete. + + // Don't do this check if sending has succeeded over at least one + // stable link. This procedure is to wait for at least one write-ready + // link. + // + // If sending succeeded also over at least one unstable link (you only have + // unstable links and none other or others just got broken), continue sending + // anyway. + + + // This procedure is for a case when the packet could not be sent + // over any link (hence "none succeeded"), but there are some unstable + // links and no parallel links. We need to WAIT for any of the links + // to become available for sending. + + // Note: A link is added in unstableLinks if sending has failed with SRT_ESYNCSND. + const unsigned num_unstable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE); + const unsigned num_wary = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE_WARY); + if ((num_unstable + num_wary == 0) || !w_none_succeeded) + return; + + HLOGC(gslog.Debug, log << "grp/sendBackup: no successfull sending: " + << (num_unstable + num_wary) << " unstable links - waiting to retry sending..."); + + // Note: GroupLock is set already, skip locks and checks + getGroupData_LOCKED((w_mc.grpdata), (&w_mc.grpdata_size)); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + + if (m_Global.m_EPoll.empty(*m_SndEpolld)) + { + // wipeme wiped, pending sockets checked, it can only mean that + // all sockets are broken. + HLOGC(gslog.Debug, log << "grp/sendBackup: epolld empty - all sockets broken?"); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + if (!m_bSynSending) + { + HLOGC(gslog.Debug, log << "grp/sendBackup: non-blocking mode - exit with no-write-ready"); + throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); + } + // Here is the situation that the only links left here are: + // - those that failed to send (already closed and wiped out) + // - those that got blockade on sending + + // At least, there was so far no socket through which we could + // successfully send anything. + + // As a last resort in this situation, try to wait for any links + // remaining in the group to become ready to write. + + CEPoll::fmap_t sready; + int brdy; + + // This keeps the number of links that existed at the entry. + // Simply notify all dead links, regardless as to whether the number + // of group members decreases below. If the number of corpses reaches + // this number, consider the group connection broken. + const size_t nlinks = m_Group.size(); + size_t ndead = 0; + +RetryWaitBlocked: + { + // Some sockets could have been closed in the meantime. + if (m_Global.m_EPoll.empty(*m_SndEpolld)) + { + HLOGC(gslog.Debug, log << "grp/sendBackup: no more sockets available for sending - group broken"); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + InvertedLock ug(m_GroupLock); + HLOGC(gslog.Debug, + log << "grp/sendBackup: swait call to get at least one link alive up to " << m_iSndTimeOut << "us"); + THREAD_PAUSED(); + brdy = m_Global.m_EPoll.swait(*m_SndEpolld, (sready), m_iSndTimeOut); + THREAD_RESUMED(); + + if (brdy == 0) // SND timeout exceeded + { + throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); + } + + HLOGC(gslog.Debug, log << "grp/sendBackup: swait exited with " << brdy << " ready sockets:"); + + // Check if there's anything in the "error" section. + // This must be cleared here before the lock on group is set again. + // (This loop will not fire neither once if no failed sockets found). + for (CEPoll::fmap_t::const_iterator i = sready.begin(); i != sready.end(); ++i) + { + if (i->second & SRT_EPOLL_ERR) + { + SRTSOCKET id = i->first; + CUDTSocket* s = m_Global.locateSocket(id, CUDTUnited::ERH_RETURN); // << LOCKS m_GlobControlLock! + if (s) + { + HLOGC(gslog.Debug, + log << "grp/sendBackup: swait/ex on @" << (id) + << " while waiting for any writable socket - CLOSING"); + CUDT::uglobal().close(s); // << LOCKS m_GlobControlLock, then GroupLock! + } + else + { + HLOGC(gslog.Debug, log << "grp/sendBackup: swait/ex on @" << (id) << " - WAS DELETED IN THE MEANTIME"); + } + + ++ndead; + } + } + HLOGC(gslog.Debug, log << "grp/sendBackup: swait/?close done, re-acquiring GroupLock"); + } + + // GroupLock is locked back + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + if (brdy == -1 || ndead >= nlinks) + { + LOGC(gslog.Error, + log << "grp/sendBackup: swait=>" << brdy << " nlinks=" << nlinks << " ndead=" << ndead + << " - looxlike all links broken"); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + // You can safely throw here - nothing to fill in when all sockets down. + // (timeout was reported by exception in the swait call). + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Ok, now check if we have at least one write-ready. + // Note that the procedure of activation of a new link in case of + // no stable links found embraces also rexmit-sending and status + // check as well, including blocked status. + + // Find which one it was. This is so rare case that we can + // suffer linear search. + + int nwaiting = 0; + int nactivated SRT_ATR_UNUSED = 0; + int stat = -1; + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) + { + // We are waiting only for active members + if (d->sndstate != SRT_GST_RUNNING) + { + HLOGC(gslog.Debug, + log << "grp/sendBackup: member @" << d->id << " state is not RUNNING - SKIPPING from retry/waiting"); + continue; + } + // Skip if not writable in this run + if (!CEPoll::isready(sready, d->id, SRT_EPOLL_OUT)) + { + ++nwaiting; + HLOGC(gslog.Debug, log << "grp/sendBackup: @" << d->id << " NOT ready:OUT, added as waiting"); + continue; + } + + try + { + // Note: this will set the currently required packet + // because it has been just freshly added to the sender buffer + stat = sendBackupRexmit(d->ps->core(), (w_mc)); + ++nactivated; + } + catch (CUDTException& e) + { + // This will be propagated from internal sendmsg2 call, + // but that's ok - we want this sending interrupted even in half. + w_cx = e; + stat = -1; + } + + d->sndresult = stat; + d->laststatus = d->ps->getStatus(); + + if (stat == -1) + { + // This link is no longer waiting. + continue; + } + + w_final_stat = stat; + d->sndstate = SRT_GST_RUNNING; + w_none_succeeded = false; + const steady_clock::time_point currtime = steady_clock::now(); + sendBackup_AssignBackupState(d->ps->core(), BKUPST_ACTIVE_UNSTABLE_WARY, currtime); + w_sendBackupCtx.updateMemberState(&(*d), BKUPST_ACTIVE_UNSTABLE_WARY); + HLOGC(gslog.Debug, log << "grp/sendBackup: after waiting, ACTIVATED link @" << d->id); + + break; + } + + // If we have no links successfully activated, but at least + // one link "not ready for writing", continue waiting for at + // least one link ready. + if (stat == -1 && nwaiting > 0) + { + HLOGC(gslog.Debug, log << "grp/sendBackup: still have " << nwaiting << " waiting and none succeeded, REPEAT"); + goto RetryWaitBlocked; + } +} + +// [[using locked(this->m_GroupLock)]] +void CUDTGroup::sendBackup_SilenceRedundantLinks(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime) +{ + // The most important principle is to keep the data being sent constantly, + // even if it means temporarily full redundancy. + // A member can be silenced only if there is at least one stable memebr. + const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); + if (num_stable == 0) + return; + + // INPUT NEEDED: + // - stable member with maximum weight + + uint16_t max_weight_stable = 0; + SRTSOCKET stableSocketId = SRT_INVALID_SOCK; // SocketID of a stable link with higher weight + + w_sendBackupCtx.sortByWeightAndState(); + //LOGC(gslog.Debug, log << "grp/silenceRedundant: links after sort: " << w_sendBackupCtx.printMembers()); + typedef vector::const_iterator const_iter_t; + for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) + { + if (!isStateActive(member->state)) + continue; + + const bool haveHigherWeightStable = stableSocketId != SRT_INVALID_SOCK; + const uint16_t weight = member->pSocketData->weight; + + if (member->state == BKUPST_ACTIVE_STABLE) + { + // silence stable link if it is not the first stable + if (!haveHigherWeightStable) + { + max_weight_stable = (int) weight; + stableSocketId = member->socketID; + continue; + } + else + { + LOGC(gslog.Note, log << "grp/sendBackup: silencing stable member @" << member->socketID << " (weight " << weight + << ") in favor of @" << stableSocketId << " (weight " << max_weight_stable << ")"); + } + } + else if (haveHigherWeightStable && weight <= max_weight_stable) + { + LOGC(gslog.Note, log << "grp/sendBackup: silencing member @" << member->socketID << " (weight " << weight + << " " << stateToStr(member->state) + << ") in favor of @" << stableSocketId << " (weight " << max_weight_stable << ")"); + } + else + { + continue; + } + + // TODO: Move to a separate function sendBackup_SilenceMember + SocketData* d = member->pSocketData; + CUDT& u = d->ps->core(); + + sendBackup_AssignBackupState(u, BKUPST_STANDBY, currtime); + w_sendBackupCtx.updateMemberState(d, BKUPST_STANDBY); + + if (d->sndstate != SRT_GST_RUNNING) + { + LOGC(gslog.Error, + log << "grp/sendBackup: IPE: misidentified a non-running link @" << d->id << " as active"); + continue; + } + + d->sndstate = SRT_GST_IDLE; + } +} + +int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + if (len <= 0) + { + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // Only live streaming is supported + if (len > SRT_LIVE_MAX_PLSIZE) + { + LOGC(gslog.Error, log << "grp/send(backup): buffer size=" << len << " exceeds maximum allowed in live mode"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // [[using assert(this->m_pSndBuffer != nullptr)]]; + + // First, acquire GlobControlLock to make sure all member sockets still exist + enterCS(m_Global.m_GlobControlLock); + ScopedLock guard(m_GroupLock); + + if (m_bClosing) + { + leaveCS(m_Global.m_GlobControlLock); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // Now, still under lock, check if all sockets still can be dispatched + send_CheckValidSockets(); + leaveCS(m_Global.m_GlobControlLock); + + steady_clock::time_point currtime = steady_clock::now(); + + SendBackupCtx sendBackupCtx; // default initialized as empty + // TODO: reserve? sendBackupCtx.memberStates.reserve(m_Group.size()); + + sendBackup_QualifyMemberStates((sendBackupCtx), currtime); + + int32_t curseq = SRT_SEQNO_NONE; + size_t nsuccessful = 0; + + SRT_ATR_UNUSED CUDTException cx(MJ_SUCCESS, MN_NONE, 0); // TODO: Delete then? + uint16_t maxActiveWeight = 0; // Maximum weight of active links. + // The number of bytes sent or -1 for error will be stored in group_send_result + int group_send_result = sendBackup_SendOverActive(buf, len, w_mc, currtime, (curseq), (nsuccessful), (maxActiveWeight), (sendBackupCtx), (cx)); + bool none_succeeded = (nsuccessful == 0); + + // Save current payload in group's sender buffer. + sendBackup_Buffering(buf, len, (curseq), (w_mc)); + + sendBackup_TryActivateStandbyIfNeeded(buf, len, (none_succeeded), + (w_mc), + (curseq), + (group_send_result), + (sendBackupCtx), + (cx), currtime); + + sendBackup_CheckPendingSockets((sendBackupCtx), currtime); + sendBackup_CheckUnstableSockets((sendBackupCtx), currtime); + + //LOGC(gslog.Debug, log << "grp/sendBackup: links after all checks: " << sendBackupCtx.printMembers()); + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + sendBackup_CloseBrokenSockets((sendBackupCtx)); + + // Re-check after the waiting lock has been reacquired + if (m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + // If all links out of the unstable-running links are blocked (SRT_EASYNCSND), + // perform epoll wait on them. In this situation we know that + // there are no idle blocked links because IDLE LINK CAN'T BE BLOCKED, + // no matter what. It's because the link may only be blocked if + // the sender buffer of this socket is full, and it can't be + // full if it wasn't used so far. + // + // This means that in case when we have no stable links, we + // need to try out any link that can accept the rexmit-load. + // We'll check link stability at the next sending attempt. + sendBackup_RetryWaitBlocked((sendBackupCtx), (group_send_result), (none_succeeded), (w_mc), (cx)); + + sendBackup_SilenceRedundantLinks((sendBackupCtx), currtime); + // (closing condition checked inside this call) + + if (none_succeeded) + { + HLOGC(gslog.Debug, log << "grp/sendBackup: all links broken (none succeeded to send a payload)"); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + // Reparse error code, if set. + // It might be set, if the last operation was failed. + // If any operation succeeded, this will not be executed anyway. + + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + + // At least one link has succeeded, update sending stats. + m_stats.sent.count(len); + + // Now fill in the socket table. Check if the size is enough, if not, + // then set the pointer to NULL and set the correct size. + + // Note that list::size() is linear time, however this shouldn't matter, + // as with the increased number of links in the redundancy group the + // impossibility of using that many of them grows exponentally. + const size_t grpsize = m_Group.size(); + + if (w_mc.grpdata_size < grpsize) + { + w_mc.grpdata = NULL; + } + + size_t i = 0; + + bool ready_again = false; + + HLOGC(gslog.Debug, log << "grp/sendBackup: copying group data"); + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d, ++i) + { + if (w_mc.grpdata) + { + // Enough space to fill + copyGroupData(*d, (w_mc.grpdata[i])); + } + + // We perform this loop anyway because we still need to check if any + // socket is writable. Note that the group lock will hold any write ready + // updates that are performed just after a single socket update for the + // group, so if any socket is actually ready at the moment when this + // is performed, and this one will result in none-write-ready, this will + // be fixed just after returning from this function. + + ready_again = ready_again || d->ps->writeReady(); + } + w_mc.grpdata_size = i; + + if (!ready_again) + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); + } + + HLOGC(gslog.Debug, + log << "grp/sendBackup: successfully sent " << group_send_result << " bytes, " + << (ready_again ? "READY for next" : "NOT READY to send next")); + return group_send_result; +} + +// [[using locked(this->m_GroupLock)]] +int32_t CUDTGroup::addMessageToBuffer(const char* buf, size_t len, SRT_MSGCTRL& w_mc) +{ + if (m_iSndAckedMsgNo == SRT_MSGNO_NONE) + { + // Very first packet, just set the msgno. + m_iSndAckedMsgNo = w_mc.msgno; + m_iSndOldestMsgNo = w_mc.msgno; + HLOGC(gslog.Debug, log << "addMessageToBuffer: initial message no #" << w_mc.msgno); + } + else if (m_iSndOldestMsgNo != m_iSndAckedMsgNo) + { + int offset = MsgNo(m_iSndAckedMsgNo) - MsgNo(m_iSndOldestMsgNo); + HLOGC(gslog.Debug, + log << "addMessageToBuffer: new ACK-ed messages: #(" << m_iSndOldestMsgNo << "-" << m_iSndAckedMsgNo + << ") - going to remove"); + + if (offset > int(m_SenderBuffer.size())) + { + LOGC(gslog.Error, + log << "addMessageToBuffer: IPE: offset=" << offset << " exceeds buffer size=" << m_SenderBuffer.size() + << " - CLEARING"); + m_SenderBuffer.clear(); + } + else + { + HLOGC(gslog.Debug, + log << "addMessageToBuffer: erasing " << offset << "/" << m_SenderBuffer.size() + << " group-senderbuffer ACKED messages for #" << m_iSndOldestMsgNo << " - #" << m_iSndAckedMsgNo); + m_SenderBuffer.erase(m_SenderBuffer.begin(), m_SenderBuffer.begin() + offset); + } + + // Position at offset is not included + m_iSndOldestMsgNo = m_iSndAckedMsgNo; + HLOGC(gslog.Debug, + log << "addMessageToBuffer: ... after: oldest #" << m_iSndOldestMsgNo); + } + + m_SenderBuffer.resize(m_SenderBuffer.size() + 1); + BufferedMessage& bm = m_SenderBuffer.back(); + bm.mc = w_mc; + bm.copy(buf, len); + + HLOGC(gslog.Debug, + log << "addMessageToBuffer: #" << w_mc.msgno << " size=" << len << " !" << BufferStamp(buf, len)); + + return m_SenderBuffer.front().mc.pktseq; +} + +int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_mc, const steady_clock::time_point& currtime, int32_t& w_curseq, + size_t& w_nsuccessful, uint16_t& w_maxActiveWeight, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx) +{ + if (w_mc.srctime == 0) + w_mc.srctime = count_microseconds(currtime.time_since_epoch()); + + SRT_ASSERT(w_nsuccessful == 0); + SRT_ASSERT(w_maxActiveWeight == 0); + + int group_send_result = SRT_ERROR; + + // TODO: implement iterator over active links + typedef vector::const_iterator const_iter_t; + for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) + { + if (!isStateActive(member->state)) + continue; + + SocketData* d = member->pSocketData; + int erc = SRT_SUCCESS; + // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. + CUDT& u = d->ps->core(); + const int32_t lastseq = u.schedSeqNo(); + int sndresult = SRT_ERROR; + try + { + // This must be wrapped in try-catch because on error it throws an exception. + // Possible return values are only 0, in case when len was passed 0, or a positive + // >0 value that defines the size of the data that it has sent, that is, in case + // of Live mode, equal to 'len'. + sndresult = u.sendmsg2(buf, len, (w_mc)); + } + catch (CUDTException& e) + { + w_cx = e; + erc = e.getErrorCode(); + sndresult = SRT_ERROR; + } + + const bool send_succeeded = sendBackup_CheckSendStatus( + currtime, + sndresult, + lastseq, + w_mc.pktseq, + (u), + (w_curseq), + (group_send_result)); + + if (send_succeeded) + { + ++w_nsuccessful; + w_maxActiveWeight = max(w_maxActiveWeight, d->weight); + + if (u.m_pSndBuffer) + w_sendBackupCtx.setRateEstimate(u.m_pSndBuffer->getRateEstimator()); + } + else if (erc == SRT_EASYNCSND) + { + sendBackup_AssignBackupState(u, BKUPST_ACTIVE_UNSTABLE, currtime); + w_sendBackupCtx.updateMemberState(d, BKUPST_ACTIVE_UNSTABLE); + } + + d->sndresult = sndresult; + d->laststatus = d->ps->getStatus(); + } + + return group_send_result; +} + +// [[using locked(this->m_GroupLock)]] +int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) +{ + // This should resend all packets + if (m_SenderBuffer.empty()) + { + LOGC(gslog.Fatal, log << "IPE: sendBackupRexmit: sender buffer empty"); + + // Although act as if it was successful, otherwise you'll get connection break + return 0; + } + + // using [[assert !m_SenderBuffer.empty()]]; + + // Send everything you currently have in the sender buffer. + // The receiver will reject packets that it currently has. + // Start from the oldest. + + CPacket packet; + + set results; + int stat = -1; + + // Make sure that the link has correctly synchronized sequence numbers. + // Note that sequence numbers should be recorded in mc. + int32_t curseq = m_SenderBuffer[0].mc.pktseq; + size_t skip_initial = 0; + if (curseq != core.schedSeqNo()) + { + const int distance = CSeqNo::seqoff(core.schedSeqNo(), curseq); + if (distance < 0) + { + // This may happen in case when the link to be activated is already running. + // Getting sequences backwards is not allowed, as sending them makes no + // sense - they are already ACK-ed or are behind the ISN. Instead, skip all + // packets that are in the past towards the scheduling sequence. + skip_initial = -distance; + LOGC(gslog.Warn, + log << "sendBackupRexmit: OVERRIDE attempt. Link seqno %" << core.schedSeqNo() << ", trying to send from seqno %" << curseq + << " - DENIED; skip " << skip_initial << " pkts, " << m_SenderBuffer.size() << " pkts in buffer"); + } + else + { + // In case when the next planned sequence on this link is behind + // the firstmost sequence in the backup buffer, synchronize the + // sequence with it first so that they go hand-in-hand with + // sequences already used by the link from which packets were + // copied to the backup buffer. + IF_HEAVY_LOGGING(int32_t old = core.schedSeqNo()); + const bool su SRT_ATR_UNUSED = core.overrideSndSeqNo(curseq); + HLOGC(gslog.Debug, + log << "sendBackupRexmit: OVERRIDING seq %" << old << " with %" << curseq + << (su ? " - succeeded" : " - FAILED!")); + } + } + + + if (skip_initial >= m_SenderBuffer.size()) + { + LOGC(gslog.Warn, + log << "sendBackupRexmit: All packets were skipped. Nothing to send %" << core.schedSeqNo() << ", trying to send from seqno %" << curseq + << " - DENIED; skip " << skip_initial << " packets"); + return 0; // can't return any other state, nothing was sent + } + + senderBuffer_t::iterator i = m_SenderBuffer.begin() + skip_initial; + + // Send everything - including the packet freshly added to the buffer + for (; i != m_SenderBuffer.end(); ++i) + { + // NOTE: an exception from here will interrupt the loop + // and will be caught in the upper level. + stat = core.sendmsg2(i->data, i->size, (i->mc)); + if (stat == -1) + { + // Stop sending if one sending ended up with error + LOGC(gslog.Warn, + log << "sendBackupRexmit: sending from buffer stopped at %" << core.schedSeqNo() << " and FAILED"); + return -1; + } + } + + // Copy the contents of the last item being updated. + w_mc = m_SenderBuffer.back().mc; + HLOGC(gslog.Debug, log << "sendBackupRexmit: pre-sent collected %" << curseq << " - %" << w_mc.pktseq); + return stat; +} + +// [[using locked(CUDTGroup::m_GroupLock)]]; +void CUDTGroup::ackMessage(int32_t msgno) +{ + // The message id could not be identified, skip. + if (msgno == SRT_MSGNO_CONTROL) + { + HLOGC(gslog.Debug, log << "ackMessage: msgno not found in ACK-ed sequence"); + return; + } + + // It's impossible to get the exact message position as the + // message is allowed also to span for multiple packets. + // Search since the oldest packet until you hit the first + // packet with this message number. + + // First, you need to decrease the message number by 1. It's + // because the sequence number being ACK-ed can be in the middle + // of the message, while it doesn't acknowledge that the whole + // message has been received. Decrease the message number so that + // partial-message-acknowledgement does not swipe the whole message, + // part of which may need to be retransmitted over a backup link. + + int offset = MsgNo(msgno) - MsgNo(m_iSndAckedMsgNo); + if (offset <= 0) + { + HLOGC(gslog.Debug, log << "ackMessage: already acked up to msgno=" << msgno); + return; + } + + HLOGC(gslog.Debug, log << "ackMessage: updated to #" << msgno); + + // Update last acked. Will be picked up when adding next message. + m_iSndAckedMsgNo = msgno; +} + +void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli) +{ + // received keepalive for that group member + // In backup group it means that the link went IDLE. + if (m_type == SRT_GTYPE_BACKUP) + { + if (gli->rcvstate == SRT_GST_RUNNING) + { + gli->rcvstate = SRT_GST_IDLE; + HLOGC(gslog.Debug, log << "GROUP: received KEEPALIVE in @" << gli->id << " - link turning rcv=IDLE"); + } + + // When received KEEPALIVE, the sending state should be also + // turned IDLE, if the link isn't temporarily activated. The + // temporarily activated link will not be measured stability anyway, + // while this should clear out the problem when the transmission is + // stopped and restarted after a while. This will simply set the current + // link as IDLE on the sender when the peer sends a keepalive because the + // data stopped coming in and it can't send ACKs therefore. + // + // This also shouldn't be done for the temporary activated links because + // stability timeout could be exceeded for them by a reason that, for example, + // the packets come with the past sequences (as they are being synchronized + // the sequence per being IDLE and empty buffer), so a large portion of initial + // series of packets may come with past sequence, delaying this way with ACK, + // which may result not only with exceeded stability timeout (which fortunately + // isn't being measured in this case), but also with receiveing keepalive + // (therefore we also don't reset the link to IDLE in the temporary activation period). + if (gli->sndstate == SRT_GST_RUNNING && is_zero(gli->ps->core().m_tsFreshActivation)) + { + gli->sndstate = SRT_GST_IDLE; + HLOGC(gslog.Debug, + log << "GROUP: received KEEPALIVE in @" << gli->id << " active=PAST - link turning snd=IDLE"); + } + } +} + +void CUDTGroup::internalKeepalive(SocketData* gli) +{ + // This is in response to AGENT SENDING keepalive. This means that there's + // no transmission in either direction, but the KEEPALIVE packet from the + // other party could have been missed. This is to ensure that the IDLE state + // is recognized early enough, before any sequence discrepancy can happen. + + if (m_type == SRT_GTYPE_BACKUP && gli->rcvstate == SRT_GST_RUNNING) + { + gli->rcvstate = SRT_GST_IDLE; + // Prevent sending KEEPALIVE again in group-sending + gli->ps->core().m_tsFreshActivation = steady_clock::time_point(); + HLOGC(gslog.Debug, log << "GROUP: EXP-requested KEEPALIVE in @" << gli->id << " - link turning IDLE"); + } +} + +CUDTGroup::BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_LIVE_MAX_PLSIZE /*, 1000*/); + +// Forwarder needed due to class definition order +int32_t CUDTGroup::generateISN() +{ + return CUDT::generateISN(); +} + +void CUDTGroup::setGroupConnected() +{ + if (!m_bConnected) + { + // Switch to connected state and give appropriate signal + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_CONNECT, true); + m_bConnected = true; + } +} + +void CUDTGroup::updateLatestRcv(CUDTSocket* s) +{ + // Currently only Backup groups use connected idle links. + if (m_type != SRT_GTYPE_BACKUP) + return; + + HLOGC(grlog.Debug, + log << "updateLatestRcv: BACKUP group, updating from active link @" << s->m_SocketID << " with %" + << s->core().m_iRcvLastSkipAck); + + CUDT* source = &s->core(); + vector targets; + + UniqueLock lg(m_GroupLock); + // Sanity check for a case when getting a deleted socket + if (!s->m_GroupOf) + return; + + // Under a group lock, we block execution of removal of the socket + // from the group, so if m_GroupOf is not NULL, we are granted + // that m_GroupMemberData is valid. + SocketData* current = s->m_GroupMemberData; + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + // Skip the socket that has reported packet reception + if (&*gi == current) + { + HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq on self @" << gi->id); + continue; + } + + // Don't update the state if the link is: + // - PENDING - because it's not in the connected state, wait for it. + // - RUNNING - because in this case it should have its own line of sequences + // - BROKEN - because it doesn't make sense anymore, about to be removed + if (gi->rcvstate != SRT_GST_IDLE) + { + HLOGC(grlog.Debug, + log << "grp: NOT updating rcv-seq on @" << gi->id + << " - link state:" << srt_log_grp_state[gi->rcvstate]); + continue; + } + + // Sanity check + if (!gi->ps->core().m_bConnected) + { + HLOGC(grlog.Debug, log << "grp: IPE: NOT updating rcv-seq on @" << gi->id << " - IDLE BUT NOT CONNECTED"); + continue; + } + + targets.push_back(&gi->ps->core()); + } + + lg.unlock(); + + // Do this on the unlocked group because this + // operation will need receiver lock, so it might + // risk a deadlock. + + for (size_t i = 0; i < targets.size(); ++i) + { + targets[i]->updateIdleLinkFrom(source); + } +} + +void CUDTGroup::activateUpdateEvent(bool still_have_items) +{ + // This function actually reacts on the fact that a socket + // was deleted from the group. This might make the group empty. + if (!still_have_items) // empty, or removal of unknown socket attempted - set error on group + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); + } + else + { + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_UPDATE, true); + } +} + +void CUDTGroup::addEPoll(int eid) +{ + enterCS(m_Global.m_EPoll.m_EPollLock); + m_sPollID.insert(eid); + leaveCS(m_Global.m_EPoll.m_EPollLock); + + bool any_read = false; + bool any_write = false; + bool any_broken = false; + bool any_pending = false; + + { + // Check all member sockets + ScopedLock gl(m_GroupLock); + + // We only need to know if there is any socket that is + // ready to get a payload and ready to receive from. + + for (gli_t i = m_Group.begin(); i != m_Group.end(); ++i) + { + if (i->sndstate == SRT_GST_IDLE || i->sndstate == SRT_GST_RUNNING) + { + any_write |= i->ps->writeReady(); + } + + if (i->rcvstate == SRT_GST_IDLE || i->rcvstate == SRT_GST_RUNNING) + { + any_read |= i->ps->readReady(); + } + + if (i->ps->broken()) + any_broken |= true; + else + any_pending |= true; + } + } + + // This is stupid, but we don't have any other interface to epoll + // internals. Actually we don't have to check if id() is in m_sPollID + // because we know it is, as we just added it. But it's not performance + // critical, sockets are not being often added during transmission. + if (any_read) + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, true); + + if (any_write) + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); + + // Set broken if none is non-broken (pending, read-ready or write-ready) + if (any_broken && !any_pending) + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); +} + +void CUDTGroup::removeEPollEvents(const int eid) +{ + // clear IO events notifications; + // since this happens after the epoll ID has been removed, they cannot be set again + set remove; + remove.insert(eid); + m_Global.m_EPoll.update_events(id(), remove, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); +} + +void CUDTGroup::removeEPollID(const int eid) +{ + enterCS(m_Global.m_EPoll.m_EPollLock); + m_sPollID.erase(eid); + leaveCS(m_Global.m_EPoll.m_EPollLock); +} + +void CUDTGroup::updateFailedLink() +{ + ScopedLock lg(m_GroupLock); + + // Check all members if they are in the pending + // or connected state. + + int nhealthy = 0; + + for (gli_t i = m_Group.begin(); i != m_Group.end(); ++i) + { + if (i->sndstate < SRT_GST_BROKEN) + nhealthy++; + } + + if (!nhealthy) + { + // No healthy links, set ERR on epoll. + HLOGC(gmlog.Debug, log << "group/updateFailedLink: All sockets broken"); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); + } + else + { + HLOGC(gmlog.Debug, log << "group/updateFailedLink: Still " << nhealthy << " links in the group"); + } +} + +#if ENABLE_HEAVY_LOGGING +// [[using maybe_locked(CUDT::uglobal()->m_GlobControlLock)]] +void CUDTGroup::debugGroup() +{ + ScopedLock gg(m_GroupLock); + + HLOGC(gmlog.Debug, log << "GROUP MEMBER STATUS - $" << id()); + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + HLOGC(gmlog.Debug, + log << " ... id { agent=@" << gi->id << " peer=@" << gi->ps->m_PeerID + << " } address { agent=" << gi->agent.str() << " peer=" << gi->peer.str() << "} " + << " state {snd=" << StateStr(gi->sndstate) << " rcv=" << StateStr(gi->rcvstate) << "}"); + } +} +#endif + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/group.h b/trunk/3rdparty/srt-1-fit/srtcore/group.h new file mode 100644 index 0000000000..1bd84aeda0 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group.h @@ -0,0 +1,822 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2020 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +Written by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef INC_SRT_GROUP_H +#define INC_SRT_GROUP_H + +#include "srt.h" +#include "common.h" +#include "packet.h" +#include "group_common.h" +#include "group_backup.h" + +namespace srt +{ + +#if ENABLE_HEAVY_LOGGING +const char* const srt_log_grp_state[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; +#endif + + +class CUDTGroup +{ + friend class CUDTUnited; + + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; + typedef sync::steady_clock steady_clock; + typedef groups::SocketData SocketData; + typedef groups::SendBackupCtx SendBackupCtx; + typedef groups::BackupMemberState BackupMemberState; + +public: + typedef SRT_MEMBERSTATUS GroupState; + + // Note that the use of states may differ in particular group types: + // + // Broadcast: links that are freshly connected become PENDING and then IDLE only + // for a short moment to be activated immediately at the nearest sending operation. + // + // Balancing: like with broadcast, just that the link activation gets its shared percentage + // of traffic balancing + // + // Multicast: The link is never idle. The data are always sent over the UDP multicast link + // and the receiver simply gets subscribed and reads packets once it's ready. + // + // Backup: The link stays idle until it's activated, and the activation can only happen + // at the moment when the currently active link is "suspected of being likely broken" + // (the current active link fails to receive ACK in a time when two ACKs should already + // be received). After a while when the current active link is confirmed broken, it turns + // into broken state. + + static const char* StateStr(GroupState); + + static int32_t s_tokenGen; + static int32_t genToken() { ++s_tokenGen; if (s_tokenGen < 0) s_tokenGen = 0; return s_tokenGen;} + + struct ConfigItem + { + SRT_SOCKOPT so; + std::vector value; + + template + bool get(T& refr) + { + if (sizeof(T) > value.size()) + return false; + refr = *(T*)&value[0]; + return true; + } + + ConfigItem(SRT_SOCKOPT o, const void* val, int size) + : so(o) + { + value.resize(size); + unsigned char* begin = (unsigned char*)val; + std::copy(begin, begin + size, value.begin()); + } + + struct OfType + { + SRT_SOCKOPT so; + OfType(SRT_SOCKOPT soso) + : so(soso) + { + } + bool operator()(ConfigItem& ci) { return ci.so == so; } + }; + }; + + typedef std::list group_t; + typedef group_t::iterator gli_t; + typedef std::vector< std::pair > sendable_t; + + struct Sendstate + { + SRTSOCKET id; + SocketData* mb; + int stat; + int code; + }; + + CUDTGroup(SRT_GROUP_TYPE); + ~CUDTGroup(); + + SocketData* add(SocketData data); + + struct HaveID + { + SRTSOCKET id; + HaveID(SRTSOCKET sid) + : id(sid) + { + } + bool operator()(const SocketData& s) { return s.id == id; } + }; + + bool contains(SRTSOCKET id, SocketData*& w_f) + { + srt::sync::ScopedLock g(m_GroupLock); + gli_t f = std::find_if(m_Group.begin(), m_Group.end(), HaveID(id)); + if (f == m_Group.end()) + { + w_f = NULL; + return false; + } + w_f = &*f; + return true; + } + + // NEED LOCKING + gli_t begin() { return m_Group.begin(); } + gli_t end() { return m_Group.end(); } + + /// Remove the socket from the group container. + /// REMEMBER: the group spec should be taken from the socket + /// (set m_GroupOf and m_GroupMemberData to NULL + /// PRIOR TO calling this function. + /// @param id Socket ID to look for in the container to remove + /// @return true if the container still contains any sockets after the operation + bool remove(SRTSOCKET id) + { + using srt_logging::gmlog; + srt::sync::ScopedLock g(m_GroupLock); + + bool empty = false; + HLOGC(gmlog.Debug, log << "group/remove: going to remove @" << id << " from $" << m_GroupID); + + gli_t f = std::find_if(m_Group.begin(), m_Group.end(), HaveID(id)); + if (f != m_Group.end()) + { + m_Group.erase(f); + + // Reset sequence numbers on a dead group so that they are + // initialized anew with the new alive connection within + // the group. + // XXX The problem is that this should be done after the + // socket is considered DISCONNECTED, not when it's being + // closed. After being disconnected, the sequence numbers + // are no longer valid, and will be reinitialized when the + // socket is connected again. This may stay as is for now + // as in SRT it's not predicted to do anything with the socket + // that was disconnected other than immediately closing it. + if (m_Group.empty()) + { + // When the group is empty, there's no danger that this + // number will collide with any ISN provided by a socket. + // Also since now every socket will derive this ISN. + m_iLastSchedSeqNo = generateISN(); + resetInitialRxSequence(); + empty = true; + } + } + else + { + HLOGC(gmlog.Debug, log << "group/remove: IPE: id @" << id << " NOT FOUND"); + empty = true; // not exactly true, but this is to cause error on group in the APP + } + + if (m_Group.empty()) + { + m_bOpened = false; + m_bConnected = false; + } + + // XXX BUGFIX + m_Positions.erase(id); + + return !empty; + } + + bool groupEmpty() + { + srt::sync::ScopedLock g(m_GroupLock); + return m_Group.empty(); + } + + void setGroupConnected(); + + int send(const char* buf, int len, SRT_MSGCTRL& w_mc); + int sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc); + int sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc); + static int32_t generateISN(); + +private: + // For Backup, sending all previous packet + int sendBackupRexmit(srt::CUDT& core, SRT_MSGCTRL& w_mc); + + // Support functions for sendBackup and sendBroadcast + /// Check if group member is idle. + /// @param d group member + /// @param[in,out] w_wipeme array of sockets to remove from group + /// @param[in,out] w_pendingLinks array of sockets pending for connection + /// @returns true if d is idle (standby), false otherwise + bool send_CheckIdle(const gli_t d, std::vector& w_wipeme, std::vector& w_pendingLinks); + + + /// This function checks if the member has just become idle (check if sender buffer is empty) to send a KEEPALIVE immidiatelly. + /// @todo Check it is some abandoned logic. + void sendBackup_CheckIdleTime(gli_t w_d); + + /// Qualify states of member links. + /// [[using locked(this->m_GroupLock, m_pGlobal->m_GlobControlLock)]] + /// @param[out] w_sendBackupCtx the context will be updated with state qualifications + /// @param[in] currtime current timestamp + void sendBackup_QualifyMemberStates(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); + + void sendBackup_AssignBackupState(srt::CUDT& socket, BackupMemberState state, const steady_clock::time_point& currtime); + + /// Qualify the state of the active link: fresh, stable, unstable, wary. + /// @retval active backup member state: fresh, stable, unstable, wary. + BackupMemberState sendBackup_QualifyActiveState(const gli_t d, const time_point currtime); + + BackupMemberState sendBackup_QualifyIfStandBy(const gli_t d); + + /// Sends the same payload over all active members. + /// @param[in] buf payload + /// @param[in] len payload length in bytes + /// @param[in,out] w_mc message control + /// @param[in] currtime current time + /// @param[in] currseq current packet sequence number + /// @param[out] w_nsuccessful number of members with successfull sending. + /// @param[in,out] maxActiveWeight + /// @param[in,out] sendBackupCtx context + /// @param[in,out] w_cx error + /// @return group send result: -1 if sending over all members has failed; number of bytes sent overwise. + int sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_mc, const steady_clock::time_point& currtime, int32_t& w_curseq, + size_t& w_nsuccessful, uint16_t& w_maxActiveWeight, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx); + + /// Check link sending status + /// @param[in] currtime Current time (logging only) + /// @param[in] send_status Result of sending over the socket + /// @param[in] lastseq Last sent sequence number before the current sending operation + /// @param[in] pktseq Packet sequence number currently tried to be sent + /// @param[out] w_u CUDT unit of the current member (to allow calling overrideSndSeqNo) + /// @param[out] w_curseq Group's current sequence number (either -1 or the value used already for other links) + /// @param[out] w_final_stat w_final_stat = send_status if sending succeded. + /// + /// @returns true if the sending operation result (submitted in stat) is a success, false otherwise. + bool sendBackup_CheckSendStatus(const time_point& currtime, + const int send_status, + const int32_t lastseq, + const int32_t pktseq, + CUDT& w_u, + int32_t& w_curseq, + int& w_final_stat); + void sendBackup_Buffering(const char* buf, const int len, int32_t& curseq, SRT_MSGCTRL& w_mc); + + size_t sendBackup_TryActivateStandbyIfNeeded( + const char* buf, + const int len, + bool& w_none_succeeded, + SRT_MSGCTRL& w_mc, + int32_t& w_curseq, + int32_t& w_final_stat, + SendBackupCtx& w_sendBackupCtx, + CUDTException& w_cx, + const steady_clock::time_point& currtime); + + /// Check if pending sockets are to be qualified as broken. + /// This qualification later results in removing the socket from a group and closing it. + /// @param[in,out] a context with a list of member sockets, some pending might qualified broken + void sendBackup_CheckPendingSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); + + /// Check if unstable sockets are to be qualified as broken. + /// The main reason for such qualification is if a socket is unstable for too long. + /// This qualification later results in removing the socket from a group and closing it. + /// @param[in,out] a context with a list of member sockets, some pending might qualified broken + void sendBackup_CheckUnstableSockets(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); + + /// @brief Marks broken sockets as closed. Used in broadcast sending. + /// @param w_wipeme a list of sockets to close + void send_CloseBrokenSockets(std::vector& w_wipeme); + + /// @brief Marks broken sockets as closed. Used in backup sending. + /// @param w_sendBackupCtx the context with a list of broken sockets + void sendBackup_CloseBrokenSockets(SendBackupCtx& w_sendBackupCtx); + + void sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx, + int& w_final_stat, + bool& w_none_succeeded, + SRT_MSGCTRL& w_mc, + CUDTException& w_cx); + void sendBackup_SilenceRedundantLinks(SendBackupCtx& w_sendBackupCtx, const steady_clock::time_point& currtime); + + void send_CheckValidSockets(); + +public: + int recv(char* buf, int len, SRT_MSGCTRL& w_mc); + + void close(); + + void setOpt(SRT_SOCKOPT optname, const void* optval, int optlen); + void getOpt(SRT_SOCKOPT optName, void* optval, int& w_optlen); + void deriveSettings(srt::CUDT* source); + bool applyFlags(uint32_t flags, HandshakeSide); + + SRT_SOCKSTATUS getStatus(); + + void debugMasterData(SRTSOCKET slave); + + bool isGroupReceiver() + { + // XXX add here also other group types, which + // predict group receiving. + return m_type == SRT_GTYPE_BROADCAST; + } + + sync::Mutex* exp_groupLock() { return &m_GroupLock; } + void addEPoll(int eid); + void removeEPollEvents(const int eid); + void removeEPollID(const int eid); + + /// @brief Update read-ready state. + /// @param sock member socket ID (unused) + /// @param sequence the latest packet sequence number available for reading. + void updateReadState(SRTSOCKET sock, int32_t sequence); + + void updateWriteState(); + void updateFailedLink(); + void activateUpdateEvent(bool still_have_items); + int32_t getRcvBaseSeqNo(); + + /// Update the in-group array of packet providers per sequence number. + /// Also basing on the information already provided by possibly other sockets, + /// report the real status of packet loss, including packets maybe lost + /// by the caller provider, but already received from elsewhere. Note that + /// these packets are not ready for extraction until ACK-ed. + /// + /// @param exp_sequence The previously received sequence at this socket + /// @param sequence The sequence of this packet + /// @param provider The core of the socket for which the packet was dispatched + /// @param time TSBPD time of this packet + /// @return The bitmap that marks by 'false' packets lost since next to exp_sequence + std::vector providePacket(int32_t exp_sequence, int32_t sequence, srt::CUDT* provider, uint64_t time); + + /// This is called from the ACK action by particular socket, which + /// actually signs off the packet for extraction. + /// + /// @param core The socket core for which the ACK was sent + /// @param ack The past-the-last-received ACK sequence number + void readyPackets(srt::CUDT* core, int32_t ack); + + void syncWithSocket(const srt::CUDT& core, const HandshakeSide side); + int getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); + int getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); + + /// Predicted to be called from the reading function to fill + /// the group data array as requested. + void fillGroupData(SRT_MSGCTRL& w_out, //< MSGCTRL to be written + const SRT_MSGCTRL& in //< MSGCTRL read from the data-providing socket + ); + + void copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUPDATA& w_target); + +#if ENABLE_HEAVY_LOGGING + void debugGroup(); +#else + void debugGroup() {} +#endif + + void ackMessage(int32_t msgno); + void processKeepalive(SocketData*); + void internalKeepalive(SocketData*); + +private: + // Check if there's at least one connected socket. + // If so, grab the status of all member sockets. + void getGroupCount(size_t& w_size, bool& w_still_alive); + + srt::CUDTUnited& m_Global; + srt::sync::Mutex m_GroupLock; + + SRTSOCKET m_GroupID; + SRTSOCKET m_PeerGroupID; + struct GroupContainer + { + std::list m_List; + + /// This field is used only by some types of groups that need + /// to keep track as to which link was lately used. Note that + /// by removal of a node from the m_List container, this link + /// must be appropriately reset. + gli_t m_LastActiveLink; + + GroupContainer() + : m_LastActiveLink(m_List.end()) + { + } + + // Property active = { m_LastActiveLink; } + SRTU_PROPERTY_RW(gli_t, active, m_LastActiveLink); + + gli_t begin() { return m_List.begin(); } + gli_t end() { return m_List.end(); } + bool empty() { return m_List.empty(); } + void push_back(const SocketData& data) { m_List.push_back(data); } + void clear() + { + m_LastActiveLink = end(); + m_List.clear(); + } + size_t size() { return m_List.size(); } + + void erase(gli_t it); + }; + GroupContainer m_Group; + const bool m_bSyncOnMsgNo; // It goes into a dedicated HS field. Could be true for balancing groups (not implemented). + SRT_GROUP_TYPE m_type; + CUDTSocket* m_listener; // A "group" can only have one listener. + srt::sync::atomic m_iBusy; + CallbackHolder m_cbConnectHook; + void installConnectHook(srt_connect_callback_fn* hook, void* opaq) + { + m_cbConnectHook.set(opaq, hook); + } + +public: + void apiAcquire() { ++m_iBusy; } + void apiRelease() { --m_iBusy; } + + // A normal cycle of the send/recv functions is the following: + // - [Initial API call for a group] + // - GroupKeeper - ctor + // - LOCK: GlobControlLock + // - Find the group ID in the group container (break if not found) + // - LOCK: GroupLock of that group + // - Set BUSY flag + // - UNLOCK GroupLock + // - UNLOCK GlobControlLock + // - [Call the sending function (sendBroadcast/sendBackup)] + // - LOCK GroupLock + // - Preparation activities + // - Loop over group members + // - Send over a single socket + // - Check send status and conditions + // - Exit, if nothing else to be done + // - Check links to send extra + // - UNLOCK GroupLock + // - Wait for first ready link + // - LOCK GroupLock + // - Check status and find sendable link + // - Send over a single socket + // - Check status and update data + // - UNLOCK GroupLock, Exit + // - GroupKeeper - dtor + // - LOCK GroupLock + // - Clear BUSY flag + // - UNLOCK GroupLock + // END. + // + // The possibility for isStillBusy to go on is only the following: + // 1. Before calling the API function. As GlobControlLock is locked, + // the nearest lock on GlobControlLock by GroupKeeper can happen: + // - before the group is moved to ClosedGroups (this allows it to be found) + // - after the group is moved to ClosedGroups (this makes the group not found) + // - NOT after the group was deleted, as it could not be found and occupied. + // + // 2. Before release of GlobControlLock (acquired by GC), but before the + // API function locks GroupLock: + // - the GC call to isStillBusy locks GroupLock, but BUSY flag is already set + // - GC then avoids deletion of the group + // + // 3. In any further place up to the exit of the API implementation function, + // the BUSY flag is still set. + // + // 4. After exit of GroupKeeper destructor and unlock of GroupLock + // - the group is no longer being accessed and can be freely deleted. + // - the group also can no longer be found by ID. + + bool isStillBusy() + { + sync::ScopedLock glk(m_GroupLock); + return m_iBusy || !m_Group.empty(); + } + + struct BufferedMessageStorage + { + size_t blocksize; + size_t maxstorage; + std::vector storage; + + BufferedMessageStorage(size_t blk, size_t max = 0) + : blocksize(blk) + , maxstorage(max) + , storage() + { + } + + char* get() + { + if (storage.empty()) + return new char[blocksize]; + + // Get the element from the end + char* block = storage.back(); + storage.pop_back(); + return block; + } + + void put(char* block) + { + if (storage.size() >= maxstorage) + { + // Simply delete + delete[] block; + return; + } + + // Put the block into the spare buffer + storage.push_back(block); + } + + ~BufferedMessageStorage() + { + for (size_t i = 0; i < storage.size(); ++i) + delete[] storage[i]; + } + }; + + struct BufferedMessage + { + static BufferedMessageStorage storage; + + SRT_MSGCTRL mc; + mutable char* data; + size_t size; + + BufferedMessage() + : data() + , size() + { + } + ~BufferedMessage() + { + if (data) + storage.put(data); + } + + // NOTE: size 's' must be checked against SRT_LIVE_MAX_PLSIZE + // before calling + void copy(const char* buf, size_t s) + { + size = s; + data = storage.get(); + memcpy(data, buf, s); + } + + BufferedMessage(const BufferedMessage& foreign) + : mc(foreign.mc) + , data(foreign.data) + , size(foreign.size) + { + foreign.data = 0; + } + + BufferedMessage& operator=(const BufferedMessage& foreign) + { + data = foreign.data; + size = foreign.size; + mc = foreign.mc; + + foreign.data = 0; + return *this; + } + + private: + void swap_with(BufferedMessage& b) + { + std::swap(this->mc, b.mc); + std::swap(this->data, b.data); + std::swap(this->size, b.size); + } + }; + + typedef std::deque senderBuffer_t; + // typedef StaticBuffer senderBuffer_t; + +private: + // Fields required for SRT_GTYPE_BACKUP groups. + senderBuffer_t m_SenderBuffer; + int32_t m_iSndOldestMsgNo; // oldest position in the sender buffer + sync::atomic m_iSndAckedMsgNo; + uint32_t m_uOPT_MinStabilityTimeout_us; + + // THIS function must be called only in a function for a group type + // that does use sender buffer. + int32_t addMessageToBuffer(const char* buf, size_t len, SRT_MSGCTRL& w_mc); + + std::set m_sPollID; // set of epoll ID to trigger + int m_iMaxPayloadSize; + int m_iAvgPayloadSize; + bool m_bSynRecving; + bool m_bSynSending; + bool m_bTsbPd; + bool m_bTLPktDrop; + int64_t m_iTsbPdDelay_us; + int m_RcvEID; + class CEPollDesc* m_RcvEpolld; + int m_SndEID; + class CEPollDesc* m_SndEpolld; + + int m_iSndTimeOut; // sending timeout in milliseconds + int m_iRcvTimeOut; // receiving timeout in milliseconds + + // Start times for TsbPd. These times shall be synchronized + // between all sockets in the group. The first connected one + // defines it, others shall derive it. The value 0 decides if + // this has been already set. + time_point m_tsStartTime; + time_point m_tsRcvPeerStartTime; + + struct ReadPos + { + std::vector packet; + SRT_MSGCTRL mctrl; + ReadPos(int32_t s) + : mctrl(srt_msgctrl_default) + { + mctrl.pktseq = s; + } + }; + std::map m_Positions; + + ReadPos* checkPacketAhead(); + + void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); + + /// The function polls alive member sockets and retrieves a list of read-ready. + /// [acquires lock for CUDT::uglobal()->m_GlobControlLock] + /// [[using locked(m_GroupLock)]] temporally unlocks-locks internally + /// + /// @returns list of read-ready sockets + /// @throws CUDTException(MJ_CONNECTION, MN_NOCONN, 0) + /// @throws CUDTException(MJ_AGAIN, MN_RDAVAIL, 0) + std::vector recv_WaitForReadReady(const std::vector& aliveMembers, std::set& w_broken); + + // This is the sequence number of a packet that has been previously + // delivered. Initially it should be set to SRT_SEQNO_NONE so that the sequence read + // from the first delivering socket will be taken as a good deal. + sync::atomic m_RcvBaseSeqNo; + + bool m_bOpened; // Set to true when at least one link is at least pending + bool m_bConnected; // Set to true on first link confirmed connected + bool m_bClosing; + + // There's no simple way of transforming config + // items that are predicted to be used on socket. + // Use some options for yourself, store the others + // for setting later on a socket. + std::vector m_config; + + // Signal for the blocking user thread that the packet + // is ready to deliver. + sync::Condition m_RcvDataCond; + sync::Mutex m_RcvDataLock; + sync::atomic m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket + sync::atomic m_iLastSchedMsgNo; + // Statistics + + struct Stats + { + // Stats state + time_point tsActivateTime; // Time when this group sent or received the first data packet + time_point tsLastSampleTime; // Time reset when clearing stats + + stats::Metric sent; // number of packets sent from the application + stats::Metric recv; // number of packets delivered from the group to the application + stats::Metric recvDrop; // number of packets dropped by the group receiver (not received from any member) + stats::Metric recvDiscard; // number of packets discarded as already delivered + + void init() + { + tsActivateTime = srt::sync::steady_clock::time_point(); + tsLastSampleTime = srt::sync::steady_clock::now(); + sent.reset(); + recv.reset(); + recvDrop.reset(); + recvDiscard.reset(); + } + + void reset() + { + tsLastSampleTime = srt::sync::steady_clock::now(); + + sent.resetTrace(); + recv.resetTrace(); + recvDrop.resetTrace(); + recvDiscard.resetTrace(); + } + } m_stats; + + void updateAvgPayloadSize(int size) + { + if (m_iAvgPayloadSize == -1) + m_iAvgPayloadSize = size; + else + m_iAvgPayloadSize = avg_iir<4>(m_iAvgPayloadSize, size); + } + + int avgRcvPacketSize() + { + // In case when no packet has been received yet, but already notified + // a dropped packet, its size will be SRT_LIVE_DEF_PLSIZE. It will be + // the value most matching in the typical uses, although no matter what + // value would be used here, each one would be wrong from some points + // of view. This one is simply the best choice for typical uses of groups + // provided that they are to be ued only for live mode. + return m_iAvgPayloadSize == -1 ? SRT_LIVE_DEF_PLSIZE : m_iAvgPayloadSize; + } + +public: + void bstatsSocket(CBytePerfMon* perf, bool clear); + + // Required after the call on newGroup on the listener side. + // On the listener side the group is lazily created just before + // accepting a new socket and therefore always open. + void setOpen() { m_bOpened = true; } + + std::string CONID() const + { +#if ENABLE_LOGGING + std::ostringstream os; + os << "@" << m_GroupID << ":"; + return os.str(); +#else + return ""; +#endif + } + + void resetInitialRxSequence() + { + // The app-reader doesn't care about the real sequence number. + // The first provided one will be taken as a good deal; even if + // this is going to be past the ISN, at worst it will be caused + // by TLPKTDROP. + m_RcvBaseSeqNo = SRT_SEQNO_NONE; + } + + bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) + { + using srt::sync::is_zero; + using srt_logging::gmlog; + + if (is_zero(m_tsStartTime)) + { + // The first socket, defines the group time for the whole group. + m_tsStartTime = w_start_time; + m_tsRcvPeerStartTime = w_peer_start_time; + return true; + } + + // Sanity check. This should never happen, fix the bug if found! + if (is_zero(m_tsRcvPeerStartTime)) + { + LOGC(gmlog.Error, log << "IPE: only StartTime is set, RcvPeerStartTime still 0!"); + // Kinda fallback, but that's not too safe. + m_tsRcvPeerStartTime = w_peer_start_time; + } + + // The redundant connection, derive the times + w_start_time = m_tsStartTime; + w_peer_start_time = m_tsRcvPeerStartTime; + + return false; + } + + // Live state synchronization + bool getBufferTimeBase(srt::CUDT* forthesakeof, time_point& w_tb, bool& w_wp, duration& w_dr); + bool applyGroupSequences(SRTSOCKET, int32_t& w_snd_isn, int32_t& w_rcv_isn); + + /// @brief Synchronize TSBPD base time and clock drift among members using the @a srcMember as a reference. + /// @param srcMember a reference for synchronization. + void synchronizeDrift(const srt::CUDT* srcMember); + + void updateLatestRcv(srt::CUDTSocket*); + + // Property accessors + SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, id, m_GroupID); + SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, peerid, m_PeerGroupID); + SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRT_GROUP_TYPE, type, m_type); + SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int32_t, currentSchedSequence, m_iLastSchedSeqNo); + SRTU_PROPERTY_RRW(std::set&, epollset, m_sPollID); + SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency, m_iTsbPdDelay_us); + SRTU_PROPERTY_RO(bool, synconmsgno, m_bSyncOnMsgNo); + SRTU_PROPERTY_RO(bool, closing, m_bClosing); +}; + +} // namespace srt + +#endif // INC_SRT_GROUP_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/group_backup.cpp b/trunk/3rdparty/srt-1-fit/srtcore/group_backup.cpp new file mode 100644 index 0000000000..9adb19607b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group_backup.cpp @@ -0,0 +1,159 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + + /***************************************************************************** + Written by + Haivision Systems Inc. + *****************************************************************************/ + +#include "platform_sys.h" +#include +#include + +#include "group_backup.h" + + +namespace srt +{ +namespace groups +{ + +using namespace std; +using namespace srt_logging; + +const char* stateToStr(BackupMemberState state) +{ + switch (state) + { + case srt::groups::BKUPST_UNKNOWN: + return "UNKNOWN"; + case srt::groups::BKUPST_PENDING: + return "PENDING"; + case srt::groups::BKUPST_STANDBY: + return "STANDBY"; + case srt::groups::BKUPST_ACTIVE_FRESH: + return "ACTIVE_FRESH"; + case srt::groups::BKUPST_ACTIVE_STABLE: + return "ACTIVE_STABLE"; + case srt::groups::BKUPST_ACTIVE_UNSTABLE: + return "ACTIVE_UNSTABLE"; + case srt::groups::BKUPST_ACTIVE_UNSTABLE_WARY: + return "ACTIVE_UNSTABLE_WARY"; + case srt::groups::BKUPST_BROKEN: + return "BROKEN"; + default: + break; + } + + return "WRONG_STATE"; +} + +/// @brief Compares group members by their weight (higher weight comes first), then state. +/// Higher weight comes first, same weight: stable, then fresh active. +struct FCompareByWeight +{ + /// @returns true if the first argument is less than (i.e. is ordered before) the second. + bool operator()(const BackupMemberStateEntry& a, const BackupMemberStateEntry& b) + { + if (a.pSocketData != NULL && b.pSocketData != NULL + && (a.pSocketData->weight != b.pSocketData->weight)) + return a.pSocketData->weight > b.pSocketData->weight; + + if (a.state != b.state) + { + SRT_STATIC_ASSERT(BKUPST_ACTIVE_STABLE > BKUPST_ACTIVE_FRESH, "Wrong ordering"); + return a.state > b.state; + } + + // the order does not matter, but comparator must return a different value for not equal a and b + return a.socketID < b.socketID; + } +}; + +void SendBackupCtx::recordMemberState(SocketData* pSockData, BackupMemberState st) +{ + m_memberStates.push_back(BackupMemberStateEntry(pSockData, st)); + ++m_stateCounter[st]; + + if (st == BKUPST_STANDBY) + { + m_standbyMaxWeight = max(m_standbyMaxWeight, pSockData->weight); + } + else if (isStateActive(st)) + { + m_activeMaxWeight = max(m_activeMaxWeight, pSockData->weight); + } +} + +void SendBackupCtx::updateMemberState(const SocketData* pSockData, BackupMemberState st) +{ + typedef vector::iterator iter_t; + for (iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) + { + if (i->pSocketData == NULL) + continue; + + if (i->pSocketData != pSockData) + continue; + + if (i->state == st) + return; + + --m_stateCounter[i->state]; + ++m_stateCounter[st]; + i->state = st; + + return; + } + + + LOGC(gslog.Error, + log << "IPE: SendBackupCtx::updateMemberState failed to locate member"); +} + +void SendBackupCtx::sortByWeightAndState() +{ + sort(m_memberStates.begin(), m_memberStates.end(), FCompareByWeight()); +} + +BackupMemberState SendBackupCtx::getMemberState(const SocketData* pSockData) const +{ + typedef vector::const_iterator const_iter_t; + for (const_iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) + { + if (i->pSocketData != pSockData) + continue; + + return i->state; + } + + // The entry was not found + // TODO: Maybe throw an exception here? + return BKUPST_UNKNOWN; +} + +unsigned SendBackupCtx::countMembersByState(BackupMemberState st) const +{ + return m_stateCounter[st]; +} + +std::string SendBackupCtx::printMembers() const +{ + stringstream ss; + typedef vector::const_iterator const_iter_t; + for (const_iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) + { + ss << "@" << i->socketID << " w " << i->pSocketData->weight << " state " << stateToStr(i->state) << ", "; + } + return ss.str(); +} + +} // namespace groups +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/group_backup.h b/trunk/3rdparty/srt-1-fit/srtcore/group_backup.h new file mode 100644 index 0000000000..790cf55ed6 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group_backup.h @@ -0,0 +1,128 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + + /***************************************************************************** + Written by + Haivision Systems Inc. + *****************************************************************************/ + +#ifndef INC_SRT_GROUP_BACKUP_H +#define INC_SRT_GROUP_BACKUP_H + +#include "srt.h" +#include "common.h" +#include "group_common.h" + +#include + +namespace srt +{ +namespace groups +{ + enum BackupMemberState + { + BKUPST_UNKNOWN = -1, + + BKUPST_PENDING = 0, + BKUPST_STANDBY = 1, + BKUPST_BROKEN = 2, + + BKUPST_ACTIVE_UNSTABLE = 3, + BKUPST_ACTIVE_UNSTABLE_WARY = 4, + BKUPST_ACTIVE_FRESH = 5, + BKUPST_ACTIVE_STABLE = 6, + + BKUPST_E_SIZE = 7 + }; + + const char* stateToStr(BackupMemberState state); + + inline bool isStateActive(BackupMemberState state) + { + if (state == BKUPST_ACTIVE_FRESH + || state == BKUPST_ACTIVE_STABLE + || state == BKUPST_ACTIVE_UNSTABLE + || state == BKUPST_ACTIVE_UNSTABLE_WARY) + { + return true; + } + + return false; + } + + struct BackupMemberStateEntry + { + BackupMemberStateEntry(SocketData* psock, BackupMemberState st) + : pSocketData(psock) + , socketID(psock->id) + , state(st) + {} + + SocketData* pSocketData; // accessing pSocketDataIt requires m_GroupLock + SRTSOCKET socketID; // therefore socketID is saved separately (needed to close broken sockets) + BackupMemberState state; + }; + + /// @brief A context needed for main/backup sending function. + /// @todo Using gli_t here does not allow to safely store the context outside of the sendBackup calls. + class SendBackupCtx + { + public: + SendBackupCtx() + : m_stateCounter() // default init with zeros + , m_activeMaxWeight() + , m_standbyMaxWeight() + { + } + + /// @brief Adds or updates a record of the member socket state. + /// @param pSocketDataIt Iterator to a socket + /// @param st State of the memmber socket + /// @todo Implement updating member state + void recordMemberState(SocketData* pSocketDataIt, BackupMemberState st); + + /// @brief Updates a record of the member socket state. + /// @param pSocketDataIt Iterator to a socket + /// @param st State of the memmber socket + /// @todo To be replaced by recordMemberState + /// @todo Update max weights? + void updateMemberState(const SocketData* pSocketDataIt, BackupMemberState st); + + /// @brief sorts members in order + /// Higher weight comes first, same weight: stable first, then fresh active. + void sortByWeightAndState(); + + BackupMemberState getMemberState(const SocketData* pSocketDataIt) const; + + unsigned countMembersByState(BackupMemberState st) const; + + const std::vector& memberStates() const { return m_memberStates; } + + uint16_t maxStandbyWeight() const { return m_standbyMaxWeight; } + uint16_t maxActiveWeight() const { return m_activeMaxWeight; } + + std::string printMembers() const; + + void setRateEstimate(const CRateEstimator& rate) { m_rateEstimate = rate; } + + const CRateEstimator& getRateEstimate() const { return m_rateEstimate; } + + private: + std::vector m_memberStates; // TODO: consider std::map here? + unsigned m_stateCounter[BKUPST_E_SIZE]; + uint16_t m_activeMaxWeight; + uint16_t m_standbyMaxWeight; + CRateEstimator m_rateEstimate; // The rate estimator state of the active link to copy to a backup on activation. + }; + +} // namespace groups +} // namespace srt + +#endif // INC_SRT_GROUP_BACKUP_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/group_common.cpp b/trunk/3rdparty/srt-1-fit/srtcore/group_common.cpp new file mode 100644 index 0000000000..536bdf52ce --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group_common.cpp @@ -0,0 +1,63 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +Written by + Haivision Systems Inc. +*****************************************************************************/ + +#include "platform_sys.h" + +#include "group_common.h" +#include "api.h" + +namespace srt +{ +namespace groups +{ + +SocketData prepareSocketData(CUDTSocket* s) +{ + // This uses default SRT_GST_BROKEN because when the group operation is done, + // then the SRT_GST_IDLE state automatically turns into SRT_GST_RUNNING. This is + // recognized as an initial state of the fresh added socket to the group, + // so some "initial configuration" must be done on it, after which it's + // turned into SRT_GST_RUNNING, that is, it's treated as all others. When + // set to SRT_GST_BROKEN, this socket is disregarded. This socket isn't cleaned + // up, however, unless the status is simultaneously SRTS_BROKEN. + + // The order of operations is then: + // - add the socket to the group in this "broken" initial state + // - connect the socket (or get it extracted from accept) + // - update the socket state (should be SRTS_CONNECTED) + // - once the connection is established (may take time with connect), set SRT_GST_IDLE + // - the next operation of send/recv will automatically turn it into SRT_GST_RUNNING + SocketData sd = { + s->m_SocketID, + s, + -1, + SRTS_INIT, + SRT_GST_BROKEN, + SRT_GST_BROKEN, + -1, + -1, + sockaddr_any(), + sockaddr_any(), + false, + false, + false, + 0, // weight + 0 // pktSndDropTotal + }; + return sd; +} + +} // namespace groups +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/group_common.h b/trunk/3rdparty/srt-1-fit/srtcore/group_common.h new file mode 100644 index 0000000000..d780d0b9a3 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group_common.h @@ -0,0 +1,62 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +Written by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef INC_SRT_GROUP_COMMON_H +#define INC_SRT_GROUP_COMMON_H + +#include "srt.h" +#include "common.h" +#include "core.h" + +#include + +namespace srt +{ +namespace groups +{ + typedef SRT_MEMBERSTATUS GroupState; + + struct SocketData + { + SRTSOCKET id; // same as ps->m_SocketID + CUDTSocket* ps; + int token; + SRT_SOCKSTATUS laststatus; + GroupState sndstate; + GroupState rcvstate; + int sndresult; + int rcvresult; + sockaddr_any agent; + sockaddr_any peer; + bool ready_read; + bool ready_write; + bool ready_error; + + // Configuration + uint16_t weight; + + // Stats + int64_t pktSndDropTotal; + }; + + SocketData prepareSocketData(CUDTSocket* s); + + typedef std::list group_t; + typedef group_t::iterator gli_t; + +} // namespace groups +} // namespace srt + +#endif // INC_SRT_GROUP_COMMON_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/handshake.cpp b/trunk/3rdparty/srt-1-fit/srtcore/handshake.cpp index 11104365cf..9e867c97e9 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/handshake.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/handshake.cpp @@ -43,6 +43,8 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#include "platform_sys.h" + #include #include #include @@ -50,32 +52,33 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "udt.h" +#include "api.h" #include "core.h" #include "handshake.h" #include "utilities.h" using namespace std; - - -CHandShake::CHandShake(): -m_iVersion(0), -m_iType(0), // Universal: UDT_UNDEFINED or no flags -m_iISN(0), -m_iMSS(0), -m_iFlightFlagSize(0), -m_iReqType(URQ_WAVEAHAND), -m_iID(0), -m_iCookie(0), -m_extension(false) +using namespace srt; + + +srt::CHandShake::CHandShake() + : m_iVersion(0) + , m_iType(0) // Universal: UDT_UNDEFINED or no flags + , m_iISN(0) + , m_iMSS(0) + , m_iFlightFlagSize(0) + , m_iReqType(URQ_WAVEAHAND) + , m_iID(0) + , m_iCookie(0) + , m_extension(false) { for (int i = 0; i < 4; ++ i) m_piPeerIP[i] = 0; } -int CHandShake::store_to(char* buf, ref_t r_size) +int srt::CHandShake::store_to(char* buf, size_t& w_size) { - size_t& size = *r_size; - if (size < m_iContentSize) + if (w_size < m_iContentSize) return -1; int32_t* p = reinterpret_cast(buf); @@ -90,12 +93,12 @@ int CHandShake::store_to(char* buf, ref_t r_size) for (int i = 0; i < 4; ++ i) *p++ = m_piPeerIP[i]; - size = m_iContentSize; + w_size = m_iContentSize; return 0; } -int CHandShake::load_from(const char* buf, size_t size) +int srt::CHandShake::load_from(const char* buf, size_t size) { if (size < m_iContentSize) return -1; @@ -118,6 +121,8 @@ int CHandShake::load_from(const char* buf, size_t size) #ifdef ENABLE_LOGGING +namespace srt +{ const char* srt_rejectreason_name [] = { "UNKNOWN", "SYSTEM", @@ -134,15 +139,31 @@ const char* srt_rejectreason_name [] = { "MESSAGEAPI", "CONGESTION", "FILTER", + "GROUP", + "TIMEOUT" }; +} -std::string RequestTypeStr(UDTRequestType rq) +std::string srt::RequestTypeStr(UDTRequestType rq) { if (rq >= URQ_FAILURE_TYPES) { - SRT_REJECT_REASON rej = RejectReasonForURQ(rq); - int id = rej; - return std::string("ERROR:") + srt_rejectreason_name[id]; + std::ostringstream rt; + rt << "ERROR:"; + int id = RejectReasonForURQ(rq); + if (id < (int) Size(srt_rejectreason_name)) + rt << srt_rejectreason_name[id]; + else if (id < SRT_REJC_USERDEFINED) + { + if (id < SRT_REJC_PREDEFINED) + rt << "UNKNOWN:" << id; + else + rt << "PREDEFINED:" << (id - SRT_REJC_PREDEFINED); + } + else + rt << "USERDEFINED:" << (id - SRT_REJC_USERDEFINED); + + return rt.str(); } switch ( rq ) @@ -156,7 +177,7 @@ std::string RequestTypeStr(UDTRequestType rq) } } -string CHandShake::RdvStateStr(CHandShake::RendezvousState s) +string srt::CHandShake::RdvStateStr(CHandShake::RendezvousState s) { switch (s) { @@ -172,11 +193,22 @@ string CHandShake::RdvStateStr(CHandShake::RendezvousState s) } #endif -string CHandShake::show() +bool srt::CHandShake::valid() +{ + if (m_iVersion < CUDT::HS_VERSION_UDT4 + || m_iISN < 0 || m_iISN >= CSeqNo::m_iMaxSeqNo + || m_iMSS < 32 + || m_iFlightFlagSize < 2) + return false; + + return true; +} + +string srt::CHandShake::show() { ostringstream so; - so << "version=" << m_iVersion << " type=" << hex << m_iType << dec + so << "version=" << m_iVersion << " type=0x" << hex << m_iType << dec << " ISN=" << m_iISN << " MSS=" << m_iMSS << " FLW=" << m_iFlightFlagSize << " reqtype=" << RequestTypeStr(m_iReqType) << " srcID=" << m_iID << " cookie=" << hex << m_iCookie << dec @@ -191,9 +223,12 @@ string CHandShake::show() // CHandShake, not CUDT. if ( m_iVersion > CUDT::HS_VERSION_UDT4 ) { - so << "EXT: "; - if (m_iType == 0) // no flags at all - so << "none"; + const int flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_iType); + so << "FLAGS: "; + if (flags == SrtHSRequest::SRT_MAGIC_CODE) + so << "MAGIC"; + else if (m_iType == 0) + so << "NONE"; // no flags and no advertised pbkeylen else so << ExtensionFlagStr(m_iType); } @@ -201,7 +236,7 @@ string CHandShake::show() return so.str(); } -string CHandShake::ExtensionFlagStr(int32_t fl) +string srt::CHandShake::ExtensionFlagStr(int32_t fl) { std::ostringstream out; if ( fl & HS_EXT_HSREQ ) @@ -211,7 +246,7 @@ string CHandShake::ExtensionFlagStr(int32_t fl) if ( fl & HS_EXT_CONFIG ) out << " config"; - int kl = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(fl) << 6; + const int kl = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(fl) << 6; if (kl != 0) { out << " AES-" << kl; @@ -228,7 +263,7 @@ string CHandShake::ExtensionFlagStr(int32_t fl) // XXX This code isn't currently used. Left here because it can // be used in future, should any refactoring for the "manual word placement" // code be done. -bool SrtHSRequest::serialize(char* buf, size_t size) const +bool srt::SrtHSRequest::serialize(char* buf, size_t size) const { if (size < SRT_HS_SIZE) return false; @@ -243,7 +278,7 @@ bool SrtHSRequest::serialize(char* buf, size_t size) const } -bool SrtHSRequest::deserialize(const char* buf, size_t size) +bool srt::SrtHSRequest::deserialize(const char* buf, size_t size) { m_iSrtVersion = 0; // just to let users recognize if it succeeded or not. @@ -258,3 +293,35 @@ bool SrtHSRequest::deserialize(const char* buf, size_t size) m_iSrtReserved = (*p++); return true; } + +std::string srt::SrtFlagString(int32_t flags) +{ +#define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) + + std::string output; + static std::string namera[] = { "TSBPD-snd", "TSBPD-rcv", "haicrypt", "TLPktDrop", "NAKReport", "ReXmitFlag", "StreamAPI" }; + + size_t i = 0; + for (; i < LEN(namera); ++i) + { + if ((flags & 1) == 1) + { + output += "+" + namera[i] + " "; + } + else + { + output += "-" + namera[i] + " "; + } + + flags >>= 1; + } + +#undef LEN + + if (flags != 0) + { + output += "+unknown"; + } + + return output; +} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/handshake.h b/trunk/3rdparty/srt-1-fit/srtcore/handshake.h index 4b4c7d8073..93a351f396 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/handshake.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/handshake.h @@ -43,12 +43,17 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -#ifndef INC__HANDSHAKE_H -#define INC__HANDSHAKE_H +#ifndef INC_SRT_HANDSHAKE_H +#define INC_SRT_HANDSHAKE_H + +#include #include "crypto.h" #include "utilities.h" +namespace srt +{ + typedef Bits<31, 16> HS_CMDSPEC_CMD; typedef Bits<15, 0> HS_CMDSPEC_SIZE; @@ -93,6 +98,7 @@ const int SRT_CMD_REJECT = 0, // REJECT is only a symbol for return type SRT_CMD_SID = 5, SRT_CMD_CONGESTION = 6, SRT_CMD_FILTER = 7, + SRT_CMD_GROUP = 8, SRT_CMD_NONE = -1; // for cases when {no pong for ping is required} | {no extension block found} enum SrtDataStruct @@ -102,7 +108,7 @@ enum SrtDataStruct SRT_HS_LATENCY, // Keep it always last - SRT_HS__SIZE + SRT_HS_E_SIZE }; // For HSv5 the lo and hi part is used for particular side's latency @@ -112,30 +118,21 @@ typedef Bits<15, 0> SRT_HS_LATENCY_SND; typedef Bits<15, 0> SRT_HS_LATENCY_LEG; -// XXX These structures are currently unused. The code can be changed -// so that these are used instead of manual tailoring of the messages. struct SrtHandshakeExtension { -protected: + int16_t type; + std::vector contents; - uint32_t m_SrtCommand; // Used only in extension - -public: - SrtHandshakeExtension(int cmd) - { - m_SrtCommand = cmd; - } + SrtHandshakeExtension(int16_t cmd): type(cmd) {} +}; - void setCommand(int cmd) - { - m_SrtCommand = cmd; - } +// Implemented in core.cpp, so far +void SrtExtractHandshakeExtensions(const char* bufbegin, size_t size, + std::vector& w_output); -}; struct SrtHSRequest: public SrtHandshakeExtension { - typedef Bits<31, 16> SRT_HSTYPE_ENCFLAGS; typedef Bits<15, 0> SRT_HSTYPE_HSFLAGS; @@ -154,6 +151,19 @@ struct SrtHSRequest: public SrtHandshakeExtension return base | SRT_HSTYPE_ENCFLAGS::wrap( SRT_PBKEYLEN_BITS::unwrap(crypto_keylen) ); } + // Group handshake extension layout + + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Group ID | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // | Group Type | Group's Flags | Group's Weight | + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + typedef Bits<31, 24> HS_GROUP_TYPE; + typedef Bits<23, 16> HS_GROUP_FLAGS; + typedef Bits<15, 0> HS_GROUP_WEIGHT; + private: friend class CHandShake; @@ -229,25 +239,37 @@ enum UDTRequestType // --> CONCLUSION (with response extensions, if RESPONDER) // <-- AGREEMENT (sent exclusively by INITIATOR upon reception of CONCLUSIOn with response extensions) - // Errors reported by the peer, also used as useless error codes - // in handshake processing functions. - URQ_FAILURE_TYPES = 1000 + // This marks the beginning of values that are error codes. + URQ_FAILURE_TYPES = 1000, // NOTE: codes above 1000 are reserved for failure codes for - // rejection reason, as per `SRT_REJECT_REASON` enum. DO NOT - // add any new values here. + // rejection reason, as per `SRT_REJECT_REASON` enum. The + // actual rejection code is the value of the request type + // minus URQ_FAILURE_TYPES. + + // This is in order to return standard error codes for server + // data retrieval failures. + URQ_SERVER_FAILURE_TYPES = URQ_FAILURE_TYPES + SRT_REJC_PREDEFINED, + + // This is for a completely user-defined reject reasons. + URQ_USER_FAILURE_TYPES = URQ_FAILURE_TYPES + SRT_REJC_USERDEFINED }; -inline UDTRequestType URQFailure(SRT_REJECT_REASON reason) +inline UDTRequestType URQFailure(int reason) { return UDTRequestType(URQ_FAILURE_TYPES + int(reason)); } -inline SRT_REJECT_REASON RejectReasonForURQ(UDTRequestType req) +inline int RejectReasonForURQ(UDTRequestType req) { - if (req < URQ_FAILURE_TYPES || req - URQ_FAILURE_TYPES >= SRT_REJ__SIZE) + if (req < URQ_FAILURE_TYPES) return SRT_REJ_UNKNOWN; - return SRT_REJECT_REASON(req - URQ_FAILURE_TYPES); + + int reason = req - URQ_FAILURE_TYPES; + if (reason < SRT_REJC_PREDEFINED && reason >= SRT_REJ_E_SIZE) + return SRT_REJ_UNKNOWN; + + return reason; } // DEPRECATED values. Use URQFailure(SRT_REJECT_REASON). @@ -265,20 +287,20 @@ inline std::string RequestTypeStr(UDTRequestType) { return ""; } class CHandShake { public: - CHandShake(); + CHandShake(); - int store_to(char* buf, ref_t size); - int load_from(const char* buf, size_t size); + int store_to(char* buf, size_t& size); + int load_from(const char* buf, size_t size); public: - // This is the size of SERIALIZED handshake. - // Might be defined as simply sizeof(CHandShake), but the - // enum values would have to be forced as int32_t, which is only - // available in C++11. Theoretically they are all 32-bit, but - // such a statement is not reliable and not portable. - static const size_t m_iContentSize = 48; // Size of hand shake data + // This is the size of SERIALIZED handshake. + // Might be defined as simply sizeof(CHandShake), but the + // enum values would have to be forced as int32_t, which is only + // available in C++11. Theoretically they are all 32-bit, but + // such a statement is not reliable and not portable. + static const size_t m_iContentSize = 48; // Size of hand shake data - // Extension flags + // Extension flags static const int32_t HS_EXT_HSREQ = BIT(0); static const int32_t HS_EXT_KMREQ = BIT(1); @@ -290,53 +312,55 @@ class CHandShake int32_t flags() { return m_iType; } public: - int32_t m_iVersion; // UDT version (HS_VERSION_* symbols) - int32_t m_iType; // UDT4: socket type (only UDT_DGRAM is valid); SRT1: extension flags - int32_t m_iISN; // random initial sequence number - int32_t m_iMSS; // maximum segment size - int32_t m_iFlightFlagSize; // flow control window size - UDTRequestType m_iReqType; // handshake stage - int32_t m_iID; // socket ID - int32_t m_iCookie; // cookie - uint32_t m_piPeerIP[4]; // The IP address that the peer's UDP port is bound to - - bool m_extension; - - std::string show(); - -// The rendezvous state machine used in HSv5 only (in HSv4 everything is happening the old way). -// -// The WAVING state is the very initial state of the rendezvous connection and restored after the -// connection is closed. -// The ATTENTION and FINE are two alternative states that are transited to from WAVING. The possible -// situations are: -// - "serial arrangement": one party transits to ATTENTION and the other party transits to FINE -// - "parallel arrangement" both parties transit to ATTENTION -// -// Parallel arrangement is a "virtually impossible" case, in which both parties must send the first -// URQ_WAVEAHAND message in a perfect time synchronization, when they are started at exactly the same -// time, on machines with exactly the same performance and all things preceding the message sending -// have taken perfectly identical amount of time. This isn't anyhow possible otherwise because if -// the clients have started at different times, the one who started first sends a message and the -// system of the receiver buffers this message even before the client binds the port for enough long -// time so that it outlasts also the possible second, repeated waveahand. -enum RendezvousState -{ - RDV_INVALID, //< This socket wasn't prepared for rendezvous process. Reject any events. - RDV_WAVING, //< Initial state for rendezvous. No contact seen from the peer. - RDV_ATTENTION, //< When received URQ_WAVEAHAND. [WAVING]:URQ_WAVEAHAND --> [ATTENTION]. - RDV_FINE, //< When received URQ_CONCLUSION. [WAVING]:URQ_CONCLUSION --> [FINE]. - RDV_INITIATED, //< When received URQ_CONCLUSION+HSREQ extension in ATTENTION state. - RDV_CONNECTED //< Final connected state. [ATTENTION]:URQ_CONCLUSION --> [CONNECTED] <-- [FINE]:URQ_AGREEMENT. -}; + int32_t m_iVersion; // UDT version (HS_VERSION_* symbols) + int32_t m_iType; // UDT4: socket type (only UDT_DGRAM is valid); SRT1: extension flags + int32_t m_iISN; // random initial sequence number + int32_t m_iMSS; // maximum segment size + int32_t m_iFlightFlagSize; // flow control window size + UDTRequestType m_iReqType; // handshake stage + int32_t m_iID; // SRT socket ID of HS sender + int32_t m_iCookie; // cookie + uint32_t m_piPeerIP[4]; // The IP address that the peer's UDP port is bound to + + bool m_extension; + + bool valid(); + std::string show(); + + // The rendezvous state machine used in HSv5 only (in HSv4 everything is happening the old way). + // + // The WAVING state is the very initial state of the rendezvous connection and restored after the + // connection is closed. + // The ATTENTION and FINE are two alternative states that are transited to from WAVING. The possible + // situations are: + // - "serial arrangement": one party transits to ATTENTION and the other party transits to FINE + // - "parallel arrangement" both parties transit to ATTENTION + // + // Parallel arrangement is a "virtually impossible" case, in which both parties must send the first + // URQ_WAVEAHAND message in a perfect time synchronization, when they are started at exactly the same + // time, on machines with exactly the same performance and all things preceding the message sending + // have taken perfectly identical amount of time. This isn't anyhow possible otherwise because if + // the clients have started at different times, the one who started first sends a message and the + // system of the receiver buffers this message even before the client binds the port for enough long + // time so that it outlasts also the possible second, repeated waveahand. + enum RendezvousState + { + RDV_INVALID, //< This socket wasn't prepared for rendezvous process. Reject any events. + RDV_WAVING, //< Initial state for rendezvous. No contact seen from the peer. + RDV_ATTENTION, //< When received URQ_WAVEAHAND. [WAVING]:URQ_WAVEAHAND --> [ATTENTION]. + RDV_FINE, //< When received URQ_CONCLUSION. [WAVING]:URQ_CONCLUSION --> [FINE]. + RDV_INITIATED, //< When received URQ_CONCLUSION+HSREQ extension in ATTENTION state. + RDV_CONNECTED //< Final connected state. [ATTENTION]:URQ_CONCLUSION --> [CONNECTED] <-- [FINE]:URQ_AGREEMENT. + }; #if ENABLE_LOGGING -static std::string RdvStateStr(RendezvousState s); + static std::string RdvStateStr(RendezvousState s); #else -static std::string RdvStateStr(RendezvousState) { return ""; } + static std::string RdvStateStr(RendezvousState) { return ""; } #endif }; +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/list.cpp b/trunk/3rdparty/srt-1-fit/srtcore/list.cpp index f5a0772432..2125995958 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/list.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/list.cpp @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,664 +50,765 @@ modified by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include "list.h" #include "packet.h" +#include "logging.h" -CSndLossList::CSndLossList(int size): -m_caSeq(), -m_iHead(-1), -m_iLength(0), -m_iSize(size), -m_iLastInsertPos(-1), -m_ListLock() +// Use "inline namespace" in C++11 +namespace srt_logging +{ +extern Logger qrlog; +extern Logger qslog; +} + +using srt_logging::qrlog; +using srt_logging::qslog; + +using namespace srt::sync; + +srt::CSndLossList::CSndLossList(int size) + : m_caSeq() + , m_iHead(-1) + , m_iLength(0) + , m_iSize(size) + , m_iLastInsertPos(-1) + , m_ListLock() { m_caSeq = new Seq[size]; - // -1 means there is no data in the node - for (int i = 0; i < size; ++ i) - { - m_caSeq[i].data1 = -1; - m_caSeq[i].data2 = -1; - } + // -1 means there is no data in the node + for (int i = 0; i < size; ++i) + { + m_caSeq[i].seqstart = SRT_SEQNO_NONE; + m_caSeq[i].seqend = SRT_SEQNO_NONE; + } + + // sender list needs mutex protection + setupMutex(m_ListLock, "LossList"); +} - // sender list needs mutex protection - pthread_mutex_init(&m_ListLock, 0); +srt::CSndLossList::~CSndLossList() +{ + delete[] m_caSeq; + releaseMutex(m_ListLock); } -CSndLossList::~CSndLossList() +void srt::CSndLossList::traceState() const { - delete [] m_caSeq; - pthread_mutex_destroy(&m_ListLock); + int pos = m_iHead; + while (pos != SRT_SEQNO_NONE) + { + std::cout << pos << ":[" << m_caSeq[pos].seqstart; + if (m_caSeq[pos].seqend != SRT_SEQNO_NONE) + std::cout << ", " << m_caSeq[pos].seqend; + std::cout << "], "; + pos = m_caSeq[pos].inext; + } + std::cout << "\n"; } -int CSndLossList::insert(int32_t seqno1, int32_t seqno2) +int srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) { - CGuard listguard(m_ListLock); - - if (0 == m_iLength) - { - // insert data into an empty list - - m_iHead = 0; - m_caSeq[m_iHead].data1 = seqno1; - if (seqno2 != seqno1) - m_caSeq[m_iHead].data2 = seqno2; - - m_caSeq[m_iHead].next = -1; - m_iLastInsertPos = m_iHead; - - m_iLength += CSeqNo::seqlen(seqno1, seqno2); - - return m_iLength; - } - - // otherwise find the position where the data can be inserted - int origlen = m_iLength; - int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno1); - int loc = (m_iHead + offset + m_iSize) % m_iSize; - - if (offset < 0) - { - // Insert data prior to the head pointer - - m_caSeq[loc].data1 = seqno1; - if (seqno2 != seqno1) - m_caSeq[loc].data2 = seqno2; - - // new node becomes head - m_caSeq[loc].next = m_iHead; - m_iHead = loc; - m_iLastInsertPos = loc; - - m_iLength += CSeqNo::seqlen(seqno1, seqno2); - } - else if (offset > 0) - { - if (seqno1 == m_caSeq[loc].data1) - { - m_iLastInsertPos = loc; - - // first seqno is equivlent, compare the second - if (-1 == m_caSeq[loc].data2) - { - if (seqno2 != seqno1) + if (seqno1 < 0 || seqno2 < 0 ) { + LOGC(qslog.Error, log << "IPE: Tried to insert negative seqno " << seqno1 << ":" << seqno2 + << " into sender's loss list. Ignoring."); + return 0; + } + + const int inserted_range = CSeqNo::seqlen(seqno1, seqno2); + if (inserted_range <= 0 || inserted_range >= m_iSize) { + LOGC(qslog.Error, log << "IPE: Tried to insert too big range of seqno: " << inserted_range << ". Ignoring. " + << "seqno " << seqno1 << ":" << seqno2); + return 0; + } + + ScopedLock listguard(m_ListLock); + + if (m_iLength == 0) + { + insertHead(0, seqno1, seqno2); + return m_iLength; + } + + // Find the insert position in the non-empty list + const int origlen = m_iLength; + const int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno1); + + if (offset >= m_iSize) + { + LOGC(qslog.Error, log << "IPE: New loss record is too far from the first record. Ignoring. " + << "First loss seqno " << m_caSeq[m_iHead].seqstart + << ", insert seqno " << seqno1 << ":" << seqno2); + return 0; + } + + int loc = (m_iHead + offset + m_iSize) % m_iSize; + + if (loc < 0) + { + const int offset_seqno2 = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno2); + const int loc_seqno2 = (m_iHead + offset_seqno2 + m_iSize) % m_iSize; + + if (loc_seqno2 < 0) + { + // The size of the CSndLossList should be at least the size of the flow window. + // It means that all the packets sender has sent should fit within m_iSize. + // If the new loss does not fit, there is some error. + LOGC(qslog.Error, log << "IPE: New loss record is too old. Ignoring. " + << "First loss seqno " << m_caSeq[m_iHead].seqstart + << ", insert seqno " << seqno1 << ":" << seqno2); + return 0; + } + + loc = loc_seqno2; + } + + if (offset < 0) + { + insertHead(loc, seqno1, seqno2); + } + else if (offset > 0) + { + if (seqno1 == m_caSeq[loc].seqstart) + { + const bool updated = updateElement(loc, seqno1, seqno2); + if (!updated) + return 0; + } + else + { + // Find the prior node. + // It should be the highest sequence number less than seqno1. + // 1. Start the search either from m_iHead, or from m_iLastInsertPos + int i = m_iHead; + if ((m_iLastInsertPos != -1) && (CSeqNo::seqcmp(m_caSeq[m_iLastInsertPos].seqstart, seqno1) < 0)) + i = m_iLastInsertPos; + + // 2. Find the highest sequence number less than seqno1. + while (m_caSeq[i].inext != -1 && CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqno1) < 0) + i = m_caSeq[i].inext; + + // 3. Check if seqno1 overlaps with (seqbegin, seqend) + const int seqend = m_caSeq[i].seqend == SRT_SEQNO_NONE ? m_caSeq[i].seqstart : m_caSeq[i].seqend; + + if (CSeqNo::seqcmp(seqend, seqno1) < 0 && CSeqNo::incseq(seqend) != seqno1) { - m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; - m_caSeq[loc].data2 = seqno2; + // No overlap + // TODO: Here we should actually insert right after i, not at loc. + insertAfter(loc, i, seqno1, seqno2); } - } - else if (CSeqNo::seqcmp(seqno2, m_caSeq[loc].data2) > 0) - { - // new seq pair is longer than old pair, e.g., insert [3, 7] to [3, 5], becomes [3, 7] - m_iLength += CSeqNo::seqlen(m_caSeq[loc].data2, seqno2) - 1; - m_caSeq[loc].data2 = seqno2; - } - else - // Do nothing if it is already there - return 0; - } - else - { - // searching the prior node - int i; - if ((-1 != m_iLastInsertPos) && (CSeqNo::seqcmp(m_caSeq[m_iLastInsertPos].data1, seqno1) < 0)) - i = m_iLastInsertPos; - else - i = m_iHead; - - while ((-1 != m_caSeq[i].next) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].next].data1, seqno1) < 0)) - i = m_caSeq[i].next; - - if ((-1 == m_caSeq[i].data2) || (CSeqNo::seqcmp(m_caSeq[i].data2, seqno1) < 0)) - { - m_iLastInsertPos = loc; - - // no overlap, create new node - m_caSeq[loc].data1 = seqno1; - if (seqno2 != seqno1) - m_caSeq[loc].data2 = seqno2; - - m_caSeq[loc].next = m_caSeq[i].next; - m_caSeq[i].next = loc; - - m_iLength += CSeqNo::seqlen(seqno1, seqno2); - } - else - { - m_iLastInsertPos = i; - - // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7] - if (CSeqNo::seqcmp(m_caSeq[i].data2, seqno2) < 0) + else { - m_iLength += CSeqNo::seqlen(m_caSeq[i].data2, seqno2) - 1; - m_caSeq[i].data2 = seqno2; + // TODO: Replace with updateElement(i, seqno1, seqno2). + // Some changes to updateElement(..) are required. + m_iLastInsertPos = i; + if (CSeqNo::seqcmp(seqend, seqno2) >= 0) + return 0; + + // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7] + m_iLength += CSeqNo::seqlen(seqend, seqno2) - 1; + m_caSeq[i].seqend = seqno2; - loc = i; + loc = i; } - else - return 0; - } - } - } - else - { - m_iLastInsertPos = m_iHead; - - // insert to head node - if (seqno2 != seqno1) - { - if (-1 == m_caSeq[loc].data2) - { - m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; - m_caSeq[loc].data2 = seqno2; - } - else if (CSeqNo::seqcmp(seqno2, m_caSeq[loc].data2) > 0) - { - m_iLength += CSeqNo::seqlen(m_caSeq[loc].data2, seqno2) - 1; - m_caSeq[loc].data2 = seqno2; - } - else + } + } + else // offset == 0, loc == m_iHead + { + const bool updated = updateElement(m_iHead, seqno1, seqno2); + if (!updated) return 0; - } - else - return 0; - } - - // coalesce with next node. E.g., [3, 7], ..., [6, 9] becomes [3, 9] - while ((-1 != m_caSeq[loc].next) && (-1 != m_caSeq[loc].data2)) - { - const int i = m_caSeq[loc].next; - - if (CSeqNo::seqcmp(m_caSeq[i].data1, CSeqNo::incseq(m_caSeq[loc].data2)) > 0) - break; - - // coalesce if there is overlap - if (-1 != m_caSeq[i].data2) - { - if (CSeqNo::seqcmp(m_caSeq[i].data2, m_caSeq[loc].data2) > 0) - { - if (CSeqNo::seqcmp(m_caSeq[loc].data2, m_caSeq[i].data1) >= 0) - m_iLength -= CSeqNo::seqlen(m_caSeq[i].data1, m_caSeq[loc].data2); - - m_caSeq[loc].data2 = m_caSeq[i].data2; - } - else - m_iLength -= CSeqNo::seqlen(m_caSeq[i].data1, m_caSeq[i].data2); - } - else - { - if (m_caSeq[i].data1 == CSeqNo::incseq(m_caSeq[loc].data2)) - m_caSeq[loc].data2 = m_caSeq[i].data1; - else - m_iLength--; - } - - m_caSeq[i].data1 = -1; - m_caSeq[i].data2 = -1; - m_caSeq[loc].next = m_caSeq[i].next; - } - - return m_iLength - origlen; + } + + coalesce(loc); + return m_iLength - origlen; } -void CSndLossList::remove(int32_t seqno) +void srt::CSndLossList::removeUpTo(int32_t seqno) { - CGuard listguard(m_ListLock); - - if (0 == m_iLength) - return; - - // Remove all from the head pointer to a node with a larger seq. no. or the list is empty - int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno); - int loc = (m_iHead + offset + m_iSize) % m_iSize; - - if (0 == offset) - { - // It is the head. Remove the head and point to the next node - loc = (loc + 1) % m_iSize; - - if (-1 == m_caSeq[m_iHead].data2) - loc = m_caSeq[m_iHead].next; - else - { - m_caSeq[loc].data1 = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[m_iHead].data2, CSeqNo::incseq(seqno)) > 0) - m_caSeq[loc].data2 = m_caSeq[m_iHead].data2; - - m_caSeq[m_iHead].data2 = -1; - - m_caSeq[loc].next = m_caSeq[m_iHead].next; - } - - m_caSeq[m_iHead].data1 = -1; - - if (m_iLastInsertPos == m_iHead) - m_iLastInsertPos = -1; - - m_iHead = loc; - - m_iLength --; - } - else if (offset > 0) - { - int h = m_iHead; - - if (seqno == m_caSeq[loc].data1) - { - // target node is not empty, remove part/all of the seqno in the node. - int temp = loc; - loc = (loc + 1) % m_iSize; - - if (-1 == m_caSeq[temp].data2) - m_iHead = m_caSeq[temp].next; - else - { - // remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3) - m_caSeq[loc].data1 = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[temp].data2, m_caSeq[loc].data1) > 0) - m_caSeq[loc].data2 = m_caSeq[temp].data2; - m_iHead = loc; - m_caSeq[loc].next = m_caSeq[temp].next; - m_caSeq[temp].next = loc; - m_caSeq[temp].data2 = -1; - } - } - else - { - // target node is empty, check prior node - int i = m_iHead; - while ((-1 != m_caSeq[i].next) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].next].data1, seqno) < 0)) - i = m_caSeq[i].next; - - loc = (loc + 1) % m_iSize; - - if (-1 == m_caSeq[i].data2) - m_iHead = m_caSeq[i].next; - else if (CSeqNo::seqcmp(m_caSeq[i].data2, seqno) > 0) - { - // remove part/all seqno in the prior node - m_caSeq[loc].data1 = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[i].data2, m_caSeq[loc].data1) > 0) - m_caSeq[loc].data2 = m_caSeq[i].data2; - - m_caSeq[i].data2 = seqno; - - m_caSeq[loc].next = m_caSeq[i].next; - m_caSeq[i].next = loc; - - m_iHead = loc; - } - else - m_iHead = m_caSeq[i].next; - } - - // Remove all nodes prior to the new head - while (h != m_iHead) - { - if (m_caSeq[h].data2 != -1) - { - m_iLength -= CSeqNo::seqlen(m_caSeq[h].data1, m_caSeq[h].data2); - m_caSeq[h].data2 = -1; - } - else - m_iLength --; - - m_caSeq[h].data1 = -1; - - if (m_iLastInsertPos == h) + ScopedLock listguard(m_ListLock); + + if (0 == m_iLength) + return; + + // Remove all from the head pointer to a node with a larger seq. no. or the list is empty + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno); + int loc = (m_iHead + offset + m_iSize) % m_iSize; + + if (0 == offset) + { + // It is the head. Remove the head and point to the next node + loc = (loc + 1) % m_iSize; + + if (SRT_SEQNO_NONE == m_caSeq[m_iHead].seqend) + loc = m_caSeq[m_iHead].inext; + else + { + m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[m_iHead].seqend, CSeqNo::incseq(seqno)) > 0) + m_caSeq[loc].seqend = m_caSeq[m_iHead].seqend; + + m_caSeq[m_iHead].seqend = SRT_SEQNO_NONE; + + m_caSeq[loc].inext = m_caSeq[m_iHead].inext; + } + + m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; + + if (m_iLastInsertPos == m_iHead) m_iLastInsertPos = -1; - h = m_caSeq[h].next; - } - } + m_iHead = loc; + + m_iLength--; + } + else if (offset > 0) + { + int h = m_iHead; + + if (seqno == m_caSeq[loc].seqstart) + { + // target node is not empty, remove part/all of the seqno in the node. + int temp = loc; + loc = (loc + 1) % m_iSize; + + if (SRT_SEQNO_NONE == m_caSeq[temp].seqend) + m_iHead = m_caSeq[temp].inext; + else + { + // remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3) + m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[temp].seqend, m_caSeq[loc].seqstart) > 0) + m_caSeq[loc].seqend = m_caSeq[temp].seqend; + m_iHead = loc; + m_caSeq[loc].inext = m_caSeq[temp].inext; + m_caSeq[temp].inext = loc; + m_caSeq[temp].seqend = SRT_SEQNO_NONE; + } + } + else + { + // target node is empty, check prior node + int i = m_iHead; + while ((-1 != m_caSeq[i].inext) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqno) < 0)) + i = m_caSeq[i].inext; + + loc = (loc + 1) % m_iSize; + + if (SRT_SEQNO_NONE == m_caSeq[i].seqend) + m_iHead = m_caSeq[i].inext; + else if (CSeqNo::seqcmp(m_caSeq[i].seqend, seqno) > 0) + { + // remove part/all seqno in the prior node + m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqstart) > 0) + m_caSeq[loc].seqend = m_caSeq[i].seqend; + + m_caSeq[i].seqend = seqno; + + m_caSeq[loc].inext = m_caSeq[i].inext; + m_caSeq[i].inext = loc; + + m_iHead = loc; + } + else + m_iHead = m_caSeq[i].inext; + } + + // Remove all nodes prior to the new head + while (h != m_iHead) + { + if (m_caSeq[h].seqend != SRT_SEQNO_NONE) + { + m_iLength -= CSeqNo::seqlen(m_caSeq[h].seqstart, m_caSeq[h].seqend); + m_caSeq[h].seqend = SRT_SEQNO_NONE; + } + else + m_iLength--; + + m_caSeq[h].seqstart = SRT_SEQNO_NONE; + + if (m_iLastInsertPos == h) + m_iLastInsertPos = -1; + + h = m_caSeq[h].inext; + } + } +} + +int srt::CSndLossList::getLossLength() const +{ + ScopedLock listguard(m_ListLock); + + return m_iLength; } -int CSndLossList::getLossLength() const +int32_t srt::CSndLossList::popLostSeq() { - CGuard listguard(m_ListLock); + ScopedLock listguard(m_ListLock); + + if (0 == m_iLength) + { + SRT_ASSERT(m_iHead == -1); + return SRT_SEQNO_NONE; + } + + if (m_iLastInsertPos == m_iHead) + m_iLastInsertPos = -1; + + // return the first loss seq. no. + const int32_t seqno = m_caSeq[m_iHead].seqstart; + + // head moves to the next node + if (SRT_SEQNO_NONE == m_caSeq[m_iHead].seqend) + { + //[3, SRT_SEQNO_NONE] becomes [], and head moves to next node in the list + m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; + m_iHead = m_caSeq[m_iHead].inext; + } + else + { + // shift to next node, e.g., [3, 7] becomes [], [4, 7] + int loc = (m_iHead + 1) % m_iSize; + + m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[m_iHead].seqend, m_caSeq[loc].seqstart) > 0) + m_caSeq[loc].seqend = m_caSeq[m_iHead].seqend; + + m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; + m_caSeq[m_iHead].seqend = SRT_SEQNO_NONE; + + m_caSeq[loc].inext = m_caSeq[m_iHead].inext; + m_iHead = loc; + } - return m_iLength; + m_iLength--; + + return seqno; +} + +void srt::CSndLossList::insertHead(int pos, int32_t seqno1, int32_t seqno2) +{ + SRT_ASSERT(pos >= 0); + m_caSeq[pos].seqstart = seqno1; + SRT_ASSERT(m_caSeq[pos].seqend == SRT_SEQNO_NONE); + if (seqno2 != seqno1) + m_caSeq[pos].seqend = seqno2; + + // new node becomes head + m_caSeq[pos].inext = m_iHead; + m_iHead = pos; + m_iLastInsertPos = pos; + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); } -int32_t CSndLossList::popLostSeq() +void srt::CSndLossList::insertAfter(int pos, int pos_after, int32_t seqno1, int32_t seqno2) { - CGuard listguard(m_ListLock); + m_caSeq[pos].seqstart = seqno1; + SRT_ASSERT(m_caSeq[pos].seqend == SRT_SEQNO_NONE); + if (seqno2 != seqno1) + m_caSeq[pos].seqend = seqno2; + + m_caSeq[pos].inext = m_caSeq[pos_after].inext; + m_caSeq[pos_after].inext = pos; + m_iLastInsertPos = pos; + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); +} - if (0 == m_iLength) - return -1; +void srt::CSndLossList::coalesce(int loc) +{ + // coalesce with next node. E.g., [3, 7], ..., [6, 9] becomes [3, 9] + while ((m_caSeq[loc].inext != -1) && (m_caSeq[loc].seqend != SRT_SEQNO_NONE)) + { + const int i = m_caSeq[loc].inext; + if (CSeqNo::seqcmp(m_caSeq[i].seqstart, CSeqNo::incseq(m_caSeq[loc].seqend)) > 0) + break; - if (m_iLastInsertPos == m_iHead) - m_iLastInsertPos = -1; + // coalesce if there is overlap + if (m_caSeq[i].seqend != SRT_SEQNO_NONE) + { + if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqend) > 0) + { + if (CSeqNo::seqcmp(m_caSeq[loc].seqend, m_caSeq[i].seqstart) >= 0) + m_iLength -= CSeqNo::seqlen(m_caSeq[i].seqstart, m_caSeq[loc].seqend); - // return the first loss seq. no. - int32_t seqno = m_caSeq[m_iHead].data1; + m_caSeq[loc].seqend = m_caSeq[i].seqend; + } + else + m_iLength -= CSeqNo::seqlen(m_caSeq[i].seqstart, m_caSeq[i].seqend); + } + else + { + if (m_caSeq[i].seqstart == CSeqNo::incseq(m_caSeq[loc].seqend)) + m_caSeq[loc].seqend = m_caSeq[i].seqstart; + else + m_iLength--; + } - // head moves to the next node - if (-1 == m_caSeq[m_iHead].data2) - { - //[3, -1] becomes [], and head moves to next node in the list - m_caSeq[m_iHead].data1 = -1; - m_iHead = m_caSeq[m_iHead].next; - } - else - { - // shift to next node, e.g., [3, 7] becomes [], [4, 7] - int loc = (m_iHead + 1) % m_iSize; + m_caSeq[i].seqstart = SRT_SEQNO_NONE; + m_caSeq[i].seqend = SRT_SEQNO_NONE; + m_caSeq[loc].inext = m_caSeq[i].inext; + } +} - m_caSeq[loc].data1 = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[m_iHead].data2, m_caSeq[loc].data1) > 0) - m_caSeq[loc].data2 = m_caSeq[m_iHead].data2; +bool srt::CSndLossList::updateElement(int pos, int32_t seqno1, int32_t seqno2) +{ + m_iLastInsertPos = pos; - m_caSeq[m_iHead].data1 = -1; - m_caSeq[m_iHead].data2 = -1; + if (seqno2 == SRT_SEQNO_NONE || seqno2 == seqno1) + return false; - m_caSeq[loc].next = m_caSeq[m_iHead].next; - m_iHead = loc; - } + if (m_caSeq[pos].seqend == SRT_SEQNO_NONE) + { + m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; + m_caSeq[pos].seqend = seqno2; + return true; + } - m_iLength --; + // seqno2 <= m_caSeq[pos].seqend + if (CSeqNo::seqcmp(seqno2, m_caSeq[pos].seqend) <= 0) + return false; - return seqno; + // seqno2 > m_caSeq[pos].seqend + m_iLength += CSeqNo::seqlen(m_caSeq[pos].seqend, seqno2) - 1; + m_caSeq[pos].seqend = seqno2; + return true; } //////////////////////////////////////////////////////////////////////////////// -CRcvLossList::CRcvLossList(int size): -m_caSeq(), -m_iHead(-1), -m_iTail(-1), -m_iLength(0), -m_iSize(size) +srt::CRcvLossList::CRcvLossList(int size) + : m_caSeq() + , m_iHead(-1) + , m_iTail(-1) + , m_iLength(0) + , m_iSize(size) + , m_iLargestSeq(SRT_SEQNO_NONE) { m_caSeq = new Seq[m_iSize]; - // -1 means there is no data in the node - for (int i = 0; i < size; ++ i) - { - m_caSeq[i].data1 = -1; - m_caSeq[i].data2 = -1; - } + // -1 means there is no data in the node + for (int i = 0; i < size; ++i) + { + m_caSeq[i].seqstart = SRT_SEQNO_NONE; + m_caSeq[i].seqend = SRT_SEQNO_NONE; + } } -CRcvLossList::~CRcvLossList() +srt::CRcvLossList::~CRcvLossList() { - delete [] m_caSeq; + delete[] m_caSeq; } -void CRcvLossList::insert(int32_t seqno1, int32_t seqno2) +void srt::CRcvLossList::insert(int32_t seqno1, int32_t seqno2) { - // Data to be inserted must be larger than all those in the list - // guaranteed by the UDT receiver - - if (0 == m_iLength) - { - // insert data into an empty list - m_iHead = 0; - m_iTail = 0; - m_caSeq[m_iHead].data1 = seqno1; - if (seqno2 != seqno1) - m_caSeq[m_iHead].data2 = seqno2; - - m_caSeq[m_iHead].next = -1; - m_caSeq[m_iHead].prior = -1; - m_iLength += CSeqNo::seqlen(seqno1, seqno2); - - return; - } - - // otherwise searching for the position where the node should be - int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno1); - int loc = (m_iHead + offset) % m_iSize; - - if ((-1 != m_caSeq[m_iTail].data2) && (CSeqNo::incseq(m_caSeq[m_iTail].data2) == seqno1)) - { - // coalesce with prior node, e.g., [2, 5], [6, 7] becomes [2, 7] - loc = m_iTail; - m_caSeq[loc].data2 = seqno2; - } - else - { - // create new node - m_caSeq[loc].data1 = seqno1; - - if (seqno2 != seqno1) - m_caSeq[loc].data2 = seqno2; - - m_caSeq[m_iTail].next = loc; - m_caSeq[loc].prior = m_iTail; - m_caSeq[loc].next = -1; - m_iTail = loc; - } - - m_iLength += CSeqNo::seqlen(seqno1, seqno2); + // Data to be inserted must be larger than all those in the list + if (m_iLargestSeq != SRT_SEQNO_NONE && CSeqNo::seqcmp(seqno1, m_iLargestSeq) <= 0) + { + if (CSeqNo::seqcmp(seqno2, m_iLargestSeq) > 0) + { + LOGC(qrlog.Warn, + log << "RCV-LOSS/insert: seqno1=" << seqno1 << " too small, adjust to " + << CSeqNo::incseq(m_iLargestSeq)); + seqno1 = CSeqNo::incseq(m_iLargestSeq); + } + else + { + LOGC(qrlog.Warn, + log << "RCV-LOSS/insert: (" << seqno1 << "," << seqno2 + << ") to be inserted is too small: m_iLargestSeq=" << m_iLargestSeq << ", m_iLength=" << m_iLength + << ", m_iHead=" << m_iHead << ", m_iTail=" << m_iTail << " -- REJECTING"); + return; + } + } + m_iLargestSeq = seqno2; + + if (0 == m_iLength) + { + // insert data into an empty list + m_iHead = 0; + m_iTail = 0; + m_caSeq[m_iHead].seqstart = seqno1; + if (seqno2 != seqno1) + m_caSeq[m_iHead].seqend = seqno2; + + m_caSeq[m_iHead].inext = -1; + m_caSeq[m_iHead].iprior = -1; + m_iLength += CSeqNo::seqlen(seqno1, seqno2); + + return; + } + + // otherwise searching for the position where the node should be + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno1); + if (offset < 0) + { + LOGC(qrlog.Error, + log << "RCV-LOSS/insert: IPE: new LOSS %(" << seqno1 << "-" << seqno2 << ") PREDATES HEAD %" + << m_caSeq[m_iHead].seqstart << " -- REJECTING"); + return; + } + + int loc = (m_iHead + offset) % m_iSize; + + if ((SRT_SEQNO_NONE != m_caSeq[m_iTail].seqend) && (CSeqNo::incseq(m_caSeq[m_iTail].seqend) == seqno1)) + { + // coalesce with prior node, e.g., [2, 5], [6, 7] becomes [2, 7] + loc = m_iTail; + m_caSeq[loc].seqend = seqno2; + } + else + { + // create new node + m_caSeq[loc].seqstart = seqno1; + + if (seqno2 != seqno1) + m_caSeq[loc].seqend = seqno2; + + m_caSeq[m_iTail].inext = loc; + m_caSeq[loc].iprior = m_iTail; + m_caSeq[loc].inext = -1; + m_iTail = loc; + } + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); } -bool CRcvLossList::remove(int32_t seqno) +bool srt::CRcvLossList::remove(int32_t seqno) { - if (0 == m_iLength) - return false; - - // locate the position of "seqno" in the list - int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno); - if (offset < 0) - return false; - - int loc = (m_iHead + offset) % m_iSize; - - if (seqno == m_caSeq[loc].data1) - { - // This is a seq. no. that starts the loss sequence - - if (-1 == m_caSeq[loc].data2) - { - // there is only 1 loss in the sequence, delete it from the node - if (m_iHead == loc) - { - m_iHead = m_caSeq[m_iHead].next; - if (-1 != m_iHead) - m_caSeq[m_iHead].prior = -1; - } - else - { - m_caSeq[m_caSeq[loc].prior].next = m_caSeq[loc].next; - if (-1 != m_caSeq[loc].next) - m_caSeq[m_caSeq[loc].next].prior = m_caSeq[loc].prior; + if (m_iLargestSeq == SRT_SEQNO_NONE || CSeqNo::seqcmp(seqno, m_iLargestSeq) > 0) + m_iLargestSeq = seqno; + + if (0 == m_iLength) + return false; + + // locate the position of "seqno" in the list + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].seqstart, seqno); + if (offset < 0) + return false; + + int loc = (m_iHead + offset) % m_iSize; + + if (seqno == m_caSeq[loc].seqstart) + { + // This is a seq. no. that starts the loss sequence + + if (SRT_SEQNO_NONE == m_caSeq[loc].seqend) + { + // there is only 1 loss in the sequence, delete it from the node + if (m_iHead == loc) + { + m_iHead = m_caSeq[m_iHead].inext; + if (-1 != m_iHead) + m_caSeq[m_iHead].iprior = -1; + else + m_iTail = -1; + } + else + { + m_caSeq[m_caSeq[loc].iprior].inext = m_caSeq[loc].inext; + if (-1 != m_caSeq[loc].inext) + m_caSeq[m_caSeq[loc].inext].iprior = m_caSeq[loc].iprior; + else + m_iTail = m_caSeq[loc].iprior; + } + + m_caSeq[loc].seqstart = SRT_SEQNO_NONE; + } + else + { + // there are more than 1 loss in the sequence + // move the node to the next and update the starter as the next loss inSeqNo(seqno) + + // find next node + int i = (loc + 1) % m_iSize; + + // remove the "seqno" and change the starter as next seq. no. + m_caSeq[i].seqstart = CSeqNo::incseq(m_caSeq[loc].seqstart); + + // process the sequence end + if (CSeqNo::seqcmp(m_caSeq[loc].seqend, CSeqNo::incseq(m_caSeq[loc].seqstart)) > 0) + m_caSeq[i].seqend = m_caSeq[loc].seqend; + + // remove the current node + m_caSeq[loc].seqstart = SRT_SEQNO_NONE; + m_caSeq[loc].seqend = SRT_SEQNO_NONE; + + // update list pointer + m_caSeq[i].inext = m_caSeq[loc].inext; + m_caSeq[i].iprior = m_caSeq[loc].iprior; + + if (m_iHead == loc) + m_iHead = i; + else + m_caSeq[m_caSeq[i].iprior].inext = i; + + if (m_iTail == loc) + m_iTail = i; else - m_iTail = m_caSeq[loc].prior; - } - - m_caSeq[loc].data1 = -1; - } - else - { - // there are more than 1 loss in the sequence - // move the node to the next and update the starter as the next loss inSeqNo(seqno) - - // find next node - int i = (loc + 1) % m_iSize; - - // remove the "seqno" and change the starter as next seq. no. - m_caSeq[i].data1 = CSeqNo::incseq(m_caSeq[loc].data1); - - // process the sequence end - if (CSeqNo::seqcmp(m_caSeq[loc].data2, CSeqNo::incseq(m_caSeq[loc].data1)) > 0) - m_caSeq[i].data2 = m_caSeq[loc].data2; - - // remove the current node - m_caSeq[loc].data1 = -1; - m_caSeq[loc].data2 = -1; - - // update list pointer - m_caSeq[i].next = m_caSeq[loc].next; - m_caSeq[i].prior = m_caSeq[loc].prior; - - if (m_iHead == loc) - m_iHead = i; - else - m_caSeq[m_caSeq[i].prior].next = i; - - if (m_iTail == loc) - m_iTail = i; - else - m_caSeq[m_caSeq[i].next].prior = i; - } - - m_iLength --; - - return true; - } - - // There is no loss sequence in the current position - // the "seqno" may be contained in a previous node - - // searching previous node - int i = (loc - 1 + m_iSize) % m_iSize; - while (-1 == m_caSeq[i].data1) - i = (i - 1 + m_iSize) % m_iSize; - - // not contained in this node, return - if ((-1 == m_caSeq[i].data2) || (CSeqNo::seqcmp(seqno, m_caSeq[i].data2) > 0)) - return false; - - if (seqno == m_caSeq[i].data2) - { - // it is the sequence end - - if (seqno == CSeqNo::incseq(m_caSeq[i].data1)) - m_caSeq[i].data2 = -1; - else - m_caSeq[i].data2 = CSeqNo::decseq(seqno); - } - else - { - // split the sequence - - // construct the second sequence from CSeqNo::incseq(seqno) to the original sequence end - // located at "loc + 1" - loc = (loc + 1) % m_iSize; - - m_caSeq[loc].data1 = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[i].data2, m_caSeq[loc].data1) > 0) - m_caSeq[loc].data2 = m_caSeq[i].data2; - - // the first (original) sequence is between the original sequence start to CSeqNo::decseq(seqno) - if (seqno == CSeqNo::incseq(m_caSeq[i].data1)) - m_caSeq[i].data2 = -1; - else - m_caSeq[i].data2 = CSeqNo::decseq(seqno); - - // update the list pointer - m_caSeq[loc].next = m_caSeq[i].next; - m_caSeq[i].next = loc; - m_caSeq[loc].prior = i; - - if (m_iTail == i) - m_iTail = loc; - else - m_caSeq[m_caSeq[loc].next].prior = loc; - } - - m_iLength --; - - return true; + m_caSeq[m_caSeq[i].inext].iprior = i; + } + + m_iLength--; + + return true; + } + + // There is no loss sequence in the current position + // the "seqno" may be contained in a previous node + + // searching previous node + int i = (loc - 1 + m_iSize) % m_iSize; + while (SRT_SEQNO_NONE == m_caSeq[i].seqstart) + i = (i - 1 + m_iSize) % m_iSize; + + // not contained in this node, return + if ((SRT_SEQNO_NONE == m_caSeq[i].seqend) || (CSeqNo::seqcmp(seqno, m_caSeq[i].seqend) > 0)) + return false; + + if (seqno == m_caSeq[i].seqend) + { + // it is the sequence end + + if (seqno == CSeqNo::incseq(m_caSeq[i].seqstart)) + m_caSeq[i].seqend = SRT_SEQNO_NONE; + else + m_caSeq[i].seqend = CSeqNo::decseq(seqno); + } + else + { + // split the sequence + + // construct the second sequence from CSeqNo::incseq(seqno) to the original sequence end + // located at "loc + 1" + loc = (loc + 1) % m_iSize; + + m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqstart) > 0) + m_caSeq[loc].seqend = m_caSeq[i].seqend; + + // the first (original) sequence is between the original sequence start to CSeqNo::decseq(seqno) + if (seqno == CSeqNo::incseq(m_caSeq[i].seqstart)) + m_caSeq[i].seqend = SRT_SEQNO_NONE; + else + m_caSeq[i].seqend = CSeqNo::decseq(seqno); + + // update the list pointer + m_caSeq[loc].inext = m_caSeq[i].inext; + m_caSeq[i].inext = loc; + m_caSeq[loc].iprior = i; + + if (m_iTail == i) + m_iTail = loc; + else + m_caSeq[m_caSeq[loc].inext].iprior = loc; + } + + m_iLength--; + + return true; } -bool CRcvLossList::remove(int32_t seqno1, int32_t seqno2) +bool srt::CRcvLossList::remove(int32_t seqno1, int32_t seqno2) { - if (seqno1 <= seqno2) - { - for (int32_t i = seqno1; i <= seqno2; ++ i) - remove(i); - } - else - { - for (int32_t j = seqno1; j < CSeqNo::m_iMaxSeqNo; ++ j) - remove(j); - for (int32_t k = 0; k <= seqno2; ++ k) - remove(k); - } - - return true; + if (seqno1 <= seqno2) + { + for (int32_t i = seqno1; i <= seqno2; ++i) + remove(i); + } + else + { + for (int32_t j = seqno1; j < CSeqNo::m_iMaxSeqNo; ++j) + remove(j); + for (int32_t k = 0; k <= seqno2; ++k) + remove(k); + } + + return true; } -bool CRcvLossList::find(int32_t seqno1, int32_t seqno2) const +bool srt::CRcvLossList::find(int32_t seqno1, int32_t seqno2) const { - if (0 == m_iLength) - return false; + if (0 == m_iLength) + return false; - int p = m_iHead; + int p = m_iHead; - while (-1 != p) - { - if ((CSeqNo::seqcmp(m_caSeq[p].data1, seqno1) == 0) || - ((CSeqNo::seqcmp(m_caSeq[p].data1, seqno1) > 0) && (CSeqNo::seqcmp(m_caSeq[p].data1, seqno2) <= 0)) || - ((CSeqNo::seqcmp(m_caSeq[p].data1, seqno1) < 0) && (m_caSeq[p].data2 != -1) && CSeqNo::seqcmp(m_caSeq[p].data2, seqno1) >= 0)) - return true; + while (-1 != p) + { + if ((CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno1) == 0) || + ((CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno1) > 0) && (CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno2) <= 0)) || + ((CSeqNo::seqcmp(m_caSeq[p].seqstart, seqno1) < 0) && (m_caSeq[p].seqend != SRT_SEQNO_NONE) && + CSeqNo::seqcmp(m_caSeq[p].seqend, seqno1) >= 0)) + return true; - p = m_caSeq[p].next; - } + p = m_caSeq[p].inext; + } - return false; + return false; } -int CRcvLossList::getLossLength() const +int srt::CRcvLossList::getLossLength() const { - return m_iLength; + return m_iLength; } -int CRcvLossList::getFirstLostSeq() const +int32_t srt::CRcvLossList::getFirstLostSeq() const { - if (0 == m_iLength) - return -1; + if (0 == m_iLength) + return SRT_SEQNO_NONE; - return m_caSeq[m_iHead].data1; + return m_caSeq[m_iHead].seqstart; } -void CRcvLossList::getLossArray(int32_t* array, int& len, int limit) +void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit) { - len = 0; + len = 0; - int i = m_iHead; + int i = m_iHead; - while ((len < limit - 1) && (-1 != i)) - { - array[len] = m_caSeq[i].data1; - if (-1 != m_caSeq[i].data2) - { - // there are more than 1 loss in the sequence - array[len] |= LOSSDATA_SEQNO_RANGE_FIRST; - ++ len; - array[len] = m_caSeq[i].data2; - } + while ((len < limit - 1) && (-1 != i)) + { + array[len] = m_caSeq[i].seqstart; + if (SRT_SEQNO_NONE != m_caSeq[i].seqend) + { + // there are more than 1 loss in the sequence + array[len] |= LOSSDATA_SEQNO_RANGE_FIRST; + ++len; + array[len] = m_caSeq[i].seqend; + } - ++ len; + ++len; - i = m_caSeq[i].next; - } + i = m_caSeq[i].inext; + } } - -CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age): ttl(initial_age) +srt::CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age) + : ttl(initial_age) + , timestamp(steady_clock::now()) { - CTimer::rdtsc(timestamp); seq[0] = seqlo; seq[1] = seqhi; } - -CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t sequence) +srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t sequence) { int32_t diffbegin = CSeqNo::seqcmp(sequence, seq[0]); - int32_t diffend = CSeqNo::seqcmp(sequence, seq[1]); + int32_t diffend = CSeqNo::seqcmp(sequence, seq[1]); - if ( diffbegin < 0 || diffend > 0 ) + if (diffbegin < 0 || diffend > 0) { return NONE; // not within the range at all. } - if ( diffbegin == 0 ) + if (diffbegin == 0) { - if ( diffend == 0 ) // exactly at begin and end + if (diffend == 0) // exactly at begin and end { return DELETE; } @@ -717,7 +818,7 @@ CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t sequence) return STRIPPED; } - if ( diffend == 0 ) // exactly at end + if (diffend == 0) // exactly at end { seq[1] = CSeqNo::decseq(seq[1]); return STRIPPED; @@ -726,7 +827,7 @@ CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t sequence) return SPLIT; } -CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t lo, int32_t hi) +srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t lo, int32_t hi) { // This should only if the range lo-hi is anyhow covered by seq[0]-seq[1]. @@ -738,13 +839,13 @@ CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t lo, int32_t hi) // ITEM: <--- delete // If the sequence range is older than the range to be revoked, // delete it anyway. - if ( CSeqNo::seqcmp(lo, seq[1]) > 0 ) + if (CSeqNo::seqcmp(lo, seq[1]) > 0) return DELETE; // LOHI: // ITEM: <-- NOTFOUND // This element is newer than the given sequence, so match failed. - if ( CSeqNo::seqcmp(hi, seq[0]) < 0 ) + if (CSeqNo::seqcmp(hi, seq[0]) < 0) return NONE; // LOHI: @@ -752,7 +853,7 @@ CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t lo, int32_t hi) // RESULT: // 2. If the 'hi' is in the middle (less than seq[1]), delete partially. // That is, take care of this range for itself and return STRIPPED. - if ( CSeqNo::seqcmp(hi, seq[1]) < 0 ) + if (CSeqNo::seqcmp(hi, seq[1]) < 0) { seq[0] = CSeqNo::incseq(hi); return STRIPPED; diff --git a/trunk/3rdparty/srt-1-fit/srtcore/list.h b/trunk/3rdparty/srt-1-fit/srtcore/list.h index e25a26a961..03f05e9279 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/list.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/list.h @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,60 +50,77 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_LIST_H__ -#define __UDT_LIST_H__ - +#ifndef INC_SRT_LIST_H +#define INC_SRT_LIST_H #include "udt.h" #include "common.h" +namespace srt { class CSndLossList { public: - CSndLossList(int size = 1024); - ~CSndLossList(); + CSndLossList(int size = 1024); + ~CSndLossList(); - /// Insert a seq. no. into the sender loss list. - /// @param [in] seqno1 sequence number starts. - /// @param [in] seqno2 sequence number ends. - /// @return number of packets that are not in the list previously. + /// Insert a seq. no. into the sender loss list. + /// @param [in] seqno1 sequence number starts. + /// @param [in] seqno2 sequence number ends. + /// @return number of packets that are not in the list previously. + int insert(int32_t seqno1, int32_t seqno2); - int insert(int32_t seqno1, int32_t seqno2); + /// Remove the given sequence number and all numbers that precede it. + /// @param [in] seqno sequence number. + void removeUpTo(int32_t seqno); - /// Remove ALL the seq. no. that are not greater than the parameter. - /// @param [in] seqno sequence number. + /// Read the loss length.∏ + /// @return The length of the list. + int getLossLength() const; - void remove(int32_t seqno); + /// Read the first (smallest) loss seq. no. in the list and remove it. + /// @return The seq. no. or -1 if the list is empty. + int32_t popLostSeq(); - /// Read the loss length. - /// @return The length of the list. + void traceState() const; - int getLossLength() const; +private: + struct Seq + { + int32_t seqstart; // sequence number starts + int32_t seqend; // sequence number ends + int inext; // index of the next node in the list + } * m_caSeq; - /// Read the first (smallest) loss seq. no. in the list and remove it. - /// @return The seq. no. or -1 if the list is empty. + int m_iHead; // first node + int m_iLength; // loss length + const int m_iSize; // size of the static array + int m_iLastInsertPos; // position of last insert node - int32_t popLostSeq(); + mutable srt::sync::Mutex m_ListLock; // used to synchronize list operation private: - struct Seq - { - int32_t data1; // sequence number starts - int32_t data2; // seqnence number ends - int next; // next node in the list - }* m_caSeq; + /// Inserts an element to the beginning and updates head pointer. + /// No lock. + void insertHead(int pos, int32_t seqno1, int32_t seqno2); - int m_iHead; // first node - int m_iLength; // loss length - int m_iSize; // size of the static array - int m_iLastInsertPos; // position of last insert node + /// Inserts an element after previous element. + /// No lock. + void insertAfter(int pos, int pos_after, int32_t seqno1, int32_t seqno2); - mutable pthread_mutex_t m_ListLock; // used to synchronize list operation + /// Check if it is possible to coalesce element at loc with further elements. + /// @param loc - last changed location + void coalesce(int loc); + + /// Update existing element with the new range (increase only) + /// @param pos position of the element being updated + /// @param seqno1 first sequence number in range + /// @param seqno2 last sequence number in range (SRT_SEQNO_NONE if no range) + bool updateElement(int pos, int32_t seqno1, int32_t seqno2); private: - CSndLossList(const CSndLossList&); - CSndLossList& operator=(const CSndLossList&); + CSndLossList(const CSndLossList&); + CSndLossList& operator=(const CSndLossList&); }; //////////////////////////////////////////////////////////////////////////////// @@ -111,123 +128,124 @@ class CSndLossList class CRcvLossList { public: - CRcvLossList(int size = 1024); - ~CRcvLossList(); + CRcvLossList(int size = 1024); + ~CRcvLossList(); - /// Insert a series of loss seq. no. between "seqno1" and "seqno2" into the receiver's loss list. - /// @param [in] seqno1 sequence number starts. - /// @param [in] seqno2 seqeunce number ends. + /// Insert a series of loss seq. no. between "seqno1" and "seqno2" into the receiver's loss list. + /// @param [in] seqno1 sequence number starts. + /// @param [in] seqno2 seqeunce number ends. - void insert(int32_t seqno1, int32_t seqno2); + void insert(int32_t seqno1, int32_t seqno2); - /// Remove a loss seq. no. from the receiver's loss list. - /// @param [in] seqno sequence number. - /// @return if the packet is removed (true) or no such lost packet is found (false). + /// Remove a loss seq. no. from the receiver's loss list. + /// @param [in] seqno sequence number. + /// @return if the packet is removed (true) or no such lost packet is found (false). - bool remove(int32_t seqno); + bool remove(int32_t seqno); - /// Remove all packets between seqno1 and seqno2. - /// @param [in] seqno1 start sequence number. - /// @param [in] seqno2 end sequence number. - /// @return if the packet is removed (true) or no such lost packet is found (false). + /// Remove all packets between seqno1 and seqno2. + /// @param [in] seqno1 start sequence number. + /// @param [in] seqno2 end sequence number. + /// @return if the packet is removed (true) or no such lost packet is found (false). - bool remove(int32_t seqno1, int32_t seqno2); + bool remove(int32_t seqno1, int32_t seqno2); - /// Find if there is any lost packets whose sequence number falling seqno1 and seqno2. - /// @param [in] seqno1 start sequence number. - /// @param [in] seqno2 end sequence number. - /// @return True if found; otherwise false. + /// Find if there is any lost packets whose sequence number falling seqno1 and seqno2. + /// @param [in] seqno1 start sequence number. + /// @param [in] seqno2 end sequence number. + /// @return True if found; otherwise false. - bool find(int32_t seqno1, int32_t seqno2) const; + bool find(int32_t seqno1, int32_t seqno2) const; - /// Read the loss length. - /// @return the length of the list. + /// Read the loss length. + /// @return the length of the list. - int getLossLength() const; + int getLossLength() const; - /// Read the first (smallest) seq. no. in the list. - /// @return the sequence number or -1 if the list is empty. + /// Read the first (smallest) seq. no. in the list. + /// @return the sequence number or -1 if the list is empty. - int getFirstLostSeq() const; + int32_t getFirstLostSeq() const; - /// Get a encoded loss array for NAK report. - /// @param [out] array the result list of seq. no. to be included in NAK. - /// @param [out] len physical length of the result array. - /// @param [in] limit maximum length of the array. + /// Get a encoded loss array for NAK report. + /// @param [out] array the result list of seq. no. to be included in NAK. + /// @param [out] len physical length of the result array. + /// @param [in] limit maximum length of the array. - void getLossArray(int32_t* array, int& len, int limit); + void getLossArray(int32_t* array, int& len, int limit); private: - struct Seq - { - int32_t data1; // sequence number starts - int32_t data2; // sequence number ends - int next; // next node in the list - int prior; // prior node in the list; - }* m_caSeq; - - int m_iHead; // first node in the list - int m_iTail; // last node in the list; - int m_iLength; // loss length - int m_iSize; // size of the static array + struct Seq + { + int32_t seqstart; // sequence number starts + int32_t seqend; // sequence number ends + int inext; // index of the next node in the list + int iprior; // index of the previous node in the list + } * m_caSeq; + + int m_iHead; // first node in the list + int m_iTail; // last node in the list; + int m_iLength; // loss length + int m_iSize; // size of the static array + int m_iLargestSeq; // largest seq ever seen private: - CRcvLossList(const CRcvLossList&); - CRcvLossList& operator=(const CRcvLossList&); -public: - - struct iterator - { - int32_t head; - Seq* seq; - - iterator(Seq* str, int32_t v): head(v), seq(str) {} - - iterator next() const - { - if ( head == -1 ) - return *this; // should report error, but we can only throw exception, so simply ignore it. - - return iterator(seq, seq[head].next); - } + CRcvLossList(const CRcvLossList&); + CRcvLossList& operator=(const CRcvLossList&); - iterator& operator++() - { - *this = next(); - return *this; - } - - iterator operator++(int) - { - iterator old (seq, head); - *this = next(); - return old; - } - - bool operator==(const iterator& second) const - { - // Ignore seq - should be the same and this is only a sanity check. - return head == second.head; - } - - bool operator!=(const iterator& second) const { return !(*this == second); } - - std::pair operator*() - { - return std::make_pair(seq[head].data1, seq[head].data2); - } - }; - - iterator begin() { return iterator(m_caSeq, m_iHead); } - iterator end() { return iterator(m_caSeq, -1); } +public: + struct iterator + { + int32_t head; + Seq* seq; + + iterator(Seq* str, int32_t v) + : head(v) + , seq(str) + { + } + + iterator next() const + { + if (head == -1) + return *this; // should report error, but we can only throw exception, so simply ignore it. + + return iterator(seq, seq[head].inext); + } + + iterator& operator++() + { + *this = next(); + return *this; + } + + iterator operator++(int) + { + iterator old(seq, head); + *this = next(); + return old; + } + + bool operator==(const iterator& second) const + { + // Ignore seq - should be the same and this is only a sanity check. + return head == second.head; + } + + bool operator!=(const iterator& second) const { return !(*this == second); } + + std::pair operator*() { return std::make_pair(seq[head].seqstart, seq[head].seqend); } + }; + iterator begin() { return iterator(m_caSeq, m_iHead); } + iterator end() { return iterator(m_caSeq, -1); } }; struct CRcvFreshLoss { - int32_t seq[2]; - int ttl; - uint64_t timestamp; + int32_t seq[2]; + int ttl; + srt::sync::steady_clock::time_point timestamp; CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl); @@ -236,15 +254,18 @@ struct CRcvFreshLoss #ifdef DELETE #undef DELETE #endif - enum Emod { - NONE, //< the given sequence was not found in this range + enum Emod + { + NONE, //< the given sequence was not found in this range STRIPPED, //< it was equal to first or last, already taken care of - SPLIT, //< found in the middle, you have to split this range into two - DELETE //< This was a range of one element exactly equal to sequence. Simply delete it. + SPLIT, //< found in the middle, you have to split this range into two + DELETE //< This was a range of one element exactly equal to sequence. Simply delete it. }; Emod revoke(int32_t sequence); Emod revoke(int32_t lo, int32_t hi); }; +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/logger_default.cpp b/trunk/3rdparty/srt-1-fit/srtcore/logger_default.cpp new file mode 100644 index 0000000000..fa7e73b317 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/logger_default.cpp @@ -0,0 +1,53 @@ +/* + WARNING: Generated from ../scripts/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + + +#include "srt.h" +#include "logging.h" +#include "logger_defs.h" + +namespace srt_logging +{ + AllFaOn::AllFaOn() + { + allfa.set(SRT_LOGFA_GENERAL, true); + allfa.set(SRT_LOGFA_SOCKMGMT, true); + allfa.set(SRT_LOGFA_CONN, true); + allfa.set(SRT_LOGFA_XTIMER, true); + allfa.set(SRT_LOGFA_TSBPD, true); + allfa.set(SRT_LOGFA_RSRC, true); + + allfa.set(SRT_LOGFA_CONGEST, true); + allfa.set(SRT_LOGFA_PFILTER, true); + + allfa.set(SRT_LOGFA_API_CTRL, true); + + allfa.set(SRT_LOGFA_QUE_CTRL, true); + + allfa.set(SRT_LOGFA_EPOLL_UPD, true); + + allfa.set(SRT_LOGFA_API_RECV, true); + allfa.set(SRT_LOGFA_BUF_RECV, true); + allfa.set(SRT_LOGFA_QUE_RECV, true); + allfa.set(SRT_LOGFA_CHN_RECV, true); + allfa.set(SRT_LOGFA_GRP_RECV, true); + + allfa.set(SRT_LOGFA_API_SEND, true); + allfa.set(SRT_LOGFA_BUF_SEND, true); + allfa.set(SRT_LOGFA_QUE_SEND, true); + allfa.set(SRT_LOGFA_CHN_SEND, true); + allfa.set(SRT_LOGFA_GRP_SEND, true); + + allfa.set(SRT_LOGFA_INTERNAL, true); + + allfa.set(SRT_LOGFA_QUE_MGMT, true); + allfa.set(SRT_LOGFA_CHN_MGMT, true); + allfa.set(SRT_LOGFA_GRP_MGMT, true); + allfa.set(SRT_LOGFA_EPOLL_API, true); + } +} // namespace srt_logging diff --git a/trunk/3rdparty/srt-1-fit/srtcore/logger_defs.cpp b/trunk/3rdparty/srt-1-fit/srtcore/logger_defs.cpp new file mode 100644 index 0000000000..041f6c8a79 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/logger_defs.cpp @@ -0,0 +1,55 @@ +/* + WARNING: Generated from ../scripts/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + + +#include "srt.h" +#include "logging.h" +#include "logger_defs.h" + +namespace srt_logging { AllFaOn logger_fa_all; } +// We need it outside the namespace to preserve the global name. +// It's a part of "hidden API" (used by applications) +SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa); + +namespace srt_logging +{ + Logger gglog(SRT_LOGFA_GENERAL, srt_logger_config, "SRT.gg"); + Logger smlog(SRT_LOGFA_SOCKMGMT, srt_logger_config, "SRT.sm"); + Logger cnlog(SRT_LOGFA_CONN, srt_logger_config, "SRT.cn"); + Logger xtlog(SRT_LOGFA_XTIMER, srt_logger_config, "SRT.xt"); + Logger tslog(SRT_LOGFA_TSBPD, srt_logger_config, "SRT.ts"); + Logger rslog(SRT_LOGFA_RSRC, srt_logger_config, "SRT.rs"); + + Logger cclog(SRT_LOGFA_CONGEST, srt_logger_config, "SRT.cc"); + Logger pflog(SRT_LOGFA_PFILTER, srt_logger_config, "SRT.pf"); + + Logger aclog(SRT_LOGFA_API_CTRL, srt_logger_config, "SRT.ac"); + + Logger qclog(SRT_LOGFA_QUE_CTRL, srt_logger_config, "SRT.qc"); + + Logger eilog(SRT_LOGFA_EPOLL_UPD, srt_logger_config, "SRT.ei"); + + Logger arlog(SRT_LOGFA_API_RECV, srt_logger_config, "SRT.ar"); + Logger brlog(SRT_LOGFA_BUF_RECV, srt_logger_config, "SRT.br"); + Logger qrlog(SRT_LOGFA_QUE_RECV, srt_logger_config, "SRT.qr"); + Logger krlog(SRT_LOGFA_CHN_RECV, srt_logger_config, "SRT.kr"); + Logger grlog(SRT_LOGFA_GRP_RECV, srt_logger_config, "SRT.gr"); + + Logger aslog(SRT_LOGFA_API_SEND, srt_logger_config, "SRT.as"); + Logger bslog(SRT_LOGFA_BUF_SEND, srt_logger_config, "SRT.bs"); + Logger qslog(SRT_LOGFA_QUE_SEND, srt_logger_config, "SRT.qs"); + Logger kslog(SRT_LOGFA_CHN_SEND, srt_logger_config, "SRT.ks"); + Logger gslog(SRT_LOGFA_GRP_SEND, srt_logger_config, "SRT.gs"); + + Logger inlog(SRT_LOGFA_INTERNAL, srt_logger_config, "SRT.in"); + + Logger qmlog(SRT_LOGFA_QUE_MGMT, srt_logger_config, "SRT.qm"); + Logger kmlog(SRT_LOGFA_CHN_MGMT, srt_logger_config, "SRT.km"); + Logger gmlog(SRT_LOGFA_GRP_MGMT, srt_logger_config, "SRT.gm"); + Logger ealog(SRT_LOGFA_EPOLL_API, srt_logger_config, "SRT.ea"); +} // namespace srt_logging diff --git a/trunk/3rdparty/srt-1-fit/srtcore/logger_defs.h b/trunk/3rdparty/srt-1-fit/srtcore/logger_defs.h new file mode 100644 index 0000000000..63a4caf3e2 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/logger_defs.h @@ -0,0 +1,61 @@ +/* + WARNING: Generated from ../scripts/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + + +#ifndef INC_SRT_LOGGER_DEFS_H +#define INC_SRT_LOGGER_DEFS_H + +#include "srt.h" +#include "logging.h" + +namespace srt_logging +{ + struct AllFaOn + { + LogConfig::fa_bitset_t allfa; + AllFaOn(); + }; + + extern Logger gglog; + extern Logger smlog; + extern Logger cnlog; + extern Logger xtlog; + extern Logger tslog; + extern Logger rslog; + + extern Logger cclog; + extern Logger pflog; + + extern Logger aclog; + + extern Logger qclog; + + extern Logger eilog; + + extern Logger arlog; + extern Logger brlog; + extern Logger qrlog; + extern Logger krlog; + extern Logger grlog; + + extern Logger aslog; + extern Logger bslog; + extern Logger qslog; + extern Logger kslog; + extern Logger gslog; + + extern Logger inlog; + + extern Logger qmlog; + extern Logger kmlog; + extern Logger gmlog; + extern Logger ealog; + +} // namespace srt_logging + +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/logging.h b/trunk/3rdparty/srt-1-fit/srtcore/logging.h index 8543472f01..e79785b46a 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/logging.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/logging.h @@ -13,8 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__SRT_LOGGING_H -#define INC__SRT_LOGGING_H +#ifndef INC_SRT_LOGGING_H +#define INC_SRT_LOGGING_H #include @@ -28,16 +28,13 @@ written by #else #include #endif -#include -#if HAVE_CXX11 -#include -#endif #include "srt.h" #include "utilities.h" #include "threadname.h" #include "logging_api.h" #include "srt_compat.h" +#include "sync.h" #ifdef __GNUC__ #define PRINTF_LIKE __attribute__((format(printf,2,3))) @@ -53,17 +50,24 @@ written by // LOGC uses an iostream-like syntax, using the special 'log' symbol. // This symbol isn't visible outside the log macro parameters. -// Usage: LOGC(mglog.Debug, log << param1 << param2 << param3); -#define LOGC(logdes, args) if (logdes.CheckEnabled()) { srt_logging::LogDispatcher::Proxy log(logdes); log.setloc(__FILE__, __LINE__, __FUNCTION__); args; } +// Usage: LOGC(gglog.Debug, log << param1 << param2 << param3); +#define LOGC(logdes, args) if (logdes.CheckEnabled()) \ +{ \ + srt_logging::LogDispatcher::Proxy log(logdes); \ + log.setloc(__FILE__, __LINE__, __FUNCTION__); \ + const srt_logging::LogDispatcher::Proxy& log_prox SRT_ATR_UNUSED = args; \ +} // LOGF uses printf-like style formatting. -// Usage: LOGF(mglog.Debug, "%s: %d", param1.c_str(), int(param2)); +// Usage: LOGF(gglog.Debug, "%s: %d", param1.c_str(), int(param2)); #define LOGF(logdes, ...) if (logdes.CheckEnabled()) logdes().setloc(__FILE__, __LINE__, __FUNCTION__).form(__VA_ARGS__) // LOGP is C++11 only OR with only one string argument. -// Usage: LOGP(mglog.Debug, param1, param2, param3); +// Usage: LOGP(gglog.Debug, param1, param2, param3); #define LOGP(logdes, ...) if (logdes.CheckEnabled()) logdes.printloc(__FILE__, __LINE__, __FUNCTION__,##__VA_ARGS__) +#define IF_LOGGING(instr) instr + #if ENABLE_HEAVY_LOGGING #define HLOGC LOGC @@ -93,6 +97,7 @@ written by #define HLOGP(...) #define IF_HEAVY_LOGGING(instr) (void)0 +#define IF_LOGGING(instr) (void)0 #endif @@ -107,29 +112,30 @@ struct LogConfig std::ostream* log_stream; SRT_LOG_HANDLER_FN* loghandler_fn; void* loghandler_opaque; - pthread_mutex_t mutex; + srt::sync::Mutex mutex; int flags; - LogConfig(const fa_bitset_t& initial_fa): - enabled_fa(initial_fa), - max_level(LogLevel::warning), - log_stream(&std::cerr) - { - pthread_mutex_init(&mutex, 0); - } - LogConfig(const fa_bitset_t& efa, LogLevel::type l, std::ostream* ls): - enabled_fa(efa), max_level(l), log_stream(ls) + LogConfig(const fa_bitset_t& efa, + LogLevel::type l = LogLevel::warning, + std::ostream* ls = &std::cerr) + : enabled_fa(efa) + , max_level(l) + , log_stream(ls) + , loghandler_fn() + , loghandler_opaque() + , flags() { - pthread_mutex_init(&mutex, 0); } ~LogConfig() { - pthread_mutex_destroy(&mutex); } - void lock() { pthread_mutex_lock(&mutex); } - void unlock() { pthread_mutex_unlock(&mutex); } + SRT_ATTR_ACQUIRE(mutex) + void lock() { mutex.lock(); } + + SRT_ATTR_RELEASE(mutex) + void unlock() { mutex.unlock(); } }; // The LogDispatcher class represents the object that is responsible for @@ -142,7 +148,6 @@ struct SRT_API LogDispatcher static const size_t MAX_PREFIX_SIZE = 32; char prefix[MAX_PREFIX_SIZE+1]; LogConfig* src_config; - pthread_mutex_t mutex; bool isset(int flg) { return (src_config->flags & flg) != 0; } @@ -169,12 +174,10 @@ struct SRT_API LogDispatcher strcat(prefix, ":"); strcat(prefix, logger_pfx); } - pthread_mutex_init(&mutex, 0); } ~LogDispatcher() { - pthread_mutex_destroy(&mutex); } bool CheckEnabled(); @@ -244,6 +247,11 @@ struct SRT_API LogDispatcher return *this; } + DummyProxy& vform(const char*, va_list) + { + return *this; + } + DummyProxy& setloc(const char* , int , std::string) { return *this; @@ -292,7 +300,7 @@ struct LogDispatcher::Proxy // or better __func__. std::string ExtractName(std::string pretty_function); - Proxy(LogDispatcher& guy); + Proxy(LogDispatcher& guy); // Copy constructor is needed due to noncopyable ostringstream. // This is used only in creation of the default object, so just @@ -407,7 +415,6 @@ inline bool LogDispatcher::CheckEnabled() return configured_enabled_fa && level <= configured_maxlevel; } -SRT_API std::string FormatTime(uint64_t time); #if HAVE_CXX11 @@ -423,7 +430,7 @@ inline void PrintArgs(std::ostream& serr, Arg1&& arg1, Args&&... args) } template -inline void LogDispatcher::PrintLogLine(const char* file ATR_UNUSED, int line ATR_UNUSED, const std::string& area ATR_UNUSED, Args&&... args ATR_UNUSED) +inline void LogDispatcher::PrintLogLine(const char* file SRT_ATR_UNUSED, int line SRT_ATR_UNUSED, const std::string& area SRT_ATR_UNUSED, Args&&... args SRT_ATR_UNUSED) { #ifdef ENABLE_LOGGING std::ostringstream serr; @@ -441,7 +448,7 @@ inline void LogDispatcher::PrintLogLine(const char* file ATR_UNUSED, int line AT #else template -inline void LogDispatcher::PrintLogLine(const char* file ATR_UNUSED, int line ATR_UNUSED, const std::string& area ATR_UNUSED, const Arg& arg ATR_UNUSED) +inline void LogDispatcher::PrintLogLine(const char* file SRT_ATR_UNUSED, int line SRT_ATR_UNUSED, const std::string& area SRT_ATR_UNUSED, const Arg& arg SRT_ATR_UNUSED) { #ifdef ENABLE_LOGGING std::ostringstream serr; diff --git a/trunk/3rdparty/srt-1-fit/srtcore/logging_api.h b/trunk/3rdparty/srt-1-fit/srtcore/logging_api.h index 71c94b19c8..4fc3b812bb 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/logging_api.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/logging_api.h @@ -13,8 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__SRT_LOGGING_API_H -#define INC__SRT_LOGGING_API_H +#ifndef INC_SRT_LOGGING_API_H +#define INC_SRT_LOGGING_API_H // These are required for access functions: // - adding FA (requires set) @@ -24,7 +24,6 @@ written by #include #endif -#include #ifdef _WIN32 #include "win/syslog_defs.h" #else diff --git a/trunk/3rdparty/srt-1-fit/srtcore/md5.cpp b/trunk/3rdparty/srt-1-fit/srtcore/md5.cpp index d6fd5d3703..72bda857b0 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/md5.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/md5.cpp @@ -54,6 +54,12 @@ #include "md5.h" #include +/* + * All symbols have been put under the srt namespace + * to avoid potential linkage conflicts. + */ +namespace srt { + #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN # define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) @@ -166,7 +172,7 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) X = (const md5_word_t *)data; } else { /* not aligned */ - memcpy(xbuf, data, 64); + memcpy((xbuf), data, 64); X = xbuf; } } @@ -340,7 +346,7 @@ md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); - memcpy(pms->buf + offset, p, copy); + memcpy((pms->buf + offset), p, copy); if (offset + copy < 64) return; p += copy; @@ -354,7 +360,7 @@ md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) /* Process a final partial block. */ if (left) - memcpy(pms->buf, p, left); + memcpy((pms->buf), p, left); } void @@ -379,3 +385,5 @@ md5_finish(md5_state_t *pms, md5_byte_t digest[16]) for (i = 0; i < 16; ++i) digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/md5.h b/trunk/3rdparty/srt-1-fit/srtcore/md5.h index f7402e7e26..98bd076651 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/md5.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/md5.h @@ -50,6 +50,12 @@ #ifndef md5_INCLUDED # define md5_INCLUDED +/* + * All symbols have been put under the srt namespace + * to avoid potential linkage conflicts. + */ +namespace srt { + /* * This package supports both compile-time and run-time determination of CPU * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be @@ -70,11 +76,6 @@ typedef struct md5_state_s { md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; -#ifdef __cplusplus -extern "C" -{ -#endif - /* Initialize the algorithm. */ void md5_init(md5_state_t *pms); @@ -84,8 +85,6 @@ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); -#ifdef __cplusplus -} /* end extern "C" */ -#endif +} // namespace srt #endif /* md5_INCLUDED */ diff --git a/trunk/3rdparty/srt-1-fit/srtcore/netinet_any.h b/trunk/3rdparty/srt-1-fit/srtcore/netinet_any.h index 0418a1a84b..a38765c087 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/netinet_any.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/netinet_any.h @@ -13,10 +13,12 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__NETINET_ANY_H -#define INC__NETINET_ANY_H +#ifndef INC_SRT_NETINET_ANY_H +#define INC_SRT_NETINET_ANY_H -#include +#include // memcmp +#include +#include #include "platform_sys.h" // This structure should replace every use of sockaddr and its currently @@ -25,6 +27,9 @@ written by // You can use the instances of sockaddr_any in every place where sockaddr is // required. +namespace srt +{ + struct sockaddr_any { union @@ -33,27 +38,234 @@ struct sockaddr_any sockaddr_in6 sin6; sockaddr sa; }; - socklen_t len; - sockaddr_any(int domain = AF_INET) + // The type is intended to be the same as the length + // parameter in ::accept, ::bind and ::connect functions. + + // This is the type used by SRT. + typedef int len_t; + + // This is the type used by system functions +#ifdef _WIN32 + typedef int syslen_t; +#else + typedef socklen_t syslen_t; +#endif + + // Note: by having `len_t` type here the usage in + // API functions is here limited to SRT. For system + // functions you can pass the address here as (socklen_t*)&sa.len, + // but just do it on your own risk, as there's no guarantee + // that sizes of `int` and `socklen_t` do not differ. The safest + // way seems to be using an intermediate proxy to be written + // back here from the value of `syslen_t`. + len_t len; + + struct SysLenWrapper { - memset(this, 0, sizeof *this); - sa.sa_family = domain; - len = size(); + syslen_t syslen; + len_t& backwriter; + syslen_t* operator&() { return &syslen; } + + SysLenWrapper(len_t& source): syslen(source), backwriter(source) + { + } + + ~SysLenWrapper() + { + backwriter = syslen; + } + }; + + // Usage: + // ::accept(lsn_sock, sa.get(), &sa.syslen()); + SysLenWrapper syslen() + { + return SysLenWrapper(len); + } + + static size_t storage_size() + { + typedef union + { + sockaddr_in sin; + sockaddr_in6 sin6; + sockaddr sa; + } ucopy; + return sizeof (ucopy); + } + + void reset() + { + // sin6 is the largest field + memset((&sin6), 0, sizeof sin6); + len = 0; + } + + // Default domain is unspecified, and + // in this case the size is 0. + // Note that AF_* (and alias PF_*) types have + // many various values, of which only + // AF_INET and AF_INET6 are handled here. + // Others make the same effect as unspecified. + explicit sockaddr_any(int domain = AF_UNSPEC) + { + // Default domain is "unspecified", 0 + reset(); + + // Overriding family as required in the parameters + // and the size then accordingly. + sa.sa_family = domain == AF_INET || domain == AF_INET6 ? domain : AF_UNSPEC; + switch (domain) + { + case AF_INET: + len = len_t(sizeof (sockaddr_in)); + break; + + // Use size of sin6 as the default size + // len must be properly set so that the + // family-less sockaddr is passed to bind/accept + default: + len = len_t(sizeof (sockaddr_in6)); + break; + } + } + + sockaddr_any(const sockaddr_storage& stor) + { + // Here the length isn't passed, so just rely on family. + set((const sockaddr*)&stor); + } + + sockaddr_any(const sockaddr* source, len_t namelen = 0) + { + if (namelen == 0) + set(source); + else + set(source, namelen); + } + + void set(const sockaddr* source) + { + // Less safe version, simply trust the caller that the + // memory at 'source' is also large enough to contain + // all data required for particular family. + if (source->sa_family == AF_INET) + { + memcpy((&sin), source, sizeof sin); + len = sizeof sin; + } + else if (source->sa_family == AF_INET6) + { + memcpy((&sin6), source, sizeof sin6); + len = sizeof sin6; + } + else + { + // Error fallback: no other families than IP are regarded. + // Note: socket set up this way isn't intended to be used + // for bind/accept. + sa.sa_family = AF_UNSPEC; + len = 0; + } + } + + void set(const sockaddr* source, syslen_t namelen) + { + // It's not safe to copy it directly, so check. + if (source->sa_family == AF_INET && namelen >= syslen_t(sizeof sin)) + { + memcpy((&sin), source, sizeof sin); + len = sizeof sin; + } + else if (source->sa_family == AF_INET6 && namelen >= syslen_t(sizeof sin6)) + { + // Note: this isn't too safe, may crash for stupid values + // of source->sa_family or any other data + // in the source structure, so make sure it's correct first. + memcpy((&sin6), source, sizeof sin6); + len = sizeof sin6; + } + else + { + reset(); + } + } + + void set(const sockaddr_in& in4) + { + memcpy((&sin), &in4, sizeof in4); + len = sizeof in4; + } + + void set(const sockaddr_in6& in6) + { + memcpy((&sin6), &in6, sizeof in6); + len = sizeof in6; + } + + sockaddr_any(const in_addr& i4_adr, uint16_t port) + { + // Some cases require separately IPv4 address passed as in_addr, + // so port is given separately. + sa.sa_family = AF_INET; + sin.sin_addr = i4_adr; + sin.sin_port = htons(port); + len = sizeof sin; + } + + sockaddr_any(const in6_addr& i6_adr, uint16_t port) + { + sa.sa_family = AF_INET6; + sin6.sin6_addr = i6_adr; + sin6.sin6_port = htons(port); + len = sizeof sin6; } - socklen_t size() const + static len_t size(int family) { - switch (sa.sa_family) + switch (family) { - case AF_INET: return socklen_t(sizeof sin); - case AF_INET6: return socklen_t(sizeof sin6); + case AF_INET: + return len_t(sizeof (sockaddr_in)); + + case AF_INET6: + return len_t(sizeof (sockaddr_in6)); + + default: + return 0; // fallback + } + } + + bool empty() const + { + bool isempty = true; // unspec-family address is always empty - default: return 0; // fallback, impossible + if (sa.sa_family == AF_INET) + { + isempty = (sin.sin_port == 0 + && sin.sin_addr.s_addr == 0); } + else if (sa.sa_family == AF_INET6) + { + isempty = (sin6.sin6_port == 0 + && memcmp(&sin6.sin6_addr, &in6addr_any, sizeof in6addr_any) == 0); + } + // otherwise isempty stays with default false + return isempty; + } + + len_t size() const + { + return size(sa.sa_family); } int family() const { return sa.sa_family; } + void family(int val) + { + sa.sa_family = val; + len = size(); + } // port is in exactly the same location in both sin and sin6 // and has the same size. This is actually yet another common @@ -71,10 +283,26 @@ struct sockaddr_any } sockaddr* get() { return &sa; } - sockaddr* operator&() { return &sa; } - const sockaddr* get() const { return &sa; } - const sockaddr* operator&() const { return &sa; } + + // Sometimes you need to get the address + // the way suitable for e.g. inet_ntop. + const void* get_addr() const + { + if (sa.sa_family == AF_INET) + return &sin.sin_addr.s_addr; + + if (sa.sa_family == AF_INET6) + return &sin6.sin6_addr; + + return NULL; + } + + void* get_addr() + { + const sockaddr_any* that = this; + return (void*)that->get_addr(); + } template struct TypeMap; @@ -85,7 +313,26 @@ struct sockaddr_any { bool operator()(const sockaddr_any& c1, const sockaddr_any& c2) { - return memcmp(&c1, &c2, sizeof(c1)) == 0; + if (c1.family() != c2.family()) + return false; + + // Cannot use memcmp due to having in some systems + // another field like sockaddr_in::sin_len. This exists + // in some BSD-derived systems, but is not required by POSIX. + // Therefore sockaddr_any class cannot operate with it, + // as in this situation it would be safest to state that + // particular implementations may have additional fields + // of different purpose beside those required by POSIX. + // + // The only reliable way to compare two underlying sockaddr + // object is then to compare the port value and the address + // value. + // + // Fortunately the port is 16-bit and located at the same + // offset in both sockaddr_in and sockaddr_in6. + + return c1.sin.sin_port == c2.sin.sin_port + && c1.equal_address(c2); } }; @@ -120,6 +367,50 @@ struct sockaddr_any return memcmp(&c1, &c2, sizeof(c1)) < 0; } }; + + // Tests if the current address is the "any" wildcard. + bool isany() const + { + if (sa.sa_family == AF_INET) + return sin.sin_addr.s_addr == INADDR_ANY; + + if (sa.sa_family == AF_INET6) + return memcmp(&sin6.sin6_addr, &in6addr_any, sizeof in6addr_any) == 0; + + return false; + } + + // Debug support + std::string str() const + { + if (family() != AF_INET && family() != AF_INET6) + return "unknown:0"; + + std::ostringstream output; + char hostbuf[1024]; + int flags; + + #if ENABLE_GETNAMEINFO + flags = NI_NAMEREQD; + #else + flags = NI_NUMERICHOST | NI_NUMERICSERV; + #endif + + if (!getnameinfo(get(), size(), hostbuf, 1024, NULL, 0, flags)) + { + output << hostbuf; + } + + output << ":" << hport(); + return output.str(); + } + + bool operator==(const sockaddr_any& other) const + { + return Equal()(*this, other); + } + + bool operator!=(const sockaddr_any& other) const { return !(*this == other); } }; template<> struct sockaddr_any::TypeMap { typedef sockaddr_in type; }; @@ -130,4 +421,6 @@ inline sockaddr_any::TypeMap::type& sockaddr_any::get() { retu template <> inline sockaddr_any::TypeMap::type& sockaddr_any::get() { return sin6; } +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packet.cpp b/trunk/3rdparty/srt-1-fit/srtcore/packet.cpp index 0b91439584..aacc37f2c9 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/packet.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/packet.cpp @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,7 +50,6 @@ modified by Haivision Systems Inc. *****************************************************************************/ - ////////////////////////////////////////////////////////////////////////////// // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -137,9 +136,9 @@ modified by // Add. Info: Error code // Control Info: None // 0x7FFF: Explained by bits 16 - 31 (UMSG_EXT) -// +// // bit 16 - 31: -// This space is used for future expansion or user defined control packets. +// This space is used for future expansion or user defined control packets. // // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -159,26 +158,29 @@ modified by // For any single loss or consectutive loss less than 2 packets, use // the original sequence numbers in the field. +#include "platform_sys.h" #include #include "packet.h" +#include "handshake.h" #include "logging.h" +#include "handshake.h" namespace srt_logging { - extern Logger mglog; +extern Logger inlog; } using namespace srt_logging; // Set up the aliases in the constructure -CPacket::CPacket(): -__pad(), -m_data_owned(false), -m_iSeqNo((int32_t&)(m_nHeader[SRT_PH_SEQNO])), -m_iMsgNo((int32_t&)(m_nHeader[SRT_PH_MSGNO])), -m_iTimeStamp((int32_t&)(m_nHeader[SRT_PH_TIMESTAMP])), -m_iID((int32_t&)(m_nHeader[SRT_PH_ID])), -m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef())) +srt::CPacket::CPacket() + : m_extra_pad() + , m_data_owned(false) + , m_iSeqNo((int32_t&)(m_nHeader[SRT_PH_SEQNO])) + , m_iMsgNo((int32_t&)(m_nHeader[SRT_PH_MSGNO])) + , m_iTimeStamp((int32_t&)(m_nHeader[SRT_PH_TIMESTAMP])) + , m_iID((int32_t&)(m_nHeader[SRT_PH_ID])) + , m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef())) { m_nHeader.clear(); @@ -192,166 +194,236 @@ m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef())) m_PacketVector[PV_DATA].set(NULL, 0); } -void CPacket::allocate(size_t alloc_buffer_size) +char* srt::CPacket::getData() +{ + return (char*)m_PacketVector[PV_DATA].dataRef(); +} + +void srt::CPacket::allocate(size_t alloc_buffer_size) { + if (m_data_owned) + { + if (getLength() == alloc_buffer_size) + return; // already allocated + + // Would be nice to reallocate; for now just allocate again. + delete[] m_pcData; + } m_PacketVector[PV_DATA].set(new char[alloc_buffer_size], alloc_buffer_size); m_data_owned = true; } -void CPacket::deallocate() +void srt::CPacket::deallocate() { if (m_data_owned) - delete [] (char*)m_PacketVector[PV_DATA].data(); + delete[](char*) m_PacketVector[PV_DATA].data(); m_PacketVector[PV_DATA].set(NULL, 0); } -CPacket::~CPacket() +char* srt::CPacket::release() +{ + // When not owned, release returns NULL. + char* buffer = NULL; + if (m_data_owned) + { + buffer = getData(); + m_data_owned = false; + } + + deallocate(); // won't delete because m_data_owned == false + return buffer; +} + +srt::CPacket::~CPacket() { // PV_HEADER is always owned, PV_DATA may use a "borrowed" buffer. // Delete the internal buffer only if it was declared as owned. if (m_data_owned) - delete[](char*)m_PacketVector[PV_DATA].data(); + delete[](char*) m_PacketVector[PV_DATA].data(); } - -size_t CPacket::getLength() const +size_t srt::CPacket::getLength() const { - return m_PacketVector[PV_DATA].size(); + return m_PacketVector[PV_DATA].size(); } -void CPacket::setLength(size_t len) +void srt::CPacket::setLength(size_t len) { - m_PacketVector[PV_DATA].setLength(len); + m_PacketVector[PV_DATA].setLength(len); } -void CPacket::pack(UDTMessageType pkttype, const void* lparam, void* rparam, int size) +void srt::CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, size_t size) { // Set (bit-0 = 1) and (bit-1~15 = type) setControl(pkttype); + HLOGC(inlog.Debug, + log << "pack: type=" << MessageTypeStr(pkttype) << " ARG=" << (lparam ? Sprint(*lparam) : std::string("NULL")) + << " [ " << (rparam ? Sprint(*(int32_t*)rparam) : std::string()) << " ]"); + + // Set additional information and control information field + switch (pkttype) + { + case UMSG_ACK: // 0010 - Acknowledgement (ACK) + // ACK packet seq. no. + if (NULL != lparam) + m_nHeader[SRT_PH_MSGNO] = *lparam; - // Set additional information and control information field - switch (pkttype) - { - case UMSG_ACK: //0010 - Acknowledgement (ACK) - // ACK packet seq. no. - if (NULL != lparam) - m_nHeader[SRT_PH_MSGNO] = *(int32_t *)lparam; + // data ACK seq. no. + // optional: RTT (microsends), RTT variance (microseconds) advertised flow window size (packets), and estimated + // link capacity (packets per second) + m_PacketVector[PV_DATA].set(rparam, size); - // data ACK seq. no. - // optional: RTT (microsends), RTT variance (microseconds) advertised flow window size (packets), and estimated link capacity (packets per second) - m_PacketVector[PV_DATA].set(rparam, size); + break; - break; + case UMSG_ACKACK: // 0110 - Acknowledgement of Acknowledgement (ACK-2) + // ACK packet seq. no. + m_nHeader[SRT_PH_MSGNO] = *lparam; - case UMSG_ACKACK: //0110 - Acknowledgement of Acknowledgement (ACK-2) - // ACK packet seq. no. - m_nHeader[SRT_PH_MSGNO] = *(int32_t *)lparam; + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); - // control info field should be none - // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void *)&__pad, 4); + break; - break; + case UMSG_LOSSREPORT: // 0011 - Loss Report (NAK) + // loss list + m_PacketVector[PV_DATA].set(rparam, size); - case UMSG_LOSSREPORT: //0011 - Loss Report (NAK) - // loss list - m_PacketVector[PV_DATA].set(rparam, size); + break; - break; + case UMSG_CGWARNING: // 0100 - Congestion Warning + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); - case UMSG_CGWARNING: //0100 - Congestion Warning - // control info field should be none - // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void *)&__pad, 4); - - break; + break; - case UMSG_KEEPALIVE: //0001 - Keep-alive - // control info field should be none - // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void *)&__pad, 4); + case UMSG_KEEPALIVE: // 0001 - Keep-alive + if (lparam) + { + // XXX EXPERIMENTAL. Pass the 32-bit integer here. + m_nHeader[SRT_PH_MSGNO] = *lparam; + } + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); - break; + break; - case UMSG_HANDSHAKE: //0000 - Handshake - // control info filed is handshake info - m_PacketVector[PV_DATA].set(rparam, size); + case UMSG_HANDSHAKE: // 0000 - Handshake + // control info filed is handshake info + m_PacketVector[PV_DATA].set(rparam, size); - break; + break; - case UMSG_SHUTDOWN: //0101 - Shutdown - // control info field should be none - // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void *)&__pad, 4); + case UMSG_SHUTDOWN: // 0101 - Shutdown + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); - break; + break; - case UMSG_DROPREQ: //0111 - Message Drop Request - // msg id - m_nHeader[SRT_PH_MSGNO] = *(int32_t *)lparam; + case UMSG_DROPREQ: // 0111 - Message Drop Request + // msg id + m_nHeader[SRT_PH_MSGNO] = *lparam; - //first seq no, last seq no - m_PacketVector[PV_DATA].set(rparam, size); + // first seq no, last seq no + m_PacketVector[PV_DATA].set(rparam, size); - break; + break; - case UMSG_PEERERROR: //1000 - Error Signal from the Peer Side - // Error type - m_nHeader[SRT_PH_MSGNO] = *(int32_t *)lparam; + case UMSG_PEERERROR: // 1000 - Error Signal from the Peer Side + // Error type + m_nHeader[SRT_PH_MSGNO] = *lparam; - // control info field should be none - // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void *)&__pad, 4); + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); - break; + break; - case UMSG_EXT: //0x7FFF - Reserved for user defined control packets - // for extended control packet - // "lparam" contains the extended type information for bit 16 - 31 - // "rparam" is the control information - m_nHeader[SRT_PH_SEQNO] |= *(int32_t *)lparam; + case UMSG_EXT: // 0x7FFF - Reserved for user defined control packets + // for extended control packet + // "lparam" contains the extended type information for bit 16 - 31 + // "rparam" is the control information + m_nHeader[SRT_PH_SEQNO] |= *lparam; - if (NULL != rparam) - { - m_PacketVector[PV_DATA].set(rparam, size); - } - else - { - m_PacketVector[PV_DATA].set((void *)&__pad, 4); - } + if (NULL != rparam) + { + m_PacketVector[PV_DATA].set(rparam, size); + } + else + { + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); + } + + break; + + default: + break; + } +} + +void srt::CPacket::toNL() +{ + // XXX USE HtoNLA! + if (isControl()) + { + for (ptrdiff_t i = 0, n = getLength() / 4; i < n; ++i) + *((uint32_t*)m_pcData + i) = htonl(*((uint32_t*)m_pcData + i)); + } + + // convert packet header into network order + uint32_t* p = m_nHeader; + for (int j = 0; j < 4; ++j) + { + *p = htonl(*p); + ++p; + } +} - break; +void srt::CPacket::toHL() +{ + // convert back into local host order + uint32_t* p = m_nHeader; + for (int k = 0; k < 4; ++k) + { + *p = ntohl(*p); + ++p; + } - default: - break; - } + if (isControl()) + { + for (ptrdiff_t l = 0, n = getLength() / 4; l < n; ++l) + *((uint32_t*)m_pcData + l) = ntohl(*((uint32_t*)m_pcData + l)); + } } -IOVector* CPacket::getPacketVector() +srt::IOVector* srt::CPacket::getPacketVector() { - return m_PacketVector; + return m_PacketVector; } -UDTMessageType CPacket::getType() const +srt::UDTMessageType srt::CPacket::getType() const { return UDTMessageType(SEQNO_MSGTYPE::unwrap(m_nHeader[SRT_PH_SEQNO])); } -int CPacket::getExtendedType() const +int srt::CPacket::getExtendedType() const { return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]); } -int32_t CPacket::getAckSeqNo() const +int32_t srt::CPacket::getAckSeqNo() const { - // read additional information field - // This field is used only in UMSG_ACK and UMSG_ACKACK, - // so 'getAckSeqNo' symbolically defines the only use of it - // in case of CONTROL PACKET. - return m_nHeader[SRT_PH_MSGNO]; + // read additional information field + // This field is used only in UMSG_ACK and UMSG_ACKACK, + // so 'getAckSeqNo' symbolically defines the only use of it + // in case of CONTROL PACKET. + return m_nHeader[SRT_PH_MSGNO]; } -uint16_t CPacket::getControlFlags() const +uint16_t srt::CPacket::getControlFlags() const { // This returns exactly the "extended type" value, // which is not used at all in case when the standard @@ -360,19 +432,19 @@ uint16_t CPacket::getControlFlags() const return SEQNO_EXTTYPE::unwrap(m_nHeader[SRT_PH_SEQNO]); } -PacketBoundary CPacket::getMsgBoundary() const +srt::PacketBoundary srt::CPacket::getMsgBoundary() const { return PacketBoundary(MSGNO_PACKET_BOUNDARY::unwrap(m_nHeader[SRT_PH_MSGNO])); } -bool CPacket::getMsgOrderFlag() const +bool srt::CPacket::getMsgOrderFlag() const { - return 0!= MSGNO_PACKET_INORDER::unwrap(m_nHeader[SRT_PH_MSGNO]); + return 0 != MSGNO_PACKET_INORDER::unwrap(m_nHeader[SRT_PH_MSGNO]); } -int32_t CPacket::getMsgSeq(bool has_rexmit) const +int32_t srt::CPacket::getMsgSeq(bool has_rexmit) const { - if ( has_rexmit ) + if (has_rexmit) { return MSGNO_SEQ::unwrap(m_nHeader[SRT_PH_MSGNO]); } @@ -382,13 +454,13 @@ int32_t CPacket::getMsgSeq(bool has_rexmit) const } } -bool CPacket::getRexmitFlag() const +bool srt::CPacket::getRexmitFlag() const { // return false; // - return 0 != MSGNO_REXMIT::unwrap(m_nHeader[SRT_PH_MSGNO]); + return 0 != MSGNO_REXMIT::unwrap(m_nHeader[SRT_PH_MSGNO]); } -EncryptionKeySpec CPacket::getMsgCryptoFlags() const +srt::EncryptionKeySpec srt::CPacket::getMsgCryptoFlags() const { return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(m_nHeader[SRT_PH_MSGNO])); } @@ -396,82 +468,31 @@ EncryptionKeySpec CPacket::getMsgCryptoFlags() const // This is required as the encryption/decryption happens in place. // This is required to clear off the flags after decryption or set // crypto flags after encrypting a packet. -void CPacket::setMsgCryptoFlags(EncryptionKeySpec spec) +void srt::CPacket::setMsgCryptoFlags(EncryptionKeySpec spec) { - int32_t clr_msgno = m_nHeader[SRT_PH_MSGNO] & ~MSGNO_ENCKEYSPEC::mask; + int32_t clr_msgno = m_nHeader[SRT_PH_MSGNO] & ~MSGNO_ENCKEYSPEC::mask; m_nHeader[SRT_PH_MSGNO] = clr_msgno | EncryptionKeyBits(spec); } -/* - Leaving old code for historical reasons. This is moved to CSRTCC. -EncryptionStatus CPacket::encrypt(HaiCrypt_Handle hcrypto) +uint32_t srt::CPacket::getMsgTimeStamp() const { - if ( !hcrypto ) - { - LOGC(mglog.Error, log << "IPE: NULL crypto passed to CPacket::encrypt!"); - return ENCS_FAILED; - } - - int rc = HaiCrypt_Tx_Data(hcrypto, (uint8_t *)m_nHeader.raw(), (uint8_t *)m_pcData, m_PacketVector[PV_DATA].iov_len); - if ( rc < 0 ) - { - // -1: encryption failure - // 0: key not received yet - return ENCS_FAILED; - } else if (rc > 0) { - m_PacketVector[PV_DATA].iov_len = rc; - } - return ENCS_CLEAR; + // SRT_DEBUG_TSBPD_WRAP may enable smaller timestamp for faster wraparoud handling tests + return (uint32_t)m_nHeader[SRT_PH_TIMESTAMP] & TIMESTAMP_MASK; } -EncryptionStatus CPacket::decrypt(HaiCrypt_Handle hcrypto) +srt::CPacket* srt::CPacket::clone() const { - if (getMsgCryptoFlags() == EK_NOENC) - { - //HLOGC(mglog.Debug, log << "CPacket::decrypt: packet not encrypted"); - return ENCS_CLEAR; // not encrypted, no need do decrypt, no flags to be modified - } - - if (!hcrypto) - { - LOGC(mglog.Error, log << "IPE: NULL crypto passed to CPacket::decrypt!"); - return ENCS_FAILED; // "invalid argument" (leave encryption flags untouched) - } - - int rc = HaiCrypt_Rx_Data(hcrypto, (uint8_t *)m_nHeader.raw(), (uint8_t *)m_pcData, m_PacketVector[PV_DATA].iov_len); - if ( rc <= 0 ) - { - // -1: decryption failure - // 0: key not received yet - return ENCS_FAILED; - } - // Otherwise: rc == decrypted text length. - m_PacketVector[PV_DATA].iov_len = rc; // In case clr txt size is different from cipher txt - - // Decryption succeeded. Update flags. - m_nHeader[SRT_PH_MSGNO] &= ~MSGNO_ENCKEYSPEC::mask; // sets EK_NOENC to ENCKEYSPEC bits. - - return ENCS_CLEAR; -} - -*/ + CPacket* pkt = new CPacket; + memcpy((pkt->m_nHeader), m_nHeader, HDR_SIZE); + pkt->m_pcData = new char[m_PacketVector[PV_DATA].size()]; + memcpy((pkt->m_pcData), m_pcData, m_PacketVector[PV_DATA].size()); + pkt->m_PacketVector[PV_DATA].setLength(m_PacketVector[PV_DATA].size()); -uint32_t CPacket::getMsgTimeStamp() const -{ - // SRT_DEBUG_TSBPD_WRAP may enable smaller timestamp for faster wraparoud handling tests - return (uint32_t)m_nHeader[SRT_PH_TIMESTAMP] & TIMESTAMP_MASK; + return pkt; } -CPacket* CPacket::clone() const +namespace srt { - CPacket* pkt = new CPacket; - memcpy(pkt->m_nHeader, m_nHeader, HDR_SIZE); - pkt->m_pcData = new char[m_PacketVector[PV_DATA].size()]; - memcpy(pkt->m_pcData, m_pcData, m_PacketVector[PV_DATA].size()); - pkt->m_PacketVector[PV_DATA].setLength(m_PacketVector[PV_DATA].size()); - - return pkt; -} // Useful for debugging std::string PacketMessageFlagStr(uint32_t msgno_field) @@ -480,10 +501,10 @@ std::string PacketMessageFlagStr(uint32_t msgno_field) stringstream out; - static const char* const boundary [] = { "PB_SUBSEQUENT", "PB_LAST", "PB_FIRST", "PB_SOLO" }; - static const char* const order [] = { "ORD_RELAXED", "ORD_REQUIRED" }; - static const char* const crypto [] = { "EK_NOENC", "EK_EVEN", "EK_ODD", "EK*ERROR" }; - static const char* const rexmit [] = { "SN_ORIGINAL", "SN_REXMIT" }; + static const char* const boundary[] = {"PB_SUBSEQUENT", "PB_LAST", "PB_FIRST", "PB_SOLO"}; + static const char* const order[] = {"ORD_RELAXED", "ORD_REQUIRED"}; + static const char* const crypto[] = {"EK_NOENC", "EK_EVEN", "EK_ODD", "EK*ERROR"}; + static const char* const rexmit[] = {"SN_ORIGINAL", "SN_REXMIT"}; out << boundary[MSGNO_PACKET_BOUNDARY::unwrap(msgno_field)] << " "; out << order[MSGNO_PACKET_INORDER::unwrap(msgno_field)] << " "; @@ -492,3 +513,70 @@ std::string PacketMessageFlagStr(uint32_t msgno_field) return out.str(); } + +inline void SprintSpecialWord(std::ostream& os, int32_t val) +{ + if (val & LOSSDATA_SEQNO_RANGE_FIRST) + os << "<" << (val & (~LOSSDATA_SEQNO_RANGE_FIRST)) << ">"; + else + os << val; +} + +} // namespace srt + +#if ENABLE_LOGGING +std::string srt::CPacket::Info() +{ + std::ostringstream os; + os << "TARGET=@" << m_iID << " "; + + if (isControl()) + { + os << "CONTROL: size=" << getLength() << " type=" << MessageTypeStr(getType(), getExtendedType()); + + if (getType() == UMSG_HANDSHAKE) + { + os << " HS: "; + // For handshake we already have a parsing method + CHandShake hs; + hs.load_from(m_pcData, getLength()); + os << hs.show(); + } + else + { + // This is a value that some messages use for some purposes. + // The "ack seq no" is one of the purposes, used by UMSG_ACK and UMSG_ACKACK. + // This is simply the SRT_PH_MSGNO field used as a message number in data packets. + os << " ARG: 0x"; + os << std::hex << getAckSeqNo() << " "; + os << std::dec << getAckSeqNo(); + + // It would be nice to see the extended packet data, but this + // requires strictly a message-dependent interpreter. So let's simply + // display all numbers in the array with the following restrictions: + // - all data contained in the buffer are considered 32-bit integer + // - sign flag will be cleared before displaying, with additional mark + size_t wordlen = getLength() / 4; // drop any remainder if present + int32_t* array = (int32_t*)m_pcData; + os << " [ "; + for (size_t i = 0; i < wordlen; ++i) + { + SprintSpecialWord(os, array[i]); + os << " "; + } + os << "]"; + } + } + else + { + // It's hard to extract the information about peer's supported rexmit flag. + // This is only a log, nothing crucial, so we can risk displaying incorrect message number. + // Declaring that the peer supports rexmit flag cuts off the highest bit from + // the displayed number. + os << "DATA: size=" << getLength() << " " << BufferStamp(m_pcData, getLength()) << " #" << getMsgSeq(true) + << " %" << getSeqNo() << " " << MessageFlagStr(); + } + + return os.str(); +} +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packet.h b/trunk/3rdparty/srt-1-fit/srtcore/packet.h index e80e100af1..a288caa525 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/packet.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/packet.h @@ -1,11 +1,11 @@ /* - * SRT - Secure, Reliable, Transport + * SRT - Secure Reliable Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,86 +50,85 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_PACKET_H__ -#define __UDT_PACKET_H__ +#ifndef INC_SRT_PACKET_H +#define INC_SRT_PACKET_H #include "udt.h" #include "common.h" #include "utilities.h" +#include "netinet_any.h" #include "packetfilter_api.h" +namespace srt +{ + ////////////////////////////////////////////////////////////////////////////// // The purpose of the IOVector class is to proide a platform-independet interface // to the WSABUF on Windows and iovec on Linux, that can be easilly converted -// to the native structure for use in WSARecvFrom() and recvmsg(...) functions +// to the native structure for use in WSARecvFrom() and recvmsg(...) functions class IOVector #ifdef _WIN32 - : public WSABUF + : public WSABUF #else - : public iovec + : public iovec #endif { public: - - inline void set(void *buffer, size_t length) - { + inline void set(void* buffer, size_t length) + { #ifdef _WIN32 - len = (ULONG)length; - buf = (CHAR*)buffer; + len = (ULONG)length; + buf = (CHAR*)buffer; #else - iov_base = (void*)buffer; - iov_len = length; + iov_base = (void*)buffer; + iov_len = length; #endif - } + } - inline char*& dataRef() - { + inline char*& dataRef() + { #ifdef _WIN32 - return buf; + return buf; #else - return (char*&) iov_base; + return (char*&)iov_base; #endif - } + } - inline char* data() - { + inline char* data() + { #ifdef _WIN32 - return buf; + return buf; #else - return (char*)iov_base; + return (char*)iov_base; #endif - } + } - inline size_t size() const - { + inline size_t size() const + { #ifdef _WIN32 - return (size_t) len; + return (size_t)len; #else - return iov_len; + return iov_len; #endif - } + } - inline void setLength(size_t length) - { + inline void setLength(size_t length) + { #ifdef _WIN32 - len = length; + len = (ULONG)length; #else - iov_len = length; + iov_len = length; #endif - } + } }; - /// To define packets in order in the buffer. This is public due to being used in buffer. enum PacketBoundary { - PB_SUBSEQUENT = 0, // 00 -/// 01: last packet of a message - PB_LAST = 1, // 01 -/// 10: first packet of a message - PB_FIRST = 2, // 10 -/// 11: solo message packet - PB_SOLO = 3, // 11 + PB_SUBSEQUENT = 0, // 00: a packet in the middle of a message, neither the first, not the last. + PB_LAST = 1, // 01: last packet of a message + PB_FIRST = 2, // 10: first packet of a message + PB_SOLO = 3, // 11: solo message packet }; // Breakdown of the PM_SEQNO field in the header: @@ -137,7 +136,7 @@ enum PacketBoundary typedef Bits<31> SEQNO_CONTROL; // 1|T T T T T T T T T T T T T T T|E E...E typedef Bits<30, 16> SEQNO_MSGTYPE; -typedef Bits<15, 0> SEQNO_EXTTYPE; +typedef Bits<15, 0> SEQNO_EXTTYPE; // 0|S S ... S typedef Bits<30, 0> SEQNO_VALUE; @@ -161,11 +160,11 @@ inline int32_t CreateControlExtSeqNo(int exttype) // MSGNO breakdown: B B|O|K K|R|M M M M M M M M M M...M typedef Bits<31, 30> MSGNO_PACKET_BOUNDARY; -typedef Bits<29> MSGNO_PACKET_INORDER; +typedef Bits<29> MSGNO_PACKET_INORDER; typedef Bits<28, 27> MSGNO_ENCKEYSPEC; #if 1 // can block rexmit flag // New bit breakdown - rexmit flag supported. -typedef Bits<26> MSGNO_REXMIT; +typedef Bits<26> MSGNO_REXMIT; typedef Bits<25, 0> MSGNO_SEQ; // Old bit breakdown - no rexmit flag typedef Bits<26, 0> MSGNO_SEQ_OLD; @@ -173,32 +172,36 @@ typedef Bits<26, 0> MSGNO_SEQ_OLD; // The message should be extracted as PMASK_MSGNO_SEQ, if REXMIT is supported, and PMASK_MSGNO_SEQ_OLD otherwise. const uint32_t PACKET_SND_NORMAL = 0, PACKET_SND_REXMIT = MSGNO_REXMIT::mask; +const int MSGNO_SEQ_MAX = MSGNO_SEQ::mask; #else // Old bit breakdown - no rexmit flag typedef Bits<26, 0> MSGNO_SEQ; #endif +typedef RollNumber MsgNo; // constexpr in C++11 ! -inline int32_t PacketBoundaryBits(PacketBoundary o) { return MSGNO_PACKET_BOUNDARY::wrap(int32_t(o)); } - +inline int32_t PacketBoundaryBits(PacketBoundary o) +{ + return MSGNO_PACKET_BOUNDARY::wrap(int32_t(o)); +} enum EncryptionKeySpec { EK_NOENC = 0, - EK_EVEN = 1, - EK_ODD = 2 + EK_EVEN = 1, + EK_ODD = 2 }; enum EncryptionStatus { - ENCS_CLEAR = 0, + ENCS_CLEAR = 0, ENCS_FAILED = -1, ENCS_NOTSUP = -2 }; -const int32_t PMASK_MSGNO_ENCKEYSPEC = MSGNO_ENCKEYSPEC::mask; +const int32_t PMASK_MSGNO_ENCKEYSPEC = MSGNO_ENCKEYSPEC::mask; inline int32_t EncryptionKeyBits(EncryptionKeySpec f) { return MSGNO_ENCKEYSPEC::wrap(int32_t(f)); @@ -212,210 +215,170 @@ const int32_t PUMASK_SEQNO_PROBE = 0xF; std::string PacketMessageFlagStr(uint32_t msgno_field); -class CChannel; - class CPacket { -friend class CChannel; -friend class CSndQueue; -friend class CRcvQueue; + friend class CChannel; + friend class CSndQueue; + friend class CRcvQueue; public: - CPacket(); - ~CPacket(); - - void allocate(size_t size); - void deallocate(); - - /// Get the payload or the control information field length. - /// @return the payload or the control information field length. - - size_t getLength() const; - - /// Set the payload or the control information field length. - /// @param len [in] the payload or the control information field length. - - void setLength(size_t len); + CPacket(); + ~CPacket(); - /// Pack a Control packet. - /// @param pkttype [in] packet type filed. - /// @param lparam [in] pointer to the first data structure, explained by the packet type. - /// @param rparam [in] pointer to the second data structure, explained by the packet type. - /// @param size [in] size of rparam, in number of bytes; + void allocate(size_t size); + void deallocate(); - void pack(UDTMessageType pkttype, const void* lparam = NULL, void* rparam = NULL, int size = 0); + /// Get the payload or the control information field length. + /// @return the payload or the control information field length. + size_t getLength() const; - /// Read the packet vector. - /// @return Pointer to the packet vector. + /// Set the payload or the control information field length. + /// @param len [in] the payload or the control information field length. + void setLength(size_t len); - IOVector* getPacketVector(); + /// Pack a Control packet. + /// @param pkttype [in] packet type filed. + /// @param lparam [in] pointer to the first data structure, explained by the packet type. + /// @param rparam [in] pointer to the second data structure, explained by the packet type. + /// @param size [in] size of rparam, in number of bytes; + void pack(UDTMessageType pkttype, const int32_t* lparam = NULL, void* rparam = NULL, size_t size = 0); - uint32_t* getHeader() { return m_nHeader; } + /// Read the packet vector. + /// @return Pointer to the packet vector. + IOVector* getPacketVector(); - /// Read the packet flag. - /// @return packet flag (0 or 1). + uint32_t* getHeader() { return m_nHeader; } - // XXX DEPRECATED. Use isControl() instead - ATR_DEPRECATED - int getFlag() const - { - return isControl() ? 1 : 0; - } + /// Read the packet type. + /// @return packet type filed (000 ~ 111). + UDTMessageType getType() const; - /// Read the packet type. - /// @return packet type filed (000 ~ 111). + bool isControl(UDTMessageType type) const { return isControl() && type == getType(); } - UDTMessageType getType() const; + bool isControl() const { return 0 != SEQNO_CONTROL::unwrap(m_nHeader[SRT_PH_SEQNO]); } - bool isControl(UDTMessageType type) const - { - return isControl() && type == getType(); - } + void setControl(UDTMessageType type) { m_nHeader[SRT_PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(type); } - bool isControl() const - { - // read bit 0 - return 0!= SEQNO_CONTROL::unwrap(m_nHeader[SRT_PH_SEQNO]); - } + /// Read the extended packet type. + /// @return extended packet type filed (0x000 ~ 0xFFF). + int getExtendedType() const; - void setControl(UDTMessageType type) - { - m_nHeader[SRT_PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(type); - } + /// Read the ACK-2 seq. no. + /// @return packet header field (bit 16~31). + int32_t getAckSeqNo() const; - /// Read the extended packet type. - /// @return extended packet type filed (0x000 ~ 0xFFF). + uint16_t getControlFlags() const; - int getExtendedType() const; + // Note: this will return a "singular" value, if the packet + // contains the control message + int32_t getSeqNo() const { return m_nHeader[SRT_PH_SEQNO]; } - /// Read the ACK-2 seq. no. - /// @return packet header field (bit 16~31). + /// Read the message boundary flag bit. + /// @return packet header field [1] (bit 0~1). + PacketBoundary getMsgBoundary() const; - int32_t getAckSeqNo() const; - uint16_t getControlFlags() const; + /// Read the message inorder delivery flag bit. + /// @return packet header field [1] (bit 2). + bool getMsgOrderFlag() const; - // Note: this will return a "singular" value, if the packet - // contains the control message - int32_t getSeqNo() const - { - return m_nHeader[SRT_PH_SEQNO]; - } + /// Read the rexmit flag (true if the packet was sent due to retransmission). + /// If the peer does not support retransmission flag, the current agent cannot use it as well + /// (because the peer will understand this bit as a part of MSGNO field). + bool getRexmitFlag() const; - /// Read the message boundary flag bit. - /// @return packet header field [1] (bit 0~1). + /// Read the message sequence number. + /// @return packet header field [1] + int32_t getMsgSeq(bool has_rexmit = true) const; - PacketBoundary getMsgBoundary() const; + /// Read the message crypto key bits. + /// @return packet header field [1] (bit 3~4). + EncryptionKeySpec getMsgCryptoFlags() const; - /// Read the message inorder delivery flag bit. - /// @return packet header field [1] (bit 2). + void setMsgCryptoFlags(EncryptionKeySpec spec); - bool getMsgOrderFlag() const; + /// Read the message time stamp. + /// @return packet header field [2] (bit 0~31, bit 0-26 if SRT_DEBUG_TSBPD_WRAP). + uint32_t getMsgTimeStamp() const; - /// Read the rexmit flag (true if the packet was sent due to retransmission). - /// If the peer does not support retransmission flag, the current agent cannot use it as well - /// (because the peer will understand this bit as a part of MSGNO field). - - bool getRexmitFlag() const; - - /// Read the message sequence number. - /// @return packet header field [1] - - int32_t getMsgSeq(bool has_rexmit = true) const; - - /// Read the message crypto key bits. - /// @return packet header field [1] (bit 3~4). - - EncryptionKeySpec getMsgCryptoFlags() const; - void setMsgCryptoFlags(EncryptionKeySpec spec); - - /// Read the message time stamp. - /// @return packet header field [2] (bit 0~31, bit 0-26 if SRT_DEBUG_TSBPD_WRAP). - - uint32_t getMsgTimeStamp() const; - -#ifdef SRT_DEBUG_TSBPD_WRAP //Receiver - static const uint32_t MAX_TIMESTAMP = 0x07FFFFFF; //27 bit fast wraparound for tests (~2m15s) +#ifdef SRT_DEBUG_TSBPD_WRAP // Receiver + static const uint32_t MAX_TIMESTAMP = 0x07FFFFFF; // 27 bit fast wraparound for tests (~2m15s) #else - static const uint32_t MAX_TIMESTAMP = 0xFFFFFFFF; //Full 32 bit (01h11m35s) + static const uint32_t MAX_TIMESTAMP = 0xFFFFFFFF; // Full 32 bit (01h11m35s) #endif protected: - static const uint32_t TIMESTAMP_MASK = MAX_TIMESTAMP; // this value to be also used as a mask + static const uint32_t TIMESTAMP_MASK = MAX_TIMESTAMP; // this value to be also used as a mask public: + /// Clone this packet. + /// @return Pointer to the new packet. + CPacket* clone() const; - /// Clone this packet. - /// @return Pointer to the new packet. - - CPacket* clone() const; + enum PacketVectorFields + { + PV_HEADER = 0, + PV_DATA = 1, - enum PacketVectorFields - { - PV_HEADER = 0, - PV_DATA = 1, + PV_SIZE = 2 + }; - PV_SIZE = 2 - }; +public: + void toNL(); + void toHL(); protected: - // Length in bytes - - // DynamicStruct is the same as array of given type and size, just it - // enforces that you index it using a symbol from symbolic enum type, not by a bare integer. - - typedef DynamicStruct HEADER_TYPE; - HEADER_TYPE m_nHeader; //< The 128-bit header field + // DynamicStruct is the same as array of given type and size, just it + // enforces that you index it using a symbol from symbolic enum type, not by a bare integer. + typedef DynamicStruct HEADER_TYPE; + HEADER_TYPE m_nHeader; //< The 128-bit header field - // XXX NOTE: iovec here is not portable. On Windows there's a different - // (although similar) structure defined, which means that this way the - // Windows function that is an equivalent of `recvmsg` cannot be used. - // For example, something like that: - // class IoVector: public iovec { public: size_t size() { return iov_len; } char* data() { return iov_base; } }; - // class IoVector: public WSAMSG { public: size_t size() { return len; } char* data() { return buf; } }; - IOVector m_PacketVector[PV_SIZE]; //< The 2-demension vector of UDT packet [header, data] + IOVector m_PacketVector[PV_SIZE]; //< The two-dimensional vector of an SRT packet [header, data] - int32_t __pad; - bool m_data_owned; + int32_t m_extra_pad; + bool m_data_owned; protected: - CPacket& operator=(const CPacket&); - CPacket (const CPacket&); + CPacket& operator=(const CPacket&); + CPacket(const CPacket&); public: + int32_t& m_iSeqNo; // alias: sequence number + int32_t& m_iMsgNo; // alias: message number + int32_t& m_iTimeStamp; // alias: timestamp + int32_t& m_iID; // alias: destination SRT socket ID + char*& m_pcData; // alias: payload (data packet) / control information fields (control packet) - int32_t& m_iSeqNo; // alias: sequence number - int32_t& m_iMsgNo; // alias: message number - int32_t& m_iTimeStamp; // alias: timestamp - int32_t& m_iID; // alias: socket ID - char*& m_pcData; // alias: data/control information + // Experimental: sometimes these references don't work! + char* getData(); + char* release(); - //static const int m_iPktHdrSize; // packet header size - static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size = SRT_PH__SIZE * sizeof(uint32_t) + static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size = SRT_PH_E_SIZE * sizeof(uint32_t) - // Used in many computations - // Actually this can be also calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr). - static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. + // Can also be calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr). + static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. - static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; + static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; - // Some well known data - static const size_t ETH_MAX_MTU_SIZE = 1500; + // Maximum transmission unit size. 1500 in case of Ethernet II (RFC 1191). + static const size_t ETH_MAX_MTU_SIZE = 1500; - // And derived - static const size_t SRT_MAX_PAYLOAD_SIZE = ETH_MAX_MTU_SIZE - SRT_DATA_HDR_SIZE; + // Maximum payload size of an SRT packet. + static const size_t SRT_MAX_PAYLOAD_SIZE = ETH_MAX_MTU_SIZE - SRT_DATA_HDR_SIZE; - // Packet interface - char* data() { return m_pcData; } - const char* data() const { return m_pcData; } - size_t size() const { return getLength(); } - uint32_t header(SrtPktHeaderFields field) const { return m_nHeader[field]; } + // Packet interface + char* data() { return m_pcData; } + const char* data() const { return m_pcData; } + size_t size() const { return getLength(); } + uint32_t header(SrtPktHeaderFields field) const { return m_nHeader[field]; } - std::string MessageFlagStr() #if ENABLE_LOGGING - { return PacketMessageFlagStr(m_nHeader[SRT_PH_MSGNO]); } + std::string MessageFlagStr() { return PacketMessageFlagStr(m_nHeader[SRT_PH_MSGNO]); } + std::string Info(); #else - { return ""; } + std::string MessageFlagStr() { return std::string(); } + std::string Info() { return std::string(); } #endif }; +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.cpp b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.cpp index 99dd4de786..305af35471 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.cpp @@ -8,6 +8,7 @@ * */ +#include "platform_sys.h" #include #include @@ -23,79 +24,138 @@ using namespace std; using namespace srt_logging; +using namespace srt::sync; -bool ParseFilterConfig(std::string s, SrtFilterConfig& out) +bool srt::ParseFilterConfig(string s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf) { - vector parts; - Split(s, ',', back_inserter(parts)); + if (!SrtParseConfig(s, (w_config))) + return false; + + PacketFilter::Factory* fac = PacketFilter::find(w_config.type); + if (!fac) + return false; + + if (ppf) + *ppf = fac; + // Extract characteristic data + w_config.extra_size = fac->ExtraSize(); + + return true; +} - out.type = parts[0]; - PacketFilter::Factory* fac = PacketFilter::find(out.type); +bool srt::ParseFilterConfig(string s, SrtFilterConfig& w_config) +{ + return ParseFilterConfig(s, (w_config), NULL); +} + +// Parameters are passed by value because they need to be potentially modicied inside. +bool srt::CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer) +{ + PacketFilter::Factory* fac = PacketFilter::find(w_agent.type); if (!fac) return false; - for (vector::iterator i = parts.begin()+1; i != parts.end(); ++i) + SrtFilterConfig defaults; + if (!ParseFilterConfig(fac->defaultConfig(), (defaults))) { - vector keyval; - Split(*i, ':', back_inserter(keyval)); - if (keyval.size() != 2) + return false; + } + + set keys; + // Extract all keys to identify also unspecified parameters on both sides + // Note that theoretically for FEC it could simply check for the "cols" parameter + // that is the only mandatory one, but this is a procedure for packet filters in + // general and every filter may define its own set of parameters and mandatory rules. + for (map::iterator x = w_agent.parameters.begin(); x != w_agent.parameters.end(); ++x) + { + keys.insert(x->first); + if (peer.parameters.count(x->first) == 0) + peer.parameters[x->first] = x->second; + } + for (map::iterator x = peer.parameters.begin(); x != peer.parameters.end(); ++x) + { + keys.insert(x->first); + if (w_agent.parameters.count(x->first) == 0) + w_agent.parameters[x->first] = x->second; + } + + HLOGC(cnlog.Debug, log << "CheckFilterCompat: re-filled: AGENT:" << Printable(w_agent.parameters) + << " PEER:" << Printable(peer.parameters)); + + // Complete nonexistent keys with default values + for (map::iterator x = defaults.parameters.begin(); x != defaults.parameters.end(); ++x) + { + if (!w_agent.parameters.count(x->first)) + w_agent.parameters[x->first] = x->second; + if (!peer.parameters.count(x->first)) + peer.parameters[x->first] = x->second; + } + + for (set::iterator x = keys.begin(); x != keys.end(); ++x) + { + // Note: operator[] will insert an element with default value + // if it doesn't exist. This will inject the empty string as value, + // which is acceptable. + if (w_agent.parameters[*x] != peer.parameters[*x]) + { + LOGC(cnlog.Error, log << "Packet Filter (" << defaults.type << "): collision on '" << (*x) + << "' parameter (agent:" << w_agent.parameters[*x] << " peer:" << (peer.parameters[*x]) << ")"); return false; - out.parameters[keyval[0]] = keyval[1]; + } } - // Extract characteristic data - out.extra_size = fac->ExtraSize(); + // Mandatory parameters will be checked when trying to create the filter object. return true; } -struct SortBySequence -{ - bool operator()(const CUnit* u1, const CUnit* u2) +namespace srt { + struct SortBySequence { - int32_t s1 = u1->m_Packet.getSeqNo(); - int32_t s2 = u2->m_Packet.getSeqNo(); + bool operator()(const CUnit* u1, const CUnit* u2) + { + int32_t s1 = u1->m_Packet.getSeqNo(); + int32_t s2 = u2->m_Packet.getSeqNo(); - return CSeqNo::seqcmp(s1, s2) < 0; - } -}; + return CSeqNo::seqcmp(s1, s2) < 0; + } + }; +} // namespace srt -void PacketFilter::receive(CUnit* unit, ref_t< std::vector > r_incoming, ref_t r_loss_seqs) +void srt::PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs) { const CPacket& rpkt = unit->m_Packet; - if (m_filter->receive(rpkt, *r_loss_seqs)) + if (m_filter->receive(rpkt, w_loss_seqs)) { // For the sake of rebuilding MARK THIS UNIT GOOD, otherwise the // unit factory will supply it from getNextAvailUnit() as if it were not in use. unit->m_iFlag = CUnit::GOOD; - HLOGC(mglog.Debug, log << "FILTER: PASSTHRU current packet %" << unit->m_Packet.getSeqNo()); - r_incoming.get().push_back(unit); + HLOGC(pflog.Debug, log << "FILTER: PASSTHRU current packet %" << unit->m_Packet.getSeqNo()); + w_incoming.push_back(unit); } else { // Packet not to be passthru, update stats - CGuard lg(m_parent->m_StatsLock); - ++m_parent->m_stats.rcvFilterExtra; - ++m_parent->m_stats.rcvFilterExtraTotal; + ScopedLock lg(m_parent->m_StatsLock); + m_parent->m_stats.rcvr.recvdFilterExtra.count(1); } - // r_loss_seqs enters empty into this function and can be only filled here. - for (loss_seqs_t::iterator i = r_loss_seqs.get().begin(); - i != r_loss_seqs.get().end(); ++i) + // w_loss_seqs enters empty into this function and can be only filled here. XXX ASSERT? + for (loss_seqs_t::iterator i = w_loss_seqs.begin(); + i != w_loss_seqs.end(); ++i) { // Sequences here are low-high, if there happens any negative distance // here, simply skip and report IPE. int dist = CSeqNo::seqoff(i->first, i->second) + 1; if (dist > 0) { - CGuard lg(m_parent->m_StatsLock); - m_parent->m_stats.rcvFilterLoss += dist; - m_parent->m_stats.rcvFilterLossTotal += dist; + ScopedLock lg(m_parent->m_StatsLock); + m_parent->m_stats.rcvr.lossFilter.count(dist); } else { - LOGC(mglog.Error, log << "FILTER: IPE: loss record: invalid loss: %" + LOGC(pflog.Error, log << "FILTER: IPE: loss record: invalid loss: %" << i->first << " - %" << i->second); } } @@ -103,14 +163,13 @@ void PacketFilter::receive(CUnit* unit, ref_t< std::vector > r_incoming, // Pack first recovered packets, if any. if (!m_provided.empty()) { - HLOGC(mglog.Debug, log << "FILTER: inserting REBUILT packets (" << m_provided.size() << "):"); + HLOGC(pflog.Debug, log << "FILTER: inserting REBUILT packets (" << m_provided.size() << "):"); size_t nsupply = m_provided.size(); - InsertRebuilt(*r_incoming, m_unitq); + InsertRebuilt(w_incoming, m_unitq); - CGuard lg(m_parent->m_StatsLock); - m_parent->m_stats.rcvFilterSupply += nsupply; - m_parent->m_stats.rcvFilterSupplyTotal += nsupply; + ScopedLock lg(m_parent->m_StatsLock); + m_parent->m_stats.rcvr.suppliedByFilter.count(nsupply); } // Now that all units have been filled as they should be, @@ -120,8 +179,7 @@ void PacketFilter::receive(CUnit* unit, ref_t< std::vector > r_incoming, // with FREE and therefore will be returned at the next // call to getNextAvailUnit(). unit->m_iFlag = CUnit::FREE; - vector& inco = *r_incoming; - for (vector::iterator i = inco.begin(); i != inco.end(); ++i) + for (vector::iterator i = w_incoming.begin(); i != w_incoming.end(); ++i) { CUnit* u = *i; u->m_iFlag = CUnit::FREE; @@ -129,7 +187,7 @@ void PacketFilter::receive(CUnit* unit, ref_t< std::vector > r_incoming, // Packets must be sorted by sequence number, ascending, in order // not to challenge the SRT's contiguity checker. - sort(inco.begin(), inco.end(), SortBySequence()); + sort(w_incoming.begin(), w_incoming.end(), SortBySequence()); // For now, report immediately the irrecoverable packets // from the row. @@ -147,7 +205,7 @@ void PacketFilter::receive(CUnit* unit, ref_t< std::vector > r_incoming, } -bool PacketFilter::packControlPacket(ref_t r_packet, int32_t seq, int kflg) +bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) { bool have = m_filter->packControlPacket(m_sndctlpkt, seq); if (!have) @@ -155,12 +213,12 @@ bool PacketFilter::packControlPacket(ref_t r_packet, int32_t seq, int k // Now this should be repacked back to CPacket. // The header must be copied, it's always part of CPacket. - uint32_t* hdr = r_packet.get().getHeader(); - memcpy(hdr, m_sndctlpkt.hdr, SRT_PH__SIZE * sizeof(*hdr)); + uint32_t* hdr = w_packet.getHeader(); + memcpy((hdr), m_sndctlpkt.hdr, SRT_PH_E_SIZE * sizeof(*hdr)); // The buffer can be assigned. - r_packet.get().m_pcData = m_sndctlpkt.buffer; - r_packet.get().setLength(m_sndctlpkt.length); + w_packet.m_pcData = m_sndctlpkt.buffer; + w_packet.setLength(m_sndctlpkt.length); // This sets only the Packet Boundary flags, while all other things: // - Order @@ -168,10 +226,10 @@ bool PacketFilter::packControlPacket(ref_t r_packet, int32_t seq, int k // - Crypto // - Message Number // will be set to 0/false - r_packet.get().m_iMsgNo = MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); + w_packet.m_iMsgNo = SRT_MSGNO_CONTROL | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); // ... and then fix only the Crypto flags - r_packet.get().setMsgCryptoFlags(EncryptionKeySpec(kflg)); + w_packet.setMsgCryptoFlags(EncryptionKeySpec(kflg)); // Don't set the ID, it will be later set for any kind of packet. // Write the timestamp clip into the timestamp field. @@ -179,7 +237,7 @@ bool PacketFilter::packControlPacket(ref_t r_packet, int32_t seq, int k } -void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) +void srt::PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) { if (m_provided.empty()) return; @@ -189,7 +247,7 @@ void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) CUnit* u = uq->getNextAvailUnit(); if (!u) { - LOGC(mglog.Error, log << "FILTER: LOCAL STORAGE DEPLETED. Can't return rebuilt packets."); + LOGC(pflog.Error, log << "FILTER: LOCAL STORAGE DEPLETED. Can't return rebuilt packets."); break; } @@ -202,11 +260,11 @@ void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) CPacket& packet = u->m_Packet; - memcpy(packet.getHeader(), i->hdr, CPacket::HDR_SIZE); - memcpy(packet.m_pcData, i->buffer, i->length); + memcpy((packet.getHeader()), i->hdr, CPacket::HDR_SIZE); + memcpy((packet.m_pcData), i->buffer, i->length); packet.setLength(i->length); - HLOGC(mglog.Debug, log << "FILTER: PROVIDING rebuilt packet %" << packet.getSeqNo()); + HLOGC(pflog.Debug, log << "FILTER: PROVIDING rebuilt packet %" << packet.getSeqNo()); incoming.push_back(u); } @@ -214,19 +272,21 @@ void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) m_provided.clear(); } -bool PacketFilter::IsBuiltin(const string& s) +bool srt::PacketFilter::IsBuiltin(const string& s) { return builtin_filters.count(s); } +namespace srt { std::set PacketFilter::builtin_filters; PacketFilter::filters_map_t PacketFilter::filters; +} -PacketFilter::Factory::~Factory() +srt::PacketFilter::Factory::~Factory() { } -void PacketFilter::globalInit() +void srt::PacketFilter::globalInit() { // Add here builtin packet filters and mark them // as builtin. This will disallow users to register @@ -236,12 +296,12 @@ void PacketFilter::globalInit() builtin_filters.insert("fec"); } -bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr) +bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr) { m_parent = parent; SrtFilterConfig cfg; - if (!ParseFilterConfig(confstr, cfg)) + if (!ParseFilterConfig(confstr, (cfg))) return false; // Extract the "type" key from parameters, or use @@ -255,7 +315,7 @@ bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& co init.snd_isn = parent->sndSeqNo(); init.rcv_isn = parent->rcvSeqNo(); init.payload_size = parent->OPT_PayloadSize(); - + init.rcvbuf_size = parent->m_config.iRcvBufSize; // Found a filter, so call the creation function m_filter = selector->second->Create(init, m_provided, confstr); @@ -270,7 +330,7 @@ bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& co return true; } -bool PacketFilter::correctConfig(const SrtFilterConfig& conf) +bool srt::PacketFilter::correctConfig(const SrtFilterConfig& conf) { const string* pname = map_getp(conf.parameters, "type"); @@ -287,7 +347,7 @@ bool PacketFilter::correctConfig(const SrtFilterConfig& conf) return true; } -PacketFilter::~PacketFilter() +srt::PacketFilter::~PacketFilter() { delete m_filter; } diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.h b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.h index fd5bf67d6d..c465145b3a 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter.h @@ -8,40 +8,50 @@ * */ -#ifndef INC__PACKETFILTER_H -#define INC__PACKETFILTER_H +#ifndef INC_SRT_PACKETFILTER_H +#define INC_SRT_PACKETFILTER_H #include #include #include #include "packet.h" -#include "queue.h" #include "utilities.h" #include "packetfilter_api.h" +namespace srt { + +class CUnitQueue; +struct CUnit; +class CUDT; + class PacketFilter { friend class SrtPacketFilterBase; public: - typedef std::vector< std::pair > loss_seqs_t; typedef SrtPacketFilterBase* filter_create_t(const SrtFilterInitializer& init, std::vector&, const std::string& config); -private: - friend bool ParseFilterConfig(std::string s, SrtFilterConfig& out); class Factory { public: virtual SrtPacketFilterBase* Create(const SrtFilterInitializer& init, std::vector& provided, const std::string& confstr) = 0; // Characteristic data - virtual size_t ExtraSize() = 0; - + virtual size_t ExtraSize() const = 0; + + // Represent default parameters. This is for completing and comparing + // filter configurations from both parties. Possible values to return: + // - an empty string (all parameters are mandatory) + // - a form of: ",:,..." + virtual std::string defaultConfig() const = 0; + virtual bool verifyConfig(const SrtFilterConfig& config, std::string& w_errormsg) const = 0; virtual ~Factory(); }; +private: + friend bool ParseFilterConfig(std::string s, SrtFilterConfig& out, PacketFilter::Factory** ppf); template class Creator: public Factory @@ -52,7 +62,12 @@ class PacketFilter { return new Target(init, provided, confstr); } // Import the extra size data - virtual size_t ExtraSize() ATR_OVERRIDE { return Target::EXTRA_SIZE; } + virtual size_t ExtraSize() const ATR_OVERRIDE { return Target::EXTRA_SIZE; } + virtual std::string defaultConfig() const ATR_OVERRIDE { return Target::defaultConfig; } + virtual bool verifyConfig(const SrtFilterConfig& config, std::string& w_errormsg) const ATR_OVERRIDE + { + return Target::verifyConfig(config, (w_errormsg)); + } public: Creator() {} @@ -157,7 +172,7 @@ class PacketFilter // Things being done: // 1. The filter is individual, so don't copy it. Set NULL. // 2. This will be configued anyway basing on possibly a new rule set. - PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_sndctlpkt(0), m_unitq() {} + PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {} // This function will be called by the parent CUDT // in appropriate time. It should select appropriate @@ -173,12 +188,13 @@ class PacketFilter ~PacketFilter(); // Simple wrappers - void feedSource(ref_t r_packet); + void feedSource(CPacket& w_packet); SRT_ARQLevel arqLevel(); - bool packControlPacket(ref_t r_packet, int32_t seq, int kflg); - void receive(CUnit* unit, ref_t< std::vector > r_incoming, ref_t r_loss_seqs); + bool packControlPacket(int32_t seq, int kflg, CPacket& w_packet); + void receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs); protected: + PacketFilter& operator=(const PacketFilter& p); void InsertRebuilt(std::vector& incoming, CUnitQueue* uq); CUDT* m_parent; @@ -191,8 +207,13 @@ class PacketFilter std::vector m_provided; }; +bool CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer); -inline void PacketFilter::feedSource(ref_t r_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource(*r_packet); } +inline void PacketFilter::feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); } inline SRT_ARQLevel PacketFilter::arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); } +bool ParseFilterConfig(std::string s, SrtFilterConfig& out, PacketFilter::Factory** ppf); + +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_api.h b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_api.h index 787c90aeed..d714b865b9 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_api.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_api.h @@ -8,8 +8,20 @@ * */ -#ifndef INC__PACKETFILTER_API_H -#define INC__PACKETFILTER_API_H +#ifndef INC_SRT_PACKETFILTER_API_H +#define INC_SRT_PACKETFILTER_API_H + +#include "platform_sys.h" + +#include +#include +#include +#include +#include + +namespace srt { + +class CPacket; enum SrtPktHeaderFields { @@ -19,7 +31,7 @@ enum SrtPktHeaderFields SRT_PH_ID = 3, //< socket ID // Must be the last value - this is size of all, not a field id - SRT_PH__SIZE + SRT_PH_E_SIZE }; @@ -30,11 +42,15 @@ enum SRT_ARQLevel SRT_ARQ_ALWAYS, //< always send LOSSREPORT immediately after detecting a loss }; - -struct SrtFilterConfig +struct SrtConfig { std::string type; - std::map parameters; + typedef std::map par_t; + par_t parameters; +}; + +struct SrtFilterConfig: SrtConfig +{ size_t extra_size; // needed for filter option check against payload size }; @@ -44,11 +60,12 @@ struct SrtFilterInitializer int32_t snd_isn; int32_t rcv_isn; size_t payload_size; + size_t rcvbuf_size; }; struct SrtPacket { - uint32_t hdr[SRT_PH__SIZE]; + uint32_t hdr[SRT_PH_E_SIZE]; char buffer[SRT_LIVE_MAX_PLSIZE]; size_t length; @@ -64,7 +81,7 @@ struct SrtPacket }; -bool ParseFilterConfig(std::string s, SrtFilterConfig& out); +bool ParseFilterConfig(std::string s, SrtFilterConfig& w_config); class SrtPacketFilterBase @@ -77,6 +94,7 @@ class SrtPacketFilterBase int32_t sndISN() const { return initParams.snd_isn; } int32_t rcvISN() const { return initParams.rcv_isn; } size_t payloadSize() const { return initParams.payload_size; } + size_t rcvBufferSize() const { return initParams.rcvbuf_size; } friend class PacketFilter; @@ -135,6 +153,6 @@ class SrtPacketFilterBase } }; - +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_builtin.h b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_builtin.h index 91e293a13c..80983250a5 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_builtin.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/packetfilter_builtin.h @@ -9,8 +9,8 @@ */ -#ifndef INC__PACKETFILTER_BUILTIN_H -#define INC__PACKETFILTER_BUILTIN_H +#ifndef INC_SRT_PACKETFILTER_BUILTIN_H +#define INC_SRT_PACKETFILTER_BUILTIN_H // Integration header #include "fec.h" diff --git a/trunk/3rdparty/srt-1-fit/srtcore/platform_sys.h b/trunk/3rdparty/srt-1-fit/srtcore/platform_sys.h index fae95803f0..cb5d0afd91 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/platform_sys.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/platform_sys.h @@ -7,28 +7,117 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. * */ -#ifndef INC__PLATFORM_SYS_H -#define INC__PLATFORM_SYS_H +#ifndef INC_SRT_PLATFORM_SYS_H +#define INC_SRT_PLATFORM_SYS_H + +// INFORMATION +// +// This file collects all required platform-specific declarations +// required to provide everything that the SRT library needs from system. +// +// There's also semi-modular system implemented using SRT_IMPORT_* macros. +// To require a module to be imported, #define SRT_IMPORT_* where * is +// the module name. Currently handled module macros: +// +// SRT_IMPORT_TIME (mach time on Mac, portability gettimeofday on WIN32) +// SRT_IMPORT_EVENT (includes kevent on Mac) + #ifdef _WIN32 + #define _CRT_SECURE_NO_WARNINGS 1 // silences windows complaints for sscanf #include #include #include #include + +#ifndef __MINGW32__ + #include +#endif + + #ifdef SRT_IMPORT_TIME + #include + #endif + #include #include #if defined(_MSC_VER) - #pragma warning(disable:4251) + #pragma warning(disable: 4251 26812) #endif #else + +#if defined(__APPLE__) && __APPLE__ +// Warning: please keep this test as it is, do not make it +// "#if __APPLE__" or "#ifdef __APPLE__". In applications with +// a strict "no warning policy", "#if __APPLE__" triggers an "undef" +// error. With GCC, an old & never fixed bug prevents muting this +// warning (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431). +// Before this fix, the solution was to "#define __APPLE__ 0" before +// including srt.h. So, don't use "#ifdef __APPLE__" either. + +// XXX Check if this condition doesn't require checking of +// also other macros, like TARGET_OS_IOS etc. + +#include "TargetConditionals.h" +#define __APPLE_USE_RFC_3542 /* IPV6_PKTINFO */ + +#ifdef SRT_IMPORT_TIME + #include +#endif + +#ifdef SRT_IMPORT_EVENT + #include + #include + #include + #include +#endif + +#endif + +#ifdef BSD +#ifdef SRT_IMPORT_EVENT + #include + #include + #include + #include +#endif +#endif + +#ifdef LINUX + +#ifdef SRT_IMPORT_EVENT + #include + #include +#endif + +#endif + +#ifdef __ANDROID__ + +#ifdef SRT_IMPORT_EVENT + #include +#endif + +#endif + #include #include +#include #include #include #include #include #include -#include +#include + +#ifdef __cplusplus +// Headers for errno, string and stdlib are +// included indirectly correct C++ way. +#else +#include +#include +#include +#endif + #endif #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/queue.cpp b/trunk/3rdparty/srt-1-fit/srtcore/queue.cpp index 5ca8f03151..054ed6406a 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/queue.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/queue.cpp @@ -50,43 +50,49 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifdef _WIN32 -#include -#include -#endif +#include "platform_sys.h" + #include #include "common.h" -#include "core.h" +#include "api.h" #include "netinet_any.h" #include "threadname.h" #include "logging.h" #include "queue.h" using namespace std; +using namespace srt::sync; using namespace srt_logging; -CUnitQueue::CUnitQueue() - : m_pQEntry(NULL) - , m_pCurrQueue(NULL) - , m_pLastQueue(NULL) - , m_iSize(0) - , m_iCount(0) - , m_iMSS() - , m_iIPversion() +srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss) + : m_iNumTaken(0) + , m_iMSS(mss) + , m_iBlockSize(initNumUnits) { + CQEntry* tempq = allocateEntry(m_iBlockSize, m_iMSS); + + if (tempq == NULL) + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY); + + m_pQEntry = m_pCurrQueue = m_pLastQueue = tempq; + m_pQEntry->m_pNext = m_pQEntry; + + m_pAvailUnit = m_pCurrQueue->m_pUnit; + + m_iSize = m_iBlockSize; } -CUnitQueue::~CUnitQueue() +srt::CUnitQueue::~CUnitQueue() { - CQEntry *p = m_pQEntry; + CQEntry* p = m_pQEntry; while (p != NULL) { delete[] p->m_pUnit; delete[] p->m_pBuffer; - CQEntry *q = p; + CQEntry* q = p; if (p == m_pLastQueue) p = NULL; else @@ -95,17 +101,17 @@ CUnitQueue::~CUnitQueue() } } -int CUnitQueue::init(int size, int mss, int version) +srt::CUnitQueue::CQEntry* srt::CUnitQueue::allocateEntry(const int iNumUnits, const int mss) { - CQEntry *tempq = NULL; - CUnit * tempu = NULL; - char * tempb = NULL; + CQEntry* tempq = NULL; + CUnit* tempu = NULL; + char* tempb = NULL; try { tempq = new CQEntry; - tempu = new CUnit[size]; - tempb = new char[size * mss]; + tempu = new CUnit[iNumUnits]; + tempb = new char[iNumUnits * mss]; } catch (...) { @@ -113,267 +119,203 @@ int CUnitQueue::init(int size, int mss, int version) delete[] tempu; delete[] tempb; - return -1; + LOGC(rslog.Error, log << "CUnitQueue: failed to allocate " << iNumUnits << " units."); + return NULL; } - for (int i = 0; i < size; ++i) + for (int i = 0; i < iNumUnits; ++i) { - tempu[i].m_iFlag = CUnit::FREE; + tempu[i].m_iFlag = CUnit::FREE; tempu[i].m_Packet.m_pcData = tempb + i * mss; } + tempq->m_pUnit = tempu; tempq->m_pBuffer = tempb; - tempq->m_iSize = size; - - m_pQEntry = m_pCurrQueue = m_pLastQueue = tempq; - m_pQEntry->m_pNext = m_pQEntry; - - m_pAvailUnit = m_pCurrQueue->m_pUnit; + tempq->m_iSize = iNumUnits; - m_iSize = size; - m_iMSS = mss; - m_iIPversion = version; - - return 0; + return tempq; } -int CUnitQueue::increase() +int srt::CUnitQueue::increase_() { - // adjust/correct m_iCount - int real_count = 0; - CQEntry *p = m_pQEntry; - while (p != NULL) - { - CUnit *u = p->m_pUnit; - for (CUnit *end = u + p->m_iSize; u != end; ++u) - if (u->m_iFlag != CUnit::FREE) - ++real_count; + const int numUnits = m_iBlockSize; + HLOGC(qrlog.Debug, log << "CUnitQueue::increase: Capacity" << capacity() << " + " << numUnits << " new units, " << m_iNumTaken << " in use."); - if (p == m_pLastQueue) - p = NULL; - else - p = p->m_pNext; - } - m_iCount = real_count; - if (double(m_iCount) / m_iSize < 0.9) + CQEntry* tempq = allocateEntry(numUnits, m_iMSS); + if (tempq == NULL) return -1; - CQEntry *tempq = NULL; - CUnit * tempu = NULL; - char * tempb = NULL; - - // all queues have the same size - int size = m_pQEntry->m_iSize; - - try - { - tempq = new CQEntry; - tempu = new CUnit[size]; - tempb = new char[size * m_iMSS]; - } - catch (...) - { - delete tempq; - delete[] tempu; - delete[] tempb; - - return -1; - } - - for (int i = 0; i < size; ++i) - { - tempu[i].m_iFlag = CUnit::FREE; - tempu[i].m_Packet.m_pcData = tempb + i * m_iMSS; - } - tempq->m_pUnit = tempu; - tempq->m_pBuffer = tempb; - tempq->m_iSize = size; - m_pLastQueue->m_pNext = tempq; m_pLastQueue = tempq; m_pLastQueue->m_pNext = m_pQEntry; - m_iSize += size; + m_iSize += numUnits; return 0; } -int CUnitQueue::shrink() +srt::CUnit* srt::CUnitQueue::getNextAvailUnit() { - // currently queue cannot be shrunk. - return -1; -} - -CUnit *CUnitQueue::getNextAvailUnit() -{ - if (m_iCount * 10 > m_iSize * 9) - increase(); + const int iNumUnitsTotal = capacity(); + if (m_iNumTaken * 10 > iNumUnitsTotal * 9) // 90% or more are in use. + increase_(); - if (m_iCount >= m_iSize) + if (m_iNumTaken >= capacity()) + { + LOGC(qrlog.Error, log << "CUnitQueue: No free units to take. Capacity" << capacity() << "."); return NULL; + } - CQEntry *entrance = m_pCurrQueue; - + int units_checked = 0; do { - for (CUnit *sentinel = m_pCurrQueue->m_pUnit + m_pCurrQueue->m_iSize - 1; m_pAvailUnit != sentinel; - ++m_pAvailUnit) + const CUnit* end = m_pCurrQueue->m_pUnit + m_pCurrQueue->m_iSize; + for (; m_pAvailUnit != end; ++m_pAvailUnit, ++units_checked) + { if (m_pAvailUnit->m_iFlag == CUnit::FREE) + { return m_pAvailUnit; - - if (m_pCurrQueue->m_pUnit->m_iFlag == CUnit::FREE) - { - m_pAvailUnit = m_pCurrQueue->m_pUnit; - return m_pAvailUnit; + } } m_pCurrQueue = m_pCurrQueue->m_pNext; m_pAvailUnit = m_pCurrQueue->m_pUnit; - } while (m_pCurrQueue != entrance); - - increase(); + } while (units_checked < m_iSize); return NULL; } -void CUnitQueue::makeUnitFree(CUnit *unit) +void srt::CUnitQueue::makeUnitFree(CUnit* unit) { SRT_ASSERT(unit != NULL); SRT_ASSERT(unit->m_iFlag != CUnit::FREE); - unit->m_iFlag = CUnit::FREE; - --m_iCount; + unit->m_iFlag.store(CUnit::FREE); + + --m_iNumTaken; } -void CUnitQueue::makeUnitGood(CUnit *unit) +void srt::CUnitQueue::makeUnitGood(CUnit* unit) { + ++m_iNumTaken; + SRT_ASSERT(unit != NULL); SRT_ASSERT(unit->m_iFlag == CUnit::FREE); - unit->m_iFlag = CUnit::GOOD; - ++m_iCount; + unit->m_iFlag.store(CUnit::GOOD); } -CSndUList::CSndUList() +srt::CSndUList::CSndUList(sync::CTimer* pTimer) : m_pHeap(NULL) , m_iArrayLength(512) , m_iLastEntry(-1) , m_ListLock() - , m_pWindowLock(NULL) - , m_pWindowCond(NULL) - , m_pTimer(NULL) + , m_pTimer(pTimer) { - m_pHeap = new CSNode *[m_iArrayLength]; - pthread_mutex_init(&m_ListLock, NULL); + setupCond(m_ListCond, "CSndUListCond"); + m_pHeap = new CSNode*[m_iArrayLength]; } -CSndUList::~CSndUList() +srt::CSndUList::~CSndUList() { + releaseCond(m_ListCond); delete[] m_pHeap; - pthread_mutex_destroy(&m_ListLock); } -void CSndUList::update(const CUDT *u, EReschedule reschedule) +void srt::CSndUList::update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts) { - CGuard listguard(m_ListLock); + ScopedLock listguard(m_ListLock); - CSNode *n = u->m_pSNode; + CSNode* n = u->m_pSNode; if (n->m_iHeapLoc >= 0) { - if (!reschedule) // EReschedule to bool conversion, predicted. + if (reschedule == DONT_RESCHEDULE) + return; + + if (n->m_tsTimeStamp <= ts) return; if (n->m_iHeapLoc == 0) { - n->m_llTimeStamp_tk = 1; + n->m_tsTimeStamp = ts; m_pTimer->interrupt(); return; } remove_(u); - insert_norealloc_(1, u); + insert_norealloc_(ts, u); return; } - insert_(1, u); + insert_(ts, u); } -int CSndUList::pop(sockaddr *&addr, CPacket &pkt) +srt::CUDT* srt::CSndUList::pop() { - CGuard listguard(m_ListLock); + ScopedLock listguard(m_ListLock); if (-1 == m_iLastEntry) - return -1; + return NULL; - // no pop until the next schedulled time - uint64_t ts; - CTimer::rdtsc(ts); - if (ts < m_pHeap[0]->m_llTimeStamp_tk) - return -1; + // no pop until the next scheduled time + if (m_pHeap[0]->m_tsTimeStamp > steady_clock::now()) + return NULL; - CUDT *u = m_pHeap[0]->m_pUDT; + CUDT* u = m_pHeap[0]->m_pUDT; remove_(u); + return u; +} -#define UST(field) ((u->m_b##field) ? "+" : "-") << #field << " " - - HLOGC(mglog.Debug, - log << "SND:pop: requesting packet from @" << u->socketID() << " STATUS: " << UST(Listening) - << UST(Connecting) << UST(Connected) << UST(Closing) << UST(Shutdown) << UST(Broken) << UST(PeerHealth) - << UST(Opened)); -#undef UST - - if (!u->m_bConnected || u->m_bBroken) - return -1; - - // pack a packet from the socket - if (u->packData(pkt, ts) <= 0) - return -1; +void srt::CSndUList::remove(const CUDT* u) +{ + ScopedLock listguard(m_ListLock); + remove_(u); +} - addr = u->m_pPeerAddr; +steady_clock::time_point srt::CSndUList::getNextProcTime() +{ + ScopedLock listguard(m_ListLock); - // insert a new entry, ts is the next processing time - if (ts > 0) - insert_norealloc_(ts, u); + if (-1 == m_iLastEntry) + return steady_clock::time_point(); - return 1; + return m_pHeap[0]->m_tsTimeStamp; } -void CSndUList::remove(const CUDT *u) +void srt::CSndUList::waitNonEmpty() const { - CGuard listguard(m_ListLock); + UniqueLock listguard(m_ListLock); + if (m_iLastEntry >= 0) + return; - remove_(u); + m_ListCond.wait(listguard); } -uint64_t CSndUList::getNextProcTime() +void srt::CSndUList::signalInterrupt() const { - CGuard listguard(m_ListLock); - - if (-1 == m_iLastEntry) - return 0; - - return m_pHeap[0]->m_llTimeStamp_tk; + ScopedLock listguard(m_ListLock); + m_ListCond.notify_one(); } -void CSndUList::realloc_() +void srt::CSndUList::realloc_() { - CSNode **temp = NULL; + CSNode** temp = NULL; try { - temp = new CSNode *[2 * m_iArrayLength]; + temp = new CSNode*[2 * m_iArrayLength]; } catch (...) { throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - memcpy(temp, m_pHeap, sizeof(CSNode *) * m_iArrayLength); + memcpy((temp), m_pHeap, sizeof(CSNode*) * m_iArrayLength); m_iArrayLength *= 2; delete[] m_pHeap; m_pHeap = temp; } -void CSndUList::insert_(int64_t ts, const CUDT *u) +void srt::CSndUList::insert_(const steady_clock::time_point& ts, const CUDT* u) { // increase the heap array size if necessary if (m_iLastEntry == m_iArrayLength - 1) @@ -382,9 +324,9 @@ void CSndUList::insert_(int64_t ts, const CUDT *u) insert_norealloc_(ts, u); } -void CSndUList::insert_norealloc_(int64_t ts, const CUDT *u) +void srt::CSndUList::insert_norealloc_(const steady_clock::time_point& ts, const CUDT* u) { - CSNode *n = u->m_pSNode; + CSNode* n = u->m_pSNode; // do not insert repeated node if (n->m_iHeapLoc >= 0) @@ -394,14 +336,14 @@ void CSndUList::insert_norealloc_(int64_t ts, const CUDT *u) m_iLastEntry++; m_pHeap[m_iLastEntry] = n; - n->m_llTimeStamp_tk = ts; + n->m_tsTimeStamp = ts; int q = m_iLastEntry; int p = q; while (p != 0) { p = (q - 1) >> 1; - if (m_pHeap[p]->m_llTimeStamp_tk <= m_pHeap[q]->m_llTimeStamp_tk) + if (m_pHeap[p]->m_tsTimeStamp <= m_pHeap[q]->m_tsTimeStamp) break; swap(m_pHeap[p], m_pHeap[q]); @@ -418,31 +360,30 @@ void CSndUList::insert_norealloc_(int64_t ts, const CUDT *u) // first entry, activate the sending queue if (0 == m_iLastEntry) { - pthread_mutex_lock(m_pWindowLock); - pthread_cond_signal(m_pWindowCond); - pthread_mutex_unlock(m_pWindowLock); + // m_ListLock is assumed to be locked. + m_ListCond.notify_one(); } } -void CSndUList::remove_(const CUDT *u) +void srt::CSndUList::remove_(const CUDT* u) { - CSNode *n = u->m_pSNode; + CSNode* n = u->m_pSNode; if (n->m_iHeapLoc >= 0) { // remove the node from heap m_pHeap[n->m_iHeapLoc] = m_pHeap[m_iLastEntry]; m_iLastEntry--; - m_pHeap[n->m_iHeapLoc]->m_iHeapLoc = n->m_iHeapLoc; + m_pHeap[n->m_iHeapLoc]->m_iHeapLoc = n->m_iHeapLoc.load(); int q = n->m_iHeapLoc; int p = q * 2 + 1; while (p <= m_iLastEntry) { - if ((p + 1 <= m_iLastEntry) && (m_pHeap[p]->m_llTimeStamp_tk > m_pHeap[p + 1]->m_llTimeStamp_tk)) + if ((p + 1 <= m_iLastEntry) && (m_pHeap[p]->m_tsTimeStamp > m_pHeap[p + 1]->m_tsTimeStamp)) p++; - if (m_pHeap[q]->m_llTimeStamp_tk > m_pHeap[p]->m_llTimeStamp_tk) + if (m_pHeap[q]->m_tsTimeStamp > m_pHeap[p]->m_tsTimeStamp) { swap(m_pHeap[p], m_pHeap[q]); m_pHeap[p]->m_iHeapLoc = p; @@ -464,20 +405,15 @@ void CSndUList::remove_(const CUDT *u) } // -CSndQueue::CSndQueue() - : m_WorkerThread() - , m_pSndUList(NULL) +srt::CSndQueue::CSndQueue() + : m_pSndUList(NULL) , m_pChannel(NULL) , m_pTimer(NULL) - , m_WindowLock() - , m_WindowCond() , m_bClosing(false) { - pthread_cond_init(&m_WindowCond, NULL); - pthread_mutex_init(&m_WindowLock, NULL); } -CSndQueue::~CSndQueue() +srt::CSndQueue::~CSndQueue() { m_bClosing = true; @@ -486,45 +422,74 @@ CSndQueue::~CSndQueue() m_pTimer->interrupt(); } - pthread_mutex_lock(&m_WindowLock); - pthread_cond_signal(&m_WindowCond); - pthread_mutex_unlock(&m_WindowLock); - if (!pthread_equal(m_WorkerThread, pthread_t())) - pthread_join(m_WorkerThread, NULL); - pthread_cond_destroy(&m_WindowCond); - pthread_mutex_destroy(&m_WindowLock); + // Unblock CSndQueue worker thread if it is waiting. + m_pSndUList->signalInterrupt(); + + if (m_WorkerThread.joinable()) + { + HLOGC(rslog.Debug, log << "SndQueue: EXIT"); + m_WorkerThread.join(); + } delete m_pSndUList; } -void CSndQueue::init(CChannel *c, CTimer *t) +int srt::CSndQueue::ioctlQuery(int type) const { - m_pChannel = c; - m_pTimer = t; - m_pSndUList = new CSndUList; - m_pSndUList->m_pWindowLock = &m_WindowLock; - m_pSndUList->m_pWindowCond = &m_WindowCond; - m_pSndUList->m_pTimer = m_pTimer; + return m_pChannel->ioctlQuery(type); +} +int srt::CSndQueue::sockoptQuery(int level, int type) const +{ + return m_pChannel->sockoptQuery(level, type); +} - ThreadName tn("SRT:SndQ:worker"); - if (0 != pthread_create(&m_WorkerThread, NULL, CSndQueue::worker, this)) - { - m_WorkerThread = pthread_t(); +#if ENABLE_LOGGING +int srt::CSndQueue::m_counter = 0; +#endif + +void srt::CSndQueue::init(CChannel* c, CTimer* t) +{ + m_pChannel = c; + m_pTimer = t; + m_pSndUList = new CSndUList(t); + +#if ENABLE_LOGGING + ++m_counter; + const std::string thrname = "SRT:SndQ:w" + Sprint(m_counter); + const char* thname = thrname.c_str(); +#else + const char* thname = "SRT:SndQ"; +#endif + if (!StartThread(m_WorkerThread, CSndQueue::worker, this, thname)) throw CUDTException(MJ_SYSTEMRES, MN_THREAD); - } } -#ifdef SRT_ENABLE_IPOPTS -int CSndQueue::getIpTTL() const { return m_pChannel ? m_pChannel->getIpTTL() : -1; } +int srt::CSndQueue::getIpTTL() const +{ + return m_pChannel ? m_pChannel->getIpTTL() : -1; +} + +int srt::CSndQueue::getIpToS() const +{ + return m_pChannel ? m_pChannel->getIpToS() : -1; +} -int CSndQueue::getIpToS() const { return m_pChannel ? m_pChannel->getIpToS() : -1; } +#ifdef SRT_ENABLE_BINDTODEVICE +bool srt::CSndQueue::getBind(char* dst, size_t len) const +{ + return m_pChannel ? m_pChannel->getBind(dst, len) : false; +} #endif -void *CSndQueue::worker(void *param) +void* srt::CSndQueue::worker(void* param) { - CSndQueue *self = (CSndQueue *)param; + CSndQueue* self = (CSndQueue*)param; +#if ENABLE_LOGGING + THREAD_STATE_INIT(("SRT:SndQ:w" + Sprint(m_counter)).c_str()); +#else THREAD_STATE_INIT("SRT:SndQ:worker"); +#endif #if defined(SRT_DEBUG_SNDQ_HIGHRATE) CTimer::rdtsc(self->m_ullDbgTime); @@ -534,13 +499,13 @@ void *CSndQueue::worker(void *param) while (!self->m_bClosing) { - uint64_t next_time = self->m_pSndUList->getNextProcTime(); + const steady_clock::time_point next_time = self->m_pSndUList->getNextProcTime(); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lIteration++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ - if (next_time <= 0) + if (is_zero(next_time)) { #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lNotReadyTs++; @@ -548,24 +513,21 @@ void *CSndQueue::worker(void *param) // wait here if there is no sockets with data to be sent THREAD_PAUSED(); - pthread_mutex_lock(&self->m_WindowLock); - if (!self->m_bClosing && (self->m_pSndUList->m_iLastEntry < 0)) + if (!self->m_bClosing) { - pthread_cond_wait(&self->m_WindowCond, &self->m_WindowLock); + self->m_pSndUList->waitNonEmpty(); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lCondWait++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ } THREAD_RESUMED(); - pthread_mutex_unlock(&self->m_WindowLock); continue; } // wait until next processing time of the first socket on the list - uint64_t currtime; - CTimer::rdtsc(currtime); + const steady_clock::time_point currtime = steady_clock::now(); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) if (self->m_ullDbgTime <= currtime) @@ -586,7 +548,7 @@ void *CSndQueue::worker(void *param) THREAD_PAUSED(); if (currtime < next_time) { - self->m_pTimer->sleepto(next_time); + self->m_pTimer->sleep_until(next_time); #if defined(HAI_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lSleepTo++; @@ -594,27 +556,50 @@ void *CSndQueue::worker(void *param) } THREAD_RESUMED(); - // it is time to send the next pkt - sockaddr *addr; - CPacket pkt; - if (self->m_pSndUList->pop(addr, pkt) < 0) + // Get a socket with a send request if any. + CUDT* u = self->m_pSndUList->pop(); + if (u == NULL) { - continue; - #if defined(SRT_DEBUG_SNDQ_HIGHRATE) self->m_WorkerStats.lNotReadyPop++; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + continue; } - if (pkt.isControl()) + +#define UST(field) ((u->m_b##field) ? "+" : "-") << #field << " " + HLOGC(qslog.Debug, + log << "CSndQueue: requesting packet from @" << u->socketID() << " STATUS: " << UST(Listening) + << UST(Connecting) << UST(Connected) << UST(Closing) << UST(Shutdown) << UST(Broken) << UST(PeerHealth) + << UST(Opened)); +#undef UST + + if (!u->m_bConnected || u->m_bBroken) { - HLOGC(mglog.Debug, - log << self->CONID() << "chn:SENDING: " << MessageTypeStr(pkt.getType(), pkt.getExtendedType())); +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lNotReadyPop++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + continue; } - else + + // pack a packet from the socket + CPacket pkt; + const std::pair res_time = u->packData((pkt)); + + // Check if payload size is invalid. + if (res_time.first == false) { - HLOGC(dlog.Debug, - log << self->CONID() << "chn:SENDING SIZE " << pkt.getLength() << " SEQ: " << pkt.getSeqNo()); +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lNotReadyPop++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + continue; } + + const sockaddr_any addr = u->m_PeerAddr; + const steady_clock::time_point next_send_time = res_time.second; + if (!is_zero(next_send_time)) + self->m_pSndUList->update(u, CSndUList::DO_RESCHEDULE, next_send_time); + + HLOGC(qslog.Debug, log << self->CONID() << "chn:SENDING: " << pkt.Info()); self->m_pChannel->sendto(addr, pkt); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) @@ -626,26 +611,26 @@ void *CSndQueue::worker(void *param) return NULL; } -int CSndQueue::sendto(const sockaddr *addr, CPacket &packet) +int srt::CSndQueue::sendto(const sockaddr_any& w_addr, CPacket& w_packet) { // send out the packet immediately (high priority), this is a control packet - m_pChannel->sendto(addr, packet); - return (int)packet.getLength(); + m_pChannel->sendto(w_addr, w_packet); + return (int)w_packet.getLength(); } // -CRcvUList::CRcvUList() +srt::CRcvUList::CRcvUList() : m_pUList(NULL) , m_pLast(NULL) { } -CRcvUList::~CRcvUList() {} +srt::CRcvUList::~CRcvUList() {} -void CRcvUList::insert(const CUDT *u) +void srt::CRcvUList::insert(const CUDT* u) { - CRNode *n = u->m_pRNode; - CTimer::rdtsc(n->m_llTimeStamp_tk); + CRNode* n = u->m_pRNode; + n->m_tsTimeStamp = steady_clock::now(); if (NULL == m_pUList) { @@ -663,9 +648,9 @@ void CRcvUList::insert(const CUDT *u) m_pLast = n; } -void CRcvUList::remove(const CUDT *u) +void srt::CRcvUList::remove(const CUDT* u) { - CRNode *n = u->m_pRNode; + CRNode* n = u->m_pRNode; if (!n->m_bOnList) return; @@ -694,14 +679,14 @@ void CRcvUList::remove(const CUDT *u) n->m_pNext = n->m_pPrev = NULL; } -void CRcvUList::update(const CUDT *u) +void srt::CRcvUList::update(const CUDT* u) { - CRNode *n = u->m_pRNode; + CRNode* n = u->m_pRNode; if (!n->m_bOnList) return; - CTimer::rdtsc(n->m_llTimeStamp_tk); + n->m_tsTimeStamp = steady_clock::now(); // if n is the last node, do not need to change if (NULL == n->m_pNext) @@ -725,20 +710,20 @@ void CRcvUList::update(const CUDT *u) } // -CHash::CHash() +srt::CHash::CHash() : m_pBucket(NULL) , m_iHashSize(0) { } -CHash::~CHash() +srt::CHash::~CHash() { for (int i = 0; i < m_iHashSize; ++i) { - CBucket *b = m_pBucket[i]; + CBucket* b = m_pBucket[i]; while (NULL != b) { - CBucket *n = b->m_pNext; + CBucket* n = b->m_pNext; delete b; b = n; } @@ -747,9 +732,9 @@ CHash::~CHash() delete[] m_pBucket; } -void CHash::init(int size) +void srt::CHash::init(int size) { - m_pBucket = new CBucket *[size]; + m_pBucket = new CBucket*[size]; for (int i = 0; i < size; ++i) m_pBucket[i] = NULL; @@ -757,10 +742,10 @@ void CHash::init(int size) m_iHashSize = size; } -CUDT *CHash::lookup(int32_t id) +srt::CUDT* srt::CHash::lookup(int32_t id) { // simple hash function (% hash table size); suitable for socket descriptors - CBucket *b = m_pBucket[id % m_iHashSize]; + CBucket* b = m_pBucket[id % m_iHashSize]; while (NULL != b) { @@ -772,11 +757,11 @@ CUDT *CHash::lookup(int32_t id) return NULL; } -void CHash::insert(int32_t id, CUDT *u) +void srt::CHash::insert(int32_t id, CUDT* u) { - CBucket *b = m_pBucket[id % m_iHashSize]; + CBucket* b = m_pBucket[id % m_iHashSize]; - CBucket *n = new CBucket; + CBucket* n = new CBucket; n->m_iID = id; n->m_pUDT = u; n->m_pNext = b; @@ -784,10 +769,10 @@ void CHash::insert(int32_t id, CUDT *u) m_pBucket[id % m_iHashSize] = n; } -void CHash::remove(int32_t id) +void srt::CHash::remove(int32_t id) { - CBucket *b = m_pBucket[id % m_iHashSize]; - CBucket *p = NULL; + CBucket* b = m_pBucket[id % m_iHashSize]; + CBucket* p = NULL; while (NULL != b) { @@ -809,233 +794,301 @@ void CHash::remove(int32_t id) } // -CRendezvousQueue::CRendezvousQueue() +srt::CRendezvousQueue::CRendezvousQueue() : m_lRendezvousID() - , m_RIDVectorLock() + , m_RIDListLock() { - pthread_mutex_init(&m_RIDVectorLock, NULL); } -CRendezvousQueue::~CRendezvousQueue() +srt::CRendezvousQueue::~CRendezvousQueue() { - pthread_mutex_destroy(&m_RIDVectorLock); - - for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) - { - if (AF_INET == i->m_iIPversion) - delete (sockaddr_in *)i->m_pPeerAddr; - else - delete (sockaddr_in6 *)i->m_pPeerAddr; - } - m_lRendezvousID.clear(); } -void CRendezvousQueue::insert(const SRTSOCKET &id, CUDT *u, int ipv, const sockaddr *addr, uint64_t ttl) +void srt::CRendezvousQueue::insert(const SRTSOCKET& id, + CUDT* u, + const sockaddr_any& addr, + const steady_clock::time_point& ttl) { - CGuard vg(m_RIDVectorLock); + ScopedLock vg(m_RIDListLock); CRL r; - r.m_iID = id; - r.m_pUDT = u; - r.m_iIPversion = ipv; - r.m_pPeerAddr = (AF_INET == ipv) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6; - memcpy(r.m_pPeerAddr, addr, (AF_INET == ipv) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); - r.m_ullTTL = ttl; + r.m_iID = id; + r.m_pUDT = u; + r.m_PeerAddr = addr; + r.m_tsTTL = ttl; m_lRendezvousID.push_back(r); + HLOGC(cnlog.Debug, + log << "RID: adding socket @" << id << " for address: " << addr.str() << " expires: " << FormatTime(ttl) + << " (total connectors: " << m_lRendezvousID.size() << ")"); } -void CRendezvousQueue::remove(const SRTSOCKET &id, bool should_lock) +void srt::CRendezvousQueue::remove(const SRTSOCKET& id) { - CGuard vg(m_RIDVectorLock, should_lock); + ScopedLock lkv(m_RIDListLock); for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { if (i->m_iID == id) { - if (AF_INET == i->m_iIPversion) - delete (sockaddr_in *)i->m_pPeerAddr; - else - delete (sockaddr_in6 *)i->m_pPeerAddr; - m_lRendezvousID.erase(i); - - return; + break; } } } -CUDT *CRendezvousQueue::retrieve(const sockaddr *addr, ref_t r_id) +srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& w_id) const { - CGuard vg(m_RIDVectorLock); - SRTSOCKET &id = *r_id; + ScopedLock vg(m_RIDListLock); // TODO: optimize search - for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) + for (list::const_iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { - if (CIPAddress::ipcmp(addr, i->m_pPeerAddr, i->m_iIPversion) && ((id == 0) || (id == i->m_iID))) + if (i->m_PeerAddr == addr && ((w_id == 0) || (w_id == i->m_iID))) { - id = i->m_iID; + HLOGC(cnlog.Debug, + log << "RID: found id @" << i->m_iID << " while looking for " + << (w_id ? "THIS ID FROM " : "A NEW CONNECTION FROM ") << i->m_PeerAddr.str()); + w_id = i->m_iID; return i->m_pUDT; } } +#if ENABLE_HEAVY_LOGGING + std::ostringstream spec; + if (w_id == 0) + spec << "A NEW CONNECTION REQUEST"; + else + spec << " AGENT @" << w_id; + HLOGC(cnlog.Debug, + log << "RID: NO CONNECTOR FOR ADR:" << addr.str() << " while looking for " << spec.str() << " (" + << m_lRendezvousID.size() << " connectors total)"); +#endif + return NULL; } -void CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, const CPacket &response) +void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit) { - CGuard vg(m_RIDVectorLock); + vector toRemove, toProcess; - if (m_lRendezvousID.empty()) - return; + const CPacket* pkt = unit ? &unit->m_Packet : NULL; - HLOGC(mglog.Debug, - log << "updateConnStatus: updating after getting pkt id=" << response.m_iID - << " status: " << ConnectStatusStr(cst)); + // Need a stub value for a case when there's no unit provided ("storage depleted" case). + // It should be normally NOT IN USE because in case of "storage depleted", rst != RST_OK. + const SRTSOCKET dest_id = pkt ? pkt->m_iID : 0; -#if ENABLE_HEAVY_LOGGING - int debug_nupd = 0; - int debug_nrun = 0; - int debug_nfail = 0; -#endif + // If no socket were qualified for further handling, finish here. + // Otherwise toRemove and toProcess contain items to handle. + if (!qualifyToHandle(rst, cst, dest_id, (toRemove), (toProcess))) + return; - for (list::iterator i = m_lRendezvousID.begin(), i_next = i; i != m_lRendezvousID.end(); i = i_next) + HLOGC(cnlog.Debug, + log << "updateConnStatus: collected " << toProcess.size() << " for processing, " << toRemove.size() + << " to close"); + + // Repeat (resend) connection request. + for (vector::iterator i = toProcess.begin(); i != toProcess.end(); ++i) { - ++i_next; - // NOTE: This is a SAFE LOOP. - // Incrementation will be done at the end, after the processing did not - // REMOVE the currently processed element. When the element was removed, - // the iterator value for the next iteration will be taken from erase()'s result. - - // RST_AGAIN happens in case when the last attempt to read a packet from the UDP - // socket has read nothing. In this case it would be a repeated update, while - // still waiting for a response from the peer. When we have any other state here - // (most expectably CONN_CONTINUE or CONN_RENDEZVOUS, which means that a packet has - // just arrived in this iteration), do the update immetiately (in SRT this also - // involves additional incoming data interpretation, which wasn't the case in UDT). - - // Use "slow" cyclic responding in case when - // - RST_AGAIN (no packet was received for whichever socket) - // - a packet was received, but not for THIS socket - if (rst == RST_AGAIN || i->m_iID != response.m_iID) + // IMPORTANT INFORMATION concerning changes towards UDT legacy. + // In the UDT code there was no attempt to interpret any incoming data. + // All data from the incoming packet were considered to be already deployed into + // m_ConnRes field, and m_ConnReq field was considered at this time accordingly updated. + // Therefore this procedure did only one thing: craft a new handshake packet and send it. + // In SRT this may also interpret extra data (extensions in case when Agent is Responder) + // and the `pktIn` packet may sometimes contain no data. Therefore the passed `rst` + // must be checked to distinguish the call by periodic update (RST_AGAIN) from a call + // due to have received the packet (RST_OK). + // + // In the below call, only the underlying `processRendezvous` function will be attempting + // to interpret these data (for caller-listener this was already done by `processConnectRequest` + // before calling this function), and it checks for the data presence. + + EReadStatus read_st = rst; + EConnectStatus conn_st = cst; + + if (i->id != dest_id) { - // If no packet has been received from the peer, - // avoid sending too many requests, at most 1 request per 250ms - const uint64_t then = i->m_pUDT->m_llLastReqTime; - const uint64_t now = CTimer::getTime(); - const bool nowstime = (now - then) > 250000; - HLOGC(mglog.Debug, - log << "RID:%" << i->m_iID << " then=" << then << " now=" << now << " passed=" << (now - then) - << "<=> 250000 -- now's " << (nowstime ? "" : "NOT ") << "the time"); - - if (!nowstime) - continue; + read_st = RST_AGAIN; + conn_st = CONN_AGAIN; } - HLOGC(mglog.Debug, log << "RID:%" << i->m_iID << " cst=" << ConnectStatusStr(cst) << " -- sending update NOW."); + HLOGC(cnlog.Debug, + log << "updateConnStatus: processing async conn for @" << i->id << " FROM " << i->peeraddr.str()); -#if ENABLE_HEAVY_LOGGING - ++debug_nrun; -#endif + if (!i->u->processAsyncConnectRequest(read_st, conn_st, pkt, i->peeraddr)) + { + // cst == CONN_REJECT can only be result of worker_ProcessAddressedPacket and + // its already set in this case. + LinkStatusInfo fi = *i; + fi.errorcode = SRT_ECONNREJ; + toRemove.push_back(fi); + i->u->sendCtrl(UMSG_SHUTDOWN); + } + } + + // NOTE: it is "believed" here that all CUDT objects will not be + // deleted in the meantime. This is based on a statement that at worst + // they have been "just" declared failed and it will pass at least 1s until + // they are moved to ClosedSockets and it is believed that this function will + // not be held on mutexes that long. - // XXX This looks like a loop that rolls in infinity without any sleeps - // inside and makes it once per about 50 calls send a hs conclusion - // for a randomly sampled rendezvous ID of a socket out of the list. - // Ok, probably the rendezvous ID should be just one so not much to - // sample from, but if so, why the container? + for (vector::iterator i = toRemove.begin(); i != toRemove.end(); ++i) + { + HLOGC(cnlog.Debug, log << "updateConnStatus: COMPLETING dep objects update on failed @" << i->id); // - // This must be somehow fixed! + // Setting m_bConnecting to false, and need to remove the socket from the rendezvous queue + // because the next CUDT::close will not remove it from the queue when m_bConnecting = false, + // and may crash on next pass. // - // Maybe the time should be simply checked once and the whole loop not - // done when "it's not the time"? - if (CTimer::getTime() >= i->m_ullTTL) + // TODO: maybe lock i->u->m_ConnectionLock? + i->u->m_bConnecting = false; + remove(i->u->m_SocketID); + + // DO NOT close the socket here because in this case it might be + // unable to get status from at the right moment. Also only member + // sockets should be taken care of internally - single sockets should + // be normally closed by the application, after it is done with them. + + // app can call any UDT API to learn the connection_broken error + CUDT::uglobal().m_EPoll.update_events( + i->u->m_SocketID, i->u->m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); + + i->u->completeBrokenConnectionDependencies(i->errorcode); + } + + { + // Now, additionally for every failed link reset the TTL so that + // they are set expired right now. + ScopedLock vg(m_RIDListLock); + for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { - HLOGC(mglog.Debug, - log << "RendezvousQueue: EXPIRED (" << (i->m_ullTTL ? "enforced on FAILURE" : "passed TTL") - << ". removing from queue"); - // connection timer expired, acknowledge app via epoll - i->m_pUDT->m_bConnecting = false; - CUDT::s_UDTUnited.m_EPoll.update_events(i->m_iID, i->m_pUDT->m_sPollID, UDT_EPOLL_ERR, true); - /* - * Setting m_bConnecting to false but keeping socket in rendezvous queue is not a good idea. - * Next CUDT::close will not remove it from rendezvous queue (because !m_bConnecting) - * and may crash here on next pass. - */ - if (AF_INET == i->m_iIPversion) - delete (sockaddr_in *)i->m_pPeerAddr; - else - delete (sockaddr_in6 *)i->m_pPeerAddr; + if (find_if(toRemove.begin(), toRemove.end(), LinkStatusInfo::HasID(i->m_iID)) != toRemove.end()) + { + LOGC(cnlog.Error, + log << "updateConnStatus: processAsyncConnectRequest FAILED on @" << i->m_iID + << ". Setting TTL as EXPIRED."); + i->m_tsTTL = + steady_clock::time_point(); // Make it expire right now, will be picked up at the next iteration + } + } + } +} + +bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, + EConnectStatus cst SRT_ATR_UNUSED, + int iDstSockID, + vector& toRemove, + vector& toProcess) +{ + ScopedLock vg(m_RIDListLock); + + if (m_lRendezvousID.empty()) + return false; // nothing to process. + + HLOGC(cnlog.Debug, + log << "updateConnStatus: updating after getting pkt with DST socket ID @" << iDstSockID + << " status: " << ConnectStatusStr(cst)); + + for (list::iterator i = m_lRendezvousID.begin(), i_next = i; i != m_lRendezvousID.end(); i = i_next) + { + // Safe iterator to the next element. If the current element is erased, the iterator is updated again. + ++i_next; + + const steady_clock::time_point tsNow = steady_clock::now(); + + if (tsNow >= i->m_tsTTL) + { + HLOGC(cnlog.Debug, + log << "RID: socket @" << i->m_iID + << " removed - EXPIRED (" + // The "enforced on FAILURE" is below when processAsyncConnectRequest failed. + << (is_zero(i->m_tsTTL) ? "enforced on FAILURE" : "passed TTL") << "). WILL REMOVE from queue."); + + // Set appropriate error information, but do not update yet. + // Exit the lock first. Collect objects to update them later. + int ccerror = SRT_ECONNREJ; + if (i->m_pUDT->m_RejectReason == SRT_REJ_UNKNOWN) + { + if (!is_zero(i->m_tsTTL)) + { + // Timer expired, set TIMEOUT forcefully + i->m_pUDT->m_RejectReason = SRT_REJ_TIMEOUT; + ccerror = SRT_ENOSERVER; + } + else + { + // In case of unknown reason, rejection should at least + // suggest error on the peer + i->m_pUDT->m_RejectReason = SRT_REJ_PEER; + } + } + + // The call to completeBrokenConnectionDependencies() cannot happen here + // under the lock of m_RIDListLock as it risks a deadlock. + // Collect in 'toRemove' to update later. + LinkStatusInfo fi = {i->m_pUDT, i->m_iID, ccerror, i->m_PeerAddr, -1}; + toRemove.push_back(fi); // i_next was preincremented, but this is guaranteed to point to // the element next to erased one. i_next = m_lRendezvousID.erase(i); continue; } - - // This queue is used only in case of Async mode (rendezvous or caller-listener). - // Synchronous connection requests are handled in startConnect() completely. - if (!i->m_pUDT->m_bSynRecving) + else { -#if ENABLE_HEAVY_LOGGING - ++debug_nupd; -#endif - // IMPORTANT INFORMATION concerning changes towards UDT legacy. - // In the UDT code there was no attempt to interpret any incoming data. - // All data from the incoming packet were considered to be already deployed into - // m_ConnRes field, and m_ConnReq field was considered at this time accordingly updated. - // Therefore this procedure did only one thing: craft a new handshake packet and send it. - // In SRT this may also interpret extra data (extensions in case when Agent is Responder) - // and the `response` packet may sometimes contain no data. Therefore the passed `rst` - // must be checked to distinguish the call by periodic update (RST_AGAIN) from a call - // due to have received the packet (RST_OK). - // - // In the below call, only the underlying `processRendezvous` function will be attempting - // to interpret these data (for caller-listener this was already done by `processConnectRequest` - // before calling this function), and it checks for the data presence. - - EReadStatus read_st = rst; - EConnectStatus conn_st = cst; - - if (i->m_iID != response.m_iID) - { - read_st = RST_AGAIN; - conn_st = CONN_AGAIN; - } + HLOGC(cnlog.Debug, + log << "RID: socket @" << i->m_iID << " still active (remaining " << std::fixed + << (count_microseconds(i->m_tsTTL - tsNow) / 1000000.0) << "s of TTL)..."); + } - if (!i->m_pUDT->processAsyncConnectRequest(read_st, conn_st, response, i->m_pPeerAddr)) - { - // cst == CONN_REJECT can only be result of worker_ProcessAddressedPacket and - // its already set in this case. - LOGC(mglog.Error, log << "RendezvousQueue: processAsyncConnectRequest FAILED. Setting TTL as EXPIRED."); - i->m_pUDT->sendCtrl(UMSG_SHUTDOWN); - i->m_ullTTL = 0; // Make it expire right now, will be picked up at the next iteration -#if ENABLE_HEAVY_LOGGING - ++debug_nfail; -#endif - } + const steady_clock::time_point tsLastReq = i->m_pUDT->m_tsLastReqTime; + const steady_clock::time_point tsRepeat = + tsLastReq + milliseconds_from(250); // Repeat connection request (send HS). + + // A connection request is repeated every 250 ms if there was no response from the peer: + // - RST_AGAIN means no packet was received over UDP. + // - a packet was received, but not for THIS socket. + if ((rst == RST_AGAIN || i->m_iID != iDstSockID) && tsNow <= tsRepeat) + { + HLOGC(cnlog.Debug, + log << "RID:@" << i->m_iID << std::fixed << count_microseconds(tsNow - tsLastReq) / 1000.0 + << " ms passed since last connection request."); - // NOTE: safe loop, the incrementation was done before the loop body, - // so the `i' node can be safely deleted. Just the body must end here. continue; } + + HLOGC(cnlog.Debug, + log << "RID:@" << i->m_iID << " cst=" << ConnectStatusStr(cst) << " -- repeating connection request."); + + // This queue is used only in case of Async mode (rendezvous or caller-listener). + // Synchronous connection requests are handled in startConnect() completely. + if (!i->m_pUDT->m_config.bSynRecving) + { + // Collect them so that they can be updated out of m_RIDListLock. + LinkStatusInfo fi = {i->m_pUDT, i->m_iID, SRT_SUCCESS, i->m_PeerAddr, -1}; + toProcess.push_back(fi); + } + else + { + HLOGC(cnlog.Debug, log << "RID: socket @" << i->m_iID << " is SYNCHRONOUS, NOT UPDATING"); + } } - HLOGC(mglog.Debug, - log << "updateConnStatus: " << debug_nupd << "/" << debug_nrun << " sockets updated (" - << (debug_nrun - debug_nupd) << " useless). REMOVED " << debug_nfail << " sockets."); + return !toRemove.empty() || !toProcess.empty(); } // -CRcvQueue::CRcvQueue() +srt::CRcvQueue::CRcvQueue() : m_WorkerThread() - , m_UnitQueue() + , m_pUnitQueue(NULL) , m_pRcvUList(NULL) , m_pHash(NULL) , m_pChannel(NULL) , m_pTimer(NULL) - , m_iPayloadSize() + , m_iIPversion() + , m_szPayloadSize() , m_bClosing(false) , m_LSLock() , m_pListener(NULL) @@ -1043,35 +1096,33 @@ CRcvQueue::CRcvQueue() , m_vNewEntry() , m_IDLock() , m_mBuffer() - , m_PassLock() - , m_PassCond() + , m_BufferCond() { - pthread_mutex_init(&m_PassLock, NULL); - pthread_cond_init(&m_PassCond, NULL); - pthread_mutex_init(&m_LSLock, NULL); - pthread_mutex_init(&m_IDLock, NULL); + setupCond(m_BufferCond, "QueueBuffer"); } -CRcvQueue::~CRcvQueue() +srt::CRcvQueue::~CRcvQueue() { m_bClosing = true; - if (!pthread_equal(m_WorkerThread, pthread_t())) - pthread_join(m_WorkerThread, NULL); - pthread_mutex_destroy(&m_PassLock); - pthread_cond_destroy(&m_PassCond); - pthread_mutex_destroy(&m_LSLock); - pthread_mutex_destroy(&m_IDLock); + if (m_WorkerThread.joinable()) + { + HLOGC(rslog.Debug, log << "RcvQueue: EXIT"); + m_WorkerThread.join(); + } + releaseCond(m_BufferCond); + + delete m_pUnitQueue; delete m_pRcvUList; delete m_pHash; delete m_pRendezvousQueue; // remove all queued messages - for (map >::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++i) + for (map >::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++i) { while (!i->second.empty()) { - CPacket *pkt = i->second.front(); + CPacket* pkt = i->second.front(); delete[] pkt->m_pcData; delete pkt; i->second.pop(); @@ -1079,11 +1130,17 @@ CRcvQueue::~CRcvQueue() } } -void CRcvQueue::init(int qsize, int payload, int version, int hsize, CChannel *cc, CTimer *t) +#if ENABLE_LOGGING +srt::sync::atomic srt::CRcvQueue::m_counter(0); +#endif + +void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CChannel* cc, CTimer* t) { - m_iPayloadSize = payload; + m_iIPversion = version; + m_szPayloadSize = payload; - m_UnitQueue.init(qsize, payload, version); + SRT_ASSERT(m_pUnitQueue == NULL); + m_pUnitQueue = new CUnitQueue(qsize, (int)payload); m_pHash = new CHash; m_pHash->init(hsize); @@ -1094,35 +1151,44 @@ void CRcvQueue::init(int qsize, int payload, int version, int hsize, CChannel *c m_pRcvUList = new CRcvUList; m_pRendezvousQueue = new CRendezvousQueue; - ThreadName tn("SRT:RcvQ:worker"); - if (0 != pthread_create(&m_WorkerThread, NULL, CRcvQueue::worker, this)) +#if ENABLE_LOGGING + const int cnt = ++m_counter; + const std::string thrname = "SRT:RcvQ:w" + Sprint(cnt); +#else + const std::string thrname = "SRT:RcvQ:w"; +#endif + + if (!StartThread(m_WorkerThread, CRcvQueue::worker, this, thrname.c_str())) { - m_WorkerThread = pthread_t(); throw CUDTException(MJ_SYSTEMRES, MN_THREAD); } } -void *CRcvQueue::worker(void *param) +void* srt::CRcvQueue::worker(void* param) { - CRcvQueue * self = (CRcvQueue *)param; - sockaddr_any sa(self->m_UnitQueue.getIPversion()); + CRcvQueue* self = (CRcvQueue*)param; + sockaddr_any sa(self->getIPversion()); int32_t id = 0; +#if ENABLE_LOGGING + THREAD_STATE_INIT(("SRT:RcvQ:w" + Sprint(m_counter)).c_str()); +#else THREAD_STATE_INIT("SRT:RcvQ:worker"); +#endif - CUnit * unit = 0; + CUnit* unit = 0; EConnectStatus cst = CONN_AGAIN; while (!self->m_bClosing) { bool have_received = false; - EReadStatus rst = self->worker_RetrieveUnit(Ref(id), Ref(unit), &sa); + EReadStatus rst = self->worker_RetrieveUnit((id), (unit), (sa)); if (rst == RST_OK) { if (id < 0) { // User error on peer. May log something, but generally can only ignore it. // XXX Think maybe about sending some "connection rejection response". - HLOGC(mglog.Debug, + HLOGC(qrlog.Debug, log << self->CONID() << "RECEIVED negative socket id '" << id << "', rejecting (POSSIBLE ATTACK)"); continue; @@ -1138,20 +1204,20 @@ void *CRcvQueue::worker(void *param) if (id == 0) { // ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets - cst = self->worker_ProcessConnectionRequest(unit, &sa); + cst = self->worker_ProcessConnectionRequest(unit, sa); } else { // Otherwise ID is expected to be associated with: // - an enqueued rendezvous socket // - a socket connected to a peer - cst = self->worker_ProcessAddressedPacket(id, unit, &sa); + cst = self->worker_ProcessAddressedPacket(id, unit, sa); // CAN RETURN CONN_REJECT, but m_RejectReason is already set } - HLOGC(mglog.Debug, log << self->CONID() << "worker: result for the unit: " << ConnectStatusStr(cst)); + HLOGC(qrlog.Debug, log << self->CONID() << "worker: result for the unit: " << ConnectStatusStr(cst)); if (cst == CONN_AGAIN) { - HLOGC(mglog.Debug, log << self->CONID() << "worker: packet not dispatched, continuing reading."); + HLOGC(qrlog.Debug, log << self->CONID() << "worker: packet not dispatched, continuing reading."); continue; } have_received = true; @@ -1165,12 +1231,12 @@ void *CRcvQueue::worker(void *param) // Check that just to report possible errors, but interrupt the loop anyway. if (self->m_bClosing) { - HLOGC(mglog.Debug, + HLOGC(qrlog.Debug, log << self->CONID() << "CChannel reported error, but Queue is closing - INTERRUPTING worker."); } else { - LOGC(mglog.Fatal, + LOGC(qrlog.Fatal, log << self->CONID() << "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway."); } @@ -1180,14 +1246,13 @@ void *CRcvQueue::worker(void *param) // OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue. // take care of the timing event for all UDT sockets - uint64_t currtime_tk; - CTimer::rdtsc(currtime_tk); + const steady_clock::time_point curtime_minus_syn = + steady_clock::now() - microseconds_from(CUDT::COMM_SYN_INTERVAL_US); - CRNode * ul = self->m_pRcvUList->m_pUList; - const uint64_t ctime_tk = currtime_tk - CUDT::COMM_SYN_INTERVAL_US * CTimer::getCPUFrequency(); - while ((NULL != ul) && (ul->m_llTimeStamp_tk < ctime_tk)) + CRNode* ul = self->m_pRcvUList->m_pUList; + while ((NULL != ul) && (ul->m_tsTimeStamp < curtime_minus_syn)) { - CUDT *u = ul->m_pUDT; + CUDT* u = ul->m_pUDT; if (u->m_bConnected && !u->m_bBroken && !u->m_bClosing) { @@ -1196,7 +1261,7 @@ void *CRcvQueue::worker(void *param) } else { - HLOGC(mglog.Debug, + HLOGC(qrlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << " SOCKET broken, REMOVING FROM RCV QUEUE/MAP."); // the socket must be removed from Hash table first, then RcvUList self->m_pHash->remove(u->m_SocketID); @@ -1209,7 +1274,7 @@ void *CRcvQueue::worker(void *param) if (have_received) { - HLOGC(mglog.Debug, + HLOGC(qrlog.Debug, log << "worker: RECEIVED PACKET --> updateConnStatus. cst=" << ConnectStatusStr(cst) << " id=" << id << " pkt-payload-size=" << unit->m_Packet.getLength()); } @@ -1220,41 +1285,19 @@ void *CRcvQueue::worker(void *param) // worker_TryAsyncRend_OrStore ---> // CUDT::processAsyncConnectResponse ---> // CUDT::processConnectResponse - self->m_pRendezvousQueue->updateConnStatus(rst, cst, unit->m_Packet); + self->m_pRendezvousQueue->updateConnStatus(rst, cst, unit); // XXX updateConnStatus may have removed the connector from the list, // however there's still m_mBuffer in CRcvQueue for that socket to care about. } + HLOGC(qrlog.Debug, log << "worker: EXIT"); + THREAD_EXIT(); return NULL; } -#if ENABLE_LOGGING -static string PacketInfo(const CPacket &pkt) -{ - ostringstream os; - os << "TARGET=" << pkt.m_iID << " "; - - if (pkt.isControl()) - { - os << "CONTROL: " << MessageTypeStr(pkt.getType(), pkt.getExtendedType()) << " size=" << pkt.getLength(); - } - else - { - // It's hard to extract the information about peer's supported rexmit flag. - // This is only a log, nothing crucial, so we can risk displaying incorrect message number. - // Declaring that the peer supports rexmit flag cuts off the highest bit from - // the displayed number. - os << "DATA: msg=" << pkt.getMsgSeq(true) << " seq=" << pkt.getSeqNo() << " size=" << pkt.getLength() - << " flags: " << PacketMessageFlagStr(pkt.m_iMsgNo); - } - - return os.str(); -} -#endif - -EReadStatus CRcvQueue::worker_RetrieveUnit(ref_t r_id, ref_t r_unit, sockaddr *addr) +srt::EReadStatus srt::CRcvQueue::worker_RetrieveUnit(int32_t& w_id, CUnit*& w_unit, sockaddr_any& w_addr) { #if !USE_BUSY_WAITING // This might be not really necessary, and probably @@ -1265,10 +1308,10 @@ EReadStatus CRcvQueue::worker_RetrieveUnit(ref_t r_id, ref_t r // check waiting list, if new socket, insert it to the list while (ifNewEntry()) { - CUDT *ne = getNewEntry(); + CUDT* ne = getNewEntry(); if (ne) { - HLOGC(mglog.Debug, + HLOGC(qrlog.Debug, log << CUDTUnited::CONID(ne->m_SocketID) << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP"); m_pRcvUList->insert(ne); @@ -1276,19 +1319,18 @@ EReadStatus CRcvQueue::worker_RetrieveUnit(ref_t r_id, ref_t r } } // find next available slot for incoming packet - *r_unit = m_UnitQueue.getNextAvailUnit(); - if (!*r_unit) + w_unit = m_pUnitQueue->getNextAvailUnit(); + if (!w_unit) { // no space, skip this packet CPacket temp; - temp.m_pcData = new char[m_iPayloadSize]; - temp.setLength(m_iPayloadSize); + temp.m_pcData = new char[m_szPayloadSize]; + temp.setLength(m_szPayloadSize); THREAD_PAUSED(); - EReadStatus rst = m_pChannel->recvfrom(addr, temp); + EReadStatus rst = m_pChannel->recvfrom((w_addr), (temp)); THREAD_RESUMED(); -#if ENABLE_LOGGING - LOGC(mglog.Error, log << CONID() << "LOCAL STORAGE DEPLETED. Dropping 1 packet: " << PacketInfo(temp)); -#endif + // Note: this will print nothing about the packet details unless heavy logging is on. + LOGC(qrlog.Error, log << CONID() << "LOCAL STORAGE DEPLETED. Dropping 1 packet: " << temp.Info()); delete[] temp.m_pcData; // Be transparent for RST_ERROR, but ignore the correct @@ -1296,40 +1338,38 @@ EReadStatus CRcvQueue::worker_RetrieveUnit(ref_t r_id, ref_t r return rst == RST_ERROR ? RST_ERROR : RST_AGAIN; } - r_unit->m_Packet.setLength(m_iPayloadSize); + w_unit->m_Packet.setLength(m_szPayloadSize); // reading next incoming packet, recvfrom returns -1 is nothing has been received THREAD_PAUSED(); - EReadStatus rst = m_pChannel->recvfrom(addr, r_unit->m_Packet); + EReadStatus rst = m_pChannel->recvfrom((w_addr), (w_unit->m_Packet)); THREAD_RESUMED(); if (rst == RST_OK) { - *r_id = r_unit->m_Packet.m_iID; - HLOGC(mglog.Debug, - log << "INCOMING PACKET: BOUND=" << SockaddrToString(m_pChannel->bindAddress()) << " " - << PacketInfo(r_unit->m_Packet)); + w_id = w_unit->m_Packet.m_iID; + HLOGC(qrlog.Debug, + log << "INCOMING PACKET: FROM=" << w_addr.str() << " BOUND=" << m_pChannel->bindAddressAny().str() << " " + << w_unit->m_Packet.Info()); } return rst; } -EConnectStatus CRcvQueue::worker_ProcessConnectionRequest(CUnit *unit, const sockaddr *addr) +srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr_any& addr) { - HLOGC(mglog.Debug, - log << "Got sockID=0 from " << SockaddrToString(addr) - << " - trying to resolve it as a connection request..."); + HLOGC(cnlog.Debug, + log << "Got sockID=0 from " << addr.str() << " - trying to resolve it as a connection request..."); // Introduced protection because it may potentially happen // that another thread could have closed the socket at // the same time and inject a bug between checking the // pointer for NULL and using it. - SRT_REJECT_REASON listener_ret = SRT_REJ_UNKNOWN; - bool have_listener = false; + int listener_ret = SRT_REJ_UNKNOWN; + bool have_listener = false; { - CGuard cg(m_LSLock); + ScopedLock cg(m_LSLock); if (m_pListener) { - LOGC(mglog.Note, - log << "PASSING request from: " << SockaddrToString(addr) << " to agent:" << m_pListener->socketID()); + LOGC(cnlog.Note, log << "PASSING request from: " << addr.str() << " to agent:" << m_pListener->socketID()); listener_ret = m_pListener->processConnectRequest(addr, unit->m_Packet); // This function does return a code, but it's hard to say as to whether @@ -1348,8 +1388,8 @@ EConnectStatus CRcvQueue::worker_ProcessConnectionRequest(CUnit *unit, const soc if (have_listener) // That is, the above block with m_pListener->processConnectRequest was executed { - LOGC(mglog.Note, - log << CONID() << "Listener managed the connection request from: " << SockaddrToString(addr) + LOGC(cnlog.Note, + log << CONID() << "Listener managed the connection request from: " << addr.str() << " result:" << RequestTypeStr(UDTRequestType(listener_ret))); return listener_ret == SRT_REJ_UNKNOWN ? CONN_CONTINUE : CONN_REJECT; } @@ -1358,24 +1398,24 @@ EConnectStatus CRcvQueue::worker_ProcessConnectionRequest(CUnit *unit, const soc return worker_TryAsyncRend_OrStore(0, unit, addr); // 0 id because the packet came in with that very ID. } -EConnectStatus CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit *unit, const sockaddr *addr) +srt::EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& addr) { - CUDT *u = m_pHash->lookup(id); + CUDT* u = m_pHash->lookup(id); if (!u) { // Pass this to either async rendezvous connection, // or store the packet in the queue. - HLOGC(mglog.Debug, log << "worker_ProcessAddressedPacket: resending to target socket %" << id); + HLOGC(cnlog.Debug, log << "worker_ProcessAddressedPacket: resending to QUEUED socket @" << id); return worker_TryAsyncRend_OrStore(id, unit, addr); } // Found associated CUDT - process this as control or data packet // addressed to an associated socket. - if (!CIPAddress::ipcmp(addr, u->m_pPeerAddr, u->m_iIPversion)) + if (addr != u->m_PeerAddr) { - HLOGC(mglog.Debug, - log << CONID() << "Packet for SID=" << id << " asoc with " << SockaddrToString(u->m_pPeerAddr) - << " received from " << SockaddrToString(addr) << " (CONSIDERED ATTACK ATTEMPT)"); + HLOGC(cnlog.Debug, + log << CONID() << "Packet for SID=" << id << " asoc with " << u->m_PeerAddr.str() << " received from " + << addr.str() << " (CONSIDERED ATTACK ATTEMPT)"); // This came not from the address that is the peer associated // with the socket. Ignore it. return CONN_AGAIN; @@ -1399,7 +1439,7 @@ EConnectStatus CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit *unit, u->checkTimers(); m_pRcvUList->update(u); - return CONN_CONTINUE; + return CONN_RUNNING; } // This function responds to the fact that a packet has come @@ -1410,13 +1450,13 @@ EConnectStatus CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit *unit, // This function then tries to manage the packet as a rendezvous connection // request in ASYNC mode; when this is not applicable, it stores the packet // in the "receiving queue" so that it will be picked up in the "main" thread. -EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, const sockaddr *addr) +srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& addr) { // This 'retrieve' requires that 'id' be either one of those // stored in the rendezvous queue (see CRcvQueue::registerConnector) // or simply 0, but then at least the address must match one of these. // If the id was 0, it will be set to the actual socket ID of the returned CUDT. - CUDT *u = m_pRendezvousQueue->retrieve(addr, Ref(id)); + CUDT* u = m_pRendezvousQueue->retrieve(addr, (id)); if (!u) { // this socket is then completely unknown to the system. @@ -1429,18 +1469,18 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c // this socket registerred in the RendezvousQueue, which causes the packet unable // to be dispatched. Therefore simply treat every "out of band" packet (with socket // not belonging to the connection and not registered as rendezvous) as "possible - // attach" and ignore it. This also should better protect the rendezvous socket + // attack" and ignore it. This also should better protect the rendezvous socket // against a rogue connector. if (id == 0) { - HLOGC(mglog.Debug, - log << CONID() << "AsyncOrRND: no sockets expect connection from " << SockaddrToString(addr) + HLOGC(cnlog.Debug, + log << CONID() << "AsyncOrRND: no sockets expect connection from " << addr.str() << " - POSSIBLE ATTACK, ignore packet"); } else { - HLOGC(mglog.Debug, - log << CONID() << "AsyncOrRND: no sockets expect socket " << id << " from " << SockaddrToString(addr) + HLOGC(cnlog.Debug, + log << CONID() << "AsyncOrRND: no sockets expect socket " << id << " from " << addr.str() << " - POSSIBLE ATTACK, ignore packet"); } return CONN_AGAIN; // This means that the packet should be ignored. @@ -1448,9 +1488,9 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c // asynchronous connect: call connect here // otherwise wait for the UDT socket to retrieve this packet - if (!u->m_bSynRecving) + if (!u->m_config.bSynRecving) { - HLOGC(mglog.Debug, log << "AsyncOrRND: packet RESOLVED TO ID=" << id << " -- continuing as ASYNC CONNECT"); + HLOGC(cnlog.Debug, log << "AsyncOrRND: packet RESOLVED TO @" << id << " -- continuing as ASYNC CONNECT"); // This is practically same as processConnectResponse, just this applies // appropriate mutex lock - which can't be done here because it's intentionally private. // OTOH it can't be applied to processConnectResponse because the synchronous @@ -1459,9 +1499,9 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c if (cst == CONN_CONFUSED) { - LOGC(mglog.Warn, log << "AsyncOrRND: PACKET NOT HANDSHAKE - re-requesting handshake from peer"); + LOGC(cnlog.Warn, log << "AsyncOrRND: PACKET NOT HANDSHAKE - re-requesting handshake from peer"); storePkt(id, unit->m_Packet.clone()); - if (!u->processAsyncConnectRequest(RST_AGAIN, CONN_CONTINUE, unit->m_Packet, u->m_pPeerAddr)) + if (!u->processAsyncConnectRequest(RST_AGAIN, CONN_CONTINUE, &unit->m_Packet, u->m_PeerAddr)) { // Reuse previous behavior to reject a packet cst = CONN_REJECT; @@ -1487,7 +1527,7 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c // that we KNOW (by the cst == CONN_ACCEPT result) that the socket should be inserted // into the pending anteroom. - CUDT *ne = getNewEntry(); // This function actuall removes the entry and returns it. + CUDT* ne = getNewEntry(); // This function actuall removes the entry and returns it. // This **should** now always return a non-null value, but check it first // because if this accidentally isn't true, the call to worker_ProcessAddressedPacket will // result in redirecting it to here and so on until the call stack overflow. In case of @@ -1497,7 +1537,7 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c // differently throughout the functions). if (ne) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << CUDTUnited::CONID(ne->m_SocketID) << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP"); m_pRcvUList->insert(ne); @@ -1510,7 +1550,7 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c // data packet that should have expected the connection to be already established. Therefore // redirect it once again into worker_ProcessAddressedPacket here. - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "AsyncOrRND: packet SWITCHED TO CONNECTED with ID=" << id << " -- passing to worker_ProcessAddressedPacket"); @@ -1524,14 +1564,14 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c } else { - LOGC(mglog.Error, + LOGC(cnlog.Error, log << "IPE: AsyncOrRND: packet SWITCHED TO CONNECTED, but ID=" << id << " is still not present in the socket ID dispatch hash - DISREGARDING"); } } return cst; } - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "AsyncOrRND: packet RESOLVED TO ID=" << id << " -- continuing through CENTRAL PACKET QUEUE"); // This is where also the packets for rendezvous connection will be landing, // in case of a synchronous connection. @@ -1540,31 +1580,49 @@ EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit *unit, c return CONN_CONTINUE; } -int CRcvQueue::recvfrom(int32_t id, ref_t r_packet) +void srt::CRcvQueue::stopWorker() { - CGuard bufferlock(m_PassLock); - CPacket &packet = *r_packet; + // We use the decent way, so we say to the thread "please exit". + m_bClosing = true; - map >::iterator i = m_mBuffer.find(id); + // Sanity check of the function's affinity. + if (srt::sync::this_thread::get_id() == m_WorkerThread.get_id()) + { + LOGC(rslog.Error, log << "IPE: RcvQ:WORKER TRIES TO CLOSE ITSELF!"); + return; // do nothing else, this would cause a hangup or crash. + } + + HLOGC(rslog.Debug, log << "RcvQueue: EXIT (forced)"); + // And we trust the thread that it does. + m_WorkerThread.join(); +} + +int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet) +{ + CUniqueSync buffercond(m_BufferLock, m_BufferCond); + + map >::iterator i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { - CTimer::condTimedWaitUS(&m_PassCond, &m_PassLock, 1000000); + THREAD_PAUSED(); + buffercond.wait_for(seconds_from(1)); + THREAD_RESUMED(); i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { - packet.setLength(-1); + w_packet.setLength(-1); return -1; } } // retrieve the earliest packet - CPacket *newpkt = i->second.front(); + CPacket* newpkt = i->second.front(); - if (packet.getLength() < newpkt->getLength()) + if (w_packet.getLength() < newpkt->getLength()) { - packet.setLength(-1); + w_packet.setLength(-1); return -1; } @@ -1576,9 +1634,9 @@ int CRcvQueue::recvfrom(int32_t id, ref_t r_packet) // copies it into the passed packet and then the source packet // gets deleted. Why not simply return the originally stored packet, // without copying, allocation and deallocation? - memcpy(packet.m_nHeader, newpkt->m_nHeader, CPacket::HDR_SIZE); - memcpy(packet.m_pcData, newpkt->m_pcData, newpkt->getLength()); - packet.setLength(newpkt->getLength()); + memcpy((w_packet.m_nHeader), newpkt->m_nHeader, CPacket::HDR_SIZE); + memcpy((w_packet.m_pcData), newpkt->m_pcData, newpkt->getLength()); + w_packet.setLength(newpkt->getLength()); delete[] newpkt->m_pcData; delete newpkt; @@ -1589,12 +1647,12 @@ int CRcvQueue::recvfrom(int32_t id, ref_t r_packet) if (i->second.empty()) m_mBuffer.erase(i); - return (int)packet.getLength(); + return (int)w_packet.getLength(); } -int CRcvQueue::setListener(CUDT *u) +int srt::CRcvQueue::setListener(CUDT* u) { - CGuard lslock(m_LSLock); + ScopedLock lslock(m_LSLock); if (NULL != m_pListener) return -1; @@ -1603,32 +1661,35 @@ int CRcvQueue::setListener(CUDT *u) return 0; } -void CRcvQueue::removeListener(const CUDT *u) +void srt::CRcvQueue::removeListener(const CUDT* u) { - CGuard lslock(m_LSLock); + ScopedLock lslock(m_LSLock); if (u == m_pListener) m_pListener = NULL; } -void CRcvQueue::registerConnector(const SRTSOCKET &id, CUDT *u, int ipv, const sockaddr *addr, uint64_t ttl) +void srt::CRcvQueue::registerConnector(const SRTSOCKET& id, + CUDT* u, + const sockaddr_any& addr, + const steady_clock::time_point& ttl) { - HLOGC(mglog.Debug, - log << "registerConnector: adding %" << id << " addr=" << SockaddrToString(addr) << " TTL=" << ttl); - m_pRendezvousQueue->insert(id, u, ipv, addr, ttl); + HLOGC(cnlog.Debug, + log << "registerConnector: adding @" << id << " addr=" << addr.str() << " TTL=" << FormatTime(ttl)); + m_pRendezvousQueue->insert(id, u, addr, ttl); } -void CRcvQueue::removeConnector(const SRTSOCKET &id, bool should_lock) +void srt::CRcvQueue::removeConnector(const SRTSOCKET& id) { - HLOGC(mglog.Debug, log << "removeConnector: removing %" << id); - m_pRendezvousQueue->remove(id, should_lock); + HLOGC(cnlog.Debug, log << "removeConnector: removing @" << id); + m_pRendezvousQueue->remove(id); - CGuard bufferlock(m_PassLock); + ScopedLock bufferlock(m_BufferLock); - map >::iterator i = m_mBuffer.find(id); + map >::iterator i = m_mBuffer.find(id); if (i != m_mBuffer.end()) { - HLOGC(mglog.Debug, + HLOGC(cnlog.Debug, log << "removeConnector: ... and its packet queue with " << i->second.size() << " packets collected"); while (!i->second.empty()) { @@ -1640,38 +1701,41 @@ void CRcvQueue::removeConnector(const SRTSOCKET &id, bool should_lock) } } -void CRcvQueue::setNewEntry(CUDT *u) +void srt::CRcvQueue::setNewEntry(CUDT* u) { - HLOGC(mglog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << "setting socket PENDING FOR CONNECTION"); - CGuard listguard(m_IDLock); + HLOGC(cnlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << "setting socket PENDING FOR CONNECTION"); + ScopedLock listguard(m_IDLock); m_vNewEntry.push_back(u); } -bool CRcvQueue::ifNewEntry() { return !(m_vNewEntry.empty()); } +bool srt::CRcvQueue::ifNewEntry() +{ + return !(m_vNewEntry.empty()); +} -CUDT *CRcvQueue::getNewEntry() +srt::CUDT* srt::CRcvQueue::getNewEntry() { - CGuard listguard(m_IDLock); + ScopedLock listguard(m_IDLock); if (m_vNewEntry.empty()) return NULL; - CUDT *u = (CUDT *)*(m_vNewEntry.begin()); + CUDT* u = (CUDT*)*(m_vNewEntry.begin()); m_vNewEntry.erase(m_vNewEntry.begin()); return u; } -void CRcvQueue::storePkt(int32_t id, CPacket *pkt) +void srt::CRcvQueue::storePkt(int32_t id, CPacket* pkt) { - CGuard bufferlock(m_PassLock); + CUniqueSync passcond(m_BufferLock, m_BufferCond); - map >::iterator i = m_mBuffer.find(id); + map >::iterator i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { m_mBuffer[id].push(pkt); - pthread_cond_signal(&m_PassCond); + passcond.notify_one(); } else { @@ -1682,3 +1746,17 @@ void CRcvQueue::storePkt(int32_t id, CPacket *pkt) i->second.push(pkt); } } + +void srt::CMultiplexer::destroy() +{ + // Reverse order of the assigned + delete m_pRcvQueue; + delete m_pSndQueue; + delete m_pTimer; + + if (m_pChannel) + { + m_pChannel->close(); + delete m_pChannel; + } +} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/queue.h b/trunk/3rdparty/srt-1-fit/srtcore/queue.h index 6b93bf6c45..51292e43d7 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/queue.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/queue.h @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -50,13 +50,12 @@ modified by Haivision Systems Inc. *****************************************************************************/ +#ifndef INC_SRT_QUEUE_H +#define INC_SRT_QUEUE_H -#ifndef __UDT_QUEUE_H__ -#define __UDT_QUEUE_H__ - -#include "channel.h" #include "common.h" #include "packet.h" +#include "socketconfig.h" #include "netinet_any.h" #include "utilities.h" #include @@ -64,485 +63,548 @@ modified by #include #include +namespace srt +{ +class CChannel; class CUDT; struct CUnit { - CPacket m_Packet; // packet - enum Flag { FREE = 0, GOOD = 1, PASSACK = 2, DROPPED = 3 }; - Flag m_iFlag; // 0: free, 1: occupied, 2: msg read but not freed (out-of-order), 3: msg dropped + CPacket m_Packet; // packet + enum Flag + { + FREE = 0, + GOOD = 1, + PASSACK = 2, + DROPPED = 3 + }; + + // TODO: The new RcvBuffer allows to use atomic_bool here. + sync::atomic m_iFlag; // 0: free, 1: occupied, 2: msg read but not freed (out-of-order), 3: msg dropped }; class CUnitQueue { - public: + /// @brief Construct a unit queue. + /// @param mss Initial number of units to allocate. + /// @param mss Maximum segment size meaning the size of each unit. + /// @throws CUDTException SRT_ENOBUF. + CUnitQueue(int initNumUnits, int mss); + ~CUnitQueue(); - CUnitQueue(); - ~CUnitQueue(); - -public: // Storage size operations - - /// Initialize the unit queue. - /// @param [in] size queue size - /// @param [in] mss maximum segment size - /// @param [in] version IP version - /// @return 0: success, -1: failure. - - int init(int size, int mss, int version); - - /// Increase (double) the unit queue size. - /// @return 0: success, -1: failure. - - int increase(); - - /// Decrease (halve) the unit queue size. - /// @return 0: success, -1: failure. - - int shrink(); - -public: // Operations on units - - /// find an available unit for incoming packet. - /// @return Pointer to the available unit, NULL if not found. - - CUnit* getNextAvailUnit(); - - - void makeUnitFree(CUnit * unit); - - void makeUnitGood(CUnit * unit); +public: + int capacity() const { return m_iSize; } + int size() const { return m_iSize - m_iNumTaken; } public: + /// @brief Find an available unit for incoming packet. Allocate new units if 90% or more are in use. + /// @note This function is not thread-safe. Currently only CRcvQueue::worker thread calls it, thus + /// it is not an issue. However, must be protected if used from several threads in the future. + /// @return Pointer to the available unit, NULL if not found. + CUnit* getNextAvailUnit(); - inline int getIPversion() const { return m_iIPversion; } + void makeUnitFree(CUnit* unit); + void makeUnitGood(CUnit* unit); private: - struct CQEntry - { - CUnit* m_pUnit; // unit queue - char* m_pBuffer; // data buffer - int m_iSize; // size of each queue - - CQEntry* m_pNext; - } - *m_pQEntry, // pointer to the first unit queue - *m_pCurrQueue, // pointer to the current available queue - *m_pLastQueue; // pointer to the last unit queue + struct CQEntry + { + CUnit* m_pUnit; // unit queue + char* m_pBuffer; // data buffer + int m_iSize; // size of each queue + + CQEntry* m_pNext; + }; + + /// Increase the unit queue size (by @a m_iBlockSize units). + /// Uses m_mtx to protect access and changes of the queue state. + /// @return 0: success, -1: failure. + int increase_(); + + /// @brief Allocated a CQEntry of iNumUnits with each unit of mss bytes. + /// @param iNumUnits a number of units to allocate + /// @param mss the size of each unit in bytes. + /// @return a pointer to a newly allocated entry on success, NULL otherwise. + static CQEntry* allocateEntry(const int iNumUnits, const int mss); - CUnit* m_pAvailUnit; // recent available unit - - int m_iSize; // total size of the unit queue, in number of packets - int m_iCount; // total number of valid packets in the queue - - int m_iMSS; // unit buffer size - int m_iIPversion; // IP version +private: + CQEntry* m_pQEntry; // pointer to the first unit queue + CQEntry* m_pCurrQueue; // pointer to the current available queue + CQEntry* m_pLastQueue; // pointer to the last unit queue + CUnit* m_pAvailUnit; // recent available unit + int m_iSize; // total size of the unit queue, in number of packets + sync::atomic m_iNumTaken; // total number of valid (occupied) packets in the queue + const int m_iMSS; // unit buffer size + const int m_iBlockSize; // Number of units in each CQEntry. private: - CUnitQueue(const CUnitQueue&); - CUnitQueue& operator=(const CUnitQueue&); + CUnitQueue(const CUnitQueue&); + CUnitQueue& operator=(const CUnitQueue&); }; struct CSNode { - CUDT* m_pUDT; // Pointer to the instance of CUDT socket - uint64_t m_llTimeStamp_tk; // Time Stamp + CUDT* m_pUDT; // Pointer to the instance of CUDT socket + sync::steady_clock::time_point m_tsTimeStamp; - int m_iHeapLoc; // location on the heap, -1 means not on the heap + sync::atomic m_iHeapLoc; // location on the heap, -1 means not on the heap }; class CSndUList { -friend class CSndQueue; - public: - CSndUList(); - ~CSndUList(); + CSndUList(sync::CTimer* pTimer); + ~CSndUList(); public: + enum EReschedule + { + DONT_RESCHEDULE = 0, + DO_RESCHEDULE = 1 + }; - enum EReschedule { DONT_RESCHEDULE = 0, DO_RESCHEDULE = 1 }; - - static EReschedule rescheduleIf(bool cond) { return cond ? DO_RESCHEDULE : DONT_RESCHEDULE; } + static EReschedule rescheduleIf(bool cond) { return cond ? DO_RESCHEDULE : DONT_RESCHEDULE; } - /// Update the timestamp of the UDT instance on the list. - /// @param [in] u pointer to the UDT instance - /// @param [in] reschedule if the timestamp should be rescheduled + /// Update the timestamp of the UDT instance on the list. + /// @param [in] u pointer to the UDT instance + /// @param [in] reschedule if the timestamp should be rescheduled + /// @param [in] ts the next time to trigger sending logic on the CUDT + void update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts = sync::steady_clock::now()); - void update(const CUDT* u, EReschedule reschedule); + /// Retrieve the next (in time) socket from the heap to process its sending request. + /// @return a pointer to CUDT instance to process next. + CUDT* pop(); - /// Retrieve the next packet and peer address from the first entry, and reschedule it in the queue. - /// @param [out] addr destination address of the next packet - /// @param [out] pkt the next packet to be sent - /// @return 1 if successfully retrieved, -1 if no packet found. + /// Remove UDT instance from the list. + /// @param [in] u pointer to the UDT instance + void remove(const CUDT* u);// EXCLUDES(m_ListLock); - int pop(sockaddr*& addr, CPacket& pkt); + /// Retrieve the next scheduled processing time. + /// @return Scheduled processing time of the first UDT socket in the list. + sync::steady_clock::time_point getNextProcTime(); - /// Remove UDT instance from the list. - /// @param [in] u pointer to the UDT instance + /// Wait for the list to become non empty. + void waitNonEmpty() const; - void remove(const CUDT* u); - - /// Retrieve the next scheduled processing time. - /// @return Scheduled processing time of the first UDT socket in the list. - - uint64_t getNextProcTime(); + /// Signal to stop waiting in waitNonEmpty(). + void signalInterrupt() const; private: - - /// Doubles the size of the list. - /// - void realloc_(); - - /// Insert a new UDT instance into the list with realloc if required. - /// - /// @param [in] ts time stamp: next processing time - /// @param [in] u pointer to the UDT instance - void insert_(int64_t ts, const CUDT* u); - - /// Insert a new UDT instance into the list without realloc. - /// Should be called if there is a gauranteed space for the element. - /// - /// @param [in] ts time stamp: next processing time - /// @param [in] u pointer to the UDT instance - void insert_norealloc_(int64_t ts, const CUDT* u); - - void remove_(const CUDT* u); + /// Doubles the size of the list. + /// + void realloc_();// REQUIRES(m_ListLock); + + /// Insert a new UDT instance into the list with realloc if required. + /// + /// @param [in] ts time stamp: next processing time + /// @param [in] u pointer to the UDT instance + void insert_(const sync::steady_clock::time_point& ts, const CUDT* u); + + /// Insert a new UDT instance into the list without realloc. + /// Should be called if there is a gauranteed space for the element. + /// + /// @param [in] ts time stamp: next processing time + /// @param [in] u pointer to the UDT instance + void insert_norealloc_(const sync::steady_clock::time_point& ts, const CUDT* u);// REQUIRES(m_ListLock); + + /// Removes CUDT entry from the list. + /// If the last entry is removed, calls sync::CTimer::interrupt(). + void remove_(const CUDT* u); private: - CSNode** m_pHeap; // The heap array - int m_iArrayLength; // physical length of the array - int m_iLastEntry; // position of last entry on the heap array + CSNode** m_pHeap; // The heap array + int m_iArrayLength; // physical length of the array + int m_iLastEntry; // position of last entry on the heap array or -1 if empty. - pthread_mutex_t m_ListLock; + mutable sync::Mutex m_ListLock; // Protects the list (m_pHeap, m_iArrayLength, m_iLastEntry). + mutable sync::Condition m_ListCond; - pthread_mutex_t* m_pWindowLock; - pthread_cond_t* m_pWindowCond; - - CTimer* m_pTimer; + sync::CTimer* const m_pTimer; private: - CSndUList(const CSndUList&); - CSndUList& operator=(const CSndUList&); + CSndUList(const CSndUList&); + CSndUList& operator=(const CSndUList&); }; struct CRNode { - CUDT* m_pUDT; // Pointer to the instance of CUDT socket - uint64_t m_llTimeStamp_tk; // Time Stamp + CUDT* m_pUDT; // Pointer to the instance of CUDT socket + sync::steady_clock::time_point m_tsTimeStamp; // Time Stamp - CRNode* m_pPrev; // previous link - CRNode* m_pNext; // next link + CRNode* m_pPrev; // previous link + CRNode* m_pNext; // next link - bool m_bOnList; // if the node is already on the list + sync::atomic m_bOnList; // if the node is already on the list }; class CRcvUList { public: - CRcvUList(); - ~CRcvUList(); + CRcvUList(); + ~CRcvUList(); public: + /// Insert a new UDT instance to the list. + /// @param [in] u pointer to the UDT instance - /// Insert a new UDT instance to the list. - /// @param [in] u pointer to the UDT instance - - void insert(const CUDT* u); + void insert(const CUDT* u); - /// Remove the UDT instance from the list. - /// @param [in] u pointer to the UDT instance + /// Remove the UDT instance from the list. + /// @param [in] u pointer to the UDT instance - void remove(const CUDT* u); + void remove(const CUDT* u); - /// Move the UDT instance to the end of the list, if it already exists; otherwise, do nothing. - /// @param [in] u pointer to the UDT instance + /// Move the UDT instance to the end of the list, if it already exists; otherwise, do nothing. + /// @param [in] u pointer to the UDT instance - void update(const CUDT* u); + void update(const CUDT* u); public: - CRNode* m_pUList; // the head node + CRNode* m_pUList; // the head node private: - CRNode* m_pLast; // the last node + CRNode* m_pLast; // the last node private: - CRcvUList(const CRcvUList&); - CRcvUList& operator=(const CRcvUList&); + CRcvUList(const CRcvUList&); + CRcvUList& operator=(const CRcvUList&); }; class CHash { public: - CHash(); - ~CHash(); + CHash(); + ~CHash(); public: + /// Initialize the hash table. + /// @param [in] size hash table size - /// Initialize the hash table. - /// @param [in] size hash table size - - void init(int size); + void init(int size); - /// Look for a UDT instance from the hash table. - /// @param [in] id socket ID - /// @return Pointer to a UDT instance, or NULL if not found. + /// Look for a UDT instance from the hash table. + /// @param [in] id socket ID + /// @return Pointer to a UDT instance, or NULL if not found. - CUDT* lookup(int32_t id); + CUDT* lookup(int32_t id); - /// Insert an entry to the hash table. - /// @param [in] id socket ID - /// @param [in] u pointer to the UDT instance + /// Insert an entry to the hash table. + /// @param [in] id socket ID + /// @param [in] u pointer to the UDT instance - void insert(int32_t id, CUDT* u); + void insert(int32_t id, CUDT* u); - /// Remove an entry from the hash table. - /// @param [in] id socket ID + /// Remove an entry from the hash table. + /// @param [in] id socket ID - void remove(int32_t id); + void remove(int32_t id); private: - struct CBucket - { - int32_t m_iID; // Socket ID - CUDT* m_pUDT; // Socket instance + struct CBucket + { + int32_t m_iID; // Socket ID + CUDT* m_pUDT; // Socket instance - CBucket* m_pNext; // next bucket - } **m_pBucket; // list of buckets (the hash table) + CBucket* m_pNext; // next bucket + } * *m_pBucket; // list of buckets (the hash table) - int m_iHashSize; // size of hash table + int m_iHashSize; // size of hash table private: - CHash(const CHash&); - CHash& operator=(const CHash&); + CHash(const CHash&); + CHash& operator=(const CHash&); }; +/// @brief A queue of sockets pending for connection. +/// It can be either a caller socket in a non-blocking mode +/// (the connection has to be handled in background), +/// or a socket in rendezvous connection mode. class CRendezvousQueue { public: - CRendezvousQueue(); - ~CRendezvousQueue(); + CRendezvousQueue(); + ~CRendezvousQueue(); public: - void insert(const SRTSOCKET& id, CUDT* u, int ipv, const sockaddr* addr, uint64_t ttl); + /// @brief Insert a new socket pending for connection (non-blocking caller or rendezvous). + /// @param id socket ID. + /// @param u pointer to a corresponding CUDT instance. + /// @param addr remote address to connect to. + /// @param ttl timepoint for connection attempt to expire. + void insert(const SRTSOCKET& id, CUDT* u, const sockaddr_any& addr, const srt::sync::steady_clock::time_point& ttl); + + /// @brief Remove a socket from the connection pending list. + /// @param id socket ID. + void remove(const SRTSOCKET& id); + + /// @brief Locate a socket in the connection pending queue. + /// @param addr source address of the packet received over UDP (peer address). + /// @param id socket ID. + /// @return a pointer to CUDT instance retrieved, or NULL if nothing was found. + CUDT* retrieve(const sockaddr_any& addr, SRTSOCKET& id) const; + + /// @brief Update status of connections in the pending queue. + /// Stop connecting if TTL expires. Resend handshake request every 250 ms if no response from the peer. + /// @param rst result of reading from a UDP socket: received packet / nothin read / read error. + /// @param cst target status for pending connection: reject or proceed. + /// @param pktIn packet received from the UDP socket. + void updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit); - // The should_lock parameter is given here to state as to whether - // the lock should be applied here. If called from some internals - // and the lock IS ALREADY APPLIED, use false here to prevent - // double locking and deadlock in result. - void remove(const SRTSOCKET& id, bool should_lock); - CUDT* retrieve(const sockaddr* addr, ref_t id); - - void updateConnStatus(EReadStatus rst, EConnectStatus, const CPacket& response); +private: + struct LinkStatusInfo + { + CUDT* u; + SRTSOCKET id; + int errorcode; + sockaddr_any peeraddr; + int token; + + struct HasID + { + SRTSOCKET id; + HasID(SRTSOCKET p) + : id(p) + { + } + bool operator()(const LinkStatusInfo& i) { return i.id == id; } + }; + }; + + /// @brief Qualify pending connections: + /// - Sockets with expired TTL go to the 'to_remove' list and removed from the queue straight away. + /// - If HS request is to be resent (resend 250 ms if no response from the peer) go to the 'to_process' list. + /// + /// @param rst result of reading from a UDP socket: received packet / nothin read / read error. + /// @param cst target status for pending connection: reject or proceed. + /// @param iDstSockID destination socket ID of the received packet. + /// @param[in,out] toRemove stores sockets with expired TTL. + /// @param[in,out] toProcess stores sockets which should repeat (resend) HS connection request. + bool qualifyToHandle(EReadStatus rst, + EConnectStatus cst, + int iDstSockID, + std::vector& toRemove, + std::vector& toProcess); private: - struct CRL - { - SRTSOCKET m_iID; // UDT socket ID (self) - CUDT* m_pUDT; // UDT instance - int m_iIPversion; // IP version - sockaddr* m_pPeerAddr; // UDT sonnection peer address - uint64_t m_ullTTL; // the time that this request expires - }; - std::list m_lRendezvousID; // The sockets currently in rendezvous mode - - pthread_mutex_t m_RIDVectorLock; + struct CRL + { + SRTSOCKET m_iID; // SRT socket ID (self) + CUDT* m_pUDT; // CUDT instance + sockaddr_any m_PeerAddr; // SRT sonnection peer address + sync::steady_clock::time_point m_tsTTL; // the time that this request expires + }; + std::list m_lRendezvousID; // The sockets currently in rendezvous mode + + mutable sync::Mutex m_RIDListLock; }; class CSndQueue { -friend class CUDT; -friend class CUDTUnited; + friend class CUDT; + friend class CUDTUnited; public: - CSndQueue(); - ~CSndQueue(); + CSndQueue(); + ~CSndQueue(); public: + // XXX There's currently no way to access the socket ID set for + // whatever the queue is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } - // XXX There's currently no way to access the socket ID set for - // whatever the queue is currently working for. Required to find - // some way to do this, possibly by having a "reverse pointer". - // Currently just "unimplemented". - std::string CONID() const { return ""; } + /// Initialize the sending queue. + /// @param [in] c UDP channel to be associated to the queue + /// @param [in] t Timer - /// Initialize the sending queue. - /// @param [in] c UDP channel to be associated to the queue - /// @param [in] t Timer + void init(CChannel* c, sync::CTimer* t); - void init(CChannel* c, CTimer* t); + /// Send out a packet to a given address. + /// @param [in] addr destination address + /// @param [in] packet packet to be sent out + /// @return Size of data sent out. - /// Send out a packet to a given address. - /// @param [in] addr destination address - /// @param [in] packet packet to be sent out - /// @return Size of data sent out. + int sendto(const sockaddr_any& addr, CPacket& packet); - int sendto(const sockaddr* addr, CPacket& packet); + /// Get the IP TTL. + /// @param [in] ttl IP Time To Live. + /// @return TTL. -#ifdef SRT_ENABLE_IPOPTS - /// Get the IP TTL. - /// @param [in] ttl IP Time To Live. - /// @return TTL. + int getIpTTL() const; - int getIpTTL() const; + /// Get the IP Type of Service. + /// @return ToS. - /// Get the IP Type of Service. - /// @return ToS. + int getIpToS() const; - int getIpToS() const; +#ifdef SRT_ENABLE_BINDTODEVICE + bool getBind(char* dst, size_t len) const; #endif - int ioctlQuery(int type) const { return m_pChannel->ioctlQuery(type); } - int sockoptQuery(int level, int type) const { return m_pChannel->sockoptQuery(level, type); } + int ioctlQuery(int type) const; + int sockoptQuery(int level, int type) const; - void setClosing() - { - m_bClosing = true; - } + void setClosing() { m_bClosing = true; } private: - static void* worker(void* param); - pthread_t m_WorkerThread; - + static void* worker(void* param); + sync::CThread m_WorkerThread; private: - CSndUList* m_pSndUList; // List of UDT instances for data sending - CChannel* m_pChannel; // The UDP channel for data sending - CTimer* m_pTimer; // Timing facility + CSndUList* m_pSndUList; // List of UDT instances for data sending + CChannel* m_pChannel; // The UDP channel for data sending + sync::CTimer* m_pTimer; // Timing facility - pthread_mutex_t m_WindowLock; - pthread_cond_t m_WindowCond; + sync::atomic m_bClosing; // closing the worker - volatile bool m_bClosing; // closing the worker - -#if defined(SRT_DEBUG_SNDQ_HIGHRATE)//>>debug high freq worker - uint64_t m_ullDbgPeriod; - uint64_t m_ullDbgTime; - struct { +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) //>>debug high freq worker + uint64_t m_ullDbgPeriod; + uint64_t m_ullDbgTime; + struct + { unsigned long lIteration; // - unsigned long lSleepTo; //SleepTo - unsigned long lNotReadyPop; //Continue + unsigned long lSleepTo; // SleepTo + unsigned long lNotReadyPop; // Continue unsigned long lSendTo; - unsigned long lNotReadyTs; - unsigned long lCondWait; //block on m_WindowCond - } m_WorkerStats; + unsigned long lNotReadyTs; + unsigned long lCondWait; // block on m_WindowCond + } m_WorkerStats; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ +#if ENABLE_LOGGING + static int m_counter; +#endif + private: - CSndQueue(const CSndQueue&); - CSndQueue& operator=(const CSndQueue&); + CSndQueue(const CSndQueue&); + CSndQueue& operator=(const CSndQueue&); }; class CRcvQueue { -friend class CUDT; -friend class CUDTUnited; + friend class CUDT; + friend class CUDTUnited; public: - CRcvQueue(); - ~CRcvQueue(); + CRcvQueue(); + ~CRcvQueue(); public: + // XXX There's currently no way to access the socket ID set for + // whatever the queue is currently working. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } - // XXX There's currently no way to access the socket ID set for - // whatever the queue is currently working. Required to find - // some way to do this, possibly by having a "reverse pointer". - // Currently just "unimplemented". - std::string CONID() const { return ""; } + /// Initialize the receiving queue. + /// @param [in] size queue size + /// @param [in] mss maximum packet size + /// @param [in] version IP version + /// @param [in] hsize hash table size + /// @param [in] c UDP channel to be associated to the queue + /// @param [in] t timer + void init(int size, size_t payload, int version, int hsize, CChannel* c, sync::CTimer* t); - /// Initialize the receiving queue. - /// @param [in] size queue size - /// @param [in] mss maximum packet size - /// @param [in] version IP version - /// @param [in] hsize hash table size - /// @param [in] c UDP channel to be associated to the queue - /// @param [in] t timer + /// Read a packet for a specific UDT socket id. + /// @param [in] id Socket ID + /// @param [out] packet received packet + /// @return Data size of the packet + int recvfrom(int32_t id, CPacket& to_packet); - void init(int size, int payload, int version, int hsize, CChannel* c, CTimer* t); + void stopWorker(); - /// Read a packet for a specific UDT socket id. - /// @param [in] id Socket ID - /// @param [out] packet received packet - /// @return Data size of the packet + void setClosing() { m_bClosing = true; } - int recvfrom(int32_t id, ref_t packet); - - void setClosing() - { - m_bClosing = true; - } + int getIPversion() { return m_iIPversion; } private: - static void* worker(void* param); - pthread_t m_WorkerThread; - // Subroutines of worker - EReadStatus worker_RetrieveUnit(ref_t id, ref_t unit, sockaddr* sa); - EConnectStatus worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* sa); - EConnectStatus worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr* sa); - EConnectStatus worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr* sa); + static void* worker(void* param); + sync::CThread m_WorkerThread; + // Subroutines of worker + EReadStatus worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr_any& sa); + EConnectStatus worker_ProcessConnectionRequest(CUnit* unit, const sockaddr_any& sa); + EConnectStatus worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& sa); + EConnectStatus worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& sa); private: - CUnitQueue m_UnitQueue; // The received packet queue - - CRcvUList* m_pRcvUList; // List of UDT instances that will read packets from the queue - CHash* m_pHash; // Hash table for UDT socket looking up - CChannel* m_pChannel; // UDP channel for receving packets - CTimer* m_pTimer; // shared timer with the snd queue - - int m_iPayloadSize; // packet payload size - - volatile bool m_bClosing; // closing the worker + CUnitQueue* m_pUnitQueue; // The received packet queue + CRcvUList* m_pRcvUList; // List of UDT instances that will read packets from the queue + CHash* m_pHash; // Hash table for UDT socket looking up + CChannel* m_pChannel; // UDP channel for receving packets + sync::CTimer* m_pTimer; // shared timer with the snd queue + + int m_iIPversion; // IP version + size_t m_szPayloadSize; // packet payload size + + sync::atomic m_bClosing; // closing the worker +#if ENABLE_LOGGING + static srt::sync::atomic m_counter; // A static counter to log RcvQueue worker thread number. +#endif private: - int setListener(CUDT* u); - void removeListener(const CUDT* u); + int setListener(CUDT* u); + void removeListener(const CUDT* u); - void registerConnector(const SRTSOCKET& id, CUDT* u, int ipv, const sockaddr* addr, uint64_t ttl); - void removeConnector(const SRTSOCKET& id, bool should_lock = true); + void registerConnector(const SRTSOCKET& id, + CUDT* u, + const sockaddr_any& addr, + const sync::steady_clock::time_point& ttl); + void removeConnector(const SRTSOCKET& id); - void setNewEntry(CUDT* u); - bool ifNewEntry(); - CUDT* getNewEntry(); + void setNewEntry(CUDT* u); + bool ifNewEntry(); + CUDT* getNewEntry(); - void storePkt(int32_t id, CPacket* pkt); + void storePkt(int32_t id, CPacket* pkt); private: - pthread_mutex_t m_LSLock; - CUDT* m_pListener; // pointer to the (unique, if any) listening UDT entity - CRendezvousQueue* m_pRendezvousQueue; // The list of sockets in rendezvous mode + sync::Mutex m_LSLock; + CUDT* m_pListener; // pointer to the (unique, if any) listening UDT entity + CRendezvousQueue* m_pRendezvousQueue; // The list of sockets in rendezvous mode - std::vector m_vNewEntry; // newly added entries, to be inserted - pthread_mutex_t m_IDLock; + std::vector m_vNewEntry; // newly added entries, to be inserted + sync::Mutex m_IDLock; - std::map > m_mBuffer; // temporary buffer for rendezvous connection request - pthread_mutex_t m_PassLock; - pthread_cond_t m_PassCond; + std::map > m_mBuffer; // temporary buffer for rendezvous connection request + sync::Mutex m_BufferLock; + sync::Condition m_BufferCond; private: - CRcvQueue(const CRcvQueue&); - CRcvQueue& operator=(const CRcvQueue&); + CRcvQueue(const CRcvQueue&); + CRcvQueue& operator=(const CRcvQueue&); }; struct CMultiplexer { - CSndQueue* m_pSndQueue; // The sending queue - CRcvQueue* m_pRcvQueue; // The receiving queue - CChannel* m_pChannel; // The UDP channel for sending and receiving - CTimer* m_pTimer; // The timer - - int m_iPort; // The UDP port number of this multiplexer - int m_iIPversion; // IP version -#ifdef SRT_ENABLE_IPOPTS - int m_iIpTTL; - int m_iIpToS; -#endif - int m_iMSS; // Maximum Segment Size - int m_iRefCount; // number of UDT instances that are associated with this multiplexer - int m_iIpV6Only; // IPV6_V6ONLY option - bool m_bReusable; // if this one can be shared with others - - int m_iID; // multiplexer ID + CSndQueue* m_pSndQueue; // The sending queue + CRcvQueue* m_pRcvQueue; // The receiving queue + CChannel* m_pChannel; // The UDP channel for sending and receiving + sync::CTimer* m_pTimer; // The timer + + int m_iPort; // The UDP port number of this multiplexer + int m_iIPversion; // Address family (AF_INET or AF_INET6) + int m_iRefCount; // number of UDT instances that are associated with this multiplexer + + CSrtMuxerConfig m_mcfg; + + int m_iID; // multiplexer ID + + // Constructor should reset all pointers to NULL + // to prevent dangling pointer when checking for memory alloc fails + CMultiplexer() + : m_pSndQueue(NULL) + , m_pRcvQueue(NULL) + , m_pChannel(NULL) + , m_pTimer(NULL) + { + } + + void destroy(); }; +} // namespace srt + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.cpp b/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.cpp new file mode 100644 index 0000000000..a49d1d639e --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.cpp @@ -0,0 +1,1020 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2018 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Haivision Systems Inc. +*****************************************************************************/ +#include + +#include "srt.h" +#include "socketconfig.h" + +using namespace srt; +extern const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION); + +namespace { +typedef void setter_function(CSrtConfig& co, const void* optval, int optlen); + +template +struct CSrtConfigSetter +{ + static setter_function set; +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + int ival = cast_optval(optval, optlen); + if (ival < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iMSS = ival; + + // Packet size cannot be greater than UDP buffer size + if (co.iMSS > co.iUDPSndBufSize) + co.iMSS = co.iUDPSndBufSize; + if (co.iMSS > co.iUDPRcvBufSize) + co.iMSS = co.iUDPRcvBufSize; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + const int fc = cast_optval(optval, optlen); + if (fc < co.DEF_MIN_FLIGHT_PKT) + { + LOGC(kmlog.Error, log << "SRTO_FC: minimum allowed value is 32 (provided: " << fc << ")"); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + + co.iFlightFlagSize = fc; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + int bs = cast_optval(optval, optlen); + if (bs <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iSndBufSize = bs / (co.iMSS - CPacket::UDP_HDR_SIZE); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + // Mimimum recv buffer size is 32 packets + const int mssin_size = co.iMSS - CPacket::UDP_HDR_SIZE; + + if (val > mssin_size * co.DEF_MIN_FLIGHT_PKT) + co.iRcvBufSize = val / mssin_size; + else + co.iRcvBufSize = co.DEF_MIN_FLIGHT_PKT; + + // recv buffer MUST not be greater than FC size + if (co.iRcvBufSize > co.iFlightFlagSize) + co.iRcvBufSize = co.iFlightFlagSize; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.Linger = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.iUDPSndBufSize = std::max(co.iMSS, cast_optval(optval, optlen)); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.iUDPRcvBufSize = std::max(co.iMSS, cast_optval(optval, optlen)); + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bRendezvous = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iSndTimeOut = val; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iRcvTimeOut = val; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bSynSending = cast_optval(optval, optlen); + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bSynRecving = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bReuseAddr = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int64_t val = cast_optval(optval, optlen); + if (val < -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.llMaxBW = val; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + int val = cast_optval(optval, optlen); + if (!(val == -1) && !((val >= 1) && (val <= 255))) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + co.iIpTTL = cast_optval(optval); + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.iIpToS = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; +#ifdef SRT_ENABLE_BINDTODEVICE + using namespace std; + + string val; + if (optlen == -1) + val = (const char *)optval; + else + val.assign((const char *)optval, optlen); + if (val.size() >= IFNAMSIZ) + { + LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE: device name too long (max: IFNAMSIZ=" << IFNAMSIZ << ")"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + co.sBindToDevice = val; +#else + (void)co; // prevent warning + (void)optval; + (void)optlen; + LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); +#endif + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int64_t val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + co.llInputBW = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int64_t val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + co.llMinInputBW = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int32_t val = cast_optval(optval, optlen); + if (val < 5 || val > 100) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + co.iOverheadBW = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bDataSender = cast_optval(optval, optlen); + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bTSBPD = cast_optval(optval, optlen); + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iRcvLatency = val; + co.iPeerLatency = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iRcvLatency = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iPeerLatency = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bTLPktDrop = cast_optval(optval, optlen); + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iSndDropDelay = val; + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; +#ifdef SRT_ENABLE_ENCRYPTION + // Password must be 10-80 characters. + // Or it can be empty to clear the password. + if ((optlen != 0) && (optlen < 10 || optlen > HAICRYPT_SECRET_MAX_SZ)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + memset(&co.CryptoSecret, 0, sizeof(co.CryptoSecret)); + co.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + co.CryptoSecret.len = (optlen <= (int)sizeof(co.CryptoSecret.str) ? optlen : (int)sizeof(co.CryptoSecret.str)); + memcpy((co.CryptoSecret.str), optval, co.CryptoSecret.len); +#else + (void)co; // prevent warning + (void)optval; + if (optlen == 0) + return; // Allow to set empty passphrase if no encryption supported. + + LOGC(aclog.Error, log << "SRTO_PASSPHRASE: encryption not enabled at compile time"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); +#endif + } +}; +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; +#ifdef SRT_ENABLE_ENCRYPTION + const int v = cast_optval(optval, optlen); + int const allowed[4] = { + 0, // Default value, if this results for initiator, defaults to 16. See below. + 16, // AES-128 + 24, // AES-192 + 32 // AES-256 + }; + const int *const allowed_end = allowed + 4; + if (std::find(allowed, allowed_end, v) == allowed_end) + { + LOGC(aclog.Error, + log << "Invalid value for option SRTO_PBKEYLEN: " << v << "; allowed are: 0, 16, 24, 32"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // Note: This works a little different in HSv4 and HSv5. + + // HSv4: + // The party that is set SRTO_SENDER will send KMREQ, and it will + // use default value 16, if SRTO_PBKEYLEN is the default value 0. + // The responder that receives KMRSP has nothing to say about + // PBKEYLEN anyway and it will take the length of the key from + // the initiator (sender) as a good deal. + // + // HSv5: + // The initiator (independently on the sender) will send KMREQ, + // and as it should be the sender to decide about the PBKEYLEN. + // Your application should do the following then: + // 1. The sender should set PBKEYLEN to the required value. + // 2. If the sender is initiator, it will create the key using + // its preset PBKEYLEN (or default 16, if not set) and the + // receiver-responder will take it as a good deal. + // 3. Leave the PBKEYLEN value on the receiver as default 0. + // 4. If sender is responder, it should then advertise the PBKEYLEN + // value in the initial handshake messages (URQ_INDUCTION if + // listener, and both URQ_WAVEAHAND and URQ_CONCLUSION in case + // of rendezvous, as it is the matter of luck who of them will + // eventually become the initiator). This way the receiver + // being an initiator will set iSndCryptoKeyLen before setting + // up KMREQ for sending to the sender-responder. + // + // Note that in HSv5 if both sides set PBKEYLEN, the responder + // wins, unless the initiator is a sender (the effective PBKEYLEN + // will be the one advertised by the responder). If none sets, + // PBKEYLEN will default to 16. + + co.iSndCryptoKeyLen = v; +#else + (void)co; // prevent warning + (void)optval; + (void)optlen; + LOGC(aclog.Error, log << "SRTO_PBKEYLEN: encryption not enabled at compile time"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); +#endif + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bRcvNakReport = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + using namespace sync; + co.tdConnTimeOut = milliseconds_from(val); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bDriftTracer = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.iMaxReorderTolerance = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.uSrtVersion = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.uMinimumPeerSrtVersion = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + if (size_t(optlen) > CSrtConfig::MAX_SID_LENGTH) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.sStreamName.set((const char*)optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + std::string val; + if (optlen == -1) + val = (const char*)optval; + else + val.assign((const char*)optval, optlen); + + // Translate alias + if (val == "vod") + val = "file"; + + bool res = SrtCongestion::exists(val); + if (!res) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.sCongestion.set(val); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bMessageAPI = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + const int val = cast_optval(optval, optlen); + if (val < 0) + { + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + if (val > SRT_LIVE_MAX_PLSIZE) + { + LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE, maximum payload per MTU."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + if (!co.sPacketFilterConfig.empty()) + { + // This means that the filter might have been installed before, + // and the fix to the maximum payload size was already applied. + // This needs to be checked now. + SrtFilterConfig fc; + if (!ParseFilterConfig(co.sPacketFilterConfig.str(), fc)) + { + // Break silently. This should not happen + LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + const size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; + if (size_t(val) > efc_max_payload_size) + { + LOGC(aclog.Error, + log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE decreased by " << fc.extra_size + << " required for packet filter header"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + } + + co.zExpPayloadSize = val; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + // XXX Note that here the configuration for SRTT_LIVE + // is the same as DEFAULT VALUES for these fields set + // in CUDT::CUDT. + switch (cast_optval(optval, optlen)) + { + case SRTT_LIVE: + // Default live options: + // - tsbpd: on + // - latency: 120ms + // - linger: off + // - congctl: live + // - extraction method: message (reading call extracts one message) + co.bTSBPD = true; + co.iRcvLatency = SRT_LIVE_DEF_LATENCY_MS; + co.iPeerLatency = 0; + co.bTLPktDrop = true; + co.iSndDropDelay = 0; + co.bMessageAPI = true; + co.bRcvNakReport = true; + co.iRetransmitAlgo = 1; + co.zExpPayloadSize = SRT_LIVE_DEF_PLSIZE; + co.Linger.l_onoff = 0; + co.Linger.l_linger = 0; + co.sCongestion.set("live", 4); + break; + + case SRTT_FILE: + // File transfer mode: + // - tsbpd: off + // - latency: 0 + // - linger: on + // - congctl: file (original UDT congestion control) + // - extraction method: stream (reading call extracts as many bytes as available and fits in buffer) + co.bTSBPD = false; + co.iRcvLatency = 0; + co.iPeerLatency = 0; + co.bTLPktDrop = false; + co.iSndDropDelay = -1; + co.bMessageAPI = false; + co.bRcvNakReport = false; + co.iRetransmitAlgo = 0; + co.zExpPayloadSize = 0; // use maximum + co.Linger.l_onoff = 1; + co.Linger.l_linger = CSrtConfig::DEF_LINGER_S; + co.sCongestion.set("file", 4); + break; + + default: + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + } +}; + +#if ENABLE_BONDING +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.iGroupConnect = cast_optval(optval, optlen); + } +}; +#endif + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + + const int val = cast_optval(optval, optlen); + if (val < 0) + { + LOGC(aclog.Error, + log << "SRTO_KMREFRESHRATE=" << val << " can't be negative"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // Changing the KMREFRESHRATE sets KMPREANNOUNCE to the maximum allowed value + co.uKmRefreshRatePkt = (unsigned) val; + + if (co.uKmPreAnnouncePkt == 0 && co.uKmRefreshRatePkt == 0) + return; // Both values are default + + const unsigned km_preanno = co.uKmPreAnnouncePkt == 0 ? HAICRYPT_DEF_KM_PRE_ANNOUNCE : co.uKmPreAnnouncePkt; + const unsigned km_refresh = co.uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : co.uKmRefreshRatePkt; + + if (co.uKmPreAnnouncePkt == 0 || km_preanno > (km_refresh - 1) / 2) + { + co.uKmPreAnnouncePkt = (km_refresh - 1) / 2; + LOGC(aclog.Warn, + log << "SRTO_KMREFRESHRATE=0x" << std::hex << km_refresh << ": setting SRTO_KMPREANNOUNCE=0x" + << std::hex << co.uKmPreAnnouncePkt); + } + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + + const int val = cast_optval(optval, optlen); + if (val < 0) + { + LOGC(aclog.Error, + log << "SRTO_KMPREANNOUNCE=" << val << " can't be negative"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + const unsigned km_preanno = val == 0 ? HAICRYPT_DEF_KM_PRE_ANNOUNCE : val; + const unsigned kmref = co.uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : co.uKmRefreshRatePkt; + if (km_preanno > (kmref - 1) / 2) + { + LOGC(aclog.Error, + log << "SRTO_KMPREANNOUNCE=0x" << std::hex << km_preanno << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2) + << " - OPTION REJECTED."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + co.uKmPreAnnouncePkt = val; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.bEnforcedEnc = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iPeerIdleTimeout_ms = val; + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + co.iIpV6Only = cast_optval(optval, optlen); + } +}; + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + std::string arg((const char*)optval, optlen); + // Parse the configuration string prematurely + SrtFilterConfig fc; + PacketFilter::Factory* fax = 0; + if (!ParseFilterConfig(arg, (fc), (&fax))) + { + LOGC(aclog.Error, + log << "SRTO_PACKETFILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. " + "FILTERTYPE (" + << fc.type << ") must be installed (or builtin)"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + std::string error; + if (!fax->verifyConfig(fc, (error))) + { + LOGC(aclog.Error, log << "SRTO_PACKETFILTER: Incorrect config: " << error); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; + if (co.zExpPayloadSize > efc_max_payload_size) + { + LOGC(aclog.Warn, + log << "Due to filter-required extra " << fc.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to " + << efc_max_payload_size << " bytes"); + co.zExpPayloadSize = efc_max_payload_size; + } + + co.sPacketFilterConfig.set(arg); + } +}; + +#if ENABLE_BONDING +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + // This option is meaningless for the socket itself. + // It's set here just for the sake of setting it on a listener + // socket so that it is then applied on the group when a + // group connection is configured. + const int val_ms = cast_optval(optval, optlen); + const int min_timeo_ms = (int) CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS; + + if (val_ms < min_timeo_ms) + { + LOGC(qmlog.Error, + log << "group option: SRTO_GROUPMINSTABLETIMEO min allowed value is " + << min_timeo_ms << " ms."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + const int idletmo_ms = co.iPeerIdleTimeout_ms; + + if (val_ms > idletmo_ms) + { + LOGC(aclog.Error, log << "group option: SRTO_GROUPMINSTABLETIMEO(" << val_ms + << ") exceeds SRTO_PEERIDLETIMEO(" << idletmo_ms << ")"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + co.uMinStabilityTimeout_ms = val_ms; + LOGC(smlog.Error, log << "SRTO_GROUPMINSTABLETIMEO set " << val_ms); + } +}; +#endif + +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int val = cast_optval(optval, optlen); + if (val < 0 || val > 1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.iRetransmitAlgo = val; + } +}; + +int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int optlen) +{ + switch (optName) + { +#define DISPATCH(optname) case optname: CSrtConfigSetter::set(co, optval, optlen); return 0; + + DISPATCH(SRTO_MSS); + DISPATCH(SRTO_FC); + DISPATCH(SRTO_SNDBUF); + DISPATCH(SRTO_RCVBUF); + DISPATCH(SRTO_LINGER); + DISPATCH(SRTO_UDP_SNDBUF); + DISPATCH(SRTO_UDP_RCVBUF); + DISPATCH(SRTO_RENDEZVOUS); + DISPATCH(SRTO_SNDTIMEO); + DISPATCH(SRTO_RCVTIMEO); + DISPATCH(SRTO_SNDSYN); + DISPATCH(SRTO_RCVSYN); + DISPATCH(SRTO_REUSEADDR); + DISPATCH(SRTO_MAXBW); + DISPATCH(SRTO_IPTTL); + DISPATCH(SRTO_IPTOS); + DISPATCH(SRTO_BINDTODEVICE); + DISPATCH(SRTO_INPUTBW); + DISPATCH(SRTO_MININPUTBW); + DISPATCH(SRTO_OHEADBW); + DISPATCH(SRTO_SENDER); + DISPATCH(SRTO_TSBPDMODE); + DISPATCH(SRTO_LATENCY); + DISPATCH(SRTO_RCVLATENCY); + DISPATCH(SRTO_PEERLATENCY); + DISPATCH(SRTO_TLPKTDROP); + DISPATCH(SRTO_SNDDROPDELAY); + DISPATCH(SRTO_PASSPHRASE); + DISPATCH(SRTO_PBKEYLEN); + DISPATCH(SRTO_NAKREPORT); + DISPATCH(SRTO_CONNTIMEO); + DISPATCH(SRTO_DRIFTTRACER); + DISPATCH(SRTO_LOSSMAXTTL); + DISPATCH(SRTO_VERSION); + DISPATCH(SRTO_MINVERSION); + DISPATCH(SRTO_STREAMID); + DISPATCH(SRTO_CONGESTION); + DISPATCH(SRTO_MESSAGEAPI); + DISPATCH(SRTO_PAYLOADSIZE); + DISPATCH(SRTO_TRANSTYPE); +#if ENABLE_BONDING + DISPATCH(SRTO_GROUPCONNECT); + DISPATCH(SRTO_GROUPMINSTABLETIMEO); +#endif + DISPATCH(SRTO_KMREFRESHRATE); + DISPATCH(SRTO_KMPREANNOUNCE); + DISPATCH(SRTO_ENFORCEDENCRYPTION); + DISPATCH(SRTO_PEERIDLETIMEO); + DISPATCH(SRTO_IPV6ONLY); + DISPATCH(SRTO_PACKETFILTER); + DISPATCH(SRTO_RETRANSMITALGO); + +#undef DISPATCH + default: + return -1; + } +} + +} // anonymous namespace + +int CSrtConfig::set(SRT_SOCKOPT optName, const void* optval, int optlen) +{ + return dispatchSet(optName, *this, optval, optlen); +} + +#if ENABLE_BONDING +bool SRT_SocketOptionObject::add(SRT_SOCKOPT optname, const void* optval, size_t optlen) +{ + // Check first if this option is allowed to be set + // as on a member socket. + + switch (optname) + { + case SRTO_BINDTODEVICE: + case SRTO_CONNTIMEO: + case SRTO_DRIFTTRACER: + //SRTO_FC - not allowed to be different among group members + case SRTO_GROUPMINSTABLETIMEO: + //SRTO_INPUTBW - per transmission setting + case SRTO_IPTOS: + case SRTO_IPTTL: + case SRTO_KMREFRESHRATE: + case SRTO_KMPREANNOUNCE: + //SRTO_LATENCY - per transmission setting + //SRTO_LINGER - not for managed sockets + case SRTO_LOSSMAXTTL: + //SRTO_MAXBW - per transmission setting + //SRTO_MESSAGEAPI - groups are live mode only + //SRTO_MINVERSION - per group connection setting + case SRTO_NAKREPORT: + //SRTO_OHEADBW - per transmission setting + //SRTO_PACKETFILTER - per transmission setting + //SRTO_PASSPHRASE - per group connection setting + //SRTO_PASSPHRASE - per transmission setting + //SRTO_PBKEYLEN - per group connection setting + case SRTO_PEERIDLETIMEO: + case SRTO_RCVBUF: + //SRTO_RCVSYN - must be always false in groups + //SRTO_RCVTIMEO - must be alwyas -1 in groups + case SRTO_SNDBUF: + case SRTO_SNDDROPDELAY: + //SRTO_TLPKTDROP - per transmission setting + //SRTO_TSBPDMODE - per transmission setting + case SRTO_UDP_RCVBUF: + case SRTO_UDP_SNDBUF: + break; + + default: + // Other options are not allowed + return false; + } + + // Header size will get the size likely aligned, but it won't + // hurt if the memory size will be up to 4 bytes more than + // needed - and it's better to not risk that alighment rules + // will make these calculations result in less space than needed. + const size_t headersize = sizeof(SingleOption); + const size_t payload = std::min(sizeof(uint32_t), optlen); + unsigned char* mem = new unsigned char[headersize + payload]; + SingleOption* option = reinterpret_cast(mem); + option->option = optname; + option->length = (uint16_t) optlen; + memcpy(option->storage, optval, optlen); + + options.push_back(option); + + return true; +} +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.h b/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.h new file mode 100644 index 0000000000..3fbf0e264c --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.h @@ -0,0 +1,377 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2018 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef INC_SRT_SOCKETCONFIG_H +#define INC_SRT_SOCKETCONFIG_H + +#include "platform_sys.h" +#ifdef SRT_ENABLE_BINDTODEVICE +#include +#endif +#include +#include "haicrypt.h" +#include "congctl.h" +#include "packet.h" +#include "handshake.h" +#include "logger_defs.h" +#include "packetfilter.h" + +// SRT Version constants +#define SRT_VERSION_UNK 0 +#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */ +#define SRT_VERSION_MAJ(v) (0xFF0000 & (v)) /* Major number ensuring backward compatibility */ +#define SRT_VERSION_MIN(v) (0x00FF00 & (v)) +#define SRT_VERSION_PCH(v) (0x0000FF & (v)) + +// NOTE: SRT_VERSION is primarily defined in the build file. +extern const int32_t SRT_DEF_VERSION; + +namespace srt +{ + +struct CSrtMuxerConfig +{ + static const int DEF_UDP_BUFFER_SIZE = 65536; + + int iIpTTL; + int iIpToS; + int iIpV6Only; // IPV6_V6ONLY option (-1 if not set) + bool bReuseAddr; // reuse an exiting port or not, for UDP multiplexer + +#ifdef SRT_ENABLE_BINDTODEVICE + std::string sBindToDevice; +#endif + int iUDPSndBufSize; // UDP sending buffer size + int iUDPRcvBufSize; // UDP receiving buffer size + + bool operator==(const CSrtMuxerConfig& other) const + { +#define CEQUAL(field) (field == other.field) + return CEQUAL(iIpTTL) + && CEQUAL(iIpToS) + && CEQUAL(iIpV6Only) + && CEQUAL(bReuseAddr) +#ifdef SRT_ENABLE_BINDTODEVICE + && CEQUAL(sBindToDevice) +#endif + && CEQUAL(iUDPSndBufSize) + && CEQUAL(iUDPRcvBufSize); +#undef CEQUAL + } + + CSrtMuxerConfig() + : iIpTTL(-1) /* IPv4 TTL or IPv6 HOPs [1..255] (-1:undefined) */ + , iIpToS(-1) /* IPv4 Type of Service or IPv6 Traffic Class [0x00..0xff] (-1:undefined) */ + , iIpV6Only(-1) + , bReuseAddr(true) // This is default in SRT + , iUDPSndBufSize(DEF_UDP_BUFFER_SIZE) + , iUDPRcvBufSize(DEF_UDP_BUFFER_SIZE) + { + } +}; + +struct CSrtConfig; + +template +class StringStorage +{ + char stor[SIZE + 1]; + uint16_t len; + + // NOTE: default copying allowed. + +public: + StringStorage() + : len(0) + { + memset(stor, 0, sizeof stor); + } + + bool set(const char* s, size_t length) + { + if (length > SIZE) + return false; + + memcpy(stor, s, length); + stor[length] = 0; + len = (int) length; + return true; + } + + bool set(const std::string& s) + { + return set(s.c_str(), s.size()); + } + + std::string str() const + { + return len == 0 ? std::string() : std::string(stor); + } + + const char* c_str() const + { + return stor; + } + + size_t size() const { return size_t(len); } + bool empty() const { return len == 0; } +}; + +struct CSrtConfig: CSrtMuxerConfig +{ + typedef srt::sync::steady_clock::time_point time_point; + typedef srt::sync::steady_clock::duration duration; + + static const int + DEF_MSS = 1500, + DEF_FLIGHT_SIZE = 25600, + DEF_BUFFER_SIZE = 8192, //Rcv buffer MUST NOT be bigger than Flight Flag size + DEF_LINGER_S = 3*60, // 3 minutes + DEF_CONNTIMEO_S = 3; // 3 seconds + + static const int COMM_RESPONSE_TIMEOUT_MS = 5 * 1000; // 5 seconds + static const uint32_t COMM_DEF_MIN_STABILITY_TIMEOUT_MS = 60; // 60 ms + + // Mimimum recv flight flag size is 32 packets + static const int DEF_MIN_FLIGHT_PKT = 32; + static const size_t MAX_SID_LENGTH = 512; + static const size_t MAX_PFILTER_LENGTH = 64; + static const size_t MAX_CONG_LENGTH = 16; + + int iMSS; // Maximum Segment Size, in bytes + size_t zExpPayloadSize; // Expected average payload size (user option) + + // Options + bool bSynSending; // Sending syncronization mode + bool bSynRecving; // Receiving syncronization mode + int iFlightFlagSize; // Maximum number of packets in flight from the peer side + int iSndBufSize; // Maximum UDT sender buffer size + int iRcvBufSize; // Maximum UDT receiver buffer size + linger Linger; // Linger information on close + bool bRendezvous; // Rendezvous connection mode + + duration tdConnTimeOut; // connect timeout in milliseconds + bool bDriftTracer; + int iSndTimeOut; // sending timeout in milliseconds + int iRcvTimeOut; // receiving timeout in milliseconds + int64_t llMaxBW; // maximum data transfer rate (threshold) + + // These fields keep the options for encryption + // (SRTO_PASSPHRASE, SRTO_PBKEYLEN). Crypto object is + // created later and takes values from these. + HaiCrypt_Secret CryptoSecret; + int iSndCryptoKeyLen; + + // XXX Consider removing. The bDataSender stays here + // in order to maintain the HS side selection in HSv4. + bool bDataSender; + + bool bMessageAPI; + bool bTSBPD; // Whether AGENT will do TSBPD Rx (whether peer does, is not agent's problem) + int iRcvLatency; // Agent's Rx latency + int iPeerLatency; // Peer's Rx latency for the traffic made by Agent's Tx. + bool bTLPktDrop; // Whether Agent WILL DO TLPKTDROP on Rx. + int iSndDropDelay; // Extra delay when deciding to snd-drop for TLPKTDROP, -1 to off + bool bEnforcedEnc; // Off by default. When on, any connection other than nopw-nopw & pw1-pw1 is rejected. + int iGroupConnect; // 1 - allow group connections + int iPeerIdleTimeout_ms; // Timeout for hearing anything from the peer (ms). + uint32_t uMinStabilityTimeout_ms; + int iRetransmitAlgo; + + int64_t llInputBW; // Input stream rate (bytes/sec). 0: use internally estimated input bandwidth + int64_t llMinInputBW; // Minimum input stream rate estimate (bytes/sec) + int iOverheadBW; // Percent above input stream rate (applies if llMaxBW == 0) + bool bRcvNakReport; // Enable Receiver Periodic NAK Reports + int iMaxReorderTolerance; //< Maximum allowed value for dynamic reorder tolerance + + // For the use of CCryptoControl + // HaiCrypt configuration + unsigned int uKmRefreshRatePkt; + unsigned int uKmPreAnnouncePkt; + + uint32_t uSrtVersion; + uint32_t uMinimumPeerSrtVersion; + + StringStorage sCongestion; + StringStorage sPacketFilterConfig; + StringStorage sStreamName; + + // Shortcuts and utilities + int32_t flightCapacity() + { + return std::min(iRcvBufSize, iFlightFlagSize); + } + + CSrtConfig() + : iMSS(DEF_MSS) + , zExpPayloadSize(SRT_LIVE_DEF_PLSIZE) + , bSynSending(true) + , bSynRecving(true) + , iFlightFlagSize(DEF_FLIGHT_SIZE) + , iSndBufSize(DEF_BUFFER_SIZE) + , iRcvBufSize(DEF_BUFFER_SIZE) + , bRendezvous(false) + , tdConnTimeOut(srt::sync::seconds_from(DEF_CONNTIMEO_S)) + , bDriftTracer(true) + , iSndTimeOut(-1) + , iRcvTimeOut(-1) + , llMaxBW(-1) + , bDataSender(false) + , bMessageAPI(true) + , bTSBPD(true) + , iRcvLatency(SRT_LIVE_DEF_LATENCY_MS) + , iPeerLatency(0) + , bTLPktDrop(true) + , iSndDropDelay(0) + , bEnforcedEnc(true) + , iGroupConnect(0) + , iPeerIdleTimeout_ms(COMM_RESPONSE_TIMEOUT_MS) + , uMinStabilityTimeout_ms(COMM_DEF_MIN_STABILITY_TIMEOUT_MS) + , iRetransmitAlgo(1) + , llInputBW(0) + , llMinInputBW(0) + , iOverheadBW(25) + , bRcvNakReport(true) + , iMaxReorderTolerance(0) // Sensible optimal value is 10, 0 preserves old behavior + , uKmRefreshRatePkt(0) + , uKmPreAnnouncePkt(0) + , uSrtVersion(SRT_DEF_VERSION) + , uMinimumPeerSrtVersion(SRT_VERSION_MAJ1) + + { + // Default UDT configurations + iUDPRcvBufSize = iRcvBufSize * iMSS; + + // Linger: LIVE mode defaults, please refer to `SRTO_TRANSTYPE` option + // for other modes. + Linger.l_onoff = 0; + Linger.l_linger = 0; + CryptoSecret.len = 0; + iSndCryptoKeyLen = 0; + + // Default congestion is "live". + // Available builtin congestions: "file". + // Others can be registerred. + sCongestion.set("live", 4); + } + + ~CSrtConfig() + { + // Wipeout critical data + memset(&CryptoSecret, 0, sizeof(CryptoSecret)); + } + + int set(SRT_SOCKOPT optName, const void* val, int size); +}; + +template +inline T cast_optval(const void* optval) +{ + return *reinterpret_cast(optval); +} + +template +inline T cast_optval(const void* optval, int optlen) +{ + if (optlen > 0 && optlen != sizeof(T)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + return cast_optval(optval); +} + +// This function is to make it possible for both C and C++ +// API to accept both bool and int types for boolean options. +// (it's not that C couldn't use , it's that people +// often forget to use correct type). +template <> +inline bool cast_optval(const void* optval, int optlen) +{ + if (optlen == sizeof(bool)) + { + return *reinterpret_cast(optval); + } + + if (optlen == sizeof(int)) + { + // 0!= is a windows warning-killer int-to-bool conversion + return 0 != *reinterpret_cast(optval); + } + return false; +} + +} // namespace srt + +struct SRT_SocketOptionObject +{ + struct SingleOption + { + uint16_t option; + uint16_t length; + unsigned char storage[1]; // NOTE: Variable length object! + }; + + std::vector options; + + SRT_SocketOptionObject() {} + + ~SRT_SocketOptionObject() + { + for (size_t i = 0; i < options.size(); ++i) + { + // Convert back + unsigned char* mem = reinterpret_cast(options[i]); + delete[] mem; + } + } + + bool add(SRT_SOCKOPT optname, const void* optval, size_t optlen); +}; + +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt.h b/trunk/3rdparty/srt-1-fit/srtcore/srt.h index 0e566e859f..6f2ee934dd 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/srt.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/srt.h @@ -13,8 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__SRTC_H -#define INC__SRTC_H +#ifndef INC_SRTC_H +#define INC_SRTC_H #include "version.h" @@ -23,7 +23,6 @@ written by #include #include -#include "srt4udt.h" #include "logging_api.h" //////////////////////////////////////////////////////////////////////////////// @@ -36,7 +35,7 @@ written by #ifdef _WIN32 - #ifndef __MINGW__ + #ifndef __MINGW32__ // Explicitly define 32-bit and 64-bit numbers typedef __int32 int32_t; typedef __int64 int64_t; @@ -47,17 +46,14 @@ written by // VC 6.0 does not support unsigned __int64: may cause potential problems. typedef __int64 uint64_t; #endif - - #ifdef SRT_DYNAMIC - #ifdef SRT_EXPORTS - #define SRT_API __declspec(dllexport) - #else - #define SRT_API __declspec(dllimport) - #endif + #endif + #ifdef SRT_DYNAMIC + #ifdef SRT_EXPORTS + #define SRT_API __declspec(dllexport) #else - #define SRT_API + #define SRT_API __declspec(dllimport) #endif - #else // __MINGW__ + #else // !SRT_DYNAMIC #define SRT_API #endif #else @@ -69,48 +65,99 @@ written by // You can use these constants with SRTO_MINVERSION option. #define SRT_VERSION_FEAT_HSv5 0x010300 +#if defined(__cplusplus) && __cplusplus > 201406 +#define SRT_HAVE_CXX17 1 +#else +#define SRT_HAVE_CXX17 0 +#endif + + +// Stadnard attributes + // When compiling in C++17 mode, use the standard C++17 attributes // (out of these, only [[deprecated]] is supported in C++14, so // for all lesser standard use compiler-specific attributes). -#if defined(SRT_NO_DEPRECATED) - -#define SRT_ATR_UNUSED -#define SRT_ATR_DEPRECATED -#define SRT_ATR_NODISCARD - -#elif defined(__cplusplus) && __cplusplus > 201406 +#if SRT_HAVE_CXX17 +// Unused: DO NOT issue a warning if this entity is unused. #define SRT_ATR_UNUSED [[maybe_unused]] -#define SRT_ATR_DEPRECATED [[deprecated]] + +// Nodiscard: issue a warning if the return value was discarded. #define SRT_ATR_NODISCARD [[nodiscard]] // GNUG is GNU C/C++; this syntax is also supported by Clang -#elif defined( __GNUC__) +#elif defined(__GNUC__) #define SRT_ATR_UNUSED __attribute__((unused)) -#define SRT_ATR_DEPRECATED __attribute__((deprecated)) #define SRT_ATR_NODISCARD __attribute__((warn_unused_result)) +#elif defined(_MSC_VER) +#define SRT_ATR_UNUSED __pragma(warning(suppress: 4100 4101)) +#define SRT_ATR_NODISCARD _Check_return_ #else #define SRT_ATR_UNUSED -#define SRT_ATR_DEPRECATED #define SRT_ATR_NODISCARD #endif + +// DEPRECATED attributes + +// There's needed DEPRECATED and DEPRECATED_PX, as some compilers require them +// before the entity, others after the entity. +// The *_PX version is the prefix attribute, which applies only +// to functions (Microsoft compilers). + +// When deprecating a function, mark it: +// +// SRT_ATR_DEPRECATED_PX retval function(arguments) SRT_ATR_DEPRECATED; +// + +// When SRT_NO_DEPRECATED defined, do not issue any deprecation warnings. +// Regardless of the compiler type. +#if defined(SRT_NO_DEPRECATED) + +#define SRT_ATR_DEPRECATED +#define SRT_ATR_DEPRECATED_PX + +#elif SRT_HAVE_CXX17 + +#define SRT_ATR_DEPRECATED +#define SRT_ATR_DEPRECATED_PX [[deprecated]] + +// GNUG is GNU C/C++; this syntax is also supported by Clang +#elif defined(__GNUC__) +#define SRT_ATR_DEPRECATED_PX +#define SRT_ATR_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SRT_ATR_DEPRECATED_PX __declspec(deprecated) +#define SRT_ATR_DEPRECATED // no postfix-type modifier +#else +#define SRT_ATR_DEPRECATED_PX +#define SRT_ATR_DEPRECATED +#endif + #ifdef __cplusplus extern "C" { #endif -typedef int SRTSOCKET; // SRTSOCKET is a typedef to int anyway, and it's not even in UDT namespace :) +typedef int32_t SRTSOCKET; + +// The most significant bit 31 (sign bit actually) is left unused, +// so that all people who check the value for < 0 instead of -1 +// still get what they want. The bit 30 is reserved for marking +// the "socket group". Most of the API functions should work +// transparently with the socket descriptor designating a single +// socket or a socket group. +static const int32_t SRTGROUP_MASK = (1 << 30); #ifdef _WIN32 - #ifndef __MINGW__ - typedef SOCKET SYSSOCKET; - #else - typedef int SYSSOCKET; - #endif + typedef SOCKET SYSSOCKET; #else typedef int SYSSOCKET; #endif +#ifndef ENABLE_BONDING +#define ENABLE_BONDING 0 +#endif + typedef SYSSOCKET UDPSOCKET; @@ -141,8 +188,7 @@ typedef enum SRT_SOCKOPT { SRTO_LINGER = 7, // waiting for unsent data when closing SRTO_UDP_SNDBUF = 8, // UDP sending buffer size SRTO_UDP_RCVBUF = 9, // UDP receiving buffer size - // XXX Free space for 2 options - // after deprecated ones are removed + // (some space left) SRTO_RENDEZVOUS = 12, // rendezvous connection mode SRTO_SNDTIMEO = 13, // send() timeout SRTO_RCVTIMEO = 14, // recv() timeout @@ -155,11 +201,10 @@ typedef enum SRT_SOCKOPT { SRTO_SENDER = 21, // Sender mode (independent of conn mode), for encryption, tsbpd handshake. SRTO_TSBPDMODE = 22, // Enable/Disable TsbPd. Enable -> Tx set origin timestamp, Rx deliver packet at origin time + delay SRTO_LATENCY = 23, // NOT RECOMMENDED. SET: to both SRTO_RCVLATENCY and SRTO_PEERLATENCY. GET: same as SRTO_RCVLATENCY. - SRTO_TSBPDDELAY = 23, // DEPRECATED. ALIAS: SRTO_LATENCY SRTO_INPUTBW = 24, // Estimated input stream rate. SRTO_OHEADBW, // MaxBW ceiling based on % over input stream rate. Applies when UDT_MAXBW=0 (auto). - SRTO_PASSPHRASE = 26, // Crypto PBKDF2 Passphrase size[0,10..64] 0:disable crypto - SRTO_PBKEYLEN, // Crypto key len in bytes {16,24,32} Default: 16 (128-bit) + SRTO_PASSPHRASE = 26, // Crypto PBKDF2 Passphrase (must be 10..79 characters, or empty to disable encryption) + SRTO_PBKEYLEN, // Crypto key len in bytes {16,24,32} Default: 16 (AES-128) SRTO_KMSTATE, // Key Material exchange status (UDT_SRTKmState) SRTO_IPTTL = 29, // IP Time To Live (passthru for system sockopt IPPROTO_IP/IP_TTL) SRTO_IPTOS, // IP Type of Service (passthru for system sockopt IPPROTO_IP/IP_TOS) @@ -168,10 +213,10 @@ typedef enum SRT_SOCKOPT { SRTO_NAKREPORT = 33, // Enable receiver to send periodic NAK reports SRTO_VERSION = 34, // Local SRT Version SRTO_PEERVERSION, // Peer SRT Version (from SRT Handshake) - SRTO_CONNTIMEO = 36, // Connect timeout in msec. Ccaller default: 3000, rendezvous (x 10) - // deprecated: SRTO_TWOWAYDATA, SRTO_SNDPBKEYLEN, SRTO_RCVPBKEYLEN (@c below) - _DEPRECATED_SRTO_SNDPBKEYLEN = 38, // (needed to use inside the code without generating -Wswitch) - // + SRTO_CONNTIMEO = 36, // Connect timeout in msec. Caller default: 3000, rendezvous (x 10) + SRTO_DRIFTTRACER = 37, // Enable or disable drift tracer + SRTO_MININPUTBW = 38, // Minimum estimate of input stream rate. + // (some space left) SRTO_SNDKMSTATE = 40, // (GET) the current state of the encryption at the peer side SRTO_RCVKMSTATE, // (GET) the current state of the encryption at the agent side SRTO_LOSSMAXTTL, // Maximum possible packet reorder tolerance (number of packets to receive after loss to send lossreport) @@ -188,15 +233,34 @@ typedef enum SRT_SOCKOPT { SRTO_ENFORCEDENCRYPTION, // Connection to be rejected or quickly broken when one side encryption set or bad password SRTO_IPV6ONLY, // IPV6_V6ONLY mode SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms] - // (some space left) - SRTO_PACKETFILTER = 60 // Add and configure a packet filter + SRTO_BINDTODEVICE, // Forward the SOL_SOCKET/SO_BINDTODEVICE option on socket (pass packets only from that device) + SRTO_GROUPCONNECT, // Set on a listener to allow group connection (ENABLE_BONDING) + SRTO_GROUPMINSTABLETIMEO, // Minimum Link Stability timeout (backup mode) in milliseconds (ENABLE_BONDING) + SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake (ENABLE_BONDING) + SRTO_PACKETFILTER = 60, // Add and configure a packet filter + SRTO_RETRANSMITALGO = 61, // An option to select packet retransmission algorithm + + SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; #ifdef __cplusplus -typedef SRT_ATR_DEPRECATED SRT_SOCKOPT SRT_SOCKOPT_DEPRECATED; + +#if __cplusplus > 199711L // C++11 + // Newer compilers report error when [[deprecated]] is applied to types, + // and C++11 and higher uses this. + // Note that this doesn't exactly use the 'deprecated' attribute, + // as it's introduced in C++14. What is actually used here is the + // fact that unknown attributes are ignored, but still warned about. + // This should only catch an eye - and that's what it does. +#define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT [[deprecated]])value) +#else + // Older (pre-C++11) compilers use gcc deprecated applied to a typedef + typedef SRT_ATR_DEPRECATED_PX SRT_SOCKOPT SRT_SOCKOPT_DEPRECATED SRT_ATR_DEPRECATED; #define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT_DEPRECATED)value) +#endif + #else @@ -213,46 +277,9 @@ enum SRT_ATR_DEPRECATED SRT_SOCKOPT_DEPRECATED #define SRT_DEPRECATED_OPTION(value) ((enum SRT_SOCKOPT_DEPRECATED)value) #endif -// DEPRECATED OPTIONS: - -// SRTO_TWOWAYDATA: not to be used. SRT connection is always bidirectional if -// both clients support HSv5 - that is, since version 1.3.0. This flag was -// introducted around 1.2.0 version when full bidirectional support was added, -// but the bidirectional feature was decided no to be enabled due to huge -// differences between bidirectional support (especially concerning encryption) -// with HSv4 and HSv5 (that is, HSv4 was decided to remain unidirectional only, -// even though partial support is already provided in this version). - -#define SRTO_TWOWAYDATA SRT_DEPRECATED_OPTION(37) - -// This has been deprecated a long time ago, treat this as never implemented. -// The value is also already reused for another option. -#define SRTO_TSBPDMAXLAG SRT_DEPRECATED_OPTION(32) - -// This option is a derivative from UDT; the mechanism that uses it is now -// settable by SRTO_CONGESTION, or more generally by SRTO_TRANSTYPE. The freed -// number has been reused for a read-only option SRTO_ISN. This option should -// have never been used anywhere, just for safety this is temporarily declared -// as deprecated. -#define SRTO_CC SRT_DEPRECATED_OPTION(3) - -// These two flags were derived from UDT, but they were never used. -// Probably it didn't make sense anyway. The maximum size of the message -// in File/Message mode is defined by SRTO_SNDBUF, and the MSGTTL is -// a parameter used in `srt_sendmsg` and `srt_sendmsg2`. -#define SRTO_MAXMSG SRT_DEPRECATED_OPTION(10) -#define SRTO_MSGTTL SRT_DEPRECATED_OPTION(11) - -// These flags come from an older experimental implementation of bidirectional -// encryption support, which were used two different SEKs, KEKs and passphrases -// per direction. The current implementation uses just one in both directions, -// so SRTO_PBKEYLEN should be used for both cases. -#define SRTO_SNDPBKEYLEN SRT_DEPRECATED_OPTION(38) -#define SRTO_RCVPBKEYLEN SRT_DEPRECATED_OPTION(39) - -// Keeping old name for compatibility (deprecated) -#define SRTO_SMOOTHER SRT_DEPRECATED_OPTION(47) -#define SRTO_STRICTENC SRT_DEPRECATED_OPTION(53) +// Note that there are no deprecated options at the moment, but the mechanism +// stays so that it can be used in future. Example: +// #define SRTO_STRICTENC SRT_DEPRECATED_OPTION(53) typedef enum SRT_TRANSTYPE { @@ -295,9 +322,7 @@ struct CBytePerfMon int pktRcvUndecryptTotal; // number of undecrypted packets uint64_t byteSentTotal; // total number of sent data bytes, including retransmissions uint64_t byteRecvTotal; // total number of received bytes -#ifdef SRT_ENABLE_LOSTBYTESCOUNT uint64_t byteRcvLossTotal; // total number of lost bytes -#endif uint64_t byteRetransTotal; // total number of retransmitted bytes uint64_t byteSndDropTotal; // number of too-late-to-send dropped bytes uint64_t byteRcvDropTotal; // number of too-late-to play missing bytes (estimate based on average packet size) @@ -327,9 +352,7 @@ struct CBytePerfMon int pktRcvUndecrypt; // number of undecrypted packets uint64_t byteSent; // number of sent data bytes, including retransmissions uint64_t byteRecv; // number of received bytes -#ifdef SRT_ENABLE_LOSTBYTESCOUNT uint64_t byteRcvLoss; // number of retransmitted bytes -#endif uint64_t byteRetrans; // number of retransmitted bytes uint64_t byteSndDrop; // number of too-late-to-send dropped bytes uint64_t byteRcvDrop; // number of too-late-to play missing bytes (estimate based on average packet size) @@ -370,6 +393,20 @@ struct CBytePerfMon int pktRcvFilterLoss; // number of packet loss not coverable by filter int pktReorderTolerance; // packet reorder tolerance value //< + + // New stats in 1.5.0 + + // Total + int64_t pktSentUniqueTotal; // total number of data packets sent by the application + int64_t pktRecvUniqueTotal; // total number of packets to be received by the application + uint64_t byteSentUniqueTotal; // total number of data bytes, sent by the application + uint64_t byteRecvUniqueTotal; // total number of data bytes to be received by the application + + // Local + int64_t pktSentUnique; // number of data packets sent by the application + int64_t pktRecvUnique; // number of packets to be received by the application + uint64_t byteSentUnique; // number of data bytes, sent by the application + uint64_t byteRecvUnique; // number of data bytes to be received by the application }; //////////////////////////////////////////////////////////////////////////////// @@ -399,12 +436,14 @@ enum CodeMinor MN_REJECTED = 2, MN_NORES = 3, MN_SECURITY = 4, + MN_CLOSED = 5, // MJ_CONNECTION MN_CONNLOST = 1, MN_NOCONN = 2, // MJ_SYSTEMRES MN_THREAD = 1, MN_MEMORY = 2, + MN_OBJECT = 3, // MJ_FILESYSTEM MN_SEEKGFAIL = 1, MN_READFAIL = 2, @@ -424,6 +463,8 @@ enum CodeMinor MN_BUSY = 11, MN_XSIZE = 12, MN_EIDINVAL = 13, + MN_EEMPTY = 14, + MN_BUSYPORT = 15, // MJ_AGAIN MN_WRAVAIL = 1, MN_RDAVAIL = 2, @@ -431,12 +472,10 @@ enum CodeMinor MN_CONGESTION = 4 }; -static const enum CodeMinor MN_ISSTREAM SRT_ATR_DEPRECATED = (enum CodeMinor)(9); -static const enum CodeMinor MN_ISDGRAM SRT_ATR_DEPRECATED = (enum CodeMinor)(10); // Stupid, but effective. This will be #undefined, so don't worry. -#define MJ(major) (1000 * MJ_##major) -#define MN(major, minor) (1000 * MJ_##major + MN_##minor) +#define SRT_EMJ(major) (1000 * MJ_##major) +#define SRT_EMN(major, minor) (1000 * MJ_##major + MN_##minor) // Some better way to define it, and better for C language. typedef enum SRT_ERRNO @@ -444,55 +483,56 @@ typedef enum SRT_ERRNO SRT_EUNKNOWN = -1, SRT_SUCCESS = MJ_SUCCESS, - SRT_ECONNSETUP = MJ(SETUP), - SRT_ENOSERVER = MN(SETUP, TIMEOUT), - SRT_ECONNREJ = MN(SETUP, REJECTED), - SRT_ESOCKFAIL = MN(SETUP, NORES), - SRT_ESECFAIL = MN(SETUP, SECURITY), - - SRT_ECONNFAIL = MJ(CONNECTION), - SRT_ECONNLOST = MN(CONNECTION, CONNLOST), - SRT_ENOCONN = MN(CONNECTION, NOCONN), - - SRT_ERESOURCE = MJ(SYSTEMRES), - SRT_ETHREAD = MN(SYSTEMRES, THREAD), - SRT_ENOBUF = MN(SYSTEMRES, MEMORY), - - SRT_EFILE = MJ(FILESYSTEM), - SRT_EINVRDOFF = MN(FILESYSTEM, SEEKGFAIL), - SRT_ERDPERM = MN(FILESYSTEM, READFAIL), - SRT_EINVWROFF = MN(FILESYSTEM, SEEKPFAIL), - SRT_EWRPERM = MN(FILESYSTEM, WRITEFAIL), - - SRT_EINVOP = MJ(NOTSUP), - SRT_EBOUNDSOCK = MN(NOTSUP, ISBOUND), - SRT_ECONNSOCK = MN(NOTSUP, ISCONNECTED), - SRT_EINVPARAM = MN(NOTSUP, INVAL), - SRT_EINVSOCK = MN(NOTSUP, SIDINVAL), - SRT_EUNBOUNDSOCK = MN(NOTSUP, ISUNBOUND), - SRT_ENOLISTEN = MN(NOTSUP, NOLISTEN), - SRT_ERDVNOSERV = MN(NOTSUP, ISRENDEZVOUS), - SRT_ERDVUNBOUND = MN(NOTSUP, ISRENDUNBOUND), - SRT_EINVALMSGAPI = MN(NOTSUP, INVALMSGAPI), - SRT_EINVALBUFFERAPI = MN(NOTSUP, INVALBUFFERAPI), - SRT_EDUPLISTEN = MN(NOTSUP, BUSY), - SRT_ELARGEMSG = MN(NOTSUP, XSIZE), - SRT_EINVPOLLID = MN(NOTSUP, EIDINVAL), - - SRT_EASYNCFAIL = MJ(AGAIN), - SRT_EASYNCSND = MN(AGAIN, WRAVAIL), - SRT_EASYNCRCV = MN(AGAIN, RDAVAIL), - SRT_ETIMEOUT = MN(AGAIN, XMTIMEOUT), - SRT_ECONGEST = MN(AGAIN, CONGESTION), - - SRT_EPEERERR = MJ(PEERERROR) + SRT_ECONNSETUP = SRT_EMJ(SETUP), + SRT_ENOSERVER = SRT_EMN(SETUP, TIMEOUT), + SRT_ECONNREJ = SRT_EMN(SETUP, REJECTED), + SRT_ESOCKFAIL = SRT_EMN(SETUP, NORES), + SRT_ESECFAIL = SRT_EMN(SETUP, SECURITY), + SRT_ESCLOSED = SRT_EMN(SETUP, CLOSED), + + SRT_ECONNFAIL = SRT_EMJ(CONNECTION), + SRT_ECONNLOST = SRT_EMN(CONNECTION, CONNLOST), + SRT_ENOCONN = SRT_EMN(CONNECTION, NOCONN), + + SRT_ERESOURCE = SRT_EMJ(SYSTEMRES), + SRT_ETHREAD = SRT_EMN(SYSTEMRES, THREAD), + SRT_ENOBUF = SRT_EMN(SYSTEMRES, MEMORY), + SRT_ESYSOBJ = SRT_EMN(SYSTEMRES, OBJECT), + + SRT_EFILE = SRT_EMJ(FILESYSTEM), + SRT_EINVRDOFF = SRT_EMN(FILESYSTEM, SEEKGFAIL), + SRT_ERDPERM = SRT_EMN(FILESYSTEM, READFAIL), + SRT_EINVWROFF = SRT_EMN(FILESYSTEM, SEEKPFAIL), + SRT_EWRPERM = SRT_EMN(FILESYSTEM, WRITEFAIL), + + SRT_EINVOP = SRT_EMJ(NOTSUP), + SRT_EBOUNDSOCK = SRT_EMN(NOTSUP, ISBOUND), + SRT_ECONNSOCK = SRT_EMN(NOTSUP, ISCONNECTED), + SRT_EINVPARAM = SRT_EMN(NOTSUP, INVAL), + SRT_EINVSOCK = SRT_EMN(NOTSUP, SIDINVAL), + SRT_EUNBOUNDSOCK = SRT_EMN(NOTSUP, ISUNBOUND), + SRT_ENOLISTEN = SRT_EMN(NOTSUP, NOLISTEN), + SRT_ERDVNOSERV = SRT_EMN(NOTSUP, ISRENDEZVOUS), + SRT_ERDVUNBOUND = SRT_EMN(NOTSUP, ISRENDUNBOUND), + SRT_EINVALMSGAPI = SRT_EMN(NOTSUP, INVALMSGAPI), + SRT_EINVALBUFFERAPI = SRT_EMN(NOTSUP, INVALBUFFERAPI), + SRT_EDUPLISTEN = SRT_EMN(NOTSUP, BUSY), + SRT_ELARGEMSG = SRT_EMN(NOTSUP, XSIZE), + SRT_EINVPOLLID = SRT_EMN(NOTSUP, EIDINVAL), + SRT_EPOLLEMPTY = SRT_EMN(NOTSUP, EEMPTY), + SRT_EBINDCONFLICT = SRT_EMN(NOTSUP, BUSYPORT), + + SRT_EASYNCFAIL = SRT_EMJ(AGAIN), + SRT_EASYNCSND = SRT_EMN(AGAIN, WRAVAIL), + SRT_EASYNCRCV = SRT_EMN(AGAIN, RDAVAIL), + SRT_ETIMEOUT = SRT_EMN(AGAIN, XMTIMEOUT), + SRT_ECONGEST = SRT_EMN(AGAIN, CONGESTION), + + SRT_EPEERERR = SRT_EMJ(PEERERROR) } SRT_ERRNO; -static const SRT_ERRNO SRT_EISSTREAM SRT_ATR_DEPRECATED = (SRT_ERRNO) MN(NOTSUP, INVALMSGAPI); -static const SRT_ERRNO SRT_EISDGRAM SRT_ATR_DEPRECATED = (SRT_ERRNO) MN(NOTSUP, INVALBUFFERAPI); - -#undef MJ -#undef MN +#undef SRT_EMJ +#undef SRT_EMN enum SRT_REJECT_REASON { @@ -510,33 +550,87 @@ enum SRT_REJECT_REASON SRT_REJ_UNSECURE, // password required or unexpected SRT_REJ_MESSAGEAPI, // streamapi/messageapi collision SRT_REJ_CONGESTION, // incompatible congestion-controller type - SRT_REJ_FILTER, // incompatible packet filter + SRT_REJ_FILTER, // incompatible packet filter + SRT_REJ_GROUP, // incompatible group + SRT_REJ_TIMEOUT, // connection timeout - SRT_REJ__SIZE, + SRT_REJ_E_SIZE, }; +// XXX This value remains for some time, but it's deprecated +// Planned deprecation removal: rel1.6.0. +#define SRT_REJ__SIZE SRT_REJ_E_SIZE + +// Reject category codes: + +#define SRT_REJC_VALUE(code) (1000 * (code/1000)) +#define SRT_REJC_INTERNAL 0 // Codes from above SRT_REJECT_REASON enum +#define SRT_REJC_PREDEFINED 1000 // Standard server error codes +#define SRT_REJC_USERDEFINED 2000 // User defined error codes + + // Logging API - specialization for SRT. -// Define logging functional areas for log selection. -// Use values greater than 0. Value 0 is reserved for LOGFA_GENERAL, -// which is considered always enabled. +// WARNING: This part is generated. // Logger Functional Areas // Note that 0 is "general". +// Values 0* - general, unqualified +// Values 1* - control +// Values 2* - receiving +// Values 3* - sending +// Values 4* - management + // Made by #define so that it's available also for C API. -#define SRT_LOGFA_GENERAL 0 -#define SRT_LOGFA_BSTATS 1 -#define SRT_LOGFA_CONTROL 2 -#define SRT_LOGFA_DATA 3 -#define SRT_LOGFA_TSBPD 4 -#define SRT_LOGFA_REXMIT 5 -#define SRT_LOGFA_HAICRYPT 6 -#define SRT_LOGFA_CONGEST 7 - -// To make a typical int32_t size, although still use std::bitset. + +// Use ../scripts/generate-logging-defs.tcl to regenerate. + +// SRT_LOGFA BEGIN GENERATED SECTION { + +#define SRT_LOGFA_GENERAL 0 // gglog: General uncategorized log, for serious issues only +#define SRT_LOGFA_SOCKMGMT 1 // smlog: Socket create/open/close/configure activities +#define SRT_LOGFA_CONN 2 // cnlog: Connection establishment and handshake +#define SRT_LOGFA_XTIMER 3 // xtlog: The checkTimer and around activities +#define SRT_LOGFA_TSBPD 4 // tslog: The TsBPD thread +#define SRT_LOGFA_RSRC 5 // rslog: System resource allocation and management + +#define SRT_LOGFA_CONGEST 7 // cclog: Congestion control module +#define SRT_LOGFA_PFILTER 8 // pflog: Packet filter module + +#define SRT_LOGFA_API_CTRL 11 // aclog: API part for socket and library managmenet + +#define SRT_LOGFA_QUE_CTRL 13 // qclog: Queue control activities + +#define SRT_LOGFA_EPOLL_UPD 16 // eilog: EPoll, internal update activities + +#define SRT_LOGFA_API_RECV 21 // arlog: API part for receiving +#define SRT_LOGFA_BUF_RECV 22 // brlog: Buffer, receiving side +#define SRT_LOGFA_QUE_RECV 23 // qrlog: Queue, receiving side +#define SRT_LOGFA_CHN_RECV 24 // krlog: CChannel, receiving side +#define SRT_LOGFA_GRP_RECV 25 // grlog: Group, receiving side + +#define SRT_LOGFA_API_SEND 31 // aslog: API part for sending +#define SRT_LOGFA_BUF_SEND 32 // bslog: Buffer, sending side +#define SRT_LOGFA_QUE_SEND 33 // qslog: Queue, sending side +#define SRT_LOGFA_CHN_SEND 34 // kslog: CChannel, sending side +#define SRT_LOGFA_GRP_SEND 35 // gslog: Group, sending side + +#define SRT_LOGFA_INTERNAL 41 // inlog: Internal activities not connected directly to a socket + +#define SRT_LOGFA_QUE_MGMT 43 // qmlog: Queue, management part +#define SRT_LOGFA_CHN_MGMT 44 // kmlog: CChannel, management part +#define SRT_LOGFA_GRP_MGMT 45 // gmlog: Group, management part +#define SRT_LOGFA_EPOLL_API 46 // ealog: EPoll, API part + +#define SRT_LOGFA_HAICRYPT 6 // hclog: Haicrypt module area +#define SRT_LOGFA_APPLOG 10 // aplog: Applications + +// } SRT_LOGFA END GENERATED SECTION + +// To make a typical int64_t size, although still use std::bitset. // C API will carry it over. -#define SRT_LOGFA_LASTNONE 31 +#define SRT_LOGFA_LASTNONE 63 enum SRT_KM_STATE { @@ -550,16 +644,73 @@ enum SRT_KM_STATE enum SRT_EPOLL_OPT { SRT_EPOLL_OPT_NONE = 0x0, // fallback - // this values are defined same as linux epoll.h + + // Values intended to be the same as in ``. // so that if system values are used by mistake, they should have the same effect + // This applies to: IN, OUT, ERR and ET. + + /// Ready for 'recv' operation: + /// + /// - For stream mode it means that at least 1 byte is available. + /// In this mode the buffer may extract only a part of the packet, + /// leaving next data possible for extraction later. + /// + /// - For message mode it means that there is at least one packet + /// available (this may change in future, as it is desired that + /// one full message should only wake up, not single packet of a + /// not yet extractable message). + /// + /// - For live mode it means that there's at least one packet + /// ready to play. + /// + /// - For listener sockets, this means that there is a new connection + /// waiting for pickup through the `srt_accept()` call, that is, + /// the next call to `srt_accept()` will succeed without blocking + /// (see an alias SRT_EPOLL_ACCEPT below). SRT_EPOLL_IN = 0x1, + + /// Ready for 'send' operation. + /// + /// - For stream mode it means that there's a free space in the + /// sender buffer for at least 1 byte of data. The next send + /// operation will only allow to send as much data as it is free + /// space in the buffer. + /// + /// - For message mode it means that there's a free space for at + /// least one UDP packet. The edge-triggered mode can be used to + /// pick up updates as the free space in the sender buffer grows. + /// + /// - For live mode it means that there's a free space for at least + /// one UDP packet. On the other hand, no readiness for OUT usually + /// means an extraordinary congestion on the link, meaning also that + /// you should immediately slow down the sending rate or you may get + /// a connection break soon. + /// + /// - For non-blocking sockets used with `srt_connect*` operation, + /// this flag simply means that the connection was established. SRT_EPOLL_OUT = 0x4, + + /// The socket has encountered an error in the last operation + /// and the next operation on that socket will end up with error. + /// You can retry the operation, but getting the error from it + /// is certain, so you may as well close the socket. SRT_EPOLL_ERR = 0x8, + + // To avoid confusion in the internal code, the following + // duplicates are introduced to improve clarity. + SRT_EPOLL_CONNECT = SRT_EPOLL_OUT, + SRT_EPOLL_ACCEPT = SRT_EPOLL_IN, + + SRT_EPOLL_UPDATE = 0x10, SRT_EPOLL_ET = 1u << 31 }; // These are actually flags - use a bit container: typedef int32_t SRT_EPOLL_T; +// Define which epoll flags determine events. All others are special flags. +#define SRT_EPOLL_EVENTTYPES (SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_UPDATE | SRT_EPOLL_ERR) +#define SRT_EPOLL_ETONLY (SRT_EPOLL_UPDATE) + enum SRT_EPOLL_FLAGS { /// This allows the EID container to be empty when calling the waiting @@ -582,17 +733,8 @@ inline SRT_EPOLL_OPT operator|(SRT_EPOLL_OPT a1, SRT_EPOLL_OPT a2) return SRT_EPOLL_OPT( (int)a1 | (int)a2 ); } -inline bool operator&(int flags, SRT_EPOLL_OPT eflg) -{ - // Using an enum prevents treating int automatically as enum, - // requires explicit enum to be passed here, and minimizes the - // risk that the right side value will contain multiple flags. - return (flags & int(eflg)) != 0; -} #endif - - typedef struct CBytePerfMon SRT_TRACEBSTATS; static const SRTSOCKET SRT_INVALID_SOCK = -1; @@ -605,18 +747,32 @@ SRT_API int srt_cleanup(void); // // Socket operations // -SRT_API SRTSOCKET srt_socket (int af, int type, int protocol); -SRT_API SRTSOCKET srt_create_socket(); +// DEPRECATED: srt_socket with 3 arguments. All these arguments are ignored +// and socket creation doesn't need any arguments. Use srt_create_socket(). +// Planned deprecation removal: rel1.6.0 +SRT_ATR_DEPRECATED_PX SRT_API SRTSOCKET srt_socket(int, int, int) SRT_ATR_DEPRECATED; +SRT_API SRTSOCKET srt_create_socket(void); + SRT_API int srt_bind (SRTSOCKET u, const struct sockaddr* name, int namelen); -SRT_API int srt_bind_peerof (SRTSOCKET u, UDPSOCKET udpsock); +SRT_API int srt_bind_acquire (SRTSOCKET u, UDPSOCKET sys_udp_sock); +// Old name of srt_bind_acquire(), please don't use +// Planned deprecation removal: rel1.6.0 +SRT_ATR_DEPRECATED_PX static inline int srt_bind_peerof(SRTSOCKET u, UDPSOCKET sys_udp_sock) SRT_ATR_DEPRECATED; +static inline int srt_bind_peerof (SRTSOCKET u, UDPSOCKET sys_udp_sock) { return srt_bind_acquire(u, sys_udp_sock); } SRT_API int srt_listen (SRTSOCKET u, int backlog); SRT_API SRTSOCKET srt_accept (SRTSOCKET u, struct sockaddr* addr, int* addrlen); +SRT_API SRTSOCKET srt_accept_bond (const SRTSOCKET listeners[], int lsize, int64_t msTimeOut); typedef int srt_listen_callback_fn (void* opaq, SRTSOCKET ns, int hsversion, const struct sockaddr* peeraddr, const char* streamid); SRT_API int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); +typedef void srt_connect_callback_fn (void* opaq, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token); +SRT_API int srt_connect_callback(SRTSOCKET clr, srt_connect_callback_fn* hook_fn, void* hook_opaque); SRT_API int srt_connect (SRTSOCKET u, const struct sockaddr* name, int namelen); SRT_API int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); +SRT_API int srt_connect_bind (SRTSOCKET u, const struct sockaddr* source, + const struct sockaddr* target, int len); SRT_API int srt_rendezvous (SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); + SRT_API int srt_close (SRTSOCKET u); SRT_API int srt_getpeername (SRTSOCKET u, struct sockaddr* name, int* namelen); SRT_API int srt_getsockname (SRTSOCKET u, struct sockaddr* name, int* namelen); @@ -625,19 +781,34 @@ SRT_API int srt_setsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCK SRT_API int srt_getsockflag (SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); SRT_API int srt_setsockflag (SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); +typedef struct SRT_SocketGroupData_ SRT_SOCKGROUPDATA; -// XXX Note that the srctime functionality doesn't work yet and needs fixing. typedef struct SRT_MsgCtrl_ { int flags; // Left for future - int msgttl; // TTL for a message, default -1 (no TTL limitation) + int msgttl; // TTL for a message (millisec), default -1 (no TTL limitation) int inorder; // Whether a message is allowed to supersede partially lost one. Unused in stream and live mode. int boundary; // 0:mid pkt, 1(01b):end of frame, 2(11b):complete frame, 3(10b): start of frame - uint64_t srctime; // source timestamp (usec), 0: use internal time + int64_t srctime; // source time since epoch (usec), 0: use internal time (sender) int32_t pktseq; // sequence number of the first packet in received message (unused for sending) int32_t msgno; // message number (output value for both sending and receiving) + SRT_SOCKGROUPDATA* grpdata; + size_t grpdata_size; } SRT_MSGCTRL; +// Trap representation for sequence and message numbers +// This value means that this is "unset", and it's never +// a result of an operation made on this number. +static const int32_t SRT_SEQNO_NONE = -1; // -1: no seq (0 is a valid seqno!) +static const int32_t SRT_MSGNO_NONE = -1; // -1: unset +static const int32_t SRT_MSGNO_CONTROL = 0; // 0: control (used by packet filter) + +static const int SRT_MSGTTL_INF = -1; // unlimited TTL specification for message TTL + +// XXX Might be useful also other special uses of -1: +// - -1 as infinity for srt_epoll_wait +// - -1 as a trap index value used in list.cpp + // You are free to use either of these two methods to set SRT_MSGCTRL object // to default values: either call srt_msgctrl_init(&obj) or obj = srt_msgctrl_default. SRT_API void srt_msgctrl_init(SRT_MSGCTRL* mctrl); @@ -657,11 +828,6 @@ SRT_API extern const SRT_MSGCTRL srt_msgctrl_default; // parameters will be filled, as needed. NULL is acceptable, in which case // the defaults are used. -// NOTE: srt_send and srt_recv have the last "..." left to allow ignore a -// deprecated and unused "flags" parameter. After confirming that all -// compat applications that pass useless 0 there are fixed, this will be -// removed. - // // Sending functions // @@ -692,16 +858,17 @@ SRT_API int srt_getlasterror(int* errno_loc); SRT_API const char* srt_strerror(int code, int errnoval); SRT_API void srt_clearlasterror(void); -// performance track -// perfmon with Byte counters for better bitrate estimation. +// Performance tracking +// Performance monitor with Byte counters for better bitrate estimation. SRT_API int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); -// permon with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. +// Performance monitor with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. SRT_API int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); // Socket Status (for problem tracking) SRT_API SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u); SRT_API int srt_epoll_create(void); +SRT_API int srt_epoll_clear_usocks(int eid); SRT_API int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); SRT_API int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); SRT_API int srt_epoll_remove_usock(int eid, SRTSOCKET u); @@ -711,10 +878,14 @@ SRT_API int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); SRT_API int srt_epoll_wait(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum); -typedef struct SRT_EPOLL_EVENT_ +typedef struct SRT_EPOLL_EVENT_STR { SRTSOCKET fd; int events; // SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR +#ifdef __cplusplus + SRT_EPOLL_EVENT_STR(SRTSOCKET s, int ev): fd(s), events(ev) {} + SRT_EPOLL_EVENT_STR(): fd(-1), events(0) {} // NOTE: allows singular values, no init. +#endif } SRT_EPOLL_EVENT; SRT_API int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); @@ -736,9 +907,88 @@ SRT_API void srt_setlogflags(int flags); SRT_API int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); -SRT_API enum SRT_REJECT_REASON srt_getrejectreason(SRTSOCKET sock); -SRT_API extern const char* const srt_rejectreason_msg []; -const char* srt_rejectreason_str(enum SRT_REJECT_REASON id); +SRT_API int srt_getrejectreason(SRTSOCKET sock); +SRT_API int srt_setrejectreason(SRTSOCKET sock, int value); +// The srt_rejectreason_msg[] array is deprecated (as unsafe). +// Planned removal: v1.6.0. +SRT_API SRT_ATR_DEPRECATED extern const char* const srt_rejectreason_msg []; +SRT_API const char* srt_rejectreason_str(int id); + +SRT_API uint32_t srt_getversion(void); + +SRT_API int64_t srt_time_now(void); + +SRT_API int64_t srt_connection_time(SRTSOCKET sock); + +// Possible internal clock types +#define SRT_SYNC_CLOCK_STDCXX_STEADY 0 // C++11 std::chrono::steady_clock +#define SRT_SYNC_CLOCK_GETTIME_MONOTONIC 1 // clock_gettime with CLOCK_MONOTONIC +#define SRT_SYNC_CLOCK_WINQPC 2 +#define SRT_SYNC_CLOCK_MACH_ABSTIME 3 +#define SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY 4 +#define SRT_SYNC_CLOCK_AMD64_RDTSC 5 +#define SRT_SYNC_CLOCK_IA32_RDTSC 6 +#define SRT_SYNC_CLOCK_IA64_ITC 7 + +SRT_API int srt_clock_type(void); + +// SRT Socket Groups API (ENABLE_BONDING) + +typedef enum SRT_GROUP_TYPE +{ + SRT_GTYPE_UNDEFINED, + SRT_GTYPE_BROADCAST, + SRT_GTYPE_BACKUP, + // ... + SRT_GTYPE_E_END +} SRT_GROUP_TYPE; + +// Free-form flags for groups +// Flags may be type-specific! +static const uint32_t SRT_GFLAG_SYNCONMSG = 1; + +typedef enum SRT_MemberStatus +{ + SRT_GST_PENDING, // The socket is created correctly, but not yet ready for getting data. + SRT_GST_IDLE, // The socket is ready to be activated + SRT_GST_RUNNING, // The socket was already activated and is in use + SRT_GST_BROKEN // The last operation broke the socket, it should be closed. +} SRT_MEMBERSTATUS; + +struct SRT_SocketGroupData_ +{ + SRTSOCKET id; + struct sockaddr_storage peeraddr; // Don't want to expose sockaddr_any to public API + SRT_SOCKSTATUS sockstate; + uint16_t weight; + SRT_MEMBERSTATUS memberstate; + int result; + int token; +}; + +typedef struct SRT_SocketOptionObject SRT_SOCKOPT_CONFIG; + +typedef struct SRT_GroupMemberConfig_ +{ + SRTSOCKET id; + struct sockaddr_storage srcaddr; + struct sockaddr_storage peeraddr; // Don't want to expose sockaddr_any to public API + uint16_t weight; + SRT_SOCKOPT_CONFIG* config; + int errorcode; + int token; +} SRT_SOCKGROUPCONFIG; + +SRT_API SRTSOCKET srt_create_group(SRT_GROUP_TYPE); +SRT_API SRTSOCKET srt_groupof(SRTSOCKET socket); +SRT_API int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen); + +SRT_API SRT_SOCKOPT_CONFIG* srt_create_config(void); +SRT_API void srt_delete_config(SRT_SOCKOPT_CONFIG* config /*nullable*/); +SRT_API int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len); + +SRT_API SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src /*nullable*/, const struct sockaddr* adr, int namelen); +SRT_API int srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name[], int arraysize); #ifdef __cplusplus } diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt4udt.h b/trunk/3rdparty/srt-1-fit/srtcore/srt4udt.h deleted file mode 100644 index 49f6d9f7af..0000000000 --- a/trunk/3rdparty/srt-1-fit/srtcore/srt4udt.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SRT - Secure, Reliable, Transport - * Copyright (c) 2018 Haivision Systems Inc. - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - */ - -/***************************************************************************** -written by - Haivision Systems Inc. - *****************************************************************************/ - -#ifndef SRT4UDT_H -#define SRT4UDT_H - -#ifndef INC__SRTC_H -#error "This is protected header, used by udt.h. This shouldn't be included directly" -#endif - -//undef SRT_ENABLE_ECN 1 /* Early Congestion Notification (for source bitrate control) */ - -//undef SRT_DEBUG_TSBPD_OUTJITTER 1 /* Packet Delivery histogram */ -//undef SRT_DEBUG_TSBPD_DRIFT 1 /* Debug Encoder-Decoder Drift) */ -//undef SRT_DEBUG_TSBPD_WRAP 1 /* Debug packet timestamp wraparound */ -//undef SRT_DEBUG_TLPKTDROP_DROPSEQ 1 -//undef SRT_DEBUG_SNDQ_HIGHRATE 1 - - -/* -* SRT_ENABLE_CONNTIMEO -* Option UDT_CONNTIMEO added to the API to set/get the connection timeout. -* The UDT hard coded default of 3000 msec is too small for some large RTT (satellite) use cases. -* The SRT handshake (2 exchanges) needs 2 times the RTT to complete with no packet loss. -*/ -#define SRT_ENABLE_CONNTIMEO 1 - -/* -* SRT_ENABLE_NOCWND -* Set the congestion window at its max (then disabling it) to prevent stopping transmission -* when too many packets are not acknowledged. -* The congestion windows is the maximum distance in pkts since the last acknowledged packets. -*/ -#define SRT_ENABLE_NOCWND 1 - -/* -* SRT_ENABLE_NAKREPORT -* Send periodic NAK report for more efficient retransmission instead of relying on ACK timeout -* to retransmit all non-ACKed packets, very inefficient with real-time and no congestion window. -*/ -#define SRT_ENABLE_NAKREPORT 1 - -#define SRT_ENABLE_RCVBUFSZ_MAVG 1 /* Recv buffer size moving average */ -#define SRT_ENABLE_SNDBUFSZ_MAVG 1 /* Send buffer size moving average */ -#define SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ - -#define SRT_ENABLE_LOSTBYTESCOUNT 1 - - -/* -* SRT_ENABLE_IPOPTS -* Enable IP TTL and ToS setting -*/ -#define SRT_ENABLE_IPOPTS 1 - - -#define SRT_ENABLE_CLOSE_SYNCH 1 - -#endif /* SRT4UDT_H */ diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt_attr_defs.h b/trunk/3rdparty/srt-1-fit/srtcore/srt_attr_defs.h new file mode 100644 index 0000000000..ee4c85b0d4 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/srt_attr_defs.h @@ -0,0 +1,191 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2019 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v.2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ +/***************************************************************************** +The file contains various planform and compiler dependent attribute definitions +used by SRT library internally. + *****************************************************************************/ + +#ifndef INC_SRT_ATTR_DEFS_H +#define INC_SRT_ATTR_DEFS_H + +// ATTRIBUTES: +// +// SRT_ATR_UNUSED: declare an entity ALLOWED to be unused (prevents warnings) +// ATR_DEPRECATED: declare an entity deprecated (compiler should warn when used) +// ATR_NOEXCEPT: The true `noexcept` from C++11, or nothing if compiling in pre-C++11 mode +// ATR_NOTHROW: In C++11: `noexcept`. In pre-C++11: `throw()`. Required for GNU libstdc++. +// ATR_CONSTEXPR: In C++11: `constexpr`. Otherwise empty. +// ATR_OVERRIDE: In C++11: `override`. Otherwise empty. +// ATR_FINAL: In C++11: `final`. Otherwise empty. + +#ifdef __GNUG__ +#define ATR_DEPRECATED __attribute__((deprecated)) +#else +#define ATR_DEPRECATED +#endif + +#if defined(__cplusplus) && __cplusplus > 199711L +#define HAVE_CXX11 1 +// For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on, +// however it's only the "most required C++11 support". +#if defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 // 4.7 only! +#define ATR_NOEXCEPT +#define ATR_NOTHROW throw() +#define ATR_CONSTEXPR +#define ATR_OVERRIDE +#define ATR_FINAL +#else +#define HAVE_FULL_CXX11 1 +#define ATR_NOEXCEPT noexcept +#define ATR_NOTHROW noexcept +#define ATR_CONSTEXPR constexpr +#define ATR_OVERRIDE override +#define ATR_FINAL final +#endif +#elif defined(_MSC_VER) && _MSC_VER >= 1800 +// Microsoft Visual Studio supports C++11, but not fully, +// and still did not change the value of __cplusplus. Treat +// this special way. +// _MSC_VER == 1800 means Microsoft Visual Studio 2013. +#define HAVE_CXX11 1 +#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026 +#define HAVE_FULL_CXX11 1 +#define ATR_NOEXCEPT noexcept +#define ATR_NOTHROW noexcept +#define ATR_CONSTEXPR constexpr +#define ATR_OVERRIDE override +#define ATR_FINAL final +#else +#define ATR_NOEXCEPT +#define ATR_NOTHROW throw() +#define ATR_CONSTEXPR +#define ATR_OVERRIDE +#define ATR_FINAL +#endif +#else +#define HAVE_CXX11 0 +#define ATR_NOEXCEPT +#define ATR_NOTHROW throw() +#define ATR_CONSTEXPR +#define ATR_OVERRIDE +#define ATR_FINAL +#endif // __cplusplus + +#if !HAVE_CXX11 && defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1 +#error "The currently compiled application required C++11, but your compiler doesn't support it." +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Attributes for thread safety analysis +// - Clang TSA (https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutexheader). +// - MSVC SAL (partially). +// - Other compilers: none. +/////////////////////////////////////////////////////////////////////////////// +#if _MSC_VER >= 1920 +// In case of MSVC these attributes have to preceed the attributed objects (variable, function). +// E.g. SRT_ATTR_GUARDED_BY(mtx) int object; +// It is tricky to annotate e.g. the following function, as clang complaints it does not know 'm'. +// SRT_ATTR_EXCLUDES(m) SRT_ATTR_ACQUIRE(m) +// inline void enterCS(Mutex& m) { m.lock(); } +#define SRT_ATTR_CAPABILITY(expr) +#define SRT_ATTR_SCOPED_CAPABILITY +#define SRT_ATTR_GUARDED_BY(expr) _Guarded_by_(expr) +#define SRT_ATTR_PT_GUARDED_BY(expr) +#define SRT_ATTR_ACQUIRED_BEFORE(...) +#define SRT_ATTR_ACQUIRED_AFTER(...) +#define SRT_ATTR_REQUIRES(expr) _Requires_lock_held_(expr) +#define SRT_ATTR_REQUIRES2(expr1, expr2) _Requires_lock_held_(expr1) _Requires_lock_held_(expr2) +#define SRT_ATTR_REQUIRES_SHARED(...) +#define SRT_ATTR_ACQUIRE(expr) _Acquires_nonreentrant_lock_(expr) +#define SRT_ATTR_ACQUIRE_SHARED(...) +#define SRT_ATTR_RELEASE(expr) _Releases_lock_(expr) +#define SRT_ATTR_RELEASE_SHARED(...) +#define SRT_ATTR_RELEASE_GENERIC(...) +#define SRT_ATTR_TRY_ACQUIRE(...) _Acquires_nonreentrant_lock_(expr) +#define SRT_ATTR_TRY_ACQUIRE_SHARED(...) +#define SRT_ATTR_EXCLUDES(...) +#define SRT_ATTR_ASSERT_CAPABILITY(expr) +#define SRT_ATTR_ASSERT_SHARED_CAPABILITY(x) +#define SRT_ATTR_RETURN_CAPABILITY(x) +#define SRT_ATTR_NO_THREAD_SAFETY_ANALYSIS +#else + +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ > 5) +#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define SRT_ATTR_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(capability(x)) + +#define SRT_ATTR_SCOPED_CAPABILITY \ + THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) + +#define SRT_ATTR_GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define SRT_ATTR_PT_GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) + +#define SRT_ATTR_ACQUIRED_BEFORE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) + +#define SRT_ATTR_ACQUIRED_AFTER(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) + +#define SRT_ATTR_REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define SRT_ATTR_REQUIRES2(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +#define SRT_ATTR_REQUIRES_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__)) + +#define SRT_ATTR_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__)) + +#define SRT_ATTR_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__)) + +#define SRT_ATTR_RELEASE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__)) + +#define SRT_ATTR_RELEASE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__)) + +#define SRT_ATTR_RELEASE_GENERIC(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(release_generic_capability(__VA_ARGS__)) + +#define SRT_ATTR_TRY_ACQUIRE(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__)) + +#define SRT_ATTR_TRY_ACQUIRE_SHARED(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) + +#define SRT_ATTR_EXCLUDES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) + +#define SRT_ATTR_ASSERT_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x)) + +#define SRT_ATTR_ASSERT_SHARED_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x)) + +#define SRT_ATTR_RETURN_CAPABILITY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) + +#define SRT_ATTR_NO_THREAD_SAFETY_ANALYSIS \ + THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) + +#endif // not _MSC_VER + +#endif // INC_SRT_ATTR_DEFS_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt_c_api.cpp b/trunk/3rdparty/srt-1-fit/srtcore/srt_c_api.cpp index 56d875f7b6..f675e990ac 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/srt_c_api.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/srt_c_api.cpp @@ -13,17 +13,18 @@ written by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include #include -#if __APPLE__ - #include "TargetConditionals.h" -#endif #include "srt.h" #include "common.h" +#include "packet.h" #include "core.h" #include "utilities.h" using namespace std; +using namespace srt; extern "C" { @@ -31,51 +32,111 @@ extern "C" { int srt_startup() { return CUDT::startup(); } int srt_cleanup() { return CUDT::cleanup(); } -SRTSOCKET srt_socket(int af, int type, int protocol) { return CUDT::socket(af, type, protocol); } -SRTSOCKET srt_create_socket() +// Socket creation. +SRTSOCKET srt_socket(int , int , int ) { return CUDT::socket(); } +SRTSOCKET srt_create_socket() { return CUDT::socket(); } + +#if ENABLE_BONDING +// Group management. +SRTSOCKET srt_create_group(SRT_GROUP_TYPE gt) { return CUDT::createGroup(gt); } +SRTSOCKET srt_groupof(SRTSOCKET socket) { return CUDT::getGroupOfSocket(socket); } +int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen) +{ + return CUDT::getGroupData(socketgroup, output, inoutlen); +} + +SRT_SOCKOPT_CONFIG* srt_create_config() { - // XXX This must include rework around m_iIPVersion. This must be - // abandoned completely and all "IP VERSION" thing should rely on - // the exact specification in the 'sockaddr' objects passed to other functions, - // that is, the "current IP Version" remains undefined until any of - // srt_bind() or srt_connect() function is done. And when any of these - // functions are being called, the IP version is contained in the - // sockaddr object passed there. + return new SRT_SocketOptionObject; +} - // Until this rework is done, srt_create_socket() will set the - // default AF_INET family. +int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) +{ + if (!config) + return -1; - // Note that all arguments except the first one here are ignored. - return CUDT::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (!config->add(option, contents, len)) + return -1; + + return 0; +} + +int srt_connect_group(SRTSOCKET group, + SRT_SOCKGROUPCONFIG name[], int arraysize) +{ + return CUDT::connectLinks(group, name, arraysize); } +#else + +SRTSOCKET srt_create_group(SRT_GROUP_TYPE) { return SRT_INVALID_SOCK; } +SRTSOCKET srt_groupof(SRTSOCKET) { return SRT_INVALID_SOCK; } +int srt_group_data(SRTSOCKET, SRT_SOCKGROUPDATA*, size_t*) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } +SRT_SOCKOPT_CONFIG* srt_create_config() { return NULL; } +int srt_config_add(SRT_SOCKOPT_CONFIG*, SRT_SOCKOPT, const void*, int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } + +int srt_connect_group(SRTSOCKET, SRT_SOCKGROUPCONFIG[], int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } + +#endif + +SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src, const struct sockaddr* dst, int namelen) +{ + SRT_SOCKGROUPCONFIG data; +#if ENABLE_BONDING + data.errorcode = SRT_SUCCESS; +#else + data.errorcode = SRT_EINVOP; +#endif + data.id = -1; + data.token = -1; + data.weight = 0; + data.config = NULL; + if (src) + memcpy(&data.srcaddr, src, namelen); + else + { + memset(&data.srcaddr, 0, sizeof data.srcaddr); + // Still set the family according to the target address + data.srcaddr.ss_family = dst->sa_family; + } + memcpy(&data.peeraddr, dst, namelen); + return data; +} + +void srt_delete_config(SRT_SOCKOPT_CONFIG* in) +{ + delete in; +} + +// Binding and connection management int srt_bind(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::bind(u, name, namelen); } -int srt_bind_peerof(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } +int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } int srt_listen(SRTSOCKET u, int backlog) { return CUDT::listen(u, backlog); } SRTSOCKET srt_accept(SRTSOCKET u, struct sockaddr * addr, int * addrlen) { return CUDT::accept(u, addr, addrlen); } -int srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, 0); } +SRTSOCKET srt_accept_bond(const SRTSOCKET lsns[], int lsize, int64_t msTimeOut) { return CUDT::accept_bond(lsns, lsize, msTimeOut); } +int srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } int srt_connect_debug(SRTSOCKET u, const struct sockaddr * name, int namelen, int forced_isn) { return CUDT::connect(u, name, namelen, forced_isn); } +int srt_connect_bind(SRTSOCKET u, + const struct sockaddr* source, + const struct sockaddr* target, int target_len) +{ + return CUDT::connect(u, source, target, target_len); +} int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen) { bool yes = 1; - CUDT::setsockopt(u, 0, UDT_RENDEZVOUS, &yes, sizeof yes); + CUDT::setsockopt(u, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); // Note: PORT is 16-bit and at the same location in both sockaddr_in and sockaddr_in6. // Just as a safety precaution, check the structs. if ( (local_name->sa_family != AF_INET && local_name->sa_family != AF_INET6) || local_name->sa_family != remote_name->sa_family) - return SRT_EINVPARAM; - - sockaddr_in* local_sin = (sockaddr_in*)local_name; - sockaddr_in* remote_sin = (sockaddr_in*)remote_name; + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); - if (local_sin->sin_port != remote_sin->sin_port) - return SRT_EINVPARAM; - - int st = srt_bind(u, local_name, local_namelen); - if ( st != 0 ) + const int st = srt_bind(u, local_name, local_namelen); + if (st != 0) return st; return srt_connect(u, remote_name, remote_namelen); @@ -111,17 +172,17 @@ int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen int srt_send(SRTSOCKET u, const char * buf, int len) { return CUDT::send(u, buf, len, 0); } int srt_recv(SRTSOCKET u, char * buf, int len) { return CUDT::recv(u, buf, len, 0); } int srt_sendmsg(SRTSOCKET u, const char * buf, int len, int ttl, int inorder) { return CUDT::sendmsg(u, buf, len, ttl, 0!= inorder); } -int srt_recvmsg(SRTSOCKET u, char * buf, int len) { uint64_t ign_srctime; return CUDT::recvmsg(u, buf, len, ign_srctime); } +int srt_recvmsg(SRTSOCKET u, char * buf, int len) { int64_t ign_srctime; return CUDT::recvmsg(u, buf, len, ign_srctime); } int64_t srt_sendfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) { if (!path || !offset ) { - return CUDT::setError(CUDTException(MJ_NOTSUP, MN_INVAL, 0)); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } fstream ifs(path, ios::binary | ios::in); if (!ifs) { - return CUDT::setError(CUDTException(MJ_FILESYSTEM, MN_READFAIL, 0)); + return CUDT::APIError(MJ_FILESYSTEM, MN_READFAIL, 0); } int64_t ret = CUDT::sendfile(u, ifs, *offset, size, block); ifs.close(); @@ -132,19 +193,29 @@ int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t siz { if (!path || !offset ) { - return CUDT::setError(CUDTException(MJ_NOTSUP, MN_INVAL, 0)); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } fstream ofs(path, ios::binary | ios::out); if (!ofs) { - return CUDT::setError(CUDTException(MJ_FILESYSTEM, MN_WRAVAIL, 0)); + return CUDT::APIError(MJ_FILESYSTEM, MN_WRAVAIL, 0); } int64_t ret = CUDT::recvfile(u, ofs, *offset, size, block); ofs.close(); return ret; } -extern const SRT_MSGCTRL srt_msgctrl_default = { 0, -1, false, 0, 0, 0, 0 }; +extern const SRT_MSGCTRL srt_msgctrl_default = { + 0, // no flags set + SRT_MSGTTL_INF, + false, // not in order (matters for msg mode only) + PB_SUBSEQUENT, + 0, // srctime: take "now" time + SRT_SEQNO_NONE, + SRT_MSGNO_NONE, + NULL, // grpdata not supplied + 0 // idem +}; void srt_msgctrl_init(SRT_MSGCTRL* mctrl) { @@ -155,17 +226,17 @@ int srt_sendmsg2(SRTSOCKET u, const char * buf, int len, SRT_MSGCTRL *mctrl) { // Allow NULL mctrl in the API, but not internally. if (mctrl) - return CUDT::sendmsg2(u, buf, len, Ref(*mctrl)); + return CUDT::sendmsg2(u, buf, len, (*mctrl)); SRT_MSGCTRL mignore = srt_msgctrl_default; - return CUDT::sendmsg2(u, buf, len, Ref(mignore)); + return CUDT::sendmsg2(u, buf, len, (mignore)); } int srt_recvmsg2(SRTSOCKET u, char * buf, int len, SRT_MSGCTRL *mctrl) { if (mctrl) - return CUDT::recvmsg2(u, buf, len, Ref(*mctrl)); + return CUDT::recvmsg2(u, buf, len, (*mctrl)); SRT_MSGCTRL mignore = srt_msgctrl_default; - return CUDT::recvmsg2(u, buf, len, Ref(mignore)); + return CUDT::recvmsg2(u, buf, len, (mignore)); } const char* srt_getlasterror_str() { return UDT::getlasterror().getErrorMessage(); } @@ -179,8 +250,8 @@ int srt_getlasterror(int* loc_errno) const char* srt_strerror(int code, int err) { - static CUDTException e; - e = CUDTException(CodeMajor(code/1000), CodeMinor(code%1000), err); + static srt::CUDTException e; + e = srt::CUDTException(CodeMajor(code/1000), CodeMinor(code%1000), err); return(e.getErrorMessage()); } @@ -198,6 +269,8 @@ SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u) { return SRT_SOCKSTATUS((int)CUDT:: // event mechanism int srt_epoll_create() { return CUDT::epoll_create(); } +int srt_epoll_clear_usocks(int eit) { return CUDT::epoll_clear_usocks(eit); } + // You can use either SRT_EPOLL_* flags or EPOLL* flags from , both are the same. IN/OUT/ERR only. // events == NULL accepted, in which case all flags are set. int srt_epoll_add_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_add_usock(eid, u, events); } @@ -302,17 +375,104 @@ int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes) return CUDT::getsndbuffer(sock, blocks, bytes); } -enum SRT_REJECT_REASON srt_getrejectreason(SRTSOCKET sock) +int srt_getrejectreason(SRTSOCKET sock) { return CUDT::rejectReason(sock); } +int srt_setrejectreason(SRTSOCKET sock, int value) +{ + return CUDT::rejectReason(sock, value); +} + int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { if (!hook) - return CUDT::setError(CUDTException(MJ_NOTSUP, MN_INVAL)); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL); return CUDT::installAcceptHook(lsn, hook, opaq); } +int srt_connect_callback(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) +{ + if (!hook) + return CUDT::APIError(MJ_NOTSUP, MN_INVAL); + + return CUDT::installConnectHook(lsn, hook, opaq); +} + +uint32_t srt_getversion() +{ + return SrtVersion(SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH); +} + +int64_t srt_time_now() +{ + return srt::sync::count_microseconds(srt::sync::steady_clock::now().time_since_epoch()); +} + +int64_t srt_connection_time(SRTSOCKET sock) +{ + return CUDT::socketStartTime(sock); +} + +int srt_clock_type() +{ + return SRT_SYNC_CLOCK; +} + +const char* const srt_rejection_reason_msg [] = { + "Unknown or erroneous", + "Error in system calls", + "Peer rejected connection", + "Resource allocation failure", + "Rogue peer or incorrect parameters", + "Listener's backlog exceeded", + "Internal Program Error", + "Socket is being closed", + "Peer version too old", + "Rendezvous-mode cookie collision", + "Incorrect passphrase", + "Password required or unexpected", + "MessageAPI/StreamAPI collision", + "Congestion controller type collision", + "Packet Filter settings error", + "Group settings collision", + "Connection timeout" +}; + +// Deprecated, available in SRT API. +extern const char* const srt_rejectreason_msg[] = { + srt_rejection_reason_msg[0], + srt_rejection_reason_msg[1], + srt_rejection_reason_msg[2], + srt_rejection_reason_msg[3], + srt_rejection_reason_msg[4], + srt_rejection_reason_msg[5], + srt_rejection_reason_msg[6], + srt_rejection_reason_msg[7], + srt_rejection_reason_msg[8], + srt_rejection_reason_msg[9], + srt_rejection_reason_msg[10], + srt_rejection_reason_msg[11], + srt_rejection_reason_msg[12], + srt_rejection_reason_msg[13], + srt_rejection_reason_msg[14], + srt_rejection_reason_msg[15], + srt_rejection_reason_msg[16] +}; + +const char* srt_rejectreason_str(int id) +{ + if (id >= SRT_REJC_PREDEFINED) + { + return "Application-defined rejection reason"; + } + + static const size_t ra_size = Size(srt_rejection_reason_msg); + if (size_t(id) >= ra_size) + return srt_rejection_reason_msg[0]; + return srt_rejection_reason_msg[id]; +} + } diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.c b/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.c index 3db4a486bd..1473a7b946 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.c +++ b/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.c @@ -16,12 +16,14 @@ written by // Prevents from misconfiguration through preprocessor. +#include "platform_sys.h" + #include #include #include #include -#if defined(__unix__) && !defined(BSD) +#if defined(__unix__) && !defined(BSD) && !defined(SUNOS) #include #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h b/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h index c05606bbd7..960c1b85a2 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h @@ -14,15 +14,15 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef HAISRT_COMPAT_H__ -#define HAISRT_COMPAT_H__ +#ifndef INC_SRT_COMPAT_H +#define INC_SRT_COMPAT_H #include #include #ifndef SRT_API #ifdef _WIN32 - #ifndef __MINGW__ + #ifndef __MINGW32__ #ifdef SRT_DYNAMIC #ifdef SRT_EXPORTS #define SRT_API __declspec(dllexport) @@ -78,6 +78,7 @@ SRT_API const char * SysStrError(int errnum, char * buf, size_t buflen); #include +#include inline std::string SysStrError(int errnum) { char buf[1024]; @@ -93,7 +94,10 @@ inline struct tm SysLocalTime(time_t tt) if (rr == 0) return tms; #else - tms = *localtime_r(&tt, &tms); + + // Ignore the error, state that if something + // happened, you simply have a pre-cleared tms. + localtime_r(&tt, &tms); #endif return tms; @@ -102,4 +106,4 @@ inline struct tm SysLocalTime(time_t tt) #endif // defined C++ -#endif // HAISRT_COMPAT_H__ +#endif // INC_SRT_COMPAT_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/stats.h b/trunk/3rdparty/srt-1-fit/srtcore/stats.h new file mode 100644 index 0000000000..bce60761b5 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/stats.h @@ -0,0 +1,221 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#ifndef INC_SRT_STATS_H +#define INC_SRT_STATS_H + +#include "platform_sys.h" +#include "packet.h" + +namespace srt +{ +namespace stats +{ + +class Packets +{ +public: + Packets() : m_count(0) {} + + Packets(uint32_t num) : m_count(num) {} + + void reset() + { + m_count = 0; + } + + Packets& operator+= (const Packets& other) + { + m_count += other.m_count; + return *this; + } + + uint32_t count() const + { + return m_count; + } + +private: + uint32_t m_count; +}; + +class BytesPackets +{ +public: + BytesPackets() + : m_bytes(0) + , m_packets(0) + {} + + BytesPackets(uint64_t bytes, uint32_t n = 1) + : m_bytes(bytes) + , m_packets(n) + {} + + BytesPackets& operator+= (const BytesPackets& other) + { + m_bytes += other.m_bytes; + m_packets += other.m_packets; + return *this; + } + +public: + void reset() + { + m_packets = 0; + m_bytes = 0; + } + + void count(uint64_t bytes, size_t n = 1) + { + m_packets += (uint32_t) n; + m_bytes += bytes; + } + + uint64_t bytes() const + { + return m_bytes; + } + + uint32_t count() const + { + return m_packets; + } + + uint64_t bytesWithHdr() const + { + return m_bytes + m_packets * CPacket::SRT_DATA_HDR_SIZE; + } + +private: + uint64_t m_bytes; + uint32_t m_packets; +}; + +template +struct Metric +{ + METRIC_TYPE trace; + METRIC_TYPE total; + + void count(METRIC_TYPE val) + { + trace += val; + total += val; + } + + void reset() + { + trace.reset(); + total.reset(); + } + + void resetTrace() + { + trace.reset(); + } +}; + +/// Sender-side statistics. +struct Sender +{ + Metric sent; + Metric sentUnique; + Metric sentRetrans; // The number of data packets retransmitted by the sender. + Metric lost; // The number of packets reported lost (including repeated reports) to the sender in NAKs. + Metric dropped; // The number of data packets dropped by the sender. + + Metric sentFilterExtra; // The number of packets generate by the packet filter and sent by the sender. + + Metric recvdAck; // The number of ACK packets received by the sender. + Metric recvdNak; // The number of ACK packets received by the sender. + + void reset() + { + sent.reset(); + sentUnique.reset(); + sentRetrans.reset(); + lost.reset(); + dropped.reset(); + recvdAck.reset(); + recvdNak.reset(); + sentFilterExtra.reset(); + } + + void resetTrace() + { + sent.resetTrace(); + sentUnique.resetTrace(); + sentRetrans.resetTrace(); + lost.resetTrace(); + dropped.resetTrace(); + recvdAck.resetTrace(); + recvdNak.resetTrace(); + sentFilterExtra.resetTrace(); + } +}; + +/// Receiver-side statistics. +struct Receiver +{ + Metric recvd; + Metric recvdUnique; + Metric recvdRetrans; // The number of retransmitted data packets received by the receiver. + Metric lost; // The number of packets detected by the receiver as lost. + Metric dropped; // The number of packets dropped by the receiver (as too-late to be delivered). + Metric recvdBelated; // The number of belated packets received (dropped as too late but eventually received). + Metric undecrypted; // The number of packets received by the receiver that failed to be decrypted. + + Metric recvdFilterExtra; // The number of filter packets (e.g. FEC) received by the receiver. + Metric suppliedByFilter; // The number of lost packets got from the packet filter at the receiver side (e.g. loss recovered by FEC). + Metric lossFilter; // The number of lost DATA packets not recovered by the packet filter at the receiver side. + + Metric sentAck; // The number of ACK packets sent by the receiver. + Metric sentNak; // The number of NACK packets sent by the receiver. + + void reset() + { + recvd.reset(); + recvdUnique.reset(); + recvdRetrans.reset(); + lost.reset(); + dropped.reset(); + recvdBelated.reset(); + undecrypted.reset(); + recvdFilterExtra.reset(); + suppliedByFilter.reset(); + lossFilter.reset(); + sentAck.reset(); + sentNak.reset(); + } + + void resetTrace() + { + recvd.resetTrace(); + recvdUnique.resetTrace(); + recvdRetrans.resetTrace(); + lost.resetTrace(); + dropped.resetTrace(); + recvdBelated.resetTrace(); + undecrypted.resetTrace(); + recvdFilterExtra.resetTrace(); + suppliedByFilter.resetTrace(); + lossFilter.resetTrace(); + sentAck.resetTrace(); + sentNak.resetTrace(); + } +}; + +} // namespace stats +} // namespace srt + +#endif // INC_SRT_STATS_H + + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/strerror_defs.cpp b/trunk/3rdparty/srt-1-fit/srtcore/strerror_defs.cpp new file mode 100644 index 0000000000..1b4c72e4e5 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/strerror_defs.cpp @@ -0,0 +1,154 @@ + + /* + WARNING: Generated from ../scripts/generate-error-types.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + +#include + + +namespace srt +{ +// MJ_SUCCESS 'Success' + +const char* strerror_msgs_success [] = { + "Success", // MN_NONE = 0 + "" +}; + +// MJ_SETUP 'Connection setup failure' + +const char* strerror_msgs_setup [] = { + "Connection setup failure", // MN_NONE = 0 + "Connection setup failure: connection timed out", // MN_TIMEOUT = 1 + "Connection setup failure: connection rejected", // MN_REJECTED = 2 + "Connection setup failure: unable to create/configure SRT socket", // MN_NORES = 3 + "Connection setup failure: aborted for security reasons", // MN_SECURITY = 4 + "Connection setup failure: socket closed during operation", // MN_CLOSED = 5 + "" +}; + +// MJ_CONNECTION '' + +const char* strerror_msgs_connection [] = { + "", // MN_NONE = 0 + "Connection was broken", // MN_CONNLOST = 1 + "Connection does not exist", // MN_NOCONN = 2 + "" +}; + +// MJ_SYSTEMRES 'System resource failure' + +const char* strerror_msgs_systemres [] = { + "System resource failure", // MN_NONE = 0 + "System resource failure: unable to create new threads", // MN_THREAD = 1 + "System resource failure: unable to allocate buffers", // MN_MEMORY = 2 + "System resource failure: unable to allocate a system object", // MN_OBJECT = 3 + "" +}; + +// MJ_FILESYSTEM 'File system failure' + +const char* strerror_msgs_filesystem [] = { + "File system failure", // MN_NONE = 0 + "File system failure: cannot seek read position", // MN_SEEKGFAIL = 1 + "File system failure: failure in read", // MN_READFAIL = 2 + "File system failure: cannot seek write position", // MN_SEEKPFAIL = 3 + "File system failure: failure in write", // MN_WRITEFAIL = 4 + "" +}; + +// MJ_NOTSUP 'Operation not supported' + +const char* strerror_msgs_notsup [] = { + "Operation not supported", // MN_NONE = 0 + "Operation not supported: Cannot do this operation on a BOUND socket", // MN_ISBOUND = 1 + "Operation not supported: Cannot do this operation on a CONNECTED socket", // MN_ISCONNECTED = 2 + "Operation not supported: Bad parameters", // MN_INVAL = 3 + "Operation not supported: Invalid socket ID", // MN_SIDINVAL = 4 + "Operation not supported: Cannot do this operation on an UNBOUND socket", // MN_ISUNBOUND = 5 + "Operation not supported: Socket is not in listening state", // MN_NOLISTEN = 6 + "Operation not supported: Listen/accept is not supported in rendezous connection setup", // MN_ISRENDEZVOUS = 7 + "Operation not supported: Cannot call connect on UNBOUND socket in rendezvous connection setup", // MN_ISRENDUNBOUND = 8 + "Operation not supported: Incorrect use of Message API (sendmsg/recvmsg).", // MN_INVALMSGAPI = 9 + "Operation not supported: Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile).", // MN_INVALBUFFERAPI = 10 + "Operation not supported: Another socket is already listening on the same port", // MN_BUSY = 11 + "Operation not supported: Message is too large to send (it must be less than the SRT send buffer size)", // MN_XSIZE = 12 + "Operation not supported: Invalid epoll ID", // MN_EIDINVAL = 13 + "Operation not supported: All sockets removed from epoll, waiting would deadlock", // MN_EEMPTY = 14 + "Operation not supported: Another socket is bound to that port and is not reusable for requested settings", // MN_BUSYPORT = 15 + "" +}; + +// MJ_AGAIN 'Non-blocking call failure' + +const char* strerror_msgs_again [] = { + "Non-blocking call failure", // MN_NONE = 0 + "Non-blocking call failure: no buffer available for sending", // MN_WRAVAIL = 1 + "Non-blocking call failure: no data available for reading", // MN_RDAVAIL = 2 + "Non-blocking call failure: transmission timed out", // MN_XMTIMEOUT = 3 + "Non-blocking call failure: early congestion notification", // MN_CONGESTION = 4 + "" +}; + +// MJ_PEERERROR 'The peer side has signaled an error' + +const char* strerror_msgs_peererror [] = { + "The peer side has signaled an error", // MN_NONE = 0 + "" +}; + + +const char** strerror_array_major [] = { + strerror_msgs_success, // MJ_SUCCESS = 0 + strerror_msgs_setup, // MJ_SETUP = 1 + strerror_msgs_connection, // MJ_CONNECTION = 2 + strerror_msgs_systemres, // MJ_SYSTEMRES = 3 + strerror_msgs_filesystem, // MJ_FILESYSTEM = 4 + strerror_msgs_notsup, // MJ_NOTSUP = 5 + strerror_msgs_again, // MJ_AGAIN = 6 + strerror_msgs_peererror, // MJ_PEERERROR = 7 + NULL +}; + +#define SRT_ARRAY_SIZE(ARR) sizeof(ARR) / sizeof(ARR[0]) + +const size_t strerror_array_sizes [] = { + SRT_ARRAY_SIZE(strerror_msgs_success) - 1, + SRT_ARRAY_SIZE(strerror_msgs_setup) - 1, + SRT_ARRAY_SIZE(strerror_msgs_connection) - 1, + SRT_ARRAY_SIZE(strerror_msgs_systemres) - 1, + SRT_ARRAY_SIZE(strerror_msgs_filesystem) - 1, + SRT_ARRAY_SIZE(strerror_msgs_notsup) - 1, + SRT_ARRAY_SIZE(strerror_msgs_again) - 1, + SRT_ARRAY_SIZE(strerror_msgs_peererror) - 1, + 0 +}; + + +const char* strerror_get_message(size_t major, size_t minor) +{ + static const char* const undefined = "UNDEFINED ERROR"; + + // Extract the major array + if (major >= sizeof(strerror_array_major)/sizeof(const char**)) + { + return undefined; + } + + const char** array = strerror_array_major[major]; + const size_t size = strerror_array_sizes[major]; + + if (minor >= size) + { + return undefined; + } + + return array[minor]; +} + + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/sync.cpp b/trunk/3rdparty/srt-1-fit/srtcore/sync.cpp new file mode 100644 index 0000000000..7f2bc12ab9 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/sync.cpp @@ -0,0 +1,355 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2019 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ +#include "platform_sys.h" + +#include +#include +#include +#include "sync.h" +#include "srt.h" +#include "srt_compat.h" +#include "logging.h" +#include "common.h" + +// HAVE_CXX11 is defined in utilities.h, included with common.h. +// The following conditional inclusion must go after common.h. +#if HAVE_CXX11 +#include +#endif + +namespace srt_logging +{ + extern Logger inlog; +} +using namespace srt_logging; +using namespace std; + +namespace srt +{ +namespace sync +{ + +std::string FormatTime(const steady_clock::time_point& timestamp) +{ + if (is_zero(timestamp)) + { + // Use special string for 0 + return "00:00:00.000000 [STDY]"; + } + + const int decimals = clockSubsecondPrecision(); + const uint64_t total_sec = count_seconds(timestamp.time_since_epoch()); + const uint64_t days = total_sec / (60 * 60 * 24); + const uint64_t hours = total_sec / (60 * 60) - days * 24; + const uint64_t minutes = total_sec / 60 - (days * 24 * 60) - hours * 60; + const uint64_t seconds = total_sec - (days * 24 * 60 * 60) - hours * 60 * 60 - minutes * 60; + ostringstream out; + if (days) + out << days << "D "; + out << setfill('0') << setw(2) << hours << ":" + << setfill('0') << setw(2) << minutes << ":" + << setfill('0') << setw(2) << seconds << "." + << setfill('0') << setw(decimals) << (timestamp - seconds_from(total_sec)).time_since_epoch().count() << " [STDY]"; + return out.str(); +} + +std::string FormatTimeSys(const steady_clock::time_point& timestamp) +{ + const time_t now_s = ::time(NULL); // get current time in seconds + const steady_clock::time_point now_timestamp = steady_clock::now(); + const int64_t delta_us = count_microseconds(timestamp - now_timestamp); + const int64_t delta_s = + floor((static_cast(count_microseconds(now_timestamp.time_since_epoch()) % 1000000) + delta_us) / 1000000.0); + const time_t tt = now_s + delta_s; + struct tm tm = SysLocalTime(tt); // in seconds + char tmp_buf[512]; + strftime(tmp_buf, 512, "%X.", &tm); + + ostringstream out; + out << tmp_buf << setfill('0') << setw(6) << (count_microseconds(timestamp.time_since_epoch()) % 1000000) << " [SYST]"; + return out.str(); +} + + +#ifdef ENABLE_STDCXX_SYNC +bool StartThread(CThread& th, ThreadFunc&& f, void* args, const string& name) +#else +bool StartThread(CThread& th, void* (*f) (void*), void* args, const string& name) +#endif +{ + ThreadName tn(name); + try + { +#if HAVE_FULL_CXX11 || defined(ENABLE_STDCXX_SYNC) + th = CThread(f, args); +#else + // No move semantics in C++03, therefore using a dedicated function + th.create_thread(f, args); +#endif + } + catch (const CThreadException& e) + { + HLOGC(inlog.Debug, log << name << ": failed to start thread. " << e.what()); + return false; + } + return true; +} + +} // namespace sync +} // namespace srt + +//////////////////////////////////////////////////////////////////////////////// +// +// CEvent class +// +//////////////////////////////////////////////////////////////////////////////// + +srt::sync::CEvent::CEvent() +{ +#ifndef _WIN32 + m_cond.init(); +#endif +} + +srt::sync::CEvent::~CEvent() +{ +#ifndef _WIN32 + m_cond.destroy(); +#endif +} + +bool srt::sync::CEvent::lock_wait_until(const TimePoint& tp) +{ + UniqueLock lock(m_lock); + return m_cond.wait_until(lock, tp); +} + +void srt::sync::CEvent::notify_one() +{ + return m_cond.notify_one(); +} + +void srt::sync::CEvent::notify_all() +{ + return m_cond.notify_all(); +} + +bool srt::sync::CEvent::lock_wait_for(const steady_clock::duration& rel_time) +{ + UniqueLock lock(m_lock); + return m_cond.wait_for(lock, rel_time); +} + +bool srt::sync::CEvent::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) +{ + return m_cond.wait_for(lock, rel_time); +} + +void srt::sync::CEvent::lock_wait() +{ + UniqueLock lock(m_lock); + return wait(lock); +} + +void srt::sync::CEvent::wait(UniqueLock& lock) +{ + return m_cond.wait(lock); +} + +namespace srt { +namespace sync { + +srt::sync::CEvent g_Sync; + +} // namespace sync +} // namespace srt + +//////////////////////////////////////////////////////////////////////////////// +// +// Timer +// +//////////////////////////////////////////////////////////////////////////////// + +srt::sync::CTimer::CTimer() +{ +} + + +srt::sync::CTimer::~CTimer() +{ +} + + +bool srt::sync::CTimer::sleep_until(TimePoint tp) +{ + // The class member m_sched_time can be used to interrupt the sleep. + // Refer to Timer::interrupt(). + enterCS(m_event.mutex()); + m_tsSchedTime = tp; + leaveCS(m_event.mutex()); + +#if USE_BUSY_WAITING +#if defined(_WIN32) + // 10 ms on Windows: bad accuracy of timers + const steady_clock::duration + td_threshold = milliseconds_from(10); +#else + // 1 ms on non-Windows platforms + const steady_clock::duration + td_threshold = milliseconds_from(1); +#endif +#endif // USE_BUSY_WAITING + + TimePoint cur_tp = steady_clock::now(); + + while (cur_tp < m_tsSchedTime) + { +#if USE_BUSY_WAITING + steady_clock::duration td_wait = m_tsSchedTime - cur_tp; + if (td_wait <= 2 * td_threshold) + break; + + td_wait -= td_threshold; + m_event.lock_wait_for(td_wait); +#else + m_event.lock_wait_until(m_tsSchedTime); +#endif // USE_BUSY_WAITING + + cur_tp = steady_clock::now(); + } + +#if USE_BUSY_WAITING + while (cur_tp < m_tsSchedTime) + { +#ifdef IA32 + __asm__ volatile ("pause; rep; nop; nop; nop; nop; nop;"); +#elif IA64 + __asm__ volatile ("nop 0; nop 0; nop 0; nop 0; nop 0;"); +#elif AMD64 + __asm__ volatile ("nop; nop; nop; nop; nop;"); +#elif defined(_WIN32) && !defined(__MINGW32__) + __nop(); + __nop(); + __nop(); + __nop(); + __nop(); +#endif + + cur_tp = steady_clock::now(); + } +#endif // USE_BUSY_WAITING + + return cur_tp >= m_tsSchedTime; +} + + +void srt::sync::CTimer::interrupt() +{ + UniqueLock lck(m_event.mutex()); + m_tsSchedTime = steady_clock::now(); + m_event.notify_all(); +} + + +void srt::sync::CTimer::tick() +{ + m_event.notify_one(); +} + + +void srt::sync::CGlobEvent::triggerEvent() +{ + return g_Sync.notify_one(); +} + +bool srt::sync::CGlobEvent::waitForEvent() +{ + return g_Sync.lock_wait_for(milliseconds_from(10)); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Random +// +//////////////////////////////////////////////////////////////////////////////// + +namespace srt +{ +#if HAVE_CXX11 +static std::mt19937& randomGen() +{ + static std::random_device s_RandomDevice; + static std::mt19937 s_GenMT19937(s_RandomDevice()); + return s_GenMT19937; +} +#elif defined(_WIN32) && defined(__MINGW32__) +static void initRandSeed() +{ + const int64_t seed = sync::steady_clock::now().time_since_epoch().count(); + srand((unsigned int) seed); +} +static pthread_once_t s_InitRandSeedOnce = PTHREAD_ONCE_INIT; +#else + +static unsigned int genRandSeed() +{ + // Duration::count() does not depend on any global objects, + // therefore it is preferred over count_microseconds(..). + const int64_t seed = sync::steady_clock::now().time_since_epoch().count(); + return (unsigned int) seed; +} + +static unsigned int* getRandSeed() +{ + static unsigned int s_uRandSeed = genRandSeed(); + return &s_uRandSeed; +} + +#endif +} + +int srt::sync::genRandomInt(int minVal, int maxVal) +{ + // This Meyers singleton initialization is thread-safe since C++11, but is not thread-safe in C++03. + // A mutex to protect simultaneous access to the random device. + // Thread-local storage could be used here instead to store the seed / random device. + // However the generator is not used often (Initial Socket ID, Initial sequence number, FileCC), + // so sharing a single seed among threads should not impact the performance. + static sync::Mutex s_mtxRandomDevice; + sync::ScopedLock lck(s_mtxRandomDevice); +#if HAVE_CXX11 + uniform_int_distribution<> dis(minVal, maxVal); + return dis(randomGen()); +#else +#if defined(__MINGW32__) + // No rand_r(..) for MinGW. + pthread_once(&s_InitRandSeedOnce, initRandSeed); + // rand() returns a pseudo-random integer in the range 0 to RAND_MAX inclusive + // (i.e., the mathematical range [0, RAND_MAX]). + // Therefore, rand_0_1 belongs to [0.0, 1.0]. + const double rand_0_1 = double(rand()) / RAND_MAX; +#else // not __MINGW32__ + // rand_r(..) returns a pseudo-random integer in the range 0 to RAND_MAX inclusive + // (i.e., the mathematical range [0, RAND_MAX]). + // Therefore, rand_0_1 belongs to [0.0, 1.0]. + const double rand_0_1 = double(rand_r(getRandSeed())) / RAND_MAX; +#endif + + // Map onto [minVal, maxVal]. + // Note. There is a minuscule probablity to get maxVal+1 as the result. + // So we have to use long long to handle cases when maxVal = INT32_MAX. + // Also we must check 'res' does not exceed maxVal, + // which may happen if rand_0_1 = 1, even though the chances are low. + const long long llMaxVal = maxVal; + const int res = minVal + static_cast((llMaxVal + 1 - minVal) * rand_0_1); + return min(res, maxVal); +#endif // HAVE_CXX11 +} + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/sync.h b/trunk/3rdparty/srt-1-fit/srtcore/sync.h new file mode 100644 index 0000000000..5d62536eb0 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/sync.h @@ -0,0 +1,945 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2019 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ +#pragma once +#ifndef INC_SRT_SYNC_H +#define INC_SRT_SYNC_H + +#include +#include +#ifdef ENABLE_STDCXX_SYNC +#include +#include +#include +#include +#include +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_STDCXX_STEADY +#define SRT_SYNC_CLOCK_STR "STDCXX_STEADY" +#else +#include + +// Defile clock type to use +#ifdef IA32 +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_IA32_RDTSC +#define SRT_SYNC_CLOCK_STR "IA32_RDTSC" +#elif defined(IA64) +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_IA64_ITC +#define SRT_SYNC_CLOCK_STR "IA64_ITC" +#elif defined(AMD64) +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_AMD64_RDTSC +#define SRT_SYNC_CLOCK_STR "AMD64_RDTSC" +#elif defined(_WIN32) +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_WINQPC +#define SRT_SYNC_CLOCK_STR "WINQPC" +#elif TARGET_OS_MAC +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_MACH_ABSTIME +#define SRT_SYNC_CLOCK_STR "MACH_ABSTIME" +#elif defined(ENABLE_MONOTONIC_CLOCK) +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_GETTIME_MONOTONIC +#define SRT_SYNC_CLOCK_STR "GETTIME_MONOTONIC" +#else +#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY +#define SRT_SYNC_CLOCK_STR "POSIX_GETTIMEOFDAY" +#endif + +#endif // ENABLE_STDCXX_SYNC + +#include "srt.h" +#include "utilities.h" +#include "srt_attr_defs.h" + + +namespace srt +{ + +class CUDTException; // defined in common.h + +namespace sync +{ + +/////////////////////////////////////////////////////////////////////////////// +// +// Duration class +// +/////////////////////////////////////////////////////////////////////////////// + +#if ENABLE_STDCXX_SYNC + +template +using Duration = std::chrono::duration; + +#else + +/// Class template srt::sync::Duration represents a time interval. +/// It consists of a count of ticks of _Clock. +/// It is a wrapper of system timers in case of non-C++11 chrono build. +template +class Duration +{ +public: + Duration() + : m_duration(0) + { + } + + explicit Duration(int64_t d) + : m_duration(d) + { + } + +public: + inline int64_t count() const { return m_duration; } + + static Duration zero() { return Duration(); } + +public: // Relational operators + inline bool operator>=(const Duration& rhs) const { return m_duration >= rhs.m_duration; } + inline bool operator>(const Duration& rhs) const { return m_duration > rhs.m_duration; } + inline bool operator==(const Duration& rhs) const { return m_duration == rhs.m_duration; } + inline bool operator!=(const Duration& rhs) const { return m_duration != rhs.m_duration; } + inline bool operator<=(const Duration& rhs) const { return m_duration <= rhs.m_duration; } + inline bool operator<(const Duration& rhs) const { return m_duration < rhs.m_duration; } + +public: // Assignment operators + inline void operator*=(const int64_t mult) { m_duration = static_cast(m_duration * mult); } + inline void operator+=(const Duration& rhs) { m_duration += rhs.m_duration; } + inline void operator-=(const Duration& rhs) { m_duration -= rhs.m_duration; } + + inline Duration operator+(const Duration& rhs) const { return Duration(m_duration + rhs.m_duration); } + inline Duration operator-(const Duration& rhs) const { return Duration(m_duration - rhs.m_duration); } + inline Duration operator*(const int64_t& rhs) const { return Duration(m_duration * rhs); } + inline Duration operator/(const int64_t& rhs) const { return Duration(m_duration / rhs); } + +private: + // int64_t range is from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 + int64_t m_duration; +}; + +#endif // ENABLE_STDCXX_SYNC + +/////////////////////////////////////////////////////////////////////////////// +// +// TimePoint and steadt_clock classes +// +/////////////////////////////////////////////////////////////////////////////// + +#if ENABLE_STDCXX_SYNC + +using steady_clock = std::chrono::steady_clock; + +template +using time_point = std::chrono::time_point; + +template +using TimePoint = std::chrono::time_point; + +template +inline bool is_zero(const time_point &tp) +{ + return tp.time_since_epoch() == Clock::duration::zero(); +} + +inline bool is_zero(const steady_clock::time_point& t) +{ + return t == steady_clock::time_point(); +} + +#else +template +class TimePoint; + +class steady_clock +{ +public: + typedef Duration duration; + typedef TimePoint time_point; + +public: + static time_point now(); +}; + +/// Represents a point in time +template +class TimePoint +{ +public: + TimePoint() + : m_timestamp(0) + { + } + + explicit TimePoint(uint64_t tp) + : m_timestamp(tp) + { + } + + TimePoint(const TimePoint& other) + : m_timestamp(other.m_timestamp) + { + } + + TimePoint(const Duration& duration_since_epoch) + : m_timestamp(duration_since_epoch.count()) + { + } + + ~TimePoint() {} + +public: // Relational operators + inline bool operator<(const TimePoint& rhs) const { return m_timestamp < rhs.m_timestamp; } + inline bool operator<=(const TimePoint& rhs) const { return m_timestamp <= rhs.m_timestamp; } + inline bool operator==(const TimePoint& rhs) const { return m_timestamp == rhs.m_timestamp; } + inline bool operator!=(const TimePoint& rhs) const { return m_timestamp != rhs.m_timestamp; } + inline bool operator>=(const TimePoint& rhs) const { return m_timestamp >= rhs.m_timestamp; } + inline bool operator>(const TimePoint& rhs) const { return m_timestamp > rhs.m_timestamp; } + +public: // Arithmetic operators + inline Duration operator-(const TimePoint& rhs) const + { + return Duration(m_timestamp - rhs.m_timestamp); + } + inline TimePoint operator+(const Duration& rhs) const { return TimePoint(m_timestamp + rhs.count()); } + inline TimePoint operator-(const Duration& rhs) const { return TimePoint(m_timestamp - rhs.count()); } + +public: // Assignment operators + inline void operator=(const TimePoint& rhs) { m_timestamp = rhs.m_timestamp; } + inline void operator+=(const Duration& rhs) { m_timestamp += rhs.count(); } + inline void operator-=(const Duration& rhs) { m_timestamp -= rhs.count(); } + +public: // + static inline ATR_CONSTEXPR TimePoint min() { return TimePoint(std::numeric_limits::min()); } + static inline ATR_CONSTEXPR TimePoint max() { return TimePoint(std::numeric_limits::max()); } + +public: + Duration time_since_epoch() const; + +private: + uint64_t m_timestamp; +}; + +template <> +srt::sync::Duration srt::sync::TimePoint::time_since_epoch() const; + +inline Duration operator*(const int& lhs, const Duration& rhs) +{ + return rhs * lhs; +} + +#endif // ENABLE_STDCXX_SYNC + +// NOTE: Moved the following class definitons to "atomic_clock.h" +// template +// class AtomicDuration; +// template +// class AtomicClock; + +/////////////////////////////////////////////////////////////////////////////// +// +// Duration and timepoint conversions +// +/////////////////////////////////////////////////////////////////////////////// + +/// Function return number of decimals in a subsecond precision. +/// E.g. for a microsecond accuracy of steady_clock the return would be 6. +/// For a nanosecond accuracy of the steady_clock the return value would be 9. +int clockSubsecondPrecision(); + +#if ENABLE_STDCXX_SYNC + +inline long long count_microseconds(const steady_clock::duration &t) +{ + return std::chrono::duration_cast(t).count(); +} + +inline long long count_microseconds(const steady_clock::time_point tp) +{ + return std::chrono::duration_cast(tp.time_since_epoch()).count(); +} + +inline long long count_milliseconds(const steady_clock::duration &t) +{ + return std::chrono::duration_cast(t).count(); +} + +inline long long count_seconds(const steady_clock::duration &t) +{ + return std::chrono::duration_cast(t).count(); +} + +inline steady_clock::duration microseconds_from(int64_t t_us) +{ + return std::chrono::microseconds(t_us); +} + +inline steady_clock::duration milliseconds_from(int64_t t_ms) +{ + return std::chrono::milliseconds(t_ms); +} + +inline steady_clock::duration seconds_from(int64_t t_s) +{ + return std::chrono::seconds(t_s); +} + +#else + +int64_t count_microseconds(const steady_clock::duration& t); +int64_t count_milliseconds(const steady_clock::duration& t); +int64_t count_seconds(const steady_clock::duration& t); + +Duration microseconds_from(int64_t t_us); +Duration milliseconds_from(int64_t t_ms); +Duration seconds_from(int64_t t_s); + +inline bool is_zero(const TimePoint& t) +{ + return t == TimePoint(); +} + +#endif // ENABLE_STDCXX_SYNC + + +/////////////////////////////////////////////////////////////////////////////// +// +// Mutex section +// +/////////////////////////////////////////////////////////////////////////////// + +#if ENABLE_STDCXX_SYNC +using Mutex = std::mutex; +using UniqueLock = std::unique_lock; +using ScopedLock = std::lock_guard; +#else +/// Mutex is a class wrapper, that should mimic the std::chrono::mutex class. +/// At the moment the extra function ref() is temporally added to allow calls +/// to pthread_cond_timedwait(). Will be removed by introducing CEvent. +class SRT_ATTR_CAPABILITY("mutex") Mutex +{ + friend class SyncEvent; + +public: + Mutex(); + ~Mutex(); + +public: + int lock() SRT_ATTR_ACQUIRE(); + int unlock() SRT_ATTR_RELEASE(); + + /// @return true if the lock was acquired successfully, otherwise false + bool try_lock() SRT_ATTR_TRY_ACQUIRE(true); + + // TODO: To be removed with introduction of the CEvent. + pthread_mutex_t& ref() { return m_mutex; } + +private: + pthread_mutex_t m_mutex; +}; + +/// A pthread version of std::chrono::scoped_lock (or lock_guard for C++11) +class SRT_ATTR_SCOPED_CAPABILITY ScopedLock +{ +public: + SRT_ATTR_ACQUIRE(m) + explicit ScopedLock(Mutex& m); + + SRT_ATTR_RELEASE() + ~ScopedLock(); + +private: + Mutex& m_mutex; +}; + +/// A pthread version of std::chrono::unique_lock +class SRT_ATTR_SCOPED_CAPABILITY UniqueLock +{ + friend class SyncEvent; + int m_iLocked; + Mutex& m_Mutex; + +public: + SRT_ATTR_ACQUIRE(m) + explicit UniqueLock(Mutex &m); + + SRT_ATTR_RELEASE() + ~UniqueLock(); + +public: + SRT_ATTR_ACQUIRE() + void lock(); + + SRT_ATTR_RELEASE() + void unlock(); + + SRT_ATTR_RETURN_CAPABILITY(m_Mutex) + Mutex* mutex(); // reflects C++11 unique_lock::mutex() +}; +#endif // ENABLE_STDCXX_SYNC + +inline void enterCS(Mutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_ACQUIRE(m) { m.lock(); } + +inline bool tryEnterCS(Mutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_TRY_ACQUIRE(true, m) { return m.try_lock(); } + +inline void leaveCS(Mutex& m) SRT_ATTR_REQUIRES(m) SRT_ATTR_RELEASE(m) { m.unlock(); } + +class InvertedLock +{ + Mutex& m_mtx; + +public: + SRT_ATTR_REQUIRES(m) SRT_ATTR_RELEASE(m) + InvertedLock(Mutex& m) + : m_mtx(m) + { + m_mtx.unlock(); + } + + SRT_ATTR_ACQUIRE(m_mtx) + ~InvertedLock() + { + m_mtx.lock(); + } +}; + +inline void setupMutex(Mutex&, const char*) {} +inline void releaseMutex(Mutex&) {} + +//////////////////////////////////////////////////////////////////////////////// +// +// Condition section +// +//////////////////////////////////////////////////////////////////////////////// + +class Condition +{ +public: + Condition(); + ~Condition(); + +public: + /// These functions do not align with C++11 version. They are here hopefully as a temporal solution + /// to avoud issues with static initialization of CV on windows. + void init(); + void destroy(); + +public: + /// Causes the current thread to block until the condition variable is notified + /// or a spurious wakeup occurs. + /// + /// @param lock Corresponding mutex locked by UniqueLock + void wait(UniqueLock& lock); + + /// Atomically releases lock, blocks the current executing thread, + /// and adds it to the list of threads waiting on *this. + /// The thread will be unblocked when notify_all() or notify_one() is executed, + /// or when the relative timeout rel_time expires. + /// It may also be unblocked spuriously. When unblocked, regardless of the reason, + /// lock is reacquired and wait_for() exits. + /// + /// @returns false if the relative timeout specified by rel_time expired, + /// true otherwise (signal or spurious wake up). + /// + /// @note Calling this function if lock.mutex() + /// is not locked by the current thread is undefined behavior. + /// Calling this function if lock.mutex() is not the same mutex as the one + /// used by all other threads that are currently waiting on the same + /// condition variable is undefined behavior. + bool wait_for(UniqueLock& lock, const steady_clock::duration& rel_time); + + /// Causes the current thread to block until the condition variable is notified, + /// a specific time is reached, or a spurious wakeup occurs. + /// + /// @param[in] lock an object of type UniqueLock, which must be locked by the current thread + /// @param[in] timeout_time an object of type time_point representing the time when to stop waiting + /// + /// @returns false if the relative timeout specified by timeout_time expired, + /// true otherwise (signal or spurious wake up). + bool wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time); + + /// Calling notify_one() unblocks one of the waiting threads, + /// if any threads are waiting on this CV. + void notify_one(); + + /// Unblocks all threads currently waiting for this CV. + void notify_all(); + +private: +#if ENABLE_STDCXX_SYNC + std::condition_variable m_cv; +#else + pthread_cond_t m_cv; +#endif +}; + +inline void setupCond(Condition& cv, const char*) { cv.init(); } +inline void releaseCond(Condition& cv) { cv.destroy(); } + +/////////////////////////////////////////////////////////////////////////////// +// +// Event (CV) section +// +/////////////////////////////////////////////////////////////////////////////// + +// This class is used for condition variable combined with mutex by different ways. +// This should provide a cleaner API around locking with debug-logging inside. +class CSync +{ +protected: + Condition* m_cond; + UniqueLock* m_locker; + +public: + // Locked version: must be declared only after the declaration of UniqueLock, + // which has locked the mutex. On this delegate you should call only + // signal_locked() and pass the UniqueLock variable that should remain locked. + // Also wait() and wait_for() can be used only with this socket. + CSync(Condition& cond, UniqueLock& g) + : m_cond(&cond), m_locker(&g) + { + // XXX it would be nice to check whether the owner is also current thread + // but this can't be done portable way. + + // When constructed by this constructor, the user is expected + // to only call signal_locked() function. You should pass the same guard + // variable that you have used for construction as its argument. + } + + // COPY CONSTRUCTOR: DEFAULT! + + // Wait indefinitely, until getting a signal on CV. + void wait() + { + m_cond->wait(*m_locker); + } + + /// Block the call until either @a timestamp time achieved + /// or the conditional is signaled. + /// @param [in] delay Maximum time to wait since the moment of the call + /// @retval false if the relative timeout specified by rel_time expired, + /// @retval true if condition is signaled or spurious wake up. + bool wait_for(const steady_clock::duration& delay) + { + return m_cond->wait_for(*m_locker, delay); + } + + // Wait until the given time is achieved. + /// @param [in] exptime The target time to wait until. + /// @retval false if the target wait time is reached. + /// @retval true if condition is signal or spurious wake up. + bool wait_until(const steady_clock::time_point& exptime) + { + return m_cond->wait_until(*m_locker, exptime); + } + + // Static ad-hoc version + static void lock_notify_one(Condition& cond, Mutex& m) + { + ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! + cond.notify_one(); + } + + static void lock_notify_all(Condition& cond, Mutex& m) + { + ScopedLock lk(m); // XXX with thread logging, don't use ScopedLock directly! + cond.notify_all(); + } + + void notify_one_locked(UniqueLock& lk SRT_ATR_UNUSED) + { + // EXPECTED: lk.mutex() is LOCKED. + m_cond->notify_one(); + } + + void notify_all_locked(UniqueLock& lk SRT_ATR_UNUSED) + { + // EXPECTED: lk.mutex() is LOCKED. + m_cond->notify_all(); + } + + // The *_relaxed functions are to be used in case when you don't care + // whether the associated mutex is locked or not (you accept the case that + // a mutex isn't locked and the condition notification gets effectively + // missed), or you somehow know that the mutex is locked, but you don't + // have access to the associated UniqueLock object. This function, although + // it does the same thing as CSync::notify_one_locked etc. here for the + // user to declare explicitly that notifying is done without being + // prematurely certain that the associated mutex is locked. + // + // It is then expected that whenever these functions are used, an extra + // comment is provided to explain, why the use of the relaxed notification + // is correctly used. + + void notify_one_relaxed() { notify_one_relaxed(*m_cond); } + static void notify_one_relaxed(Condition& cond) { cond.notify_one(); } + static void notify_all_relaxed(Condition& cond) { cond.notify_all(); } +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// CEvent class +// +//////////////////////////////////////////////////////////////////////////////// + +// XXX Do not use this class now, there's an unknown issue +// connected to object management with the use of release* functions. +// Until this is solved, stay with separate *Cond and *Lock fields. +class CEvent +{ +public: + CEvent(); + ~CEvent(); + +public: + Mutex& mutex() { return m_lock; } + Condition& cond() { return m_cond; } + +public: + /// Causes the current thread to block until + /// a specific time is reached. + /// + /// @return true if condition occured or spuriously woken up + /// false on timeout + bool lock_wait_until(const steady_clock::time_point& tp); + + /// Blocks the current executing thread, + /// and adds it to the list of threads waiting on* this. + /// The thread will be unblocked when notify_all() or notify_one() is executed, + /// or when the relative timeout rel_time expires. + /// It may also be unblocked spuriously. + /// Uses internal mutex to lock. + /// + /// @return true if condition occured or spuriously woken up + /// false on timeout + bool lock_wait_for(const steady_clock::duration& rel_time); + + /// Atomically releases lock, blocks the current executing thread, + /// and adds it to the list of threads waiting on* this. + /// The thread will be unblocked when notify_all() or notify_one() is executed, + /// or when the relative timeout rel_time expires. + /// It may also be unblocked spuriously. + /// When unblocked, regardless of the reason, lock is reacquiredand wait_for() exits. + /// + /// @return true if condition occured or spuriously woken up + /// false on timeout + bool wait_for(UniqueLock& lk, const steady_clock::duration& rel_time); + + void lock_wait(); + + void wait(UniqueLock& lk); + + void notify_one(); + + void notify_all(); + + void lock_notify_one() + { + ScopedLock lk(m_lock); // XXX with thread logging, don't use ScopedLock directly! + m_cond.notify_one(); + } + + void lock_notify_all() + { + ScopedLock lk(m_lock); // XXX with thread logging, don't use ScopedLock directly! + m_cond.notify_all(); + } + +private: + Mutex m_lock; + Condition m_cond; +}; + + +// This class binds together the functionality of +// UniqueLock and CSync. It provides a simple interface of CSync +// while having already the UniqueLock applied in the scope, +// so a safe statement can be made about the mutex being locked +// when signalling or waiting. +class CUniqueSync: public CSync +{ + UniqueLock m_ulock; + +public: + + UniqueLock& locker() { return m_ulock; } + + CUniqueSync(Mutex& mut, Condition& cnd) + : CSync(cnd, m_ulock) + , m_ulock(mut) + { + } + + CUniqueSync(CEvent& event) + : CSync(event.cond(), m_ulock) + , m_ulock(event.mutex()) + { + } + + // These functions can be used safely because + // this whole class guarantees that whatever happens + // while its object exists is that the mutex is locked. + + void notify_one() + { + m_cond->notify_one(); + } + + void notify_all() + { + m_cond->notify_all(); + } +}; + +class CTimer +{ +public: + CTimer(); + ~CTimer(); + +public: + /// Causes the current thread to block until + /// the specified time is reached. + /// Sleep can be interrupted by calling interrupt() + /// or woken up to recheck the scheduled time by tick() + /// @param tp target time to sleep until + /// + /// @return true if the specified time was reached + /// false should never happen + bool sleep_until(steady_clock::time_point tp); + + /// Resets target wait time and interrupts waiting + /// in sleep_until(..) + void interrupt(); + + /// Wakes up waiting thread (sleep_until(..)) without + /// changing the target waiting time to force a recheck + /// of the current time in comparisson to the target time. + void tick(); + +private: + CEvent m_event; + steady_clock::time_point m_tsSchedTime; +}; + + +/// Print steady clock timepoint in a human readable way. +/// days HH:MM:SS.us [STD] +/// Example: 1D 02:12:56.123456 +/// +/// @param [in] steady clock timepoint +/// @returns a string with a formatted time representation +std::string FormatTime(const steady_clock::time_point& time); + +/// Print steady clock timepoint relative to the current system time +/// Date HH:MM:SS.us [SYS] +/// @param [in] steady clock timepoint +/// @returns a string with a formatted time representation +std::string FormatTimeSys(const steady_clock::time_point& time); + +enum eDurationUnit {DUNIT_S, DUNIT_MS, DUNIT_US}; + +template +struct DurationUnitName; + +template<> +struct DurationUnitName +{ + static const char* name() { return "us"; } + static double count(const steady_clock::duration& dur) { return static_cast(count_microseconds(dur)); } +}; + +template<> +struct DurationUnitName +{ + static const char* name() { return "ms"; } + static double count(const steady_clock::duration& dur) { return static_cast(count_microseconds(dur))/1000.0; } +}; + +template<> +struct DurationUnitName +{ + static const char* name() { return "s"; } + static double count(const steady_clock::duration& dur) { return static_cast(count_microseconds(dur))/1000000.0; } +}; + +template +inline std::string FormatDuration(const steady_clock::duration& dur) +{ + return Sprint(DurationUnitName::count(dur)) + DurationUnitName::name(); +} + +inline std::string FormatDuration(const steady_clock::duration& dur) +{ + return FormatDuration(dur); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// CGlobEvent class +// +//////////////////////////////////////////////////////////////////////////////// + +class CGlobEvent +{ +public: + /// Triggers the event and notifies waiting threads. + /// Simply calls notify_one(). + static void triggerEvent(); + + /// Waits for the event to be triggered with 10ms timeout. + /// Simply calls wait_for(). + static bool waitForEvent(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// CThread class +// +//////////////////////////////////////////////////////////////////////////////// + +#ifdef ENABLE_STDCXX_SYNC +typedef std::system_error CThreadException; +using CThread = std::thread; +namespace this_thread = std::this_thread; +#else // pthreads wrapper version +typedef CUDTException CThreadException; + +class CThread +{ +public: + CThread(); + /// @throws std::system_error if the thread could not be started. + CThread(void *(*start_routine) (void *), void *arg); + +#if HAVE_FULL_CXX11 + CThread& operator=(CThread &other) = delete; + CThread& operator=(CThread &&other); +#else + CThread& operator=(CThread &other); + /// To be used only in StartThread function. + /// Creates a new stread and assigns to this. + /// @throw CThreadException + void create_thread(void *(*start_routine) (void *), void *arg); +#endif + +public: // Observers + /// Checks if the CThread object identifies an active thread of execution. + /// A default constructed thread is not joinable. + /// A thread that has finished executing code, but has not yet been joined + /// is still considered an active thread of execution and is therefore joinable. + bool joinable() const; + + struct id + { + explicit id(const pthread_t t) + : value(t) + {} + + const pthread_t value; + inline bool operator==(const id& second) const + { + return pthread_equal(value, second.value) != 0; + } + }; + + /// Returns the id of the current thread. + /// In this implementation the ID is the pthread_t. + const id get_id() const { return id(m_thread); } + +public: + /// Blocks the current thread until the thread identified by *this finishes its execution. + /// If that thread has already terminated, then join() returns immediately. + /// + /// @throws std::system_error if an error occurs + void join(); + +public: // Internal + /// Calls pthread_create, throws exception on failure. + /// @throw CThreadException + void create(void *(*start_routine) (void *), void *arg); + +private: + pthread_t m_thread; +}; + +template +inline Stream& operator<<(Stream& str, const CThread::id& cid) +{ +#if defined(_WIN32) && (defined(PTW32_VERSION) || defined (__PTW32_VERSION)) + // This is a version specific for pthread-win32 implementation + // Here pthread_t type is a structure that is not convertible + // to a number at all. + return str << pthread_getw32threadid_np(cid.value); +#else + return str << cid.value; +#endif +} + +namespace this_thread +{ + const inline CThread::id get_id() { return CThread::id (pthread_self()); } + + inline void sleep_for(const steady_clock::duration& t) + { +#if !defined(_WIN32) + usleep(count_microseconds(t)); // microseconds +#else + Sleep((DWORD) count_milliseconds(t)); +#endif + } +} + +#endif + +/// StartThread function should be used to do CThread assignments: +/// @code +/// CThread a(); +/// a = CThread(func, args); +/// @endcode +/// +/// @returns true if thread was started successfully, +/// false on failure +/// +#ifdef ENABLE_STDCXX_SYNC +typedef void* (&ThreadFunc) (void*); +bool StartThread(CThread& th, ThreadFunc&& f, void* args, const std::string& name); +#else +bool StartThread(CThread& th, void* (*f) (void*), void* args, const std::string& name); +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +// CThreadError class - thread local storage wrapper +// +//////////////////////////////////////////////////////////////////////////////// + +/// Set thread local error +/// @param e new CUDTException +void SetThreadLocalError(const CUDTException& e); + +/// Get thread local error +/// @returns CUDTException pointer +CUDTException& GetThreadLocalError(); + +//////////////////////////////////////////////////////////////////////////////// +// +// Random distribution functions. +// +//////////////////////////////////////////////////////////////////////////////// + +/// Generate a uniform-distributed random integer from [minVal; maxVal]. +/// If HAVE_CXX11, uses std::uniform_distribution(std::random_device). +/// @param[in] minVal minimum allowed value of the resulting random number. +/// @param[in] maxVal maximum allowed value of the resulting random number. +int genRandomInt(int minVal, int maxVal); + +} // namespace sync +} // namespace srt + +#include "atomic_clock.h" + +#endif // INC_SRT_SYNC_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/sync_cxx11.cpp b/trunk/3rdparty/srt-1-fit/srtcore/sync_cxx11.cpp new file mode 100644 index 0000000000..bdfbf9a4e2 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/sync_cxx11.cpp @@ -0,0 +1,108 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2020 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ +#include "platform_sys.h" + +#include +#include +#include +#include "sync.h" +#include "srt_compat.h" +#include "common.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// Clock frequency helpers +// +//////////////////////////////////////////////////////////////////////////////// + +namespace { +template +int pow10(); + +template <> +int pow10<10>() +{ + return 1; +} + +template +int pow10() +{ + return 1 + pow10(); +} +} + +int srt::sync::clockSubsecondPrecision() +{ + const int64_t ticks_per_sec = (srt::sync::steady_clock::period::den / srt::sync::steady_clock::period::num); + const int decimals = pow10(); + return decimals; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// SyncCond (based on stl chrono C++11) +// +//////////////////////////////////////////////////////////////////////////////// + +srt::sync::Condition::Condition() {} + +srt::sync::Condition::~Condition() {} + +void srt::sync::Condition::init() {} + +void srt::sync::Condition::destroy() {} + +void srt::sync::Condition::wait(UniqueLock& lock) +{ + m_cv.wait(lock); +} + +bool srt::sync::Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) +{ + // Another possible implementation is wait_until(steady_clock::now() + timeout); + return m_cv.wait_for(lock, rel_time) != std::cv_status::timeout; +} + +bool srt::sync::Condition::wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time) +{ + return m_cv.wait_until(lock, timeout_time) != std::cv_status::timeout; +} + +void srt::sync::Condition::notify_one() +{ + m_cv.notify_one(); +} + +void srt::sync::Condition::notify_all() +{ + m_cv.notify_all(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// CThreadError class - thread local storage error wrapper +// +//////////////////////////////////////////////////////////////////////////////// + +// Threal local error will be used by CUDTUnited +// with a static scope, therefore static thread_local +static thread_local srt::CUDTException s_thErr; + +void srt::sync::SetThreadLocalError(const srt::CUDTException& e) +{ + s_thErr = e; +} + +srt::CUDTException& srt::sync::GetThreadLocalError() +{ + return s_thErr; +} + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/sync_posix.cpp b/trunk/3rdparty/srt-1-fit/srtcore/sync_posix.cpp new file mode 100644 index 0000000000..c44fe86c27 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/sync_posix.cpp @@ -0,0 +1,572 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2019 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ +#include "platform_sys.h" + +#include +#include +#include +#include "sync.h" +#include "utilities.h" +#include "udt.h" +#include "srt.h" +#include "srt_compat.h" +#include "logging.h" +#include "common.h" + +#if defined(_WIN32) +#include "win/wintime.h" +#include +#elif TARGET_OS_MAC +#include +#endif + +namespace srt_logging +{ + extern Logger inlog; +} +using namespace srt_logging; + +namespace srt +{ +namespace sync +{ + +static void rdtsc(uint64_t& x) +{ +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_IA32_RDTSC + uint32_t lval, hval; + // asm volatile ("push %eax; push %ebx; push %ecx; push %edx"); + // asm volatile ("xor %eax, %eax; cpuid"); + asm volatile("rdtsc" : "=a"(lval), "=d"(hval)); + // asm volatile ("pop %edx; pop %ecx; pop %ebx; pop %eax"); + x = hval; + x = (x << 32) | lval; +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_IA64_ITC + asm("mov %0=ar.itc" : "=r"(x)::"memory"); +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_AMD64_RDTSC + uint32_t lval, hval; + asm("rdtsc" : "=a"(lval), "=d"(hval)); + x = hval; + x = (x << 32) | lval; +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC + // This function should not fail, because we checked the QPC + // when calling to QueryPerformanceFrequency. If it failed, + // the m_bUseMicroSecond was set to true. + QueryPerformanceCounter((LARGE_INTEGER*)&x); +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_MACH_ABSTIME + x = mach_absolute_time(); +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC + // get_cpu_frequency() returns 1 us accuracy in this case + timespec tm; + clock_gettime(CLOCK_MONOTONIC, &tm); + x = tm.tv_sec * uint64_t(1000000) + (tm.tv_nsec / 1000); +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_POSIX_GETTIMEOFDAY + // use system call to read time clock for other archs + timeval t; + gettimeofday(&t, 0); + x = t.tv_sec * uint64_t(1000000) + t.tv_usec; +#else +#error Wrong SRT_SYNC_CLOCK +#endif +} + +static int64_t get_cpu_frequency() +{ + int64_t frequency = 1; // 1 tick per microsecond. + +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC + LARGE_INTEGER ccf; // in counts per second + if (QueryPerformanceFrequency(&ccf)) + { + frequency = ccf.QuadPart / 1000000; // counts per microsecond + if (frequency == 0) + { + LOGC(inlog.Warn, log << "Win QPC frequency of " << ccf.QuadPart + << " counts/s is below the required 1 us accuracy. Please consider using C++11 timing (-DENABLE_STDCXX_SYNC=ON) instead."); + frequency = 1; // set back to 1 to avoid division by zero. + } + } + else + { + // Can't throw an exception, it won't be handled. + LOGC(inlog.Error, log << "IPE: QueryPerformanceFrequency failed with " << GetLastError()); + } + +#elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_MACH_ABSTIME + mach_timebase_info_data_t info; + mach_timebase_info(&info); + frequency = info.denom * int64_t(1000) / info.numer; + +#elif SRT_SYNC_CLOCK >= SRT_SYNC_CLOCK_AMD64_RDTSC && SRT_SYNC_CLOCK <= SRT_SYNC_CLOCK_IA64_ITC + // SRT_SYNC_CLOCK_AMD64_RDTSC or SRT_SYNC_CLOCK_IA32_RDTSC or SRT_SYNC_CLOCK_IA64_ITC + uint64_t t1, t2; + + rdtsc(t1); + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 100000000; + nanosleep(&ts, NULL); + rdtsc(t2); + + // CPU clocks per microsecond + frequency = int64_t(t2 - t1) / 100000; +#endif + + return frequency; +} + +static int count_subsecond_precision(int64_t ticks_per_us) +{ + int signs = 6; // starting from 1 us + while (ticks_per_us /= 10) ++signs; + return signs; +} + +const int64_t s_clock_ticks_per_us = get_cpu_frequency(); + +const int s_clock_subsecond_precision = count_subsecond_precision(s_clock_ticks_per_us); + +int clockSubsecondPrecision() { return s_clock_subsecond_precision; } + +} // namespace sync +} // namespace srt + +//////////////////////////////////////////////////////////////////////////////// +// +// Sync utilities section +// +//////////////////////////////////////////////////////////////////////////////// + +static timespec us_to_timespec(const uint64_t time_us) +{ + timespec timeout; + timeout.tv_sec = time_us / 1000000; + timeout.tv_nsec = (time_us % 1000000) * 1000; + return timeout; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TimePoint section +// +//////////////////////////////////////////////////////////////////////////////// + +template <> +srt::sync::Duration srt::sync::TimePoint::time_since_epoch() const +{ + return srt::sync::Duration(m_timestamp); +} + +srt::sync::TimePoint srt::sync::steady_clock::now() +{ + uint64_t x = 0; + rdtsc(x); + return TimePoint(x); +} + +int64_t srt::sync::count_microseconds(const steady_clock::duration& t) +{ + return t.count() / s_clock_ticks_per_us; +} + +int64_t srt::sync::count_milliseconds(const steady_clock::duration& t) +{ + return t.count() / s_clock_ticks_per_us / 1000; +} + +int64_t srt::sync::count_seconds(const steady_clock::duration& t) +{ + return t.count() / s_clock_ticks_per_us / 1000000; +} + +srt::sync::steady_clock::duration srt::sync::microseconds_from(int64_t t_us) +{ + return steady_clock::duration(t_us * s_clock_ticks_per_us); +} + +srt::sync::steady_clock::duration srt::sync::milliseconds_from(int64_t t_ms) +{ + return steady_clock::duration((1000 * t_ms) * s_clock_ticks_per_us); +} + +srt::sync::steady_clock::duration srt::sync::seconds_from(int64_t t_s) +{ + return steady_clock::duration((1000000 * t_s) * s_clock_ticks_per_us); +} + +srt::sync::Mutex::Mutex() +{ + const int err = pthread_mutex_init(&m_mutex, 0); + if (err) + { + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } +} + +srt::sync::Mutex::~Mutex() +{ + pthread_mutex_destroy(&m_mutex); +} + +int srt::sync::Mutex::lock() +{ + return pthread_mutex_lock(&m_mutex); +} + +int srt::sync::Mutex::unlock() +{ + return pthread_mutex_unlock(&m_mutex); +} + +bool srt::sync::Mutex::try_lock() +{ + return (pthread_mutex_trylock(&m_mutex) == 0); +} + +srt::sync::ScopedLock::ScopedLock(Mutex& m) + : m_mutex(m) +{ + m_mutex.lock(); +} + +srt::sync::ScopedLock::~ScopedLock() +{ + m_mutex.unlock(); +} + + +srt::sync::UniqueLock::UniqueLock(Mutex& m) + : m_Mutex(m) +{ + m_iLocked = m_Mutex.lock(); +} + +srt::sync::UniqueLock::~UniqueLock() +{ + if (m_iLocked == 0) + { + unlock(); + } +} + +void srt::sync::UniqueLock::lock() +{ + if (m_iLocked != -1) + throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); + + m_iLocked = m_Mutex.lock(); +} + +void srt::sync::UniqueLock::unlock() +{ + if (m_iLocked != 0) + throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); + + m_Mutex.unlock(); + m_iLocked = -1; +} + +srt::sync::Mutex* srt::sync::UniqueLock::mutex() +{ + return &m_Mutex; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// Condition section (based on pthreads) +// +//////////////////////////////////////////////////////////////////////////////// + +namespace srt +{ +namespace sync +{ + +Condition::Condition() +#ifdef _WIN32 + : m_cv(PTHREAD_COND_INITIALIZER) +#endif +{} + +Condition::~Condition() {} + +void Condition::init() +{ + pthread_condattr_t* attr = NULL; +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC + pthread_condattr_t CondAttribs; + pthread_condattr_init(&CondAttribs); + pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC); + attr = &CondAttribs; +#endif + const int res = pthread_cond_init(&m_cv, attr); + if (res != 0) + throw std::runtime_error("pthread_cond_init monotonic failed"); +} + +void Condition::destroy() +{ + pthread_cond_destroy(&m_cv); +} + +void Condition::wait(UniqueLock& lock) +{ + pthread_cond_wait(&m_cv, &lock.mutex()->ref()); +} + +bool Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) +{ + timespec timeout; +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, &timeout); + const uint64_t now_us = timeout.tv_sec * uint64_t(1000000) + (timeout.tv_nsec / 1000); +#else + timeval now; + gettimeofday(&now, 0); + const uint64_t now_us = now.tv_sec * uint64_t(1000000) + now.tv_usec; +#endif + timeout = us_to_timespec(now_us + count_microseconds(rel_time)); + return pthread_cond_timedwait(&m_cv, &lock.mutex()->ref(), &timeout) != ETIMEDOUT; +} + +bool Condition::wait_until(UniqueLock& lock, const steady_clock::time_point& timeout_time) +{ + // This will work regardless as to which clock is in use. The time + // should be specified as steady_clock::time_point, so there's no + // question of the timer base. + const steady_clock::time_point now = steady_clock::now(); + if (now >= timeout_time) + return false; // timeout + + // wait_for() is used because it will be converted to pthread-frienly timeout_time inside. + return wait_for(lock, timeout_time - now); +} + +void Condition::notify_one() +{ + pthread_cond_signal(&m_cv); +} + +void Condition::notify_all() +{ + pthread_cond_broadcast(&m_cv); +} + +}; // namespace sync +}; // namespace srt + + +//////////////////////////////////////////////////////////////////////////////// +// +// CThread class +// +//////////////////////////////////////////////////////////////////////////////// + +srt::sync::CThread::CThread() +{ + m_thread = pthread_t(); +} + +srt::sync::CThread::CThread(void *(*start_routine) (void *), void *arg) +{ + create(start_routine, arg); +} + +#if HAVE_FULL_CXX11 +srt::sync::CThread& srt::sync::CThread::operator=(CThread&& other) +#else +srt::sync::CThread& srt::sync::CThread::operator=(CThread& other) +#endif +{ + if (joinable()) + { + // If the thread has already terminated, then + // pthread_join() returns immediately. + // But we have to check it has terminated before replacing it. + LOGC(inlog.Error, log << "IPE: Assigning to a thread that is not terminated!"); + +#ifndef DEBUG +#ifndef __ANDROID__ + // In case of production build the hanging thread should be terminated + // to avoid hang ups and align with C++11 implementation. + // There is no pthread_cancel on Android. See #1476. This error should not normally + // happen, but if it happen, then detaching the thread. + pthread_cancel(m_thread); +#endif // __ANDROID__ +#else + join(); +#endif + } + + // Move thread handler from other + m_thread = other.m_thread; + other.m_thread = pthread_t(); + return *this; +} + +#if !HAVE_FULL_CXX11 +void srt::sync::CThread::create_thread(void *(*start_routine) (void *), void *arg) +{ + SRT_ASSERT(!joinable()); + create(start_routine, arg); +} +#endif + +bool srt::sync::CThread::joinable() const +{ + return !pthread_equal(m_thread, pthread_t()); +} + +void srt::sync::CThread::join() +{ + void *retval; + const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval); + if (ret != 0) + { + LOGC(inlog.Error, log << "pthread_join failed with " << ret); + } +#ifdef HEAVY_LOGGING + else + { + HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED"); + } +#endif + // After joining, joinable should be false + m_thread = pthread_t(); + return; +} + +void srt::sync::CThread::create(void *(*start_routine) (void *), void *arg) +{ + const int st = pthread_create(&m_thread, NULL, start_routine, arg); + if (st != 0) + { + LOGC(inlog.Error, log << "pthread_create failed with " << st); + throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// CThreadError class - thread local storage error wrapper +// +//////////////////////////////////////////////////////////////////////////////// +namespace srt { +namespace sync { + +class CThreadError +{ +public: + CThreadError() + { + pthread_key_create(&m_ThreadSpecKey, ThreadSpecKeyDestroy); + + // This is a global object and as such it should be called in the + // main application thread or at worst in the thread that has first + // run `srt_startup()` function and so requested the SRT library to + // be dynamically linked. Most probably in this very thread the API + // errors will be reported, so preallocate the ThreadLocalSpecific + // object for this error description. + + // This allows std::bac_alloc to crash the program during + // the initialization of the SRT library (likely it would be + // during the DL constructor, still way before any chance of + // doing any operations here). This will prevent SRT from running + // into trouble while trying to operate. + CUDTException* ne = new CUDTException(); + pthread_setspecific(m_ThreadSpecKey, ne); + } + + ~CThreadError() + { + // Likely all objects should be deleted in all + // threads that have exited, but std::this_thread didn't exit + // yet :). + ThreadSpecKeyDestroy(pthread_getspecific(m_ThreadSpecKey)); + pthread_key_delete(m_ThreadSpecKey); + } + + void set(const CUDTException& e) + { + CUDTException* cur = get(); + // If this returns NULL, it means that there was an unexpected + // memory allocation error. Simply ignore this request if so + // happened, and then when trying to get the error description + // the application will always get the memory allocation error. + + // There's no point in doing anything else here; lack of memory + // must be prepared for prematurely, and that was already done. + if (!cur) + return; + + *cur = e; + } + + /*[[nullable]]*/ CUDTException* get() + { + if (!pthread_getspecific(m_ThreadSpecKey)) + { + // This time if this can't be done due to memory allocation + // problems, just allow this value to be NULL, which during + // getting the error description will redirect to a memory + // allocation error. + + // It would be nice to somehow ensure that this object is + // created in every thread of the application using SRT, but + // POSIX thread API doesn't contain any possibility to have + // a creation callback that would apply to every thread in + // the application (as it is for C++11 thread_local storage). + CUDTException* ne = new(std::nothrow) CUDTException(); + pthread_setspecific(m_ThreadSpecKey, ne); + return ne; + } + return (CUDTException*)pthread_getspecific(m_ThreadSpecKey); + } + + static void ThreadSpecKeyDestroy(void* e) + { + delete (CUDTException*)e; + } + +private: + pthread_key_t m_ThreadSpecKey; +}; + +// Threal local error will be used by CUDTUnited +// that has a static scope + +// This static makes this object file-private access so that +// the access is granted only for the accessor functions. +static CThreadError s_thErr; + +void SetThreadLocalError(const CUDTException& e) +{ + s_thErr.set(e); +} + +CUDTException& GetThreadLocalError() +{ + // In POSIX version we take into account the possibility + // of having an allocation error here. Therefore we need to + // allow thie value to return NULL and have some fallback + // for that case. The dynamic memory allocation failure should + // be the only case as to why it is unable to get the pointer + // to the error description. + static CUDTException resident_alloc_error (MJ_SYSTEMRES, MN_MEMORY); + CUDTException* curx = s_thErr.get(); + if (!curx) + return resident_alloc_error; + return *curx; +} + +} // namespace sync +} // namespace srt + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/threadname.h b/trunk/3rdparty/srt-1-fit/srtcore/threadname.h index 8fdcd9dfdc..1c064c86c5 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/threadname.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/threadname.h @@ -1,11 +1,11 @@ /* * SRT - Secure, Reliable, Transport * Copyright (c) 2018 Haivision Systems Inc. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * + * */ /***************************************************************************** @@ -13,75 +13,212 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__THREADNAME_H -#define INC__THREADNAME_H +#ifndef INC_SRT_THREADNAME_H +#define INC_SRT_THREADNAME_H + +// NOTE: +// HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H +// HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H +// HAVE_PTHREAD_GETNAME_NP +// HAVE_PTHREAD_GETNAME_NP +// Are detected and set in ../CMakeLists.txt. +// OS Availability of pthread_getname_np(..) and pthread_setname_np(..):: +// MacOS(10.6) +// iOS(3.2) +// AIX(7.1) +// FreeBSD(version?), OpenBSD(Version?) +// Linux-GLIBC(GLIBC-2.12). +// Linux-MUSL(MUSL-1.1.20 Partial Implementation. See below). +// MINGW-W64(4.0.6) + +#if defined(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) \ + || defined(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) + #include + #if defined(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) \ + && !defined(HAVE_PTHREAD_GETNAME_NP) + #define HAVE_PTHREAD_GETNAME_NP 1 + #endif + #if defined(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) \ + && !defined(HAVE_PTHREAD_SETNAME_NP) + #define HAVE_PTHREAD_SETNAME_NP 1 + #endif +#endif + +#if (defined(HAVE_PTHREAD_GETNAME_NP) && defined(HAVE_PTHREAD_GETNAME_NP)) \ + || defined(__linux__) + // NOTE: + // Linux pthread_getname_np() and pthread_setname_np() became available + // in GLIBC-2.12 and later. + // Some Linux runtimes do not have pthread_getname_np(), but have + // pthread_setname_np(), for instance MUSL at least as of v1.1.20. + // So using the prctl() for Linux is more portable. + #if defined(__linux__) + #include + #endif + #include +#endif -#ifdef __linux__ +#include +#include +#include -#include +#include "common.h" +#include "sync.h" + +namespace srt { class ThreadName { - char old_name[128]; - char new_name[128]; - bool good; -public: - static const size_t BUFSIZE = 128; +#if (defined(HAVE_PTHREAD_GETNAME_NP) && defined(HAVE_PTHREAD_GETNAME_NP)) \ + || defined(__linux__) - static bool get(char* namebuf) + class ThreadNameImpl { - return prctl(PR_GET_NAME, (unsigned long)namebuf, 0, 0) != -1; - } + public: + static const size_t BUFSIZE = 64; + static const bool DUMMY_IMPL = false; - static bool set(const char* name) - { - return prctl(PR_SET_NAME, (unsigned long)name, 0, 0) != -1; - } + static bool get(char* namebuf) + { +#if defined(__linux__) + // since Linux 2.6.11. The buffer should allow space for up to 16 + // bytes; the returned string will be null-terminated. + return prctl(PR_GET_NAME, (unsigned long)namebuf, 0, 0) != -1; +#elif defined(HAVE_PTHREAD_GETNAME_NP) + return pthread_getname_np(pthread_self(), namebuf, BUFSIZE) == 0; +#else +#error "unsupported platform" +#endif + } + static bool set(const char* name) + { + SRT_ASSERT(name != NULL); +#if defined(__linux__) + // The name can be up to 16 bytes long, including the terminating + // null byte. (If the length of the string, including the terminating + // null byte, exceeds 16 bytes, the string is silently truncated.) + return prctl(PR_SET_NAME, (unsigned long)name, 0, 0) != -1; +#elif defined(HAVE_PTHREAD_SETNAME_NP) + #if defined(__APPLE__) + return pthread_setname_np(name) == 0; + #else + return pthread_setname_np(pthread_self(), name) == 0; + #endif +#else +#error "unsupported platform" +#endif + } - ThreadName(const char* name) - { - if ( (good = get(old_name)) ) + explicit ThreadNameImpl(const std::string& name) + : reset(false) { - snprintf(new_name, 127, "%s", name); - new_name[127] = 0; - prctl(PR_SET_NAME, (unsigned long)new_name, 0, 0); + tid = pthread_self(); + + if (!get(old_name)) + return; + + reset = set(name.c_str()); + if (reset) + return; + + // Try with a shorter name. 15 is the upper limit supported by Linux, + // other platforms should support a larger value. So 15 should works + // on all platforms. + const size_t max_len = 15; + if (name.size() > max_len) + reset = set(name.substr(0, max_len).c_str()); } - } - ~ThreadName() - { - if ( good ) - prctl(PR_SET_NAME, (unsigned long)old_name, 0, 0); - } -}; + ~ThreadNameImpl() + { + if (!reset) + return; + + // ensure it's called on the right thread + if (tid == pthread_self()) + set(old_name); + } + + private: + ThreadNameImpl(ThreadNameImpl& other); + ThreadNameImpl& operator=(const ThreadNameImpl& other); + + private: + bool reset; + pthread_t tid; + char old_name[BUFSIZE]; + }; #else -// Fake class, which does nothing. You can also take a look how -// this works in other systems that are not supported here and add -// the support. This is a fallback for systems that do not support -// thread names. + class ThreadNameImpl + { + public: + static const bool DUMMY_IMPL = true; + static const size_t BUFSIZE = 64; + + static bool get(char* output) + { + // The default implementation will simply try to get the thread ID + std::ostringstream bs; + bs << "T" << sync::this_thread::get_id(); + size_t s = bs.str().copy(output, BUFSIZE - 1); + output[s] = '\0'; + return true; + } -class ThreadName -{ -public: + static bool set(const char*) { return false; } - static bool get(char*) { return false; } - static bool set(const char*) { return false; } + ThreadNameImpl(const std::string&) {} - ThreadName(const char*) + ~ThreadNameImpl() // just to make it "non-trivially-destructible" for compatibility with normal version + { + } + }; + +#endif // platform dependent impl + + // Why delegate to impl: + // 1. to make sure implementation on different platforms have the same interface. + // 2. it's simple to add some wrappers like get(const std::string &). + ThreadNameImpl impl; + +public: + static const bool DUMMY_IMPL = ThreadNameImpl::DUMMY_IMPL; + static const size_t BUFSIZE = ThreadNameImpl::BUFSIZE; + + /// @brief Print thread ID to the provided buffer. + /// The size of the destination buffer is assumed to be at least ThreadName::BUFSIZE. + /// @param [out] output destination buffer to get thread name + /// @return true on success, false on failure + static bool get(char* output) { + return ThreadNameImpl::get(output); + } + + static bool get(std::string& name) { + char buf[BUFSIZE]; + bool ret = get(buf); + if (ret) + name = buf; + return ret; } - ~ThreadName() // just to make it "non-trivially-destructible" for compatibility with normal version + static bool set(const std::string& name) { return ThreadNameImpl::set(name.c_str()); } + + explicit ThreadName(const std::string& name) + : impl(name) { } +private: + ThreadName(const ThreadName&); + ThreadName(const char*); + ThreadName& operator=(const ThreadName& other); }; +} // namespace srt - -#endif #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.cpp b/trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.cpp new file mode 100644 index 0000000000..046c90b74a --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.cpp @@ -0,0 +1,267 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ +#include "tsbpd_time.h" + +#include "logging.h" +#include "logger_defs.h" +#include "packet.h" + +using namespace srt_logging; +using namespace srt::sync; + +namespace srt +{ + +#if SRT_DEBUG_TRACE_DRIFT +class drift_logger +{ + typedef srt::sync::steady_clock steady_clock; + +public: + drift_logger() {} + + ~drift_logger() + { + ScopedLock lck(m_mtx); + m_fout.close(); + } + + void trace(unsigned ackack_timestamp, + int rtt_us, + int64_t drift_sample, + int64_t drift, + int64_t overdrift, + const srt::sync::steady_clock::time_point& pkt_base, + const srt::sync::steady_clock::time_point& tsbpd_base) + { + using namespace srt::sync; + ScopedLock lck(m_mtx); + create_file(); + + // std::string str_tnow = srt::sync::FormatTime(steady_clock::now()); + // str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [STDY]' part + + std::string str_tbase = srt::sync::FormatTime(tsbpd_base); + str_tbase.resize(str_tbase.size() - 7); // remove trailing ' [STDY]' part + + std::string str_pkt_base = srt::sync::FormatTime(pkt_base); + str_pkt_base.resize(str_pkt_base.size() - 7); // remove trailing ' [STDY]' part + + // m_fout << str_tnow << ","; + m_fout << count_microseconds(steady_clock::now() - m_start_time) << ","; + m_fout << ackack_timestamp << ","; + m_fout << rtt_us << ","; + m_fout << drift_sample << ","; + m_fout << drift << ","; + m_fout << overdrift << ","; + m_fout << str_pkt_base << ","; + m_fout << str_tbase << "\n"; + m_fout.flush(); + } + +private: + void print_header() + { + m_fout << "usElapsedStd,usAckAckTimestampStd,"; + m_fout << "usRTTStd,usDriftSampleStd,usDriftStd,usOverdriftStd,tsPktBase,TSBPDBase\n"; + } + + void create_file() + { + if (m_fout.is_open()) + return; + + m_start_time = srt::sync::steady_clock::now(); + std::string str_tnow = srt::sync::FormatTimeSys(m_start_time); + str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part + while (str_tnow.find(':') != std::string::npos) + { + str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); + } + const std::string fname = "drift_trace_" + str_tnow + ".csv"; + m_fout.open(fname, std::ofstream::out); + if (!m_fout) + std::cerr << "IPE: Failed to open " << fname << "!!!\n"; + + print_header(); + } + +private: + srt::sync::Mutex m_mtx; + std::ofstream m_fout; + srt::sync::steady_clock::time_point m_start_time; +}; + +drift_logger g_drift_logger; + +#endif // SRT_DEBUG_TRACE_DRIFT + +bool CTsbpdTime::addDriftSample(uint32_t usPktTimestamp, const time_point& tsPktArrival, int usRTTSample) +{ + if (!m_bTsbPdMode) + return false; + + ScopedLock lck(m_mtxRW); + + // Remember the first RTT sample measured. Ideally we need RTT0 - the one from the handshaking phase, + // because TSBPD base is initialized there. But HS-based RTT is not yet implemented. + // Take the first one assuming it is close to RTT0. + if (m_iFirstRTT == -1) + { + m_iFirstRTT = usRTTSample; + } + + // A change in network delay has to be taken into account. The only way to get some estimation of it + // is to estimate RTT change and assume that the change of the one way network delay is + // approximated by the half of the RTT change. + const duration tdRTTDelta = usRTTSample >= 0 ? microseconds_from((usRTTSample - m_iFirstRTT) / 2) : duration(0); + const time_point tsPktBaseTime = getPktTsbPdBaseTime(usPktTimestamp); + const steady_clock::duration tdDrift = tsPktArrival - tsPktBaseTime - tdRTTDelta; + + const bool updated = m_DriftTracer.update(count_microseconds(tdDrift)); + + if (updated) + { + IF_HEAVY_LOGGING(const steady_clock::time_point oldbase = m_tsTsbPdTimeBase); + steady_clock::duration overdrift = microseconds_from(m_DriftTracer.overdrift()); + m_tsTsbPdTimeBase += overdrift; + + HLOGC(brlog.Debug, + log << "DRIFT=" << FormatDuration(tdDrift) << " AVG=" << (m_DriftTracer.drift() / 1000.0) + << "ms, TB: " << FormatTime(oldbase) << " EXCESS: " << FormatDuration(overdrift) + << " UPDATED TO: " << FormatTime(m_tsTsbPdTimeBase)); + } + else + { + HLOGC(brlog.Debug, + log << "DRIFT=" << FormatDuration(tdDrift) << " TB REMAINS: " << FormatTime(m_tsTsbPdTimeBase)); + } + +#if SRT_DEBUG_TRACE_DRIFT + g_drift_logger.trace(usPktTimestamp, + usRTTSample, + count_microseconds(tdDrift), + m_DriftTracer.drift(), + m_DriftTracer.overdrift(), + tsPktBaseTime, + m_tsTsbPdTimeBase); +#endif + return updated; +} + +void CTsbpdTime::setTsbPdMode(const steady_clock::time_point& timebase, bool wrap, duration delay) +{ + m_bTsbPdMode = true; + m_bTsbPdWrapCheck = wrap; + + // Timebase passed here comes is calculated as: + // Tnow - hspkt.m_iTimeStamp + // where hspkt is the packet with SRT_CMD_HSREQ message. + // + // This function is called in the HSREQ reception handler only. + m_tsTsbPdTimeBase = timebase; + m_tdTsbPdDelay = delay; +} + +void CTsbpdTime::applyGroupTime(const steady_clock::time_point& timebase, + bool wrp, + uint32_t delay, + const steady_clock::duration& udrift) +{ + // Same as setTsbPdMode, but predicted to be used for group members. + // This synchronizes the time from the INTERNAL TIMEBASE of an existing + // socket's internal timebase. This is required because the initial time + // base stays always the same, whereas the internal timebase undergoes + // adjustment as the 32-bit timestamps in the sockets wrap. The socket + // newly added to the group must get EXACTLY the same internal timebase + // or otherwise the TsbPd time calculation will ship different results + // on different member sockets. + + m_bTsbPdMode = true; + + m_tsTsbPdTimeBase = timebase; + m_bTsbPdWrapCheck = wrp; + m_tdTsbPdDelay = microseconds_from(delay); + m_DriftTracer.forceDrift(count_microseconds(udrift)); +} + +void CTsbpdTime::applyGroupDrift(const steady_clock::time_point& timebase, + bool wrp, + const steady_clock::duration& udrift) +{ + // This is only when a drift was updated on one of the group members. + HLOGC(brlog.Debug, + log << "rcv-buffer: group synch uDRIFT: " << m_DriftTracer.drift() << " -> " << FormatDuration(udrift) + << " TB: " << FormatTime(m_tsTsbPdTimeBase) << " -> " << FormatTime(timebase)); + + m_tsTsbPdTimeBase = timebase; + m_bTsbPdWrapCheck = wrp; + + m_DriftTracer.forceDrift(count_microseconds(udrift)); +} + +CTsbpdTime::time_point CTsbpdTime::getTsbPdTimeBase(uint32_t timestamp_us) const +{ + // A data packet within [TSBPD_WRAP_PERIOD; 2 * TSBPD_WRAP_PERIOD] would end TSBPD wrap-aware state. + // Some incoming control packets may not update the TSBPD base (calling updateTsbPdTimeBase(..)), + // but may come before a data packet with a timestamp in this range. Therefore the whole range should be tracked. + const int64_t carryover_us = + (m_bTsbPdWrapCheck && timestamp_us <= 2 * TSBPD_WRAP_PERIOD) ? int64_t(CPacket::MAX_TIMESTAMP) + 1 : 0; + + return (m_tsTsbPdTimeBase + microseconds_from(carryover_us)); +} + +CTsbpdTime::time_point CTsbpdTime::getPktTsbPdTime(uint32_t usPktTimestamp) const +{ + return getPktTsbPdBaseTime(usPktTimestamp) + m_tdTsbPdDelay + microseconds_from(m_DriftTracer.drift()); +} + +CTsbpdTime::time_point CTsbpdTime::getPktTsbPdBaseTime(uint32_t usPktTimestamp) const +{ + return getTsbPdTimeBase(usPktTimestamp) + microseconds_from(usPktTimestamp); +} + +void CTsbpdTime::updateTsbPdTimeBase(uint32_t usPktTimestamp) +{ + if (m_bTsbPdWrapCheck) + { + // Wrap check period. + if ((usPktTimestamp >= TSBPD_WRAP_PERIOD) && (usPktTimestamp <= (TSBPD_WRAP_PERIOD * 2))) + { + /* Exiting wrap check period (if for packet delivery head) */ + m_bTsbPdWrapCheck = false; + m_tsTsbPdTimeBase += microseconds_from(int64_t(CPacket::MAX_TIMESTAMP) + 1); + LOGC(tslog.Debug, + log << "tsbpd wrap period ends with ts=" << usPktTimestamp << " - NEW TIME BASE: " + << FormatTime(m_tsTsbPdTimeBase) << " drift: " << m_DriftTracer.drift() << "us"); + } + return; + } + + // Check if timestamp is within the TSBPD_WRAP_PERIOD before reaching the MAX_TIMESTAMP. + if (usPktTimestamp > (CPacket::MAX_TIMESTAMP - TSBPD_WRAP_PERIOD)) + { + // Approching wrap around point, start wrap check period (if for packet delivery head) + m_bTsbPdWrapCheck = true; + LOGC(tslog.Debug, + log << "tsbpd wrap period begins with ts=" << usPktTimestamp + << " TIME BASE: " << FormatTime(m_tsTsbPdTimeBase) << " drift: " << m_DriftTracer.drift() << "us."); + } +} + +void CTsbpdTime::getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift) const +{ + ScopedLock lck(m_mtxRW); + w_tb = m_tsTsbPdTimeBase; + w_udrift = microseconds_from(m_DriftTracer.drift()); + w_wrp = m_bTsbPdWrapCheck; +} + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.h b/trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.h new file mode 100644 index 0000000000..3483c197f2 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/tsbpd_time.h @@ -0,0 +1,163 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2021 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#ifndef INC_SRT_TSBPD_TIME_H +#define INC_SRT_TSBPD_TIME_H + +#include "platform_sys.h" +#include "sync.h" +#include "utilities.h" + +namespace srt +{ + +/// @brief TimeStamp-Based Packet Delivery Mode (TSBPD) time conversion logic. +/// Used by the receiver to calculate delivery time of data packets. +/// See SRT Internet Draft Section "Timestamp-Based Packet Delivery". +class CTsbpdTime +{ + typedef srt::sync::steady_clock steady_clock; + typedef steady_clock::time_point time_point; + typedef steady_clock::duration duration; + typedef srt::sync::Mutex Mutex; + +public: + CTsbpdTime() + : m_iFirstRTT(-1) + , m_bTsbPdMode(false) + , m_tdTsbPdDelay(0) + , m_bTsbPdWrapCheck(false) + { + } + + /// Set TimeStamp-Based Packet Delivery Mode (receiver). + /// @param [in] timebase local time base (uSec) of packet time stamps including buffering delay. + /// @param [in] wrap wrapping period. + /// @param [in] delay negotiated TsbPD delay (buffering latency). + void setTsbPdMode(const time_point& timebase, bool wrap, duration delay); + + /// @brief Check if TSBPD logic is enabled. + /// @return true if TSBPD is enabled. + bool isEnabled() const { return m_bTsbPdMode; } + + /// @brief Apply new state derived from other members of a socket group. + /// @param timebase TSBPD base time. + /// @param wrp wrap period (enabled or not). + /// @param delay TSBPD delay. + /// @param udrift clock drift. + void applyGroupTime(const time_point& timebase, bool wrp, uint32_t delay, const duration& udrift); + + /// @brief Apply new clock state (TSBPD base and drift) derived from other members of a socket group. + /// @param timebase TSBPD base time. + /// @param wrp state of the wrapping period (enabled or disabled). + /// @param udrift clock drift. + void applyGroupDrift(const time_point& timebase, bool wrp, const duration& udrift); + + /// @brief Add new drift sample from an ACK-ACKACK pair. + /// ACKACK packets are sent immediately (except for UDP buffering). + /// Therefore their timestamp roughly corresponds to the time of sending + /// and can be used to estimate clock drift. + /// + /// @param [in] pktTimestamp Timestamp of the arrived ACKACK packet. + /// @param [in] tsPktArrival packet arrival time. + /// @param [in] usRTTSample RTT sample from an ACK-ACKACK pair. If no sample, pass '-1'. + /// + /// @return true if TSBPD base time has changed, false otherwise. + bool addDriftSample(uint32_t pktTimestamp, const time_point& tsPktArrival, int usRTTSample); + + /// @brief Handle timestamp of data packet when 32-bit integer carryover is about to happen. + /// When packet timestamp approaches CPacket::MAX_TIMESTAMP, the TSBPD base time should be + /// shifted accordingly to correctly handle new packets with timestamps starting from zero. + /// @param usPktTimestamp timestamp field value of a data packet. + void updateTsbPdTimeBase(uint32_t usPktTimestamp); + + /// @brief Get TSBPD base time adjusted for carryover, which occurs when + /// a packet's timestamp exceeds the UINT32_MAX and continues from zero. + /// @param [in] usPktTimestamp 32-bit value of packet timestamp field (microseconds). + /// + /// @return TSBPD base time for a provided packet timestamp. + time_point getTsbPdTimeBase(uint32_t usPktTimestamp) const; + + /// @brief Get packet TSBPD time without buffering delay and clock drift, which is + /// the target time for delivering the packet to an upstream application. + /// Essentially: getTsbPdTimeBase(usPktTimestamp) + usPktTimestamp + /// @param [in] usPktTimestamp 32-bit value of packet timestamp field (microseconds). + /// + /// @return Packet TSBPD base time without buffering delay. + time_point getPktTsbPdBaseTime(uint32_t usPktTimestamp) const; + + /// @brief Get packet TSBPD time with buffering delay and clock drift, which is + /// the target time for delivering the packet to an upstream application + /// (including drift and carryover effects, if any). + /// Essentially: getPktTsbPdBaseTime(usPktTimestamp) + m_tdTsbPdDelay + drift() + /// @param [in] usPktTimestamp 32-bit value of packet timestamp field (microseconds). + /// + /// @return Packet TSBPD time with buffering delay. + time_point getPktTsbPdTime(uint32_t usPktTimestamp) const; + + /// @brief Get current drift value. + /// @return current drift value. + int64_t drift() const { return m_DriftTracer.drift(); } + + /// @brief Get current overdrift value. + /// @return current overdrift value. + int64_t overdrift() const { return m_DriftTracer.overdrift(); } + + /// @brief Get internal state to apply to another member of a socket group. + /// @param w_tb TsbPd base time. + /// @param w_udrift drift value. + /// @param w_wrp wrap check. + void getInternalTimeBase(time_point& w_tb, bool& w_wrp, duration& w_udrift) const; + +private: + int m_iFirstRTT; // First measured RTT sample. + bool m_bTsbPdMode; // Receiver buffering and TSBPD is active when true. + duration m_tdTsbPdDelay; // Negotiated buffering delay. + + /// @brief Local time base for TsbPd. + /// @note m_tsTsbPdTimeBase is changed in the following cases: + /// 1. Initialized upon SRT_CMD_HSREQ packet as the difference with the current time: + /// = (NOW - PACKET_TIMESTAMP), at the time of HSREQ reception. + /// 2. Shifted forward on timestamp overflow (@see CTsbpdTime::updateTsbPdTimeBase), when overflow + /// of the timestamp field value of a data packet is detected. + /// += CPacket::MAX_TIMESTAMP + 1 + /// 3. Clock drift (@see CTsbpdTime::addDriftSample, executed exclusively + /// from ACKACK handler). This is updated with (positive or negative) TSBPD_DRIFT_MAX_VALUE + /// once the value of average drift exceeds this value in either direction. + /// += (+/-)TSBPD_DRIFT_MAX_VALUE + /// + /// @note The TSBPD base time is expected to hold the following condition: + /// (PACKET_TIMESTAMP + m_tsTsbPdTimeBase + drift) == NOW. + /// Then it can be used to estimate the origin time of a data packet, and calculate its delivery time + /// with buffering delay applied. + time_point m_tsTsbPdTimeBase; + + /// @note Packet timestamps wrap around every 01h11m35s (32-bit in usec). + /// A wrap check period starts 30 seconds (TSBPD_WRAP_PERIOD) before the wrap point. + /// During the wrap check period, packet timestamps smaller than 30 seconds + /// are considered to have been wrapped around. + /// The wrap check period ends 30 seconds after the wrap point, + /// after which the TSBPD base time is adjusted. + bool m_bTsbPdWrapCheck; // true: check packet time stamp wraparound (overflow). + static const uint32_t TSBPD_WRAP_PERIOD = (30 * 1000000); // 30 seconds (in usec) for timestamp wrapping period. + + /// Maximum clock drift (microseconds) above which TsbPD base time is already adjusted. + static const int TSBPD_DRIFT_MAX_VALUE = 5000; + /// Number of samples (ACKACK packets) on which to perform drift calculation and compensation. + static const int TSBPD_DRIFT_MAX_SAMPLES = 1000; + DriftTracer m_DriftTracer; + + /// Protect simultaneous change of state (read/write). + mutable Mutex m_mtxRW; +}; + +} // namespace srt + +#endif // INC_SRT_TSBPD_TIME_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/udt.h b/trunk/3rdparty/srt-1-fit/srtcore/udt.h index 77f903bd50..ee4c02f4d7 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/udt.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/udt.h @@ -64,16 +64,16 @@ modified by * file doesn't contain _FUNCTIONS_ predicted to be used in C - see udtc.h */ -#ifndef __UDT_H__ -#define __UDT_H__ +#ifndef INC_SRT_UDT_H +#define INC_SRT_UDT_H #include "srt.h" /* -* SRT_ENABLE_THREADCHECK (THIS IS SET IN MAKEFILE NOT HERE) +* SRT_ENABLE_THREADCHECK IS SET IN MAKEFILE, NOT HERE */ #if defined(SRT_ENABLE_THREADCHECK) -#include +#include "threadcheck.h" #else #define THREAD_STATE_INIT(name) #define THREAD_EXIT() @@ -82,13 +82,6 @@ modified by #define INCREMENT_THREAD_ITERATIONS() #endif -/* Obsolete way to define MINGW */ -#ifndef __MINGW__ -#if defined(__MINGW32__) || defined(__MINGW64__) -#define __MINGW__ 1 -#endif -#endif - #ifdef __cplusplus #include #include @@ -96,10 +89,6 @@ modified by #include #endif - -// Legacy/backward/deprecated -#define UDT_API SRT_API - //////////////////////////////////////////////////////////////////////////////// //if compiling on VC6.0 or pre-WindowsXP systems @@ -108,83 +97,6 @@ modified by //if compiling with MinGW, it only works on XP or above //use -D_WIN32_WINNT=0x0501 - -//////////////////////////////////////////////////////////////////////////////// - -#ifdef __cplusplus -// This facility is used only for select() function. -// This is considered obsolete and the epoll() functionality rather should be used. -typedef std::set ud_set; -#define UD_CLR(u, uset) ((uset)->erase(u)) -#define UD_ISSET(u, uset) ((uset)->find(u) != (uset)->end()) -#define UD_SET(u, uset) ((uset)->insert(u)) -#define UD_ZERO(uset) ((uset)->clear()) -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// Legacy names - -#define UDT_MSS SRTO_MSS -#define UDT_SNDSYN SRTO_SNDSYN -#define UDT_RCVSYN SRTO_RCVSYN -#define UDT_FC SRTO_FC -#define UDT_SNDBUF SRTO_SNDBUF -#define UDT_RCVBUF SRTO_RCVBUF -#define UDT_LINGER SRTO_LINGER -#define UDP_SNDBUF SRTO_UDP_SNDBUF -#define UDP_RCVBUF SRTO_UDP_RCVBUF -#define UDT_MAXMSG SRTO_MAXMSG -#define UDT_MSGTTL SRTO_MSGTTL -#define UDT_RENDEZVOUS SRTO_RENDEZVOUS -#define UDT_SNDTIMEO SRTO_SNDTIMEO -#define UDT_RCVTIMEO SRTO_RCVTIMEO -#define UDT_REUSEADDR SRTO_REUSEADDR -#define UDT_MAXBW SRTO_MAXBW -#define UDT_STATE SRTO_STATE -#define UDT_EVENT SRTO_EVENT -#define UDT_SNDDATA SRTO_SNDDATA -#define UDT_RCVDATA SRTO_RCVDATA -#define SRT_SENDER SRTO_SENDER -#define SRT_TSBPDMODE SRTO_TSBPDMODE -#define SRT_TSBPDDELAY SRTO_TSBPDDELAY -#define SRT_INPUTBW SRTO_INPUTBW -#define SRT_OHEADBW SRTO_OHEADBW -#define SRT_PASSPHRASE SRTO_PASSPHRASE -#define SRT_PBKEYLEN SRTO_PBKEYLEN -#define SRT_KMSTATE SRTO_KMSTATE -#define SRT_IPTTL SRTO_IPTTL -#define SRT_IPTOS SRTO_IPTOS -#define SRT_TLPKTDROP SRTO_TLPKTDROP -#define SRT_TSBPDMAXLAG SRTO_TSBPDMAXLAG -#define SRT_RCVNAKREPORT SRTO_NAKREPORT -#define SRT_CONNTIMEO SRTO_CONNTIMEO -#define SRT_SNDPBKEYLEN SRTO_SNDPBKEYLEN -#define SRT_RCVPBKEYLEN SRTO_RCVPBKEYLEN -#define SRT_SNDPEERKMSTATE SRTO_SNDPEERKMSTATE -#define SRT_RCVKMSTATE SRTO_RCVKMSTATE - -#define UDT_EPOLL_OPT SRT_EPOLL_OPT -#define UDT_EPOLL_IN SRT_EPOLL_IN -#define UDT_EPOLL_OUT SRT_EPOLL_OUT -#define UDT_EPOLL_ERR SRT_EPOLL_ERR - -/* Binary backward compatibility obsolete options */ -#define SRT_NAKREPORT SRT_RCVNAKREPORT - -#if !defined(SRT_DISABLE_LEGACY_UDTSTATUS) -#define UDTSTATUS SRT_SOCKSTATUS -#define INIT SRTS_INIT -#define OPENED SRTS_OPENED -#define LISTENING SRTS_LISTENING -#define CONNECTING SRTS_CONNECTING -#define CONNECTED SRTS_CONNECTED -#define BROKEN SRTS_BROKEN -#define CLOSING SRTS_CLOSING -#define CLOSED SRTS_CLOSED -#define NONEXIST SRTS_NONEXIST -#endif - //////////////////////////////////////////////////////////////////////////////// struct CPerfMon @@ -236,166 +148,75 @@ typedef SRTSOCKET UDTSOCKET; //legacy alias #ifdef __cplusplus -// Class CUDTException exposed for C++ API. -// This is actually useless, unless you'd use a DIRECT C++ API, -// however there's no such API so far. The current C++ API for UDT/SRT -// is predicted to NEVER LET ANY EXCEPTION out of implementation, -// so it's useless to catch this exception anyway. - -class UDT_API CUDTException -{ -public: - - CUDTException(CodeMajor major = MJ_SUCCESS, CodeMinor minor = MN_NONE, int err = -1); - CUDTException(const CUDTException& e); - - ~CUDTException(); - - /// Get the description of the exception. - /// @return Text message for the exception description. - - const char* getErrorMessage(); - - /// Get the system errno for the exception. - /// @return errno. - - int getErrorCode() const; - - /// Get the system network errno for the exception. - /// @return errno. - - int getErrno() const; - /// Clear the error code. - - void clear(); - -private: - CodeMajor m_iMajor; // major exception categories - CodeMinor m_iMinor; // for specific error reasons - int m_iErrno; // errno returned by the system if there is any - std::string m_strMsg; // text error message - - std::string m_strAPI; // the name of UDT function that returns the error - std::string m_strDebug; // debug information, set to the original place that causes the error - -public: // Legacy Error Code - - static const int EUNKNOWN = SRT_EUNKNOWN; - static const int SUCCESS = SRT_SUCCESS; - static const int ECONNSETUP = SRT_ECONNSETUP; - static const int ENOSERVER = SRT_ENOSERVER; - static const int ECONNREJ = SRT_ECONNREJ; - static const int ESOCKFAIL = SRT_ESOCKFAIL; - static const int ESECFAIL = SRT_ESECFAIL; - static const int ECONNFAIL = SRT_ECONNFAIL; - static const int ECONNLOST = SRT_ECONNLOST; - static const int ENOCONN = SRT_ENOCONN; - static const int ERESOURCE = SRT_ERESOURCE; - static const int ETHREAD = SRT_ETHREAD; - static const int ENOBUF = SRT_ENOBUF; - static const int EFILE = SRT_EFILE; - static const int EINVRDOFF = SRT_EINVRDOFF; - static const int ERDPERM = SRT_ERDPERM; - static const int EINVWROFF = SRT_EINVWROFF; - static const int EWRPERM = SRT_EWRPERM; - static const int EINVOP = SRT_EINVOP; - static const int EBOUNDSOCK = SRT_EBOUNDSOCK; - static const int ECONNSOCK = SRT_ECONNSOCK; - static const int EINVPARAM = SRT_EINVPARAM; - static const int EINVSOCK = SRT_EINVSOCK; - static const int EUNBOUNDSOCK = SRT_EUNBOUNDSOCK; - static const int ESTREAMILL = SRT_EINVALMSGAPI; - static const int EDGRAMILL = SRT_EINVALBUFFERAPI; - static const int ENOLISTEN = SRT_ENOLISTEN; - static const int ERDVNOSERV = SRT_ERDVNOSERV; - static const int ERDVUNBOUND = SRT_ERDVUNBOUND; - static const int EINVALMSGAPI = SRT_EINVALMSGAPI; - static const int EINVALBUFFERAPI = SRT_EINVALBUFFERAPI; - static const int EDUPLISTEN = SRT_EDUPLISTEN; - static const int ELARGEMSG = SRT_ELARGEMSG; - static const int EINVPOLLID = SRT_EINVPOLLID; - static const int EASYNCFAIL = SRT_EASYNCFAIL; - static const int EASYNCSND = SRT_EASYNCSND; - static const int EASYNCRCV = SRT_EASYNCRCV; - static const int ETIMEOUT = SRT_ETIMEOUT; - static const int ECONGEST = SRT_ECONGEST; - static const int EPEERERR = SRT_EPEERERR; -}; +namespace srt { class CUDTException; } namespace UDT { -typedef CUDTException ERRORINFO; -//typedef UDT_SOCKOPT SOCKOPT; +typedef srt::CUDTException ERRORINFO; typedef CPerfMon TRACEINFO; -typedef CBytePerfMon TRACEBSTATS; -typedef ud_set UDSET; -UDT_API extern const SRTSOCKET INVALID_SOCK; +// This facility is used only for select() function. +// This is considered obsolete and the epoll() functionality rather should be used. +typedef std::set UDSET; +#define UD_CLR(u, uset) ((uset)->erase(u)) +#define UD_ISSET(u, uset) ((uset)->find(u) != (uset)->end()) +#define UD_SET(u, uset) ((uset)->insert(u)) +#define UD_ZERO(uset) ((uset)->clear()) + +SRT_API extern const SRTSOCKET INVALID_SOCK; #undef ERROR -UDT_API extern const int ERROR; - -UDT_API int startup(); -UDT_API int cleanup(); -UDT_API UDTSOCKET socket(int af, int type, int protocol); -UDT_API int bind(UDTSOCKET u, const struct sockaddr* name, int namelen); -UDT_API int bind2(UDTSOCKET u, UDPSOCKET udpsock); -UDT_API int listen(UDTSOCKET u, int backlog); -UDT_API UDTSOCKET accept(UDTSOCKET u, struct sockaddr* addr, int* addrlen); -UDT_API int connect(UDTSOCKET u, const struct sockaddr* name, int namelen); -UDT_API int close(UDTSOCKET u); -UDT_API int getpeername(UDTSOCKET u, struct sockaddr* name, int* namelen); -UDT_API int getsockname(UDTSOCKET u, struct sockaddr* name, int* namelen); -UDT_API int getsockopt(UDTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); -UDT_API int setsockopt(UDTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); -UDT_API int send(UDTSOCKET u, const char* buf, int len, int flags); -UDT_API int recv(UDTSOCKET u, char* buf, int len, int flags); - -UDT_API int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, uint64_t srctime = 0); -UDT_API int recvmsg(UDTSOCKET u, char* buf, int len, uint64_t& srctime); -UDT_API int recvmsg(UDTSOCKET u, char* buf, int len); - -UDT_API int64_t sendfile(UDTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = 364000); -UDT_API int64_t recvfile(UDTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = 7280000); -UDT_API int64_t sendfile2(UDTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 364000); -UDT_API int64_t recvfile2(UDTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 7280000); +SRT_API extern const int ERROR; + +SRT_API int startup(); +SRT_API int cleanup(); +SRT_API SRTSOCKET socket(); +inline SRTSOCKET socket(int , int , int ) { return socket(); } +SRT_API int bind(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API int bind2(SRTSOCKET u, UDPSOCKET udpsock); +SRT_API int listen(SRTSOCKET u, int backlog); +SRT_API SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen); +SRT_API int connect(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API int close(SRTSOCKET u); +SRT_API int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); +SRT_API int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); +SRT_API int send(SRTSOCKET u, const char* buf, int len, int flags); +SRT_API int recv(SRTSOCKET u, char* buf, int len, int flags); + +SRT_API int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, int64_t srctime = 0); +SRT_API int recvmsg(SRTSOCKET u, char* buf, int len, uint64_t& srctime); +SRT_API int recvmsg(SRTSOCKET u, char* buf, int len); + +SRT_API int64_t sendfile(SRTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = 364000); +SRT_API int64_t recvfile(SRTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = 7280000); +SRT_API int64_t sendfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 364000); +SRT_API int64_t recvfile2(SRTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 7280000); // select and selectEX are DEPRECATED; please use epoll. -UDT_API int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout); -UDT_API int selectEx(const std::vector& fds, std::vector* readfds, - std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); - -UDT_API int epoll_create(); -UDT_API int epoll_add_usock(int eid, UDTSOCKET u, const int* events = NULL); -UDT_API int epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); -UDT_API int epoll_remove_usock(int eid, UDTSOCKET u); -UDT_API int epoll_remove_ssock(int eid, SYSSOCKET s); -UDT_API int epoll_update_usock(int eid, UDTSOCKET u, const int* events = NULL); -UDT_API int epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); -UDT_API int epoll_wait(int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, +SRT_API int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout); +SRT_API int selectEx(const std::vector& fds, std::vector* readfds, + std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); + +SRT_API int epoll_create(); +SRT_API int epoll_add_usock(int eid, SRTSOCKET u, const int* events = NULL); +SRT_API int epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); +SRT_API int epoll_remove_usock(int eid, SRTSOCKET u); +SRT_API int epoll_remove_ssock(int eid, SYSSOCKET s); +SRT_API int epoll_update_usock(int eid, SRTSOCKET u, const int* events = NULL); +SRT_API int epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); +SRT_API int epoll_wait(int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); -UDT_API int epoll_wait2(int eid, UDTSOCKET* readfds, int* rnum, UDTSOCKET* writefds, int* wnum, int64_t msTimeOut, +SRT_API int epoll_wait2(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, SYSSOCKET* lrfds = NULL, int* lrnum = NULL, SYSSOCKET* lwfds = NULL, int* lwnum = NULL); -UDT_API int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); -UDT_API int epoll_release(int eid); -UDT_API ERRORINFO& getlasterror(); -UDT_API int getlasterror_code(); -UDT_API const char* getlasterror_desc(); -UDT_API int bstats(UDTSOCKET u, TRACEBSTATS* perf, bool clear = true); -UDT_API SRT_SOCKSTATUS getsockstate(UDTSOCKET u); - -// This is a C++ SRT API extension. This is not a part of legacy UDT API. -UDT_API void setloglevel(srt_logging::LogLevel::type ll); -UDT_API void addlogfa(srt_logging::LogFA fa); -UDT_API void dellogfa(srt_logging::LogFA fa); -UDT_API void resetlogfa(std::set fas); -UDT_API void resetlogfa(const int* fara, size_t fara_size); -UDT_API void setlogstream(std::ostream& stream); -UDT_API void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); -UDT_API void setlogflags(int flags); - -UDT_API bool setstreamid(UDTSOCKET u, const std::string& sid); -UDT_API std::string getstreamid(UDTSOCKET u); +SRT_API int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); +SRT_API int epoll_release(int eid); +SRT_API ERRORINFO& getlasterror(); +SRT_API int getlasterror_code(); +SRT_API const char* getlasterror_desc(); +SRT_API int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear = true); +SRT_API SRT_SOCKSTATUS getsockstate(SRTSOCKET u); } // namespace UDT @@ -405,7 +226,47 @@ UDT_API std::string getstreamid(UDTSOCKET u); // own logger FA objects, or create their own. The object of this type // is required to initialize the logger FA object. namespace srt_logging { struct LogConfig; } -UDT_API extern srt_logging::LogConfig srt_logger_config; +SRT_API extern srt_logging::LogConfig srt_logger_config; + +namespace srt +{ + +// This is a C++ SRT API extension. This is not a part of legacy UDT API. +SRT_API void setloglevel(srt_logging::LogLevel::type ll); +SRT_API void addlogfa(srt_logging::LogFA fa); +SRT_API void dellogfa(srt_logging::LogFA fa); +SRT_API void resetlogfa(std::set fas); +SRT_API void resetlogfa(const int* fara, size_t fara_size); +SRT_API void setlogstream(std::ostream& stream); +SRT_API void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); +SRT_API void setlogflags(int flags); + +SRT_API bool setstreamid(SRTSOCKET u, const std::string& sid); +SRT_API std::string getstreamid(SRTSOCKET u); + +// Namespace alias +namespace logging { + using namespace srt_logging; +} + +} // namespace srt + +// Planned deprecated removal: rel1.6.0 +// There's also no portable way possible to enforce a deprecation +// compiler warning, so leaving as is. +namespace UDT +{ + // Backward-compatible aliases, just for a case someone was using it. + using srt::setloglevel; + using srt::addlogfa; + using srt::dellogfa; + using srt::resetlogfa; + using srt::setlogstream; + using srt::setloghandler; + using srt::setlogflags; + using srt::setstreamid; + using srt::getstreamid; +} #endif /* __cplusplus */ diff --git a/trunk/3rdparty/srt-1-fit/srtcore/utilities.h b/trunk/3rdparty/srt-1-fit/srtcore/utilities.h old mode 100755 new mode 100644 index d323a16518..31e05b205e --- a/trunk/3rdparty/srt-1-fit/srtcore/utilities.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/utilities.h @@ -13,72 +13,14 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__SRT_UTILITIES_H -#define INC__SRT_UTILITIES_H - - -#ifdef __GNUG__ -#define ATR_UNUSED __attribute__((unused)) -#define ATR_DEPRECATED __attribute__((deprecated)) -#else -#define ATR_UNUSED -#define ATR_DEPRECATED -#endif - -#if defined(__cplusplus) && __cplusplus > 199711L -#define HAVE_CXX11 1 - -// For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on, -// however it's only the "most required C++11 support". -#if defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 // 4.7 only! -#define ATR_NOEXCEPT -#define ATR_CONSTEXPR -#define ATR_OVERRIDE -#define ATR_FINAL -#else -#define HAVE_FULL_CXX11 1 -#define ATR_NOEXCEPT noexcept -#define ATR_CONSTEXPR constexpr -#define ATR_OVERRIDE override -#define ATR_FINAL final -#endif - -// Microsoft Visual Studio supports C++11, but not fully, -// and still did not change the value of __cplusplus. Treat -// this special way. -// _MSC_VER == 1800 means Microsoft Visual Studio 2013. -#elif defined(_MSC_VER) && _MSC_VER >= 1800 -#define HAVE_CXX11 1 -#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026 -#define HAVE_FULL_CXX11 1 -#define ATR_NOEXCEPT noexcept -#define ATR_CONSTEXPR constexpr -#define ATR_OVERRIDE override -#define ATR_FINAL final -#else -#define ATR_NOEXCEPT -#define ATR_CONSTEXPR -#define ATR_OVERRIDE -#define ATR_FINAL -#endif -#else -#define HAVE_CXX11 0 -#define ATR_NOEXCEPT // throw() - bad idea -#define ATR_CONSTEXPR -#define ATR_OVERRIDE -#define ATR_FINAL - -#endif - -#if !HAVE_CXX11 && defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1 -#error "The currently compiled application required C++11, but your compiler doesn't support it." -#endif - +#ifndef INC_SRT_UTILITIES_H +#define INC_SRT_UTILITIES_H // Windows warning disabler #define _CRT_SECURE_NO_WARNINGS 1 #include "platform_sys.h" +#include "srt_attr_defs.h" // defines HAVE_CXX11 // Happens that these are defined, undefine them in advance #undef min @@ -88,10 +30,11 @@ written by #include #include #include +#include #include #include -#include #include +#include #if HAVE_CXX11 #include @@ -100,6 +43,7 @@ written by #include #include #include +#include // -------------- UTILITIES ------------------------ @@ -113,7 +57,7 @@ written by #endif -#if defined(__linux__) || defined(__CYGWIN__) || defined(__GNU__) +#if defined(__linux__) || defined(__CYGWIN__) || defined(__GNU__) || defined(__GLIBC__) # include @@ -171,7 +115,7 @@ written by # include -#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) # include @@ -196,6 +140,46 @@ written by # define le64toh(x) letoh64(x) #endif +#elif defined(SUNOS) + + // SunOS/Solaris + + #include + #include + + #define __LITTLE_ENDIAN 1234 + #define __BIG_ENDIAN 4321 + + # if defined(_BIG_ENDIAN) + #define __BYTE_ORDER __BIG_ENDIAN + #define be64toh(x) (x) + #define be32toh(x) (x) + #define be16toh(x) (x) + #define le16toh(x) ((uint16_t)BSWAP_16(x)) + #define le32toh(x) BSWAP_32(x) + #define le64toh(x) BSWAP_64(x) + #define htobe16(x) (x) + #define htole16(x) ((uint16_t)BSWAP_16(x)) + #define htobe32(x) (x) + #define htole32(x) BSWAP_32(x) + #define htobe64(x) (x) + #define htole64(x) BSWAP_64(x) + # else + #define __BYTE_ORDER __LITTLE_ENDIAN + #define be64toh(x) BSWAP_64(x) + #define be32toh(x) ntohl(x) + #define be16toh(x) ntohs(x) + #define le16toh(x) (x) + #define le32toh(x) (x) + #define le64toh(x) (x) + #define htobe16(x) htons(x) + #define htole16(x) (x) + #define htobe32(x) htonl(x) + #define htole32(x) (x) + #define htobe64(x) BSWAP_64(x) + #define htole64(x) (x) + # endif + #elif defined(__WINDOWS__) # include @@ -317,7 +301,7 @@ template struct BitsetMask { static const bool correct = true; - static const uint32_t value = 1 << R; + static const uint32_t value = 1u << R; }; // This is a trap for a case that BitsetMask::correct in the master template definition @@ -426,56 +410,116 @@ struct DynamicStruct }; -// ------------------------------------------------------------ +/// Fixed-size array template class. +namespace srt { +template +class FixedArray +{ +public: + FixedArray(size_t size) + : m_size(size) + , m_entries(new T[size]) + { + } + ~FixedArray() + { + delete [] m_entries; + } -inline bool IsSet(int32_t bitset, int32_t flagset) -{ - return (bitset & flagset) == flagset; -} +public: + const T& operator[](size_t index) const + { + if (index >= m_size) + raise_expection(index); -// Homecooked version of ref_t. It's a copy of std::reference_wrapper -// voided of unwanted properties and renamed to ref_t. + return m_entries[index]; + } + T& operator[](size_t index) + { + if (index >= m_size) + raise_expection(index); -#if HAVE_CXX11 -#include -#endif + return m_entries[index]; + } -template -class ref_t -{ - Type* m_data; + const T& operator[](int index) const + { + if (index < 0 || static_cast(index) >= m_size) + raise_expection(index); -public: - typedef Type type; + return m_entries[index]; + } -#if HAVE_CXX11 - explicit ref_t(Type& __indata) - : m_data(std::addressof(__indata)) - { } -#else - explicit ref_t(Type& __indata) - : m_data((Type*)(&(char&)(__indata))) - { } -#endif + T& operator[](int index) + { + if (index < 0 || static_cast(index) >= m_size) + raise_expection(index); - ref_t(const ref_t& inref) - : m_data(inref.m_data) - { } + return m_entries[index]; + } -#if HAVE_CXX11 - ref_t(const std::reference_wrapper& i): m_data(std::addressof(i.get())) {} -#endif + size_t size() const { return m_size; } + + typedef T* iterator; + typedef const T* const_iterator; - Type& operator*() { return *m_data; } + iterator begin() { return m_entries; } + iterator end() { return m_entries + m_size; } - Type& get() const - { return *m_data; } + const_iterator cbegin() const { return m_entries; } + const_iterator cend() const { return m_entries + m_size; } + + T* data() { return m_entries; } + +private: + FixedArray(const FixedArray& ); + FixedArray& operator=(const FixedArray&); + + void raise_expection(int i) const + { + std::stringstream ss; + ss << "Index " << i << "out of range"; + throw std::runtime_error(ss.str()); + } - Type operator->() const - { return *m_data; } +private: + size_t m_size; + T* const m_entries; +}; + +} // namespace srt + +// ------------------------------------------------------------ + + + +inline bool IsSet(int32_t bitset, int32_t flagset) +{ + return (bitset & flagset) == flagset; +} + +// std::addressof in C++11, +// needs to be provided for C++03 +template +inline RefType* AddressOf(RefType& r) +{ + return (RefType*)(&(unsigned char&)(r)); +} + +template +struct explicit_t +{ + T inobject; + explicit_t(const T& uo): inobject(uo) {} + + operator T() const { return inobject; } + +private: + template + explicit_t(const X& another); }; // This is required for Printable function if you have a container of pairs, @@ -492,15 +536,6 @@ namespace srt_pair_op #if HAVE_CXX11 -// This alias was created so that 'Ref' (not 'ref') is used everywhere. -// Normally the C++11 'ref' fits perfectly here, however in C++03 mode -// it would have to be newly created. This would then cause a conflict -// between C++03 SRT and C++11 applications as well as between C++ standard -// library and SRT when SRT is compiled in C++11 mode (as it happens on -// Darwin/clang). -template -inline auto Ref(In& i) -> decltype(std::ref(i)) { return std::ref(i); } - template inline auto Move(In& i) -> decltype(std::move(i)) { return std::move(i); } @@ -530,8 +565,6 @@ inline std::string Sprint(Args&&... args) template using UniquePtr = std::unique_ptr; -// Some utilities borrowed from tumux, as this is using options -// similar way. template inline std::string Printable(const Container& in, Value /*pseudoargument*/, Args&&... args) { @@ -577,12 +610,6 @@ auto map_getp(const Map& m, const Key& key) -> typename Map::mapped_type const* #else -template -ref_t Ref(Type& arg) -{ - return ref_t(arg); -} - // The unique_ptr requires C++11, and the rvalue-reference feature, // so here we're simulate the behavior using the old std::auto_ptr. @@ -610,14 +637,14 @@ class UniquePtr: public std::auto_ptr // All constructor declarations must be repeated. // "Constructor delegation" is also only C++11 feature. - explicit UniquePtr(element_type* __p = 0) throw() : Base(__p) {} - UniquePtr(UniquePtr& __a) throw() : Base(__a) { } - template - UniquePtr(UniquePtr<_Tp1>& __a) throw() : Base(__a) {} + explicit UniquePtr(element_type* p = 0) throw() : Base(p) {} + UniquePtr(UniquePtr& a) throw() : Base(a) { } + template + UniquePtr(UniquePtr& a) throw() : Base(a) {} - UniquePtr& operator=(UniquePtr& __a) throw() { return Base::operator=(__a); } - template - UniquePtr& operator=(UniquePtr<_Tp1>& __a) throw() { return Base::operator=(__a); } + UniquePtr& operator=(UniquePtr& a) throw() { return Base::operator=(a); } + template + UniquePtr& operator=(UniquePtr& a) throw() { return Base::operator=(a); } // Good, now we need to add some parts of the API of unique_ptr. @@ -630,7 +657,15 @@ class UniquePtr: public std::auto_ptr operator bool () { return 0!= get(); } }; -// A primitive one-argument version of Printable +// A primitive one-argument versions of Sprint and Printable +template +inline std::string Sprint(const Arg1& arg) +{ + std::ostringstream sout; + sout << arg; + return sout.str(); +} + template inline std::string Printable(const Container& in) { @@ -675,6 +710,44 @@ typename Map::mapped_type const* map_getp(const Map& m, const Key& key) #endif +// Printable with prefix added for every element. +// Useful when printing a container of sockets or sequence numbers. +template inline +std::string PrintableMod(const Container& in, const std::string& prefix) +{ + using namespace srt_pair_op; + typedef typename Container::value_type Value; + std::ostringstream os; + os << "[ "; + for (typename Container::const_iterator y = in.begin(); y != in.end(); ++y) + os << prefix << Value(*y) << " "; + os << "]"; + return os.str(); +} + +template +inline void FilterIf(InputIterator bg, InputIterator nd, + OutputIterator out, TransFunction fn) +{ + for (InputIterator i = bg; i != nd; ++i) + { + std::pair result = fn(*i); + if (!result.second) + continue; + *out++ = result.first; + } +} + +template +inline void insert_uniq(std::vector& v, const ArgValue& val) +{ + typename std::vector::iterator i = std::find(v.begin(), v.end(), val); + if (i != v.end()) + return; + + v.push_back(val); +} + template struct CallbackHolder { @@ -696,7 +769,8 @@ struct CallbackHolder // Casting function-to-function, however, should not. Unfortunately // newer compilers disallow that, too (when a signature differs), but // then they should better use the C++11 way, much more reliable and safer. - void* (*testfn)(void*) ATR_UNUSED = (void*(*)(void*))f; + void* (*testfn)(void*) = (void*(*)(void*))f; + (void)(testfn); #endif opaque = o; fn = f; @@ -767,11 +841,13 @@ class DriftTracer m_qDriftSum += driftval; ++m_uDriftSpan; + // I moved it here to calculate accumulated overdrift. + if (CLEAR_ON_UPDATE) + m_qOverdrift = 0; + if (m_uDriftSpan < MAX_SPAN) return false; - if (CLEAR_ON_UPDATE) - m_qOverdrift = 0; // Calculate the median of all drift values. // In most cases, the divisor should be == MAX_SPAN. @@ -799,6 +875,12 @@ class DriftTracer return true; } + // For group overrides + void forceDrift(int64_t driftval) + { + m_qDrift = driftval; + } + // These values can be read at any time, however if you want // to depend on the fact that they have been changed lately, // you have to check the return value from update(). @@ -869,15 +951,15 @@ struct MapProxy } }; +/// Print some hash-based stamp of the first 16 bytes in the buffer inline std::string BufferStamp(const char* mem, size_t size) { using namespace std; char spread[16]; - int n = 16-size; - if (n > 0) - memset(spread+16-n, 0, n); - memcpy(spread, mem, min(size_t(16), size)); + if (size < 16) + memset((spread + size), 0, 16 - size); + memcpy((spread), mem, min(size_t(16), size)); // Now prepare 4 cells for uint32_t. union @@ -885,7 +967,7 @@ inline std::string BufferStamp(const char* mem, size_t size) uint32_t sum; char cells[4]; }; - memset(cells, 0, 4); + memset((cells), 0, 4); for (size_t x = 0; x < 4; ++x) for (size_t y = 0; y < 4; ++y) @@ -894,9 +976,7 @@ inline std::string BufferStamp(const char* mem, size_t size) } // Convert to hex string - ostringstream os; - os << hex << uppercase << setfill('0') << setw(8) << sum; return os.str(); @@ -963,7 +1043,56 @@ ATR_CONSTEXPR size_t Size(const V (&)[N]) ATR_NOEXCEPT { return N; } template inline ValueType avg_iir(ValueType old_value, ValueType new_value) { - return (old_value*(DEPRLEN-1) + new_value)/DEPRLEN; + return (old_value * (DEPRLEN - 1) + new_value) / DEPRLEN; +} + +template +inline ValueType avg_iir_w(ValueType old_value, ValueType new_value, size_t new_val_weight) +{ + return (old_value * (DEPRLEN - new_val_weight) + new_value * new_val_weight) / DEPRLEN; } +// Property accessor definitions +// +// "Property" is a special method that accesses given field. +// This relies only on a convention, which is the following: +// +// V x = object.prop(); <-- get the property's value +// object.prop(x); <-- set the property a value +// +// Properties might be also chained when setting: +// +// object.prop1(v1).prop2(v2).prop3(v3); +// +// Properties may be defined various even very complicated +// ways, which is simply providing a method with body. In order +// to define a property simplest possible way, that is, refer +// directly to the field that keeps it, here are the following macros: +// +// Prefix: SRTU_PROPERTY_ +// Followed by: +// - access type: RO, WO, RW, RR, RRW +// - chain flag: optional _CHAIN +// Where access type is: +// - RO - read only. Defines reader accessor. The accessor method will be const. +// - RR - read reference. The accessor isn't const to allow reference passthrough. +// - WO - write only. Defines writer accessor. +// - RW - combines RO and WO. +// - RRW - combines RR and WO. +// +// The _CHAIN marker is optional for macros providing writable accessors +// for properties. The difference is that while simple write accessors return +// void, the chaining accessors return the reference to the object for which +// the write accessor was called so that you can call the next accessor (or +// any other method as well) for the result. + +#define SRTU_PROPERTY_RR(type, name, field) type name() { return field; } +#define SRTU_PROPERTY_RO(type, name, field) type name() const { return field; } +#define SRTU_PROPERTY_WO(type, name, field) void set_##name(type arg) { field = arg; } +#define SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) otype& set_##name(type arg) { field = arg; return *this; } +#define SRTU_PROPERTY_RW(type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO(type, name, field) +#define SRTU_PROPERTY_RRW(type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO(type, name, field) +#define SRTU_PROPERTY_RW_CHAIN(otype, type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) +#define SRTU_PROPERTY_RRW_CHAIN(otype, type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) + #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/version.h.in b/trunk/3rdparty/srt-1-fit/srtcore/version.h.in index d5ab886424..c32ac12260 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/version.h.in +++ b/trunk/3rdparty/srt-1-fit/srtcore/version.h.in @@ -13,8 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC__SRT_VERSION_H -#define INC__SRT_VERSION_H +#ifndef INC_SRT_VERSION_H +#define INC_SRT_VERSION_H // To construct version value #define SRT_MAKE_VERSION(major, minor, patch) \ @@ -24,11 +24,11 @@ written by #define SRT_VERSION_MAJOR @SRT_VERSION_MAJOR@ #define SRT_VERSION_MINOR @SRT_VERSION_MINOR@ #define SRT_VERSION_PATCH @SRT_VERSION_PATCH@ -#cmakedefine SRT_VERSION_BUILD @APPVEYOR_BUILD_NUMBER_STRING@ +#cmakedefine SRT_VERSION_BUILD @CI_BUILD_NUMBER_STRING@ #define SRT_VERSION_STRING "@SRT_VERSION@" #define SRT_VERSION_VALUE \ SRT_MAKE_VERSION_VALUE( \ SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH ) -#endif // INC__SRT_VERSION_H +#endif // INC_SRT_VERSION_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/window.cpp b/trunk/3rdparty/srt-1-fit/srtcore/window.cpp index dfa1449e3a..b077178c96 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/window.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/window.cpp @@ -50,6 +50,8 @@ modified by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include #include #include "common.h" @@ -57,7 +59,10 @@ modified by #include using namespace std; +using namespace srt::sync; +namespace srt +{ namespace ACKWindowTools { @@ -65,7 +70,7 @@ void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t s { r_aSeq[r_iHead].iACKSeqNo = seq; r_aSeq[r_iHead].iACK = ack; - r_aSeq[r_iHead].TimeStamp = CTimer::getTime(); + r_aSeq[r_iHead].tsTimeStamp = steady_clock::now(); r_iHead = (r_iHead + 1) % size; @@ -74,27 +79,26 @@ void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t s r_iTail = (r_iTail + 1) % size; } -int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack) +int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack, const steady_clock::time_point& currtime) { + // Head has not exceeded the physical boundary of the window if (r_iHead >= r_iTail) { - // Head has not exceeded the physical boundary of the window - for (int i = r_iTail, n = r_iHead; i < n; ++ i) { - // looking for indentical ACK Seq. No. + // Looking for an identical ACK Seq. No. if (seq == r_aSeq[i].iACKSeqNo) { - // return the Data ACK it carried + // Return the Data ACK it carried r_ack = r_aSeq[i].iACK; - // calculate RTT - int rtt = int(CTimer::getTime() - r_aSeq[i].TimeStamp); + // Calculate RTT estimate + const int rtt = count_microseconds(currtime - r_aSeq[i].tsTimeStamp); if (i + 1 == r_iHead) { r_iTail = r_iHead = 0; - r_aSeq[0].iACKSeqNo = -1; + r_aSeq[0].iACKSeqNo = SRT_SEQNO_NONE; } else r_iTail = (i + 1) % size; @@ -103,22 +107,22 @@ int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int3 } } - // Bad input, the ACK node has been overwritten + // The record about ACK is not found in the buffer, RTT can not be calculated return -1; } // Head has exceeded the physical window boundary, so it is behind tail for (int j = r_iTail, n = r_iHead + size; j < n; ++ j) { - // looking for indentical ACK seq. no. + // Looking for an identical ACK Seq. No. if (seq == r_aSeq[j % size].iACKSeqNo) { - // return Data ACK + // Return the Data ACK it carried j %= size; r_ack = r_aSeq[j].iACK; - // calculate RTT - int rtt = int(CTimer::getTime() - r_aSeq[j].TimeStamp); + // Calculate RTT estimate + const int rtt = count_microseconds(currtime - r_aSeq[j].tsTimeStamp); if (j == r_iHead) { @@ -132,14 +136,16 @@ int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int3 } } - // bad input, the ACK node has been overwritten + // The record about ACK is not found in the buffer, RTT can not be calculated return -1; } -} + +} // namespace AckTools +} // namespace srt //////////////////////////////////////////////////////////////////////////////// -void CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeWindow, int* r_bytesWindow, size_t asize, size_t psize) +void srt::CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeWindow, int* r_bytesWindow, size_t asize, size_t psize) { for (size_t i = 0; i < asize; ++ i) r_pktWindow[i] = 1000000; //1 sec -> 1 pkt/sec @@ -148,11 +154,11 @@ void CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeW r_probeWindow[k] = 1000; //1 msec -> 1000 pkts/sec for (size_t i = 0; i < asize; ++ i) - r_bytesWindow[i] = CPacket::SRT_MAX_PAYLOAD_SIZE; //based on 1 pkt/sec set in r_pktWindow[i] + r_bytesWindow[i] = srt::CPacket::SRT_MAX_PAYLOAD_SIZE; //based on 1 pkt/sec set in r_pktWindow[i] } -int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, size_t asize, int& bytesps) +int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, size_t asize, int& bytesps) { // get median value, but cannot change the original value order in the window std::copy(window, window + asize, replica); @@ -185,7 +191,7 @@ int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, cons // claculate speed, or return 0 if not enough valid value if (count > (asize >> 1)) { - bytes += (CPacket::SRT_DATA_HDR_SIZE * count); //Add protocol headers to bytes received + bytes += (srt::CPacket::SRT_DATA_HDR_SIZE * count); //Add protocol headers to bytes received bytesps = (unsigned long)ceil(1000000.0 / (double(sum) / double(bytes))); return (int)ceil(1000000.0 / (sum / count)); } @@ -196,7 +202,7 @@ int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, cons } } -int CPktTimeWindowTools::getBandwidth_in(const int* window, int* replica, size_t psize) +int srt::CPktTimeWindowTools::getBandwidth_in(const int* window, int* replica, size_t psize) { // This calculation does more-less the following: // diff --git a/trunk/3rdparty/srt-1-fit/srtcore/window.h b/trunk/3rdparty/srt-1-fit/srtcore/window.h index 4182883726..ecc4a49478 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/window.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/window.h @@ -50,28 +50,31 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_WINDOW_H__ -#define __UDT_WINDOW_H__ +#ifndef INC_SRT_WINDOW_H +#define INC_SRT_WINDOW_H #ifndef _WIN32 #include #include #endif -#include "udt.h" #include "packet.h" +#include "udt.h" + +namespace srt +{ namespace ACKWindowTools { struct Seq { - int32_t iACKSeqNo; // Seq. No. for the ACK packet - int32_t iACK; // Data Seq. No. carried by the ACK packet - uint64_t TimeStamp; // The timestamp when the ACK was sent + int32_t iACKSeqNo; // Seq. No. of the ACK packet + int32_t iACK; // Data packet Seq. No. carried by the ACK packet + sync::steady_clock::time_point tsTimeStamp; // The timestamp when the ACK was sent }; void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t ack); - int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack); + int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack, const sync::steady_clock::time_point& currtime); } template @@ -83,28 +86,30 @@ class CACKWindow m_iHead(0), m_iTail(0) { - m_aSeq[0].iACKSeqNo = -1; + m_aSeq[0].iACKSeqNo = SRT_SEQNO_NONE; } ~CACKWindow() {} /// Write an ACK record into the window. - /// @param [in] seq ACK seq. no. - /// @param [in] ack DATA ACK no. + /// @param [in] seq Seq. No. of the ACK packet + /// @param [in] ack Data packet Seq. No. carried by the ACK packet void store(int32_t seq, int32_t ack) { return ACKWindowTools::store(m_aSeq, SIZE, m_iHead, m_iTail, seq, ack); } - /// Search the ACK-2 "seq" in the window, find out the DATA "ack" and caluclate RTT . - /// @param [in] seq ACK-2 seq. no. - /// @param [out] ack the DATA ACK no. that matches the ACK-2 no. - /// @return RTT. + /// Search the ACKACK "seq" in the window, find out the data packet "ack" + /// and calculate RTT estimate based on the ACK/ACKACK pair + /// @param [in] seq Seq. No. of the ACK packet carried within ACKACK + /// @param [out] ack Acknowledged data packet Seq. No. from the ACK packet that matches the ACKACK + /// @param [in] currtime The timestamp of ACKACK packet reception by the receiver + /// @return RTT - int acknowledge(int32_t seq, int32_t& r_ack) + int acknowledge(int32_t seq, int32_t& r_ack, const sync::steady_clock::time_point& currtime) { - return ACKWindowTools::acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, seq, r_ack); + return ACKWindowTools::acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, seq, r_ack, currtime); } private: @@ -112,7 +117,7 @@ class CACKWindow typedef ACKWindowTools::Seq Seq; Seq m_aSeq[SIZE]; - int m_iHead; // Pointer to the lastest ACK record + int m_iHead; // Pointer to the latest ACK record int m_iTail; // Pointer to the oldest ACK record private: @@ -143,24 +148,22 @@ class CPktTimeWindow: CPktTimeWindowTools m_iProbeWindowPtr(0), m_iLastSentTime(0), m_iMinPktSndInt(1000000), - m_LastArrTime(), - m_CurrArrTime(), - m_ProbeTime(), - m_Probe1Sequence(-1) + m_tsLastArrTime(sync::steady_clock::now()), + m_tsCurrArrTime(), + m_tsProbeTime(), + m_Probe1Sequence(SRT_SEQNO_NONE) { - pthread_mutex_init(&m_lockPktWindow, NULL); - pthread_mutex_init(&m_lockProbeWindow, NULL); - m_LastArrTime = CTimer::getTime(); + // Exception: up to CUDT ctor + sync::setupMutex(m_lockPktWindow, "PktWindow"); + sync::setupMutex(m_lockProbeWindow, "ProbeWindow"); CPktTimeWindowTools::initializeWindowArrays(m_aPktWindow, m_aProbeWindow, m_aBytesWindow, ASIZE, PSIZE); } ~CPktTimeWindow() { - pthread_mutex_destroy(&m_lockPktWindow); - pthread_mutex_destroy(&m_lockProbeWindow); } - +public: /// read the minimum packet sending interval. /// @return minimum packet sending interval (microseconds). @@ -169,19 +172,19 @@ class CPktTimeWindow: CPktTimeWindowTools /// Calculate the packets arrival speed. /// @return Packet arrival speed (packets per second). - int getPktRcvSpeed(ref_t bytesps) const + int getPktRcvSpeed(int& w_bytesps) const { // Lock access to the packet Window - CGuard cg(m_lockPktWindow); + sync::ScopedLock cg(m_lockPktWindow); int pktReplica[ASIZE]; // packet information window (inter-packet time) - return getPktRcvSpeed_in(m_aPktWindow, pktReplica, m_aBytesWindow, ASIZE, *bytesps); + return getPktRcvSpeed_in(m_aPktWindow, pktReplica, m_aBytesWindow, ASIZE, (w_bytesps)); } int getPktRcvSpeed() const { int bytesps; - return getPktRcvSpeed(Ref(bytesps)); + return getPktRcvSpeed((bytesps)); } /// Estimate the bandwidth. @@ -190,7 +193,7 @@ class CPktTimeWindow: CPktTimeWindowTools int getBandwidth() const { // Lock access to the packet Window - CGuard cg(m_lockProbeWindow); + sync::ScopedLock cg(m_lockProbeWindow); int probeReplica[PSIZE]; return getBandwidth_in(m_aProbeWindow, probeReplica, PSIZE); @@ -213,12 +216,12 @@ class CPktTimeWindow: CPktTimeWindowTools void onPktArrival(int pktsz = 0) { - CGuard cg(m_lockPktWindow); + sync::ScopedLock cg(m_lockPktWindow); - m_CurrArrTime = CTimer::getTime(); + m_tsCurrArrTime = sync::steady_clock::now(); // record the packet interval between the current and the last one - m_aPktWindow[m_iPktWindowPtr] = int(m_CurrArrTime - m_LastArrTime); + m_aPktWindow[m_iPktWindowPtr] = (int) sync::count_microseconds(m_tsCurrArrTime - m_tsLastArrTime); m_aBytesWindow[m_iPktWindowPtr] = pktsz; // the window is logically circular @@ -227,7 +230,7 @@ class CPktTimeWindow: CPktTimeWindowTools m_iPktWindowPtr = 0; // remember last packet arrival time - m_LastArrTime = m_CurrArrTime; + m_tsLastArrTime = m_tsCurrArrTime; } /// Shortcut to test a packet for possible probe 1 or 2 @@ -259,11 +262,11 @@ class CPktTimeWindow: CPktTimeWindowTools // Reset the starting probe into "undefined", when // a packet has come as retransmitted before the // measurement at arrival of 17th could be taken. - m_Probe1Sequence = -1; + m_Probe1Sequence = SRT_SEQNO_NONE; return; } - m_ProbeTime = CTimer::getTime(); + m_tsProbeTime = sync::steady_clock::now(); m_Probe1Sequence = pkt.m_iSeqNo; // Record the sequence where 16th packet probe was taken } @@ -279,26 +282,26 @@ class CPktTimeWindow: CPktTimeWindowTools // expected packet pair, behave as if the 17th packet was lost. // no start point yet (or was reset) OR not very next packet - if (m_Probe1Sequence == -1 || CSeqNo::incseq(m_Probe1Sequence) != pkt.m_iSeqNo) + if (m_Probe1Sequence == SRT_SEQNO_NONE || CSeqNo::incseq(m_Probe1Sequence) != pkt.m_iSeqNo) return; // Grab the current time before trying to acquire // a mutex. This might add extra delay and therefore // screw up the measurement. - const uint64_t now = CTimer::getTime(); + const sync::steady_clock::time_point now = sync::steady_clock::now(); // Lock access to the packet Window - CGuard cg(m_lockProbeWindow); + sync::ScopedLock cg(m_lockProbeWindow); - m_CurrArrTime = now; + m_tsCurrArrTime = now; // Reset the starting probe to prevent checking if the // measurement was already taken. - m_Probe1Sequence = -1; + m_Probe1Sequence = SRT_SEQNO_NONE; // record the probing packets interval // Adjust the time for what a complete packet would have take - const int64_t timediff = m_CurrArrTime - m_ProbeTime; + const int64_t timediff = sync::count_microseconds(m_tsCurrArrTime - m_tsProbeTime); const int64_t timediff_times_pl_size = timediff * CPacket::SRT_MAX_PAYLOAD_SIZE; // Let's take it simpler than it is coded here: @@ -312,11 +315,11 @@ class CPktTimeWindow: CPktTimeWindowTools // the ETH+IP+UDP+SRT header part elliminates the constant packet delivery time influence. // const size_t pktsz = pkt.getLength(); - m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? timediff_times_pl_size / pktsz : int(timediff); + m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? int(timediff_times_pl_size / pktsz) : int(timediff); // OLD CODE BEFORE BSTATS: // record the probing packets interval - // m_aProbeWindow[m_iProbeWindowPtr] = int(m_CurrArrTime - m_ProbeTime); + // m_aProbeWindow[m_iProbeWindowPtr] = int(m_tsCurrArrTime - m_tsProbeTime); // the window is logically circular ++ m_iProbeWindowPtr; @@ -324,29 +327,29 @@ class CPktTimeWindow: CPktTimeWindowTools m_iProbeWindowPtr = 0; } - private: - int m_aPktWindow[ASIZE]; // packet information window (inter-packet time) - int m_aBytesWindow[ASIZE]; // - int m_iPktWindowPtr; // position pointer of the packet info. window. - mutable pthread_mutex_t m_lockPktWindow; // used to synchronize access to the packet window + int m_aPktWindow[ASIZE]; // Packet information window (inter-packet time) + int m_aBytesWindow[ASIZE]; + int m_iPktWindowPtr; // Position pointer of the packet info. window + mutable sync::Mutex m_lockPktWindow; // Used to synchronize access to the packet window - int m_aProbeWindow[PSIZE]; // record inter-packet time for probing packet pairs - int m_iProbeWindowPtr; // position pointer to the probing window - mutable pthread_mutex_t m_lockProbeWindow; // used to synchronize access to the probe window + int m_aProbeWindow[PSIZE]; // Record inter-packet time for probing packet pairs + int m_iProbeWindowPtr; // Position pointer to the probing window + mutable sync::Mutex m_lockProbeWindow; // Used to synchronize access to the probe window - int m_iLastSentTime; // last packet sending time - int m_iMinPktSndInt; // Minimum packet sending interval + int m_iLastSentTime; // Last packet sending time + int m_iMinPktSndInt; // Minimum packet sending interval - uint64_t m_LastArrTime; // last packet arrival time - uint64_t m_CurrArrTime; // current packet arrival time - uint64_t m_ProbeTime; // arrival time of the first probing packet - int32_t m_Probe1Sequence; // sequence number for which the arrival time was notified + sync::steady_clock::time_point m_tsLastArrTime; // Last packet arrival time + sync::steady_clock::time_point m_tsCurrArrTime; // Current packet arrival time + sync::steady_clock::time_point m_tsProbeTime; // Arrival time of the first probing packet + int32_t m_Probe1Sequence; // Sequence number for which the arrival time was notified private: CPktTimeWindow(const CPktTimeWindow&); CPktTimeWindow &operator=(const CPktTimeWindow&); }; +} // namespace srt #endif diff --git a/trunk/src/app/srs_app_config.cpp b/trunk/src/app/srs_app_config.cpp index ebf8c65e5c..effecfc886 100644 --- a/trunk/src/app/srs_app_config.cpp +++ b/trunk/src/app/srs_app_config.cpp @@ -7599,7 +7599,7 @@ unsigned short SrsConfig::get_srt_listen_port() return (unsigned short)atoi(conf->arg0().c_str()); } -int SrsConfig::get_srto_maxbw() +int64_t SrsConfig::get_srto_maxbw() { SRS_OVERWRITE_BY_ENV_INT("srs.srt_server.maxbw"); // SRS_SRT_SERVER_MAXBW @@ -7613,7 +7613,7 @@ int SrsConfig::get_srto_maxbw() if (!conf || conf->arg0().empty()) { return DEFAULT; } - return atoi(conf->arg0().c_str()); + return atoll(conf->arg0().c_str()); } int SrsConfig::get_srto_mss() diff --git a/trunk/src/app/srs_app_config.hpp b/trunk/src/app/srs_app_config.hpp index 259ffc498a..4cbd4b9101 100644 --- a/trunk/src/app/srs_app_config.hpp +++ b/trunk/src/app/srs_app_config.hpp @@ -655,7 +655,7 @@ class SrsConfig // Get the srt service listen port virtual unsigned short get_srt_listen_port(); // Get the srt SRTO_MAXBW, max bandwith, default is -1. - virtual int get_srto_maxbw(); + virtual int64_t get_srto_maxbw(); // Get the srt SRTO_MSS, Maximum Segment Size, default is 1500. virtual int get_srto_mss(); // Get the srt SRTO_TSBPDMODE, timestamp base packet delivery mode, default is false. diff --git a/trunk/src/app/srs_app_srt_server.cpp b/trunk/src/app/srs_app_srt_server.cpp index 91840fbb4d..5f0635e405 100644 --- a/trunk/src/app/srs_app_srt_server.cpp +++ b/trunk/src/app/srs_app_srt_server.cpp @@ -66,7 +66,7 @@ srs_error_t SrsSrtAcceptor::set_srt_opt() srs_error_t err = srs_success; if ((err = srs_srt_set_maxbw(listener_->fd(), _srs_config->get_srto_maxbw())) != srs_success) { - return srs_error_wrap(err, "set opt maxbw=%d failed", _srs_config->get_srto_maxbw()); + return srs_error_wrap(err, "set opt maxbw=%" PRId64 " failed", _srs_config->get_srto_maxbw()); } if ((err = srs_srt_set_mss(listener_->fd(), _srs_config->get_srto_mss())) != srs_success) { diff --git a/trunk/src/protocol/srs_protocol_srt.cpp b/trunk/src/protocol/srs_protocol_srt.cpp index f751ac0a00..41888b2e14 100644 --- a/trunk/src/protocol/srs_protocol_srt.cpp +++ b/trunk/src/protocol/srs_protocol_srt.cpp @@ -221,7 +221,7 @@ srs_error_t srs_srt_nonblock(srs_srt_t srt_fd) return srs_success; } -srs_error_t srs_srt_set_maxbw(srs_srt_t srt_fd, int maxbw) +srs_error_t srs_srt_set_maxbw(srs_srt_t srt_fd, int64_t maxbw) { SET_SRT_OPT(srt_fd, SRTO_MAXBW, maxbw); return srs_success; @@ -311,7 +311,7 @@ srs_error_t srs_srt_set_pbkeylen(srs_srt_t srt_fd, int pbkeylen) return srs_success; } -srs_error_t srs_srt_get_maxbw(srs_srt_t srt_fd, int& maxbw) +srs_error_t srs_srt_get_maxbw(srs_srt_t srt_fd, int64_t& maxbw) { GET_SRT_OPT(srt_fd, SRTO_MAXBW, maxbw); return srs_success; diff --git a/trunk/src/protocol/srs_protocol_srt.hpp b/trunk/src/protocol/srs_protocol_srt.hpp index b3829f616e..90e6967638 100644 --- a/trunk/src/protocol/srs_protocol_srt.hpp +++ b/trunk/src/protocol/srs_protocol_srt.hpp @@ -34,7 +34,7 @@ extern srs_error_t srs_srt_listen(srs_srt_t srt_fd, std::string ip, int port); extern srs_error_t srs_srt_nonblock(srs_srt_t srt_fd); // Set SRT options. -extern srs_error_t srs_srt_set_maxbw(srs_srt_t srt_fd, int maxbw); +extern srs_error_t srs_srt_set_maxbw(srs_srt_t srt_fd, int64_t maxbw); extern srs_error_t srs_srt_set_mss(srs_srt_t srt_fd, int mss); extern srs_error_t srs_srt_set_payload_size(srs_srt_t srt_fd, int payload_size); extern srs_error_t srs_srt_set_connect_timeout(srs_srt_t srt_fd, int timeout); @@ -51,7 +51,7 @@ extern srs_error_t srs_srt_set_passphrase(srs_srt_t srt_fd, const std::string& p extern srs_error_t srs_srt_set_pbkeylen(srs_srt_t srt_fd, int pbkeylen); // Get SRT options. -extern srs_error_t srs_srt_get_maxbw(srs_srt_t srt_fd, int& maxbw); +extern srs_error_t srs_srt_get_maxbw(srs_srt_t srt_fd, int64_t& maxbw); extern srs_error_t srs_srt_get_mss(srs_srt_t srt_fd, int& mss); extern srs_error_t srs_srt_get_payload_size(srs_srt_t srt_fd, int& payload_size); extern srs_error_t srs_srt_get_connect_timeout(srs_srt_t srt_fd, int& timeout); diff --git a/trunk/src/utest/srs_utest_srt.cpp b/trunk/src/utest/srs_utest_srt.cpp index e945b33d07..dc9b154b2c 100644 --- a/trunk/src/utest/srs_utest_srt.cpp +++ b/trunk/src/utest/srs_utest_srt.cpp @@ -74,7 +74,7 @@ VOID TEST(ServiceSrtPoller, SrtSetGetSocketOpt) HELPER_EXPECT_SUCCESS(srs_srt_socket(&srt_fd)); HELPER_EXPECT_SUCCESS(srs_srt_nonblock(srt_fd)); - int maxbw = 20000; + int64_t maxbw = 20000; int mss = 1400; int payload_size = 1316; int connect_timeout = 5000; @@ -104,10 +104,11 @@ VOID TEST(ServiceSrtPoller, SrtSetGetSocketOpt) bool b; int i = 0; + int64_t i64 = 0; std::string s; - HELPER_EXPECT_SUCCESS(srs_srt_get_maxbw(srt_fd, i)); - EXPECT_EQ(i, maxbw); + HELPER_EXPECT_SUCCESS(srs_srt_get_maxbw(srt_fd, i64)); + EXPECT_EQ(i64, maxbw); HELPER_EXPECT_SUCCESS(srs_srt_get_mss(srt_fd, i)); EXPECT_EQ(i, mss); HELPER_EXPECT_SUCCESS(srs_srt_get_payload_size(srt_fd, i));