diff --git a/trunk/3rdparty/README.md b/trunk/3rdparty/README.md index ebeebd526d6..3bcd0479367 100644 --- a/trunk/3rdparty/README.md +++ b/trunk/3rdparty/README.md @@ -9,8 +9,8 @@ nginx-1.5.7.zip * for srs to support hls streaming. srt-1-fit -srt-1.4.1.tar.gz -* https://github.com/Haivision/srt/releases/tag/v1.4.1 +srt-1.5.3.tar.gz +* https://github.com/Haivision/srt/releases/tag/v1.5.3 * https://ossrs.net/lts/zh-cn/license#srt openssl-1.1-fit diff --git a/trunk/3rdparty/srt-1-fit/CMakeLists.txt b/trunk/3rdparty/srt-1-fit/CMakeLists.txt index 7fafe77ccca..1492306a28f 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.3) 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) - -# 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_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") -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 @@ -90,33 +117,76 @@ if (ENABLE_DEBUG) set(ENABLE_HEAVY_LOGGING_DEFAULT ON) endif() +# Note that the IP_PKTINFO has a limited portability and may not work on some platforms +# (Windows, FreeBSD, ...). +set (ENABLE_PKTINFO_DEFAULT OFF) + +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}) 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_PKTINFO "Enable using IP_PKTINFO to allow the listener extracting the target IP address from incoming packets" ${ENABLE_PKTINFO_DEFAULT}) 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) +option(ENABLE_AEAD_API_PREVIEW "Enable AEAD API preview in SRT" Off) +option(ENABLE_MAXREXMITBW "Enable SRTO_MAXREXMITBW (v1.6.0 API preview)" Off) option(ENABLE_CXX_DEPS "Extra library dependencies in srt.pc for the CXX libraries useful with C language" ON) 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_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") @@ -135,6 +205,16 @@ else() message(STATUS "USE_BUSY_WAITING: OFF (default)") endif() +# Reduce the frequency of some frequent logs, milliseconds +set(SRT_LOG_SLOWDOWN_FREQ_MS_DEFAULT 1000) # 1s +if (NOT DEFINED SRT_LOG_SLOWDOWN_FREQ_MS) + if (ENABLE_HEAVY_LOGGING) + set(SRT_LOG_SLOWDOWN_FREQ_MS 0) # Just show every log message. + else() + set(SRT_LOG_SLOWDOWN_FREQ_MS ${SRT_LOG_SLOWDOWN_FREQ_MS_DEFAULT}) + endif() +endif() + if ( CYGWIN AND NOT CYGWIN_USE_POSIX ) set(WIN32 1) set(CMAKE_LEGACY_CYGWIN_WIN32 1) @@ -149,7 +229,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 +239,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() @@ -199,7 +300,13 @@ if(WIN32) if(ENABLE_INET_PTON) set(CMAKE_REQUIRED_LIBRARIES ws2_32) check_function_exists(inet_pton HAVE_INET_PTON) - add_definitions(-D_WIN32_WINNT=0x0600) + try_compile(AT_LEAST_VISTA + ${CMAKE_BINARY_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/test_vista.c") + if(NOT AT_LEAST_VISTA) + # force targeting Vista + add_definitions(-D_WIN32_WINNT=0x0600) + endif() else() add_definitions(-D_WIN32_WINNT=0x0501) endif() @@ -210,7 +317,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 +347,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,49 +399,135 @@ 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) message(STATUS "ENCRYPTION: ENABLED, using: ${SSL_REQUIRED_MODULES}") message (STATUS "SSL libraries: ${SSL_LIBRARIES}") + + if (ENABLE_AEAD_API_PREVIEW) + if ("${USE_ENCLIB}" STREQUAL "openssl-evp") + add_definitions(-DENABLE_AEAD_API_PREVIEW) + message(STATUS "ENCRYPTION AEAD API: ENABLED") + else() + message(FATAL_ERROR "ENABLE_AEAD_API_PREVIEW is only available with USE_ENCLIB=openssl-evp!") + endif() + else() + message(STATUS "ENCRYPTION AEAD API: DISABLED") + endif() + else() message(STATUS "ENCRYPTION: DISABLED") + message(STATUS "ENCRYPTION AEAD API: N/A") 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() -# Detect if the compiler is GNU compatable for flags -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Intel|Clang|AppleClang") +if (ENABLE_MAXREXMITBW) + add_definitions(-DENABLE_MAXREXMITBW) + message(STATUS "MAXREXMITBW API: ENABLED") +else() + message(STATUS "MAXREXMITBW API: DISABLED") +endif() + +if (USING_DEFAULT_COMPILER_PREFIX) +# Detect if the compiler is GNU compatible for flags +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() + +# Check for std::put_time(): +# Sets: +# HAVE_CXX_STD_PUT_TIME +include(CheckCXXStdPutTime) +CheckCXXStdPutTime() +if (HAVE_CXX_STD_PUT_TIME) + add_definitions(-DHAVE_CXX_STD_PUT_TIME=1) +endif() + if (DISABLE_CXX11) set (ENABLE_CXX11 0) elseif( DEFINED ENABLE_CXX11 ) @@ -314,13 +535,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 (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 if (HAVE_COMPILER_GNU_COMPAT) set (SRT_GCC_WARN "-Wall -Wextra") + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set (SRT_GCC_WARN "${SRT_GCC_WARN} -Wshadow=local") + endif() else() # cpp debugging on Windows :D #set (SRT_GCC_WARN "/showIncludes") @@ -334,14 +672,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 +688,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 +707,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 +737,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 +765,28 @@ if (ENABLE_GETNAMEINFO) list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_GETNAMEINFO=1") endif() +if (ENABLE_PKTINFO) + if (WIN32 OR BSD) + message(FATAL_ERROR "PKTINFO is not implemented on Windows or *BSD.") + endif() + + list(APPEND SRT_EXTRA_CFLAGS "-DSRT_ENABLE_PKTINFO=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 +795,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 +814,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 +831,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 +846,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 +927,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 +939,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 +956,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 +1047,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 +1058,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 +1090,41 @@ 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() +target_compile_definitions(srt_virtual PRIVATE -DSRT_LOG_SLOWDOWN_FREQ_MS=${SRT_LOG_SLOWDOWN_FREQ_MS}) + 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() +elseif (LINUX AND HAVE_LIBATOMIC AND HAVE_LIBATOMIC_COMPILES) + # This is a workaround for some older Linux Toolchains. + 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 +1135,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() # --- @@ -767,7 +1183,7 @@ endif() # obtained by `pkg-config --libs`. if(ENABLE_CXX_DEPS) foreach(LIB ${CMAKE_CXX_IMPLICIT_LINK_LIBRARIES}) - if(IS_ABSOLUTE ${LIB} AND EXISTS ${LIB}) + if((IS_ABSOLUTE ${LIB} AND EXISTS ${LIB}) OR (${LIB} MATCHES "^-l")) set(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${LIB}) else() set(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} "-l${LIB}") @@ -777,22 +1193,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 +1217,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 +1254,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 +1270,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 +1292,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 +1304,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 +1327,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 +1340,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 +1365,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,34 +1385,56 @@ 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(sendmsg.cpp) + + srt_add_example(recvmsg.cpp) 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) + # Version ranges are only supported with CMake 3.19 or later. + # Need GTest v1.10 or higher to support GTEST_SKIP. + if (${CMAKE_VERSION} VERSION_LESS "3.19.0") + find_package(GTest 1.10) + else() + find_package(GTest 1.10...1.12) + endif() if (NOT GTEST_FOUND) message(STATUS "GTEST not found! Fetching from git.") include(googletest) @@ -983,14 +1446,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 +1461,32 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) ${PTHREAD_LIBRARY} ) - add_test( - NAME - test-srt - COMMAND - ${CMAKE_BINARY_DIR}/test-srt - ) + if (USE_OLD_ADD_METHOD) + add_test( + NAME test-srt + COMMAND ${CMAKE_BINARY_DIR}/test-srt + ) + #set_tests_properties(test-srt PROPERTIES RUN_SERIAL TRUE) + else() + gtest_add_tests( + TEST_LIST tests_srt + TARGET test-srt + ) + set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) + 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 +1495,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 098ed2d3b5c..00000000000 --- 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 6d761b5e5c4..7d83126412f 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 781bbe56345..ff1bc65de63 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 83ea7b09824..2778cea7970 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 946df108dd6..796e0a1511b 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 446b9514b8f..2944c1c77f0 100644 --- a/trunk/3rdparty/srt-1-fit/configure-data.tcl +++ b/trunk/3rdparty/srt-1-fit/configure-data.tcl @@ -25,47 +25,56 @@ # 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-pktinfo "Should pktinfo reading and using be enabled (POSIX only) (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 +171,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 +197,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 +235,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 +258,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 +273,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 +281,8 @@ proc postprocess {} { set iscross 1 } } + } else { + puts "CONFIGURE: default compiler used" } } @@ -332,8 +370,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 747653d12ba..ee4921dbe5f 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 217a70e8ac4..95eaf2856ce 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 c10727a0aa6..a961cca61e4 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.c @@ -13,8 +13,10 @@ written by Haivision Systems Inc. + 2022-05-19 (jdube) + CRYSPR2 adaptation 2019-06-27 (jdube) - GnuTLS/Nettle CRYSPR/4SRT (CRYypto Service PRovider for SRT) + MBedTLS CRYSPR/4SRT (CRYypto Service PRovider for SRT) *****************************************************************************/ #include "hcrypt.h" @@ -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 { +typedef struct tag_crysprMBedTLS_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,25 +54,30 @@ 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). if (bEncrypt) { /* Encrypt key */ - ret = mbedtls_aes_setkey_enc(aes_key, kstr, kstr_len*8); + ret = mbedtls_aes_setkey_enc(aes_key, kstr, (unsigned int)kstr_len*8); } else { /* Decrypt key */ - ret = mbedtls_aes_setkey_dec(aes_key, kstr, kstr_len*8); + ret = mbedtls_aes_setkey_dec(aes_key, kstr, (unsigned int)kstr_len*8); } return ret == 0 ? 0 : -1; @@ -81,8 +91,8 @@ int crysprMbedtls_AES_EcbCipher( /* AES Electronic Codebook cipher*/ unsigned char *out_txt, /* dst (cipher text) */ size_t *outlen) /* dst len */ { - int nblk = inlen/CRYSPR_AESBLKSZ; - int nmore = inlen%CRYSPR_AESBLKSZ; + int nblk = (int)(inlen/CRYSPR_AESBLKSZ); + int nmore = (int)(inlen%CRYSPR_AESBLKSZ); int i; if (bEncrypt) { @@ -146,6 +156,30 @@ 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; + + 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,9 +195,29 @@ 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); + itr, (uint32_t)key_len, out); + + mbedtls_md_free(&mdctx); if (ret == 0) return 0; @@ -196,8 +250,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 +279,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 aa10da8743f..f62b32e4860 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-mbedtls.h @@ -13,12 +13,12 @@ written by Haivision Systems Inc. 2019-06-27 (jdube) - GnuTLS/Nettle CRYSPR/4SRT (CRYypto Service PRovider for SRT) + MBedTLS CRYSPR/4SRT (CRYypto Service PRovider for SRT) *****************************************************************************/ -#ifndef CRYSPR_GNUTLS_H -#define CRYSPR_GNUTLS_H +#ifndef CRYSPR_MBEDTLS_H +#define CRYSPR_MBEDTLS_H #include #include @@ -52,12 +52,12 @@ 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); -#endif /* CRYSPR_GNUTLS_H */ +#endif /* CRYSPR_MBEDTLS_H */ 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 00000000000..3e87e9a5ef6 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.c @@ -0,0 +1,417 @@ +/* + * 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}, // HCRYPT_CTX_MODE_CLRTXT + {EVP_aes_128_ecb, EVP_aes_192_ecb, EVP_aes_256_ecb}, // HCRYPT_CTX_MODE_AESECB + {EVP_aes_128_ctr, EVP_aes_192_ctr, EVP_aes_256_ctr}, // HCRYPT_CTX_MODE_AESCTR + {NULL, NULL, NULL}, // HCRYPT_CTX_MODE_AESCBC + {EVP_aes_128_gcm, EVP_aes_192_gcm, EVP_aes_256_gcm}, // HCRYPT_CTX_MODE_AESGCM +}; + +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 = (int)((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; + case HCRYPT_CTX_MODE_AESGCM: + 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 = (int)(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, bEncrypt)) + { + 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, (int)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, (int)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; +} + +int crysprOpenSSL_EVP_AES_GCMCipher(bool bEncrypt, /* true:encrypt, false:decrypt */ + CRYSPR_AESCTX* aes_key, /* CRYpto Service PRovider AES Key context */ + unsigned char* iv, /* iv */ + const unsigned char* aad, /* associated data */ + size_t aadlen, + const unsigned char* indata, /* src */ + size_t inlen, /* length */ + unsigned char* out_txt, + unsigned char* out_tag) /* auth tag */ +{ + int c_len, f_len; + + /* 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; + } + + /* + * Provide any AAD data. This can be called zero or more times as + * required + */ + if (1 != EVP_CipherUpdate(aes_key, NULL, &c_len, aad, (int) aadlen)) + { + ERR_print_errors_fp(stderr); + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_EncryptUpdate 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, (int) inlen)) + { + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_CipherUpdate() failed"); + return -1; + } + + if (!bEncrypt && !EVP_CIPHER_CTX_ctrl(aes_key, EVP_CTRL_GCM_SET_TAG, HAICRYPT_AUTHTAG_MAX, out_tag)) { + ERR_print_errors_fp(stderr); + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_EncryptUpdate 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; + } + + /* Get the tag if we are encrypting */ + if (bEncrypt && !EVP_CIPHER_CTX_ctrl(aes_key, EVP_CTRL_GCM_GET_TAG, HAICRYPT_AUTHTAG_MAX, out_tag)) + { + ERR_print_errors_fp(stderr); + HCRYPT_LOG(LOG_ERR, "%s\n", "EVP_CIPHER_CTX_ctrl(EVP_CTRL_GCM_GET_TAG) failed"); + 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, (int)passwd_len, salt, (int)salt_len, itr, (int)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 + crysprOpenSSL_EVP_methods.aes_gcm_cipher = crysprOpenSSL_EVP_AES_GCMCipher; +#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 00000000000..a1fb0b925fa --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr-openssl-evp.h @@ -0,0 +1,67 @@ +/* + * 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_AESGCM to 1 if this CRYSPR has AES GCM cipher mode. OpenSSL EVP supports GCM. +*/ +#define CRYSPR_HAS_AESGCM 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 a5d50ac6510..87eb535ec55 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,18 +39,21 @@ 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)) { + if (AES_set_encrypt_key(kstr, (int)(kstr_len * 8), aes_key)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(kek) failed\n"); return(-1); } } else { /* Decrypt key */ - if (AES_set_decrypt_key(kstr, kstr_len * 8, aes_key)) { + if (AES_set_decrypt_key(kstr, (int)(kstr_len * 8), aes_key)) { HCRYPT_LOG(LOG_ERR, "%s", "AES_set_decrypt_key(kek) failed\n"); return(-1); } @@ -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 */ @@ -138,7 +163,7 @@ int crysprOpenSSL_KmPbkdf2( unsigned char *out) /* derived key */ { (void)cryspr_cb; - int rc = PKCS5_PBKDF2_HMAC_SHA1(passwd,passwd_len,salt,salt_len,itr,key_len,out); + int rc = PKCS5_PBKDF2_HMAC_SHA1(passwd,(int)passwd_len,salt,(int)salt_len,itr,(int)key_len,out); return(rc == 1? 0 : -1); } @@ -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 e59cbfc764c..2e3a68a599f 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.c @@ -14,7 +14,7 @@ written by Haivision Systems Inc. 2019-06-28 (jdube) - CRYSPR/4SRT Initial implementation. + CRYSPR/4SRT Initial implementation. *****************************************************************************/ #include "hcrypt.h" @@ -25,109 +25,135 @@ written by int crysprStub_Prng(unsigned char *rn, int len) { - (void)rn; - (void)len; - return(0); + (void)rn; + (void)len; + return(0); } int crysprStub_AES_SetKey( - 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 */ + 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)bEncrypt; - (void)kstr; - (void)kstr_len; - (void)aes_key; + (void)cipher_type; + (void)bEncrypt; + (void)kstr; + (void)kstr_len; + (void)aes_key; - return(0); + return(0); } int crysprStub_AES_EcbCipher( - bool bEncrypt, /* true:encrypt, false:decrypt */ - CRYSPR_AESCTX *aes_key, /* AES context */ - const unsigned char *indata,/* src (clear text)*/ - size_t inlen, /* length */ - unsigned char *out_txt, /* dst (cipher text) */ - size_t *outlen) /* dst len */ + bool bEncrypt, /* true:encrypt, false:decrypt */ + CRYSPR_AESCTX *aes_key, /* AES context */ + const unsigned char *indata,/* src (clear text)*/ + size_t inlen, /* length */ + unsigned char *out_txt, /* dst (cipher text) */ + size_t *outlen) /* dst len */ { - (void)bEncrypt; - (void)aes_key; - (void)indata; - (void)inlen; - (void)out_txt; - (void)outlen; - - return -1; + (void)bEncrypt; + (void)aes_key; + (void)indata; + (void)inlen; + (void)out_txt; + (void)outlen; + + return -1; } int crysprStub_AES_CtrCipher( - bool bEncrypt, /* true:encrypt, false:decrypt */ - CRYSPR_AESCTX *aes_key, /* AES context */ - unsigned char *iv, /* iv */ - const unsigned char *indata,/* src */ - size_t inlen, /* length */ - unsigned char *out_txt) /* dest */ + bool bEncrypt, /* true:encrypt, false:decrypt */ + CRYSPR_AESCTX *aes_key, /* AES context */ + unsigned char *iv, /* iv */ + const unsigned char *indata,/* src */ + size_t inlen, /* length */ + unsigned char *out_txt) /* dest */ +{ + (void)bEncrypt; + (void)aes_key; + (void)iv; + (void)indata; + (void)inlen; + (void)out_txt; + + return(-1); +} + +int crysprStub_AES_GCMCipher( + bool bEncrypt, /* true:encrypt, false:decrypt */ + CRYSPR_AESCTX *aes_key, /* AES context */ + unsigned char *iv, /* iv */ + const unsigned char *aad, /* associated data */ + size_t aadlen, + const unsigned char * indata, + size_t inlen, + unsigned char *out_txt, + unsigned char* out_tag) { - (void)bEncrypt; - (void)aes_key; - (void)iv; - (void)indata; - (void)inlen; - (void)out_txt; - - return(-1); + (void)bEncrypt; + (void)aes_key; + (void)iv; + (void)aad; + (void)aadlen; + (void)indata; + (void)inlen; + (void)out_txt; + (void)out_tag; + + return(-1); } unsigned char *crysprStub_SHA1_MsgDigest( - const unsigned char *m, /* in: message */ - size_t m_len, /* message length */ - unsigned char *md) /* out: message digest buffer *160 bytes */ + const unsigned char *m, /* in: message */ + size_t m_len, /* message length */ + unsigned char *md) /* out: message digest buffer *160 bytes */ { - (void)m; - (void)m_len; - (void)md; + (void)m; + (void)m_len; + (void)md; - return(NULL);//return md; + return(NULL);//return md; } /* * Password-based Key Derivation Function */ int crysprStub_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 */ + 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; - (void)passwd; - (void)passwd_len; - (void)salt; - (void)salt_len; - (void)itr; - (void)key_len; - (void)out; - - /* >>Todo: - * develop PBKDF2 using SHA1 primitive cryspr_cb->cryspr->sha1_msg_digest() for cryptolibs not providing it - */ - return(-1); + (void)cryspr_cb; + (void)passwd; + (void)passwd_len; + (void)salt; + (void)salt_len; + (void)itr; + (void)key_len; + (void)out; + + /* >>Todo: + * develop PBKDF2 using SHA1 primitive cryspr_cb->cryspr->sha1_msg_digest() for cryptolibs not providing it + */ + return(-1); } 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"); - return(-1); - } + 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); } @@ -142,7 +168,7 @@ static const unsigned char default_iv[] = { int crysprFallback_AES_WrapKey(CRYSPR_cb *cryspr_cb, unsigned char *out, const unsigned char *in, - unsigned int inlen) + unsigned int inlen) { unsigned char *A, B[16], *R; const unsigned char *iv = default_iv; @@ -163,7 +189,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) @@ -176,13 +204,13 @@ int crysprFallback_AES_WrapKey(CRYSPR_cb *cryspr_cb, } } memcpy(out, A, 8); - return 0; + return 0; } int crysprFallback_AES_UnwrapKey(CRYSPR_cb *cryspr_cb, unsigned char *out, const unsigned char *in, - unsigned int inlen) + unsigned int inlen) { unsigned char *A, B[16], *R; const unsigned char *iv = default_iv; @@ -211,7 +239,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); } @@ -219,9 +249,9 @@ int crysprFallback_AES_UnwrapKey(CRYSPR_cb *cryspr_cb, if (memcmp(A, iv, 8)) { memset(out, 0, inlen); - return -1; + return -1; } - return 0; + return 0; } static unsigned char *_crysprFallback_GetOutbuf(CRYSPR_cb *cryspr_cb, size_t pfx_len, size_t out_len) @@ -237,20 +267,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 +291,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 +311,42 @@ static CRYSPR_cb *crysprFallback_Open(CRYSPR_methods *cryspr, size_t max_len) return(cryspr_cb); } -static int crysprFallback_Close(CRYSPR_cb *cryspr_cb) +int crysprHelper_Close(CRYSPR_cb *cryspr_cb) { - if (NULL != cryspr_cb) { - free(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) +{ + 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 */ + if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) { /* AES GCM mode */ + if (cryspr_cb->cryspr->aes_set_key(HCRYPT_CTX_MODE_AESGCM, (ctx->flags & HCRYPT_CTX_F_ENCRYPT) != 0, key, key_len, aes_sek)) { + HCRYPT_LOG(LOG_ERR, "%s", "CRYSPR->set_encrypt_key(sek) failed\n"); + return(-1); + } + } else 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); } @@ -372,71 +424,68 @@ static int crysprFallback_MsEncrypt( * to reserve room for unencrypted message header in output buffer */ pfx_len = ctx->msg_info->pfx_len; + /* Extra 16 bytes are needed for an authentication tag in GCM. */ + const int aux_len = (ctx->mode == HCRYPT_CTX_MODE_AESGCM) ? HAICRYPT_AUTHTAG_MAX : 0; - /* Get buffer room from the internal circular output buffer */ - out_msg = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, in_data[0].len); + /* Auth tag produced by AES GCM. */ + unsigned char tag[HAICRYPT_AUTHTAG_MAX]; - 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 buffer room from the internal circular output buffer. + * Reserve additional 16 bytes for auth tag in AES GCM mode when needed. + */ + out_msg = _crysprFallback_GetOutbuf(cryspr_cb, pfx_len, in_data[0].len + aux_len); + if (NULL == out_msg) { + /* input data too big */ + return(-1); + } - /* Get input packet index (in network order) */ - hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); + switch(ctx->mode) { + case HCRYPT_CTX_MODE_AESCTR: /* Counter mode */ + case HCRYPT_CTX_MODE_AESGCM: + { + /* 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 */ - /* - * 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); + 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 (ctx->mode == HCRYPT_CTX_MODE_AESGCM) + { + const int iret = cryspr_cb->cryspr->aes_gcm_cipher(true, aes_key, iv, in_data[0].pfx, pfx_len, in_data[0].payload, in_data[0].len, + &out_msg[pfx_len], tag); + if (iret) { + return(iret); + } + } + else { +#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*/ - /* 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; - - /* 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); /* 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); + int iret = _crysprFallback_AES_SetCtrStream(cryspr_cb, ctx, in_data[0].len, iv); if (iret) { return(iret); } @@ -452,59 +501,29 @@ static int crysprFallback_MsEncrypt( 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; } - 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); + /* 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; + if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) + { + memcpy(out_msg + pfx_len + out_len, tag, sizeof(tag)); + out_len += sizeof(tag); + } + 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 +533,43 @@ 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); + if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) { + // Encoding produced more payload (auth tag). + return (int)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); } @@ -534,10 +590,10 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, if (NULL != out_txt) { switch(ctx->mode) { case HCRYPT_CTX_MODE_AESCTR: + case HCRYPT_CTX_MODE_AESGCM: { -#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) */ @@ -561,54 +617,63 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, */ hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); - cryspr_cb->cryspr->aes_ctr_cipher(false, aes_key, iv, in_data[0].payload, in_data[0].len, + if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) + { + unsigned char* tag = in_data[0].payload + in_data[0].len - HAICRYPT_AUTHTAG_MAX; + int liret = cryspr_cb->cryspr->aes_gcm_cipher(false, aes_key, iv, in_data[0].pfx, ctx->msg_info->pfx_len, in_data[0].payload, in_data[0].len - HAICRYPT_AUTHTAG_MAX, + out_txt, tag); + if (liret) { + return(liret); + } + out_len = in_data[0].len - HAICRYPT_AUTHTAG_MAX; + } + else { +#if CRYSPR_HAS_AESCTR + cryspr_cb->cryspr->aes_ctr_cipher(false, aes_key, iv, in_data[0].payload, in_data[0].len, out_txt); - out_len = in_data[0].len; + 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)]; - unsigned char iv[CRYSPR_AESBLKSZ]; - int iret = 0; - - /* 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); - /* Create CtrStream. May be longer than in_len (next cipher 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_txt = _crysprFallback_GetOutbuf(cryspr_cb, 0, cryspr_cb->ctr_stream_len); - - /* Create KeyStream (encrypt CtrStream) */ - iret = cryspr_cb->cryspr->aes_ecb_cipher(true, aes_key, + /* 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); + + /* Create CtrStream. May be longer than in_len (next cipher block size boundary) */ + int liret = _crysprFallback_AES_SetCtrStream(cryspr_cb, ctx, in_data[0].len, iv); + if (liret) { + return(liret); + } + /* Reserve output buffer for cryspr */ + out_txt = _crysprFallback_GetOutbuf(cryspr_cb, 0, cryspr_cb->ctr_stream_len); + + /* Create KeyStream (encrypt CtrStream) */ + liret = cryspr_cb->cryspr->aes_ecb_cipher(true, aes_key, cryspr_cb->ctr_stream, cryspr_cb->ctr_stream_len, out_txt, &out_len); - if (iret) { - HCRYPT_LOG(LOG_ERR, "%s", "crysprNatural_AES_ecb_cipher(encrypt failed\n"); - return(iret); - } + if (liret) { + HCRYPT_LOG(LOG_ERR, "%s", "crysprNatural_AES_ecb_cipher(encrypt failed\n"); + return(liret); + } #endif /*CRYSPR_HAS_AESCTR*/ + } break; } case HCRYPT_CTX_MODE_CLRTXT: @@ -639,6 +704,7 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, #else /* CRYSPR_HAS_AESCTR */ /* Copy output data back in input buffer */ memcpy(in_data[0].payload, out_txt, out_len); + in_data->len = out_len; #endif /* CRYSPR_HAS_AESCTR */ } else { /* Copy header in output buffer if needed */ @@ -684,9 +750,9 @@ CRYSPR_methods *crysprInit(CRYSPR_methods *cryspr) cryspr->aes_set_key = crysprStub_AES_SetKey; cryspr->aes_ecb_cipher = crysprStub_AES_EcbCipher; cryspr->aes_ctr_cipher = crysprStub_AES_CtrCipher; + cryspr->aes_gcm_cipher = crysprStub_AES_GCMCipher; cryspr->sha1_msg_digest = crysprStub_SHA1_MsgDigest; - /* Crypto Session API */ cryspr->open = crysprFallback_Open; cryspr->close = crysprFallback_Close; @@ -705,5 +771,5 @@ CRYSPR_methods *crysprInit(CRYSPR_methods *cryspr) HaiCrypt_Cryspr HaiCryptCryspr_Get_Instance(void) { - return((HaiCrypt_Cryspr)cryspr4SRT()); + return((HaiCrypt_Cryspr)cryspr4SRT()); } diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h index 45a300feff3..42426f4223d 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/cryspr.h @@ -39,24 +39,33 @@ extern "C" { #include "cryspr-config.h" typedef struct tag_CRYSPR_cb { - CRYSPR_AESCTX aes_kek; /* Key Encrypting Key (KEK) */ - CRYSPR_AESCTX aes_sek[2]; /* even/odd Stream Encrypting Key (SEK) */ - - struct tag_CRYSPR_methods *cryspr; +#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; #if !CRYSPR_HAS_AESCTR /* Reserve room to build the counter stream ourself */ #define HCRYPT_CTR_BLK_SZ CRYSPR_AESBLKSZ #define HCRYPT_CTR_STREAM_SZ 2048 - unsigned char * ctr_stream; - size_t ctr_stream_len; /* Content size */ - size_t ctr_stream_siz; /* Allocated length */ + unsigned char * ctr_stream; + size_t ctr_stream_len; /* Content size */ + size_t ctr_stream_siz; /* Allocated length */ #endif /* !CRYSPR_HAS_AESCTR */ #define CRYSPR_OUTMSGMAX 6 - uint8_t * outbuf; /* output circle buffer */ - size_t outbuf_ofs; /* write offset in circle buffer */ - size_t outbuf_siz; /* circle buffer size */ + uint8_t * outbuf; /* output circle buffer */ + size_t outbuf_ofs; /* write offset in circle buffer */ + size_t outbuf_siz; /* circle buffer size */ } CRYSPR_cb; typedef struct tag_CRYSPR_methods { @@ -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) */ @@ -90,6 +100,17 @@ typedef struct tag_CRYSPR_methods { size_t inlen, /* src length */ unsigned char *out_txt);/* dest */ + int (*aes_gcm_cipher)( + bool bEncrypt, /* true:encrypt false:decrypt (don't care with CTR) */ + CRYSPR_AESCTX* aes_key, /* ctx */ + unsigned char* iv, /* iv */ + const unsigned char* aad, /* associated data */ + size_t aadlen, + const unsigned char* indata, /* src (clear text) */ + size_t inlen, /* src length */ + unsigned char* out_txt, /* dest */ + unsigned char* out_tag); + unsigned char *(*sha1_msg_digest)( const unsigned char *m, /* in: message */ size_t m_len, /* message length */ @@ -194,6 +215,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 8a38714473b..b0f78346b3e 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 835c1f535ad..ac6d383ca71 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 00000000000..9548dc7964d --- /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 e3ef0a1aafa..0179042c493 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 79763b7af8b..b6e83ad7a36 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt.h @@ -27,32 +27,16 @@ 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 */ #define HAICRYPT_PWD_MAX_SZ 80 /* MAX password (for Password-based Key Derivation) */ #define HAICRYPT_KEY_MAX_SZ 32 /* MAX key */ #define HAICRYPT_SECRET_MAX_SZ (HAICRYPT_PWD_MAX_SZ > HAICRYPT_KEY_MAX_SZ ? HAICRYPT_PWD_MAX_SZ : HAICRYPT_KEY_MAX_SZ) - +#define HAICRYPT_AUTHTAG_MAX 16 /* maximum length of the auth tag (e.g. GCM) */ #define HAICRYPT_SALT_SZ 16 @@ -76,6 +60,7 @@ typedef struct { #define HAICRYPT_CFG_F_TX 0x01 /* !TX -> RX */ #define HAICRYPT_CFG_F_CRYPTO 0x02 /* Perform crypto Tx:Encrypt Rx:Decrypt */ #define HAICRYPT_CFG_F_FEC 0x04 /* Do Forward Error Correction */ +#define HAICRYPT_CFG_F_GCM 0x08 /* Use AES-GCM */ unsigned flags; HaiCrypt_Secret secret; /* Security Association */ @@ -108,26 +93,31 @@ typedef struct hcrypt_Session_str* HaiCrypt_Handle; -HAICRYPT_API int HaiCrypt_SetLogLevel(int level, int logfa); +int HaiCrypt_SetLogLevel(int level, int logfa); + +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_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_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); -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); +/// @brief Check if the crypto service provider supports AES GCM. +/// @return returns 1 if AES GCM is supported, 0 otherwise. +int HaiCrypt_IsAESGCM_Supported(void); /* Status values */ #define HAICRYPT_ERROR -1 #define HAICRYPT_ERROR_WRONG_SECRET -2 +#define HAICRYPT_ERROR_CIPHER -3 #define HAICRYPT_OK 0 diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.cpp b/trunk/3rdparty/srt-1-fit/haicrypt/haicrypt_log.cpp index 85a25b1fd89..77350719620 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" { @@ -42,10 +44,10 @@ int HaiCrypt_SetLogLevel(int level, int logfa) #define HAICRYPT_DEFINE_LOG_DISPATCHER(LOGLEVEL, dispatcher) \ int HaiCrypt_LogF_##LOGLEVEL ( const char* file, int line, const char* function, const char* format, ...) \ { \ - va_list ap; \ - va_start(ap, format); \ srt_logging::LogDispatcher& lg = hclog.dispatcher; \ if (!lg.CheckEnabled()) return -1; \ + va_list ap; \ + va_start(ap, format); \ lg().setloc(file, line, function).vform(format, ap); \ va_end(ap); \ return 0; \ @@ -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 665bc70caef..d788316bf8f 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" { @@ -21,7 +21,7 @@ HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_EMERG); #define HCRYPT_LOG_INIT() #define HCRYPT_LOG_EXIT() -#define HCRYPT_LOG(lvl, fmt, ...) HaiCrypt_LogF_##lvl (__FILE__, __LINE__, __FUNCTION__, fmt, __VA_ARGS__) +#define HCRYPT_LOG(lvl, ...) HaiCrypt_LogF_##lvl (__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) #if ENABLE_HAICRYPT_LOGGING == 2 #define HCRYPT_DEV 1 diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.c index 00f777b2a57..2568654b1ea 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(); @@ -160,7 +156,7 @@ int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) || hcryptCtx_Tx_Init(crypto, &crypto->ctx_pair[1], cfg)) { free(crypto); return(-1); - } + } /* Generate keys for first (default) context */ if (hcryptCtx_Tx_Rekey(crypto, &crypto->ctx_pair[0])) { free(crypto); @@ -200,6 +196,9 @@ int HaiCrypt_ExtractConfig(HaiCrypt_Handle hhcSrc, HaiCrypt_Cfg* pcfg) pcfg->flags = HAICRYPT_CFG_F_CRYPTO; if ((ctx->flags & HCRYPT_CTX_F_ENCRYPT) == HCRYPT_CTX_F_ENCRYPT) pcfg->flags |= HAICRYPT_CFG_F_TX; + + if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) + pcfg->flags |= HAICRYPT_CFG_F_GCM; /* Set this explicitly - this use of this library is SRT only. */ pcfg->xport = HAICRYPT_XPT_SRT; @@ -241,7 +240,8 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl if (tx) { HaiCrypt_Cfg crypto_config; - HaiCrypt_ExtractConfig(hhcSrc, &crypto_config); + if (-1 == HaiCrypt_ExtractConfig(hhcSrc, &crypto_config)) + return -1; /* * Just invert the direction written in flags and use the @@ -307,8 +307,7 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl return(-1); } - - /* Configure contexts */ + /* Configure contexts. Note that GCM mode has been already copied from the source context. */ if (hcryptCtx_Rx_Init(cryptoClone, &cryptoClone->ctx_pair[0], NULL) || hcryptCtx_Rx_Init(cryptoClone, &cryptoClone->ctx_pair[1], NULL)) { free(cryptoClone); @@ -340,3 +339,12 @@ int HaiCrypt_Close(HaiCrypt_Handle hhc) HCRYPT_LOG_EXIT(); return rc; } + +int HaiCrypt_IsAESGCM_Supported(void) +{ +#if CRYSPR_HAS_AESGCM + return 1; +#else + return 0; +#endif +} diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h index f2193f58667..34e744e8a9c 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt.h @@ -24,18 +24,14 @@ written by MINGW-W64 Build. *****************************************************************************/ -#ifndef HCRYPT_H -#define HCRYPT_H +#ifndef INC_SRT_HCRYPT_H +#define INC_SRT_HCRYPT_H #include #ifdef _WIN32 #include #include - #if defined(_MSC_VER) - #pragma warning(disable:4267) - #pragma warning(disable:4018) - #endif #else #include #endif @@ -163,7 +159,18 @@ int hcryptCtx_Tx_AsmKM(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *a int hcryptCtx_Tx_ManageKM(hcrypt_Session *crypto); int hcryptCtx_Tx_InjectKM(hcrypt_Session *crypto, void *out_p[], size_t out_len_p[], int maxout); +/// @brief Initialize receiving crypto context. +/// @param crypto library instance handle. +/// @param ctx additional crypto context. +/// @param cfg crypto configuration. +/// @return -1 on error, 0 otherwise. int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg); + +/// @brief Parse an incoming message related to cryptography module. +/// @param crypto library instance handle. +/// @param msg a message to parse. +/// @param msg_len length of the message in bytes. +/// @return 0 on success; -3 on cipher mode mismatch; -2 on unmatched shared secret; -1 on other failures. int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *msg, size_t msg_len); #endif /* HCRYPT_H */ diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx.h b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx.h index 9c60c0854cc..3a46fd40f7d 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx.h @@ -45,7 +45,7 @@ typedef struct { typedef struct tag_hcrypt_Ctx { struct tag_hcrypt_Ctx * alt; /* Alternative ctx (even/odd) */ -#define HCRYPT_CTX_F_MSG 0x00FF /* Aligned wiht message header flags */ +#define HCRYPT_CTX_F_MSG 0x00FF /* Aligned with message header flags */ #define HCRYPT_CTX_F_eSEK HCRYPT_MSG_F_eSEK #define HCRYPT_CTX_F_oSEK HCRYPT_MSG_F_oSEK #define HCRYPT_CTX_F_xSEK HCRYPT_MSG_F_xSEK @@ -68,6 +68,7 @@ typedef struct tag_hcrypt_Ctx { #define HCRYPT_CTX_MODE_AESECB 1 /* Electronic Code Book mode */ #define HCRYPT_CTX_MODE_AESCTR 2 /* Counter mode */ #define HCRYPT_CTX_MODE_AESCBC 3 /* Cipher-block chaining mode */ +#define HCRYPT_CTX_MODE_AESGCM 4 /* AES GCM authenticated encryption */ unsigned mode; struct { diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_rx.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_rx.c index 9bd8a8e6392..9fcba6c3064 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_rx.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_rx.c @@ -14,9 +14,9 @@ written by Haivision Systems Inc. 2011-06-23 (jdube) - HaiCrypt initial implementation. + HaiCrypt initial implementation. 2014-03-11 (jdube) - Adaptation for SRT. + Adaptation for SRT. *****************************************************************************/ #include /* memcpy */ @@ -24,16 +24,18 @@ written by int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg) { - ctx->mode = HCRYPT_CTX_MODE_AESCTR; - ctx->status = HCRYPT_CTX_S_INIT; + if (cfg) { + ctx->mode = (cfg->flags & HAICRYPT_CFG_F_GCM) ? HCRYPT_CTX_MODE_AESGCM : HCRYPT_CTX_MODE_AESCTR; + } + ctx->status = HCRYPT_CTX_S_INIT; - ctx->msg_info = crypto->msg_info; + ctx->msg_info = crypto->msg_info; - if (cfg && hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { - return(-1); - } - ctx->status = HCRYPT_CTX_S_SARDY; - return(0); + if (cfg && hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { + return(-1); + } + ctx->status = HCRYPT_CTX_S_SARDY; + return(0); } int hcryptCtx_Rx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *sek, size_t sek_len) @@ -98,9 +100,31 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m } /* Check options support */ - if ((HCRYPT_CIPHER_AES_CTR != km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) - || (HCRYPT_AUTH_NONE != km_msg[HCRYPT_MSG_KM_OFS_AUTH])) { - HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported option\n"); + if (HCRYPT_CIPHER_AES_CTR != km_msg[HCRYPT_MSG_KM_OFS_CIPHER] + && HCRYPT_CIPHER_AES_GCM != km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) + { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported cipher\n"); + return(-1); + } + +#if !CRYSPR_HAS_AESGCM + /* Only OpenSSL EVP crypto provider allows the use of GCM.Add this condition. Reject if GCM is not supported by the CRYSPR. */ + if (HCRYPT_CIPHER_AES_GCM == km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) + { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported GCM cipher\n"); + return(-1); + } +#endif + + if (HCRYPT_CIPHER_AES_GCM == km_msg[HCRYPT_MSG_KM_OFS_CIPHER] + && HCRYPT_AUTH_AES_GCM != km_msg[HCRYPT_MSG_KM_OFS_AUTH]) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg GCM auth method was expected.\n"); + return(-1); + } + + if (HCRYPT_CIPHER_AES_CTR == km_msg[HCRYPT_MSG_KM_OFS_CIPHER] + && HCRYPT_AUTH_NONE != km_msg[HCRYPT_MSG_KM_OFS_AUTH]) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported auth method\n"); return(-1); } @@ -144,6 +168,13 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m do_pbkdf = 1; /* Impact on password derived kek */ } + /* Check cipher mode */ + if (ctx->mode != km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) + { + HCRYPT_LOG(LOG_WARNING, "%s", "cipher mode mismatch\n"); + return(-3); + } + /* * Regenerate KEK if it is password derived * and Salt or SEK length changed @@ -159,7 +190,7 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m /* Unwrap SEK(s) and set in context */ if (0 > crypto->cryspr->km_unwrap(crypto->cryspr_cb, seks, &km_msg[HCRYPT_MSG_KM_OFS_SALT + salt_len], - (sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ)) { + (unsigned int)((sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ))) { HCRYPT_LOG(LOG_WARNING, "%s", "unwrap key failed\n"); return(-2); //Report unmatched shared secret } @@ -184,7 +215,6 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m alt->salt_len = salt_len; if (kek_len) { /* New or changed KEK */ -// memcpy(&alt->aes_kek, &ctx->aes_kek, sizeof(alt->aes_kek)); alt->status = HCRYPT_CTX_S_SARDY; } 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 77560a543da..9ed5ba36c28 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_tx.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_ctx_tx.c @@ -14,18 +14,18 @@ written by Haivision Systems Inc. 2011-06-23 (jdube) - HaiCrypt initial implementation. + HaiCrypt initial implementation. 2014-03-11 (jdube) - Adaptation for SRT. + Adaptation for SRT. *****************************************************************************/ #include /* memcpy */ #ifdef _WIN32 - #include - #include - #include + #include + #include + #include #else - #include + #include #endif #include "hcrypt.h" @@ -33,7 +33,7 @@ int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cf { ctx->cfg.key_len = cfg->key_len; - ctx->mode = HCRYPT_CTX_MODE_AESCTR; + ctx->mode = (cfg->flags & HAICRYPT_CFG_F_GCM) ? HCRYPT_CTX_MODE_AESGCM : HCRYPT_CTX_MODE_AESCTR; ctx->status = HCRYPT_CTX_S_INIT; ctx->msg_info = crypto->msg_info; @@ -52,14 +52,14 @@ int hcryptCtx_Tx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx) /* Generate Salt */ ctx->salt_len = HAICRYPT_SALT_SZ; - if (0 > (iret = crypto->cryspr->prng(ctx->salt, ctx->salt_len))) { + if (0 > (iret = crypto->cryspr->prng(ctx->salt, (int)ctx->salt_len))) { HCRYPT_LOG(LOG_ERR, "PRNG(salt[%zd]) failed\n", ctx->salt_len); return(iret); } /* Generate SEK */ ctx->sek_len = ctx->cfg.key_len; - if (0 > (iret = crypto->cryspr->prng(ctx->sek, ctx->sek_len))) { + if (0 > (iret = crypto->cryspr->prng(ctx->sek, (int)ctx->sek_len))) { HCRYPT_LOG(LOG_ERR, "PRNG(sek[%zd] failed\n", ctx->sek_len); return(iret); } @@ -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 */ @@ -193,7 +195,7 @@ int hcryptCtx_Tx_Refresh(hcrypt_Session *crypto) HCRYPT_LOG(LOG_DEBUG, "refresh/generate SEK. salt_len=%d sek_len=%d\n", (int)new_ctx->salt_len, (int)new_ctx->sek_len); - if (0 > crypto->cryspr->prng(new_ctx->sek, new_ctx->sek_len)) { + if (0 > crypto->cryspr->prng(new_ctx->sek, (int)new_ctx->sek_len)) { HCRYPT_LOG(LOG_ERR, "PRNG(sek[%zd] failed\n", new_ctx->sek_len); return(-1); } @@ -297,9 +299,9 @@ int hcryptCtx_Tx_AsmKM(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *a 2 == sek_cnt ? HCRYPT_MSG_F_xSEK : (ctx->flags & HCRYPT_MSG_F_xSEK)); /* crypto->KMmsg_cache[4..7]: KEKI=0 */ - km_msg[HCRYPT_MSG_KM_OFS_CIPHER] = HCRYPT_CIPHER_AES_CTR; - km_msg[HCRYPT_MSG_KM_OFS_AUTH] = HCRYPT_AUTH_NONE; - km_msg[HCRYPT_MSG_KM_OFS_SE] = crypto->se; + km_msg[HCRYPT_MSG_KM_OFS_CIPHER] = (ctx->mode == HCRYPT_CTX_MODE_AESGCM) ? HCRYPT_CIPHER_AES_GCM : HCRYPT_CIPHER_AES_CTR; + km_msg[HCRYPT_MSG_KM_OFS_AUTH] = (ctx->mode == HCRYPT_CTX_MODE_AESGCM) ? HCRYPT_AUTH_AES_GCM : HCRYPT_AUTH_NONE; + km_msg[HCRYPT_MSG_KM_OFS_SE] = (char) crypto->se; hcryptMsg_KM_SetSaltLen(km_msg, ctx->salt_len); hcryptMsg_KM_SetSekLen(km_msg, ctx->sek_len); @@ -320,7 +322,7 @@ int hcryptCtx_Tx_AsmKM(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *a } if (0 > crypto->cryspr->km_wrap(crypto->cryspr_cb, &km_msg[HCRYPT_MSG_KM_OFS_SALT + ctx->salt_len], - seks, sek_cnt * ctx->sek_len)) { + seks, (unsigned int)(sek_cnt * ctx->sek_len))) { HCRYPT_LOG(LOG_ERR, "%s", "wrap key failed\n"); return(-1); @@ -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 @@ -358,7 +360,7 @@ int hcryptCtx_Tx_ManageKM(hcrypt_Session *crypto) * prepare next SEK for announcement */ hcryptCtx_Tx_Refresh(crypto); - + HCRYPT_LOG(LOG_INFO, "KM[%d] Pre-announced\n", (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2); diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_msg.h b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_msg.h index a915c2fa026..33a9522f998 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_msg.h +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_msg.h @@ -122,8 +122,10 @@ typedef struct { #define HCRYPT_CIPHER_AES_ECB 1 #define HCRYPT_CIPHER_AES_CTR 2 #define HCRYPT_CIPHER_AES_CBC 3 +#define HCRYPT_CIPHER_AES_GCM 4 #define HCRYPT_AUTH_NONE 0 +#define HCRYPT_AUTH_AES_GCM 1 #define HCRYPT_SE_TSUDP 1 hcrypt_MsgInfo * hcryptMsg_STA_MsgInfo(void); @@ -148,8 +150,8 @@ typedef struct { #define hcryptMsg_KM_GetSaltLen(msg) (size_t)((msg)[HCRYPT_MSG_KM_OFS_SLEN] * 4) #define hcryptMsg_KM_GetSekLen(msg) (size_t)((msg)[HCRYPT_MSG_KM_OFS_KLEN] * 4) -#define hcryptMsg_KM_SetSaltLen(msg,len)do {(msg)[HCRYPT_MSG_KM_OFS_SLEN] = (len)/4;} while(0) -#define hcryptMsg_KM_SetSekLen(msg,len) do {(msg)[HCRYPT_MSG_KM_OFS_KLEN] = (len)/4;} while(0) +#define hcryptMsg_KM_SetSaltLen(msg,len)do {(msg)[HCRYPT_MSG_KM_OFS_SLEN] = (unsigned char)(len)/4;} while(0) +#define hcryptMsg_KM_SetSekLen(msg,len) do {(msg)[HCRYPT_MSG_KM_OFS_KLEN] = (unsigned char)(len)/4;} while(0) #endif /* HCRYPT_MSG_H */ diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_rx.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_rx.c index e77c73b355b..68cb396f837 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_rx.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_rx.c @@ -53,7 +53,7 @@ int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, if (0 > (nb = crypto->cryspr->ms_decrypt(crypto->cryspr_cb, ctx, &indata, 1, NULL, NULL, NULL))) { HCRYPT_LOG(LOG_ERR, "%s", "ms_decrypt failed\n"); } else { - nb = indata.len; + nb = (int)indata.len; } } else { /* No key received yet */ nb = 0; @@ -124,9 +124,6 @@ int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, || (0 != memcmp(ctx->KMmsg_cache, in_msg, in_len))) { /* or different */ nbout = hcryptCtx_Rx_ParseKM(crypto, in_msg, in_len); - //-2: unmatched shared secret - //-1: other failures - //0: success } else { nbout = 0; } diff --git a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_sa.c index 995334d81aa..6884c76b13a 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 d1ceab26cc3..3472c625b4e 100644 --- a/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c +++ b/trunk/3rdparty/srt-1-fit/haicrypt/hcrypt_tx.c @@ -14,20 +14,20 @@ written by Haivision Systems Inc. 2011-06-23 (jdube) - HaiCrypt initial implementation. + HaiCrypt initial implementation. 2014-03-11 (jdube) - Adaptation for SRT. + Adaptation for SRT. *****************************************************************************/ #include #include /* NULL */ #include /* memcpy */ #ifdef _WIN32 - #include - #include - #include + #include + #include + #include #else - #include /* htonl */ + #include /* htonl */ #endif #include "hcrypt.h" @@ -52,28 +52,28 @@ int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_ return(crypto->msg_info->pfx_len); } -int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout) +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; int nbout = 0; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx)) + || (NULL == crypto->ctx) || (NULL == out_p) || (NULL == out_len_p)) { - HCRYPT_LOG(LOG_ERR, "ManageKeys: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); + HCRYPT_LOG(LOG_ERR, "ManageKeys: invalid params: crypto=%p out_p=%p out_len_p=%p\n", + crypto, out_p, out_len_p); return(-1); } /* Manage Key Material (refresh, announce, decommission) */ hcryptCtx_Tx_ManageKM(crypto); - if (NULL == (ctx = crypto->ctx)) { - HCRYPT_LOG(LOG_ERR, "%s", "crypto context not defined\n"); + if (NULL == crypto->ctx) { + HCRYPT_LOG(LOG_ERR, "%s", "crypto context NULL after ManageKM call\n"); return(-1); } - ASSERT(ctx->status == HCRYPT_CTX_S_ACTIVE); + ASSERT(crypto->ctx->status == HCRYPT_CTX_S_ACTIVE); nbout = hcryptCtx_Tx_InjectKM(crypto, out_p, out_len_p, maxout); return(nbout); @@ -85,27 +85,32 @@ int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc) hcrypt_Ctx *ctx = NULL; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx))){ + || (NULL == (ctx = crypto->ctx))) { HCRYPT_LOG(LOG_ERR, "GetKeyFlags: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } - return(hcryptCtx_GetKeyFlags(ctx)); + return(hcryptCtx_GetKeyFlags(crypto->ctx)); } -int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, - unsigned char *in_pfx, unsigned char *in_data, size_t in_len) +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; int nbout = 0; if ((NULL == crypto) - || (NULL == (ctx = crypto->ctx))){ + || (NULL == (ctx = crypto->ctx))) { HCRYPT_LOG(LOG_ERR, "Tx_Data: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } /* Get/Set packet index */ - ctx->msg_info->indexMsg(in_pfx, ctx->MSpfx_cache); + ctx->msg_info->indexMsg(in_pfx, ctx->MSpfx_cache); + + if (hcryptMsg_GetKeyIndex(ctx->msg_info, in_pfx) != hcryptCtx_GetKeyIndex(ctx)) + { + HCRYPT_LOG(LOG_ERR, "Tx_Data: Key mismatch!"); + } /* Encrypt */ { @@ -124,8 +129,8 @@ int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, return(nbout); } -int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, - unsigned char *in_msg, size_t in_len, +int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, + unsigned char *in_msg, size_t in_len, void *out_p[], size_t out_len_p[], int maxout) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; @@ -143,10 +148,6 @@ int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, /* Manage Key Material (refresh, announce, decommission) */ hcryptCtx_Tx_ManageKM(crypto); - if (NULL == (ctx = crypto->ctx)) { - HCRYPT_LOG(LOG_ERR, "%s", "crypto context not defined\n"); - return(-1); - } ASSERT(ctx->status == HCRYPT_CTX_S_ACTIVE); nbout += hcryptCtx_Tx_InjectKM(crypto, out_p, out_len_p, maxout); 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 cc2651be034..00000000000 --- 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 00000000000..d3a3b9e71e5 --- /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/CheckCXXStdPutTime.cmake b/trunk/3rdparty/srt-1-fit/scripts/CheckCXXStdPutTime.cmake new file mode 100644 index 00000000000..7f15ae09f88 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/CheckCXXStdPutTime.cmake @@ -0,0 +1,57 @@ +# +# SRT - Secure, Reliable, Transport Copyright (c) 2022 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::put_time(). +# +# Sets: +# HAVE_CXX_STD_PUT_TIME + +include(CheckCSourceCompiles) + +function(CheckCXXStdPutTime) + + unset(HAVE_CXX_STD_PUT_TIME CACHE) + + set(CMAKE_TRY_COMPILE_TARGET_TYPE EXECUTABLE) # CMake 3.6 + + unset(CMAKE_REQUIRED_FLAGS) + unset(CMAKE_REQUIRED_LIBRARIES) + unset(CMAKE_REQUIRED_LINK_OPTIONS) + + set(CheckCXXStdPutTime_CODE + " + #include + #include + #include + int main(void) + { + const int result = 0; + std::time_t t = std::time(nullptr); + std::tm tm = *std::localtime(&t); + std::cout + << std::put_time(&tm, \"%FT%T\") + << std::setfill('0') + << std::setw(6) + << std::endl; + return result; + } + " + ) + + # NOTE: Should we set -std or use the current compiler configuration. + # It seems that the top level build does not track the compiler + # in a consistent manner. So Maybe we need this? + set(CMAKE_REQUIRED_FLAGS "-std=c++11") + + # Check that the compiler can build the std::put_time() example: + message(STATUS "Checking for C++ 'std::put_time()':") + check_cxx_source_compiles( + "${CheckCXXStdPutTime_CODE}" + HAVE_CXX_STD_PUT_TIME) + +endfunction(CheckCXXStdPutTime) 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 00000000000..7952dd750cf --- /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 existence 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 7dca6e47e23..66225562255 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 00000000000..65685e1eb71 --- /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 00000000000..108dde5dbd4 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/ShowProjectConfig.cmake @@ -0,0 +1,192 @@ +# +# 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" + " HAVE_CXX_STD_PUT_TIME: ${HAVE_CXX_STD_PUT_TIME}\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-android/README.md b/trunk/3rdparty/srt-1-fit/scripts/build-android/README.md new file mode 100644 index 00000000000..85277f2a652 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-android/README.md @@ -0,0 +1,3 @@ +## Scripts for building SRT for Android + +See [Building SRT for Android](../../docs/build/build-android.md) for the instructions. diff --git a/trunk/3rdparty/srt-1-fit/scripts/build-android/build-android b/trunk/3rdparty/srt-1-fit/scripts/build-android/build-android new file mode 100755 index 00000000000..b1c7363a985 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-android/build-android @@ -0,0 +1,111 @@ +#!/bin/sh + +echo_help() +{ + echo "Usage: $0 [options...]" + echo " -n NDK root path for the build" + echo " -a Target API level" + echo " -t Space-separated list of target architectures" + echo " Android supports the following architectures: armeabi-v7a arm64-v8a x86 x86_64" + echo " -e Encryption library to be used. Possible options: openssl (default) mbedtls" + echo " -o OpenSSL version. E.g. 1.1.1l" + echo " -m Mbed TLS version. E.g. v2.26.0" + echo + echo "Example: ./build-android -n /home/username/Android/Sdk/ndk/23.0.7599858 -a 28 -t \"arm64-v8a x86_64\"" + echo +} + +# Init optional command line vars +NDK_ROOT="" +API_LEVEL=28 +BUILD_TARGETS="armeabi-v7a arm64-v8a x86 x86_64" +OPENSSL_VERSION=1.1.1l +ENC_LIB=openssl +MBEDTLS_VERSION=v2.26.0 + +while getopts n:a:t:o:s:e:m: option +do + case "${option}" + in + n) NDK_ROOT=${OPTARG};; + a) API_LEVEL=${OPTARG};; + t) BUILD_TARGETS=${OPTARG};; + o) OPENSSL_VERSION=${OPTARG};; + s) SRT_VERSION=${OPTARG};; + e) ENC_LIB=${OPTARG};; + m) MBEDTLS_VERSION=${OPTARG};; + *) twentytwo=${OPTARG};; + esac +done + +echo_help + +if [ -z "$NDK_ROOT" ] ; then + echo "NDK directory not set." + exit 128 +else + if [ ! -d "$NDK_ROOT" ]; then + echo "NDK directory does not exist: $NDK_ROOT" + exit 128 + fi +fi + +SCRIPT_DIR=$(pwd) +HOST_TAG='unknown' +unamestr=$(uname -s) +if [ "$unamestr" = 'Linux' ]; then + HOST_TAG='linux-x86_64' +elif [ "$unamestr" = 'Darwin' ]; then + if [ $(uname -p) = 'arm' ]; then + echo "NDK does not currently support ARM64" + exit 128 + else + HOST_TAG='darwin-x86_64' + fi +fi + +# Write files relative to current location +BASE_DIR=$(pwd) +case "${BASE_DIR}" in + *\ * ) + echo "Your path contains whitespaces, which is not supported by 'make install'." + exit 128 + ;; +esac +cd "${BASE_DIR}" + +if [ $ENC_LIB = 'openssl' ]; then + echo "Building OpenSSL $OPENSSL_VERSION" + $SCRIPT_DIR/mkssl -n $NDK_ROOT -a $API_LEVEL -t "$BUILD_TARGETS" -o $OPENSSL_VERSION -d $BASE_DIR -h $HOST_TAG +elif [ $ENC_LIB = 'mbedtls' ]; then + if [ ! -d $BASE_DIR/mbedtls ]; then + git clone https://github.com/ARMmbed/mbedtls mbedtls + if [ ! -z "$MBEDTLS_VERSION" ]; then + git -C $BASE_DIR/mbedtls checkout $MBEDTLS_VERSION + fi + fi +else + echo "Unknown encryption library. Possible options: openssl mbedtls" + exit 128 +fi + +# Build working copy of srt repository +REPO_DIR="../.." + +for build_target in $BUILD_TARGETS; do + LIB_DIR=$BASE_DIR/$build_target/lib + JNI_DIR=$BASE_DIR/prebuilt/$build_target + + mkdir -p $JNI_DIR + + if [ $ENC_LIB = 'mbedtls' ]; then + $SCRIPT_DIR/mkmbedtls -n $NDK_ROOT -a $API_LEVEL -t $build_target -s $BASE_DIR/mbedtls -i $BASE_DIR/$build_target + cp $LIB_DIR/libmbedcrypto.so $JNI_DIR/libmbedcrypto.so + cp $LIB_DIR/libmbedtls.so $JNI_DIR/libmbedtls.so + cp $LIB_DIR/libmbedx509.so $JNI_DIR/libmbedx509.so + fi + + git -C $REPO_DIR clean -fd -e scripts + $SCRIPT_DIR/mksrt -n $NDK_ROOT -a $API_LEVEL -t $build_target -e $ENC_LIB -s $REPO_DIR -i $BASE_DIR/$build_target + cp $LIB_DIR/libsrt.so $JNI_DIR/libsrt.so +done diff --git a/trunk/3rdparty/srt-1-fit/scripts/build-android/mkmbedtls b/trunk/3rdparty/srt-1-fit/scripts/build-android/mkmbedtls new file mode 100755 index 00000000000..176154184c2 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-android/mkmbedtls @@ -0,0 +1,27 @@ +#!/bin/sh + +while getopts s:i:t:n:a: option +do + case "${option}" + in + s) SRC_DIR=${OPTARG};; + i) INSTALL_DIR=${OPTARG};; + t) ARCH_ABI=${OPTARG};; + n) NDK_ROOT=${OPTARG};; + a) API_LEVEL=${OPTARG};; + *) twentytwo=${OPTARG};; + esac +done + + +BUILD_DIR=/tmp/mbedtls_android_build +rm -rf $BUILD_DIR +mkdir $BUILD_DIR +cd $BUILD_DIR +cmake -DENABLE_TESTING=Off -DUSE_SHARED_MBEDTLS_LIBRARY=On \ +-DCMAKE_PREFIX_PATH=$INSTALL_DIR -DCMAKE_INSTALL_PREFIX=$INSTALL_DIR -DCMAKE_ANDROID_NDK=$NDK_ROOT \ +-DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=$API_LEVEL -DCMAKE_ANDROID_ARCH_ABI=$ARCH_ABI \ +-DCMAKE_C_FLAGS="-fPIC" -DCMAKE_SHARED_LINKER_FLAGS="-Wl,--build-id" \ +-DCMAKE_BUILD_TYPE=RelWithDebInfo $SRC_DIR +cmake --build . +cmake --install . diff --git a/trunk/3rdparty/srt-1-fit/scripts/build-android/mksrt b/trunk/3rdparty/srt-1-fit/scripts/build-android/mksrt new file mode 100755 index 00000000000..a36900768b4 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-android/mksrt @@ -0,0 +1,32 @@ +#!/bin/sh + +while getopts s:i:t:n:a:e: option +do + case "${option}" + in + s) SRC_DIR=${OPTARG};; + i) INSTALL_DIR=${OPTARG};; + t) ARCH_ABI=${OPTARG};; + n) NDK_ROOT=${OPTARG};; + a) API_LEVEL=${OPTARG};; + e) ENC_LIB=${OPTARG};; + *) twentytwo=${OPTARG};; + esac +done + + +cd $SRC_DIR +./configure --use-enclib=$ENC_LIB \ +--use-openssl-pc=OFF \ +--OPENSSL_INCLUDE_DIR=$INSTALL_DIR/include \ +--OPENSSL_CRYPTO_LIBRARY=$INSTALL_DIR/lib/libcrypto.a --OPENSSL_SSL_LIBRARY=$INSTALL_DIR/lib/libssl.a \ +--STATIC_MBEDTLS=FALSE \ +--MBEDTLS_INCLUDE_DIR=$INSTALL_DIR/include --MBEDTLS_INCLUDE_DIRS=$INSTALL_DIR/include \ +--MBEDTLS_LIBRARIES=$INSTALL_DIR/lib/libmbedtls.so \ +--CMAKE_PREFIX_PATH=$INSTALL_DIR --CMAKE_INSTALL_PREFIX=$INSTALL_DIR --CMAKE_ANDROID_NDK=$NDK_ROOT \ +--CMAKE_SYSTEM_NAME=Android --CMAKE_SYSTEM_VERSION=$API_LEVEL --CMAKE_ANDROID_ARCH_ABI=$ARCH_ABI \ +--CMAKE_C_FLAGS="-fPIC" --CMAKE_SHARED_LINKER_FLAGS="-Wl,--build-id" \ +--enable-c++11 --enable-stdcxx-sync \ +--enable-debug=2 --enable-logging=0 --enable-heavy-logging=0 --enable-apps=0 +make +make install diff --git a/trunk/3rdparty/srt-1-fit/scripts/build-android/mkssl b/trunk/3rdparty/srt-1-fit/scripts/build-android/mkssl new file mode 100755 index 00000000000..22a4421f0b1 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/build-android/mkssl @@ -0,0 +1,85 @@ +#!/bin/sh + +while getopts n:o:a:t:d:h: option +do + case "${option}" + in + n) ANDROID_NDK=${OPTARG};; + o) OPENSSL_VERSION=${OPTARG};; + a) API_LEVEL=${OPTARG};; + t) BUILD_TARGETS=${OPTARG};; + d) OUT_DIR=${OPTARG};; + h) HOST_TAG=${OPTARG};; + *) twentytwo=${OPTARG};; + esac +done + + +BUILD_DIR=/tmp/openssl_android_build + +if [ ! -d openssl-${OPENSSL_VERSION} ] +then + if [ ! -f openssl-${OPENSSL_VERSION}.tar.gz ] + then + wget https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz || exit 128 + fi + tar xzf openssl-${OPENSSL_VERSION}.tar.gz || exit 128 +fi + +cd openssl-${OPENSSL_VERSION} || exit 128 + + +##### export ndk directory. Required by openssl-build-scripts ##### +case ${OPENSSL_VERSION} in + 1.1.1*) + export ANDROID_NDK_HOME=$ANDROID_NDK + ;; + *) + export ANDROID_NDK_ROOT=$ANDROID_NDK + ;; +esac + +export PATH=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG/bin:$PATH + +##### build-function ##### +build_the_thing() { + make clean + ./Configure $SSL_TARGET -D__ANDROID_API__=$API_LEVEL && \ + make SHLIB_EXT=.so && \ + make install SHLIB_EXT=.so DESTDIR=$DESTDIR || exit 128 +} + +##### set variables according to build-tagret ##### +for build_target in $BUILD_TARGETS +do + case $build_target in + armeabi-v7a) + DESTDIR="$BUILD_DIR/armeabi-v7a" + SSL_TARGET="android-arm" + ;; + x86) + DESTDIR="$BUILD_DIR/x86" + SSL_TARGET="android-x86" + ;; + x86_64) + DESTDIR="$BUILD_DIR/x86_64" + SSL_TARGET="android-x86_64" + ;; + arm64-v8a) + DESTDIR="$BUILD_DIR/arm64-v8a" + SSL_TARGET="android-arm64" + ;; + esac + + rm -rf $DESTDIR + build_the_thing +#### copy libraries and includes to output-directory ##### + mkdir -p $OUT_DIR/$build_target/include + cp -R $DESTDIR/usr/local/include/* $OUT_DIR/$build_target/include + cp -R $DESTDIR/usr/local/ssl/* $OUT_DIR/$build_target/ + mkdir -p $OUT_DIR/$build_target/lib + cp -R $DESTDIR/usr/local/lib/*.so $OUT_DIR/$build_target/lib + cp -R $DESTDIR/usr/local/lib/*.a $OUT_DIR/$build_target/lib +done + +echo Success 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 00000000000..78a926cb53f --- /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 00000000000..4db3fe62748 --- /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 available 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 00000000000..7b458e6c1ae --- /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 e56a7bf3efa..a0221cc0546 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 00000000000..e4d29c62331 --- /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 rendezvous 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 00000000000..a86ab9a62ba --- /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/googletest-download.cmake b/trunk/3rdparty/srt-1-fit/scripts/googletest-download.cmake index 55f2c5a55ed..7469f4f7b1f 100644 --- a/trunk/3rdparty/srt-1-fit/scripts/googletest-download.cmake +++ b/trunk/3rdparty/srt-1-fit/scripts/googletest-download.cmake @@ -11,7 +11,7 @@ ExternalProject_Add( BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build" GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.8.1 + GIT_TAG release-1.10.0 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/trunk/3rdparty/srt-1-fit/scripts/haiUtil.cmake b/trunk/3rdparty/srt-1-fit/scripts/haiUtil.cmake index b05a2b68575..2226619893e 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 73544a29b5f..dfb6aeb97a4 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 00000000000..2ae97b260b7 --- /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 00000000000..d6c4adbfc8b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/release-notes/README.md @@ -0,0 +1,27 @@ +# 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: + +```shell +git log --pretty=format:"%h|%s|%an|%ae" v1.4.0...HEAD > commits.csv +``` + +Use the produced `commits.csv` file as an input to the script: + +```shell +python scripts/release-notes/generate-release-notes.py commits.csv +``` + +The script produces `release-notes.md` as an output. + + +## Requirements + +* Python 3.6+ + +To install Python libraries use: +```shell +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 00000000000..2392b434848 --- /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 00000000000..898c1f7d0cf --- /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 cd9b4be3e82..050838e5e6c 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 00000000000..a90ac487d95 --- /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 and 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 different 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 78ddf812507..f141b0fbdf4 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/test_vista.c b/trunk/3rdparty/srt-1-fit/scripts/test_vista.c new file mode 100644 index 00000000000..833a4d373b0 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/test_vista.c @@ -0,0 +1,10 @@ +/* Copyright © 2023 Steve Lhomme */ +/* SPDX-License-Identifier: ISC */ +#include +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600 /* _WIN32_WINNT_VISTA */ +#error NOPE +#endif +int main(void) +{ + return 0; +} 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 00000000000..ed44eef33e4 --- /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 00000000000..ff54df25886 --- /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 00000000000..4265edf8e84 --- /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 00000000000..ae1b8133e67 --- /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 00000000000..14879b5217c --- /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 00000000000..83b59954fe7 --- /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 00000000000..1dffc3fd174 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/scripts/win-installer/libsrt.nsi @@ -0,0 +1,219 @@ +;----------------------------------------------------------------------------- +; +; 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\access_control.h" + File "${RepoDir}\srtcore\logging_api.h" + File "${RepoDir}\srtcore\platform_sys.h" + File "${RepoDir}\srtcore\srt.h" + File "${RepoDir}\srtcore\udt.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 00000000000..b1da7487910 --- /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 f4ad38608ec..00000000000 --- 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 5bf93032c90..00000000000 --- 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 00000000000..611e1dad8e8 --- /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 length 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 8b1003891fd..7ec8ff5708d 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,669 @@ std::string CUDTUnited::CONID(SRTSOCKET sock) return os.str(); } -int CUDTUnited::startup() +int srt::CUDTUnited::startup() +{ + ScopedLock gcinit(m_InitLock); + + if (m_iInstanceCount++ > 0) + return 1; + + // Global initialization code +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + wVersionRequested = MAKEWORD(2, 2); + + if (0 != WSAStartup(wVersionRequested, &wsaData)) + throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); +#endif + + CCryptoControl::globalInit(); + + PacketFilter::globalInit(); + + if (m_bGCStatus) + return 1; + + m_bClosing = false; + + if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC")) + return -1; + + m_bGCStatus = true; + + HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); + + return 0; +} + +int srt::CUDTUnited::cleanup() { - CGuard gcinit(m_InitLock); + // 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_iInstanceCount++ > 0) - return 0; + if (!m_bGCStatus) + return 0; - // Global initialization code - #ifdef _WIN32 - WORD wVersionRequested; - WSADATA wsaData; - wVersionRequested = MAKEWORD(2, 2); + { + 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); - if (0 != WSAStartup(wVersionRequested, &wsaData)) - throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); - #endif + int sockval = m_SocketIDGenerator - 1; - PacketFilter::globalInit(); + // First problem: zero-value should be avoided by various reasons. - //init CTimer::EventLock + 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; + sockval = MAX_SOCKET_VAL; + } - if (m_bGCStatus) - return true; + // 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; + } - 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); + // 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; + + 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; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } - m_bGCStatus = true; + 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&) + { + LOGC(cnlog.Fatal, log << "newConnection: IPE: all sockets occupied? Last gen=" << 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. + HLOGC(cnlog.Debug, log << + "newConnection: incoming " << peer.str() << ", mapping socket " << 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(); + if (!updateListenerMux(ns, ls)) + { + // This is highly unlikely if not impossible, but there's + // a theoretical runtime chance of failure so it should be + // handled + ns->core().m_RejectReason = SRT_REJ_IPE; + throw false; // let it jump directly into the omni exception handler + } + + 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 + { + HLOGC(cnlog.Debug, log << "newConnection: mapping peer " << ns->m_PeerID + << " to that socket (" << 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 grlock(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->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) + + 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 + 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 +908,3422 @@ 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); + + if (s->core().m_config.iIpV6Only == -1 && name.family() == AF_INET6 && name.isany()) + { + // V6ONLY option must be set explicitly if you want to bind to a wildcard address in IPv6 + HLOGP(smlog.Error, + "bind: when binding to :: (IPv6 wildcard), SRTO_IPV6ONLY option must be set explicitly to 0 or 1"); + + throw CUDTException(MJ_NOTSUP, MN_INVAL, 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 connection 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 || namelen < int(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) +{ + if (!name || namelen < int(sizeof(sockaddr_in))) + { + LOGC(aclog.Error, log << "connect(): invalid call: name=" << name << " namelen=" << namelen); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + + 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; + HLOGC(aclog.Debug, 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 asymmetrically lost. But for no other purpose. + /* + ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival); + */ + + int isn = g.currentSchedSequence(); + + // 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 + { + // record peer address + s->m_PeerAddr = target_addr; + 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(); + s->m_Status = SRTS_CLOSING; + + // broadcast all "accept" waiting + CSync::lock_notify_all(s->m_AcceptCond, s->m_AcceptLock); + } + else + { + s->m_Status = SRTS_CLOSING; + // 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 ) + { + ... + } + */ + CSync::notify_one_relaxed(m_GCStopCond); + + 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"). + && s->core().m_pRcvBuffer->hasAvailablePackets()) + { + 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) + { + HLOGC(smlog.Debug, + 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) + { + // HLOGC(smlog.Debug, log << "checking CLOSED socket: " << 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"); + + // HLOGC(smlog.Debug, log << "will unref socket: " << 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::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, + const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket) +{ + if (muxaddr.family() != AF_INET6) + return true; // Don't check - the family has been checked already + + if (reqaddr.isany()) + { + if (cfgSocket.iIpV6Only == -1) // Treat as "adaptive" + return true; + + // If set explicitly, then it must be equal to the one of found muxer. + return cfgSocket.iIpV6Only == cfgMuxer.iIpV6Only; + } + + // If binding to the certain IPv6 address, then this setting doesn't matter. + return true; +} + +bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket) +{ + if (!cfgMuxer.bReuseAddr) + { + HLOGP(smlog.Debug, "channelSettingsMatch: fail: the multiplexer is not reusable"); + return false; + } + + if (cfgMuxer.isCompatWith(cfgSocket)) + return true; + + HLOGP(smlog.Debug, "channelSettingsMatch: fail: some options have different values"); + return false; +} + +void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, 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 = reqaddr.hport(); + const CSrtConfig& cfgSocket = s->core().m_config; + + // This loop is going to check the attempted binding of + // address:port and socket settings against every existing + // multiplexer. Possible results of the check are: + + // 1. MATCH: identical address - reuse it and quit. + // 2. CONFLICT: report error: the binding partially overlaps + // so it neither can be reused nor is free to bind. + // 3. PASS: different and not overlapping - continue searching. + + // In this function the convention is: + // MATCH: do nothing and proceed with binding reusage, THEN break. + // CONFLICT: throw an exception. + // PASS: use 'continue' to pass to the next element. + + 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: + // - reqaddr is also a wildcard + // - channel settings match + // Otherwise it's a conflict. + sockaddr_any mux_addr; + m.m_pChannel->getSockAddr((mux_addr)); + + HLOGC(smlog.Debug, + log << "bind: Found existing muxer @" << m.m_iID << " : " << mux_addr.str() << " - check against " + << reqaddr.str()); + + if (mux_addr.isany()) + { + if (mux_addr.family() == AF_INET6) + { + // With IPv6 we need to research two possibilities: + // iIpV6Only == 1 -> This means that it binds only :: wildcard, but not 0.0.0.0 + // iIpV6Only == 0 -> This means that it binds both :: and 0.0.0.0. + // iIpV6Only == -1 -> Hard to say what to do, but treat it as a potential conflict in any doubtful case. + + if (m.m_mcfg.iIpV6Only == 1) + { + // PASS IF: candidate is IPv4, no matter the address + // MATCH IF: candidate is IPv6 with only=1 + // CONFLICT IF: candidate is IPv6 with only != 1 or IPv6 non-wildcard. + + if (reqaddr.family() == AF_INET) + { + HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID + << " is :: v6only - requested IPv4 ANY is NOT IN THE WAY. Searching on."); + continue; + } + + // Candidate is AF_INET6 + + if (cfgSocket.iIpV6Only != 1 || !reqaddr.isany()) + { + // CONFLICT: + // 1. attempting to make a wildcard IPv4 + IPv6 + // while the multiplexer for wildcard IPv6 exists. + // 2. If binding to a given address, it conflicts with the wildcard + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() + << " conflicts with existing IPv6 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + + // Otherwise, MATCH. + } + else if (m.m_mcfg.iIpV6Only == 0) + { + // Muxer's address is a wildcard for :: and 0.0.0.0 at once. + // This way only IPv6 wildcard with v6only=0 is a perfect match and everything + // else is a conflict. + + if (reqaddr.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == 0) + { + // MATCH + } + else + { + // CONFLICT: attempting to make a wildcard IPv4 + IPv6 while + // the multiplexer for wildcard IPv6 exists. + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " conflicts with existing IPv6 + IPv4 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + else // Case -1, by unknown reason. Accept only with -1 setting, others are conflict. + { + if (reqaddr.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == -1) + { + // MATCH + } + else + { + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " conflicts with existing IPv6 v6only=unknown wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + } + else // muxer is IPv4 wildcard + { + // Then only IPv4 wildcard is a match and: + // - IPv6 with only=true is PASS (not a conflict) + // - IPv6 with only=false is CONFLICT + // - IPv6 with only=undefined is CONFLICT + // REASON: we need to make a potential conflict a conflict as there will be + // no bind() call to check if this wouldn't be a conflict in result. If you want + // to have a binding to IPv6 that should avoid conflict with IPv4 wildcard binding, + // then SRTO_IPV6ONLY option must be explicitly set before binding. + // Also: + if (reqaddr.family() == AF_INET) + { + if (reqaddr.isany()) + { + // MATCH + } + else + { + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() + << " conflicts with existing IPv4 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + else // AF_INET6 + { + if (cfgSocket.iIpV6Only == 1 || !reqaddr.isany()) + { + // PASS + HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID + << " is IPv4 wildcard - requested " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " is NOT IN THE WAY. Searching on."); + continue; + } + else + { + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " conflicts with existing IPv4 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + } + + reuse_attempt = true; + HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable"); + } + // Muxer address is NOT a wildcard, so conflicts only with WILDCARD of the same type + else if (reqaddr.isany() && reqaddr.family() == mux_addr.family()) + { + LOGC(smlog.Error, + log << "bind: Wildcard address: " << reqaddr.str() + << " conflicts with existting IP binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + // If this is bound to a certain address, AND: + else if (mux_addr.equal_address(reqaddr)) + { + // - the address is the same as reqaddr + 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"); + continue; + } + // Otherwise: + // - the address is different than reqaddr + // - 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) && inet6SettingsCompat(mux_addr, m.m_mcfg, reqaddr, 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: " << reqaddr.str() << " conflicts with binding: " << mux_addr.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 reusage or conflict. + LOGC(smlog.Fatal, log << "SHOULD NOT GET HERE!!!"); + SRT_ASSERT(false); + } + } + + // a new multiplexer is needed + CMultiplexer m; + configureMuxer((m), s, reqaddr.family()); + + try + { + m.m_pChannel = new CChannel(); + m.m_pChannel->setConfig(m.m_mcfg); + + if (udpsock) + { + // In this case, reqaddr contains the address + // that has been extracted already from the + // given socket + m.m_pChannel->attach(*udpsock, reqaddr); + } + else if (reqaddr.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(reqaddr.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(reqaddr); + } + + // AFTER OPENING, check the matter of IPV6_V6ONLY option, + // as it decides about the fact that the occupied binding address + // in case of wildcard is both :: and 0.0.0.0, or only ::. + if (reqaddr.family() == AF_INET6 && m.m_mcfg.iIpV6Only == -1) + { + // XXX We don't know how probable it is to get the error here + // and resulting -1 value. As a fallback for that case, the value -1 + // is honored here, just all side-bindings for other sockes will be + // rejected as a potential conflict, even if binding would be accepted + // in these circumstances. Only a perfect match in case of potential + // overlapping will be accepted on the same port. + m.m_mcfg.iIpV6Only = m.m_pChannel->sockopt(IPPROTO_IPV6, IPV6_V6ONLY, -1); + } + + 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 situation when the listener + // socket has been closed in the meantime when the accepted socket is being + // processed. This procedure is different than updateMux because this time we + // only want to have a multiplexer socket to be assigned to the accepted socket. + // It is also unlikely that the listener socket is garbage-collected so fast, so + // this procedure will most likely find the multiplexer of the zombie listener socket, + // which no longer accepts new connections (the listener is withdrawn immediately from + // the port) that wasn't yet completely deleted. + 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 if (m.m_iIPversion == AF_INET6) + { + // Allowed fallback case when we only need an accepted socket. + 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 CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* name, int* namelen) +int srt::CUDT::recv(SRTSOCKET u, char* buf, int len, int) { - 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)); + return ret; } -int CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* name, int* namelen) +int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { - 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); - - return 0; + SRT_MSGCTRL mctrl = srt_msgctrl_default; + int ret = recvmsg2(u, buf, len, (mctrl)); + srctime = mctrl.srctime; + return ret; } -int CUDTUnited::select( - ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout) +int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) { - uint64_t entertime = CTimer::getTime(); + 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 - uint64_t to; - if (!timeout) - to = 0xFFFFFFFFFFFFFFFFULL; - else - to = timeout->tv_sec * 1000000 + timeout->tv_usec; + 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); + } +} - // initialize results - int count = 0; - set rs, ws, es; +int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) +{ + 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) +#if ENABLE_BONDING + if (u & SRTGROUP_MASK) + return groupsockbstats(u, perf, clear); #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))); -#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 +4331,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 +4523,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 + // compatibility 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 +4670,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 +4696,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 7ee6293eaa2..9ba77d23a88 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,436 @@ 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 is necessary to be called from the group before the group clears + // the connection with the socket. As for managed groups (and there are + // currently no other group types), a socket disconnected from the group + // is no longer usable. + void setClosing() + { + core().m_bClosing = true; + } - /// 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. + /// 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); - SRTSOCKET newSocket(int af, int ); + // Instrumentally used by select() and also required for non-blocking + // mode check in groups + bool readReady(); + bool writeReady() const; + bool broken() const; - /// 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. +private: + CUDTSocket& operator=(const CUDTSocket&); +}; - int newConnection(const SRTSOCKET listen, const sockaddr* peer, CHandShake* hs, const CPacket& hspkt, - ref_t r_error); +//////////////////////////////////////////////////////////////////////////////// - int installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); +class CUDTUnited +{ + friend class CUDT; + friend class CUDTGroup; + friend class CRendezvousQueue; + friend class CCryptoControl; - /// look up the UDT entity according to its ID. - /// @param [in] u the UDT socket ID. - /// @return Pointer to the UDT entity. +public: + CUDTUnited(); + ~CUDTUnited(); - CUDT* lookup(const SRTSOCKET u); + // Public constants + static const int32_t MAX_SOCKET_VAL = SRTGROUP_MASK - 1; // maximum value for a regular socket - /// Check the status of the UDT socket. - /// @param [in] u the UDT socket ID. - /// @return UDT socket status, or NONEXIST if not found. +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; + } + + void deleteGroup(CUDTGroup* g); + void deleteGroup_LOCKED(CUDTGroup* g); - SRT_SOCKSTATUS getStatus(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 - // socket APIs + CEPoll& epoll_ref() { return m_EPoll; } - 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); +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); - /// record the UDT exception. - /// @param [in] e pointer to a UDT exception instance. +private: + typedef std::map sockets_t; // stores all the socket structures + sockets_t m_Sockets; - void setError(CUDTException* e); +#if ENABLE_BONDING + typedef std::map groups_t; + groups_t m_Groups; +#endif - /// look up the most recent UDT exception. - /// @return pointer to a UDT exception instance. + sync::Mutex m_GlobControlLock; // used to synchronize UDT API - CUDTException* getError(); + sync::Mutex m_IDLock; // used to synchronize ID generation -private: -// void init(); + SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID + SRTSOCKET m_SocketIDGenerator_init; // Keeps track of the very first one + + std::map > + m_PeerRec; // record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn private: - std::map m_Sockets; // stores all the socket structures + friend struct FLookupSocketWithEvent_LOCKED; - pthread_mutex_t m_ControlLock; // used to synchronize UDT API + 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_IDLock; // used to synchronize ID generation - SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID +#if ENABLE_BONDING + CUDTGroup* locateAcquireGroup(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); + CUDTGroup* acquireSocketsGroup(CUDTSocket* s); - std::map > m_PeerRec;// record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn + 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 + } + } + }; -private: - pthread_key_t m_TLSError; // thread local error record (last error) - static void TLSDestroy(void* e) {if (NULL != e) delete (CUDTException*)e;} +#endif + void updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* = NULL); + bool updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); -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); + // Utility functions for updateMux + void configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af); + uint16_t installMuxer(CUDTSocket* w_s, CMultiplexer& sm); + + /// @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); + static bool inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, + const sockaddr_any& reqaddr, const CSrtMuxerConfig& 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 00000000000..f1394a7dac0 --- /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 00000000000..e01012313ee --- /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 00000000000..9e4df2dbdbe --- /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 deleted file mode 100644 index 1d46b688fec..00000000000 --- a/trunk/3rdparty/srt-1-fit/srtcore/buffer.cpp +++ /dev/null @@ -1,1955 +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 03/12/2011 -modified by - Haivision Systems Inc. -*****************************************************************************/ - -#include -#include -#include "buffer.h" -#include "packet.h" -#include "core.h" // provides some constants -#include "logging.h" - -using namespace std; -using namespace srt_logging; - -CSndBuffer::CSndBuffer(int size, int mss) - : m_BufLock() - , m_pBlock(NULL) - , m_pFirstBlock(NULL) - , m_pCurrBlock(NULL) - , m_pLastBlock(NULL) - , m_pBuffer(NULL) - , m_iNextMsgNo(1) - , m_iSize(size) - , m_iMSS(mss) - , 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); -} - -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); -} - -void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order, uint64_t srctime, ref_t r_msgno) -{ - int32_t& msgno = *r_msgno; - - int size = len / m_iMSS; - if ((len % m_iMSS) != 0) - size ++; - - HLOGC(mglog.Debug, log << "addBuffer: 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 << "addBuffer: ... still lacking " << (size + m_iCount - m_iSize) << " buffers..."); - increase(); - } - - const uint64_t time = CTimer::getTime(); - int32_t inorder = order ? MSGNO_PACKET_INORDER::mask : 0; - - HLOGC(dlog.Debug, log << CONID() << "addBuffer: adding " - << size << " packets (" << len << " bytes) to send, msgno=" << m_iNextMsgNo - << (inorder ? "" : " NOT") << " in order"); - - Block* s = m_pLastBlock; - msgno = m_iNextMsgNo; - for (int i = 0; i < size; ++ i) - { - int pktlen = len - i * m_iMSS; - if (pktlen > m_iMSS) - pktlen = m_iMSS; - - 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); - s->m_iLength = pktlen; - - s->m_iMsgNoBitset = m_iNextMsgNo | inorder; - if (i == 0) - s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); - if (i == size - 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. - // 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. - // 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_iBytesCount += len; - m_ullLastOriginTime_us = time; - - updateInputRate(time, size, len); - -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - updAvgBufSize(time); -#endif - - CGuard::leaveCS(m_BufLock); - - - // 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). - // If this value has been reached in a step of incrementation, it means that the - // 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 ++; - if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) - m_iNextMsgNo = 1; -} - -void CSndBuffer::setInputRateSmpPeriod(int period) -{ - m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation -} - -void CSndBuffer::updateInputRate(uint64_t time, int pkts, int bytes) -{ - //no input rate calculation - if (m_InRatePeriod == 0) - return; - - if (m_InRateStartTime == 0) - { - m_InRateStartTime = time; - 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 = (time - m_InRateStartTime); - 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(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; - - setInputRateSmpPeriod(INPUTRATE_RUNNING_US); - } -} - - -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; -} - -int CSndBuffer::readData(char** data, int32_t& msgno_bitset, uint64_t& srctime, int kflgs) -{ - // 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; -} - -int CSndBuffer::readData(char** data, const int offset, int32_t& msgno_bitset, uint64_t& srctime, int& msglen) -{ - 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; -} - -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 - - CTimer::triggerEvent(); -} - -int CSndBuffer::getCurrBufSize() const -{ - return m_iCount; -} - -#ifdef SRT_ENABLE_SNDBUFSZ_MAVG - -int CSndBuffer::getAvgBufSize(ref_t r_bytes, ref_t r_tsp) -{ - int& bytes = *r_bytes; - int& timespan = *r_tsp; - CGuard 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); -} - -void CSndBuffer::updAvgBufSize(uint64_t 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; - } -} - -#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ - -int CSndBuffer::getCurrBufSize(ref_t bytes, ref_t timespan) -{ - *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; -} - -int CSndBuffer::dropLateData(int &bytes, uint64_t latetime) -{ - 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); -} - -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)"); - -} - -//////////////////////////////////////////////////////////////////////////////// - -/* -* 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 - -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 -{ - 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); -} - -CRcvBuffer::~CRcvBuffer() -{ - for (int i = 0; i < m_iSize; ++ i) - { - if (m_pUnit[i] != NULL) - { - m_pUnitQueue->makeUnitFree(m_pUnit[i]); - } - } - - delete [] m_pUnit; - - pthread_mutex_destroy(&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 */ - } -} - -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; -} - -int CRcvBuffer::readBuffer(char* data, int len) -{ - int p = m_iStartPos; - int lastack = m_iLastAckPos; - int rs = len; -#if ENABLE_HEAVY_LOGGING - char* begin = data; -#endif - - const uint64_t now = (m_bTsbPdMode ? CTimer::getTime() : uint64_t()); - - HLOGC(dlog.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"); - return -1; - } - - if (m_bTsbPdMode) - { - 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 */ - } - - int unitsize = (int) m_pUnit[p]->m_Packet.getLength() - m_iNotch; - if (unitsize > rs) - unitsize = rs; - - 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 ((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; -} - -int CRcvBuffer::readBufferToFile(fstream& ofs, 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; -} - -void CRcvBuffer::ackData(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(); -} - -void CRcvBuffer::skipData(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; -} - -bool CRcvBuffer::getRcvFirstMsg(ref_t r_tsbpdtime, ref_t r_passack, ref_t r_skipseqno, ref_t r_curpktseq) -{ - int32_t& skipseqno = *r_skipseqno; - bool& passack = *r_passack; - skipseqno = -1; - 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) - // - @return: whether the reported packet is ready to play - - /* Check the acknowledged packets */ - if (getRcvReadyMsg(r_tsbpdtime, r_curpktseq)) - { - HLOGC(dlog.Debug, log << "getRcvFirstMsg: ready CONTIG packet: %" << (*r_curpktseq)); - return true; - } - else if (*r_tsbpdtime != 0) - { - HLOGC(dlog.Debug, log << "getRcvFirstMsg: no packets found"); - return false; - } - - // getRcvReadyMsg returned false and tsbpdtime == 0. - - // 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 - // - 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 - - /* - * 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; - - // XXX SUSPECTED ISSUE with this algorithm: - // The above call to getRcvReadyMsg() should report as to whether: - // - there is an EXACTLY NEXT SEQUENCE packet - // - this packet is ready to play. - // - // Situations handled after the call are when: - // - there's the next sequence packet available and it is ready to play - // - there are no packets at all, ready to play or not - // - // So, the remaining situation is that THERE ARE PACKETS that follow - // the current sequence, but they are not ready to play. This includes - // packets that have the exactly next sequence and packets that jump - // over a lost packet. - // - // As the getRcvReadyMsg() function walks through the incoming units - // to see if there's anything that satisfies these conditions, it *SHOULD* - // be also capable of checking if the next available packet, if it is - // there, is the next sequence packet or not. Retrieving this exactly - // packet would be most useful, as the test for play-readiness and - // sequentiality can be done on it directly. - // - // 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. - - for (int i = m_iLastAckPos, n = (m_iLastAckPos + m_iMaxPos) % m_iSize; i != n; i = (i + 1) % m_iSize) - { - 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); - } - else - { - /* We got the 1st valid packet */ - *r_tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); - if (*r_tsbpdtime <= CTimer::getTime()) - { - /* Packet ready to play */ - if (haslost) - { - /* - * 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(dlog.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 - // possibility that it is so otherwise because: - // - if this first good packet is ready to play, THIS HERE RETURNS NOW. - // ... - return true; - } - HLOGC(dlog.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. - // ... - return false; - } - // ... and if this first packet WASN'T GOOD, the loop continues, however since now - // 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"); - return false; -} - -bool CRcvBuffer::getRcvReadyMsg(ref_t tsbpdtime, ref_t curpktseq) -{ - *tsbpdtime = 0; - - IF_HEAVY_LOGGING(const char* reason = "NOT RECEIVED"); - - for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) - { - 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; - continue; - } - - *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"); - freeunit = true; - } - else - { - *tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); - int64_t towait = (*tsbpdtime - CTimer::getTime()); - if (towait > 0) - { - 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; - } - - if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) - { - IF_HEAVY_LOGGING(reason = "DECRYPTION FAILED"); - freeunit = true; /* packet not decrypted */ - } - 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; - } - } - - if (freeunit) - { - HLOGC(mglog.Debug, log << "getRcvReadyMsg: POS=" << i << " FREED"); - /* 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; - } - } - - HLOGC(mglog.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) -{ - *tsbpdtime = 0; - - if (m_bTsbPdMode) - { - CPacket* pkt = getRcvReadyPacket(); - if (!pkt) - 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()); - - return (*tsbpdtime <= CTimer::getTime()); - } - - return isRcvDataAvailable(); -} - -// XXX This function may be called only after checking -// if m_bTsbPdMode. -CPacket* CRcvBuffer::getRcvReadyPacket() -{ - for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) - { - /* - * Skip missing packets that did not arrive in time. - */ - if ( m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD ) - return &m_pUnit[i]->m_Packet; - } - - return 0; -} - -#if ENABLE_HEAVY_LOGGING -// This function is for debug purposes only and it's called only -// from within HLOG* macros. -void CRcvBuffer::reportBufferStats() -{ - int nmissing = 0; - int32_t low_seq= -1, high_seq = -1; - 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 ) - { - low_seq = m_pUnit[i]->m_Packet.m_iSeqNo; - low_ts = m_pUnit[i]->m_Packet.m_iTimeStamp; - break; - } - ++nmissing; - } - - // Not sure if a packet MUST BE at the last ack pos position, so check, just in case. - 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_seq = m_pUnit[n]->m_Packet.m_iSeqNo; - } - else - { - // Possibilities are: - // m_iStartPos == m_iLastAckPos, high_ts == low_ts, defined. - // No packet: low_ts == 0, so high_ts == 0, too. - high_ts = low_ts; - } - // The 32-bit timestamps are relative and roll over oftten; what - // we really need is the timestamp difference. The only place where - // we can ask for the time base is the upper time because when trying - // to receive the time base for the lower time we'd break the requirement - // for monotonic clock. - - uint64_t upper_time = high_ts; - uint64_t lower_time = low_ts; - - if (lower_time > upper_time) - 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) - { - 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) << ")"); -} - -#endif // ENABLE_HEAVY_LOGGING - -bool CRcvBuffer::isRcvDataReady() -{ - uint64_t tsbpdtime; - int32_t seq; - - return isRcvDataReady(Ref(tsbpdtime), Ref(seq)); -} - -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; -} - -int CRcvBuffer::getRcvDataSize() const -{ - if (m_iLastAckPos >= m_iStartPos) - return m_iLastAckPos - m_iStartPos; - - return m_iSize + m_iLastAckPos - m_iStartPos; -} - -int CRcvBuffer::debugGetSize() const -{ - // Does exactly the same as getRcvDataSize, but - // it should be used FOR INFORMATIONAL PURPOSES ONLY. - // The source values might be changed in another thread - // during the calculation, although worst case the - // resulting value may differ to the real buffer size by 1. - int from = m_iStartPos, to = m_iLastAckPos; - int size = to - from; - if (size < 0) - size += m_iSize; - - 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) -{ - timespan = m_TimespanMAvg; - bytes = m_iBytesCountMAvg; - return(m_iCountMAvg); -} - -/* Update moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ -void CRcvBuffer::updRcvAvgDataSize(uint64_t 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"); - } -} -#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ - -/* Return acked data pkts, bytes, and timespan (ms) of the receive buffer */ -int CRcvBuffer::getRcvDataSize(int &bytes, int ×pan) -{ - 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; -} - -int CRcvBuffer::getRcvAvgPayloadSize() const -{ - return m_iAvgPayloadSz; -} - -void CRcvBuffer::dropMsg(int32_t msgno, bool using_rexmit_flag) -{ - 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; -} - -uint64_t CRcvBuffer::getTsbPdTimeBase(uint32_t timestamp_us) -{ - /* - * 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. - - if (m_bTsbPdWrapCheck) - { - // Wrap check period. - - 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"); - } - } - // 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)) - { - /* Approching wrap around point, start wrap check period (if for packet delivery head) */ - m_bTsbPdWrapCheck = true; - tslog.Debug("tsbpd wrap period begins"); - } - - return (m_ullTsbPdTimeBase + carryover); -} - -uint64_t CRcvBuffer::getPktTsbPdTime(uint32_t timestamp) -{ - return(getTsbPdTimeBase(timestamp) + m_uTsbPdDelay + timestamp + m_DriftTracer.drift()); -} - -int CRcvBuffer::setRcvTsbPdMode(uint64_t timebase, uint32_t delay) -{ - 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; -} - -#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 - { - /* 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]++; - } - - if ((m_iTsbPdDriftNbSamples % TSBPD_DRIFT_PRT_SAMPLES) == 0) - { - int *histo = m_TsbPdDriftHisto1ms; - - 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]); - - 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::printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg) -{ - 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)); -} -#endif /* SRT_DEBUG_TSBPD_DRIFT */ - -void CRcvBuffer::addRcvTsbPdDriftSample(uint32_t timestamp, pthread_mutex_t& mutex_to_lock) -{ - 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). - */ - - // 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). - - int64_t iDrift = CTimer::getTime() - (getTsbPdTimeBase(timestamp) + timestamp); - - CGuard::enterCS(mutex_to_lock); - - bool updated = m_DriftTracer.update(iDrift); - -#ifdef SRT_DEBUG_TSBPD_DRIFT - printDriftHistogram(iDrift); -#endif /* SRT_DEBUG_TSBPD_DRIFT */ - - if ( updated ) - { -#ifdef SRT_DEBUG_TSBPD_DRIFT - printDriftOffset(m_DriftTracer.overdrift(), m_DriftTracer.drift()); -#endif /* SRT_DEBUG_TSBPD_DRIFT */ - -#if ENABLE_HEAVY_LOGGING - uint64_t oldbase = m_ullTsbPdTimeBase; -#endif - m_ullTsbPdTimeBase += m_DriftTracer.overdrift(); - - 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)); - } - - CGuard::leaveCS(mutex_to_lock); -} - -int CRcvBuffer::readMsg(char* data, int len) -{ - SRT_MSGCTRL dummy = srt_msgctrl_default; - return readMsg(data, len, Ref(dummy)); -} - - -int CRcvBuffer::readMsg(char* data, int len, ref_t r_msgctl) -{ - SRT_MSGCTRL& msgctl = *r_msgctl; - int p, q; - bool passack; - bool empty = true; - uint64_t& rplaytime = msgctl.srctime; - -#ifdef ENABLE_HEAVY_LOGGING - reportBufferStats(); -#endif - - if (m_bTsbPdMode) - { - passack = false; - int seq = 0; - - if (getRcvReadyMsg(Ref(rplaytime), Ref(seq))) - { - 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; - -#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 */ - } - } - else - { - rplaytime = 0; - if (scanMsg(Ref(p), Ref(q), Ref(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(); - - SRT_ASSERT(len > 0); - int rs = len > 0 ? len : 0; - while (p != (q + 1) % m_iSize) - { - 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', - // but still the whole packet is removed from the receiver buffer. - if (pktlen > 0) - countBytes(-1, -pktlen, true); - - const int unitsize = ((rs >= 0) && (pktlen > rs)) ? rs : pktlen; - - HLOGC(mglog.Debug, log << "readMsg: checking unit POS=" << p); - - if (unitsize > 0) - { - 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 - } - else - { - HLOGC(dlog.Debug, log << CONID() << "readMsg: SKIPPED POS=" << p << " - ZERO SIZE UNIT"); - } - - 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); - } - else - { - HLOGC(dlog.Debug, log << CONID() << "readMsg: PASSACK UNIT POS=" << p); - m_pUnit[p]->m_iFlag = CUnit::PASSACK; - } - - if (++ p == m_iSize) - p = 0; - } - - if (!passack) - m_iStartPos = (q + 1) % m_iSize; - - return len - rs; -} - - -bool CRcvBuffer::scanMsg(ref_t r_p, ref_t r_q, ref_t passack) -{ - int& p = *r_p; - int& q = *r_q; - - // empty buffer - if ((m_iStartPos == m_iLastAckPos) && (m_iMaxPos <= 0)) - { - HLOGC(mglog.Debug, log << "scanMsg: empty buffer"); - return false; - } - - int rmpkts = 0; - int rmbytes = 0; - //skip all bad msgs at the beginning - while (m_iStartPos != m_iLastAckPos) - { - // Roll up to the first valid unit - if (!m_pUnit[m_iStartPos]) - { - 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 ) - { - bool good = true; - - // look ahead for the whole message - - // We expect to see either of: - // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - // [PB_SOLO] - // but not: - // [PB_FIRST] NULL ... - // [PB_FIRST] FREE/PASSACK/DROPPED... - // If the message didn't look as expected, interrupt this. - - // This begins with a message starting at m_iStartPos - // up to m_iLastAckPos OR until the PB_LAST message is found. - // If any of the units on this way isn't good, this OUTER loop - // will be interrupted. - for (int i = m_iStartPos; i != m_iLastAckPos;) - { - if (!m_pUnit[i] || m_pUnit[i]->m_iFlag != CUnit::GOOD) - { - good = false; - break; - } - - // Likewise, boundary() & PB_LAST will be satisfied for last OR solo. - if ( m_pUnit[i]->m_Packet.getMsgBoundary() & PB_LAST ) - break; - - if (++ i == m_iSize) - i = 0; - } - - if (good) - break; - } - - CUnit* tmp = m_pUnit[m_iStartPos]; - m_pUnit[m_iStartPos] = NULL; - rmpkts++; - rmbytes += (int) tmp->m_Packet.getLength(); - m_pUnitQueue->makeUnitFree(tmp); - - if (++ m_iStartPos == m_iSize) - m_iStartPos = 0; - } - /* we removed bytes form receive buffer */ - countBytes(-rmpkts, -rmbytes, true); - - // Not sure if this is correct, but this above 'while' loop exits - // under the following conditions only: - // - m_iStartPos == m_iLastAckPos (that makes passack = true) - // - found at least GOOD unit with PB_FIRST and not all messages up to PB_LAST are good, - // 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; - bool found = false; - - // looking for the first message - //>>m_pUnit[size + m_iMaxPos] is not valid - - // XXX Would be nice to make some very thorough refactoring here. - - // This rolls by q variable from m_iStartPos up to m_iLastAckPos, - // 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. - - for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i < n; ++ i) - { - if (m_pUnit[q] && m_pUnit[q]->m_iFlag == CUnit::GOOD) - { - // Equivalent pseudocode: - // PacketBoundary bound = m_pUnit[q]->m_Packet.getMsgBoundary(); - // if ( IsSet(bound, PB_FIRST) ) - // p = q; - // if ( IsSet(bound, PB_LAST) && p != -1 ) - // found = true; - // - // Not implemented this way because it uselessly check p for -1 - // also after setting it explicitly. - - switch (m_pUnit[q]->m_Packet.getMsgBoundary()) - { - case PB_SOLO: // 11 - p = q; - found = true; - break; - - case PB_FIRST: // 10 - p = q; - break; - - case PB_LAST: // 01 - if (p != -1) - found = true; - break; - - case PB_SUBSEQUENT: - ; // do nothing (caught first, rolling for last) - } - } - else - { - // a hole in this message, not valid, restart search - p = -1; - } - - // 'found' is set when the current iteration hit a message with PB_LAST - // (including PB_SOLO since the very first message). - 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()) - { - HLOGC(mglog.Debug, log << "scanMsg: found next-to-broken message, delivering OUT OF ORDER."); - break; - } - - found = false; - } - - if (++ q == m_iSize) - q = 0; - - if (q == m_iLastAckPos) - *passack = true; - } - - // no msg found - if (!found) - { - // 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 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)) - { - HLOGC(mglog.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); - } - } - else - { - HLOGC(mglog.Debug, log << "scanMsg: extracted message p=" << p << " q=" << q << " (" << ((q-p+m_iSize+1)%m_iSize) << " packets)"); - } - - return found; -} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer.h b/trunk/3rdparty/srt-1-fit/srtcore/buffer.h deleted file mode 100644 index a267d8b4446..00000000000 --- a/trunk/3rdparty/srt-1-fit/srtcore/buffer.h +++ /dev/null @@ -1,510 +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 - 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 05/05/2009 -modified by - Haivision Systems Inc. -*****************************************************************************/ - -#ifndef __UDT_BUFFER_H__ -#define __UDT_BUFFER_H__ - - -#include "udt.h" -#include "list.h" -#include "queue.h" -#include "utilities.h" -#include - -class CSndBuffer -{ -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 ""; } - - CSndBuffer(int size = 32, int mss = 1500); - ~CSndBuffer(); - -public: - - /// 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 - - void addBuffer(const char* data, int len, int ttl, bool order, uint64_t srctime, ref_t r_msgno); - - /// 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. - - int addBufferFromFile(std::fstream& ifs, int len); - - /// 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. - - int readData(char** data, int32_t& msgno, uint64_t& origintime, int kflgs); - - - /// 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. - - int readData(char** data, const int offset, int32_t& msgno, uint64_t& origintime, int& msglen); - - /// Update the ACK point and may release/unmap/return the user data according to the flag. - /// @param [in] offset number of packets acknowledged. - - 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; - - int dropLateData(int &bytes, uint64_t latetime); - -#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); - - uint64_t getInRatePeriod() const { return m_InRatePeriod; } - - /// Retrieve input bitrate in bytes per second - int getInputRate() const { return m_iInRateBps; } - - /// 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); - - - void resetInputRateSmpPeriod(bool disable = false) - { - setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); - } - - -private: - - void increase(); - void setInputRateSmpPeriod(int period); - -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; - -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 - -private: - CSndBuffer(const CSndBuffer&); - CSndBuffer& operator=(const CSndBuffer&); -}; - -//////////////////////////////////////////////////////////////////////////////// - - -class CRcvBuffer -{ -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(); - - -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. - - 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(); - - /// 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 - - int setRcvTsbPdMode(uint64_t timebase, uint32_t delay); - - /// 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 - - void addRcvTsbPdDriftSample(uint32_t timestamp, pthread_mutex_t& lock); - -#ifdef SRT_DEBUG_TSBPD_DRIFT - void printDriftHistogram(int64_t iDrift); - void printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg); -#endif - - /// 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. - - - bool getRcvFirstMsg(ref_t tsbpdtime, ref_t passack, ref_t skipseqno, ref_t curpktseq); - - /// Update the ACK point of the buffer. - /// @param [in] len size of data to be skip & acknowledged. - - void skipData(int len); - -#if ENABLE_HEAVY_LOGGING - void reportBufferStats(); // Heavy logging Debug only -#endif - -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 - - - bool getRcvReadyMsg(ref_t tsbpdtime, ref_t curpktseq); - -public: - - // (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); - - /// Get packet local delivery time - /// @param [in] timestamp packet timestamp (relative to peer StartTime), wrapping around every ~72 min - /// @return local delivery time (usec) - -public: - uint64_t getPktTsbPdTime(uint32_t timestamp); - int debugGetSize() const; - bool empty() const; - - // Required by PacketFilter facility to use as a storage - // for provided packets - CUnitQueue* getUnitQueue() - { - return m_pUnitQueue; - } - -private: - - /// 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); - -private: - bool scanMsg(ref_t start, ref_t end, ref_t passack); - -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 */ - -#ifdef SRT_DEBUG_TSBPD_OUTJITTER - unsigned long m_ulPdHisto[4][10]; -#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ - -private: - CRcvBuffer(); - CRcvBuffer(const CRcvBuffer&); - CRcvBuffer& operator=(const CRcvBuffer&); -}; - - -#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 00000000000..1f46788aabe --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.cpp @@ -0,0 +1,1153 @@ +/* + * 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 - 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. +*****************************************************************************/ + +#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 + iMaxPosOff) % 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 iMaxPosOff, size_t iSize, int iFirstNonreadPos) + { + if (iFirstNonreadPos == iStartPos) + return true; + + const int iLastPos = (iStartPos + iMaxPosOff) % iSize; + const bool isOverrun = iLastPos < iStartPos; + + if (isOverrun) + return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; + + return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; + } +} + + +/* + * RcvBufferNew (circular buffer): + * + * |<------------------- m_iSize ----------------------------->| + * | |<----------- m_iMaxPosOff ------------>| | + * | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | 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_iMaxPosOff: none? (modified on add and ack + */ + +CRcvBuffer::CRcvBuffer(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_iMaxPosOff(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 +} + +CRcvBuffer::~CRcvBuffer() +{ + // Can be optimized by only iterating m_iMaxPosOff 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 CRcvBuffer::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 << "CRcvBuffer::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_iMaxPosOff) + m_iMaxPosOff = 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->makeUnitTaken(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 CRcvBuffer::dropUpTo(int32_t seqno) +{ + IF_RCVBUF_DEBUG(ScopedLog scoped_log); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::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_iMaxPosOff -= len; + if (m_iMaxPosOff < 0) + m_iMaxPosOff = 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(); + + // If the nonread position is now behind the starting position, set it to the starting position and update. + // Preceding packets were likely missing, and the non read position can probably be moved further now. + if (CSeqNo::seqcmp(m_iFirstNonreadPos, m_iStartPos) < 0) + { + m_iFirstNonreadPos = m_iStartPos; + updateNonreadPos(); + } + if (!m_tsbpd.isEnabled() && m_bMessageAPI) + updateFirstReadableOutOfOrder(); + return iDropCnt; +} + +int CRcvBuffer::dropAll() +{ + if (empty()) + return 0; + + const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); + return dropUpTo(end_seqno); +} + +int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting) +{ + IF_RCVBUF_DEBUG(ScopedLog scoped_log); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropMessage(): %(" << seqnolo << " - " << seqnohi << ")" + << " #" << msgno << " actionOnExisting=" << actionOnExisting << " m_iStartSeqNo=%" + << m_iStartSeqNo); + + // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. + 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 << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " + << seqnohi << "]. Buffer start " << m_iStartSeqNo << "."); + return 0; + } + + const bool bKeepExisting = (actionOnExisting == KEEP_EXISTING); + int minDroppedOffset = -1; + int iDropCnt = 0; + const int start_off = max(0, offset_a); + const int start_pos = incPos(m_iStartPos, start_off); + const int end_off = min((int) m_szSize - 1, offset_b + 1); + const int end_pos = incPos(m_iStartPos, end_off); + bool bDropByMsgNo = msgno > SRT_MSGNO_CONTROL; // Excluding both SRT_MSGNO_NONE (-1) and SRT_MSGNO_CONTROL (0). + for (int i = start_pos; i != end_pos; i = incPos(i)) + { + // Check if the unit was already dropped earlier. + if (m_entries[i].status == EntryState_Drop) + continue; + + if (m_entries[i].pUnit) + { + const PacketBoundary bnd = packetAt(i).getMsgBoundary(); + + // 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 (bKeepExisting && bnd == PB_SOLO) + { + bDropByMsgNo = false; // Solo packet, don't search for the rest of the message. + LOGC(rbuflog.Debug, + log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO packet %" + << packetAt(i).getSeqNo() << "."); + continue; + } + + const int32_t msgseq = packetAt(i).getMsgSeq(m_bPeerRexmitFlag); + if (msgno > SRT_MSGNO_CONTROL && msgseq != msgno) + { + LOGC(rbuflog.Warn, log << "CRcvBuffer.dropMessage(): Packet seqno %" << packetAt(i).getSeqNo() << " has msgno " << msgseq << " differs from requested " << msgno); + } + + if (bDropByMsgNo && bnd == PB_FIRST) + { + // First packet of the message is about to be dropped. That was the only reason to search for msgno. + bDropByMsgNo = false; + } + } + + dropUnitInPos(i); + ++iDropCnt; + m_entries[i].status = EntryState_Drop; + if (minDroppedOffset == -1) + minDroppedOffset = offPos(m_iStartPos, i); + } + + if (bDropByMsgNo) + { + // If msgno is specified, potentially not the whole message was dropped using seqno range. + // The sender might have removed the first packets of the message, and thus @a seqnolo may point to a packet in the middle. + // The sender should have the last packet of the message it is requesting to be dropped. + // Therefore we don't search forward, but need to check earlier packets in the RCV buffer. + // Try to drop by the message number in case the message starts earlier than @a seqnolo. + const int stop_pos = decPos(m_iStartPos); + for (int i = start_pos; i != stop_pos; i = decPos(i)) + { + // Can't drop if message number is not known. + if (!m_entries[i].pUnit) // also dropped earlier. + continue; + + const PacketBoundary bnd = packetAt(i).getMsgBoundary(); + const int32_t msgseq = packetAt(i).getMsgSeq(m_bPeerRexmitFlag); + if (msgseq != msgno) + break; + + if (bKeepExisting && bnd == PB_SOLO) + { + LOGC(rbuflog.Debug, + log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO message packet %" + << packetAt(i).getSeqNo() << "."); + break; + } + + ++iDropCnt; + dropUnitInPos(i); + m_entries[i].status = EntryState_Drop; + // As the search goes backward, i is always earlier than minDroppedOffset. + minDroppedOffset = offPos(m_iStartPos, i); + + // Break the loop if the start of the message has been found. No need to search further. + if (bnd == PB_FIRST) + break; + } + IF_RCVBUF_DEBUG(scoped_log.ss << " iDropCnt " << iDropCnt); + } + + // Check if units before m_iFirstNonreadPos are dropped. + const 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 CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) +{ + const bool canReadInOrder = hasReadableInorderPkts(); + if (!canReadInOrder && m_iFirstReadableOutOfOrder < 0) + { + LOGC(rbuflog.Warn, log << "CRcvBuffer.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 << "CRcvBuffer::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 << "CRcvBuffer::readMessage(): null packet encountered."); + break; + } + + const CPacket& packet = packetAt(i); + 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_iMaxPosOff; + SRT_ASSERT(m_iMaxPosOff >= 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_iMaxPosOff, 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 CRcvBuffer::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 = packetAt(p); + + 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_iMaxPosOff; + SRT_ASSERT(m_iMaxPosOff >= 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 preceding packets are invalid. + if (!isInRange(m_iStartPos, m_iMaxPosOff, 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 CRcvBuffer::readBuffer(char* dst, int len) +{ + return readBufferTo(len, copyBytesToBuf, reinterpret_cast(dst)); +} + +int CRcvBuffer::readBufferToFile(fstream& ofs, int len) +{ + return readBufferTo(len, writeBytesToFile, reinterpret_cast(&ofs)); +} + +bool CRcvBuffer::hasAvailablePackets() const +{ + return hasReadableInorderPkts() || (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); +} + +int CRcvBuffer::getRcvDataSize() const +{ + if (m_iFirstNonreadPos >= m_iStartPos) + return m_iFirstNonreadPos - m_iStartPos; + + return int(m_szSize + m_iFirstNonreadPos - m_iStartPos); +} + +int CRcvBuffer::getTimespan_ms() const +{ + if (!m_tsbpd.isEnabled()) + return 0; + + if (m_iMaxPosOff == 0) + return 0; + + int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); + // Normally the last position should always be non empty + // if TSBPD is enabled (reading out of order is not allowed). + // However if decryption of the last packet fails, it may be dropped + // from the buffer (AES-GCM), and the position will be empty. + SRT_ASSERT(m_entries[lastpos].pUnit != NULL || m_entries[lastpos].status == EntryState_Drop); + while (m_entries[lastpos].pUnit == NULL && lastpos != m_iStartPos) + { + lastpos = decPos(lastpos); + } + + if (m_entries[lastpos].pUnit == NULL) + return 0; + + int startpos = m_iStartPos; + while (m_entries[startpos].pUnit == NULL && startpos != lastpos) + { + startpos = incPos(startpos); + } + + if (m_entries[startpos].pUnit == NULL) + return 0; + + const steady_clock::time_point startstamp = + getPktTsbPdTime(packetAt(startpos).getMsgTimeStamp()); + const steady_clock::time_point endstamp = getPktTsbPdTime(packetAt(lastpos).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 CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) const +{ + ScopedLock lck(m_BytesCountLock); + bytes = m_iBytesCount; + timespan = getTimespan_ms(); + return m_iPktsCount; +} + +CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const +{ + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); + for (int i = m_iStartPos; i != end_pos; i = incPos(i)) + { + // TODO: Maybe check status? + if (!m_entries[i].pUnit) + continue; + + const CPacket& packet = packetAt(i); + const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; + return info; + } + + const PacketInfo info = { -1, false, time_point() }; + return info; +} + +std::pair CRcvBuffer::getAvailablePacketsRange() const +{ + const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, (int) countReadable()); + return std::pair(m_iStartSeqNo, seqno_last); +} + +size_t CRcvBuffer::countReadable() const +{ + if (m_iFirstNonreadPos >= m_iStartPos) + return m_iFirstNonreadPos - m_iStartPos; + return m_szSize + m_iFirstNonreadPos - m_iStartPos; +} + +bool CRcvBuffer::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; +} + +CRcvBuffer::PacketInfo CRcvBuffer::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 = packetAt(m_iStartPos); + 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 = packetAt(m_iFirstReadableOutOfOrder); + 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 CRcvBuffer::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 CRcvBuffer::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 CRcvBuffer::dropUnitInPos(int pos) +{ + if (!m_entries[pos].pUnit) + return false; + if (m_tsbpd.isEnabled()) + { + updateTsbPdTimeBase(packetAt(pos).getMsgTimeStamp()); + } + else if (m_bMessageAPI && !packetAt(pos).getMsgOrderFlag()) + { + --m_numOutOfOrderPackets; + if (pos == m_iFirstReadableOutOfOrder) + m_iFirstReadableOutOfOrder = -1; + } + releaseUnitInPos(pos); + return true; +} + +void CRcvBuffer::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_iMaxPosOff; + if (m_iMaxPosOff < 0) + m_iMaxPosOff = 0; + } +} + +// TODO: Is this function complete? There are some comments left inside. +void CRcvBuffer::updateNonreadPos() +{ + if (m_iMaxPosOff == 0) + return; + + const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // 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 && (packetAt(pos).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 || packetAt(i).getMsgBoundary() & PB_LAST) + { + m_iFirstNonreadPos = incPos(i); + break; + } + } + + if (pos == m_iFirstNonreadPos || !m_entries[m_iFirstNonreadPos].pUnit) + break; + + pos = m_iFirstNonreadPos; + } +} + +int CRcvBuffer::findLastMessagePkt() +{ + for (int i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) + { + SRT_ASSERT(m_entries[i].pUnit); + + if (packetAt(i).getMsgBoundary() & PB_LAST) + { + return i; + } + } + + return -1; +} + +void CRcvBuffer::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_iMaxPosOff > 0); + SRT_ASSERT(m_entries[insertPos].pUnit); + const CPacket& pkt = packetAt(insertPos); + 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 CRcvBuffer::checkFirstReadableOutOfOrder() +{ + if (m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder < 0 || m_iMaxPosOff == 0) + return false; + + const int endPos = incPos(m_iStartPos, m_iMaxPosOff); + int msgno = -1; + for (int pos = m_iFirstReadableOutOfOrder; pos != endPos; pos = incPos(pos)) + { + if (!m_entries[pos].pUnit) + return false; + + const CPacket& pkt = packetAt(pos); + 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 CRcvBuffer::updateFirstReadableOutOfOrder() +{ + if (hasReadableInorderPkts() || m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder >= 0) + return; + + if (m_iMaxPosOff == 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_iMaxPosOff - 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 = packetAt(pos); + + 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 CRcvBuffer::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_iMaxPosOff - 1) % m_szSize; + if (startPos == lastPos) + return -1; + + int pos = startPos; + do + { + pos = incPos(pos); + if (!m_entries[pos].pUnit) + break; + + const CPacket& pkt = packetAt(pos); + + 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 CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const +{ + // Search preceding 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 = packetAt(pos); + + 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 CRcvBuffer::addRcvTsbPdDriftSample(uint32_t usTimestamp, const time_point& tsPktArrival, int usRTTSample) +{ + return m_tsbpd.addDriftSample(usTimestamp, tsPktArrival, usRTTSample); +} + +void CRcvBuffer::setTsbPdMode(const steady_clock::time_point& timebase, bool wrap, duration delay) +{ + m_tsbpd.setTsbPdMode(timebase, wrap, delay); +} + +void CRcvBuffer::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 CRcvBuffer::applyGroupDrift(const steady_clock::time_point& timebase, + bool wrp, + const steady_clock::duration& udrift) +{ + m_tsbpd.applyGroupDrift(timebase, wrp, udrift); +} + +CRcvBuffer::time_point CRcvBuffer::getTsbPdTimeBase(uint32_t usPktTimestamp) const +{ + return m_tsbpd.getTsbPdTimeBase(usPktTimestamp); +} + +void CRcvBuffer::updateTsbPdTimeBase(uint32_t usPktTimestamp) +{ + m_tsbpd.updateTsbPdTimeBase(usPktTimestamp); +} + +string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const +{ + stringstream ss; + + ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo + << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; + + ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; + + if (m_tsbpd.isEnabled() && m_iMaxPosOff > 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_iMaxPosOff - 1); + if (m_entries[iLastPos].pUnit) + { + ss << ", timespan "; + const uint32_t usPktTimestamp = packetAt(iLastPos).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(); +} + +CRcvBuffer::time_point CRcvBuffer::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 CRcvBuffer::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 CRcvBuffer::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 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 00000000000..d4b50fab7c3 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_rcv.h @@ -0,0 +1,392 @@ +/* + * 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 + +#include "buffer_tools.h" // AvgBufSize +#include "common.h" +#include "queue.h" +#include "tsbpd_time.h" + +namespace srt +{ + +/* + * Circular receiver buffer. + * + * |<------------------- m_szSize ---------------------------->| + * | |<------------ m_iMaxPosOff ----------->| | + * | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + * | 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 CRcvBuffer +{ + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; + +public: + CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI); + + ~CRcvBuffer(); + +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(); + + enum DropActionIfExists { + DROP_EXISTING = 0, + KEEP_EXISTING = 1 + }; + + /// @brief Drop a sequence of packets from the buffer. + /// If @a msgno is valid, sender has requested to drop the whole message by TTL. In this case it has to also provide a pkt seqno range. + /// However, if a message has been partially acknowledged and already removed from the SND buffer, + /// the @a seqnolo might specify some position in the middle of the message, not the very first packet. + /// If those packets have been acknowledged, they must exist in the receiver buffer unless already read. + /// In this case the @a msgno should be used to determine starting packets of the message. + /// Some packets of the message can be missing on the receiver, therefore the actual drop should still be performed by pkt seqno range. + /// If message number is 0 or SRT_MSGNO_NONE, then use sequence numbers to locate sequence range to drop [seqnolo, seqnohi]. + /// A SOLO message packet can be kept depending on @a actionOnExisting value. + /// TODO: A message in general can be kept if all of its packets are in the buffer, depending on @a actionOnExisting value. + /// This is done to avoid dropping existing packet when the sender was asked to re-transmit a packet from an outdated loss report, + /// which is already not available in the SND buffer. + /// @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) + /// @param actionOnExisting Should an exising SOLO packet be dropped from the buffer or preserved? + /// @return the number of packets actually dropped. + int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting); + + /// 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. + return capacity(); + } + + // 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. + /// A message is available for reading only if all of its packets are present in the buffer. + /// @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 (continuous) 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 acknowledged (allowed to be delivered to the app) + /// @param [out] skipseqno -1 or sequence number of 1st unacknowledged packet (after one or more missing packets) that is ready to play. + /// @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 preceded 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_iMaxPosOff == 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); } + inline int cmpPos(int pos2, int pos1) const + { + // XXX maybe not the best implementation, but this keeps up to the rule + const int off1 = pos1 >= m_iStartPos ? pos1 - m_iStartPos : pos1 + (int)m_szSize - m_iStartPos; + const int off2 = pos2 >= m_iStartPos ? pos2 - m_iStartPos : pos2 + (int)m_szSize - m_iStartPos; + + return off2 - off1; + } + + // NOTE: Assumes that pUnit != NULL + CPacket& packetAt(int pos) { return m_entries[pos].pUnit->m_Packet; } + const CPacket& packetAt(int pos) const { return m_entries[pos].pUnit->m_Packet; } + +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 makeUnitTaken 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_iMaxPosOff; // 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 agreed 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); + + bool isTsbPd() const { return m_tsbpd.isEnabled(); } + + /// 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 // INC_SRT_BUFFER_RCV_H diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer_snd.cpp b/trunk/3rdparty/srt-1-fit/srtcore/buffer_snd.cpp new file mode 100644 index 00000000000..26f885dd662 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_snd.cpp @@ -0,0 +1,730 @@ +/* + * 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 03/12/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#include "platform_sys.h" + +#include +#include "buffer_snd.h" +#include "packet.h" +#include "core.h" // provides some constants +#include "logging.h" + +namespace srt { + +using namespace std; +using namespace srt_logging; +using namespace sync; + +CSndBuffer::CSndBuffer(int size, int maxpld, int authtag) + : m_BufLock() + , m_pBlock(NULL) + , m_pFirstBlock(NULL) + , m_pCurrBlock(NULL) + , m_pLastBlock(NULL) + , m_pBuffer(NULL) + , m_iNextMsgNo(1) + , m_iSize(size) + , m_iBlockLen(maxpld) + , m_iAuthTagSize(authtag) + , m_iCount(0) + , m_iBytesCount(0) +{ + // 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; + + 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"); +} + +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; + } + + releaseMutex(m_BufLock); +} + +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 = getMaxPacketLen(); + const int iNumBlocks = countNumPacketsRequired(len, iPktLen); + + 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(bslog.Debug, log << "addBuffer: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers..."); + increase(); + } + + 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()); + + // 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; + + if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied + { + 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; + } + + 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 == 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. + // 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_iTTL = ttl; + 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; + + m_iCount += iNumBlocks; + m_iBytesCount += len; + + 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). + // If this value has been reached in a step of incrementation, it means that the + // 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. + + 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 = getMaxPacketLen(); + const int iNumBlocks = countNumPacketsRequired(len, iPktLen); + + HLOGC(bslog.Debug, + log << "addBufferFromFile: size=" << m_iCount << " reserved=" << m_iSize << " needs=" << iPktLen + << " buffers for " << len << " bytes"); + + // dynamically increase sender buffer + while (iNumBlocks + m_iCount >= m_iSize) + { + HLOGC(bslog.Debug, + log << "addBufferFromFile: ... still lacking " << (iNumBlocks + 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 < iNumBlocks; ++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 == iNumBlocks - 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 += iNumBlocks; + m_iBytesCount += total; + + leaveCS(m_BufLock); + + m_iNextMsgNo++; + if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) + m_iNextMsgNo = 1; + + return total; +} + +int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) +{ + 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, m_iBlockLen); + 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; +} + +CSndBuffer::time_point CSndBuffer::peekNextOriginal() const +{ + ScopedLock bufferguard(m_BufLock); + if (m_pCurrBlock == m_pLastBlock) + return time_point(); + + return m_pCurrBlock->m_tsOriginTime; +} + +int32_t CSndBuffer::getMsgNoAt(const int offset) +{ + ScopedLock bufferguard(m_BufLock); + + Block* p = m_pFirstBlock; + + if (p) + { + HLOGC(bslog.Debug, + log << "CSndBuffer::getMsgNoAt: FIRST MSG: size=" << p->m_iLength << " %" << p->m_iSeqNo << " #" + << p->getMsgSeq() << " !" << BufferStamp(p->m_pcData, p->m_iLength)); + } + + 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; + } + + // 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) + { + ee = p; + p = p->m_pNext; + } + + 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)); + + return p->getMsgSeq(); +} + +int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, int& w_msglen) +{ + 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, m_iBlockLen); + + // 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; +} + +sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) +{ + 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) +{ + 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; + + updAvgBufSize(steady_clock::now()); +} + +int CSndBuffer::getCurrBufSize() const +{ + return m_iCount; +} + +int CSndBuffer::getMaxPacketLen() const +{ + return m_iBlockLen - m_iAuthTagSize; +} + +int CSndBuffer::countNumPacketsRequired(int iPldLen) const +{ + const int iPktLen = getMaxPacketLen(); + return countNumPacketsRequired(iPldLen, iPktLen); +} + +int CSndBuffer::countNumPacketsRequired(int iPldLen, int iPktLen) const +{ + return (iPldLen + iPktLen - 1) / iPktLen; +} + +namespace { +int round_val(double val) +{ + return static_cast(round(val)); +} +} + +int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) +{ + ScopedLock bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ + + /* update stats in case there was no add/ack activity lately */ + 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(const steady_clock::time_point& 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); +} + +int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) const +{ + 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; + + return m_iCount; +} + +CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const +{ + 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& w_bytes, int32_t& w_first_msgno, const steady_clock::time_point& too_late_time) +{ + 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_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)"); +} + +} // namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer_snd.h b/trunk/3rdparty/srt-1-fit/srtcore/buffer_snd.h new file mode 100644 index 00000000000..4440b9bfd6a --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_snd.h @@ -0,0 +1,259 @@ +/* + * 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 - 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 05/05/2009 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef INC_SRT_BUFFER_SND_H +#define INC_SRT_BUFFER_SND_H + +#include "srt.h" +#include "packet.h" +#include "buffer_tools.h" + +// 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 { + +class CSndBuffer +{ + 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 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 ""; } + + /// @brief CSndBuffer constructor. + /// @param size initial number of blocks (each block to store one packet payload). + /// @param maxpld maximum packet payload (including auth tag). + /// @param authtag auth tag length in bytes (16 for GCM, 0 otherwise). + CSndBuffer(int size = 32, int maxpld = 1500, int authtag = 0); + ~CSndBuffer(); + +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) const; + + + /// Het maximum payload length per packet. + int getMaxPacketLen() const; + + /// @brief Count the number of required packets to store the payload (message). + /// @param iPldLen the length of the payload to check. + /// @return the number of required data packets. + int countNumPacketsRequired(int iPldLen) const; + + /// @brief Count the number of required packets to store the payload (message). + /// @param iPldLen the length of the payload to check. + /// @param iMaxPktLen the maximum payload length of the packet (the value returned from getMaxPacketLen()). + /// @return the number of required data packets. + int countNumPacketsRequired(int iPldLen, int iMaxPktLen) const; + + /// @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; } + +private: + void increase(); + +private: + mutable sync::Mutex m_BufLock; // used to synchronize buffer operation + + struct Block + { + char* m_pcData; // pointer to the data block + int m_iLength; // payload length of the block (excluding auth tag). + + 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) + + 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) + const int m_iBlockLen; // maximum length of a block holding packet payload and AUTH tag (excluding packet header). + const int m_iAuthTagSize; // Authentication tag size (if GCM is enabled). + int m_iCount; // number of used blocks + + int m_iBytesCount; // number of payload bytes in queue + time_point m_tsLastOriginTime; + + AvgBufSize m_mavg; + CRateEstimator m_rateEstimator; + +private: + CSndBuffer(const CSndBuffer&); + CSndBuffer& operator=(const CSndBuffer&); +}; + +} // namespace srt + +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer_tools.cpp b/trunk/3rdparty/srt-1-fit/srtcore/buffer_tools.cpp new file mode 100644 index 00000000000..0dcf2547fdc --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_tools.cpp @@ -0,0 +1,275 @@ +/* + * 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 03/12/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#include "platform_sys.h" +#include "buffer_tools.h" +#include "packet.h" +#include "logger_defs.h" +#include "utilities.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); +} + +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) + return; + + // 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); +} + +CSndRateEstimator::CSndRateEstimator(const time_point& tsNow) + : m_tsFirstSampleTime(tsNow) + , m_iFirstSampleIdx(0) + , m_iCurSampleIdx(0) + , m_iRateBps(0) +{ + +} + +void CSndRateEstimator::addSample(const time_point& ts, int pkts, size_t bytes) +{ + const int iSampleDeltaIdx = (int) count_milliseconds(ts - m_tsFirstSampleTime) / SAMPLE_DURATION_MS; + const int delta = NUM_PERIODS - iSampleDeltaIdx; + + // TODO: -delta <= NUM_PERIODS, then just reset the state on the estimator. + + if (iSampleDeltaIdx >= 2 * NUM_PERIODS) + { + // Just reset the estimator and start like if new. + for (int i = 0; i < NUM_PERIODS; ++i) + { + const int idx = incSampleIdx(m_iFirstSampleIdx, i); + m_Samples[idx].reset(); + + if (idx == m_iCurSampleIdx) + break; + } + + m_iFirstSampleIdx = 0; + m_iCurSampleIdx = 0; + m_iRateBps = 0; + m_tsFirstSampleTime += milliseconds_from(iSampleDeltaIdx * SAMPLE_DURATION_MS); + } + else if (iSampleDeltaIdx > NUM_PERIODS) + { + // In run-time a constant flow of samples is expected. Once all periods are filled (after 1 second of sampling), + // the iSampleDeltaIdx should be either (NUM_PERIODS - 1), + // or NUM_PERIODS. In the later case it means the start of a new sampling period. + int d = delta; + while (d < 0) + { + m_Samples[m_iFirstSampleIdx].reset(); + m_iFirstSampleIdx = incSampleIdx(m_iFirstSampleIdx); + m_tsFirstSampleTime += milliseconds_from(SAMPLE_DURATION_MS); + m_iCurSampleIdx = incSampleIdx(m_iCurSampleIdx); + ++d; + } + } + + // Check if the new sample period has started. + const int iNewDeltaIdx = (int) count_milliseconds(ts - m_tsFirstSampleTime) / SAMPLE_DURATION_MS; + if (incSampleIdx(m_iFirstSampleIdx, iNewDeltaIdx) != m_iCurSampleIdx) + { + // Now there should be some periods (at most last NUM_PERIODS) ready to be summed, + // rate estimation updated, after which all the new entry should be added. + Sample sum; + int iNumPeriods = 0; + bool bMetNonEmpty = false; + for (int i = 0; i < NUM_PERIODS; ++i) + { + const int idx = incSampleIdx(m_iFirstSampleIdx, i); + const Sample& s = m_Samples[idx]; + sum += s; + if (bMetNonEmpty || !s.empty()) + { + ++iNumPeriods; + bMetNonEmpty = true; + } + + if (idx == m_iCurSampleIdx) + break; + } + + if (iNumPeriods == 0) + { + m_iRateBps = 0; + } + else + { + m_iRateBps = sum.m_iBytesCount * 1000 / (iNumPeriods * SAMPLE_DURATION_MS); + } + + HLOGC(bslog.Note, + log << "CSndRateEstimator: new rate estimation :" << (m_iRateBps * 8) / 1000 << " kbps. Based on " + << iNumPeriods << " periods, " << sum.m_iPktsCount << " packets, " << sum.m_iBytesCount << " bytes."); + + // Shift one sampling period to start collecting the new one. + m_iCurSampleIdx = incSampleIdx(m_iCurSampleIdx); + m_Samples[m_iCurSampleIdx].reset(); + + // If all NUM_SAMPLES are recorded, the first position has to be shifted as well. + if (delta <= 0) + { + m_iFirstSampleIdx = incSampleIdx(m_iFirstSampleIdx); + m_tsFirstSampleTime += milliseconds_from(SAMPLE_DURATION_MS); + } + } + + m_Samples[m_iCurSampleIdx].m_iBytesCount += bytes; + m_Samples[m_iCurSampleIdx].m_iPktsCount += pkts; +} + +int CSndRateEstimator::getCurrentRate() const +{ + SRT_ASSERT(m_iCurSampleIdx >= 0 && m_iCurSampleIdx < NUM_PERIODS); + return (int) avg_iir<16, unsigned long long>(m_iRateBps, m_Samples[m_iCurSampleIdx].m_iBytesCount * 1000 / SAMPLE_DURATION_MS); +} + +int CSndRateEstimator::incSampleIdx(int val, int inc) const +{ + SRT_ASSERT(inc >= 0 && inc <= NUM_PERIODS); + val += inc; + while (val >= NUM_PERIODS) + val -= NUM_PERIODS; + return val; +} + +} + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/buffer_tools.h b/trunk/3rdparty/srt-1-fit/srtcore/buffer_tools.h new file mode 100644 index 00000000000..e6ce89d0de7 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/buffer_tools.h @@ -0,0 +1,201 @@ +/* + * 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 - 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 05/05/2009 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef INC_SRT_BUFFER_TOOLS_H +#define INC_SRT_BUFFER_TOOLS_H + +#include "common.h" + +namespace srt +{ + +/// The AvgBufSize class is used to calculate moving average of the buffer (RCV or SND) +class AvgBufSize +{ + typedef sync::steady_clock::time_point time_point; + +public: + AvgBufSize() + : m_dBytesCountMAvg(0.0) + , m_dCountMAvg(0.0) + , m_dTimespanMAvg(0.0) + { + } + +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; } + +private: + time_point m_tsLastSamplingTime; + double m_dBytesCountMAvg; + double m_dCountMAvg; + double m_dTimespanMAvg; +}; + +/// 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(); + +public: + uint64_t getInRatePeriod() const { return m_InRatePeriod; } + + /// Retrieve input bitrate in bytes per second + int getInputRate() const { return m_iInRateBps; } + + void setInputRateSmpPeriod(int period); + + /// Update input rate calculation. + /// @param [in] time current time + /// @param [in] pkts number of packets newly added to the buffer + /// @param [in] bytes number of payload bytes in those newly added packets + void updateInputRate(const time_point& time, int pkts = 0, int bytes = 0); + + void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); } + +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; + +private: + int m_iInRatePktsCount; // number of payload packets 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 +}; + + +class CSndRateEstimator +{ + typedef sync::steady_clock::time_point time_point; + +public: + CSndRateEstimator(const time_point& tsNow); + + /// Add sample. + /// @param [in] time sample (sending) time. + /// @param [in] pkts number of packets in the sample. + /// @param [in] bytes number of payload bytes in the sample. + void addSample(const time_point& time, int pkts = 0, size_t bytes = 0); + + /// Retrieve estimated bitrate in bytes per second + int getRate() const { return m_iRateBps; } + + /// Retrieve estimated bitrate in bytes per second inluding the current sampling interval. + int getCurrentRate() const; + +private: + static const int NUM_PERIODS = 10; + static const int SAMPLE_DURATION_MS = 100; // 100 ms + struct Sample + { + int m_iPktsCount; // number of payload packets + int m_iBytesCount; // number of payload bytes + + void reset() + { + m_iPktsCount = 0; + m_iBytesCount = 0; + } + + Sample() + : m_iPktsCount(0) + , m_iBytesCount(0) + { + } + + Sample(int iPkts, int iBytes) + : m_iPktsCount(iPkts) + , m_iBytesCount(iBytes) + { + } + + Sample operator+(const Sample& other) + { + return Sample(m_iPktsCount + other.m_iPktsCount, m_iBytesCount + other.m_iBytesCount); + } + + Sample& operator+=(const Sample& other) + { + *this = *this + other; + return *this; + } + + bool empty() const { return m_iPktsCount == 0; } + }; + + int incSampleIdx(int val, int inc = 1) const; + + Sample m_Samples[NUM_PERIODS]; + + time_point m_tsFirstSampleTime; //< Start time of the first sameple. + int m_iFirstSampleIdx; //< Index of the first sample. + int m_iCurSampleIdx; //< Index of the current sample being collected. + int m_iRateBps; // Input Rate in Bytes/sec +}; + +} // namespace srt + +#endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/cache.cpp b/trunk/3rdparty/srt-1-fit/srtcore/cache.cpp index fdde5998fdd..4cda9a70f3c 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) const { 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 346f9d81308..47633706a54 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) const; + 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 dad6fd8dbfa..091adf11596 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,397 +50,629 @@ 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; + + 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; -CChannel::CChannel(int version): -m_iIPversion(version), -m_iSocket(), -#ifdef SRT_ENABLE_IPOPTS -m_iIpTTL(-1), -m_iIpToS(-1), + 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) +#ifdef SRT_ENABLE_PKTINFO + , m_bBindMasked(true) #endif -m_iSndBufSize(65536), -m_iRcvBufSize(65536), -m_iIpV6Only(-1), -m_BindAddr(version) { - SRT_ASSERT(version == AF_INET || version == AF_INET6); - m_iSockAddrSize = (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); +#ifdef SRT_ENABLE_PKTINFO + // Do the check for ancillary data buffer size, kinda assertion + static const size_t CMSG_MAX_SPACE = sizeof (CMSGNodeIPv4) + sizeof (CMSGNodeIPv6); + + if (CMSG_MAX_SPACE < CMSG_SPACE(sizeof(in_pktinfo)) + CMSG_SPACE(sizeof(in6_pktinfo))) + { + LOGC(kmlog.Fatal, log << "Size of CMSG_MAX_SPACE=" + << CMSG_MAX_SPACE << " too short for cmsg " + << CMSG_SPACE(sizeof(in_pktinfo)) << ", " + << CMSG_SPACE(sizeof(in6_pktinfo)) << " - PLEASE FIX"); + throw CUDTException(MJ_SETUP, MN_NONE, 0); + } +#endif } -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); + createSocket(addr.family()); + socklen_t namelen = addr.size(); + + if (::bind(m_iSocket, &addr.sa, namelen) == -1) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - #ifdef _WIN32 - if (INVALID_SOCKET == m_iSocket) - #else - if (m_iSocket < 0) - #endif - throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); + m_BindAddr = addr; +#ifdef SRT_ENABLE_PKTINFO + m_bBindMasked = m_BindAddr.isany(); +#endif + LOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str()); - 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)); + setUDPSockOpt(); +} - if (NULL != addr) - { - socklen_t namelen = m_iSockAddrSize; +void srt::CChannel::open(int family) +{ + createSocket(family); - 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; + // sendto or WSASendTo will also automatically bind the socket + addrinfo hints; + addrinfo* res; + + memset(&hints, 0, sizeof(struct addrinfo)); - memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_PASSIVE; - hints.ai_family = m_iIPversion; - 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); + } - 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); + } + m_BindAddr = sockaddr_any(res->ai_addr, (sockaddr_any::len_t)res->ai_addrlen); - // 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; +#ifdef SRT_ENABLE_PKTINFO + // We know that this is intentionally bound now to "any", + // so the requester-destination address must be remembered and passed. + m_bBindMasked = true; +#endif - ::freeaddrinfo(res); - } + ::freeaddrinfo(res); - HLOGC(mglog.Debug, log << "CHANNEL: Bound to local address: " << SockaddrToString(&m_BindAddr)); + HLOGC(kmlog.Debug, log << "CHANNEL: Bound to local address: " << m_BindAddr.str()); - setUDPSockOpt(); + setUDPSockOpt(); } -void CChannel::attach(UDPSOCKET udpsock) +void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr) { - m_iSocket = udpsock; - setUDPSockOpt(); + // The getsockname() call is done before calling it and the + // result is placed into udpsocks_addr. + m_iSocket = udpsock; + m_BindAddr = udpsocks_addr; + setUDPSockOpt(); } -void CChannel::setUDPSockOpt() +void srt::CChannel::setUDPSockOpt() { - #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. +#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; + } -#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); - } - } + // 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 -{ - #ifndef _WIN32 - ::close(m_iSocket); - #else - ::closesocket(m_iSocket); - #endif +#ifdef SRT_ENABLE_PKTINFO + if (m_bBindMasked) + { + HLOGP(kmlog.Debug, "Socket bound to ANY - setting PKTINFO for address retrieval"); + const int on = 1, off SRT_ATR_UNUSED = 0; + + if (m_BindAddr.family() == AF_INET || m_mcfg.iIpV6Only == 0) + { + ::setsockopt(m_iSocket, IPPROTO_IP, IP_PKTINFO, (char*)&on, sizeof(on)); + } + + if (m_BindAddr.family() == AF_INET6) + { + ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)); + } + + // XXX Unknown why this has to be off. RETEST. + //::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)); + } +#endif } -int CChannel::getSndBufSize() +void srt::CChannel::close() const { - socklen_t size = sizeof(socklen_t); - ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char *)&m_iSndBufSize, &size); - return m_iSndBufSize; +#ifndef _WIN32 + ::close(m_iSocket); +#else + ::closesocket(m_iSocket); +#endif } -int CChannel::getRcvBufSize() +int srt::CChannel::getSndBufSize() { - 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.iUDPSndBufSize; + ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_mcfg.iUDPSndBufSize, &size); + return m_mcfg.iUDPSndBufSize; } -void CChannel::setSndBufSize(int size) +int srt::CChannel::getRcvBufSize() { - m_iSndBufSize = size; + 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::setRcvBufSize(int size) +void srt::CChannel::setConfig(const CSrtMuxerConfig& config) { - m_iRcvBufSize = size; + m_mcfg = config; } -void CChannel::setIpV6Only(int ipV6Only) +void srt::CChannel::getSocketOption(int level, int option, char* pw_dataptr, socklen_t& w_len, int& w_status) { - m_iIpV6Only = ipV6Only; + w_status = ::getsockopt(m_iSocket, level, option, (pw_dataptr), (&w_len)); } -#ifdef SRT_ENABLE_IPOPTS -int CChannel::getIpTTL() const +int srt::CChannel::getIpTTL() 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); + + 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; } -int CChannel::getIpToS() const +int srt::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 - { + if (m_iSocket == INVALID_SOCKET) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + 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 sockaddr_any& source_addr SRT_ATR_UNUSED) 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()); - } + ostringstream dsrc; +#ifdef SRT_ENABLE_PKTINFO + dsrc << " sourceIP=" << (m_bBindMasked && !source_addr.isany() ? source_addr.str() : "default"); +#endif - 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()); + LOGC(kslog.Debug, + log << "CChannel::sendto: SENDING NOW DST=" << addr.str() << " target=@" << packet.m_iID + << " size=" << packet.getLength() << " pkt.ts=" << packet.m_iTimeStamp + << dsrc.str() << " " << packet.Info()); #endif #ifdef SRT_TEST_FAKE_LOSS @@ -451,18 +683,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 +702,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,96 +733,122 @@ 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; - } + // convert control information into network order + packet.toNL(); - #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; - } +#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; + bool have_set_src = false; + +#ifdef SRT_ENABLE_PKTINFO + + // Note that even if PKTINFO is desired, the first caller's packet will be sent + // without ancillary info anyway because there's no "peer" yet to know where to send it. + if (m_bBindMasked && source_addr.family() != AF_UNSPEC && !source_addr.isany()) + { + if (!setSourceAddress(mh, source_addr)) + { + LOGC(kslog.Error, log << "CChannel::setSourceAddress: source address invalid family #" << source_addr.family() << ", NOT setting."); + } + else + { + HLOGC(kslog.Debug, log << "CChannel::setSourceAddress: setting as " << source_addr.str()); + have_set_src = true; + } + } - 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)); - } +#endif + + if (!have_set_src) + { + mh.msg_control = NULL; + mh.msg_controllen = 0; + } + mh.msg_flags = 0; + + const int res = (int)::sendmsg(m_iSocket, &mh, 0); +#else + DWORD size = (DWORD)(CPacket::HDR_SIZE + packet.getLength()); + int addrsize = addr.size(); + WSAOVERLAPPED overlapped; + SecureZeroMemory((PVOID)&overlapped, sizeof(WSAOVERLAPPED)); + int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, &overlapped, NULL); + + if (res == SOCKET_ERROR && NET_ERROR == WSA_IO_PENDING) + { + DWORD dwFlags = 0; + const bool bCompleted = WSAGetOverlappedResult(m_iSocket, &overlapped, &size, true, &dwFlags); + WSACloseEvent(overlapped.hEvent); + res = bCompleted ? 0 : -1; + } + + res = (0 == res) ? size : -1; +#endif + + packet.toHL(); - return res; + 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; } #ifndef _WIN32 + msghdr mh; // will not be used on failure + 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; + + // Default + mh.msg_control = NULL; mh.msg_controllen = 0; - mh.msg_flags = 0; - recv_size = ::recvmsg(m_iSocket, &mh, 0); +#ifdef SRT_ENABLE_PKTINFO + // Without m_bBindMasked, we don't need ancillary data - the source + // address will always be the bound address. + if (m_bBindMasked) + { + // Extract the destination IP address from the ancillary + // data. This might be interesting for the connection to + // know to which address the packet should be sent back during + // the handshake and then addressed when sending during connection. + mh.msg_control = (m_acCmsgRecvBuffer); + mh.msg_controllen = sizeof m_acCmsgRecvBuffer; + } +#endif + + mh.msg_flags = 0; + + recv_size = (int)::recvmsg(m_iSocket, (&mh), 0); msg_flags = mh.msg_flags; } @@ -621,19 +877,31 @@ 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; } goto Return_error; } +#ifdef SRT_ENABLE_PKTINFO + if (m_bBindMasked) + { + // Extract the address. Set it explicitly; if this returns address that isany(), + // it will simply set this on the packet so that it behaves as if nothing was + // extracted (it will "fail the old way"). + w_packet.m_DestAddr = getTargetAddress(mh); + HLOGC(krlog.Debug, log << CONID() << "(sys)recvmsg: ANY BOUND, retrieved DEST ADDR: " << w_packet.m_DestAddr.str()); + } +#endif + #else // XXX REFACTORING NEEDED! // This procedure uses the WSARecvFrom function that just reads @@ -651,15 +919,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 +950,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 +971,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 +997,60 @@ 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) << ")"); +#if ENABLE_HEAVY_LOGGING + + std::ostringstream flg; + +#if !defined(_WIN32) + + static const pair errmsgflg [] = { + make_pair(MSG_OOB, "OOB"), + make_pair(MSG_EOR, "EOR"), + make_pair(MSG_TRUNC, "TRUNC"), + make_pair(MSG_CTRUNC, "CTRUNC") + }; + + for (size_t i = 0; i < Size(errmsgflg); ++i) + if ((msg_flags & errmsgflg[i].first) != 0) + flg << " " << errmsgflg[i].second; + + // This doesn't work the same way on Windows, so on Windows just skip it. +#endif + + HLOGC(krlog.Debug, + log << CONID() << "NET ERROR: packet size=" << recv_size << " msg_flags=0x" << hex << msg_flags + << ", detected flags:" << flg.str()); +#endif 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 0efdcaa8fef..1bfcc47c85c 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,249 @@ 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); +class CChannel +{ + void createSocket(int family); - /// Open a UDP channel based on an existing UDP socket. - /// @param [in] udpsock UDP socket descriptor. +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 ""; } - void attach(UDPSOCKET udpsock); + CChannel(); + ~CChannel(); - /// Disconnect and close the UDP entity. + /// Open a UDP channel. + /// @param [in] addr The local address that UDP will use. - void close() const; + void open(const sockaddr_any& addr); - /// Get the UDP sending buffer size. - /// @return Current UDP sending buffer size. + void open(int family); - int getSndBufSize(); + /// Open a UDP channel based on an existing UDP socket. + /// @param [in] udpsock UDP socket descriptor. - /// Get the UDP receiving buffer size. - /// @return Current UDP receiving buffer size. + void attach(UDPSOCKET udpsock, const sockaddr_any& adr); - int getRcvBufSize(); + /// Disconnect and close the UDP entity. - /// Set the UDP sending buffer size. - /// @param [in] size expected UDP sending buffer size. + void close() const; - void setSndBufSize(int size); + /// Get the UDP sending buffer size. + /// @return Current UDP sending buffer size. - /// Set the UDP receiving buffer size. - /// @param [in] size expected UDP receiving buffer size. + int getSndBufSize(); - void setRcvBufSize(int size); + /// Get the UDP receiving buffer size. + /// @return Current UDP receiving buffer size. - /// Set the IPV6ONLY option. - /// @param [in] IPV6ONLY value. + int getRcvBufSize(); - void setIpV6Only(int ipV6Only); + /// Query the socket address that the channel is using. + /// @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 getSockAddr(sockaddr_any& addr) const; - void getSockAddr(sockaddr* addr) const; + /// Query the peer side socket address that the channel is connect to. + /// @param [out] addr pointer to store the returned socket address. - /// Query the peer side socket address that the channel is connect to. - /// @param [out] addr pointer to store the returned socket address. + void getPeerAddr(sockaddr_any& addr) const; - void getPeerAddr(sockaddr* 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. + /// @param [in] src source address to sent on an outgoing packet (if not ANY) + /// @return Actual size of data sent. - /// 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. + int sendto(const sockaddr_any& addr, srt::CPacket& packet, const sockaddr_any& src) const; - int sendto(const sockaddr* addr, CPacket& packet) 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. - /// 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. + EReadStatus recvfrom(sockaddr_any& addr, srt::CPacket& packet) const; - EReadStatus recvfrom(sockaddr* addr, CPacket& packet) const; + void setConfig(const CSrtMuxerConfig& config); -#ifdef SRT_ENABLE_IPOPTS - /// Set the IP TTL. - /// @param [in] ttl IP Time To Live. - /// @return none. + void getSocketOption(int level, int sockoptname, char* pw_dataptr, socklen_t& w_len, int& w_status); - void setIpTTL(int ttl); + template + Type sockopt(int level, int sockoptname, Type deflt) + { + Type retval; + socklen_t socklen = sizeof retval; + int status; + getSocketOption(level, sockoptname, ((char*)&retval), (socklen), (status)); + if (status == -1) + return deflt; - /// Set the IP Type of Service. - /// @param [in] tos IP Type of Service. + return retval; + } - 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 + + // 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; + + // This feature is not enabled on Windows, for now. + // This is also turned off in case of MinGW +#ifdef SRT_ENABLE_PKTINFO + bool m_bBindMasked; // True if m_BindAddr is INADDR_ANY. Need for quick check. + + // Calculating the required space is extremely tricky, and whereas on most + // platforms it's possible to define it this way: + // + // size_t s = max( CMSG_SPACE(sizeof(in_pktinfo)), CMSG_SPACE(sizeof(in6_pktinfo)) ) + // + // ...on some platforms however CMSG_SPACE macro can't be resolved as constexpr. + // + // This structure is exclusively used to determine the required size for + // CMSG buffer so that it can be allocated in a solid block with CChannel. + // NOT TO BE USED to access any data inside the CMSG message. + struct CMSGNodeIPv4 + { + in_pktinfo in4; + size_t extrafill; + cmsghdr hdr; + }; + + struct CMSGNodeIPv6 + { + in6_pktinfo in6; + size_t extrafill; + cmsghdr hdr; + }; + + // This is 'mutable' because it's a utility buffer defined here + // to avoid unnecessary re-allocations. + mutable char m_acCmsgRecvBuffer [sizeof (CMSGNodeIPv4) + sizeof (CMSGNodeIPv6)]; // Reserved space for ancillary data with pktinfo + mutable char m_acCmsgSendBuffer [sizeof (CMSGNodeIPv4) + sizeof (CMSGNodeIPv6)]; // Reserved space for ancillary data with pktinfo + + // IMPORTANT!!! This function shall be called EXCLUSIVELY just after + // calling ::recvmsg function. It uses a static buffer to supply data + // for the call, and it's stated that only one thread is trying to + // use a CChannel object in receiving mode. + sockaddr_any getTargetAddress(const msghdr& msg) const + { + // Loop through IP header messages + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(((msghdr*)&msg), cmsg)) + { + // This should be safe - this packet contains always either + // IPv4 headers or IPv6 headers. + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) + { + in_pktinfo *dest_ip_ptr = (in_pktinfo*)CMSG_DATA(cmsg); + return sockaddr_any(dest_ip_ptr->ipi_addr, 0); + } + + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) + { + in6_pktinfo* dest_ip_ptr = (in6_pktinfo*)CMSG_DATA(cmsg); + return sockaddr_any(dest_ip_ptr->ipi6_addr, 0); + } + } + + // Fallback for an error + return sockaddr_any(m_BindAddr.family()); + } + + // IMPORTANT!!! This function shall be called EXCLUSIVELY just before + // calling ::sendmsg function. It uses a static buffer to supply data + // for the call, and it's stated that only one thread is trying to + // use a CChannel object in sending mode. + bool setSourceAddress(msghdr& mh, const sockaddr_any& adr) const + { + // In contrast to an advice followed on the net, there's no case of putting + // both IPv4 and IPv6 ancillary data, case we could have them. Only one + // IP version is used and it's the version as found in @a adr, which should + // be the version used for binding. + + if (adr.family() == AF_INET) + { + mh.msg_control = m_acCmsgSendBuffer; + mh.msg_controllen = CMSG_SPACE(sizeof(in_pktinfo)); + cmsghdr* cmsg_send = CMSG_FIRSTHDR(&mh); + + // after initializing msghdr & control data to CMSG_SPACE(sizeof(struct in_pktinfo)) + cmsg_send->cmsg_level = IPPROTO_IP; + cmsg_send->cmsg_type = IP_PKTINFO; + cmsg_send->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + in_pktinfo* pktinfo = (in_pktinfo*) CMSG_DATA(cmsg_send); + pktinfo->ipi_ifindex = 0; + pktinfo->ipi_spec_dst = adr.sin.sin_addr; + + return true; + } + + if (adr.family() == AF_INET6) + { + mh.msg_control = m_acCmsgSendBuffer; + mh.msg_controllen = CMSG_SPACE(sizeof(in6_pktinfo)); + cmsghdr* cmsg_send = CMSG_FIRSTHDR(&mh); + + cmsg_send->cmsg_level = IPPROTO_IPV6; + cmsg_send->cmsg_type = IPV6_PKTINFO; + cmsg_send->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + in6_pktinfo* pktinfo = (in6_pktinfo*) CMSG_DATA(cmsg_send); + pktinfo->ipi6_ifindex = 0; + pktinfo->ipi6_addr = adr.sin6.sin6_addr; + + return true; + } + + return false; + } + +#endif // SRT_ENABLE_PKTINFO - 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; }; +} // namespace srt #endif diff --git a/trunk/3rdparty/srt-1-fit/srtcore/common.cpp b/trunk/3rdparty/srt-1-fit/srtcore/common.cpp index 5614de4afc6..b621c8025ea 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() +const char* srt::CUDTException::getErrorMessage() const ATR_NOTHROW { + return strerror_get_message(m_iMajor, m_iMinor); } -CUDTException::~CUDTException() +std::string srt::CUDTException::getErrorString() const { -} - -const char* CUDTException::getErrorMessage() -{ - // 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,150 @@ 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; - 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]; - } + 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 + { + std::memcpy(ip, addr.sin6.sin6_addr.s6_addr, 16); + } } -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. + std::memcpy(a->sin6_addr.s6_addr, ip, 16); + 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 +352,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 +388,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 +431,8 @@ std::string TransmissionEventStr(ETransmissionEvent ev) "checktimer", "send", "receive", - "custom" + "custom", + "sync" }; size_t vals_size = Size(vals); @@ -875,58 +442,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(const 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 +549,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 +646,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 95d9161f097..5021fa5a884 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/common.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/common.h @@ -50,12 +50,12 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_COMMON_H__ -#define __UDT_COMMON_H__ - -#define _CRT_SECURE_NO_WARNINGS 1 // silences windows complaints for sscanf +#ifndef INC_SRT_COMMON_H +#define INC_SRT_COMMON_H +#include #include +#include #ifndef _WIN32 #include #include @@ -63,10 +63,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 +84,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 +278,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 +294,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 +333,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; + } - template - EventVariant(T arg) + explicit EventVariant(ECheckTimerStage arg) { - *this = arg; + type = STAGE; + u.stage = arg; + } + + explicit EventVariant(EInitEvent arg) + { + type = INIT; + u.init = arg; } const int32_t* get_ptr() const @@ -257,25 +381,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 +438,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 +524,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 +553,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); + int32_t value; - 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 - - 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 +634,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 +698,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 +724,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 +874,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 +985,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) { @@ -826,14 +1392,17 @@ inline ATR_CONSTEXPR uint32_t SrtVersion(int major, int minor, int patch) inline int32_t SrtParseVersion(const char* v) { int major, minor, patch; +#if defined(_MSC_VER) + int result = sscanf_s(v, "%d.%d.%d", &major, &minor, &patch); +#else int result = sscanf(v, "%d.%d.%d", &major, &minor, &patch); - +#endif if (result != 3) { return 0; } - return major*0x10000 + minor*0x100 + patch; + return SrtVersion(major, minor, patch); } inline std::string SrtVersionString(int version) @@ -842,9 +1411,17 @@ inline std::string SrtVersionString(int version) int minor = (version/0x100)%0x100; int major = version/0x10000; - char buf[20]; - sprintf(buf, "%d.%d.%d", major, minor, patch); + char buf[22]; +#if defined(_MSC_VER) && _MSC_VER < 1900 + _snprintf(buf, sizeof(buf) - 1, "%d.%d.%d", major, minor, patch); +#else + snprintf(buf, sizeof(buf), "%d.%d.%d", major, minor, patch); +#endif return buf; } +bool SrtParseConfig(const 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 dde90ac27e5..91c73d66092 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: @@ -225,7 +230,7 @@ class LiveCC: public SrtCongestionControlBase * For realtime Transport Stream content, pkts/sec is not a good indication of time to transmit * since packets are not filled to m_iMSS and packet size average is lower than (7*188) * for low bit rates. - * If NAK report is lost, another cycle (RTT) is requred which is bad for low latency so we + * If NAK report is lost, another cycle (RTT) is required which is bad for low latency so we * accelerate the NAK Reports frequency, at the cost of possible duplicate resend. * Finally, the UDT4 native minimum NAK interval (m_ullMinNakInt_tk) is 300 ms which is too high * (~10 i30 video frames) to maintain low latency. @@ -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 0e1729f0f22..a2264b59486 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 { @@ -50,18 +52,29 @@ class SrtCongestion struct IsName { - std::string n; - IsName(std::string nn): n(nn) {} + const std::string n; + IsName(const std::string& nn): n(nn) {} 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 edadafaaa43..084cad670f4 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/core.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/core.cpp @@ -50,25 +50,31 @@ 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 "access_control.h" // Required for SRT_REJX_FALLBACK #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 +85,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 +119,163 @@ 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; +#ifdef ENABLE_AEAD_API_PREVIEW + flags[SRTO_CRYPTOMODE] = SRTO_R_PRE; +#endif + + // 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,947 +283,411 @@ 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_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) +#ifdef ENABLE_MAXREXMITBW + , m_SndRexmitRate(sync::steady_clock::now()) +#endif + , m_iISN(-1) + , m_iPeerISN(-1) { 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) +#ifdef ENABLE_MAXREXMITBW + , m_SndRexmitRate(sync::steady_clock::now()) +#endif + , m_iISN(-1) + , m_iPeerISN(-1) { 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_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) + { + try + { + // 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()); + } + catch (...) + { + LOGC(gglog.Error, log << "IPE: failed to set a declared default option!"); + } + } + } - m_uKmRefreshRatePkt = ancestor.m_uKmRefreshRatePkt; - m_uKmPreAnnouncePkt = ancestor.m_uKmPreAnnouncePkt; + 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 + // Runtime 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() +srt::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) +void srt::CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) { - if (optlen == sizeof(bool)) + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 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); + + // Restriction check + const int oflags = s_sockopt_action.flags[optName]; + + ScopedLock cg (m_ConnectionLock); + ScopedLock sendguard (m_SendLock); + ScopedLock recvguard (m_RecvLock); + + HLOGC(aclog.Debug, + log << CONID() << "OPTION: #" << optName << " value:" << FormatBinaryString((uint8_t*)optval, optlen)); + + if (IsSet(oflags, SRTO_R_PREBIND) && m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + + if (IsSet(oflags, SRTO_R_PRE) && (m_bConnected || m_bConnecting || m_bListening)) + 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) { - return *(bool *)optval; + LOGC(aclog.Error, log << CONID() << "OPTION: #" << optName << " UNKNOWN"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - if (optlen == sizeof(int)) + // Post-action, if applicable + if (IsSet(oflags, SRTO_POST_SPEC) && m_bConnected) { - return 0 != *(int *)optval; // 0!= is a windows warning-killer int-to-bool conversion + switch (optName) + { + case SRTO_MAXBW: + updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET)); + break; + + case SRTO_INPUTBW: + case SRTO_MININPUTBW: + updateCC(TEV_INIT, EventVariant(TEV_INIT_INPUTBW)); + break; + + case SRTO_OHEADBW: + updateCC(TEV_INIT, EventVariant(TEV_INIT_OHEADBW)); + break; + + case SRTO_LOSSMAXTTL: + m_iReorderTolerance = m_config.iMaxReorderTolerance; + + default: break; + } } - return false; } -void CUDT::setOpt(SRT_SOCKOPT optName, const void *optval, int optlen) +void srt::CUDT::getOpt(SRT_SOCKOPT optName, 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); + ScopedLock cg(m_ConnectionLock); 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; - + *(int *)optval = m_config.iMSS; + optlen = sizeof(int); break; case SRTO_SNDSYN: - m_bSynSending = bool_int_value(optval, optlen); + *(bool *)optval = m_config.bSynSending; + optlen = sizeof(bool); break; case SRTO_RCVSYN: - m_bSynRecving = bool_int_value(optval, optlen); + *(bool *)optval = m_config.bSynRecving; + optlen = sizeof(bool); 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; + case SRTO_ISN: + *(int *)optval = m_iISN; + optlen = sizeof(int); + break; + case SRTO_FC: + *(int *)optval = m_config.iFlightFlagSize; + optlen = sizeof(int); 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); - + *(int *)optval = m_config.iSndBufSize * (m_config.iMSS - CPacket::UDP_HDR_SIZE); + optlen = sizeof(int); 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; - } - + *(int *)optval = m_config.iRcvBufSize * (m_config.iMSS - CPacket::UDP_HDR_SIZE); + optlen = sizeof(int); break; case SRTO_LINGER: - m_Linger = *(linger *)optval; + if (optlen < (int)(sizeof(linger))) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + *(linger *)optval = m_config.Linger; + optlen = sizeof(linger); 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; - + *(int *)optval = m_config.iUDPSndBufSize; + optlen = sizeof(int); 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; - + *(int *)optval = m_config.iUDPRcvBufSize; + optlen = sizeof(int); break; case SRTO_RENDEZVOUS: - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_bRendezvous = bool_int_value(optval, optlen); + *(bool *)optval = m_config.bRendezvous; + optlen = sizeof(bool); break; case SRTO_SNDTIMEO: - m_iSndTimeOut = *(int *)optval; + *(int *)optval = m_config.iSndTimeOut; + optlen = sizeof(int); break; case SRTO_RCVTIMEO: - m_iRcvTimeOut = *(int *)optval; + *(int *)optval = m_config.iRcvTimeOut; + optlen = sizeof(int); break; case SRTO_REUSEADDR: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_bReuseAddr = bool_int_value(optval, optlen); + *(bool *)optval = m_config.bReuseAddr; + optlen = sizeof(bool); break; case SRTO_MAXBW: - m_llMaxBW = *(int64_t *)optval; + 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; - // 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); + 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 = uglobal().getStatus(m_SocketID); + optlen = sizeof(int32_t); + break; + + case SRTO_EVENT: + { + int32_t event = 0; + if (m_bBroken) + event |= SRT_EPOLL_ERR; + else + { + 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); + break; + } + + case SRTO_SNDDATA: + if (m_pSndBuffer) + *(int32_t *)optval = m_pSndBuffer->getCurrBufSize(); + else + *(int32_t *)optval = 0; + optlen = sizeof(int32_t); + break; + + case SRTO_RCVDATA: + if (m_pRcvBuffer) + { + enterCS(m_RecvLock); + *(int32_t *)optval = m_pRcvBuffer->getRcvDataSize(); + leaveCS(m_RecvLock); + } + else + *(int32_t *)optval = 0; + optlen = sizeof(int32_t); 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; + *(int32_t *)optval = m_pSndQueue->getIpTTL(); + else + *(int32_t *)optval = m_config.iIpTTL; + optlen = sizeof(int32_t); 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); + *(int32_t *)optval = m_pSndQueue->getIpToS(); + else + *(int32_t *)optval = m_config.iIpToS; + optlen = sizeof(int32_t); break; - case SRTO_OHEADBW: - if ((*(int *)optval < 5) || (*(int *)optval > 100)) + case SRTO_BINDTODEVICE: +#ifdef SRT_ENABLE_BINDTODEVICE + if (optlen < IFNAMSIZ) 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); + if (m_bOpened && m_pSndQueue->getBind(((char*)optval), optlen)) + { + optlen = strlen((char*)optval); + break; + } + + // Fallback: return from internal data + optlen = (int)m_config.sBindToDevice.copy((char*)optval, (size_t)optlen - 1); + ((char*)optval)[optlen] = '\0'; +#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: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bDataSender = bool_int_value(optval, optlen); + *(bool *)optval = m_config.bDataSender; + optlen = sizeof(bool); break; case SRTO_TSBPDMODE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bOPT_TsbPd = bool_int_value(optval, optlen); + *(bool *)optval = m_config.bTSBPD; + optlen = sizeof(bool); 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; + *(int32_t *)optval = m_iTsbPdDelay_ms; + else + *(int32_t *)optval = m_config.iRcvLatency; + optlen = sizeof(int32_t); break; case SRTO_PEERLATENCY: if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iOPT_PeerTsbPdDelay = *(int *)optval; + *(int32_t *)optval = m_iPeerTsbPdDelay_ms; + else + *(int32_t *)optval = m_config.iPeerLatency; + + optlen = sizeof(int32_t); break; case SRTO_TLPKTDROP: if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bOPT_TLPktDrop = bool_int_value(optval, optlen); + *(bool *)optval = m_bTLPktDrop; + else + *(bool *)optval = m_config.bTLPktDrop; + + optlen = sizeof(bool); 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; + *(int32_t *)optval = m_config.iSndDropDelay; + optlen = sizeof(int32_t); 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); - } - - if (m_OPT_PktFilterConfigString != "") - { - // 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)) - { - // Break silently. This should not happen - LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; - if (m_zOPT_ExpPayloadSize > efc_max_payload_size) - { - 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); - } - } - - 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; - - 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; - - default: - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } - break; - - case SRTO_KMREFRESHRATE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - // 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; - - 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); - } - - m_uKmPreAnnouncePkt = val; - } - break; - - case SRTO_ENFORCEDENCRYPTION: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - m_bOPT_StrictEncryption = bool_int_value(optval, optlen); - break; - - case SRTO_PEERIDLETIMEO: - - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iOPT_PeerIdleTimeout = *(int *)optval; - break; - - case SRTO_IPV6ONLY: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - m_iIpV6Only = *(int *)optval; - break; - - case SRTO_PACKETFILTER: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - - { - 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); - } - - 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; - } - - m_OPT_PktFilterConfigString = arg; - } - break; - - default: - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - } -} - -void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) -{ - CGuard cg(m_ConnectionLock); - - switch (optName) - { - case SRTO_MSS: - *(int *)optval = m_iMSS; - optlen = sizeof(int); - break; - - case SRTO_SNDSYN: - *(bool *)optval = m_bSynSending; - optlen = sizeof(bool); - break; - - case SRTO_RCVSYN: - *(bool *)optval = m_bSynRecving; - optlen = sizeof(bool); - break; - - case SRTO_ISN: - *(int *)optval = m_iISN; - optlen = sizeof(int); - break; - - case SRTO_FC: - *(int *)optval = m_iFlightFlagSize; - optlen = sizeof(int); - break; - - case SRTO_SNDBUF: - *(int *)optval = m_iSndBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE); - optlen = sizeof(int); - break; - - case SRTO_RCVBUF: - *(int *)optval = m_iRcvBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE); - optlen = sizeof(int); - break; - - case SRTO_LINGER: - if (optlen < (int)(sizeof(linger))) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - *(linger *)optval = m_Linger; - optlen = sizeof(linger); - break; - - case SRTO_UDP_SNDBUF: - *(int *)optval = m_iUDPSndBufSize; - optlen = sizeof(int); - break; - - case SRTO_UDP_RCVBUF: - *(int *)optval = m_iUDPRcvBufSize; - optlen = sizeof(int); - break; - - case SRTO_RENDEZVOUS: - *(bool *)optval = m_bRendezvous; - optlen = sizeof(bool); - break; - - case SRTO_SNDTIMEO: - *(int *)optval = m_iSndTimeOut; - optlen = sizeof(int); - break; - - case SRTO_RCVTIMEO: - *(int *)optval = m_iRcvTimeOut; - optlen = sizeof(int); - break; - - case SRTO_REUSEADDR: - *(bool *)optval = m_bReuseAddr; - optlen = sizeof(bool); - break; - - case SRTO_MAXBW: - *(int64_t *)optval = m_llMaxBW; - optlen = sizeof(int64_t); - break; - - case SRTO_STATE: - *(int32_t *)optval = s_UDTUnited.getStatus(m_SocketID); - optlen = sizeof(int32_t); - break; - - case SRTO_EVENT: - { - int32_t event = 0; - if (m_bBroken) - event |= UDT_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; - } - *(int32_t *)optval = event; - optlen = sizeof(int32_t); - break; - } - - case SRTO_SNDDATA: - if (m_pSndBuffer) - *(int32_t *)optval = m_pSndBuffer->getCurrBufSize(); - else - *(int32_t *)optval = 0; - optlen = sizeof(int32_t); - break; - - case SRTO_RCVDATA: - if (m_pRcvBuffer) - { - CGuard::enterCS(m_RecvLock); - *(int32_t *)optval = m_pRcvBuffer->getRcvDataSize(); - CGuard::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; - optlen = sizeof(int32_t); - break; - - case SRTO_IPTOS: - if (m_bOpened) - *(int32_t *)optval = m_pSndQueue->getIpToS(); - else - *(int32_t *)optval = m_iIpToS; - optlen = sizeof(int32_t); - break; -#endif - - case SRTO_SENDER: - *(int32_t *)optval = m_bDataSender; - optlen = sizeof(int32_t); - break; - - case SRTO_TSBPDMODE: - *(int32_t *)optval = m_bOPT_TsbPd; - optlen = sizeof(int32_t); - break; - - case SRTO_LATENCY: - case SRTO_RCVLATENCY: - *(int32_t *)optval = m_iTsbPdDelay_ms; - optlen = sizeof(int32_t); - break; - - case SRTO_PEERLATENCY: - *(int32_t *)optval = m_iPeerTsbPdDelay_ms; - optlen = sizeof(int32_t); - break; - - case SRTO_TLPKTDROP: - *(int32_t *)optval = m_bTLPktDrop; - optlen = sizeof(int32_t); - break; - - case SRTO_SNDDROPDELAY: - *(int32_t *)optval = m_iOPT_SndDropDelay; - optlen = sizeof(int32_t); - break; - - case SRTO_PBKEYLEN: - if (m_pCryptoControl) - *(int32_t *)optval = m_pCryptoControl->KeyLen(); // Running Key length. - else - *(int32_t *)optval = m_iSndCryptoKeyLen; // May be 0. - optlen = sizeof(int32_t); + case SRTO_PBKEYLEN: + if (m_pCryptoControl) + *(int32_t *)optval = (int32_t) m_pCryptoControl->KeyLen(); // Running Key length. + else + *(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,126 +711,183 @@ 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(); + optlen = (int)m_config.sStreamName.copy((char*)optval, (size_t)optlen - 1); + ((char*)optval)[optlen] = '\0'; 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); + + optlen = (int)m_config.sCongestion.copy((char*)optval, (size_t)optlen - 1); + ((char*)optval)[optlen] = '\0'; + 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(); + optlen = (int)m_config.sPacketFilterConfig.copy((char*)optval, (size_t)optlen - 1); + ((char*)optval)[optlen] = '\0'; + break; + + case SRTO_RETRANSMITALGO: + *(int32_t *)optval = m_config.iRetransmitAlgo; + optlen = sizeof(int32_t); break; +#ifdef ENABLE_AEAD_API_PREVIEW + case SRTO_CRYPTOMODE: + if (m_pCryptoControl) + *(int32_t*)optval = m_pCryptoControl->getCryptoMode(); + else + *(int32_t*)optval = m_config.iCryptoMode; + optlen = sizeof(int32_t); + break; +#endif default: throw CUDTException(MJ_NOTSUP, MN_NONE, 0); } } -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 << CONID() << "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() +// Initial sequence number, loss, acknowledgement, etc. +void srt::CUDT::clearData() { - // Initial sequence number, loss, acknowledgement, etc. - int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE; - - m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; - - HLOGC(mglog.Debug, log << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + m_iMaxSRTPayloadSize = m_config.iMSS - CPacket::UDP_HDR_SIZE - CPacket::HDR_SIZE; + HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); m_iEXPCount = 1; m_iBandwidth = 1; // pkts/sec @@ -1284,136 +895,100 @@ 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); + +#if ENABLE_BONDING + m_tsUnstableSince = steady_clock::time_point(); + m_tsFreshActivation = steady_clock::time_point(); + m_tsWarySince = steady_clock::time_point(); +#endif + 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 +1007,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 << CONID() << "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 << CONID() << "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 +1047,124 @@ 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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() + << "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 << CONID() << "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,73 +1173,64 @@ 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 << CONID() << "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; + HLOGP(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"); + HLOGP(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 << CONID() << "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, - "CMD:%s(%d) Len:%d Version: %s Flags: %08X (%s) sdelay:%d", - MessageTypeStr(UMSG_EXT, cmd).c_str(), - cmd, - (int)(srtlen * sizeof(int32_t)), - SrtVersionString(srtdata[SRT_HS_VERSION]).c_str(), - srtdata[SRT_HS_FLAGS], - SrtFlagString(srtdata[SRT_HS_FLAGS]).c_str(), - srtdata[SRT_HS_LATENCY]); + HLOGC(cnlog.Debug, log << "CMD:" << MessageTypeStr(UMSG_EXT, cmd) << "(" << cmd << ") Len:" + << int(srtlen * sizeof(int32_t)) + << " Version: " << SrtVersionString(srtdata[SRT_HS_VERSION]) + << " Flags: " << srtdata[SRT_HS_FLAGS] + << " (" << SrtFlagString(srtdata[SRT_HS_FLAGS]) << ") sdelay:" + << srtdata[SRT_HS_LATENCY]); 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 +1244,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 +1260,7 @@ void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in) break; default: - LOGF(mglog.Error, "sndSrtMsg: cmd=%d unsupported", cmd); + LOGC(cnlog.Error, log << "sndSrtMsg: IPE: cmd=" << cmd << " unsupported"); break; } @@ -1708,61 +1272,191 @@ 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: 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 << CONID() << "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 << CONID() + << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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) - << " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize - << " version=" << hs.m_iVersion); + HLOGC(cnlog.Debug, + log << CONID() << "createSrtHandshake: buf size=" << w_pkt.getLength() + << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd) << " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) + << " kmdata_wordsize=" << kmdata_wordsize << " 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, - log << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. " + LOGC(cnlog.Error, + log << CONID() + << "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 +1466,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, - log << "createSrtHandshake: " << (whether ? "" : "NOT ") - << " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen); + 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 << CONID() << "createSrtHandshake: " << (whether ? "" : "NOT ") + << " 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 +1481,21 @@ 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 << CONID() << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << w_hs.show()); return true; } @@ -1807,28 +1503,23 @@ 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 << CONID() << "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 +1532,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 +1550,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"; } - HLOGC(mglog.Debug, log << "createSrtHandshake: (ext: " << logext << ") data: " << hs.show()); +#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(cnlog.Debug, log << CONID() << "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 +1619,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 +1637,37 @@ 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, - log << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size + HLOGC(cnlog.Debug, + log << CONID() << "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, - log << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes"); + LOGC(cnlog.Warn, + log << CONID() << "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; + offset += ra_size + 1; + ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_SID, m_config.sStreamName.str()); - 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); - - 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 << CONID() << "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 +1676,85 @@ 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); - - 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); + offset += ra_size + 1; + ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_CONGESTION, sm); - HLOGC(mglog.Debug, - log << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size() - << " alignedln=" << aligned_bytesize << ": offset=" << offset << " CONGCTL size=" << ra_size + HLOGC(cnlog.Debug, + log << CONID() << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.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; - - memset(p + offset, 0, aligned_bytesize); - memcpy(p + offset, m_OPT_PktFilterConfigString.data(), m_OPT_PktFilterConfigString.size()); + HLOGC(cnlog.Debug, + log << CONID() << "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)); + } - ra_size = wordsize; - *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_FILTER) | HS_CMDSPEC_SIZE::wrap(ra_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 << CONID() << "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 << CONID() << "createSrtHandshake: group is closing during the process, rejecting."); + return false; + } + 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 << CONID() << "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, - log << "createSrtHandshake: " - << (m_CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION")); + HLOGC(cnlog.Debug, + log << CONID() << "createSrtHandshake: " + << (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 << CONID() << "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,157 +1766,208 @@ 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); + offset += ra_size + 1; + ra_size = fillHsExtKMREQ(p + offset - 1, ki); - // Store the CMD + SIZE in the next field - *(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size); - ++offset; - - // 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 << CONID() << "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 << CONID() << "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 << CONID() << "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. + // + // When SRT_CMD_NONE is returned, it means that nothing has been extracted and nothing else + // can be further extracted from this block. + + int cmd = HS_CMDSPEC_CMD::unwrap(*begin); + size_t size = HS_CMDSPEC_SIZE::unwrap(*begin); + + if (size + 1 > total_length) + return SRT_CMD_NONE; + + w_out_len = size; + + if (total_length == size + 1) + w_next_block = NULL; + else + w_next_block = begin + 1 + size; + + return cmd; +} + +// 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; + + w_length = w_length - (next - w_begin); + w_begin = next; + return true; +} + +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(); + } - // 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. - // - // When SRT_CMD_NONE is returned, it means that nothing has been extracted and nothing else - // can be further extracted from this block. + 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(); + } - int cmd = HS_CMDSPEC_CMD::unwrap(*begin); - size_t size = HS_CMDSPEC_SIZE::unwrap(*begin); +private: + void print_header() + { + m_fout << "Timepoint_SYST,Timepoint_STDY,Event,usRTTSample," + "usRTTVarSample,IsSmoothedRTTReset,pktsRecvTotal," + "usSmoothedRTT,usRTTVar\n"; + } - if (size + 1 > total_length) - return SRT_CMD_NONE; + void create_file() + { + if (m_fout.is_open()) + return; - out_len = size; + 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"; - if (total_length == size + 1) - next_block = NULL; - else - next_block = begin + 1 + size; + print_header(); + } - return cmd; -} +private: + srt::sync::Mutex m_mtx; + std::ofstream m_fout; +}; -static inline bool NextExtensionBlock(ref_t begin, uint32_t *next, ref_t length) -{ - if (!next) - return false; +RttTracer s_rtt_trace; +#endif - *length = *length - (next - *begin); - *begin = next; - return true; -} -bool CUDT::processSrtMsg(const CPacket *ctrlpkt) +bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData; size_t len = ctrlpkt->getLength(); @@ -2187,7 +1976,8 @@ 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 << CONID() << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t))); switch (etype) { case SRT_CMD_HSREQ: @@ -2207,24 +1997,27 @@ 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, - log << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])) - << " - rejecting per strict encryption"); - return false; + LOGC(cnlog.Warn, + log << CONID() << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])) + << " - rejecting per enforced encryption"); + res = SRT_CMD_NONE; + break; } - HLOGC(mglog.Debug, - log << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))); + HLOGC(cnlog.Debug, + log << CONID() + << "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 << CONID() << "KMREQ -> requested to send KMRSP length=" << len_out); } sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); } @@ -2232,7 +2025,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 << CONID() << "KMREQ failed to process the request - ignoring"); } return true; // already done what's necessary @@ -2258,7 +2051,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,122 +2061,126 @@ 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); + LOGC(cnlog.Error, log << "HSREQ/rcv: cmd=" << SRT_CMD_HSREQ << "(HSREQ) len=" << bytelen << " invalid"); return SRT_CMD_NONE; } - LOGF(mglog.Note, - "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " vers=0x%x opts=0x%x delay=%d", - SRT_CMD_HSREQ, - len, - srtdata[SRT_HS_VERSION], - srtdata[SRT_HS_FLAGS], - SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); + LOGC(cnlog.Note, log << "HSREQ/rcv: cmd=" << SRT_CMD_HSREQ << "(HSREQ) len=" << bytelen + << hex << " vers=0x" << srtdata[SRT_HS_VERSION] << " opts=0x" << srtdata[SRT_HS_FLAGS] + << dec << " delay=" << 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, - log << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) + LOGC(cnlog.Error, + log << CONID() << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " is not acceptable."); return SRT_CMD_REJECT; } } else { - if (m_lPeerSrtVersion < SRT_VERSION_FEAT_HSv5) + if (m_uPeerSrtVersion < SRT_VERSION_FEAT_HSv5) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, - log << "HSREQ/rcv: With HSv5 version must be >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " ."); + LOGC(cnlog.Error, + log << CONID() << "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 << CONID() << "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 << CONID() << "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); + HLOGC(cnlog.Debug, log << CONID() << "HSREQ/rcv: peer " << (m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND") << " REXMIT flag"); // 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 << CONID() << "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, - log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting."); + LOGC(cnlog.Error, + log << CONID() + << "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 << CONID() << "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 << CONID() << "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 +2207,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, - log << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency - << " Selecting:" << maxdelay); + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "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 +2235,28 @@ 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, - log << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay_ms << " Peer:" << peer_decl_latency - << " Selecting:" << maxdelay); + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "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 +2266,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 << CONID() << "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); + LOGC(cnlog.Error, log << CONID() << "HSRSP/rcv: cmd=" << SRT_CMD_HSRSP << "(HSRSP) len=" << bytelen << " invalid"); return SRT_CMD_NONE; } @@ -2493,39 +2292,51 @@ 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 << CONID() + << "HSRSP/rcv: PEER START TIME not yet defined, setting: " << FormatTime(m_tsRcvPeerStartTime)); + } + else + { + HLOGC(cnlog.Debug, + log << CONID() + << "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, - "HSRSP/rcv: Version: %s Flags: SND:%08X (%s)", - SrtVersionString(m_lPeerSrtVersion).c_str(), - m_lPeerSrtFlags, - SrtFlagString(m_lPeerSrtFlags).c_str()); + HLOGC(cnlog.Debug, log << "HSRSP/rcv: Version: " << SrtVersionString(m_uPeerSrtVersion) + << " Flags: SND:" << setw(8) << setfill('0') << hex << m_uPeerSrtFlags + << setw(0) << " (" << SrtFlagString(m_uPeerSrtFlags) << ")"); + // Basic version check + if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) + { + m_RejectReason = SRT_REJ_VERSION; + LOGC(cnlog.Error, + log << CONID() << "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, - log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms + HLOGC(cnlog.Debug, + log << CONID() << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << " (Agent: declared:" << m_iTsbPdDelay_ms << " rcv:" << m_iTsbPdDelay_ms << ")"); } // TSBPDSND isn't set in HSv4 by the RESPONDER, because HSv4 RESPONDER is always RECEIVER. @@ -2534,63 +2345,67 @@ 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 << CONID() << "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 << CONID() << "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, - log << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD."); + LOGC(cnlog.Warn, + log << CONID() + << "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 << CONID() << "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"); + HLOGP(cnlog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag"); } handshakeDone(); @@ -2599,26 +2414,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 << CONID() << "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 +2451,33 @@ 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 << CONID() << "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 << CONID() << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!"); return false; } - HLOGC(mglog.Debug, - log << "HS VERSION=" << hs.m_iVersion << " EXTENSIONS: " << CHandShake::ExtensionFlagStr(ext_flags)); + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "interpretSrtHandshake: extracting HSREQ/RSP type extension"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); @@ -2656,20 +2485,21 @@ 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 << CONID() << "HS-ext HSREQ found but invalid size: " << bytelen + << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } @@ -2678,23 +2508,24 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (rescmd != SRT_CMD_HSRSP) { // m_RejectReason already set - LOGC(mglog.Error, - log << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd); + LOGC(cnlog.Error, + log << CONID() << "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 << CONID() << "HS-ext HSRSP found but invalid size: " << bytelen + << " (expected: " << SRT_HS_E_SIZE << ")"); return false; // don't interpret } @@ -2704,19 +2535,22 @@ 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, - log << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd); + // 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 << CONID() << "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 << CONID() << "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 +2569,7 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, } } - HLOGC(mglog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ"); + HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: HSREQ done, checking KMREQ"); // Now check the encrypted @@ -2743,22 +2577,24 @@ 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 << CONID() << "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 << CONID() + << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per enforced " + "encryption"); return false; } - LOGC(mglog.Error, - log << "HS KMREQ: Peer declares encryption, but agent does not - still allowing connection."); + LOGC(cnlog.Warn, + log << CONID() + << "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. // Also normally allow the key to be processed; worst case it will send the failure response. @@ -2771,36 +2607,50 @@ 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, - log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "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, - log << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res); + HLOGC(cnlog.Debug, + log << CONID() << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " + << res); return false; } - if (*out_len == 1) + if (*pw_len == 1) { +#ifdef ENABLE_AEAD_API_PREVIEW + if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADCRYPTOMODE) + { + // Cryptographic modes mismatch. Not acceptable at all. + m_RejectReason = SRT_REJ_CRYPTO; + LOGC(cnlog.Error, + log << CONID() + << "interpretSrtHandshake: KMREQ result: Bad crypto mode - rejecting"); + return false; + } +#endif + // 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 +2660,9 @@ 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 << CONID() + << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption"); return false; } } @@ -2820,10 +2671,18 @@ 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."); + if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADSECRET) + m_RejectReason = SRT_REJ_BADSECRET; +#ifdef ENABLE_AEAD_API_PREVIEW + else if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADCRYPTOMODE) + m_RejectReason = SRT_REJ_CRYPTO; +#endif + else + m_RejectReason = SRT_REJ_UNSECURE; + LOGC(cnlog.Error, + log << CONID() << "KMRSP failed - rejecting connection as per enforced encryption."); return false; } encrypted = true; @@ -2831,13 +2690,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 << CONID() << "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 << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); + if (NextExtensionBlock((begin), next, (length))) continue; } @@ -2847,17 +2706,19 @@ 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, - log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting " - "per strict requirement"); + LOGC(cnlog.Error, + log << CONID() + << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting " + "per enforced encryption"); return false; } - LOGC(mglog.Error, - log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing " + LOGC(cnlog.Warn, + log << CONID() + << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing " "connection."); encrypted = true; #endif @@ -2865,16 +2726,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 << CONID() << "interpretSrtHandshake: extracting various CONFIG extensions"); uint32_t *begin = p; uint32_t *next = 0; @@ -2883,63 +2746,64 @@ 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, - log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); + HLOGC(cnlog.Debug, + log << CONID() << "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 - << " - PROTOCOL ERROR, REJECTING"); + LOGC(cnlog.Error, + log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " + << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } // Copied through a cleared array. This is because the length is aligned to 4 // where the padding is filled by zero bytes. For the case when the string is // exactly of a 4-divisible length, we make a big array with maximum allowed size // filled with zeros. Copying to this array should then copy either only the valid - // characters of the string (if the lenght is divisible by 4), or the string with + // characters of the string (if the length is divisible by 4), or the string with // 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 - << " blocklen=" << blocklen << ")"); + m_config.sStreamName.set(target, strlen(target)); + HLOGC(cnlog.Debug, + log << CONID() << "CONNECTOR'S REQUESTED SID [" << m_config.sStreamName.c_str() + << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_CONGESTION) { if (have_congctl) { m_RejectReason = SRT_REJ_ROGUE; - LOGC(mglog.Error, log << "CONGCTL BLOCK REPEATED!"); + LOGC(cnlog.Error, log << CONID() << "CONGCTL BLOCK REPEATED!"); return false; } - if (!bytelen || bytelen > MAX_SID_LENGTH) + if (!bytelen || bytelen > CSrtConfig::MAX_CONG_LENGTH) { - LOGC(mglog.Error, - log << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > " - << +MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); + LOGC(cnlog.Error, + log << CONID() << "interpretSrtHandshake: CONGESTION-control type length " << bytelen + << " is 0 or > " << +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,185 +2815,623 @@ bool CUDT::interpretSrtHandshake(const CHandShake &hs, if (sm != agsm) { m_RejectReason = SRT_REJ_CONGESTION; - LOGC(mglog.Error, - log << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'"); + LOGC(cnlog.Error, + log << CONID() << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm + << "'"); return false; } - HLOGC(mglog.Debug, - log << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen - << ")"); + HLOGC(cnlog.Debug, + log << CONID() << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen + << " blocklen=" << blocklen << ")"); } else if (cmd == SRT_CMD_FILTER) { if (have_filter) { m_RejectReason = SRT_REJ_FILTER; - LOGC(mglog.Error, log << "FILTER BLOCK REPEATED!"); + LOGC(cnlog.Error, log << CONID() << "FILTER BLOCK REPEATED!"); + return false; + } + + if (!bytelen || bytelen > CSrtConfig::MAX_PFILTER_LENGTH) + { + LOGC(cnlog.Error, + log << CONID() << "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); + // Un-swap on big endian machines + ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen); + string fltcfg = target; - HLOGC(mglog.Debug, - log << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen - << ")"); + HLOGC(cnlog.Debug, + log << CONID() << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen + << " blocklen=" << blocklen << ")"); + + if (!checkApplyFilterConfig(fltcfg)) + { + m_RejectReason = SRT_REJ_FILTER; + LOGC(cnlog.Error, log << CONID() << "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 << CONID() << "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 << CONID() << "CONNECTOR'S PEER GROUP [" << groupdata[0] << "] (bytelen=" << bytelen + << " blocklen=" << blocklen << ")"); + } +#endif + else if (cmd == SRT_CMD_NONE) + { + break; + } + else + { + // Found some block that is not interesting here. Skip this and get the next one. + HLOGC(cnlog.Debug, + log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); + } + + if (!NextExtensionBlock((begin), next, (length))) + break; + } + } + + // Post-checks + // Check if peer declared encryption + if (!encrypted && m_config.CryptoSecret.len > 0) + { + if (m_config.bEnforcedEnc) + { + m_RejectReason = SRT_REJ_UNSECURE; + LOGC(cnlog.Error, + log << CONID() + << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per " + "enforced encryption."); + return false; + } + + LOGC(cnlog.Warn, + log << CONID() + << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets " + "from Peer)."); + + // This is required so that the sender is still allowed to send data, when encryption is required, + // just this will be for waste because the receiver won't decrypt them anyway. + m_pCryptoControl->createFakeSndContext(); + m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Because Peer did not send KMX, though Agent has pw + m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Because Peer has no PW, as has sent no KMREQ. + return true; + } + + // If agent has set some nondefault congctl, then congctl is expected from the peer. + if (agsm != "live" && !have_congctl) + { + m_RejectReason = SRT_REJ_CONGESTION; + LOGC(cnlog.Error, + log << CONID() << "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 << CONID() + << "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 srt::CUDT::checkApplyFilterConfig(const std::string &confstr) +{ + SrtFilterConfig cfg; + if (!ParseFilterConfig(confstr, (cfg))) + return false; + + // Now extract the type, if present, and + // check if you have this type of corrector available. + if (!PacketFilter::correctConfig(cfg)) + return false; + + string thisconf = m_config.sPacketFilterConfig.str(); + + // Now parse your own string, if you have it. + if (thisconf != "") + { + // - 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(thisconf, (mycfg))) + return false; + + // Check only if both have set a filter of the same type. + if (mycfg.type != cfg.type) + return false; + + // If so, then: + // - for caller-listener configuration, accept the listener version. + if (m_SrtHsSide == HSD_INITIATOR) + { + // This is a caller, this should apply all parameters received + // from the listener, forcefully. + for (map::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x) + { + mycfg.parameters[x->first] = x->second; + } + } + else + { + if (!CheckFilterCompat((mycfg), cfg)) + return false; + } + + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "checkApplyFilterConfig: Effective config: " << thisconf); + } + else + { + // Take the foreign configuration as a good deal. + HLOGC(cnlog.Debug, log << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() + << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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. - if (!checkApplyFilterConfig(fltcfg)) - { - LOGC(mglog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected"); - return false; - } - } - else if (cmd == SRT_CMD_NONE) - { - break; - } - 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)); - } + SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags); + if (!lgid) + return true; // already done - if (!NextExtensionBlock(Ref(begin), next, Ref(length))) - break; + 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 } - } - // Post-checks - // Check if peer declared encryption - if (!encrypted && m_CryptoSecret.len > 0) - { - if (m_bOPT_StrictEncryption) + if (!m_parent->m_GroupOf) { - m_RejectReason = SRT_REJ_UNSECURE; - LOGC(mglog.Error, - log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per strict " - "requirement."); + // Strange, we just added it... + m_RejectReason = SRT_REJ_IPE; + LOGC(cnlog.Fatal, log << CONID() << "IPE: socket not in group after adding to it"); return false; } - LOGC(mglog.Error, - log << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets " - "from Peer)."); + groups::SocketData* f = m_parent->m_GroupMemberData; - // This is required so that the sender is still allowed to send data, when encryption is required, - // just this will be for waste because the receiver won't decrypt them anyway. - m_pCryptoControl->createFakeSndContext(); - m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Because Peer did not send KMX, though Agent has pw - m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Because Peer has no PW, as has sent no KMREQ. - return true; + f->weight = link_weight; + f->agent = m_parent->m_SelfAddr; + f->peer = m_PeerAddr; } - // If agent has set some nondefault congctl, then congctl is expected from the peer. - if (agsm != "live" && !have_congctl) - { - m_RejectReason = SRT_REJ_CONGESTION; - LOGC(mglog.Error, - log << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live')."); - return false; - } + m_parent->m_GroupOf->debugGroup(); - // Ok, finished, for now. + // 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+). -bool CUDT::checkApplyFilterConfig(const std::string &confstr) +// [[using locked(s_UDTUnited.m_GlobControlLock)]] +SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags) { - SrtFilterConfig cfg; - if (!ParseFilterConfig(confstr, cfg)) - return false; + // Note: This function will lock pg->m_GroupLock! - // Now extract the type, if present, and - // check if you have this type of corrector available. - if (!PacketFilter::correctConfig(cfg)) - return false; + CUDTSocket* s = m_parent; - // Now parse your own string, if you have it. - if (m_OPT_PktFilterConfigString != "") + // 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) { - // - for rendezvous, both must be exactly the same, or only one side specified. - if (m_bRendezvous && m_OPT_PktFilterConfigString != confstr) + if (gp->type() != gtp) { - return false; + LOGC(gmlog.Error, + log << CONID() << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp + << " agent group=$" << gp->id() << " type" << gp->type()); + return -1; } - SrtFilterConfig mycfg; - if (!ParseFilterConfig(m_OPT_PktFilterConfigString, mycfg)) - return false; - - // Check only if both have set a filter of the same type. - if (mycfg.type != cfg.type) - return false; + HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id()); - // If so, then: - // - for caller-listener configuration, accept the listener version. - if (m_SrtHsSide == HSD_INITIATOR) + if (!gp->groupEmpty()) + was_empty = false; + } + else + { + try { - // This is a caller, this should apply all parameters received - // from the listener, forcefully. - for (map::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x) - { - mycfg.parameters[x->first] = x->second; - } + gp = &newGroup(gtp); } - else + catch (...) { - // 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; - } + // Expected exceptions are only those referring to system resources + return -1; } - HLOGC(mglog.Debug, - log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters) - << " FORGN: " << Printable(cfg.parameters)); + if (!gp->applyFlags(link_flags, m_SrtHsSide)) + { + // Wrong settings. Must reject. Delete group. + uglobal().deleteGroup_LOCKED(gp); + return -1; + } - ostringstream myos; - myos << mycfg.type; - for (map::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x) + gp->set_peerid(peergroup); + gp->deriveSettings(this); + + // 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 << CONID() << "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 << CONID() << "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; + } - m_OPT_PktFilterConfigString = myos.str(); + s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); + s->m_GroupOf = gp; + m_HSGroupType = gtp; - HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Effective config: " << m_OPT_PktFilterConfigString); + // 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; + + start_time = m_stats.tsStartTime; + peer_start_time = m_tsRcvPeerStartTime; + + if (!gp->applyGroupTime((start_time), (peer_start_time))) + { + HLOGC(gmlog.Debug, + log << CONID() << "synchronizeWithGroup: 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 << CONID() << "synchronizeWithGroup: 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); + m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); + leaveCS(m_RecvLock); + + HLOGC(gmlog.Debug, log << "AFTER HS: Set Rcv TsbPd mode: delay=" + << (m_iTsbPdDelay_ms/1000) << "." << (m_iTsbPdDelay_ms%1000) + << "s GROUP TIME BASE: " << FormatTime(rcv_buffer_time_base) + << " (" << (rcv_buffer_wrap_period ? "" : "NOT") << " WRAP PERIOD)"); + } + else + { + HLOGC(gmlog.Debug, + log << CONID() << "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(); + + // 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 << CONID() << "synchronizeWithGroup: 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 << CONID() << "synchronizeWithGroup: 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 +3443,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 @@ -3168,8 +3463,12 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // handle handshake extension flags. m_ConnReq.m_iType = UDT_DGRAM; + // Auto mode for Caller and in Rendezvous is equivalent to CIPHER_MODE_AES_CTR. + if (m_config.iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO) + m_config.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + // 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 +3485,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, - log << "startConnect (rnd): " << (whether ? "" : "NOT ") - << " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen); + m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_config.iSndCryptoKeyLen); + IF_HEAVY_LOGGING(const bool whether = m_config.iSndCryptoKeyLen != 0); + HLOGC(aclog.Debug, + log << CONID() << "startConnect (rnd): " << (whether ? "" : "NOT ") + << " Advertising PBKEYLEN - value = " << m_config.iSndCryptoKeyLen); m_RdvState = CHandShake::RDV_WAVING; m_SrtHsSide = HSD_DRAW; // initially not resolved. } @@ -3211,30 +3510,26 @@ 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 << CONID() << "startConnect: ISN generated = " << forced_isn); } else { - // Predefined ISN (for debug purposes) - m_iISN = m_ConnReq.m_iISN = forced_isn; + HLOGC(aclog.Debug, log << CONID() << "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); // Inform the server my configurations. CPacket reqpkt; @@ -3255,27 +3550,33 @@ 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); + const steady_clock::time_point tnow = steady_clock::now(); + m_SndLastAck2Time = tnow; + setPacketTS(reqpkt, tnow); - 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_pSndQueue->sendto(serv_addr, reqpkt); + m_tsLastReqTime = tnow; + m_bConnecting = true; + + // At this point m_SourceAddr is probably default-any, but this function + // now requires that the address be specified here because there will be + // no possibility to do it at any next stage of sending. + m_pSndQueue->sendto(serv_addr, reqpkt, m_SourceAddr); // /// @@ -3287,13 +3588,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 @@ -3304,55 +3610,63 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) CUDTException e; EConnectStatus cst = CONN_CONTINUE; + // This is a temporary place to store the DESTINATION IP from the incoming packet. + // We can't record this address yet until the cookie-confirmation is done, for safety reasons. + sockaddr_any use_source_adr(serv_addr.family()); while (!m_bClosing) { - int64_t tdiff = CTimer::getTime() - m_llLastReqTime; + const steady_clock::time_point local_tnow = steady_clock::now(); + const steady_clock::duration tdiff = local_tnow - 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 << CONID() << "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(); #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_pSndQueue->sendto(serv_addr, reqpkt); + m_tsLastReqTime = local_tnow; + setPacketTS(reqpkt, local_tnow); + m_pSndQueue->sendto(serv_addr, reqpkt, use_source_adr); } else { - HLOGC(mglog.Debug, log << "startConnect: LOOP: too early to send - " << tdiff << " < 250000"); + HLOGC(cnlog.Debug, + log << CONID() << "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*/); + use_source_adr = response.udpDestAddr(); - HLOGC(mglog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst)); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: got response for connect request"); + cst = processConnectResponse(response, &e); + + 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,14 +3697,32 @@ 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; + + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: processRendezvous returned cst=" << ConnectStatusStr(cst)); + + if (cst == CONN_REJECT) + { + // Just in case it wasn't set, set this as a fallback + if (m_RejectReason == SRT_REJ_UNKNOWN) + m_RejectReason = SRT_REJ_ROGUE; + + // rejection or erroneous code. + reqpkt.setLength(m_iMaxSRTPayloadSize); + reqpkt.setControl(UMSG_HANDSHAKE); + sendRendezvousRejection(serv_addr, (reqpkt)); + } } if (cst == CONN_REJECT) + { + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: REJECTED by processConnectResponse - sending SHUTDOWN"); sendCtrl(UMSG_SHUTDOWN); + } if (cst != CONN_CONTINUE && cst != CONN_CONFUSED) break; // --> OUTSIDE-LOOP @@ -3398,10 +3730,11 @@ void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn) // IMPORTANT // [[using assert(m_pCryptoControl != nullptr)]]; - // new request/response should be sent out immediately on receving a response - HLOGC(mglog.Debug, - log << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) << ", REQ-TIME: LOW."); - m_llLastReqTime = 0; + // new request/response should be sent out immediately on receiving a response + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) + << ", REQ-TIME: LOW."); + 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 +3746,8 @@ 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 << CONID() << "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 +3763,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 << CONID() << "createSrtHandshake failed - REJECTING."); cst = CONN_REJECT; break; } @@ -3442,23 +3776,27 @@ 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, - log << "startConnect: timeout from Q:recvfrom, looping again; cst=" << ConnectStatusStr(cst)); + HLOGC(cnlog.Debug, + log << CONID() << "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, - log << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!"); + LOGC(cnlog.Error, + log << CONID() + << "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 << CONID() << "startConnect: TTL time " << FormatTime(ttl_time) << " exceeded, TIMEOUT."); break; } } @@ -3475,13 +3813,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 +3832,38 @@ 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. sourceIP=" << m_SourceAddr.str()); // Parameters at the end. - HLOGC(mglog.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); + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: END. Parameters: mss=" << 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,25 +3875,30 @@ 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 << CONID() << "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 << CONID() << "processAsyncConnectRequest: passing to processRendezvous"); + cst = processRendezvous(pResponse, serv_addr, rst, (request)); if (cst == CONN_ACCEPT) { - HLOGC(mglog.Debug, - log << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. " + HLOGC(cnlog.Debug, + log << CONID() + << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. " "Done."); return true; } @@ -3563,32 +3906,43 @@ bool CUDT::processAsyncConnectRequest(EReadStatus rst, if (cst != CONN_CONTINUE) { // processRendezvous already set the reject reason - LOGC(mglog.Error, - log << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further."); + LOGC(cnlog.Warn, + log << CONID() + << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further."); + + if (m_RejectReason == SRT_REJ_UNKNOWN) + m_RejectReason = SRT_REJ_ROGUE; + + sendRendezvousRejection(serv_addr, (request)); status = false; } } 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 << CONID() << "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 << CONID() << "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 << CONID() << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing."); status = false; } else { - HLOGC(mglog.Debug, - log << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) + HLOGC(cnlog.Debug, + log << CONID() + << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) << " to socket " << request.m_iID << " size=" << request.getLength()); } } @@ -3600,24 +3954,46 @@ 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(); - m_pSndQueue->sendto(serv_addr, request); + HLOGC(cnlog.Debug, + log << CONID() << "processAsyncConnectRequest: setting REQ-TIME HIGH, SENDING HS:" << m_ConnReq.show()); + m_tsLastReqTime = steady_clock::now(); + m_pSndQueue->sendto(serv_addr, request, m_SourceAddr); return status; } -void CUDT::cookieContest() +void srt::CUDT::sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& r_rsppkt) +{ + // We can reuse m_ConnReq because we are about to abandon the connection process. + m_ConnReq.m_iReqType = URQFailure(m_RejectReason); + + // Assumed that r_rsppkt refers to a packet object that was already prepared + // to be used for storing the handshake there. + size_t size = r_rsppkt.getLength(); + m_ConnReq.store_to((r_rsppkt.m_pcData), (size)); + r_rsppkt.setLength(size); + + HLOGC(cnlog.Debug, log << CONID() << "sendRendezvousRejection: using code=" << m_ConnReq.m_iReqType + << " for reject reason code " << m_RejectReason << " (" << srt_rejectreason_str(m_RejectReason) << ")"); + + setPacketTS(r_rsppkt, steady_clock::now()); + m_pSndQueue->sendto(serv_addr, r_rsppkt, m_SourceAddr); +} + +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 << CONID() << "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 +4006,146 @@ 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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "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 << CONID() << "processRendezvous: already in CONNECTED state."); return CONN_ACCEPT; } uint32_t kmdata[SRTDATA_MAXSIZE]; size_t kmdatasize = SRTDATA_MAXSIZE; - CPacket &rpkt = *reqpkt; cookieContest(); @@ -3678,8 +4155,8 @@ EConnectStatus CUDT::processRendezvous( if (m_SrtHsSide == HSD_DRAW) { m_RejectReason = SRT_REJ_RDVCOOKIE; - LOGC(mglog.Error, - log << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute."); + LOGC(cnlog.Error, + log << CONID() << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute."); return CONN_REJECT; } @@ -3692,12 +4169,13 @@ 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, - log << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type)); + HLOGC(cnlog.Debug, + log << CONID() + << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type)); return CONN_REJECT; } checkUpdateCryptoKeyLen("processRendezvous", m_ConnRes.m_iType); @@ -3710,14 +4188,20 @@ 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(pResponse)) + { + LOGC(cnlog.Error, log << CONID() << "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) || !prepareBuffers(NULL)) { // m_RejectReason already handled - HLOGC(mglog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); + HLOGC(cnlog.Debug, + log << CONID() << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); return CONN_REJECT; } @@ -3730,109 +4214,53 @@ 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, - log << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)"); + LOGC(cnlog.Fatal, + log << CONID() << "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, - log << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW."); + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "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, - log << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize); + HLOGC(cnlog.Debug, + log << CONID() + << "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, - log << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW"); - m_llLastReqTime = 0; + HLOGC(cnlog.Debug, + log << CONID() + << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW"); + m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } @@ -3850,45 +4278,49 @@ 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, - log << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)"); + cnlog.Debug, + log << CONID() + << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)"); } else { - HLOGC(mglog.Debug, - log << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"); - if (!interpretSrtHandshake(m_ConnRes, response, 0, 0)) + HLOGC(cnlog.Debug, + log << CONID() << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"); + if (!interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0)) { // m_RejectReason is already set, so set the reqtype accordingly m_ConnReq.m_iReqType = URQFailure(m_RejectReason); + return CONN_REJECT; } } // 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 << CONID() << "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 << CONID() << "... WON'T SEND any response, both sides considered connected"); } else { - HLOGC(mglog.Debug, - log << "... WILL SEND " << RequestTypeStr(rsp_type) << " " << (m_ConnReq.m_extension ? "with" : "without") - << " SRT HS extensions"); + HLOGC(cnlog.Debug, + log << CONID() << "... WILL SEND " << RequestTypeStr(rsp_type) << " " + << (m_ConnReq.m_extension ? "with" : "without") << " SRT HS extensions"); } // This marks the information for the serializer that @@ -3897,17 +4329,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 << CONID() << "processRendezvous: rejecting due to problems in postConnect."); return CONN_REJECT; } } @@ -3918,7 +4347,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 << CONID() << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)"); return CONN_ACCEPT; } @@ -3928,11 +4357,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 << CONID() << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW"); + m_tsLastReqTime = steady_clock::time_point(); return CONN_REJECT; } @@ -3952,14 +4382,14 @@ 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 << CONID() + << "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, m_SourceAddr); return CONN_ACCEPT; } @@ -3967,19 +4397,21 @@ 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 << CONID() << "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,8 +4426,8 @@ 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, - log << "processConnectResponse: TYPE:" + HLOGC(cnlog.Debug, + log << CONID() << "processConnectResponse: TYPE:" << (response.isControl() ? MessageTypeStr(response.getType(), response.getExtendedType()) : string("DATA"))); // ConnectStatus res = CONN_REJECT; // used later for status - must be declared here due to goto POST_CONNECT. @@ -4003,7 +4435,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 +4449,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 +4464,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,33 +4478,59 @@ 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: " << MessageTypeStr(response.getType(), response.getExtendedType())); + + if (response.getType() == UMSG_SHUTDOWN) + { + LOGC(cnlog.Error, + log << CONID() << "processConnectResponse: UMSG_SHUTDOWN received, rejecting connection."); + return CONN_REJECT; + } } + + if (m_config.bRendezvous) + { + // In rendezvous mode we expect that both sides are known + // to the service operator (unlike a listener, which may + // operate connections from unknown sources). This means that + // the connection process should be terminated anyway, on + // whichever side it would happen. + return CONN_REJECT; + } + return CONN_CONFUSED; } + if (m_config.bRendezvous) + { + m_SourceAddr = response.udpDestAddr(); + } + if (m_ConnRes.load_from(response.m_pcData, response.getLength()) == -1) { 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()); - if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) + 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); + LOGC(cnlog.Warn, + log << CONID() << "processConnectResponse: rejecting per reception of a rejection HS response: " + << RequestTypeStr(m_ConnRes.m_iReqType)); return CONN_REJECT; } @@ -4081,7 +4539,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 << CONID() << "MSS size " << m_config.iMSS << "exceeds MTU size!"); return CONN_REJECT; } @@ -4090,13 +4548,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 +4566,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 +4586,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 +4610,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"); @@ -4164,11 +4622,14 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti // it means that it is HSv5 capable. It can still accept the HSv4 handshake. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4) { - int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); + const int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); 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 << CONID() << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE."); + m_RejectReason = SRT_REJ_ROGUE; + return CONN_REJECT; } checkUpdateCryptoKeyLen("processConnectResponse", m_ConnRes.m_iType); @@ -4178,7 +4639,7 @@ EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTExcepti m_ConnReq.m_iVersion = HS_VERSION_SRT1; // CONTROVERSIAL: use 0 as m_iType according to the meaning in HSv5. // The HSv4 client might not understand it, which means that agent - // must switch itself to HSv4 rendezvous, and this time iType sould + // must switch itself to HSv4 rendezvous, and this time iType should // be set to UDT_DGRAM value. m_ConnReq.m_iType = 0; @@ -4189,12 +4650,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 +4669,67 @@ 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(const CPacket* pHspkt /*[[nullable]]*/) ATR_NOEXCEPT { + if (!m_ConnRes.valid()) + { + LOGC(cnlog.Error, log << CONID() << "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); + if (pHspkt) + m_SourceAddr = pHspkt->udpDestAddr(); - 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); + << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " peer-ISN=" << m_ConnRes.m_iISN + << " local-ISN=" << m_iISN + << " peerID=" << m_ConnRes.m_iID + << " sourceIP=" << m_SourceAddr.str()); + 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) { + HLOGC(cnlog.Debug, log << CONID() << boolalpha << "postConnect: packet:" << bool(pResponse) << " rendezvous:" << 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 +4741,72 @@ 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(pResponse); // 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); + ok = ok && prepareBuffers(eout); + + 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,16 +4816,28 @@ 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 // that thinks it's connected, but his last handshake was missed, - // they are collected by CRcvQueue::storePkt. The removeConnector + // they are collected by CRcvQueue::storePktClone. The removeConnector // function will want to delete them all, so it would be nice // if these packets can be re-delivered. Of course the listener // should be prepared to resend them (as every packet can be lost @@ -4318,18 +4854,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 << CONID() << "Connection broken in the process - socket 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 +4872,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 +4941,44 @@ 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 << CONID() << 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 " - << rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)"); - m_iSndCryptoKeyLen = rcv_pbkeylen; + LOGC(cnlog.Warn, + log << CONID() << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_config.iSndCryptoKeyLen + << " by " << rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)"); + m_config.iSndCryptoKeyLen = rcv_pbkeylen; } else { - LOGC(mglog.Warn, - log << loghdr << ": PBKEYLEN conflict - keep " << m_iSndCryptoKeyLen + LOGC(cnlog.Warn, + log << CONID() << 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 << CONID() << 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 << CONID() << 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 +5017,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 << CONID() << "rendezvousSwitchState: HS: " << m_ConnRes.show()); struct LogAtTheEnd { @@ -4455,14 +5038,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 +5061,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 +5092,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; } @@ -4526,16 +5109,16 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // If no HSRSP attached, stay in this state. if (hs_flags == 0) { - HLOGC( - mglog.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 + HLOGC(cnlog.Debug, + log << CONID() + << "rendezvousSwitchState: {INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got " + "CONCLUSION, remain in [ATTENTION]"); + 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; } @@ -4546,26 +5129,26 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need // (Although this seems completely impossible). if (hs_flags == 0) { - LOGC( - mglog.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) + LOGC(cnlog.Warn, + log << CONID() + << "rendezvousSwitchState: (IPE!){RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got " + "CONCLUSION, remain in [ATTENTION]"); + 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 << CONID() << "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 +5167,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 +5177,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,8 +5209,9 @@ 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, - 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, - 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 +5255,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 +5271,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 << CONID() << "<-- AGREEMENT: already connected"); } else { - HLOGC(mglog.Debug, log << "<-- AGREEMENT: switched to connected"); + HLOGC(cnlog.Debug, log << CONID() << "<-- AGREEMENT: switched to connected"); } m_RdvState = CHandShake::RDV_CONNECTED; - *rsptype = URQ_DONE; + w_rsptype = URQ_DONE; return; } @@ -4702,15 +5287,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, - log << "rendezvousSwitchState: " - "{RESPONDER}[INITIATED] awaits AGREEMENT, " + HLOGC(cnlog.Debug, + log << CONID() + << "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,21 +5305,21 @@ 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, " + HLOGC(cnlog.Debug, + log << CONID() + << "rendezvousSwitchState: {INITIATOR}[INITIATED] awaits AGREEMENT, " "got empty CONCLUSION, STILL RESPONDING CONCLUSION+HSRSP"); } else { - HLOGC(mglog.Debug, - log << "rendezvousSwitchState: " - "{INITIATOR}[INITIATED] awaits AGREEMENT, " + HLOGC(cnlog.Debug, + log << CONID() + << "rendezvousSwitchState: {INITIATOR}[INITIATED] awaits AGREEMENT, " "got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP"); } - *needs_extension = true; - *needs_hsrsp = true; + w_needs_extension = true; + w_needs_hsrsp = true; return; } } @@ -4744,14 +5329,14 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need case CHandShake::RDV_CONNECTED: // Do nothing. This theoretically should never happen. - *rsptype = URQ_DONE; + w_rsptype = URQ_DONE; return; } - HLOGC(mglog.Debug, log << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID"); + HLOGC(cnlog.Debug, log << CONID() << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID"); // All others are treated as errors m_RdvState = CHandShake::RDV_WAVING; - *rsptype = URQFailure(SRT_REJ_ROGUE); + w_rsptype = URQFailure(SRT_REJ_ROGUE); } /* @@ -4759,138 +5344,155 @@ void CUDT::rendezvousSwitchState(ref_t rsptype, ref_t need * 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) +void * srt::CUDT::tsbpd(void* param) { - CUDT *self = (CUDT *)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 + + 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) { - int32_t current_pkt_seq = 0; - uint64_t tsbpdtime = 0; - bool rxready = false; + steady_clock::time_point tsNextDelivery; // Next packet delivery time + bool rxready = false; +#if ENABLE_BONDING + bool shall_update_group = false; +#endif - CGuard::enterCS(self->m_RcvBufferLock); + INCREMENT_THREAD_ITERATIONS(); -#ifdef SRT_ENABLE_RCVBUFSZ_MAVG - self->m_pRcvBuffer->updRcvAvgDataSize(CTimer::getTime()); -#endif + enterCS(self->m_RcvBufferLock); + const steady_clock::time_point tnow = steady_clock::now(); - if (self->m_bTLPktDrop) - { - int32_t skiptoseqno = -1; - bool passack = true; // Get next packet to wait for even if not acked + self->m_pRcvBuffer->updRcvAvgDataSize(tnow); + const srt::CRcvBuffer::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); - rxready = self->m_pRcvBuffer->getRcvFirstMsg( - Ref(tsbpdtime), Ref(passack), Ref(skiptoseqno), Ref(current_pkt_seq)); + const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); + tsNextDelivery = info.tsbpd_time; - HLOGC(tslog.Debug, - log << boolalpha << "NEXT PKT CHECK: rdy=" << rxready << " passack=" << passack << " skipto=%" - << skiptoseqno << " current=%" << current_pkt_seq << " buf-base=%" << self->m_iRcvLastSkipAck); - /* - * VALUES RETURNED: - * - * rxready: if true, packet at head of queue ready to play - * tsbpdtime: timestamp of packet at head of queue, ready or not. 0 if none. - * passack: if true, ready head of queue not yet acknowledged - * skiptoseqno: sequence number of packet at head of queue if ready to play but - * some preceeding packets are missing (need to be skipped). -1 if none. - */ - if (rxready) + if (!self->m_bTLPktDrop) + { + rxready = !info.seq_gap && is_time_to_deliver; + } + else if (is_time_to_deliver) + { + rxready = true; + if (info.seq_gap) { - /* Packet ready to play according to time stamp but... */ - int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno); - - if (skiptoseqno != -1 && seqlen > 0) - { - /* - * skiptoseqno != -1, - * 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->m_pRcvBuffer->skipData(seqlen); - - self->m_iRcvLastSkipAck = skiptoseqno; + const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); +#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()); + 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 seq=" << CSeqNo::decseq(skiptoseqno) << " (" - << seqlen << " packets) playable at " << FormatTime(tsbpdtime) << " delayed " - << (timediff / 1000) << "." << (timediff % 1000) << " ms"); + 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(dlog.Debug, log << "RCV-DROPPED packet delay=" << (timediff / 1000) << "ms"); + 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 - tsbpdtime = 0; // 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 - } /* else packet ready to play */ - } /* else packets not ready to play */ - } - else - { - rxready = self->m_pRcvBuffer->isRcvDataReady(Ref(tsbpdtime), Ref(current_pkt_seq)); + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } } - 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)"); + 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_bSynRecving) + if (self->m_config.bSynRecving) { - pthread_cond_signal(&self->m_RecvDataCond); + recvdata_lcc.notify_one(); } /* * 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_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); + } + } +#endif + CGlobEvent::triggerEvent(); + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } - if (tsbpdtime != 0) + // We may just briefly unlocked the m_RecvLock, so we need to check m_bClosing again to avoid deadlock. + if (self->m_bClosing) + break; + + if (!is_zero(tsNextDelivery)) { - int64_t timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime()); + 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; - 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); + 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 @@ -4909,33 +5511,76 @@ 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; } -bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout) +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); + + dropFromLossLists(SRT_SEQNO_NONE, CSeqNo::decseq(seqno)); + + 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; +} + +void srt::CUDT::setInitialRcvSeq(int32_t isn) +{ + m_iRcvLastAck = isn; +#ifdef ENABLE_LOGGING + m_iDebugPrevLastAck = isn; +#endif + m_iRcvLastAckAck = isn; + m_iRcvCurrSeqNo = CSeqNo::decseq(isn); + + sync::ScopedLock rb(m_RcvBufferLock); + if (m_pRcvBuffer) + { + if (!m_pRcvBuffer->empty()) + { + LOGC(cnlog.Error, log << CONID() << "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(isn); + } +} + +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 // in a little bit "randomly selected" moment, but must // be run once in the whole connection process. - if (m_pSndBuffer) + if (m_pCryptoControl) { - HLOGC(mglog.Debug, log << "prepareConnectionObjects: (lazy) already created."); + HLOGC(rslog.Debug, log << CONID() << "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,165 +5594,222 @@ 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; } } + if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy) + { + if (eout) + *eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + m_RejectReason = SRT_REJ_RESOURCE; + return false; + } + + return true; +} + +bool srt::CUDT::prepareBuffers(CUDTException* eout) +{ + if (m_pSndBuffer) + { + HLOGC(rslog.Debug, log << CONID() << "prepareBuffers: (lazy) already created."); + return true; + } + try { - m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize); - m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_iRcvBufSize); - // after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. + // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. + const int authtag = (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) ? HAICRYPT_AUTHTAG_MAX : 0; + m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize, authtag); + SRT_ASSERT(m_iPeerISN != -1); + m_pRcvBuffer = new srt::CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pRcvQueue->m_pUnitQueue, m_config.bMessageAPI); + // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space. m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); - m_pRcvLossList = new CRcvLossList(m_iFlightFlagSize); + m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) { // Simply reject. if (eout) - { *eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } m_RejectReason = SRT_REJ_RESOURCE; return false; } + return true; +} - if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy) +void srt::CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) +{ + // this is a response 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; + + if (w_hs.m_iVersion > HS_VERSION_UDT4) { - m_RejectReason = SRT_REJ_RESOURCE; - return false; + // 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; } - return true; + CIPAddress::ntop(peer, (w_hs.m_piPeerIP)); } -void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket &hspkt) +void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) { - HLOGC(mglog.Debug, log << "acceptAndRespond: setting up data according to handshake"); + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: setting up data according to handshake"); - CGuard cg(m_ConnectionLock); + ScopedLock cg(m_ConnectionLock); - m_ullRcvPeerStartTime = 0; // will be set correctly at SRT HS + m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS // Uses the smaller MSS between the peers - if (hs->m_iMSS > m_iMSS) - hs->m_iMSS = m_iMSS; - else - m_iMSS = hs->m_iMSS; + m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); // exchange info for maximum flow window size - m_iFlowWindowSize = hs->m_iFlightFlagSize; - hs->m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize; + m_iFlowWindowSize = w_hs.m_iFlightFlagSize; + m_iPeerISN = w_hs.m_iISN; + setInitialRcvSeq(m_iPeerISN); + m_iRcvCurrPhySeqNo = CSeqNo::decseq(w_hs.m_iISN); - m_iPeerISN = hs->m_iISN; - - 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_PeerID = hs->m_iID; - hs->m_iID = m_SocketID; + m_PeerID = w_hs.m_iID; // use peer's ISN and send it back for security check - m_iISN = hs->m_iISN; + m_iISN = 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(); - - // this is a reponse handshake - hs->m_iReqType = URQ_CONCLUSION; - - 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 << CONID() << "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 << CONID() << "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 << CONID() << "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); + } + + if (!prepareBuffers(NULL)) + { + HLOGC(cnlog.Debug, + log << CONID() << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); + // If the SRT buffers failed to be allocated, + // the connection must be rejected. + // + // Respond with the rejection message and exit with exception + // so that the caller will know that this new socket should be deleted. + 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,16 +5818,17 @@ 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, - 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)) + HLOGC(cnlog.Debug, + log << CONID() + << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); + 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 << CONID() << "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; + // We can safely assign it here stating that this has passed the cookie test. + m_SourceAddr = hspkt.udpDestAddr(); #if ENABLE_HEAVY_LOGGING { @@ -5133,10 +5836,11 @@ void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket // 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() + << " sourceIP=" << m_SourceAddr.str()); } #endif @@ -5145,7 +5849,16 @@ 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)); +} + +bool srt::CUDT::frequentLogAllowed(const time_point& tnow) const +{ +#ifndef SRT_LOG_SLOWDOWN_FREQ_MS +#define SRT_LOG_SLOWDOWN_FREQ_MS 1000 +#endif + + return (m_tsLogSlowDown + milliseconds_from(SRT_LOG_SLOWDOWN_FREQ_MS)) <= tnow; } // This function is required to be called when a caller receives an INDUCTION @@ -5156,7 +5869,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 +5878,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 << CONID() << "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 +5906,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 << CONID() << "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 +5953,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 - << " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)" - << " rtt=" << m_iRTT << " bw=" << m_iBandwidth); - - updateCC(TEV_INIT, TEV_INIT_RESET); + 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 << CONID() << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize=" + << m_iFlowWindowSize << " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)" + << " rtt=" << m_iSRTT << " bw=" << m_iBandwidth); + + if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET))) + { + LOGC(rslog.Error, log << CONID() << "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 << CONID() << "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 +6008,8 @@ 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 << CONID() << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times"); return; } } @@ -5288,55 +6017,66 @@ 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, - log << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt - << " times"); + HLOGC(cnlog.Debug, + log << CONID() << "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 << CONID() << "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() { if (m_SrtHsSide == HSD_INITIATOR) { - HLOGC(mglog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase"); + HLOGC(cnlog.Debug, + log << CONID() << "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, - log << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)") + HLOGC(cnlog.Debug, + log << CONID() + << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)") << " - not considering legacy handshake"); } - // This must be done always on sender, regardless of HS side. - // When regen == DONT_REGEN_KM, it's a handshake call, so do - // it only for initiator. - if (regen || m_SrtHsSide == HSD_INITIATOR) - { - // Don't call this function in "non-regen mode" (sending only), - // 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); - } + // Retransmit KM request after a timeout if there is no response (KM RSP). + // Or send KM REQ in case of the HSv4. + ScopedLock lck(m_ConnectionLock); + if (m_pCryptoControl) + m_pCryptoControl->sendKeysToPeer(this, SRTT()); } -void CUDT::addressAndSend(CPacket &pkt) +void srt::CUDT::checkSndKMRefresh() { - pkt.m_iID = m_PeerID; - pkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); + // Do not apply the regenerated key to the to the receiver context. + const bool bidir = false; + if (m_pCryptoControl) + m_pCryptoControl->regenCryptoKm(this, bidir); +} - m_pSndQueue->sendto(m_pPeerAddr, pkt); +void srt::CUDT::addressAndSend(CPacket& w_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, m_SourceAddr); } -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 +6085,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, - log << "CUDT::close: linger-nonblocking, setting expire time T=" - << FormatTime(m_ullLingerExpiration)); + HLOGC(smlog.Debug, + log << CONID() << "CUDT::close: linger-nonblocking, setting expire time T=" + << FormatTime(m_tsLingerExpiration)); return false; } @@ -5389,23 +6134,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 << CONID() << "close: SETTING ERR readiness on E" << Printable(epollid)); + 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 << CONID() << "close: CLEARING subscription on E" << (*i)); + 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 << CONID() << "close: removing E" << (*i) << " from back-subscribers"); } + // 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 +6186,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 +6209,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 << CONID() << "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 +6286,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 << CONID() << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } - HLOGC(mglog.Debug, - log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") + HLOGC(arlog.Debug, + log << CONID() << (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,133 +6333,150 @@ 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 << CONID() << "STREAM API, SHUTDOWN: marking as EOF"); return 0; } - HLOGC(mglog.Debug, - log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no") + HLOGC(arlog.Debug, + log << CONID() << (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 << CONID() << "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) + // Minimum value must accommodate an I-Frame (~8 x average frame size) // >>need picture rate or app to set min treshold // >>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 << CONID() << "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 +6485,23 @@ 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 << CONID() << "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 << CONID() << "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 +6509,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 +6537,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, - log << "Message length (" << len << ") exceeds the size of sending buffer: " - << (m_iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); + LOGC(aslog.Error, + log << CONID() << "Message length (" << len << ") exceeds the size of sending buffer: " + << (m_config.iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } @@ -5845,58 +6550,65 @@ 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(); + + // For MESSAGE API the minimum outgoing buffer space required is + // the size that can carry over the whole message as passed here. + // Otherwise it is allowed to send less bytes. + const int iNumPktsRequired = m_config.bMessageAPI ? m_pSndBuffer->countNumPacketsRequired(len) : 1; - int minlen = 1; // Minimum sender buffer space required for STREAM API - if (m_bMessageAPI) + if (m_bTsbPd && iNumPktsRequired > 1) { - // For MESSAGE API the minimum outgoing buffer space required is - // the size that can carry over the whole message as passed here. - minlen = (len + m_iMaxSRTPayloadSize - 1) / m_iMaxSRTPayloadSize; + LOGC(aslog.Error, + log << CONID() << "Message length (" << len << ") can't fit into a single data packet (" + << m_pSndBuffer->getMaxPacketLen() << " bytes max)."); + throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } - if (sndBuffersLeft() < minlen) + if (sndBuffersLeft() < iNumPktsRequired) { //>>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); + while (stillConnected() && sndBuffersLeft() < iNumPktsRequired && m_bPeerHealth) + 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() < iNumPktsRequired && m_bPeerHealth) + { + if (!m_SendBlockCond.wait_until(sendblock_lock, exptime)) + break; + } + THREAD_RESUMED(); } } @@ -5913,13 +6625,13 @@ int CUDT::sendmsg2(const char *data, int len, ref_t r_mctrl) /* * The code below is to return ETIMEOUT when blocking mode could not get free buffer in time. - * If no free buffer available in non-blocking mode, we alredy returned. If buffer availaible, + * If no free buffer available in non-blocking mode, we alredy returned. If buffer available, * we test twice if this code is outside the else section. * This fix move it in the else (blocking-mode) section */ - if (sndBuffersLeft() < minlen) + if (sndBuffersLeft() < iNumPktsRequired) { - 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,8 +6648,9 @@ 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, - log << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. " + LOGC(aslog.Fatal, + log << CONID() + << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. " "Impossible."); return 0; @@ -5948,13 +6661,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 +6676,185 @@ 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 << CONID() << "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 << CONID() + << "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 << CONID() << "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 << CONID() << "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) +// [[using locked(m_RcvBufferLock)]] +size_t srt::CUDT::getAvailRcvBufferSizeNoLock() 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); - } + return m_pRcvBuffer->getAvailSize(m_iRcvLastAck); +} - if (m_bMessageAPI) - return receiveMessage(data, len, mctrl); +bool srt::CUDT::isRcvBufferReady() const +{ + ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); +} - return receiveBuffer(data, len); +bool srt::CUDT::isRcvBufferReadyNoLock() const +{ + return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } -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 +6863,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 +6871,190 @@ 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); + const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + : 0; + leaveCS(m_RcvBufferLock); - /* 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 (!m_config.bSynRecving) { + HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN ASYNC MODE. Going to extract payload size=" << len); + enterCS(m_RcvBufferLock); + const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + : 0; + 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); - - // 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); - - // 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)"); + HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more - kicking TSBPD."); + tscond.notify_one_locked(recvguard); } - return res; + else + { + HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more"); + } + + // 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. +#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 (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady(steady_clock::now())) { /* 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); + res = m_pRcvBuffer->readMessage((data), len, &w_mctrl); + 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 +7067,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 +7098,25 @@ 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, - log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); + LOGC(aslog.Error, + log << CONID() + << "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; + ScopedLock ack_lock(m_RecvAckLock); + m_tsLastRspAckTime = steady_clock::now(); + m_iReXmitCount = 1; } // positioning... @@ -6286,10 +7162,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 +7184,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 +7201,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 +7212,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 +7226,17 @@ 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 << CONID() << "Reading from file is incompatible with TSBPD mode and would cause a deadlock"); 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 +7295,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 +7325,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(); + 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(); + // 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(); + 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 +7487,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 +7520,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 << CONID() << "updateCC: EVENT:" << TransmissionEventStr(evt)); if (evt == TEV_INIT) { @@ -6690,24 +7538,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 +7564,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 +7579,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 +7595,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,315 +7613,176 @@ 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 << CONID() << "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); - - pthread_mutex_lock(&m_SendLock); - pthread_mutex_unlock(&m_SendLock); - - pthread_mutex_lock(&m_RecvDataLock); - pthread_cond_signal(&m_RecvDataCond); - pthread_mutex_unlock(&m_RecvDataLock); - - pthread_mutex_lock(&m_RecvLock); - pthread_cond_signal(&m_RcvTsbPdCond); - pthread_mutex_unlock(&m_RecvLock); - - pthread_mutex_lock(&m_RecvDataLock); - if (!pthread_equal(m_RcvTsbPdThread, pthread_t())) - { - pthread_join(m_RcvTsbPdThread, NULL); - m_RcvTsbPdThread = pthread_t(); - } - pthread_mutex_unlock(&m_RecvDataLock); - - pthread_mutex_lock(&m_RecvLock); - pthread_mutex_unlock(&m_RecvLock); -} - -#if ENABLE_HEAVY_LOGGING -static void DebugAck(string hdr, int prev, int ack) -{ - if (!prev) - { - HLOGC(mglog.Debug, log << hdr << "ACK " << ack); - return; - } - - prev = CSeqNo::incseq(prev); - int diff = CSeqNo::seqoff(prev, ack); - if (diff < 0) - { - HLOGC(mglog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")"); - return; - } - - bool shorted = diff > 100; // sanity - if (shorted) - ack = CSeqNo::incseq(prev, 100); - - 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; - } + CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); - // [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]] + enterCS(m_SendLock); + leaveCS(m_SendLock); - // 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]; + // 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); - // 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; + // 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); - // 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 + // 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); +} - 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. +#if ENABLE_BONDING +void srt::CUDT::dropToGroupRecvBase() +{ + int32_t group_recv_base = SRT_SEQNO_NONE; + 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 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(); + } + if (group_recv_base == SRT_SEQNO_NONE) + return; - 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); - } + ScopedLock lck(m_RcvBufferLock); + int cnt = rcvDropTooLateUpTo(CSeqNo::incseq(group_recv_base)); + if (cnt > 0) + { + HLOGC(grlog.Debug, + log << CONID() << "dropToGroupRecvBase: dropped " << cnt << " packets before ACK: group_recv_base=" + << group_recv_base << " m_iRcvLastAck=" << m_iRcvLastAck + << " m_iRcvCurrSeqNo=" << m_iRcvCurrSeqNo << " m_bTsbPd=" << m_bTsbPd); + } +} +#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); +namespace srt { +#if ENABLE_HEAVY_LOGGING +static void DebugAck(string hdr, int prev, int ack) +{ + if (!prev) + { + HLOGC(xtlog.Debug, log << hdr << "ACK " << ack); + return; + } - m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); + int diff = CSeqNo::seqoff(prev, ack); + if (diff < 0) + { + HLOGC(xtlog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")"); + return; + } - CGuard::enterCS(m_StatsLock); - ++m_stats.sentACK; - ++m_stats.sentACKTotal; - CGuard::leaveCS(m_StatsLock); - } - CGuard::leaveCS(m_RcvBufferLock); + bool shorted = diff > 100; // sanity + if (shorted) + ack = CSeqNo::incseq(prev, 100); + + ostringstream ackv; + for (; prev != ack; prev = CSeqNo::incseq(prev)) + 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 +} + +void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size) +{ + CPacket ctrlpkt; + setPacketTS(ctrlpkt, steady_clock::now()); + + int nbsent = 0; + + 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, m_SourceAddr); break; @@ -7088,16 +7797,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, m_SourceAddr); - 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 +7818,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, m_SourceAddr); - 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 +7848,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, m_SourceAddr); - 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, m_SourceAddr); 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, m_SourceAddr); 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, m_SourceAddr); 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, m_SourceAddr); 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, m_SourceAddr); break; @@ -7188,15 +7900,316 @@ 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()); +} + +// [[using locked(m_RcvBufferLock)]] +bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) +{ + { + ScopedLock losslock (m_RcvLossLock); + const int32_t seq = m_pRcvLossList->getFirstLostSeq(); + if (seq != SRT_SEQNO_NONE) + { + HLOGC(xtlog.Debug, log << "NONCONT-SEQUENCE: first loss %" << seq << " (loss len=" << + m_pRcvLossList->getLossLength() << ")"); + w_seq = seq; + w_log_reason = "first lost"; + return true; + } + } + + w_seq = CSeqNo::incseq(m_iRcvCurrSeqNo); + HLOGC(xtlog.Debug, log << "NONCONT-SEQUENCE: past-recv %" << w_seq); + w_log_reason = "expected next"; + + return true; +} + + +int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) +{ + SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); + 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 + string reason; // just for "a reason" of giving particular % for ACK + +#if ENABLE_BONDING + dropToGroupRecvBase(); +#endif + + // The TSBPD thread may change the first lost sequence record (TLPKTDROP). + // To avoid it the m_RcvBufferLock has to be acquired. + UniqueLock bufflock(m_RcvBufferLock); + // The full ACK should be sent to indicate there is now available space in the RCV buffer + // since the last full ACK. It should unblock the sender to proceed further. + const bool bNeedFullAck = (m_bBufferWasFull && getAvailRcvBufferSizeNoLock() > 0); + int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). + if (!getFirstNoncontSequence((ack), (reason))) + return nbsent; + + if (m_iRcvLastAckAck == ack && !bNeedFullAck) + { + HLOGC(xtlog.Debug, + log << CONID() << "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 && !bNeedFullAck) + { + bufflock.unlock(); + ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + DebugAck(CONID() + "sendCtrl(lite): ", local_prevack, ack); + return nbsent; + } + + // IF ack %> m_iRcvLastAck + // There are new received packets to acknowledge, update related information. + if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) + { + // Sanity check if the "selected ACK" points to a sequence + // in the past for the buffer. This SHOULD NEVER HAPPEN because + // on drop the loss records should have been removed, and the last received + // sequence also can't be in the past towards the buffer. + + // NOTE: This problem has been observed when the packet sequence + // was incorrectly removed from the receiver loss list. This should + // then stay here as a condition in order to detect this problem, + // should it happen in the future. + if (CSeqNo::seqcmp(ack, m_pRcvBuffer->getStartSeqNo()) < 0) + { + LOGC(xtlog.Error, + log << CONID() << "sendCtrlAck: IPE: invalid ACK from %" << m_iRcvLastAck << " to %" << ack << " (" + << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets) buffer=%" << m_pRcvBuffer->getStartSeqNo()); + } + else + { + HLOGC(xtlog.Debug, + log << CONID() << "sendCtrlAck: %" << m_iRcvLastAck << " -> %" << ack << " (" + << CSeqNo::seqoff(m_iRcvLastAck, ack) << " packets)"); + } + + m_iRcvLastAck = ack; + +#if ENABLE_BONDING + const int32_t group_read_seq = m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()).seqno; +#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 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 << CONID() << "ACK: clip %" << m_iRcvLastAck << "-%" << 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); + + // 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()) + { + 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 && !bNeedFullAck) + { + // 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 << CONID() << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat"); + return nbsent; + } + } + else if (!bNeedFullAck) + { + // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) + LOGC(xtlog.Error, log << CONID() << "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 || bNeedFullAck) + { + // 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(); + m_bBufferWasFull = data[ACKD_BUFFERLEFT] == 0; + 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, m_SourceAddr); + DebugAck(CONID() + "sendCtrl(UMSG_ACK): ", 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 << CONID() << "sendCtrl(UMSG_ACK): " << "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 +8219,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 << CONID() << "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,16 +8313,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 - // 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 } - return; } @@ -7273,12 +8332,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,52 +8345,73 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) // // Protect packet retransmission - CGuard::enterCS(m_RecvAckLock); - - // Check the validation of the ack - if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) { - CGuard::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) << "!"); - m_bBroken = true; - m_iBrokenCounter = 0; - return; - } + ScopedLock ack_lock(m_RecvAckLock); + + // Check the validation of the ack + if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) + { + // this should not happen: attack or bug + 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; + } if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { + const int cwnd1 = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); + const bool bWasStuck = cwnd1<= getFlightSpan(); // 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 + + const int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); + if (bWasStuck && cwnd > getFlightSpan()) + { + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + HLOGC(gglog.Debug, + log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize + << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); + } } - /* - * We must not ignore full ack received by peer - * if data has been artificially acked by late packet drop. - * Therefore, a distinct ack state is used for received Ack (iSndLastFullAck) - * and ack position in send buffer (m_iSndLastDataAck). - * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) - * occures, we drop received acks (as duplicates) and do not update stats like RTT, - * which may go crazy and stay there, preventing proper stream recovery. - */ + /* + * We must not ignore full ack received by peer + * if data has been artificially acked by late packet drop. + * Therefore, a distinct ack state is used for received Ack (iSndLastFullAck) + * and ack position in send buffer (m_iSndLastDataAck). + * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) + * occures, we drop received acks (as duplicates) and do not update stats like RTT, + * which may go crazy and stay there, preventing proper stream recovery. + */ - if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) - { - // discard it if it is a repeated ACK - CGuard::leaveCS(m_RecvAckLock); - return; + if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) + { + // discard it if it is a repeated ACK + return; + } + m_iSndLastFullAck = ackdata_seqno; } - m_iSndLastFullAck = ackdata_seqno; - // // END of the new code with TLPKTDROP // - CGuard::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 +8420,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 +8428,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 +8503,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 +8516,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 +8524,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; @@ -7408,342 +8535,519 @@ void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk) // cudt->deliveryRate() instead. } - 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() << "ACK record not found, can't estimate RTT " + << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo + << ", RTT (EWMA): " << m_iSRTT << ")"); + return; + } + + if (rtt <= 0) + { + LOGC(inlog.Error, + log << CONID() << "IPE: invalid RTT estimate " << rtt + << ", possible time shift. Clock: " << SRT_SYNC_CLOCK_STR); + return; + } - // RTT EWMA - m_iRTTVar = (m_iRTTVar * 3 + abs(rtt - m_iRTT)) >> 2; - m_iRTT = (m_iRTT * 7 + rtt) >> 3; + // If increasing delay is detected. + // sendCtrl(UMSG_CGWARNING); - updateCC(TEV_ACKACK, ack); + // 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; + } - // 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); +#if SRT_DEBUG_RTT + s_rtt_trace.trace(tsArrival, "ACKACK", rtt, -1, m_bIsFirstRTTReceived, + -1, m_iSRTT, m_iRTTVar); +#endif - // update last ACK that has been received by the sender - if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) - m_iRcvLastAckAck = ack; + updateCC(TEV_ACKACK, EventVariant(ack)); - break; + // 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) + { + 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 } - case UMSG_LOSSREPORT: // 011 - Loss Report - { - int32_t *losslist = (int32_t *)(ctrlpkt.m_pcData); - size_t losslist_len = ctrlpkt.getLength() / 4; + // Update last ACK that has been received by the sender + if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) + m_iRcvLastAckAck = ack; +} + +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; + 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 = SRT_SEQNO_NONE; - // 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) + for (int i = 0, n = (int)losslist_len; i < n; ++i) { + // IF the loss is a range 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]; - // specification means that the consecutive cell has been already interpreted. + // Then it's this is a specification with HI in a consecutive cell. + 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); + HLOGC(inlog.Debug, log << CONID() << "received UMSG_LOSSREPORT: " + << losslist_lo << "-" << losslist_hi + << " (" << CSeqNo::seqlen(losslist_lo, losslist_hi) << " packets)..."); if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) || (CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0)) { - // seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq - secure = false; + // LO must not be greater than HI. + // HI must not be greater than the most recent sent seq. + LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT rng " << losslist_lo << " - " << losslist_hi + << " with last sent " << m_iSndCurrSeqNo << " - DISCARDING"); + 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 (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0) + } + // ELSE losslist_lo %< m_iSndLastAck + else { - // This should be theoretically impossible because this would mean - // that the received packet loss report informs about the loss that predates + // This should be theoretically impossible because this would mean that + // the received packet loss report informs about the loss that predates // the ACK sequence. - // 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. - num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); + // However, this can happen in these situations: + // - 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. + // - 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. + int32_t dropreq_hi = losslist_hi; + IF_HEAVY_LOGGING(const char* drop_type = "completely"); + + // IF losslist_hi %>= m_iSndLastAck + if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0) + { + HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " + << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); + num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); + dropreq_hi = CSeqNo::decseq(m_iSndLastAck); + IF_HEAVY_LOGGING(drop_type = "partially"); + } + + // In distinction to losslist, DROPREQ has always just one range, + // and the data are , with no range bit. + int32_t seqpair[2] = { losslist_lo, dropreq_hi }; + const int32_t no_msgno = 0; // We don't know. + + // 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: " << drop_type << " IGNORED with SndLastAck=%" << m_iSndLastAck + << ": %" << losslist_lo << "-" << dropreq_hi << " - sending DROPREQ"); + 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) + // ELSE the loss is a single seq + else { - HLOGF(mglog.Debug, "received UMSG_LOSSREPORT: %d (1 packet)...", losslist[i]); - - if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0) + // IF loss_seq %>= m_iSndLastAck + if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0) { - // seq_a must not be greater than the most recent sent seq - secure = false; - wrong_loss = losslist[i]; - CGuard::leaveCS(m_RecvAckLock); - break; - } + if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0) + { + LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT pkt %" << losslist[i] + << " with last sent %" << m_iSndCurrSeqNo << " - DISCARDING"); + // loss_seq must not be greater than the most recent sent seq + secure = false; + wrong_loss = losslist[i]; + break; + } - int num = m_pSndLossList->insert(losslist[i], losslist[i]); + HLOGC(inlog.Debug, + log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); + const int num = m_pSndLossList->insert(losslist[i], losslist[i]); - 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 loss_seq %< m_iSndLastAck + else + { + // In distinction to losslist, DROPREQ has always just one range, + // and the data are , with no range bit. + int32_t seqpair[2] = { losslist[i], losslist[i] }; + const int32_t no_msgno = 0; // We don't know. + HLOGC(inlog.Debug, + log << CONID() << "LOSSREPORT: IGNORED with SndLastAck=%" << m_iSndLastAck << ": %" << losslist[i] + << " - sending DROPREQ"); + sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); + } } } - CGuard::leaveCS(m_RecvAckLock); - - updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); - - if (!secure) - { - 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; - } - - // the lost packet (retransmission) should be sent out immediately - m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); - - CGuard::enterCS(m_StatsLock); - ++m_stats.recvNAK; - ++m_stats.recvNAKTotal; - CGuard::leaveCS(m_StatsLock); - - 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; - // 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 - // UMSG_ACKACK handling, but it's commented out. - - 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. - - break; + updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len)); - case UMSG_HANDSHAKE: // 000 - Handshake + if (!secure) { - 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 + 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; + } - // 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; + // the lost packet (retransmission) should be sent out immediately + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); - // 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; + enterCS(m_StatsLock); + m_stats.sndr.recvdNak.count(1); + leaveCS(m_StatsLock); +} - uint32_t kmdata[SRTDATA_MAXSIZE]; - size_t kmdatasize = SRTDATA_MAXSIZE; - bool have_hsreq = false; - if (req.m_iVersion > HS_VERSION_UDT4) +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 { - 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(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) { - 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); - } + initdata.m_iVersion = 0; + m_RejectReason = SRT_REJ_ROGUE; + initdata.m_iReqType = URQFailure(m_RejectReason); } else { - HLOGC(mglog.Debug, log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)); + // 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 { - initdata.m_iVersion = HS_VERSION_UDT4; + 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; + 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" : "")); + HLOGC(inlog.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); + 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)) + // 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, m_SourceAddr); + if (nbsent) { - 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; - } + m_tsLastSndTime.store(steady_clock::now()); } } - else + } + 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); + const int iDropCnt = m_pRcvBuffer->dropMessage(dropdata[0], dropdata[1], ctrlpkt.getMsgSeq(using_rexmit_flag), CRcvBuffer::KEEP_EXISTING); + + 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); + } + } + // 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) { - HLOGC(mglog.Debug, log << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."); + HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); + rcvtscc.notify_one(); } + } - break; + dropFromLossLists(dropdata[0], dropdata[1]); + + // 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); } +} - case UMSG_SHUTDOWN: // 101 - Shutdown - m_bShutdown = true; - m_bClosing = true; - m_bBroken = true; - m_iBrokenCounter = 60; +void srt::CUDT::processCtrlShutdown() +{ + m_bShutdown = true; + m_bClosing = true; + m_bBroken = true; + m_iBrokenCounter = 60; + + // 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! +} + +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) + { + updateAfterSrtHandshake(HS_VERSION_UDT4); + } + } + else + { + updateCC(TEV_CUSTOM, EventVariant(&ctrlpkt)); + } +} - // 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); +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; - CTimer::triggerEvent(); + 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; - 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); + case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement + processCtrlAckAck(ctrlpkt, currtime); + break; - dropFromLossLists(*(int32_t *)ctrlpkt.m_pcData, *(int32_t *)(ctrlpkt.m_pcData + 4)); + case UMSG_LOSSREPORT: // 011 - Loss Report + processCtrlLossReport(ctrlpkt); + break; - // 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); - } + case UMSG_CGWARNING: // 100 - Delay Warning + // One way packet delay is increasing, so decrease the sending rate + 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 + // UMSG_ACKACK handling, but it's commented out. + + break; + + case UMSG_KEEPALIVE: // 001 - Keep-alive + processKeepalive(ctrlpkt, currtime); + break; + case UMSG_HANDSHAKE: // 000 - Handshake + processCtrlHS(ctrlpkt); + break; + + case UMSG_SHUTDOWN: // 101 - Shutdown + processCtrlShutdown(); + break; + + case UMSG_DROPREQ: // 111 - Msg drop request + 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,65 @@ 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. + m_pRcvBuffer->setPeerRexmitFlag(m_bPeerRexmitFlag); + + // 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); + m_pRcvBuffer->setTsbPdMode(m_tsRcvPeerStartTime, false, milliseconds_from(m_iTsbPdDelay_ms)); - HLOGF(mglog.Debug, - "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03u secs", - m_iTsbPdDelay_ms / 1000, - m_iTsbPdDelay_ms % 1000); + HLOGC(cnlog.Debug, log << "AFTER HS: Set Rcv TsbPd mode" + << (m_bGroupTsbPd ? " (AS GROUP MEMBER)" : "") + << ": delay=" << (m_iTsbPdDelay_ms / 1000) << "." << (m_iTsbPdDelay_ms % 1000) + << "s RCV START: " << FormatTime(m_tsRcvPeerStartTime).c_str()); } else { - HLOGC(mglog.Debug, log << "AFTER HS: Rcv TsbPd mode not set"); + HLOGC(cnlog.Debug, log << CONID() << "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); + HLOGC(cnlog.Debug, log << "AFTER HS: Set Snd TsbPd mode " + << (m_bPeerTLPktDrop ? "with" : "without") + << " TLPktDrop: delay=" << (m_iPeerTsbPdDelay_ms/1000) << "." << (m_iPeerTsbPdDelay_ms%1000) + << "s START TIME: " << FormatTime(m_stats.tsStartTime).c_str()); } else { - HLOGC(mglog.Debug, log << "AFTER HS: Snd TsbPd mode not set"); + HLOGC(cnlog.Debug, log << CONID() << "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 << CONID() << "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 +9138,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 << CONID() << "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 +9176,99 @@ void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv) } } -int CUDT::packLostData(CPacket &packet, uint64_t &origintime) +int srt::CUDT::packLostData(CPacket& w_packet) { // 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 << CONID() << "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 << CONID() << "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; } - int msglen; + 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; + } + } - const int payload = m_pSndBuffer->readData(&(packet.m_pcData), offset, packet.m_iMsgNo, origintime, msglen); - SRT_ASSERT(payload != 0); + int msglen; + steady_clock::time_point tsOrigin; + const int payload = m_pSndBuffer->readData(offset, (w_packet), (tsOrigin), (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 << CONID() << "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; + // The packet has been ecrypted, thus the authentication tag is expected to be stored + // in the SND buffer as well right after the payload. + if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + { + w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX); + } + // 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,8 +9276,13 @@ 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; } + setDataPacketTS(w_packet, tsOrigin); + +#ifdef ENABLE_MAXREXMITBW + m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength()); +#endif return payload; } @@ -7941,235 +9290,466 @@ 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 +{ + typedef srt::sync::steady_clock steady_clock; + +public: + snd_logger() {} + + ~snd_logger() + { + ScopedLock lck(m_mtx); + m_fout.close(); + } + + 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 + +void srt::CUDT::setPacketTS(CPacket& p, const time_point& ts) +{ + enterCS(m_StatsLock); + const time_point tsStart = m_stats.tsStartTime; + leaveCS(m_StatsLock); + p.m_iTimeStamp = makeTS(ts, tsStart); +} + +void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) +{ + enterCS(m_StatsLock); + const time_point tsStart = m_stats.tsStartTime; + leaveCS(m_StatsLock); + + if (!m_bPeerTsbPd) + { + // If TSBPD is disabled, use the current time as the source (timestamp using the sending time). + p.m_iTimeStamp = makeTS(steady_clock::now(), tsStart); + return; + } + + // TODO: Might be better for performance to ensure this condition is always false, and just use SRT_ASSERT here. + if (ts < tsStart) + { + p.m_iTimeStamp = makeTS(steady_clock::now(), tsStart); + LOGC(qslog.Warn, + log << CONID() << "setPacketTS: reference time=" << FormatTime(ts) + << " is in the past towards start time=" << FormatTime(tsStart) + << " - setting NOW as reference time for the data packet"); + return; + } + + // Use the provided source time for the timestamp. + p.m_iTimeStamp = makeTS(ts, tsStart); +} + +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 (tsNextPacket != time_point()) + { + // Can send original packet, so just send it + return false; + } + +#ifdef ENABLE_MAXREXMITBW + m_SndRexmitRate.addSample(tnow, 0, 0); // Update the estimation. + const int64_t iRexmitRateBps = m_SndRexmitRate.getRate(); + const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW; + if (iRexmitRateLimitBps >= 0 && iRexmitRateBps > iRexmitRateLimitBps) + { + // Too many retransmissions, so don't send anything. + // TODO: When to wake up next time? + return false; + } +#endif + +#if SRT_DEBUG_TRACE_SND + g_snd_logger.state.canRexmit = true; +#endif + return true; +} + +bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, sockaddr_any& w_src_addr) { - int payload = 0; - bool probe = false; - uint64_t origintime = 0; - bool new_packet_packed = false; - bool filter_ctl_pkt = false; + int payload = 0; + bool probe = false; + bool new_packet_packed = false; + + const steady_clock::time_point enter_time = steady_clock::now(); - int kflg = EK_NOENC; + w_nexttime = enter_time; - uint64_t entertime_tk; - CTimer::rdtsc(entertime_tk); + if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime) + { + m_tdSendTimeDiff = m_tdSendTimeDiff.load() + (enter_time - m_tsNextSendTime); + } -#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); - } -#endif - if ((0 != m_ullTargetTime_tk) && (entertime_tk > m_ullTargetTime_tk)) - m_ullTimeDiff_tk += entertime_tk - m_ullTargetTime_tk; + 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 false; - string reason; + payload = isRetransmissionAllowed(enter_time) + ? packLostData((w_packet)) + : 0; - payload = packLostData(packet, origintime); + IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) if (payload > 0) { - reason = "reXmit"; + IF_HEAVY_LOGGING(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(); - reason = "filter"; - filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set + HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); + payload = (int) w_packet.getLength(); + IF_HEAVY_LOGGING(reason = "filter"); // 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)) { - 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 false; } + new_packet_packed = true; - reason = "normal"; - } + // every 16 (0xF) packets, a packet pair is sent + if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) + probe = true; - // Normally packet.m_iTimeStamp field is set exactly here, - // usually as taken from m_StartTime 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 - // timestamp. - if (!filter_ctl_pkt) - { - if (m_bPeerTsbPd) - { - /* - * 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 - * doesn't screw up the start time on the other side. - */ - if (origintime >= m_stats.startTime) - packet.m_iTimeStamp = int(origintime - m_stats.startTime); - else - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - } - else - { - packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime); - } + payload = (int) w_packet.getLength(); + IF_HEAVY_LOGGING(reason = "normal"); } - 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; // Set the destination SRT socket ID. 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 << CONID() << "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 immediately + 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 + } + HLOGC(qslog.Debug, log << "packData: Setting source address: " << m_SourceAddr.str()); + w_src_addr = m_SourceAddr; + w_nexttime = m_tsNextSendTime; + + return payload >= 0; // XXX shouldn't be > 0 ? == 0 is only when buffer range exceeded. +} + +bool srt::CUDT::packUniqueData(CPacket& w_packet) +{ + // 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 << CONID() << "packUniqueData: 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; + time_point tsOrigin; + const int pld_size = m_pSndBuffer->readData((w_packet), (tsOrigin), kflg, (pktskipseqno)); + if (pktskipseqno) + { + // Some packets were skipped due to TTL expiry. + m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo, pktskipseqno); + HLOGC(qslog.Debug, log << "packUniqueData: reading skipped " << pktskipseqno << " seq up to %" << m_iSndCurrSeqNo + << " due to TTL expiry"); + } + + if (pld_size == 0) + { + HLOGC(qslog.Debug, log << "packUniqueData: nothing extracted from the buffer"); + 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() << "packUniqueData: 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: packUniqueData: 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() << "packUniqueData: 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() << "packUniqueData: 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; + } + + // Set missing fields before encrypting the packet, because those fields might be used for encryption. + w_packet.m_iID = m_PeerID; // Destination SRT Socket ID + setDataPacketTS(w_packet, tsOrigin); + + 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 << CONID() << "ENCRYPT FAILED - packet won't be sent, size=" << pld_size); + return false; + } + + checkSndKMRefresh(); } - 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,28 +9758,25 @@ 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; - vector seqbuffer; seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i) @@ -8207,118 +9784,450 @@ 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); + HLOGC(qrlog.Debug, log << "lost packet " << i->first << ": sending LOSSREPORT"); } else { seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST); seqbuffer.push_back(i->second); - HLOGF(mglog.Debug, - "lost packets %d-%d (%d packets): sending LOSSREPORT", - i->first, - i->second, - 1 + CSeqNo::seqcmp(i->second, i->first)); + HLOGC(qrlog.Debug, log << "lost packets " << i->first << "-" << i->second + << " (" << (1 + CSeqNo::seqcmp(i->second, i->first)) << " packets): sending LOSSREPORT"); } } 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 management 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 srt::CUDT::checkLazySpawnTsbPdThread() +{ + const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; + + if (need_tsbpd && !m_RcvTsbPdThread.joinable()) + { + 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 << setfill('0') << setw(2) << m_SocketID; + std::string s = tns1.str(); + tns2 << "SRT:TsbPd:@" << s.substr(s.size()-2, 2); + const string thname = tns2.str(); +#else + const string thname = "SRT:TsbPd"; +#endif + if (!StartThread(m_RcvTsbPdThread, CUDT::tsbpd, this, thname)) + return -1; + } + + return 0; +} + +CUDT::time_point srt::CUDT::getPktTsbPdTime(void*, const CPacket& packet) +{ + return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); +} + +SRT_ATR_UNUSED static const char *const s_rexmitstat_str[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; + +// [[using locked(m_RcvBufferLock)]] +int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) +{ + bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added + + w_new_inserted = false; + const int32_t bufseq = m_pRcvBuffer->getStartSeqNo(); + + // 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::const_iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) + { + CUnit * u = *unitIt; + CPacket &rpkt = u->m_Packet; + const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; + const bool retransmitted = pktrexmitflag == 1; + + bool adding_successful = true; + + const int32_t bufidx = CSeqNo::seqoff(bufseq, rpkt.m_iSeqNo); + + IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); + + // bufidx < 0: the packet is in the past for the buffer + // seqno <% m_iRcvLastAck : the sequence may be within the buffer, + // but if so, it is in the acknowledged-but-not-retrieved area. + + // NOTE: if we have a situation when there are any packets in the + // acknowledged area, but they aren't retrieved, this area DOES NOT + // contain any losses. So a packet in this area is at best a duplicate. + + // In case when a loss would be abandoned (TLPKTDROP), there must at + // some point happen to be an empty first cell in the buffer, followed + // somewhere by a valid packet. If this state is achieved at some point, + // the acknowledgement sequence should be equal to the beginning of the + // buffer. Then, when TSBPD decides to drop these initial empty cells, + // we'll have: (m_iRcvLastAck <% buffer->getStartSeqNo()) - and in this + // case (bufidx < 0) condition will be satisfied also for this case. + // + // The only case when bufidx > 0, but packet seq is <% m_iRcvLastAck + // is when the packet sequence is within the initial contiguous area, + // which never contains losses, so discarding this packet does not + // discard a loss coverage, even if this were past ACK. + + if (bufidx < 0 || CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvLastAck) < 0) + { + time_point pts = getPktTsbPdTime(NULL, rpkt); + + enterCS(m_StatsLock); + const double bltime = (double) CountIIR( + uint64_t(m_stats.traceBelatedTime) * 1000, + count_microseconds(steady_clock::now() - pts), 0.2); + + m_stats.traceBelatedTime = bltime / 1000.0; + m_stats.rcvr.recvdBelated.count(rpkt.getLength()); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, + log << CONID() << "RECEIVED: %" << rpkt.m_iSeqNo << " bufidx=" << bufidx << " (BELATED/" + << s_rexmitstat_str[pktrexmitflag] << ") with ACK %" << m_iRcvLastAck + << " FLAGS: " << rpkt.MessageFlagStr()); + continue; + } + + if (bufidx >= int(m_pRcvBuffer->capacity())) + { + // This is already a sequence discrepancy. Probably there could be found + // some way to make it continue reception by overriding the sequence and + // make a kinda TLKPTDROP, but there has been found no reliable way to do this. + if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) + { + // Only in live mode. In File mode this shall not be possible + // because the sender should stop sending in this situation. + // In Live mode this means that there is a gap between the + // 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(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION." + " %" << rpkt.m_iSeqNo + << " buffer=(%" << bufseq + << ":%" << m_iRcvCurrSeqNo // -1 = size to last index + << "+%" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) + << "), " << (m_pRcvBuffer->capacity() - bufidx + 1) + << " past max. Reception no longer possible. REQUESTING TO CLOSE."); + + return -2; + } + else + { + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + << ", insert offset " << bufidx << ". " + << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) + ); + + return -1; + } + } + + const int buffer_add_result = m_pRcvBuffer->insert(u); + if (buffer_add_result < 0) + { + // The insert() result is -1 if at the position evaluated from this packet's + // sequence number there already is a packet. + // So this packet is "redundant". + IF_HEAVY_LOGGING(exc_type = "UNACKED"); + adding_successful = false; + } + else + { + w_new_inserted = true; + + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; + if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) + { + // TODO: reset and restore the timestamp if TSBPD is disabled. + // Reset retransmission flag (must be excluded from GCM auth tag). + u->m_Packet.setRexmitFlag(false); + const EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP; + u->m_Packet.setRexmitFlag(retransmitted); // Recover the flag. + + if (rc != ENCS_CLEAR) + { + adding_successful = false; + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + + // If TSBPD is disabled, then SRT either operates in buffer mode, of in message API without a restriction + // of a single message packet. In that case just dropping a packet is not enough. + // In message mode the whole message has to be dropped. + // However, when decryption fails the message number in the packet cannot be trusted. + // The packet has to be removed from the RCV buffer based on that pkt sequence number, + // and the sequence number itself must go into the RCV loss list. + // See issue ##2626. + SRT_ASSERT(m_bTsbPd); + + // Drop the packet from the receiver buffer. + // The packet was added to the buffer based on the sequence number, therefore sequence number should be used to drop it from the buffer. + // A drawback is that it would prevent a valid packet with the same sequence number, if it happens to arrive later, to end up in the buffer. + const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); + + const steady_clock::time_point tnow = steady_clock::now(); + ScopedLock lg(m_StatsLock); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * rpkt.getLength(), iDropCnt)); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); + if (frequentLogAllowed(tnow)) + { + LOGC(qrlog.Warn, log << CONID() << "Decryption failed (seqno %" << u->m_Packet.getSeqNo() << "), dropped " + << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "."); + m_tsLogSlowDown = tnow; + } + } + } + else if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + { + // Unencrypted packets are not allowed. + const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); + + const steady_clock::time_point tnow = steady_clock::now(); + ScopedLock lg(m_StatsLock); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt* rpkt.getLength(), iDropCnt)); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); + if (frequentLogAllowed(tnow)) + { + LOGC(qrlog.Warn, log << CONID() << "Packet not encrypted (seqno %" << u->m_Packet.getSeqNo() << "), dropped " + << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "."); + m_tsLogSlowDown = tnow; + } + } + } + + 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 << ")"; + else + expectspec << "ACCEPTED"; + + std::ostringstream bufinfo; + + if (m_pRcvBuffer) + { + // XXX Fix this when the end of contiguous region detection is added. + const int ackidx = std::max(0, CSeqNo::seqoff(m_pRcvBuffer->getStartSeqNo(), m_iRcvLastAck)); + + bufinfo << " BUF.s=" << m_pRcvBuffer->capacity() + << " avail=" << (int(m_pRcvBuffer->capacity()) - ackidx) + << " buffer=(%" << bufseq + << ":%" << m_iRcvCurrSeqNo // -1 = size to last index + << "+%" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) + << ")"; + } + + // Empty buffer info in case of groupwise receiver. + // There's no way to obtain this information here. + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: %" << rpkt.m_iSeqNo + << bufinfo.str() + << " RSL=" << expectspec.str() + << " SN=" << s_rexmitstat_str[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif + + // Decryption should have made the crypto flags EK_NOENC. + // Otherwise it's an error. + if (adding_successful) + { + HLOGC(qrlog.Debug, + log << CONID() + << "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); + int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); + w_srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); + HLOGC(qrlog.Debug, log << "pkt/LOSS DETECTED: %" << seqlo << " - %" << seqhi); + } + } + + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. + if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } } + + return 0; } -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()); + // We are receiving data, start tsbpd thread if TsbPd is enabled - if (m_bTsbPd && pthread_equal(m_RcvTsbPdThread, pthread_t())) + if (-1 == checkLazySpawnTsbPdThread()) { - 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) - return -1; + return -1; } const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; + const bool retransmitted = pktrexmitflag == 1; #if ENABLE_HEAVY_LOGGING - static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; string rexmit_reason; #endif - if (pktrexmitflag == 1) + if (retransmitted) { // 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 "; + ScopedLock lock(m_RcvLossLock); 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(); // Retransmitted and unordered packets do not provide expected measurement. // We expect the 16th and 17th packet to be sent regularly, // 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; bool was_sent_in_order = true; - bool reorder_prevent_lossreport = false; // If the peer doesn't understand REXMIT flag, send rexmit request // always immediately. @@ -8326,34 +10235,26 @@ int CUDT::processData(CUnit *in_unit) if (m_bPeerRexmitFlag) initial_loss_ttl = m_iReorderTolerance; - // After introduction of packet filtering, the "recordable loss detection" - // does not exactly match the true loss detection. When a FEC filter is - // working, for example, then getting one group filled with all packet but - // the last one and the FEC control packet, in this special case this packet - // won't be notified at all as lost because it will be recovered by the - // filter immediately before anyone notices what happened (and the loss - // detection for the further functionality is checked only afterwards, - // and in this case the immediate recovery makes the loss to not be noticed - // at all). - // - // Because of that the check for losses must happen BEFORE passing the packet - // 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 + // Track packet loss in statistics early, because a packet filter (e.g. FEC) might recover it later on, + // supply the missing packet(s), and the loss will no longer be visible for the code that follows. + if (packet.getMsgSeq(m_bPeerRexmitFlag) != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing { - int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); + const 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, - log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " + 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 << CONID() << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " << CSeqNo::decseq(packet.m_iSeqNo) << "]"); } @@ -8364,188 +10265,108 @@ int CUDT::processData(CUnit *in_unit) } } - { - // 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); - - // 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, - log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) - << " loss to report, " - << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" - : "REPORT ONLY THOSE")); - } - else - { - // Stuff in just one packet that has come in. - incoming.push_back(in_unit); - } - - bool excessive = true; // stays true unless it was successfully added + // [[using locked()]]; // (NOTHING locked) - // Needed for possibly check for needsQuickACK. - bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0); +#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; - // 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) + // 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 pointer). + if (gi != NULL) { - CUnit * u = *i; - CPacket &rpkt = u->m_Packet; - - // m_iRcvLastSkipAck is the base sequence number for the receiver buffer. - // This is the offset in the buffer; if this is negative, it means that - // this sequence is already in the past and the buffer is not interested. - // Meaning, this packet will be rejected, even if it could potentially be - // one of missing packets in the transmission. - int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo); - - IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); - - if (offset < 0) + if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely { - 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, - log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/" - << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr()); - continue; + HLOGC(qrlog.Debug, + log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] + << " -> RUNNING."); + gi->rcvstate = SRT_GST_RUNNING; } - - const int avail_bufsize = m_pRcvBuffer->getAvailBufSize(); - if (offset >= avail_bufsize) + else { - // This is already a sequence discrepancy. Probably there could be found - // some way to make it continue reception by overriding the sequence and - // make a kinda TLKPTDROP, but there has been found no reliable way to do this. - if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty()) - { - // Only in live mode. In File mode this shall not be possible - // because the sender should stop sending in this situation. - // In Live mode this means that there is a gap between the - // 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()); - - // 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(); - 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()); - return -1; - } + HLOGC(qrlog.Debug, + log << CONID() << "processData: IN-GROUP rcv state transition NOT DONE - state:" + << srt_log_grp_state[gi->rcvstate]); } + } + } +#endif - bool adding_successful = true; - if (m_pRcvBuffer->addData(*i, offset) < 0) - { - // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. - // So this packet is "redundant". - IF_HEAVY_LOGGING(exc_type = "UNACKED"); - adding_successful = false; - } - else - { - IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); - excessive = false; - if (u->m_Packet.getMsgCryptoFlags()) - { - EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt(Ref(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; - } + bool new_inserted = false; + + if (m_PacketFilter) + { + // Stuff this data into the filter + m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); + HLOGC(qrlog.Debug, + log << CONID() << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) + << " loss to report, " + << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" + : "REPORT ONLY THOSE")); + } + else + { + // Stuff in just one packet that has come in. + incoming.push_back(in_unit); + } + + { + // 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() + UniqueLock recvbuf_acklock(m_RcvBufferLock); + // Needed for possibly check for needsQuickACK. + const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_pRcvBuffer->getStartSeqNo()) < 0); - // Log message degraded to debug because it may happen very often - HLOGC(dlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data."); - adding_successful = false; - IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); - } - } - } + const int res = handleSocketPacketReception(incoming, + (new_inserted), + (was_sent_in_order), + (srt_loss_seqs)); - 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 (res == -2) + { + // 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.unlock(); + processClose(); - // Decryption should have made the crypto flags EK_NOENC. - // Otherwise it's an error. - if (adding_successful) - { - HLOGC(dlog.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); - int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); + return -1; + } - srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); + if (res == -1) + { + return -1; + } - if (initial_loss_ttl) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. + if (!srt_loss_seqs.empty()) + { + ScopedLock lock(m_RcvLossLock); - for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) - { - m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); - } - HLOGC(mglog.Debug, - log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs) - << " tolerance: " << initial_loss_ttl); - reorder_prevent_lossreport = true; - } - } - } + HLOGC(qrlog.Debug, + log << CONID() << "processData: RECORDING LOSS: " << Printable(srt_loss_seqs) + << " tolerance=" << initial_loss_ttl); - // Update the current largest sequence number that has been received. - // Or it is a retransmitted packet, remove it from receiver loss list. - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0) - { - m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received - } - else + for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i) { - unlose(rpkt); // was BELATED or RETRANSMITTED - was_sent_in_order &= 0 != pktrexmitflag; + m_pRcvLossList->insert(i->first, i->second); + if (initial_loss_ttl) + { + // The LOSSREPORT will be sent after initial_loss_ttl. + m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + } } } @@ -8563,15 +10384,14 @@ 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()); } } - if (excessive) + if (!new_inserted) { return -1; } - } // End of recvbuf_acklock if (m_bClosing) @@ -8594,34 +10414,21 @@ int CUDT::processData(CUnit *in_unit) if (!srt_loss_seqs.empty()) { - // A loss is detected - { - // 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."); - // if record_loss == false, nothing will be contained here - // Insert lost sequence numbers to the receiver loss list - CGuard 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 - m_pRcvLossList->insert(i->first, i->second); - } - } - const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS; - if (!reorder_prevent_lossreport && report_recorded_loss) + if (!initial_loss_ttl && report_recorded_loss) { - HLOGC(mglog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); + HLOGC(qrlog.Debug, log << CONID() << "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 << CONID() << "loss: signaling TSBPD cond"); + CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); + } + else + { + HLOGC(qrlog.Debug, log << CONID() << "loss: socket is not TSBPD, not signaling"); } } @@ -8632,14 +10439,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 << CONID() << "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 << CONID() << "loss: signaling TSBPD cond"); + CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); } } @@ -8650,7 +10456,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 +10469,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,15 +10479,12 @@ 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, - "Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT", - i->seq[0], - i->seq[1], - CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1); + HLOGC(qrlog.Debug, log << "Packet seq " << i->seq[0] << "-" << i->seq[1] + << " (" << (CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1) << " packets) considered lost - sending LOSSREPORT"); addLossRecord(lossdata, i->seq[0], i->seq[1]); } @@ -8694,17 +10497,13 @@ 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, - "STILL %" PRIzu " FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d", - m_FreshLoss.size(), - i->seq[0], - i->seq[1], - 1 + CSeqNo::seqoff(i->seq[0], i->seq[1]), - i->ttl); + HLOGC(qrlog.Debug, log << "STILL " << m_FreshLoss.size() << " FRESH LOSS RECORDS, FIRST: " + << i->seq[0] << "-" << i->seq[1] + << " (" << (1 + CSeqNo::seqoff(i->seq[0], i->seq[1])) << ") TTL: " << i->ttl); } // Phase 2: rest of the records should have TTL decreased. @@ -8714,7 +10513,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,12 +10529,11 @@ 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, - "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d", - m_iReorderTolerance); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, log << "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to " + << m_iReorderTolerance); } } } @@ -8743,6 +10541,59 @@ int CUDT::processData(CUnit *in_unit) return 0; } +#if ENABLE_BONDING + +// NOTE: this is updated from the value of m_iRcvLastAck, +// which might be past the buffer and potentially cause setting +// the value to the last received and re-requiring retransmission. +// Worst case is that there could be a few packets to tear the transmission +// even more (as there will be likely no time to recover them), but +// if the transmission was already torn in the previously active link +// this shouldn't be a problem that these packets won't be recovered +// after activating the second link, although will be retried this way. +void srt::CUDT::updateIdleLinkFrom(CUDT* source) +{ + int bufseq; + { + ScopedLock lg (m_RcvBufferLock); + bufseq = source->m_pRcvBuffer->getStartSeqNo(); + } + 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; + } + + int32_t new_last_rcv = source->m_iRcvLastAck; + + if (CSeqNo::seqcmp(new_last_rcv, bufseq) < 0) + { + // Emergency check whether the last ACK was behind the + // buffer. This may happen when TSBPD dropped empty cells. + // This may cause that the newly activated link may derive + // these empty cells which will never be recovered. + new_last_rcv = bufseq; + } + + // 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 << ": %" << new_last_rcv); + setInitialRcvSeq(new_last_rcv); +} + +#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 +10607,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,20 +10630,17 @@ void CUDT::unlose(const CPacket &packet) was_reordered = !packet.getRexmitFlag(); if (was_reordered) { - HLOGF(mglog.Debug, "received out-of-band packet seq %d", sequence); + HLOGC(qrlog.Debug, log << "received out-of-band packet %" << 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, - "Belated by %d seqs - Reorder tolerance %s %d", - seqdiff, - (new_tolerance == m_iReorderTolerance) ? "REMAINS with" : "increased to", - new_tolerance); + const int new_tolerance = min(seqdiff, m_config.iMaxReorderTolerance); + HLOGC(qrlog.Debug, log << "Belated by " << seqdiff << " seqs - Reorder tolerance " + << (new_tolerance == m_iReorderTolerance ? "REMAINS with " : "increased to ") << new_tolerance); m_iReorderTolerance = new_tolerance; has_increased_tolerance = true; // Yes, even if reorder tolerance is already at maximum - this prevents decreasing tolerance. @@ -8800,12 +10648,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); + HLOGC(qrlog.Debug, log << "received reXmitted or belated packet seq " << sequence << " (distinction not supported by peer)"); } // Don't do anything if "belated loss report" feature is not used. @@ -8819,55 +10667,10 @@ void CUDT::unlose(const CPacket &packet) if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; - size_t i = 0; - int had_ttl = 0; - for (i = 0; i < m_FreshLoss.size(); ++i) - { - had_ttl = m_FreshLoss[i].ttl; - switch (m_FreshLoss[i].revoke(sequence)) - { - case CRcvFreshLoss::NONE: - continue; // Not found. Search again. - - case CRcvFreshLoss::STRIPPED: - goto breakbreak; // Found and the modification is applied. We're done here. - - case CRcvFreshLoss::DELETE: - // No more elements. Kill it. - m_FreshLoss.erase(m_FreshLoss.begin() + i); - // Every loss is unique. We're done here. - goto breakbreak; - - case CRcvFreshLoss::SPLIT: - // Oh, this will be more complicated. This means that it was in between. - { - // So create a new element that will hold the upper part of the range, - // and this one modify to be the lower part of the range. - - // Keep the current end-of-sequence value for the second element - int32_t next_end = m_FreshLoss[i].seq[1]; - - // seq-1 set to the end of this element - m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence); - // seq+1 set to the begin of the next element - int32_t next_begin = CSeqNo::incseq(sequence); - - // Use position of the NEXT element because insertion happens BEFORE pointed element. - // Use the same TTL (will stay the same in the other one). - m_FreshLoss.insert(m_FreshLoss.begin() + i + 1, - CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl)); - } - goto breakbreak; - } - } - - // Could have made the "return" instruction instead of goto, but maybe there will be something - // to add in future, so keeping that. -breakbreak:; - - if (i != m_FreshLoss.size()) + int had_ttl = 0; + if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl))) { - HLOGF(mglog.Debug, "sequence %d removed from belated lossreport record", sequence); + HLOGC(qrlog.Debug, log << "sequence " << sequence << " removed from belated lossreport record"); } if (was_reordered) @@ -8880,7 +10683,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); + HLOGC(qrlog.Debug, log << "... arrived at TTL " << had_ttl << " case " << m_iConsecEarlyDelivery); // After 10 consecutive if (m_iConsecEarlyDelivery >= 10) @@ -8889,13 +10692,11 @@ breakbreak:; if (m_iReorderTolerance > 0) { m_iReorderTolerance--; - CGuard::enterCS(m_StatsLock); + enterCS(m_StatsLock); m_stats.traceReorderDistance--; - CGuard::leaveCS(m_StatsLock); - HLOGF(mglog.Debug, - "... reached %d times - decreasing tolerance to %d", - m_iConsecEarlyDelivery, - m_iReorderTolerance); + leaveCS(m_StatsLock); + HLOGC(qrlog.Debug, log << "... reached " << m_iConsecEarlyDelivery + << " times - decreasing tolerance to " << m_iReorderTolerance); } } } @@ -8903,12 +10704,50 @@ breakbreak:; } } -void CUDT::dropFromLossLists(int32_t from, int32_t to) +void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) { - CGuard lg(m_RcvLossLock); - m_pRcvLossList->remove(from, to); + ScopedLock lg(m_RcvLossLock); + + IF_HEAVY_LOGGING(bool autodetected = false); + int32_t begin SRT_ATR_UNUSED; + if (from == SRT_SEQNO_NONE) + { + begin = m_pRcvLossList->removeUpTo(to); + IF_HEAVY_LOGGING(autodetected = true); + } + else + { + begin = from; + m_pRcvLossList->remove(from, to); + } + +#if ENABLE_HEAVY_LOGGING + ostringstream range; + if (begin == SRT_SEQNO_NONE) + { + range << "no"; + } + else + { + int off = CSeqNo::seqoff(begin, to); + if (off < 0) + { + range << "WEIRD NUMBER OF"; + } + else + { + range << (off + 1); + } + } + + static const char* const beginwhere[2] = {"explicit", "detected"}; - HLOGF(mglog.Debug, "TLPKTDROP seq %d-%d (%d packets)", from, to, CSeqNo::seqoff(from, to)); + const char* const reqtype = (from == SRT_SEQNO_NONE) ? "TLPKTDROP" : "DROPREQ"; + + HLOGC(qrlog.Debug, log << CONID() << "DROP PER " << reqtype << " %" << begin + << "[" << beginwhere[1*autodetected] << "]-" << to << " (" + << range.str() << " packets)"); +#endif if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) return; @@ -8946,7 +10785,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 +10795,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 +10842,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 << CONID() << "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 << CONID() << "processConnectRequest: ... NOT. Rejecting because closing."); return m_RejectReason; } @@ -9025,11 +10866,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 << CONID() << "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,9 +10881,9 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (packet.getLength() < exp_len) { m_RejectReason = SRT_REJ_ROGUE; - HLOGC(mglog.Debug, - log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len - << ")"); + HLOGC(cnlog.Debug, + log << CONID() << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() + << " (expected: " << exp_len << ")"); return m_RejectReason; } @@ -9052,7 +10893,8 @@ 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 << CONID() << "processConnectRequest: the packet received as handshake is not a handshake message"); return m_RejectReason; } @@ -9070,14 +10912,22 @@ 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 << CONID() << "processConnectRequest: new cookie: " << hex << cookie_val); + + // Remember the incoming destination address here and use it as a source + // address when responding. It's not possible to record this address yet + // because this happens still in the frames of the listener socket. Only + // when processing switches to the newly spawned accepted socket can the + // address be recorded in its m_SourceAddr field. + sockaddr_any use_source_addr = packet.udpDestAddr(); // 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 << CONID() << "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,16 +10953,20 @@ 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, - log << "processConnectRequest: " << (whether ? "" : "NOT ") - << " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen); + 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 << CONID() << "processConnectRequest: " << (whether ? "" : "NOT ") + << " 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); - m_pSndQueue->sendto(addr, packet); + hs.store_to((packet.m_pcData), (size)); + setPacketTS(packet, steady_clock::now()); + + // Display the HS before sending it to peer + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (i): " << hs.show()); + + m_pSndQueue->sendto(addr, packet, use_source_addr); return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code. } @@ -9121,8 +10975,16 @@ 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, - log << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie..."); + if (!hs.valid()) + { + LOGC(cnlog.Error, log << CONID() << "processConnectRequest: ROGUE HS RECEIVED. Rejecting"); + m_RejectReason = SRT_REJ_ROGUE; + return SRT_REJ_ROGUE; + } + + HLOGC(cnlog.Debug, + log << CONID() << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) + << " - checking cookie..."); if (hs.m_iCookie != cookie_val) { cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie @@ -9130,15 +10992,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 << CONID() << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); return m_RejectReason; } - HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."); } else { - HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); } int32_t id = hs.m_iID; @@ -9182,21 +11044,28 @@ SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &pac if (!accepted_hs) { - HLOGC(mglog.Debug, - log << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason + HLOGC(cnlog.Debug, + log << CONID() << "processConnectRequest: version/type mismatch. Sending REJECT code:" << 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); - m_pSndQueue->sendto(addr, packet); + setPacketTS(packet, steady_clock::now()); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (e): " << hs.show()); + m_pSndQueue->sendto(addr, packet, use_source_addr); } 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 +11077,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=" + LOGC(cnlog.Warn, log << "processConnectRequest: rsp(REJECT): " << 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 response 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 << CONID() << "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 << CONID() << "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 << CONID() << "processConnectRequest: 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, use_source_addr); + } + 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 << CONID() << "processConnectRequest: SENDING HS (a): " << hs.show()); + m_pSndQueue->sendto(addr, packet, use_source_addr); + } } } - LOGC(mglog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); + LOGC(cnlog.Note, log << CONID() << "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 +11197,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 +11230,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 +11249,96 @@ 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). */ + enterCS(m_RcvLossLock); const int loss_len = m_pRcvLossList->getLossLength(); + leaveCS(m_RcvLossLock); + 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 << CONID() << "CONNECTION EXPIRED after " << count_milliseconds(currtime - last_rsp_time) << "ms"); m_bClosing = true; m_bBroken = true; m_iBrokenCounter = 30; @@ -9382,19 +11346,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, - log << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed=" - << ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "/" << (+PEER_IDLE_TMO_US) << "us"); + HLOGC(xtlog.Debug, + log << CONID() << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) + << " elapsed=" << (count_microseconds(currtime - last_rsp_time)) << "/" << (+PEER_IDLE_TMO_US) << "us"); ++m_iEXPCount; @@ -9402,80 +11362,81 @@ 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. - */ + // Check if HSv4 should be retransmitted, and if KM_REQ should be resent if the side is INITIATOR. + checkSndTimers(); - 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; + // 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. + // - if (currtime_tk <= (m_ullLastRspAckTime_tk + exp_int)) - return; + { + ScopedLock ack_lock(m_RecvAckLock); + 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 <= (m_tsLastRspAckTime + microseconds_from(exp_int_us))) + return; + } // If there is no unacknowledged data in the sending buffer, // then there is nothing to retransmit. 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)"); @@ -9484,106 +11445,187 @@ void CUDT::checkRexmitTimer(uint64_t currtime_tk) ++m_iReXmitCount; - 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 << CONID() << "updateBrokenConnection: a pending link was broken - will be removed"); + pending_broken = true; + } + else + { + HLOGC(gmlog.Debug, + log << CONID() << "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 +11633,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 +11656,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 +11718,107 @@ 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 - << " - PROTOCOL ERROR, REJECTING"); + LOGC(cnlog.Error, + log << CONID() << "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 << CONID() << "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 + + // Set the default value + acore->m_RejectReason = SRT_REJX_FALLBACK; 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; } + acore->m_RejectReason = SRT_REJ_UNKNOWN; 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 + + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); + if (m_config.bDriftTracer) + m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); +} diff --git a/trunk/3rdparty/srt-1-fit/srtcore/core.h b/trunk/3rdparty/srt-1-fit/srtcore/core.h index 2b0a7874133..71c955c3313 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_snd.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,60 +288,172 @@ 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); + +#if ENABLE_BONDING + sync::steady_clock::time_point lastRspTime() const { return m_tsLastRspTime.load(); } + sync::steady_clock::time_point freshActivationStart() const { return m_tsFreshActivation; } +#endif + + 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) // weird, can't use non-static data member as default argument! + len = ps; + return m_config.bMessageAPI ? (len+ps-1)/ps : 1; + } + + static int32_t makeTS(const time_point& from_time, const time_point& tsStartTime) + { + // 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) + SRT_ASSERT(from_time >= tsStartTime); + return (int32_t) sync::count_microseconds(from_time - tsStartTime); + } + + /// @brief Set the timestamp field of the packet using the provided value (no check) + /// @param p the packet structure to set the timestamp on. + /// @param ts timestamp to use as a source for packet timestamp. + SRT_ATTR_EXCLUDES(m_StatsLock) + void setPacketTS(CPacket& p, const time_point& ts); + + /// @brief Set the timestamp field of the packet according the TSBPD mode. + /// Also checks the connection start time (m_tsStartTime). + /// @param p the packet structure to set the timestamp on. + /// @param ts timestamp to use as a source for packet timestamp. Ignored if m_bPeerTsbPd is false. + SRT_ATTR_EXCLUDES(m_StatsLock) + void setDataPacketTS(CPacket& p, const time_point& ts); + + // 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); + SRTU_PROPERTY_RO(srt::CRcvBuffer*, rcvBuffer, m_pRcvBuffer); + 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. - void open(); /// Start listening to any connection request. - void setListenState(); /// 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 @@ -308,9 +463,8 @@ class CUDT /// @retval 0 Connection successful /// @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 +473,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 +482,26 @@ 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); + void sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& request); + + /// Create the CryptoControl object based on the HS packet. + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException* eout); + + /// Allocates sender and receiver buffers and loss lists. + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + bool prepareBuffers(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(const CPacket* hspkt /*[[nullable]]*/) 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 +509,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(); + + /// @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 listening at address "peer", which has sent "hs" request. + /// 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 +584,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 +602,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 sendmsg2(const char* data, int len, SRT_MSGCTRL& w_m); - SRT_ATR_NODISCARD int recvmsg(char* data, int len, uint64_t& srctime); - - 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 +647,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() /// @@ -457,9 +668,14 @@ class CUDT /// the receiver fresh loss list. void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); + bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); + + SRT_ATTR_EXCLUDES(m_ConnectionLock) + void checkSndTimers(); + + /// @brief Check and perform KM refresh if needed. + void checkSndKMRefresh(); - void considerLegacySrtHandshake(uint64_t timebase); - void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); void handshakeDone() { m_iSndHsRetryCnt = 0; @@ -467,7 +683,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,376 +704,482 @@ 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; + + SRT_ATTR_REQUIRES(m_RcvBufferLock) + bool isRcvBufferReadyNoLock() const; // TSBPD thread main function. static void* tsbpd(void* param); - static CUDTUnited s_UDTUnited; // UDT global management base + /// Drop too late packets (receiver side). Update 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); + + 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 - 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 -#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; +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 +#ifdef ENABLE_MAXREXMITBW + CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation. +#endif + + 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 giving 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 + time_point m_tsLogSlowDown; // The last time a log message from the "slow down" group was shown. + // The "slow down" group of logs are those that can be printed too often otherwise, but can't be turned off (warnings and errors). + // Currently only used by decryption failure message, therefore no mutex protection needed. + + /// @brief Check if a frequent log can be shown. + /// @param tnow current time + /// @return true if it is ok to print a frequent log message. + bool frequentLogAllowed(const time_point& tnow) const; + private: // Receiving related data CRcvBuffer* m_pRcvBuffer; //< Receiver buffer + SRT_ATTR_GUARDED_BY(m_RcvLossLock) CRcvLossList* m_pRcvLossList; //< Receiver loss list + SRT_ATTR_GUARDED_BY(m_RcvLossLock) 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 - + bool m_bBufferWasFull; // Indicate that RX buffer was full last time a ack was 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) + sync::Mutex m_RecvAckLock; // Protects the state changes while processing incoming ACK (SRT_EPOLL_OUT) + 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_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 - - pthread_mutex_t m_RcvLossLock; // Protects the receiver loss list (access: CRcvQueue::worker, CUDT::tsbpd) - - 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); /// Pack a packet from a list of lost packets. - /// /// @param packet [in, out] a packet structure to fill - /// @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); + + /// Pack a unique data packet (never sent so far) in CPacket for sending. + /// @param packet [in, out] a CPacket structure to fill. + /// + /// @return true if a packet has been packets; false otherwise. + bool packUniqueData(CPacket& packet); + + /// Pack in CPacket the next data to be send. + /// + /// @param packet [out] a CPacket structure to fill + /// @param nexttime [out] Time when this socket should be next time picked up for processing. + /// @param src_addr [out] Source address to pass to channel's sendto + /// + /// @retval true A packet was extracted for sending, the socket should be rechecked at @a nexttime + /// @retval false Nothing was extracted for sending, @a nexttime should be ignored + bool packData(CPacket& packet, time_point& nexttime, sockaddr_any& src_addr); - int packData(CPacket& packet, uint64_t& ts); int processData(CUnit* unit); + + /// This function passes the incoming packet to the initial processing + /// (like packet filter) and is about to store it effectively to the + /// receiver buffer and do some postprocessing (decryption) if necessary + /// and report the status thereof. + /// + /// @param incoming [in] The packet coming from the network medium + /// @param w_new_inserted [out] Set false, if the packet already exists, otherwise true (packet added) + /// @param w_was_sent_in_order [out] Set false, if the packet was belated, but had no R flag set. + /// @param w_srt_loss_seqs [out] Gets inserted a loss, if this function has detected it. + /// + /// @return 0 The call was successful (regardless if the packet was accepted or not). + /// @return -1 The call has failed: no space left in the buffer. + /// @return -2 The incoming packet exceeds the expected sequence by more than a length of the buffer (irrepairable discrepancy). + int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); + + /// Get the packet's TSBPD time. + /// The @a grp passed by void* is not used yet + /// and shall not be used when ENABLE_BONDING=0. + time_point getPktTsbPdTime(void* grp, const CPacket& packet); + + /// Checks and spawns the TSBPD thread if required. + int checkLazySpawnTsbPdThread(); 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 +#if ENABLE_BONDING + /// @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); + + + /// 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 +private: // Timers functions +#if ENABLE_BONDING + 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 +#endif - uint64_t m_ullTargetTime_tk; // scheduled time of next packet sending + 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 + sockaddr_any m_SourceAddr; // override UDP source address with this one when sending + 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 +1188,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 3c09570a1f5..fdd643f22b8 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) @@ -53,18 +57,46 @@ std::string KmStateStr(SRT_KM_STATE state) TAKE(SECURING); TAKE(NOSECRET); TAKE(BADSECRET); +#ifdef ENABLE_AEAD_API_PREVIEW + TAKE(BADCRYPTOMODE); +#endif #undef TAKE default: { char buf[256]; - sprintf(buf, "??? (%d)", state); +#if defined(_MSC_VER) && _MSC_VER < 1900 + _snprintf(buf, sizeof(buf) - 1, "??? (%d)", state); +#else + snprintf(buf, sizeof(buf), "??? (%d)", state); +#endif return buf; } } } +} // namespace +using srt_logging::KmStateStr; -std::string CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen) +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 +} + +bool srt::CCryptoControl::isAESGCMSupported() +{ +#ifdef SRT_ENABLE_ENCRYPTION + return HaiCrypt_IsAESGCM_Supported() != 0; +#else + return false; +#endif +} + +#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 +107,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,61 +115,63 @@ 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_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, false)) { - 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 // connection. - bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; + const bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; // Local macro to return rejection appropriately. // 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; + const bool bUseGCM = + (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM) || + (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM); + // What we have to do: // If encryption is on (we know that by having m_KmSecret nonempty), create // the crypto context (if bidirectional, create for both sending and receiving). @@ -147,16 +181,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 +202,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 +213,25 @@ 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_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM)) { - 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(); } + // Deduce resulting mode. + m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR; + 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 +244,44 @@ 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_CIPHER: +#ifdef ENABLE_AEAD_API_PREVIEW + m_RcvKmState = m_SndKmState = SRT_KM_S_BADCRYPTOMODE; +#else + m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback. +#endif + w_srtlen = 1; + LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADCRYPTOMODE"); 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 +294,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 +305,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 +349,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 @@ -365,9 +410,17 @@ int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int m_SndKmState = SRT_KM_S_UNSECURED; retstatus = 0; break; +#ifdef ENABLE_AEAD_API_PREVIEW + case SRT_KM_S_BADCRYPTOMODE: + // The peer expects to use a different cryptographic mode (e.g. AES-GCM, not AES-CTR). + m_RcvKmState = SRT_KM_S_BADCRYPTOMODE; + m_SndKmState = SRT_KM_S_BADCRYPTOMODE; + retstatus = -1; + break; +#endif 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 +428,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 +442,41 @@ 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) { - if ( !m_hSndCrypto || m_SndKmState == SRT_KM_S_UNSECURED) + sync::ScopedLock lck(m_mtxLock); + 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,39 +486,28 @@ 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 #endif } -#ifdef SRT_ENABLE_ENCRYPTION -void CCryptoControl::regenCryptoKm(bool sendit, bool bidirectional) +void srt::CCryptoControl::regenCryptoKm(CUDT* sock SRT_ATR_UNUSED, bool bidirectional SRT_ATR_UNUSED) { +#ifdef SRT_ENABLE_ENCRYPTION + sync::ScopedLock lck(m_mtxLock); if (!m_hSndCrypto) return; @@ -473,8 +516,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 +534,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_iCryptoMode(CSrtConfig::CIPHER_MODE_AUTO) + , 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,45 +606,58 @@ 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")); // Set UNSECURED state as default m_RcvKmState = SRT_KM_S_UNSECURED; + m_iCryptoMode = cfg.iCryptoMode; + +#ifdef SRT_ENABLE_ENCRYPTION + if (!cfg.bTSBPD && m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO) + m_iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + const bool bUseGCM = m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM; + + if (bUseGCM && !isAESGCMSupported()) + { + LOGC(cnlog.Warn, log << "CCryptoControl: AES GCM is not supported by the crypto service provider."); + return false; + } +#endif // 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 ) + if (side == HSD_INITIATOR) { if (hasPassphrase()) { #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_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, bUseGCM); + 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); + const int st = HaiCrypt_Clone(m_hSndCrypto, HAICRYPT_CRYPTO_DIR_RX, &m_hRcvCrypto); + HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating CLONED RCV crypto context: status=" << st); ok = st == 0; } @@ -618,45 +672,54 @@ 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 + ); + + m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR; #else - LOGC(mglog.Error, log << "CCryptoControl::init: encryption not supported"); - return true; + // 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 false; #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 */ + sync::ScopedLock lck(m_mtxLock); 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 +736,12 @@ 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(HaiCrypt_Handle& w_hCrypto, size_t keylen, HaiCrypt_CryptoDir cdir, bool bAESGCM) { - - 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 +752,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; } @@ -700,7 +762,7 @@ bool CCryptoControl::createCryptoCtx(ref_t hCrypto, size_t keyl m_KmRefreshRatePkt = 2000; m_KmPreAnnouncePkt = 500; #endif - crypto_cfg.flags = HAICRYPT_CFG_F_CRYPTO | (cdir == HAICRYPT_CRYPTO_DIR_TX ? HAICRYPT_CFG_F_TX : 0); + crypto_cfg.flags = HAICRYPT_CFG_F_CRYPTO | (cdir == HAICRYPT_CRYPTO_DIR_TX ? HAICRYPT_CFG_F_TX : 0) | (bAESGCM ? HAICRYPT_CFG_F_GCM : 0); crypto_cfg.xport = HAICRYPT_XPT_SRT; crypto_cfg.cryspr = HaiCryptCryspr_Get_Instance(); crypto_cfg.key_len = (size_t)keylen; @@ -709,38 +771,38 @@ bool CCryptoControl::createCryptoCtx(ref_t hCrypto, size_t keyl crypto_cfg.km_refresh_rate_pkt = m_KmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_KmRefreshRatePkt; crypto_cfg.km_pre_announce_pkt = m_KmPreAnnouncePkt == 0 ? SRT_CRYPT_KM_PRE_ANNOUNCE : m_KmPreAnnouncePkt; 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(HaiCrypt_Handle&, size_t, HaiCrypt_CryptoDir, bool) { 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()); + // Note that in case of GCM the header has to zero Retransmitted Packet Flag (R). + // If TSBPD is disabled, timestamp also has to be zeroed. + 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 +811,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 +820,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 +836,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 +846,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 +867,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()); - if ( rc <= 0 ) + const 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.Note, log << "decrypt ERROR: 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 +896,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 +911,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 31744e663de..370d5529ca1 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,34 @@ 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); - -enum Whether2RegenKm {DONT_REGEN_KM = 0, REGEN_KM = 1}; +const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ/sizeof(uint32_t); 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: @@ -66,10 +67,12 @@ class CCryptoControl // putting the whole HaiCrypt_Cfg object here. int m_KmRefreshRatePkt; int m_KmPreAnnouncePkt; + int m_iCryptoMode; HaiCrypt_Secret m_KmSecret; //Key material shared secret // Sender - uint64_t m_SndKmLastTime; + sync::steady_clock::time_point m_SndKmLastTime; + sync::Mutex m_mtxLock; // A mutex to protect concurrent access to CCryptoControl. struct { unsigned char Msg[HCRYPT_MSG_KM_MAX_SZ]; size_t MsgLen; @@ -82,6 +85,9 @@ class CCryptoControl bool m_bErrorReported; public: + static void globalInit(); + + static bool isAESGCMSupported(); bool sendingAllowed() { @@ -105,13 +111,16 @@ class CCryptoControl return m_KmSecret.len > 0; } -private: - -#ifdef SRT_ENABLE_ENCRYPTION - void regenCryptoKm(bool sendit, bool bidirectional); -#endif + int getCryptoMode() const + { + return m_iCryptoMode; + } -public: + /// Regenerate cryptographic key material if needed. + /// @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). + SRT_ATTR_EXCLUDES(m_mtxLock) + void regenCryptoKm(CUDT* sock, bool bidirectional); size_t KeyLen() { return m_iSndKmKeyLen; } @@ -119,7 +128,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 +168,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 +201,28 @@ 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); + SRT_ATTR_EXCLUDES(m_mtxLock) 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); - + /// (Re)send KM request to a peer on timeout. + /// This function is used in: + /// - HSv4 (initial key material exchange - in HSv5 it's attached to handshake). + /// - The case of key regeneration (KM refresh), when a new key has to be sent again. + /// In this case the first sending happens in regenCryptoKm(..). This function + /// retransmits the KM request by timeout if not KM response has been received. + SRT_ATTR_EXCLUDES(m_mtxLock) + void sendKeysToPeer(CUDT* sock, int iSRTT); void setCryptoSecret(const HaiCrypt_Secret& secret) { m_KmSecret = secret; - //memcpy(&m_KmSecret, &secret, sizeof(m_KmSecret)); } void setCryptoKeylen(size_t keylen) @@ -218,7 +231,7 @@ class CCryptoControl m_iRcvKmKeyLen = keylen; } - bool createCryptoCtx(ref_t rh, size_t keylen, HaiCrypt_CryptoDir tx); + bool createCryptoCtx(HaiCrypt_Handle& rh, size_t keylen, HaiCrypt_CryptoDir tx, bool bAESGCM); int getSndCryptoFlags() const { @@ -253,16 +266,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 84a63c854c5..269b9ff5930 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/epoll.cpp @@ -50,37 +50,36 @@ 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" +#include "utilities.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 +88,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 +114,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 +146,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 j = 0; j < cleared.size(); ++j) + d.removeSubscription(cleared[j]); +} + +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 +236,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 +258,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 +271,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 +291,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 +303,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 +322,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 +336,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 +349,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 +362,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 +382,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 +411,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 +441,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 +465,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 +490,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 +498,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 +512,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 +546,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 +569,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 +587,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 +609,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,15 +632,16 @@ 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(); - epoll_event ev[max_events]; - int nfds = ::epoll_wait(ed.m_iLocalID, ev, max_events, 0); + SRT_ASSERT(max_events > 0); + srt::FixedArray ev(max_events); + int nfds = ::epoll_wait(ed.m_iLocalID, ev.data(), ev.size(), 0); IF_HEAVY_LOGGING(const int prev_total = total); for (int i = 0; i < nfds; ++ i) @@ -554,31 +657,32 @@ 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(); - struct kevent ke[max_events]; + const int max_events = (int)ed.m_sLocals.size(); + SRT_ASSERT(max_events > 0); + srt::FixedArray ke(max_events); - int nfds = kevent(ed.m_iLocalID, NULL, 0, ke, max_events, &tmout); + int nfds = kevent(ed.m_iLocalID, NULL, 0, ke.data(), (int)ke.size(), &tmout); IF_HEAVY_LOGGING(const int prev_total = total); for (int i = 0; i < nfds; ++ i) { if ((NULL != lrfds) && (ke[i].filter == EVFILT_READ)) { - lrfds->insert(ke[i].ident); + lrfds->insert((int)ke[i].ident); ++ total; } if ((NULL != lwfds) && (ke[i].filter == EVFILT_WRITE)) { - lwfds->insert(ke[i].ident); + lwfds->insert((int)ke[i].ident); ++ total; } } - 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. @@ -599,7 +703,7 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd if (lwfds) FD_SET(*i, &rqwritefds); if ((int)*i > max_fd) - max_fd = *i; + max_fd = (int)*i; } IF_HEAVY_LOGGING(const int prev_total = total); @@ -623,34 +727,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 srt::CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut, bool report_by_exception) +{ + { + 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; } -int CEPoll::release(const int eid) +bool srt::CEPoll::empty(const CEPollDesc& d) const +{ + ScopedLock lg (m_EPollLock); + return d.watch_empty(); +} + +int srt::CEPoll::release(const int eid) { - CGuard pg(m_EPollLock); + ScopedLock pg(m_EPollLock); map::iterator i = m_mPolls.find(eid); if (i == m_mPolls.end()) @@ -659,7 +856,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 +866,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 +902,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 +917,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 +939,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 1d0463ffcb4..00d46ceb418 --- 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,11 +59,21 @@ modified by #include #include "udt.h" +namespace srt +{ + +class CUDT; +class CRendezvousQueue; +class CUDTGroup; + -struct CEPollDesc +class CEPollDesc { +#ifdef __GNUG__ const int m_iID; // epoll ID - +#else + const int m_iID SRT_ATR_UNUSED; // epoll ID +#endif struct Wait; struct Notice: public SRT_EPOLL_EVENT @@ -86,40 +96,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 +167,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 +182,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 +200,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 +262,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 +325,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 +370,122 @@ friend class CRendezvousQueue; public: // for CUDTUnited API - /// create a new EPoll. - /// @return new EPoll ID if success, otherwise an error number. - - int create(); + /// create a new EPoll. + /// @return new EPoll ID if success, otherwise an error number. + int create(CEPollDesc** ppd = 0); - /// 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. - int add_usock(const int eid, const SRTSOCKET& u, const int* events = NULL) { return update_usock(eid, u, events); } + /// delete all user sockets (SRT sockets) from an EPoll + /// @param [in] eid EPoll ID. + /// @return 0 + int clear_usocks(int eid); - /// 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 +493,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 b1e810e5251..a41e3a33bbe 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,28 +330,28 @@ 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: +" - << offset << " %" << CSeqNo::incseq(isn, offset) << ")"); + HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "]... (resetting to row 0: +" + << offset << " %" << CSeqNo::incseq(isn, (int32_t)offset) << ")"); } else { offset += 1 + sizeRow(); - HLOGC(mglog.Debug, log << "ConfigureColumns: [" << (col+1) << "] ... (continue +" - << offset << " %" << CSeqNo::incseq(isn, offset) << ")"); + HLOGC(pflog.Debug, log << "ConfigureColumns: [" << (col+1) << "] ... (continue +" + << offset << " %" << CSeqNo::incseq(isn, (int32_t)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,17 +450,19 @@ 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) + // [[assert sizeCol() >= 2]]; // see the condition above. + + if (vert_off >= 0) { // BEWARE! X % Y with different signedness upgrades int to unsigned! // 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 +470,10 @@ void FECFilterBuiltin::feedSource(CPacket& packet) return; } - int vert_pos = vert_off / sizeRow(); + // [[assert vert_off >= 0]]; // this condition branch + 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,22 +491,21 @@ 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++; } 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 +522,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 +533,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 +560,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 +571,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 +582,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) @@ -511,8 +605,8 @@ void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg, } // Fill the rest with zeros. When this packet is going to be - // recovered, the payload extraced from this process will have - // the maximum lenght, but it will be cut to the right length + // recovered, the payload extracted from this process will have + // the maximum length, but it will be cut to the right length // and these padding 0s taken out. for (size_t i = payload_size; i < payloadSize(); ++i) g.payload_clip[i] = g.payload_clip[i] ^ 0; @@ -551,20 +645,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 +667,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 +677,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 +687,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 +710,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 +734,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 +748,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 +760,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 +797,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 +811,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 +830,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 +838,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 +966,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 +986,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 +1007,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 +1027,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 +1051,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 +1059,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 +1075,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 +1096,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 +1105,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) + os << "cell[" << i << "-" << (i+row_size-1) << "] %" << CSeqNo::incseq(base, (int32_t)i) << ":"; + for (size_t y = 0; y < row_size; ++y) { - os << " " << CellMark(cells, i+y); + os << " " << CellMark(cells, (int)(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 +1161,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 +1194,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 +1219,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 +1260,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 +1268,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 +1295,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 +1309,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 +1322,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 +1339,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 +1347,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 +1361,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 +1375,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 +1392,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 +1400,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 +1417,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 +1458,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 +1495,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 +1503,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 +1516,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 +1532,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 +1540,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 +1553,26 @@ 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"); + const size_t size_in_packets = rowx * numberCols(); + const int n_series = int(rowx / numberRows()); - rcv.rowq.erase(rcv.rowq.begin(), rcv.rowq.begin() + m_number_cols); - rowx -= m_number_cols; - - // 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 (CheckEmergencyShrink(n_series, size_in_packets)) + { + HLOGC(pflog.Debug, log << "FEC: DONE Emergency resize, rowx=" << rowx << " series=" << n_series + << "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize()); } // 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,200 @@ 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) +bool FECFilterBuiltin::CheckEmergencyShrink(size_t n_series, size_t size_in_packets) +{ + // The minimum required size of the covered sequence range must be such + // that groups for packets from the previous range must be still reachable. + // It's then "this and previous" series in case of even arrangement. + // + // For staircase arrangement the potential range for a single column series + // (number of columns equal to a row size) spans for 2 matrices (rows * cols) + // minus one row. As dismissal is only allowed to be done by one full series + // of rows and columns, the history must keep as many groups as needed to reach + // out for this very packet of this group and all packets in the same row. + // Hence we need two series of columns to cover a similar range as two row, twice. + + const size_t min_series_history = m_arrangement_staircase ? 4 : 2; + + if (n_series <= min_series_history) + return false; + + if (size_in_packets < rcvBufferSize() && n_series < SRT_FEC_MAX_RCV_HISTORY) + return false; + + // 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.begin() + 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; + + return true; +} + +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 +1862,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 +1875,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 +1892,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 +1909,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 +1920,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 +1933,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,13 +1947,13 @@ 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()) + << CSeqNo::incseq(pg.base, (int32_t)((sizeCol()-1)*sizeRow())) << ")"); continue; } @@ -1681,24 +1962,24 @@ 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()) + << CSeqNo::incseq(pg.base, (int32_t)((sizeCol()-1)*sizeRow())) << " - collecting losses."); 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 +2001,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()) - { - 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()) + 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 +#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 +2048,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 +2117,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 +2199,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 +2230,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 +2248,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 +2259,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 +2277,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 +2308,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 +2382,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 +2430,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 +2484,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 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()); - } - } + // 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. - size_t ncellrem = CSeqNo::seqoff(rcv.cell_base, newbase); - rcv.cells.erase(rcv.cells.begin(), rcv.cells.begin() + ncellrem); - rcv.cell_base = newbase; + // 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(); - // Note that after this shift, column groups that were - // in particular column, remain in that column. + if (CheckEmergencyShrink(n_series, size_in_packets)) + { + HLOGC(pflog.Debug, log << "FEC: DONE Emergency resize, colgx=" << colgx << " series=" << n_series + << "npackets=" << size_in_packets << " exceeds buf=" << rcvBufferSize()); + } + else + { + 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 +2545,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 +2558,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 +2570,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 5912a196775..02970947521 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; @@ -45,7 +47,7 @@ class FECFilterBuiltin: public SrtPacketFilterBase size_t drop; //< by how much the sequence should increase to get to the next series size_t collected; //< how many packets were taken to collect the clip - Group(): base(CSeqNo::m_iMaxSeqNo), step(0), drop(0), collected(0) + Group(): base(SRT_SEQNO_NONE), step(0), drop(0), collected(0) { } @@ -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 @@ -79,7 +87,7 @@ class FECFilterBuiltin: public SrtPacketFilterBase #if ENABLE_HEAVY_LOGGING std::string DisplayStats() { - if (base == CSeqNo::m_iMaxSeqNo) + if (base == SRT_SEQNO_NONE) return "UNINITIALIZED!!!"; std::ostringstream os; @@ -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); + bool CheckEmergencyShrink(size_t n_series, size_t size_in_packets); 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 e2a6983c4a2..512e3524d28 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/filelist.maf +++ b/trunk/3rdparty/srt-1-fit/srtcore/filelist.maf @@ -2,7 +2,9 @@ SOURCES api.cpp -buffer.cpp +buffer_snd.cpp +buffer_rcv.cpp +buffer_tools.cpp cache.cpp channel.cpp common.cpp @@ -12,27 +14,49 @@ 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_snd.h +buffer_rcv.h +buffer_tools.h cache.h channel.h common.h @@ -45,13 +69,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 00000000000..001dd4802db --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group.cpp @@ -0,0 +1,4145 @@ +#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; + + LOGC(gmlog.Note, log << "group/add: adding member @" << data.id << " into group $" << id()); + m_Group.push_back(data); + gli_t end = m_Group.end(); + if (m_iMaxPayloadSize == -1) + { + int plsize = (int)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_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); + --m_SizeCache; +} + +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; + + 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(), (int)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(), (int)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; + } + + // Make the socket closing BEFORE withdrawing its group membership + // because a socket created as a group member cannot be valid + // without the group. + // This is not true in case of non-managed groups, which + // only collect sockets, but also non-managed groups should not + // use common group buffering and tsbpd. Also currently there are + // no other groups than managed one. + s->setClosing(); + + 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, if the first packet arriving looks like a jump over, + // the corresponding LOSSREPORT is sent. For packets that are truly lost, + // the sender retransmits them, for packets that before ISN, DROPREQ is sent. + + // 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 (int)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 + // omitting 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; +} + +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; + CRcvBuffer::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 CRcvBuffer::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); + + bool canReadFurther = false; + 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) + { + const 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); + } + } + + if (!ps->core().isRcvBufferReadyNoLock()) + m_Global.m_EPoll.update_events(ps->m_SocketID, ps->core().m_sPollID, SRT_EPOLL_IN, false); + else + canReadFurther = true; + } + + if (!canReadFurther) + m_Global.m_EPoll.update_events(id(), 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); +} + +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 = (int)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, (int)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_iRcvLastAck); + + 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 00000000000..c2863b44e71 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/group.h @@ -0,0 +1,809 @@ +/* + * 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; + LOGC(gmlog.Note, log << "group/remove: removing member @" << id << " from group $" << 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; + } + + 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 succeeded. + /// + /// @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 + { + private: + std::list m_List; + sync::atomic m_SizeCache; + + /// 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; + + public: + + GroupContainer() + : m_SizeCache(0) + , 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); ++m_SizeCache; } + void clear() + { + m_LastActiveLink = end(); + m_List.clear(); + m_SizeCache = 0; + } + size_t size() { return m_SizeCache; } + + void erase(gli_t it); + }; + GroupContainer m_Group; + 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; + + 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, 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 00000000000..9adb19607b2 --- /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 00000000000..790cf55ed61 --- /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 00000000000..536bdf52ce8 --- /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 00000000000..d780d0b9a3d --- /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 11104365cf3..f8f03c84d87 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,32 @@ const char* srt_rejectreason_name [] = { "MESSAGEAPI", "CONGESTION", "FILTER", + "GROUP", + "TIMEOUT", + "CRYPTO" }; +} -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 +178,7 @@ std::string RequestTypeStr(UDTRequestType rq) } } -string CHandShake::RdvStateStr(CHandShake::RendezvousState s) +string srt::CHandShake::RdvStateStr(CHandShake::RendezvousState s) { switch (s) { @@ -172,11 +194,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 +224,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 +237,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 +247,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 +264,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 +279,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 +294,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 4b4c7d80738..93a351f3962 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 f5a07724321..b6e70d39d75 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,792 @@ modified by Haivision Systems Inc. *****************************************************************************/ +#include "platform_sys.h" + #include "list.h" #include "packet.h" +#include "logging.h" + +// Use "inline namespace" in C++11 +namespace srt_logging +{ +extern Logger qrlog; +extern Logger qslog; +extern Logger tslog; +} -CSndLossList::CSndLossList(int size): -m_caSeq(), -m_iHead(-1), -m_iLength(0), -m_iSize(size), -m_iLastInsertPos(-1), -m_ListLock() +using srt_logging::qrlog; +using srt_logging::qslog; +using srt_logging::tslog; + +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 - pthread_mutex_init(&m_ListLock, 0); + // sender list needs mutex protection + setupMutex(m_ListLock, "LossList"); } -CSndLossList::~CSndLossList() +srt::CSndLossList::~CSndLossList() { - delete [] m_caSeq; - pthread_mutex_destroy(&m_ListLock); + delete[] m_caSeq; + releaseMutex(m_ListLock); } -int CSndLossList::insert(int32_t seqno1, int32_t seqno2) +void srt::CSndLossList::traceState() const { - 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) + 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 srt::CSndLossList::insert(int32_t seqno1, int32_t seqno2) +{ + 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; - loc = i; + // 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; } - 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 CSndLossList::getLossLength() const +int srt::CSndLossList::getLossLength() const { - CGuard listguard(m_ListLock); + ScopedLock listguard(m_ListLock); - return m_iLength; + return m_iLength; } -int32_t CSndLossList::popLostSeq() +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; - if (0 == m_iLength) - return -1; + // 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; + } + + 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); +} + +void srt::CSndLossList::insertAfter(int pos, int pos_after, int32_t seqno1, int32_t seqno2) +{ + 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); +} + +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) +int 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 0; + } + } + 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; + const int n = CSeqNo::seqlen(seqno1, seqno2); + m_iLength += n; + return n; + } + + // otherwise searching for the position where the node should be + const 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 -1; + } + + 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; + } + + const int n = CSeqNo::seqlen(seqno1, seqno2); + m_iLength += n; + return n; } -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 (CSeqNo::seqcmp(seqno1, seqno2) > 0) + { + return false; + } + for (int32_t i = seqno1; CSeqNo::seqcmp(i, seqno2) <= 0; i = CSeqNo::incseq(i)) + { + remove(i); + } + return true; } -bool CRcvLossList::find(int32_t seqno1, int32_t seqno2) const +int32_t srt::CRcvLossList::removeUpTo(int32_t seqno_last) { - if (0 == m_iLength) - return false; + int32_t first = getFirstLostSeq(); + if (first == SRT_SEQNO_NONE) + { + //HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " - empty list"); + return first; // empty, so nothing to remove + } - int p = m_iHead; + if (CSeqNo::seqcmp(seqno_last, first) < 0) + { + //HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " - first %" << first << " is newer, exitting"); + return first; // seqno_last older than first - nothing to remove + } - 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; + HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " ..."); - p = m_caSeq[p].next; - } + // NOTE: seqno_last is past-the-end here. Removed are only seqs + // that are earlier than this. + for (int32_t i = first; CSeqNo::seqcmp(i, seqno_last) <= 0; i = CSeqNo::incseq(i)) + { + //HLOGC(tslog.Debug, log << "... removing %" << i); + remove(i); + } - return false; + return first; } -int CRcvLossList::getLossLength() const +bool srt::CRcvLossList::find(int32_t seqno1, int32_t seqno2) const { - return m_iLength; + if (0 == m_iLength) + return false; + + int p = m_iHead; + + 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].inext; + } + + return false; +} + +int srt::CRcvLossList::getLossLength() const +{ + 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 +845,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 +854,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 +866,15 @@ 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 (lo != SRT_SEQNO_NONE && CSeqNo::seqcmp(lo, seq[1]) > 0) return DELETE; + // IF is NONE, then rely simply on that item.hi <% arg.hi, + // which is a condition at the end. // 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 +882,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; @@ -767,3 +897,53 @@ CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t lo, int32_t hi) return DELETE; } + +bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32_t sequence, int* pw_had_ttl) +{ + for (size_t i = 0; i < w_container.size(); ++i) + { + const int had_ttl = w_container[i].ttl; + Emod wh = w_container[i].revoke(sequence); + + if (wh == NONE) + continue; // Not found. Search again. + + if (wh == DELETE) // ... oo ... x ... o ... => ... oo ... o ... + { + // Removed the only element in the record - remove the record. + w_container.erase(w_container.begin() + i); + } + else if (wh == SPLIT) // ... ooxooo ... => ... oo ... ooo ... + { + // Create a new element that will hold the upper part of the range, + // and the found one modify to be the lower part of the range. + + // Keep the current end-of-sequence value for the second element + int32_t next_end = w_container[i].seq[1]; + + // seq-1 set to the end of this element + w_container[i].seq[1] = CSeqNo::decseq(sequence); + // seq+1 set to the begin of the next element + int32_t next_begin = CSeqNo::incseq(sequence); + + // Use position of the NEXT element because insertion happens BEFORE pointed element. + // Use the same TTL (will stay the same in the other one). + w_container.insert(w_container.begin() + i + 1, + CRcvFreshLoss(next_begin, next_end, w_container[i].ttl)); + } + // For STRIPPED: ... xooo ... => ... ooo ... + // i.e. there's nothing to do. + + // Every loss is unique. We're done here. + if (pw_had_ttl) + *pw_had_ttl = had_ttl; + + return true; + } + + if (pw_had_ttl) + *pw_had_ttl = 0; + return false; + +} + diff --git a/trunk/3rdparty/srt-1-fit/srtcore/list.h b/trunk/3rdparty/srt-1-fit/srtcore/list.h index e25a26a9618..8f921c698d5 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,87 @@ modified by Haivision Systems Inc. *****************************************************************************/ -#ifndef __UDT_LIST_H__ -#define __UDT_LIST_H__ +#ifndef INC_SRT_LIST_H +#define INC_SRT_LIST_H +#include #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; + // Debug/unittest support. - /// 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 head() const { return m_iHead; } + int next(int loc) const { return m_caSeq[loc].inext; } + int last() const { return m_iLastInsertPos; } - int32_t popLostSeq(); +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; + + 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 + + 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); + + /// Inserts an element after previous element. + /// No lock. + void insertAfter(int pos, int pos_after, int32_t seqno1, int32_t seqno2); + + /// Check if it is possible to coalesce element at loc with further elements. + /// @param loc - last changed location + void coalesce(int loc); - 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 + /// 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); - mutable pthread_mutex_t m_ListLock; // used to synchronize list operation + static const int LOC_NONE = -1; private: - CSndLossList(const CSndLossList&); - CSndLossList& operator=(const CSndLossList&); + CSndLossList(const CSndLossList&); + CSndLossList& operator=(const CSndLossList&); }; //////////////////////////////////////////////////////////////////////////////// @@ -111,123 +138,130 @@ 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. + /// @return length of the loss record inserted (seqlen(seqno1, seqno2)), -1 on error. + int 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. + /// Remove all numbers that precede the given sequence number. + /// @param [in] seqno sequence number. + /// @return the first removed sequence number + int32_t removeUpTo(int32_t seqno); - bool find(int32_t seqno1, int32_t seqno2) const; + /// 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. - /// Read the loss length. - /// @return the length of the list. + bool find(int32_t seqno1, int32_t seqno2) const; - int getLossLength() const; + /// Read the loss length. + /// @return the length of the list. - /// Read the first (smallest) seq. no. in the list. - /// @return the sequence number or -1 if the list is empty. + int getLossLength() const; - int getFirstLostSeq() const; + /// Read the first (smallest) seq. no. in the list. + /// @return the sequence number or -1 if the list is empty. - /// 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. + int32_t getFirstLostSeq() const; - void getLossArray(int32_t* array, int& len, int limit); + /// 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. -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 + void getLossArray(int32_t* array, int& len, int limit); 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. + 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 - return iterator(seq, seq[head].next); - } - - 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); - } - }; +private: + CRcvLossList(const CRcvLossList&); + CRcvLossList& operator=(const CRcvLossList&); - 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 +270,20 @@ 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); + + static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL); }; +} // 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 00000000000..fa7e73b3175 --- /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 00000000000..041f6c8a790 --- /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 00000000000..63a4caf3e2c --- /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 8543472f01e..e90ad4ac212 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,25 @@ 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)); +// NOTE: LOGF is deprecated and should not be used #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 +98,7 @@ written by #define HLOGP(...) #define IF_HEAVY_LOGGING(instr) (void)0 +#define IF_LOGGING(instr) (void)0 #endif @@ -107,29 +113,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 +149,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; } @@ -160,21 +166,29 @@ struct SRT_API LogDispatcher // See Logger::Logger; we know this has normally 2 characters, // except !!FATAL!!, which has 9. Still less than 32. - strcpy(prefix, your_pfx); - // If the size of the FA name together with severity exceeds the size, // just skip the former. if (logger_pfx && strlen(prefix) + strlen(logger_pfx) + 1 < MAX_PREFIX_SIZE) { - strcat(prefix, ":"); - strcat(prefix, logger_pfx); +#if defined(_MSC_VER) && _MSC_VER < 1900 + _snprintf(prefix, MAX_PREFIX_SIZE, "%s:%s", your_pfx, logger_pfx); +#else + snprintf(prefix, MAX_PREFIX_SIZE + 1, "%s:%s", your_pfx, logger_pfx); +#endif + } + else + { +#ifdef _MSC_VER + strncpy_s(prefix, MAX_PREFIX_SIZE + 1, your_pfx, _TRUNCATE); +#else + strncpy(prefix, your_pfx, MAX_PREFIX_SIZE); + prefix[MAX_PREFIX_SIZE] = '\0'; +#endif } - pthread_mutex_init(&mutex, 0); } ~LogDispatcher() { - pthread_mutex_destroy(&mutex); } bool CheckEnabled(); @@ -239,7 +253,14 @@ struct SRT_API LogDispatcher return *this; } - DummyProxy& form(const char*, ...) + // DEPRECATED: DO NOT use LOGF/HLOGF macros anymore. + // Use iostream-style formatting with LOGC or a direct argument with LOGP. + SRT_ATR_DEPRECATED_PX DummyProxy& form(const char*, ...) SRT_ATR_DEPRECATED + { + return *this; + } + + DummyProxy& vform(const char*, va_list) { return *this; } @@ -292,7 +313,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 @@ -348,7 +369,11 @@ struct LogDispatcher::Proxy { char buf[512]; - vsprintf(buf, fmts, ap); +#if defined(_MSC_VER) && _MSC_VER < 1900 + _vsnprintf(buf, sizeof(buf) - 1, fmts, ap); +#else + vsnprintf(buf, sizeof(buf), fmts, ap); +#endif size_t len = strlen(buf); if ( buf[len-1] == '\n' ) { @@ -407,7 +432,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 +447,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 +465,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 71c94b19c82..4fc3b812bb0 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 d6fd5d37035..7fd139944e8 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/md5.cpp +++ b/trunk/3rdparty/srt-1-fit/srtcore/md5.cpp @@ -27,7 +27,7 @@ This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at - http://www.ietf.org/rfc/rfc1321.txt + http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being @@ -38,159 +38,166 @@ that follows (in reverse chronological order): 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order - either statically or dynamically; added missing #include - in library. + either statically or dynamically; added missing #include + in library. 2002-03-11 lpd Corrected argument list for main(), and added int return - type, in test program and T value program. + type, in test program and T value program. 2002-02-21 lpd Added missing #include in test program. 2000-07-03 lpd Patched to eliminate warnings about "constant is - unsigned in ANSI C, signed in traditional"; made test program - self-checking. + unsigned in ANSI C, signed in traditional"; made test program + self-checking. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). 1999-05-03 lpd Original version. */ #include "md5.h" +#include #include -#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +/* + * 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) +#define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else -# define BYTE_ORDER 0 +#define BYTE_ORDER 0 #endif #define T_MASK ((md5_word_t)~0) #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) -#define T3 0x242070db +#define T3 0x242070db #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) -#define T6 0x4787c62a +#define T6 0x4787c62a #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) -#define T9 0x698098d8 +#define T9 0x698098d8 #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) -#define T13 0x6b901122 +#define T13 0x6b901122 #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) -#define T16 0x49b40821 +#define T16 0x49b40821 #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) -#define T19 0x265e5a51 +#define T19 0x265e5a51 #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) -#define T22 0x02441453 +#define T22 0x02441453 #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) -#define T25 0x21e1cde6 +#define T25 0x21e1cde6 #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) -#define T28 0x455a14ed +#define T28 0x455a14ed #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) -#define T31 0x676f02d9 +#define T31 0x676f02d9 #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) -#define T35 0x6d9d6122 +#define T35 0x6d9d6122 #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) -#define T38 0x4bdecfa9 +#define T38 0x4bdecfa9 #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) -#define T41 0x289b7ec6 +#define T41 0x289b7ec6 #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) -#define T44 0x04881d05 +#define T44 0x04881d05 #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) -#define T47 0x1fa27cf8 +#define T47 0x1fa27cf8 #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) -#define T50 0x432aff97 +#define T50 0x432aff97 #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) -#define T53 0x655b59c3 +#define T53 0x655b59c3 #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) -#define T57 0x6fa87e4f +#define T57 0x6fa87e4f #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) -#define T60 0x4e0811a1 +#define T60 0x4e0811a1 #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) -#define T63 0x2ad7d2bb +#define T63 0x2ad7d2bb #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) - -static void -md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +static void md5_process(md5_state_t* pms, const md5_byte_t* data /*[64]*/) { - md5_word_t - a = pms->abcd[0], b = pms->abcd[1], - c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ - md5_word_t xbuf[16]; - const md5_word_t *X; + md5_word_t xbuf[16]; + const md5_word_t* X; #endif { #if BYTE_ORDER == 0 - /* - * Determine dynamically whether this is a big-endian or - * little-endian machine, since we can use a more efficient - * algorithm on the latter. - */ - static const int w = 1; - - if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t*)&w)) /* dynamic little-endian */ #endif -#if BYTE_ORDER <= 0 /* little-endian */ - { - /* - * On little-endian machines, we can process properly aligned - * data without copying it. - */ - if (!((data - (const md5_byte_t *)0) & 3)) { - /* data are properly aligned */ - X = (const md5_word_t *)data; - } else { - /* not aligned */ - memcpy(xbuf, data, 64); - X = xbuf; - } - } +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!(uintptr_t(data) & 3)) + { + /* data are properly aligned */ + X = (const md5_word_t*)data; + } + else + { + /* not aligned */ + memcpy((xbuf), data, 64); + X = xbuf; + } + } #endif #if BYTE_ORDER == 0 - else /* dynamic big-endian */ + else /* dynamic big-endian */ #endif -#if BYTE_ORDER >= 0 /* big-endian */ - { - /* - * On big-endian machines, we must arrange the bytes in the - * right order. - */ - const md5_byte_t *xp = data; - int i; - -# if BYTE_ORDER == 0 - X = xbuf; /* (dynamic only) */ -# else -# define xbuf X /* (static only) */ -# endif - for (i = 0; i < 16; ++i, xp += 4) - xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); - } +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t* xp = data; + int i; + +#if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +#else +#define xbuf X /* (static only) */ +#endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } #endif } @@ -200,182 +207,179 @@ md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) /* Let [abcd k s i] denote the operation a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + F(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b +#define SET(a, b, c, d, k, s, Ti) \ + t = a + F(b, c, d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ - SET(a, b, c, d, 0, 7, T1); - SET(d, a, b, c, 1, 12, T2); - SET(c, d, a, b, 2, 17, T3); - SET(b, c, d, a, 3, 22, T4); - SET(a, b, c, d, 4, 7, T5); - SET(d, a, b, c, 5, 12, T6); - SET(c, d, a, b, 6, 17, T7); - SET(b, c, d, a, 7, 22, T8); - SET(a, b, c, d, 8, 7, T9); - SET(d, a, b, c, 9, 12, T10); + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); SET(c, d, a, b, 10, 17, T11); SET(b, c, d, a, 11, 22, T12); - SET(a, b, c, d, 12, 7, T13); + SET(a, b, c, d, 12, 7, T13); SET(d, a, b, c, 13, 12, T14); SET(c, d, a, b, 14, 17, T15); SET(b, c, d, a, 15, 22, T16); #undef SET - /* Round 2. */ - /* Let [abcd k s i] denote the operation - a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + G(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 1, 5, T17); - SET(d, a, b, c, 6, 9, T18); +#define SET(a, b, c, d, k, s, Ti) \ + t = a + G(b, c, d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); SET(c, d, a, b, 11, 14, T19); - SET(b, c, d, a, 0, 20, T20); - SET(a, b, c, d, 5, 5, T21); - SET(d, a, b, c, 10, 9, T22); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); SET(c, d, a, b, 15, 14, T23); - SET(b, c, d, a, 4, 20, T24); - SET(a, b, c, d, 9, 5, T25); - SET(d, a, b, c, 14, 9, T26); - SET(c, d, a, b, 3, 14, T27); - SET(b, c, d, a, 8, 20, T28); - SET(a, b, c, d, 13, 5, T29); - SET(d, a, b, c, 2, 9, T30); - SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); SET(b, c, d, a, 12, 20, T32); #undef SET - /* Round 3. */ - /* Let [abcd k s t] denote the operation - a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define H(x, y, z) ((x) ^ (y) ^ (z)) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + H(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 5, 4, T33); - SET(d, a, b, c, 8, 11, T34); +#define SET(a, b, c, d, k, s, Ti) \ + t = a + H(b, c, d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); SET(c, d, a, b, 11, 16, T35); SET(b, c, d, a, 14, 23, T36); - SET(a, b, c, d, 1, 4, T37); - SET(d, a, b, c, 4, 11, T38); - SET(c, d, a, b, 7, 16, T39); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); SET(b, c, d, a, 10, 23, T40); - SET(a, b, c, d, 13, 4, T41); - SET(d, a, b, c, 0, 11, T42); - SET(c, d, a, b, 3, 16, T43); - SET(b, c, d, a, 6, 23, T44); - SET(a, b, c, d, 9, 4, T45); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); SET(d, a, b, c, 12, 11, T46); SET(c, d, a, b, 15, 16, T47); - SET(b, c, d, a, 2, 23, T48); + SET(b, c, d, a, 2, 23, T48); #undef SET - /* Round 4. */ - /* Let [abcd k s t] denote the operation - a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define I(x, y, z) ((y) ^ ((x) | ~(z))) -#define SET(a, b, c, d, k, s, Ti)\ - t = a + I(b,c,d) + X[k] + Ti;\ - a = ROTATE_LEFT(t, s) + b - /* Do the following 16 operations. */ - SET(a, b, c, d, 0, 6, T49); - SET(d, a, b, c, 7, 10, T50); +#define SET(a, b, c, d, k, s, Ti) \ + t = a + I(b, c, d) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); SET(c, d, a, b, 14, 15, T51); - SET(b, c, d, a, 5, 21, T52); - SET(a, b, c, d, 12, 6, T53); - SET(d, a, b, c, 3, 10, T54); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); SET(c, d, a, b, 10, 15, T55); - SET(b, c, d, a, 1, 21, T56); - SET(a, b, c, d, 8, 6, T57); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); SET(d, a, b, c, 15, 10, T58); - SET(c, d, a, b, 6, 15, T59); + SET(c, d, a, b, 6, 15, T59); SET(b, c, d, a, 13, 21, T60); - SET(a, b, c, d, 4, 6, T61); + SET(a, b, c, d, 4, 6, T61); SET(d, a, b, c, 11, 10, T62); - SET(c, d, a, b, 2, 15, T63); - SET(b, c, d, a, 9, 21, T64); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); #undef SET - /* Then perform the following additions. (That is increment each - of the four registers by the value it had before this block - was started.) */ + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } -void -md5_init(md5_state_t *pms) +void md5_init(md5_state_t* pms) { pms->count[0] = pms->count[1] = 0; - pms->abcd[0] = 0x67452301; - pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; - pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; - pms->abcd[3] = 0x10325476; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; } -void -md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +void md5_append(md5_state_t* pms, const md5_byte_t* data, int nbytes) { - const md5_byte_t *p = data; - int left = nbytes; - int offset = (pms->count[0] >> 3) & 63; - md5_word_t nbits = (md5_word_t)(nbytes << 3); + const md5_byte_t* p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) - return; + return; /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) - pms->count[1]++; + pms->count[1]++; /* Process an initial partial block. */ - if (offset) { - int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); - - memcpy(pms->buf + offset, p, copy); - if (offset + copy < 64) - return; - p += copy; - left -= copy; - md5_process(pms, pms->buf); + if (offset) + { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy((pms->buf + offset), p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); } /* Process full blocks. */ for (; left >= 64; p += 64, left -= 64) - md5_process(pms, p); + md5_process(pms, p); /* Process a final partial block. */ if (left) - memcpy(pms->buf, p, left); + memcpy((pms->buf), p, left); } -void -md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +void md5_finish(md5_state_t* pms, md5_byte_t digest[16]) { - static const md5_byte_t pad[64] = { - 0x80, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - md5_byte_t data[8]; - int i; + static const md5_byte_t pad[64] = {0x80, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + md5_byte_t data[8]; + int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) - data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) - digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); + 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 f7402e7e269..643981c01fd 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/md5.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/md5.h @@ -27,7 +27,7 @@ This code implements the MD5 Algorithm defined in RFC 1321, whose text is available at - http://www.ietf.org/rfc/rfc1321.txt + http://www.ietf.org/rfc/rfc1321.txt The code is derived from the text of the RFC, including the test suite (section A.5) but excluding the rest of Appendix A. It does not include any code or documentation that is identified in the RFC as being @@ -38,17 +38,24 @@ that follows (in reverse chronological order): 2002-04-13 lpd Removed support for non-ANSI compilers; removed - references to Ghostscript; clarified derivation from RFC 1321; - now handles byte order either statically or dynamically. + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); - added conditionalization for C++ compilation from Martin - Purschke . + added conditionalization for C++ compilation from Martin + Purschke . 1999-05-03 lpd Original version. */ #ifndef md5_INCLUDED -# define 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 @@ -61,31 +68,25 @@ */ typedef unsigned char md5_byte_t; /* 8-bit byte */ -typedef unsigned int md5_word_t; /* 32-bit word */ +typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ -typedef struct md5_state_s { - md5_word_t count[2]; /* message length in bits, lsw first */ - md5_word_t abcd[4]; /* digest buffer */ - md5_byte_t buf[64]; /* accumulate block */ -} md5_state_t; - -#ifdef __cplusplus -extern "C" +typedef struct md5_state_s { -#endif + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; /* Initialize the algorithm. */ -void md5_init(md5_state_t *pms); +void md5_init(md5_state_t* pms); /* Append a string to the message. */ -void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); +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]); +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 0418a1a84b5..a38765c087a 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 0b914395847..fbb56a42c7f 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 @@ -152,33 +151,39 @@ modified by // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Loss List Field Coding: -// For any consectutive lost seqeunce numbers that the differnece between +// For any consecutive lost seqeunce numbers that the differnece between // the last and first is more than 1, only record the first (a) and the // the last (b) sequence numbers in the loss list field, and modify the // the first bit of a to 1. -// For any single loss or consectutive loss less than 2 packets, use +// For any single loss or consecutive 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; +namespace srt { + // 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())) +CPacket::CPacket() + : m_nHeader() // Silences GCC 12 warning "used uninitialized". + , 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,8 +197,21 @@ m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef())) m_PacketVector[PV_DATA].set(NULL, 0); } +char* CPacket::getData() +{ + return (char*)m_PacketVector[PV_DATA].dataRef(); +} + void 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; } @@ -201,135 +219,261 @@ void CPacket::allocate(size_t alloc_buffer_size) void 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); + m_data_owned = false; +} + +char* 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; } 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(); + deallocate(); } - size_t CPacket::getLength() const { - return m_PacketVector[PV_DATA].size(); + return m_PacketVector[PV_DATA].size(); } void CPacket::setLength(size_t len) { - m_PacketVector[PV_DATA].setLength(len); + m_PacketVector[PV_DATA].setLength(len); +} + +void CPacket::setLength(size_t len, size_t cap) +{ + SRT_ASSERT(len <= cap); + setLength(len); + m_zCapacity = cap; } -void CPacket::pack(UDTMessageType pkttype, const void* lparam, void* rparam, int size) +#if ENABLE_HEAVY_LOGGING +// Debug only +static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, void* rparam, const size_t size) +{ + // This may be changed over time, so use special interpretation + // only for certain types, and still display all data, no matter + // if it is expected to provide anything or not. + std::ostringstream out; + + out << "ARG="; + if (lparam) + out << *lparam; + else + out << "none"; + + if (size == 0) + { + out << " [no data]"; + return out.str(); + } + else if (!rparam) + { + out << " [ {" << size << "} ]"; + return out.str(); + } + + bool interp_as_seq = (pkttype == UMSG_LOSSREPORT || pkttype == UMSG_DROPREQ); + bool display_dec = (pkttype == UMSG_ACK || pkttype == UMSG_ACKACK || pkttype == UMSG_DROPREQ); + + out << " [ "; + + // Will be effective only for hex/oct. + out << std::showbase; + + const size_t size32 = size/4; + for (size_t i = 0; i < size32; ++i) + { + int32_t val = ((int32_t*)rparam)[i]; + if (interp_as_seq) + { + if (val & LOSSDATA_SEQNO_RANGE_FIRST) + out << "<" << (val & (~LOSSDATA_SEQNO_RANGE_FIRST)) << ">"; + else + out << val; + } + else + { + if (!display_dec) + { + out << std::hex; + out << val << "/"; + out << std::dec; + } + out << val; + + } + out << " "; + } + + out << "]"; + return out.str(); +} +#endif + +void 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) << " " << FormatNumbers(pkttype, lparam, rparam, size)); + + // 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; + + // 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; + + case UMSG_ACKACK: // 0110 - Acknowledgement of Acknowledgement (ACK-2) + // ACK packet seq. no. + 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; + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); - // 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_LOSSREPORT: // 0011 - Loss Report (NAK) + // loss list + m_PacketVector[PV_DATA].set(rparam, size); - case UMSG_ACKACK: //0110 - Acknowledgement of Acknowledgement (ACK-2) - // ACK packet seq. no. - m_nHeader[SRT_PH_MSGNO] = *(int32_t *)lparam; + break; - // control info field should be none - // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void *)&__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*)&m_extra_pad, 4); - break; + break; - case UMSG_LOSSREPORT: //0011 - Loss Report (NAK) - // loss list - m_PacketVector[PV_DATA].set(rparam, size); + 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_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; + case UMSG_HANDSHAKE: // 0000 - Handshake + // control info filed is handshake info + m_PacketVector[PV_DATA].set(rparam, size); - 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); + 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*)&m_extra_pad, 4); - case UMSG_HANDSHAKE: //0000 - Handshake - // control info filed is handshake info - m_PacketVector[PV_DATA].set(rparam, size); + break; - break; + case UMSG_DROPREQ: // 0111 - Message Drop Request + // msg id + m_nHeader[SRT_PH_MSGNO] = *lparam; - 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); + // first seq no, last seq no + m_PacketVector[PV_DATA].set(rparam, size); - break; + break; - case UMSG_DROPREQ: //0111 - Message Drop Request - // msg id - 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; - //first seq no, last seq no - m_PacketVector[PV_DATA].set(rparam, size); + // 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_PEERERROR: //1000 - Error Signal from the Peer Side - // Error type - m_nHeader[SRT_PH_MSGNO] = *(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; - // control info field should be none - // but "writev" does not allow this - 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; + 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; + default: + break; + } +} - if (NULL != rparam) - { - m_PacketVector[PV_DATA].set(rparam, size); - } - else - { - m_PacketVector[PV_DATA].set((void *)&__pad, 4); - } +void 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)); + } - break; + // convert packet header into network order + uint32_t* p = m_nHeader; + for (int j = 0; j < 4; ++j) + { + *p = htonl(*p); + ++p; + } +} + +void 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() { - return m_PacketVector; + return m_PacketVector; } UDTMessageType CPacket::getType() const @@ -344,11 +488,11 @@ int CPacket::getExtendedType() const int32_t 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 @@ -367,12 +511,12 @@ PacketBoundary CPacket::getMsgBoundary() const bool 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 { - if ( has_rexmit ) + if (has_rexmit) { return MSGNO_SEQ::unwrap(m_nHeader[SRT_PH_MSGNO]); } @@ -384,8 +528,13 @@ int32_t CPacket::getMsgSeq(bool has_rexmit) const bool 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]); +} + +void CPacket::setRexmitFlag(bool bRexmit) +{ + const int32_t clr_msgno = m_nHeader[SRT_PH_MSGNO] & ~MSGNO_REXMIT::mask; + m_nHeader[SRT_PH_MSGNO] = clr_msgno | MSGNO_REXMIT::wrap(bRexmit? 1 : 0); } EncryptionKeySpec CPacket::getMsgCryptoFlags() const @@ -398,79 +547,26 @@ EncryptionKeySpec CPacket::getMsgCryptoFlags() const // crypto flags after encrypting a packet. void 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) -{ - 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; -} - -EncryptionStatus CPacket::decrypt(HaiCrypt_Handle hcrypto) -{ - 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; -} - -*/ - 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; + // SRT_DEBUG_TSBPD_WRAP used to enable smaller timestamps for faster testing of how wraparounds are handled + return (uint32_t)m_nHeader[SRT_PH_TIMESTAMP] & TIMESTAMP_MASK; } CPacket* CPacket::clone() const { - 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; + CPacket* pkt = new CPacket; + memcpy((pkt->m_nHeader), m_nHeader, HDR_SIZE); + pkt->allocate(this->getLength()); + SRT_ASSERT(this->getLength() == pkt->getLength()); + memcpy((pkt->m_pcData), m_pcData, this->getLength()); + pkt->m_DestAddr = m_DestAddr; + + return pkt; } // Useful for debugging @@ -480,10 +576,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 +588,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; +} + +#if ENABLE_LOGGING +std::string 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 + +} // end namespace srt diff --git a/trunk/3rdparty/srt-1-fit/srtcore/packet.h b/trunk/3rdparty/srt-1-fit/srtcore/packet.h index e80e100af15..027d5f0b3b0 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; @@ -151,7 +150,7 @@ const int32_t LOSSDATA_SEQNO_RANGE_LAST = 0, LOSSDATA_SEQNO_SOLO = 0; inline int32_t CreateControlSeqNo(UDTMessageType type) { - return SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(size_t(type)); + return SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(uint32_t(type)); } inline int32_t CreateControlExtSeqNo(int exttype) @@ -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,183 @@ 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; + CPacket(); + ~CPacket(); - /// Set the payload or the control information field length. - /// @param len [in] the payload or the control information field length. + void allocate(size_t size); + void deallocate(); - void setLength(size_t len); + /// Get the payload or the control information field length. + /// @return the payload or the control information field length. + size_t getLength() const; - /// 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; + /// 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); - void pack(UDTMessageType pkttype, const void* lparam = NULL, void* rparam = NULL, int size = 0); + /// Set the payload or the control information field length. + /// @param len [in] the payload or the control information field length. + /// @param cap [in] capacity (if known). + void setLength(size_t len, size_t cap); - /// Read the packet vector. - /// @return Pointer to the packet vector. + /// 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); - IOVector* getPacketVector(); + /// Read the packet vector. + /// @return Pointer to the packet vector. + IOVector* getPacketVector(); - uint32_t* getHeader() { return m_nHeader; } + uint32_t* getHeader() { return m_nHeader; } - /// Read the packet flag. - /// @return packet flag (0 or 1). + /// Read the packet type. + /// @return packet type filed (000 ~ 111). + UDTMessageType getType() const; - // XXX DEPRECATED. Use isControl() instead - ATR_DEPRECATED - int getFlag() const - { - return isControl() ? 1 : 0; - } + bool isControl(UDTMessageType type) const { return isControl() && type == getType(); } - /// Read the packet type. - /// @return packet type filed (000 ~ 111). + bool isControl() const { return 0 != SEQNO_CONTROL::unwrap(m_nHeader[SRT_PH_SEQNO]); } - UDTMessageType getType() const; + void setControl(UDTMessageType type) { m_nHeader[SRT_PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(type); } - bool isControl(UDTMessageType type) const - { - return isControl() && type == getType(); - } + /// Read the extended packet type. + /// @return extended packet type filed (0x000 ~ 0xFFF). + int getExtendedType() const; - bool isControl() const - { - // read bit 0 - return 0!= SEQNO_CONTROL::unwrap(m_nHeader[SRT_PH_SEQNO]); - } + /// Read the ACK-2 seq. no. + /// @return packet header field (bit 16~31). + int32_t getAckSeqNo() const; - void setControl(UDTMessageType type) - { - m_nHeader[SRT_PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(type); - } + uint16_t getControlFlags() const; - /// Read the extended packet type. - /// @return extended packet type filed (0x000 ~ 0xFFF). + // Note: this will return a "singular" value, if the packet + // contains the control message + int32_t getSeqNo() const { return m_nHeader[SRT_PH_SEQNO]; } - int getExtendedType() const; + /// Read the message boundary flag bit. + /// @return packet header field [1] (bit 0~1). + PacketBoundary getMsgBoundary() const; - /// Read the ACK-2 seq. no. - /// @return packet header field (bit 16~31). + /// Read the message inorder delivery flag bit. + /// @return packet header field [1] (bit 2). + bool getMsgOrderFlag() const; - int32_t getAckSeqNo() const; - uint16_t getControlFlags() 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; - // Note: this will return a "singular" value, if the packet - // contains the control message - int32_t getSeqNo() const - { - return m_nHeader[SRT_PH_SEQNO]; - } + void setRexmitFlag(bool bRexmit); - /// 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). + sockaddr_any udpDestAddr() const { return m_DestAddr; } - 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; + sockaddr_any m_DestAddr; + size_t m_zCapacity; 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(); } + size_t capacity() const { return m_zCapacity; } + void setCapacity(size_t cap) { m_zCapacity = cap; } + 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 99dd4de7867..37785f43a1a 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(const 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(const 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); + unit->m_bTaken = true; + 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((uint32_t)nsupply); } // Now that all units have been filled as they should be, @@ -119,17 +178,16 @@ void PacketFilter::receive(CUnit* unit, ref_t< std::vector > r_incoming, // Wanted units will be set GOOD flag, unwanted will remain // 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) + unit->m_bTaken = false; + for (vector::iterator i = w_incoming.begin(); i != w_incoming.end(); ++i) { CUnit* u = *i; - u->m_iFlag = CUnit::FREE; + u->m_bTaken = false; } // 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,24 +247,24 @@ 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; } - // LOCK the unit as GOOD because otherwise the next + // LOCK the unit as taken because otherwise the next // call to getNextAvailUnit will return THE SAME UNIT. - u->m_iFlag = CUnit::GOOD; + u->m_bTaken = true; // After returning from this function, all units will be // set back to FREE so that the buffer can decide whether // it wants them or not. 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 fd5bf67d6d5..429e81e7958 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(const 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(const 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 787c90aeedc..3bfba7c7636 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(const 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 91e293a13cc..80983250a5c 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 fae95803f02..e2f0aa4d9c9 100644 --- a/trunk/3rdparty/srt-1-fit/srtcore/platform_sys.h +++ b/trunk/3rdparty/srt-1-fit/srtcore/platform_sys.h @@ -7,28 +7,113 @@ * 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 #include #include #include #include + +#ifndef __MINGW32__ + #include +#endif + + #ifdef SRT_IMPORT_TIME + #include + #endif + #include #include - #if defined(_MSC_VER) - #pragma warning(disable:4251) - #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 5ca8f031515..863148b34a1 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_bTaken = false; 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; + tempq->m_iSize = iNumUnits; - m_pAvailUnit = m_pCurrQueue->m_pUnit; - - 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; - - if (p == m_pLastQueue) - p = NULL; - else - p = p->m_pNext; - } - m_iCount = real_count; - if (double(m_iCount) / m_iSize < 0.9) - 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; + const int numUnits = m_iBlockSize; + HLOGC(qrlog.Debug, log << "CUnitQueue::increase: Capacity" << capacity() << " + " << numUnits << " new units, " << m_iNumTaken << " in use."); + CQEntry* tempq = allocateEntry(numUnits, m_iMSS); + if (tempq == NULL) 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() -{ - // currently queue cannot be shrunk. - return -1; -} - -CUnit *CUnitQueue::getNextAvailUnit() +srt::CUnit* srt::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) - if (m_pAvailUnit->m_iFlag == CUnit::FREE) - return m_pAvailUnit; - - if (m_pCurrQueue->m_pUnit->m_iFlag == CUnit::FREE) + const CUnit* end = m_pCurrQueue->m_pUnit + m_pCurrQueue->m_iSize; + for (; m_pAvailUnit != end; ++m_pAvailUnit, ++units_checked) { - m_pAvailUnit = m_pCurrQueue->m_pUnit; - return m_pAvailUnit; + if (!m_pAvailUnit->m_bTaken) + { + 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; + SRT_ASSERT(unit->m_bTaken); + unit->m_bTaken.store(false); + + --m_iNumTaken; } -void CUnitQueue::makeUnitGood(CUnit *unit) +void srt::CUnitQueue::makeUnitTaken(CUnit* unit) { + ++m_iNumTaken; + SRT_ASSERT(unit != NULL); - SRT_ASSERT(unit->m_iFlag == CUnit::FREE); - unit->m_iFlag = CUnit::GOOD; - ++m_iCount; + SRT_ASSERT(!unit->m_bTaken); + unit->m_bTaken.store(true); } -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,166 +422,210 @@ 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 CSndQueue::getIpToS() const { return m_pChannel ? m_pChannel->getIpToS() : -1; } +int srt::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 + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) +static void CSndQueueDebugHighratePrint(const srt::CSndQueue* self, const steady_clock::time_point currtime) +{ + if (self->m_DbgTime <= currtime) + { + fprintf(stdout, + "SndQueue %lu slt:%lu nrp:%lu snt:%lu nrt:%lu ctw:%lu\n", + self->m_WorkerStats.lIteration, + self->m_WorkerStats.lSleepTo, + self->m_WorkerStats.lNotReadyPop, + self->m_WorkerStats.lSendTo, + self->m_WorkerStats.lNotReadyTs, + self->m_WorkerStats.lCondWait); + memset(&self->m_WorkerStats, 0, sizeof(self->m_WorkerStats)); + self->m_DbgTime = currtime + self->m_DbgPeriod; + } +} #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); - self->m_ullDbgPeriod = uint64_t(5000000) * CTimer::getCPUFrequency(); - self->m_ullDbgTime += self->m_ullDbgPeriod; +#define IF_DEBUG_HIGHRATE(statement) statement + self->m_DbgTime = sync::steady_clock::now(); + self->m_DbgPeriod = sync::microseconds_from(5000000); + self->m_DbgTime += self->m_DbgPeriod; +#else +#define IF_DEBUG_HIGHRATE(statement) (void)0 #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ 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 */ + INCREMENT_THREAD_ITERATIONS(); + + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lIteration++); - if (next_time <= 0) + if (is_zero(next_time)) { -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lNotReadyTs++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyTs++); // 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); - -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lCondWait++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + self->m_pSndUList->waitNonEmpty(); + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lCondWait++); } 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); - -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - if (self->m_ullDbgTime <= currtime) - { - fprintf(stdout, - "SndQueue %lu slt:%lu nrp:%lu snt:%lu nrt:%lu ctw:%lu\n", - self->m_WorkerStats.lIteration, - self->m_WorkerStats.lSleepTo, - self->m_WorkerStats.lNotReadyPop, - self->m_WorkerStats.lSendTo, - self->m_WorkerStats.lNotReadyTs, - self->m_WorkerStats.lCondWait); - memset(&self->m_WorkerStats, 0, sizeof(self->m_WorkerStats)); - self->m_ullDbgTime = currtime + self->m_ullDbgPeriod; - } -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + const steady_clock::time_point currtime = steady_clock::now(); - THREAD_PAUSED(); + IF_DEBUG_HIGHRATE(CSndQueueDebugHighratePrint(self, currtime)); if (currtime < next_time) { - self->m_pTimer->sleepto(next_time); - -#if defined(HAI_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lSleepTo++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + THREAD_PAUSED(); + self->m_pTimer->sleep_until(next_time); + THREAD_RESUMED(); + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lSleepTo++); } - 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) { + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); continue; - -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lNotReadyPop++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ } - 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_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); + continue; } - else + + // pack a packet from the socket + CPacket pkt; + steady_clock::time_point next_send_time; + sockaddr_any source_addr; + const bool res = u->packData((pkt), (next_send_time), (source_addr)); + + // Check if extracted anything to send + if (res == false) { - HLOGC(dlog.Debug, - log << self->CONID() << "chn:SENDING SIZE " << pkt.getLength() << " SEQ: " << pkt.getSeqNo()); + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); + continue; } - self->m_pChannel->sendto(addr, pkt); -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) - self->m_WorkerStats.lSendTo++; -#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + const sockaddr_any addr = u->m_PeerAddr; + 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, source_addr); + + IF_DEBUG_HIGHRATE(self->m_WorkerStats.lSendTo++); } THREAD_EXIT(); return NULL; } -int CSndQueue::sendto(const sockaddr *addr, CPacket &packet) +int srt::CSndQueue::sendto(const sockaddr_any& addr, CPacket& w_packet, const sockaddr_any& src) { // send out the packet immediately (high priority), this is a control packet - m_pChannel->sendto(addr, packet); - return (int)packet.getLength(); + // NOTE: w_packet is passed by mutable reference because this function will do + // a modification in place and then it will revert it. After returning this object + // should look unmodified, hence it is here passed without a reference marker. + m_pChannel->sendto(addr, w_packet, src); + 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 +643,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 +674,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 +705,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 +727,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 +737,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 +752,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 +764,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 +789,345 @@ 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); + + IF_HEAVY_LOGGING(const char* const id_type = w_id ? "THIS ID" : "A NEW CONNECTION"); // 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; + // This procedure doesn't exactly respond to the original UDT idea. + // As the "rendezvous queue" is used for both handling rendezvous and + // the caller sockets in the non-blocking mode (for blocking mode the + // entire handshake procedure is handled in a loop-style in CUDT::startConnect), + // the RID list should give up a socket entity in the following cases: + // 1. For THE SAME id as passed in w_id, respond always, as per a caller + // socket that is currently trying to connect and is managed with + // HS roundtrips in an event-style. Same for rendezvous. + // 2. For the "connection request" ID=0 the found socket should be given up + // ONLY IF it is rendezvous. Normally ID=0 is only for listener as a + // connection request. But if there was a listener, then this function + // wouldn't even be called, as this case would be handled before trying + // to call this function. + // + // This means: if an incoming ID is 0, then this search should succeed ONLY + // IF THE FOUND SOCKET WAS RENDEZVOUS. + + if (!w_id && !i->m_pUDT->m_config.bRendezvous) + { + HLOGC(cnlog.Debug, + log << "RID: found id @" << i->m_iID << " while looking for " + << id_type << " FROM " << i->m_PeerAddr.str() + << ", but it's NOT RENDEZVOUS, skipping"); + continue; + } + + HLOGC(cnlog.Debug, + log << "RID: found id @" << i->m_iID << " while looking for " + << id_type << " 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 (cst != CONN_RENDEZVOUS && dest_id != 0) { - // 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; + if (i->id != dest_id) + { + HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " but for RID @" << i->id + << " dest_id=@" << dest_id << " - resetting to AGAIN"); + + read_st = RST_AGAIN; + conn_st = CONN_AGAIN; + } + else + { + HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " for @" + << i->id); + } + } + else + { + HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " and dest_id=@" << dest_id + << " - NOT checking against RID @" << i->id); } - 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); + } + } - // 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? + // 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. + + 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; + 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_iID != response.m_iID) - { - read_st = RST_AGAIN; - conn_st = CONN_AGAIN; - } + 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). - 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 - } + // 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,47 +1135,50 @@ 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(); - delete[] pkt->m_pcData; + CPacket* pkt = i->second.front(); delete pkt; i->second.pop(); } } } -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 +1189,46 @@ 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)); + + INCREMENT_THREAD_ITERATIONS(); 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 +1244,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 +1271,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 +1286,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 +1301,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 +1314,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 +1325,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 +1348,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,60 +1359,55 @@ 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.allocate(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 - delete[] temp.m_pcData; + // 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()); // Be transparent for RST_ERROR, but ignore the correct // data read and fake that the packet was dropped. 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 +1426,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 +1436,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 +1477,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 +1488,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 +1507,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 +1526,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 +1537,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"); - storePkt(id, unit->m_Packet.clone()); - if (!u->processAsyncConnectRequest(RST_AGAIN, CONN_CONTINUE, unit->m_Packet, u->m_pPeerAddr)) + LOGC(cnlog.Warn, log << "AsyncOrRND: PACKET NOT HANDSHAKE - re-requesting handshake from peer"); + storePktClone(id, unit->m_Packet); + 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 +1565,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 +1575,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 +1588,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,47 +1602,65 @@ 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. - storePkt(id, unit->m_Packet.clone()); + storePktClone(id, unit->m_Packet); return CONN_CONTINUE; } -int CRcvQueue::recvfrom(int32_t id, ref_t r_packet) +void srt::CRcvQueue::stopWorker() +{ + // We use the decent way, so we say to the thread "please exit". + m_bClosing = true; + + // 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) { - CGuard bufferlock(m_PassLock); - CPacket &packet = *r_packet; + CUniqueSync buffercond(m_BufferLock, m_BufferCond); - map >::iterator i = m_mBuffer.find(id); + 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,11 +1672,11 @@ 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()); + w_packet.m_DestAddr = newpkt->m_DestAddr; - delete[] newpkt->m_pcData; delete newpkt; // remove this message from queue, @@ -1589,12 +1685,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,36 +1699,38 @@ 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()) { - delete[] i->second.front()->m_pcData; delete i->second.front(); i->second.pop(); } @@ -1640,45 +1738,62 @@ 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::storePktClone(int32_t id, const 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); + m_mBuffer[id].push(pkt.clone()); + passcond.notify_one(); } else { - // avoid storing too many packets, in case of malfunction or attack + // Avoid storing too many packets, in case of malfunction or attack. if (i->second.size() > 16) return; - i->second.push(pkt); + i->second.push(pkt.clone()); + } +} + +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 6b93bf6c458..dd68a77214f 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,541 @@ 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 + sync::atomic m_bTaken; // true if the unit is is use (can be stored in the RCV buffer). }; 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 makeUnitTaken(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 - - CUnit* m_pAvailUnit; // recent available unit + 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); - 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 + /// @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()); - /// 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 + /// 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(); - void update(const CUDT* u, EReschedule reschedule); + /// Remove UDT instance from the list. + /// @param [in] u pointer to the UDT instance + void remove(const CUDT* u);// EXCLUDES(m_ListLock); - /// 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. + /// Retrieve the next scheduled processing time. + /// @return Scheduled processing time of the first UDT socket in the list. + sync::steady_clock::time_point getNextProcTime(); - int pop(sockaddr*& addr, CPacket& pkt); + /// Wait for the list to become non empty. + void waitNonEmpty() const; - /// Remove UDT instance from the list. - /// @param [in] u pointer to the UDT instance - - 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 guaranteed 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); - - // 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); + /// @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); - 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 ""; } - - /// Initialize the sending queue. - /// @param [in] c UDP channel to be associated to the queue - /// @param [in] t Timer - - 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. - - int sendto(const sockaddr* addr, CPacket& packet); - -#ifdef SRT_ENABLE_IPOPTS - /// Get the IP TTL. - /// @param [in] ttl IP Time To Live. - /// @return TTL. - - int getIpTTL() const; - - /// Get the IP Type of Service. - /// @return ToS. - - int getIpToS() const; + // 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 + void init(CChannel* c, sync::CTimer* t); + + /// Send out a packet to a given address. The @a src parameter is + /// blindly passed by the caller down the call with intention to + /// be received eventually by CChannel::sendto, and used only if + /// appropriate conditions state so. + /// @param [in] addr destination address + /// @param [in,ref] packet packet to be sent out + /// @param [in] src The source IP address (details above) + /// @return Size of data sent out. + int sendto(const sockaddr_any& addr, CPacket& packet, const sockaddr_any& src); + + /// Get the IP TTL. + /// @param [in] ttl IP Time To Live. + /// @return TTL. + int getIpTTL() const; + + /// Get the IP Type of Service. + /// @return ToS. + 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 - - pthread_mutex_t m_WindowLock; - pthread_cond_t m_WindowCond; + 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 - volatile bool m_bClosing; // closing the worker + sync::atomic m_bClosing; // closing the worker -#if defined(SRT_DEBUG_SNDQ_HIGHRATE)//>>debug high freq worker - uint64_t m_ullDbgPeriod; - uint64_t m_ullDbgTime; - struct { +public: +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) //>>debug high freq worker + sync::steady_clock::duration m_DbgPeriod; + mutable sync::steady_clock::time_point m_DbgTime; + 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 + } mutable m_WorkerStats; #endif /* SRT_DEBUG_SNDQ_HIGHRATE */ private: - CSndQueue(const CSndQueue&); - CSndQueue& operator=(const CSndQueue&); + +#if ENABLE_LOGGING + static int m_counter; +#endif + + 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 + /// 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); - void init(int size, int payload, int version, int hsize, CChannel* c, CTimer* t); + /// 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); - /// 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 stopWorker(); - int recvfrom(int32_t id, ref_t packet); + void setClosing() { m_bClosing = true; } - 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 receiving 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 storePktClone(int32_t id, const 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 00000000000..5c5b8e0903b --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.cpp @@ -0,0 +1,1095 @@ +/* + * 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) + { + const 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; + } +}; + +#ifdef ENABLE_MAXREXMITBW +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.llMaxRexmitBW = val; + } +}; +#endif + +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) + { + const bool val = cast_optval(optval, optlen); +#ifdef SRT_ENABLE_ENCRYPTION + if (val == false && co.iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) + { + using namespace srt_logging; + LOGC(aclog.Error, log << "Can't disable TSBPD as long as AES GCM is enabled."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } +#endif + + co.bTSBPD = 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; + 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 srt::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 << " bytes decreased by " << fc.extra_size + << " required for packet filter header"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + } + + // Not checking AUTO to allow defaul 1456 bytes. + if ((co.iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) + && (val > (SRT_LIVE_MAX_PLSIZE - HAICRYPT_AUTHTAG_MAX))) + { + LOGC(aclog.Error, + log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE << " bytes decreased by " << HAICRYPT_AUTHTAG_MAX + << " required for AES-GCM."); + 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; + } +}; + +#ifdef ENABLE_AEAD_API_PREVIEW +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + using namespace srt_logging; + const int val = cast_optval(optval, optlen); +#ifdef SRT_ENABLE_ENCRYPTION + if (val < CSrtConfig::CIPHER_MODE_AUTO || val > CSrtConfig::CIPHER_MODE_AES_GCM) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + if (val == CSrtConfig::CIPHER_MODE_AES_GCM && !HaiCrypt_IsAESGCM_Supported()) + { + LOGC(aclog.Error, log << "AES GCM is not supported by the crypto provider."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + if (val == CSrtConfig::CIPHER_MODE_AES_GCM && !co.bTSBPD) + { + LOGC(aclog.Error, log << "Enable TSBPD to use AES GCM."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + co.iCryptoMode = val; +#else + LOGC(aclog.Error, log << "SRT was built without crypto module."); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); +#endif + + } +}; +#endif + +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); +#ifdef ENABLE_AEAD_API_PREVIEW + DISPATCH(SRTO_CRYPTOMODE); +#endif +#ifdef ENABLE_MAXREXMITBW + DISPATCH(SRTO_MAXREXMITBW); +#endif + +#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 always -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 00000000000..403616edfed --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/socketconfig.h @@ -0,0 +1,410 @@ +/* + * 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 + + // NOTE: this operator is not reversable. The syntax must use: + // muxer_entry == socket_entry + bool isCompatWith(const CSrtMuxerConfig& other) const + { +#define CEQUAL(field) (field == other.field) + return CEQUAL(iIpTTL) + && CEQUAL(iIpToS) + && CEQUAL(bReuseAddr) +#ifdef SRT_ENABLE_BINDTODEVICE + && CEQUAL(sBindToDevice) +#endif + && CEQUAL(iUDPSndBufSize) + && CEQUAL(iUDPRcvBufSize) + && (other.iIpV6Only == -1 || CEQUAL(iIpV6Only)) + // NOTE: iIpV6Only is not regarded because + // this matches only in case of IPv6 with "any" address. + // And this aspect must be checked separately because here + // this procedure has no access to neither the address, + // nor the IP version (family). +#undef CEQUAL + && true; + } + + 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()); + } + + size_t copy(char* s, size_t length) const + { + if (!s) + return 0; + + size_t copy_len = std::min((size_t)len, length); + memcpy(s, stor, copy_len); + return copy_len; + } + + 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 + + enum + { + CIPHER_MODE_AUTO = 0, + CIPHER_MODE_AES_CTR = 1, + CIPHER_MODE_AES_GCM = 2 + }; + + 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 synchronization mode + bool bSynRecving; // Receiving synchronization 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) +#ifdef ENABLE_MAXREXMITBW + int64_t llMaxRexmitBW; // maximum bandwidth limit for retransmissions (Bytes/s). +#endif + + // 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; + int iCryptoMode; // SRTO_CRYPTOMODE + + 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) +#ifdef ENABLE_MAXREXMITBW + , llMaxRexmitBW(-1) +#endif + , 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) + , iCryptoMode(CIPHER_MODE_AUTO) + , 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 0e566e859fd..53b6fd274c0 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 + + +// Standard 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,40 @@ 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 +#ifdef ENABLE_AEAD_API_PREVIEW + SRTO_CRYPTOMODE = 62, // Encryption cipher mode (AES-CTR, AES-GCM, ...). +#endif +#ifdef ENABLE_MAXREXMITBW + SRTO_MAXREXMITBW = 63, // Maximum bandwidth limit for retransmision (Bytes/s) +#endif + + 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 +283,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 +328,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 +358,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 +399,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 +442,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 +469,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 +478,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 +489,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,56 +556,173 @@ 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 +#ifdef ENABLE_AEAD_API_PREVIEW + SRT_REJ_CRYPTO, // conflicting cryptographic configurations +#endif - 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 { - SRT_KM_S_UNSECURED = 0, //No encryption - SRT_KM_S_SECURING = 1, //Stream encrypted, exchanging Keying Material - SRT_KM_S_SECURED = 2, //Stream encrypted, keying Material exchanged, decrypting ok. - SRT_KM_S_NOSECRET = 3, //Stream encrypted and no secret to decrypt Keying Material - SRT_KM_S_BADSECRET = 4 //Stream encrypted and wrong secret, cannot decrypt Keying Material + SRT_KM_S_UNSECURED = 0, // No encryption + SRT_KM_S_SECURING = 1, // Stream encrypted, exchanging Keying Material + SRT_KM_S_SECURED = 2, // Stream encrypted, keying Material exchanged, decrypting ok. + SRT_KM_S_NOSECRET = 3, // Stream encrypted and no secret to decrypt Keying Material + SRT_KM_S_BADSECRET = 4 // Stream encrypted and wrong secret is used, cannot decrypt Keying Material +#ifdef ENABLE_AEAD_API_PREVIEW + ,SRT_KM_S_BADCRYPTOMODE = 5 // Stream encrypted but wrong cryptographic mode is used, cannot decrypt. Since v1.5.2. +#endif }; 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 +745,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 +759,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 +793,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 +840,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 +870,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 +890,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 +919,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 49f6d9f7afc..00000000000 --- 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 00000000000..84daabeb1cd --- /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 precede 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 56d875f7b6a..885c8006814 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; +} + +int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) +{ + if (!config) + return -1; + + 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); +} - // Until this rework is done, srt_create_socket() will set the - // default AF_INET family. +#else - // Note that all arguments except the first one here are ignored. - return CUDT::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +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; - - if (local_sin->sin_port != remote_sin->sin_port) - return SRT_EINVPARAM; + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); - 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,110 @@ 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" +#ifdef ENABLE_AEAD_API_PREVIEW + ,"Crypto mode" +#endif +}; + +// 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] +#ifdef ENABLE_AEAD_API_PREVIEW + , srt_rejection_reason_msg[17] +#endif +}; + +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 3db4a486bdc..fbf4859ae29 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 @@ -68,7 +70,7 @@ extern const char * SysStrError(int errnum, char * buf, size_t buflen) // your compilation fails when you use wide characters. // The problem is that when TCHAR != char, then the buffer written this way // would have to be converted to ASCII, not just copied by strncpy. - FormatMessage(0 + FormatMessageA(0 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -85,8 +87,12 @@ extern const char * SysStrError(int errnum, char * buf, size_t buflen) if (lpMsgBuf) { +#ifdef _MSC_VER + strncpy_s(buf, buflen, lpMsgBuf, _TRUNCATE); +#else strncpy(buf, lpMsgBuf, buflen-1); buf[buflen-1] = 0; +#endif LocalFree((HLOCAL)lpMsgBuf); } else diff --git a/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h b/trunk/3rdparty/srt-1-fit/srtcore/srt_compat.h index c05606bbd75..960c1b85a23 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 00000000000..bce60761b51 --- /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 00000000000..e99c8e2778c --- /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 rendezvous 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", // 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 00000000000..a7cebb90979 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/sync.cpp @@ -0,0 +1,359 @@ +/* + * 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 = + static_cast(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 + } +#if ENABLE_HEAVY_LOGGING + catch (const CThreadException& e) +#else + catch (const CThreadException&) +#endif + { + 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 00000000000..87be6f45848 --- /dev/null +++ b/trunk/3rdparty/srt-1-fit/srtcore/sync.h @@ -0,0 +1,947 @@ +/* + * 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 "platform_sys.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 definitions 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 occurred 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 occurred 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 occurred 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 00000000000..bdfbf9a4e2b --- /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 00000000000..8cb475ea769 --- /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 volatile("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 8fdcd9dfdcc..1c064c86c5c 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 00000000000..046c90b74a8 --- /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 00000000000..3483c197f29 --- /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 77f903bd50e..ee4c02f4d72 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 d323a165186..31e05b205e7 --- 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 d5ab8864248..c32ac122604 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 dfa1449e3a0..46889ecb07c 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 = (int)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) + for (int j = r_iTail, n = r_iHead + (int)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 = (int)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); @@ -170,7 +176,7 @@ int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, cons const int* bp = abytes; // median filtering const int* p = window; - for (int i = 0, n = asize; i < n; ++ i) + for (int i = 0, n = (int)asize; i < n; ++ i) { if ((*p < upper) && (*p > lower)) { @@ -185,8 +191,8 @@ 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 - bytesps = (unsigned long)ceil(1000000.0 / (double(sum) / double(bytes))); + bytes += (srt::CPacket::SRT_DATA_HDR_SIZE * count); //Add protocol headers to bytes received + bytesps = (int)ceil(1000000.0 / (double(sum) / double(bytes))); return (int)ceil(1000000.0 / (sum / count)); } else @@ -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: // @@ -234,7 +240,7 @@ int CPktTimeWindowTools::getBandwidth_in(const int* window, int* replica, size_t // median filtering const int* p = window; - for (int i = 0, n = psize; i < n; ++ i) + for (int i = 0, n = (int)psize; i < n; ++ i) { if ((*p < upper) && (*p > lower)) { diff --git a/trunk/3rdparty/srt-1-fit/srtcore/window.h b/trunk/3rdparty/srt-1-fit/srtcore/window.h index 41828837267..ecc4a49478f 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/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 18d2d593f2c..26d89ea49ac 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* v5.0, 2023-09-21, Merge [#3808](https://github.com/ossrs/srs/pull/3808): Upgrade libsrt to v1.5.3. v5.0.183 (#3808) * v5.0, 2023-09-21, Merge [#3404](https://github.com/ossrs/srs/pull/3404): WebRTC: Support WHEP for play. v5.0.182 (#3404) * v5.0, 2023-09-21, Merge [#3807](https://github.com/ossrs/srs/pull/3807): Prevent the output of srt logs in utest. v5.0.181 (#3807) * v5.0, 2023-09-21, Merge [#3696](https://github.com/ossrs/srs/pull/3696): SRT: modify log level from error to debug when no socket to accept. v5.0.180 (#3696) diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index ad57ddc131d..e754894673e 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 182 +#define VERSION_REVISION 183 #endif