diff --git a/.appveyor.yml b/.appveyor.yml index 1254c9c16..70d84ee87 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,7 +5,6 @@ configuration: image: - Visual Studio 2022 - Visual Studio 2019 - - Visual Studio 2015 platform: - x64 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..9736fe321 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "name": "SRT Development", + "dockerFile": "../docker/Dockerfile", + "workspaceFolder": "/srt_build_env", + "workspaceMount": "source=${localWorkspaceFolder},target=/srt_build_env,type=bind", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + }, + "extensions": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "twxs.cmake" + ] + } + }, + "remoteUser": "srt-dev" +} diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml index 4d4d6012c..51f440a62 100644 --- a/.github/workflows/abi.yml +++ b/.github/workflows/abi.yml @@ -32,7 +32,7 @@ jobs: run: | sudo apt install -y abi-dumper sudo apt install -y tcl - cd gitview_pr/_build && cmake --build ./ + cd gitview_pr/_build && cmake --build ./ --parallel make install DESTDIR=./installdir SRT_TAG_VERSION=v$(../scripts/get-build-version.tcl full) echo "SRT_TAG_VERSION=$SRT_TAG_VERSION" >> "$GITHUB_OUTPUT" @@ -89,7 +89,7 @@ jobs: sudo apt install -y abi-dumper sudo apt install -y tcl cd gitview_base - cd _build && cmake --build ./ + cd _build && cmake --build ./ --parallel make install DESTDIR=./installdir echo "TAGGING BASE BUILD: $SRT_BASE" abi-dumper libsrt.so -o libsrt-base.dump -public-headers installdir/usr/local/include/srt/ -lver $SRT_BASE diff --git a/.github/workflows/cxx03-ubuntu.yaml b/.github/workflows/cxx03-ubuntu.yaml index ce27436aa..faacb3bb8 100644 --- a/.github/workflows/cxx03-ubuntu.yaml +++ b/.github/workflows/cxx03-ubuntu.yaml @@ -19,7 +19,7 @@ jobs: - name: build # That below is likely SonarQube remains, which was removed earlier. #run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . - run: cd _build && cmake --build . + run: cd _build && make -j -k - name: test run: | cd _build && ctest --extra-verbose diff --git a/.github/workflows/cxx11-macos.yaml b/.github/workflows/cxx11-macos.yaml index b03b4f75f..d9cdfdd66 100644 --- a/.github/workflows/cxx11-macos.yaml +++ b/.github/workflows/cxx11-macos.yaml @@ -24,8 +24,8 @@ jobs: - name: configure run: | mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DUSE_CXX_STD=17 + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DUSE_CXX_STD=17 -DENABLE_HEAVY_LOGGING=ON - name: build - run: cd _build && cmake --build ./ + run: cd _build && make -j -k - name: test run: cd _build && ctest --extra-verbose diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index eda7ecca3..46cf13e4c 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -17,7 +17,7 @@ jobs: mkdir _build && cd _build cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: build - run: cd _build && cmake --build . + run: cd _build && cmake --build . --parallel - name: test run: | cd _build && ctest --extra-verbose diff --git a/.github/workflows/cxx11-win.yaml b/.github/workflows/cxx11-win.yaml index 2947809ab..d98a7c00f 100644 --- a/.github/workflows/cxx11-win.yaml +++ b/.github/workflows/cxx11-win.yaml @@ -19,6 +19,6 @@ jobs: md _build && cd _build cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_LOCALIF_WIN32=ON -DUSE_CXX_STD=c++11 - name: build - run: cd _build && cmake --build ./ --config Release --verbose + run: cd _build && cmake --build ./ --parallel --config Release --verbose - name: test run: cd _build && ctest -E "TestIPv6.v6_calls_v4|TestConnectionTimeout.BlockingLoop" --extra-verbose -C Release diff --git a/.github/workflows/iOS.yaml b/.github/workflows/iOS.yaml index 9ae9e9124..c8a7285fb 100644 --- a/.github/workflows/iOS.yaml +++ b/.github/workflows/iOS.yaml @@ -10,7 +10,7 @@ jobs: build: strategy: matrix: - cxxstdsync: [OFF, ON] + cxxstdsync: [ON] #[OFF, ON] # cxxsync=off is Linux-gcc-only. name: iOS-cxxsync${{ matrix.cxxstdsync }} runs-on: macos-latest @@ -26,4 +26,4 @@ jobs: export PATH="/opt/homebrew/opt/llvm/bin:$PATH" CC=clang CXX=clang++ cmake .. -DCMAKE_MAKE_PROGRAM=gmake -DENABLE_ENCRYPTION=OFF -DENABLE_STDCXX_SYNC=${{matrix.cxxstdsync}} -DENABLE_MONOTONIC_CLOCK=OFF -DENABLE_UNITTESTS=OFF -DUSE_CXX_STD=c++11 -DENABLE_BONDING=ON --toolchain scripts/iOS.cmake - name: build - run: cd _build && cmake --build ./ + run: cd _build && cmake --build ./ --parallel diff --git a/.github/workflows/s390x-focal.yaml b/.github/workflows/s390x-focal.yaml index d91991456..5f030c99a 100644 --- a/.github/workflows/s390x-focal.yaml +++ b/.github/workflows/s390x-focal.yaml @@ -34,8 +34,8 @@ jobs: apt-get -y install tzdata && uname -a && lscpu | grep Endian && - apt-get -y install cmake g++ libssl-dev git && + apt-get -y install cmake g++ libssl-dev git python && mkdir _build && cd _build && - cmake ../ -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON && - cmake --build ./ && + cmake --debug-output ../ -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON && + cmake --build ./ --parallel && ./test-srt -disable-ipv6" diff --git a/.travis.yml b/.travis.yml index 5c54148ea..ee6a2ceab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ # - COLON characters (in freestanding strings) not allowed in the script language: cpp -dist: xenial +dist: trusty addons: apt: @@ -32,6 +32,11 @@ jobs: - env: - BUILD_TYPE=Debug CFG="nologging mbedtls monotonic werror" - CMAKE_OPTS='-DENABLE_LOGGING=OFF -DUSE_ENCLIB=mbedtls -DENABLE_MONOTONIC_CLOCK=ON -DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' + - os: linux + dist: xenial + env: + - BUILD_TYPE=Release CFG="old-distro notests werror" + - CMAKE_OPTS='-DUSE_CXX_STD=C++98 -DCMAKE_CXX_FLAGS="-Werror"' - os: linux env: BUILD_TYPE=Release CFG=default # - os: osx @@ -60,7 +65,7 @@ jobs: - ./Configure --cross-compile-prefix=x86_64-w64-mingw32- mingw64 - make - cd .. - env: BUILD_TYPE=Release CFG=no-UT + env: BUILD_TYPE=Release CFG=notests # Power jobs # Forcing Focal distro because Xenial @@ -84,6 +89,7 @@ script: exit 1; fi; - export REQUIRE_UNITTESTS=1 + - if [[ $CFG == *"notests"* ]]; then REQUIRE_UNITTESTS=0; fi - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then CMAKE_OPTS+=" -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc"; CMAKE_OPTS+=" -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++"; diff --git a/test/any.hpp b/ATTIC/any.hpp similarity index 100% rename from test/any.hpp rename to ATTIC/any.hpp diff --git a/apps/logsupport.cpp b/ATTIC/logsupport.cpp similarity index 88% rename from apps/logsupport.cpp rename to ATTIC/logsupport.cpp index fbd70c47e..705de10ac 100644 --- a/apps/logsupport.cpp +++ b/ATTIC/logsupport.cpp @@ -17,15 +17,20 @@ #include "logsupport.hpp" #include "../srtcore/srt.h" #include "../srtcore/utilities.h" +#include "ofmt.h" using namespace std; +using namespace srt; + +namespace hvu +{ // This is based on codes taken from // This is POSIX standard, so it's not going to change. // Haivision standard only adds one more severity below // DEBUG named DEBUG_TRACE to satisfy all possible needs. -map srt_level_names +map level_names { { "alert", LOG_ALERT }, { "crit", LOG_CRIT }, @@ -46,9 +51,9 @@ map srt_level_names -srt_logging::LogLevel::type SrtParseLogLevel(string level) +hvu::logging::LogLevel::type ParseLogLevel(string level) { - using namespace srt_logging; + using namespace hvu::logging; if ( level.empty() ) return LogLevel::fatal; @@ -56,7 +61,7 @@ srt_logging::LogLevel::type SrtParseLogLevel(string level) if ( isdigit(level[0]) ) { long lev = strtol(level.c_str(), 0, 10); - if ( lev >= SRT_LOG_LEVEL_MIN && lev <= SRT_LOG_LEVEL_MAX ) + if ( lev >= HVU_LOG_LEVEL_MIN && lev <= HVU_LOG_LEVEL_MAX ) return LogLevel::type(lev); cerr << "ERROR: Invalid loglevel number: " << level << " - fallback to FATAL\n"; @@ -91,24 +96,13 @@ struct ToLowerFormat } }; -void LogFANames::Install(string upname, int value) -{ - string id; - transform(upname.begin(), upname.end(), back_inserter(id), ToLowerFormat()); - namemap[id] = value; -} // See logsupport_appdefs.cpp for log FA definitions LogFANames srt_transmit_logfa_names; -const map SrtLogFAList() +set ParseLogFA(string fa, set* punknown) { - return srt_transmit_logfa_names.namemap; -} - -set SrtParseLogFA(string fa, set* punknown) -{ - using namespace srt_logging; + using namespace hvu::logging; set fas; @@ -173,7 +167,7 @@ set SrtParseLogFA(string fa, set* punknown) void ParseLogFASpec(const vector& speclist, string& w_on, string& w_off) { - std::ostringstream son, soff; + hvu::ofmtbufstream son, soff; for (auto& s: speclist) { @@ -202,4 +196,5 @@ void ParseLogFASpec(const vector& speclist, string& w_on, string& w_off) w_off = soffs.empty() ? string() : soffs.substr(1); } +} diff --git a/apps/logsupport.hpp b/ATTIC/logsupport.hpp similarity index 53% rename from apps/logsupport.hpp rename to ATTIC/logsupport.hpp index 79115d726..d6c6a4d72 100644 --- a/apps/logsupport.hpp +++ b/ATTIC/logsupport.hpp @@ -14,21 +14,16 @@ #include #include #include -#include "../srtcore/srt.h" -#include "../srtcore/logging_api.h" +#include "logging_api.h" -srt_logging::LogLevel::type SrtParseLogLevel(std::string level); -std::set SrtParseLogFA(std::string fa, std::set* punknown = nullptr); +namespace hvu +{ + +hvu::logging::LogLevel::type ParseLogLevel(std::string level); +std::set ParseLogFA(std::string fa, std::set* punknown = nullptr); void ParseLogFASpec(const std::vector& speclist, std::string& w_on, std::string& w_off); -const std::map SrtLogFAList(); -SRT_API extern std::map srt_level_names; -struct LogFANames -{ - std::map namemap; - void Install(std::string upname, int value); - LogFANames(); -}; +} #endif diff --git a/apps/logsupport_appdefs.cpp b/ATTIC/logsupport_appdefs.cpp similarity index 100% rename from apps/logsupport_appdefs.cpp rename to ATTIC/logsupport_appdefs.cpp diff --git a/ATTIC/sync_timer.cpp b/ATTIC/sync_timer.cpp new file mode 100644 index 000000000..84ed8551c --- /dev/null +++ b/ATTIC/sync_timer.cpp @@ -0,0 +1,126 @@ +#include "sync_timer.h" + +namespace srt +{ +namespace sync +{ + + +//////////////////////////////////////////////////////////////////////////////// +// +// Timer +// +//////////////////////////////////////////////////////////////////////////////// + +CTimer::CTimer() +{ +} + + +CTimer::~CTimer() +{ +} + +// This function sleeps up to the given time, then exits. +// Meanwhile it can be influenced from another thread by calling: +// - tick(): exit waiting, but re-check the end time and fall back to sleep if not reached +// - interrupt(): exit waiting with setting wait time to now() so that it exits immediately +// +// This function returns true if it has exit on the originally set time. +// If the time was changed due to being interrupted and it did really exit before +// that time, false is returned. +bool 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 SRT_BUSY_WAITING + wait_busy(); +#else + wait_stalled(); +#endif + + // Returning false means that sleep was early interrupted + return m_tsSchedTime.load() >= tp; +} + +void CTimer::wait_stalled() +{ + TimePoint cur_tp = steady_clock::now(); + { + UniqueLock elk (m_event.mutex()); + while (cur_tp < m_tsSchedTime.load()) + { + m_event.wait_until(elk, m_tsSchedTime); + cur_tp = steady_clock::now(); + } + } +} + +void srt::sync::CTimer::wait_busy() +{ +#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 + + TimePoint cur_tp = steady_clock::now(); + { + UniqueLock elk (m_event.mutex()); + while (cur_tp < m_tsSchedTime.load()) + { + steady_clock::duration td_wait = m_tsSchedTime.load() - cur_tp; + if (td_wait <= 2 * td_threshold) + break; + + td_wait -= td_threshold; + m_event.wait_for(elk, td_wait); + + cur_tp = steady_clock::now(); + } + + while (cur_tp < m_tsSchedTime.load()) + { + InvertedLock ulk (m_event.mutex()); +#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(); + } + } +} + + +void CTimer::interrupt() +{ + UniqueLock lck(m_event.mutex()); + m_tsSchedTime = steady_clock::now(); + m_event.notify_all(); +} + + +void CTimer::tick() +{ + m_event.notify_one(); +} + +} +} diff --git a/ATTIC/sync_timer.h b/ATTIC/sync_timer.h new file mode 100644 index 000000000..57b4ede27 --- /dev/null +++ b/ATTIC/sync_timer.h @@ -0,0 +1,44 @@ +#include "sync.h" // Requires CEvent + +namespace srt +{ +namespace sync +{ + +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 comparison to the target time. + void tick(); + +private: + CEvent m_event; + sync::AtomicClock m_tsSchedTime; + + void wait_busy(); + void wait_stalled(); +}; + + +} +} diff --git a/test/test_timer.cpp b/ATTIC/test_timer.cpp similarity index 100% rename from test/test_timer.cpp rename to ATTIC/test_timer.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 130a0adf9..ad04e7169 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,108 +7,187 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -cmake_minimum_required (VERSION 3.5 FATAL_ERROR) -set (SRT_VERSION 1.5.5) +cmake_minimum_required (VERSION 3.10 FATAL_ERROR) +set (SRT_VERSION 1.6.0) +# Also sets SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH +project(SRT VERSION ${SRT_VERSION} LANGUAGES C CXX) -set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") -include(CheckSymbolExists) -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() - -if (NOT ${CMAKE_VERSION} VERSION_LESS "3.28.1") - cmake_policy(SET CMP0054 NEW) -endif () +# --------- +# Includes +# --------- +set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") +include(haiUtil) include(FindPkgConfig) -# XXX See 'if (MINGW)' condition below, may need fixing. include(FindThreads) include(CheckFunctionExists) +include(GNUInstallDirs) +include(FindPThreadGetSetName) +include(CheckGCCAtomicIntrinsics) +include(CheckCXXAtomic) +include(CheckCXXStdPutTime) + +# This is required in some projects that add some other sources +# to the SRT library to be compiled together (aka "virtual library"). +if (DEFINED SRT_EXTRA_LIB_INC) + include(${SRT_EXTRA_LIB_INC}.cmake) + # Expected to provide variables: + # - SOURCES_srt_extra + # - EXTRA_stransmit +endif() +# Required before platform shortcuts +option(ENABLE_CYGWIN_POSIX "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin." OFF) + +# ------------------ # Platform shortcuts +# ------------------ string(TOLOWER ${CMAKE_SYSTEM_NAME} SYSNAME_LC) -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") - OR (${CMAKE_SYSTEM_NAME} MATCHES "visionOS")) -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_OS ${CMAKE_SYSTEM_NAME} MATCHES "GNU") # Use GNU_OS to not confuse with gcc -set_if(ANDROID ${SYSNAME_LC} MATCHES "android") -set_if(OHOS ${CMAKE_SYSTEM_NAME} MATCHES "OHOS") -set_if(SUNOS "${SYSNAME_LC}" MATCHES "sunos") -set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR OHOS OR (CYGWIN AND CYGWIN_USE_POSIX) OR GNU_OS) -set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU_OS) -set_if(NEED_DESTINATION ${CMAKE_VERSION} VERSION_LESS "3.14.0") -include(GNUInstallDirs) +# Symbols already defined in cmake: +# MINGW is added in cmake 3.2 +# ANDROID is added in cmake 3.7 + +# Symbols not always defined: +# LINUX is added in cmake 3.20 +set1_if(LINUX ${CMAKE_SYSTEM_NAME} MATCHES "Linux") +# BSD is added in cmake 3.25 +set1_if(BSD ${SYSNAME_LC} MATCHES "bsd$") + +# Convenience shortcuts: +# DARWIN is not defined, although system name is available. +# These are other posix-like systems: +set1_if(DARWIN (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") OR APPLE) +set1_if(GNU_OS ${CMAKE_SYSTEM_NAME} MATCHES "GNU") # Use GNU_OS to not confuse with gcc +set1_if(OHOS ${CMAKE_SYSTEM_NAME} MATCHES "OHOS") +set1_if(SUNOS ${SYSNAME_LC} MATCHES "sunos") +set1_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR OHOS OR (CYGWIN AND CYGWIN_USE_POSIX) OR GNU_OS) +set1_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU_OS) + +# "MICROSOFT" means "compiling on Microsoft Windows using Visual Studio compiler" +# (using elimination method - note that MINGW includes also potentially LLVM/Clang version) +set1_if(MICROSOFT WIN32 AND (NOT MINGW AND NOT CYGWIN)) + +# Cmake-version-dependent symbols for convenience +set1_if(NEED_DESTINATION ${CMAKE_VERSION} VERSION_LESS "3.14.0") + +# Prepare special variable for feature report + +set (FEATURE_REPORT "FEAT: ") + -# The CMAKE_BUILD_TYPE seems not to be always set, weird. -if (NOT DEFINED ENABLE_DEBUG) +# ---------------------- +# Basic system PP macros +# ---------------------- - if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set (ENABLE_DEBUG ON) +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 (no macro added)") +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(OHOS) + add_definitions(-DLINUX=1) + message(STATUS "DETECTED SYSTEM: OHOS; LINUX=1" ) +elseif(CYGWIN) +# For Cygwin with MinGW (cross-system compiling) + if (NOT ENABLE_CYGWIN_POSIX) + set(WIN32 1) + set(CMAKE_LEGACY_CYGWIN_WIN32 1) + add_definitions(-DWIN32=1 -DCYGWIN=1) + message(STATUS "DETECTED SYSTEM: CYGWIN (NO POSIX). Setting backward compat CMAKE_LEGACY_CYGWIN_WIN32 and -DWIN32") else() - set (ENABLE_DEBUG OFF) + add_definitions(-DCYGWIN=1) + message(STATUS "DETECTED SYSTEM: CYGWIN (posix mode); CYGWIN=1") endif() +elseif(GNU_OS) + 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() -# 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) +# 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 (CMAKE_CONFIGURATION_TYPES) + if (DEFINED ENABLE_DEBUG OR DEFINED ENABLE_ASSERT) + message(FATAL_ERROR "Do not define ENABLE_DEBUG in multi-config generators.") + endif() + + message(STATUS "MULTI-BUILD TYPES: ${CMAKE_CONFIGURATION_TYPES}") +else() - # 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") + # In single configuration generators, you can do the following: + # - do not provide ENABLE_DEBUG = rely on CMAKE_BUILD_TYPE. + # - ENABLE_DEBUG=0 - force Release mode, #define NDEBUG 1 + # - ENABLE_DEBUG=1 - force Debug mode, #define _DEBUG 1 + # - ENABLE_DEBUG=2 - RelWithDebInfo mode, define + # _DEBUG=1 only if ENABLE_ASSERT, otherwise NDEBUG=1 + + # You can enforce release by using --disable-debug and -DENABLE_DEBUG=0. + # Check if this is the case. + if (ENABLE_DEBUG EQUAL 0) + set (CMAKE_BUILD_TYPE Release) + set (SRT_DEBUG_MODE 0) + elseif (ENABLE_DEBUG EQUAL 2) + set (CMAKE_BUILD_TYPE RelWithDebInfo) + set (SRT_DEBUG_MODE 2) + elseif (ENABLE_DEBUG) + set (CMAKE_BUILD_TYPE Debug) + set (SRT_DEBUG_MODE 1) + else() + # Do not set CMAKE_BUILD_TYPE. Rely on it. + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set (SRT_DEBUG_MODE 1) + elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set (SRT_DEBUG_MODE 2) + else() + set (SRT_DEBUG_MODE 0) + endif() + endif() + # NOTE: DO NOT set CMAKE_BUILD_TYPE to anything if ENABLE_DEBUG + # was not defined. + + # Set properly _DEBUG and NDEBUG flags: + # - NDEBUG is defined only in Release + # - _DEBUG is defined in Debug + # - In RelWithDebInfo it is decided basing on ENABLE_ASSERT + if (SRT_DEBUG_MODE EQUAL 2) if (ENABLE_ASSERT) # Add _DEBUG macro if explicitly requested, to enable SRT_ASSERT(). add_definitions(-D_DEBUG) else() add_definitions(-DNDEBUG) endif() - elseif (ENABLE_DEBUG) # 1, ON, YES, TRUE, Y, or any other non-zero number - set (CMAKE_BUILD_TYPE "Debug") - + elseif (SRT_DEBUG_MODE) # 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}") + message(STATUS "BUILD TYPE: ${CMAKE_BUILD_TYPE}, DEBUG MODE: ${SRT_DEBUG_MODE} ASSERT: ${ENABLE_ASSERT}") -getVarsWith(ENFORCE_ enforcers) -foreach(ef ${enforcers}) - set (val ${${ef}}) - if (NOT val STREQUAL "") - set(val =${val}) - endif() - string(LENGTH ENFORCE_ pflen) - string(LENGTH ${ef} eflen) - math(EXPR alen ${eflen}-${pflen}) - string(SUBSTRING ${ef} ${pflen} ${alen} ef) - message(STATUS "FORCED PP VARIABLE: ${ef}${val}") - add_definitions(-D${ef}${val}) -endforeach() + # No longer needed and can be confusing + unset (SRT_DEBUG_MODE) +endif() + +# Add -D=1 for every ENFORCE_ variable +addDefinitionsFromPrefixed( ENFORCE_ ) # NOTE: Known options you can change using ENFORCE_ variables: # SRT_ENABLE_ECN 1 /* Early Congestion Notification (for source bitrate control) */ @@ -122,6 +201,10 @@ endforeach() # SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ # SRT_ENABLE_FREQUENT_LOG_TRACE 0 : set to 1 to enable printing reason for suppressed freq logs +# --------------------------------------- +# Configure compiler type and directories +# --------------------------------------- + # CMake offers sometimes OLD behavior, where names that are defined below with # option() function, will be deleted if they are not in cache or do not have set # type - so effectively options provided through LIBSRT_* prefix would be rejected. @@ -132,234 +215,623 @@ cmake_policy(SET CMP0077 NEW) # Names are not being checked, at worst it will set an unused variable. srt_import_parent_options() -# option defaults -set(ENABLE_HEAVY_LOGGING_DEFAULT OFF) -# Always turn logging on if the build type is debug -if (ENABLE_DEBUG) - set(ENABLE_HEAVY_LOGGING_DEFAULT ON) +# Make sure DLLs and executables go to the same path regardless of subdirectory +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + +# ------------------------------ +# ---- +# This section determines the compiler type and specifics to know +# what is currently available for options. This handles also the +# toolchain-changing variable WITH_COMPILER_TYPE/WITH_COMPILER_PREFIX. +# ---- +# ------------------------------ + +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() -# Note that the IP_PKTINFO has a limited portability and may not work on some platforms -# (Windows, FreeBSD, ...). -set (ENABLE_PKTINFO_DEFAULT OFF) +if (NOT USING_DEFAULT_COMPILER_PREFIX OR DEFINED WITH_COMPILER_PREFIX) + # Example: WITH_COMPILER_PREFIX=/opt/gcc-10/bin/x86_64-linux-gnu- WITH_COMPILER_TYPE=gcc-10.3 + # RESULTS IN: + # * CMAKE_C_COMPILER: /opt/gcc-10/bin/x86_64-linux-gnu-gcc-10.3 + # * CMAKE_CXX_COMPILER: /opt/gcc-10/bin/x86_64-linux-gnu-g++-10.3 + # * HAVE_COMPILER_GNU_COMPAT: 1 + srt_configure_compiler("${WITH_COMPILER_TYPE}" "${WITH_COMPILER_PREFIX}" + CMAKE_C_COMPILER CMAKE_CXX_COMPILER SRT_COMPILER_TYPE) + + message(STATUS "Compiler prefix=${WITH_COMPILER_PREFIX} type=${WITH_COMPILER_TYPE} -> C: ${CMAKE_C_COMPILER}; C++: ${CMAKE_CXX_COMPILER}") + unset(USING_DEFAULT_COMPILER_PREFIX) +else() + message(STATUS "Compiler unchanged (default): C: ${CMAKE_C_COMPILER}; C++: ${CMAKE_CXX_COMPILER}") -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) + # This should theoretically use the bare compiler command, but + # we can later match both, no problem. + set(SRT_COMPILER_TYPE ${CMAKE_CXX_COMPILER_ID}) 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() -# options -option(CYGWIN_USE_POSIX "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin." OFF) -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) +else() # Compiler altered by WITH_COMPILER_TYPE/PREFIX - can't rely on CMAKE_CXX_* + + # (we can still rely on the manually extracted compiler type) + if (SRT_COMPILER_TYPE MATCHES "cc|gcc|clang|GNU|Intel|Clang|AppleClang") + set (HAVE_COMPILER_GNU_COMPAT 1) + endif() + + # We can only state which version of gcc + message(STATUS "COMPILER CHANGED TO: ${SRT_COMPILER_TYPE} - NOT forcing C++11 standard for apps") endif() + + +# ------------------------------------------ +# Default option values (platform-dependent) +# (including autodetections if needed) +# ------------------------------------------ + +# PKTINFO not supported on these platforms. +seton_if(ENABLE_PKTINFO_DEFAULT NOT (BSD OR WIN32)) +seton_if(ENABLE_STDCXX_SYNC_FORCED DEFINED ENABLE_STDCXX_SYNC) + +# Default to be controlled by an environment variable HAI_BUILD_PROFILE +seton_if(ENABLE_PROFILE_DEFAULT ENV{HAI_BUILD_PROFILE}) + +# ------- +# options +# ------- + +# See docs/build/build-options.md for detailed explanation. + +# Build and code variant options 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_CXX_DEPS "Extra library dependencies in srt.pc for the CXX libraries useful with C language" ON) +# Note that in case of C++11 sync, this option is ignored. +option(ENABLE_MONOTONIC_CLOCK "Enforced clock_gettime with monotonic clock for getting time with POSIX sync" ON) +option(ENABLE_STDCXX_SYNC "Use C++11 chrono and threads for timing instead of pthreads" ON) +option(ENABLE_CLOEXEC "Enable setting various *_CLOEXEC flags on system resources" ON) +# NOTE: Use USE_MUTEX_ATOMIC and will override the auto-detection of the +# Atomic implementation in srtcore/atomic.h. +option(USE_MUTEX_ATOMIC "Use srt::sync::Mutex to Implement Atomics" OFF) + +# Development options +option(ENABLE_SHOW_PROJECT_CONFIG "Enable show Project Configuration" OFF) +option(ENABLE_CLANG_TSA "Enable Clang Thread Safety Analysis" OFF) +option(ENABLE_CODE_COVERAGE "Enable code coverage reporting" OFF) +option(ENABLE_PROFILE "Should instrument the code for profiling. Ignored for non-GNU compiler." ${ENABLE_PROFILE_DEFAULT}) +option(ENABLE_TESTING "Should the Developer Test Applications be Built?" OFF) option(ENABLE_GETNAMEINFO "In-logs sockaddr-to-string should do rev-dns" OFF) +option(ENABLE_LOGGING "Should logging be enabled" ON) +option(ENABLE_HEAVY_LOGGING "Should heavy debug logging be enabled" OFF) +option(ENABLE_HAICRYPT_LOGGING "Should logging in haicrypt be enabled" 0) +option(ENABLE_THREAD_DEBUG "Enable userspace special mutex debug facilities" OFF) option(ENABLE_UNITTESTS "Enable unit tests" OFF) -option(ENABLE_UNITTESTS_DISCOVERY "Do unit test discovery when unit tests enabled" ON) +option(ENABLE_UNITTESTS_DISCOVERY "Do unit test discovery when UT enabled" ON) + +# Built-in optional features +option(ENABLE_BONDING "Should the bonding functionality be enabled?" ON) 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(ENABLE_LOCALIF_WIN32 "Enable local interface check ability on Windows (adds Iphlpapi.lib dep)" ON) +option(ENABLE_AEAD "Enable AEAD API preview in SRT" ON) +option(ENABLE_MAXREXMITBW "Enable SRTO_MAXREXMITBW (limiting rexmit bandwidth)" ON) + +# Variant selection 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" ${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(SRT_USE_OPENSSL_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) +# Non-boolean options -if (DEFINED OPENSSL_USE_STATIC_LIBS AND "${OPENSSL_USE_STATIC_LIBS}" STREQUAL "ON") - message(WARNING "Use of OPENSSL_USE_STATIC_LIBS as SRT build option here is deprecated. -Please use SRT_USE_OPENSSL_STATIC_LIBS instead.") - set(SRT_USE_OPENSSL_STATIC_LIBS ${OPENSSL_USE_STATIC_LIBS}) +set_default(USE_ENCLIB openssl-evp) +set(USE_ENCLIB "${USE_ENCLIB}" CACHE STRING "The crypto library that SRT uses") +set_property(CACHE USE_ENCLIB PROPERTY STRINGS "openssl" "openssl-evp" "gnutls" "mbedtls" "botan") + +# --------------------------------------------------------- +# Post-checks for having compiler type and version selected +# --------------------------------------------------------- + +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 98/03/11/14/17/20 possibly with c++/gnu++ prefix") + endif() + + if (NOT STDCXX STREQUAL "") + + if (${STDCXX} LESS 11) + if (ENABLE_STDCXX_SYNC) + if (ENABLE_STDCXX_SYNC_FORCED) + message(FATAL_ERROR "If ENABLE_STDCXX_SYNC, then you can't USE_CXX_STD less than 11") + else() + message(WARNING "C++98 standard enforced -- setting ENABLE_STDCXX_SYNC=OFF") + set (ENABLE_STDCXX_SYNC OFF) + endif() + 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() + 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) + if ((${STDCXX} GREATER_EQUAL 17) AND (ENABLE_STDCXX_SYNC)) + set (ENABLE_STDCXX_SHARED_MUTEX 1) + endif() + + # Do not set variables to not duplicate flags + set (USE_CXX_STD_LIB ${STDCXX}) + set (USE_CXX_STD_APP ${STDCXX}) + 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() + + # DO NOT force C++ standard if it was required less than 11. + # That would enforce C++11 standard when C++17 required and this would end up bad + if (FORCE_CXX_STANDARD AND (${STDCXX} LESS 11 OR ${STDCXX} EQUAL 98)) + if (USE_CXX_STD_APP STREQUAL "") + set (USE_CXX_STD_APP 11) + message(STATUS "C++ STD: Forcing C++11 on applications") + endif() + + if (USE_CXX_STD_LIB STREQUAL "" AND ENABLE_STDCXX_SYNC) + set (USE_CXX_STD_LIB 11) + message(STATUS "C++ STD: Forcing C++11 on library, as C++11 sync requested") + endif() + endif() +else() + # NOTE: Setting empty means that you should use the value of + # CMAKE_CXX_STANDARD_DEFAULT, and not even the value of CMAKE_CXX_STANDARD + set (USE_CXX_STD_LIB "") + set (USE_CXX_STD_APP "") endif() -# NOTE: Use ATOMIC_USE_SRT_SYNC_MUTEX and will override the auto-detection of the -# Atomic implementation 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) +if (ENABLE_STDCXX_SYNC) + add_definitions(-DSRT_ENABLE_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 OR ${STDCXX} EQUAL 98) + 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() + if (MINGW) + # FIXME: with MINGW, it fails to build with C++11 + # https://github.com/Haivision/srt/issues/177 + message(WARNING "MinGW has problems with proper C++11 headers for . Set ENABLE_STDCXX_SYNC to OFF if compiling fails.") + endif() 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) +# Ultimately, if nothing above has set this value, set this to +# the default value in CMake. +if (USE_CXX_STD_LIB STREQUAL "") + set (USE_CXX_STD_LIB ${CMAKE_CXX_STANDARD_DEFAULT}) + set (STDCXX ${CMAKE_CXX_STANDARD_DEFAULT}) + set (STDPFX "c++") +endif() +if (USE_CXX_STD_APP STREQUAL "") + if (${CMAKE_CXX_STANDARD_DEFAULT} EQUAL 98) + set (USE_CXX_STD_APP 11) + else() + set (USE_CXX_STD_APP ${CMAKE_CXX_STANDARD_DEFAULT}) + endif() +endif() + +if ("${USE_CXX_STD_LIB}" EQUAL 98) + + string(APPEND FEATURE_REPORT "LOGGER_SYNC=srtsync ") + + # Use SRT sync definitions for logging - for everything, regardless which standard apps use. + # We use options that can be only handled by gcc and clang, + # but on Windows we don't support non-C++11 compiling anymore. + add_definitions(-DHVU_EXT_INCLUDE_SYNC="../srtcore/srt_sync_cxx98.h") +else() + string(APPEND FEATURE_REPORT "LOGGER_SYNC=c++std ") +endif() + +# add extra warning flags for gccish compilers +if ((HAVE_COMPILER_GNU_COMPAT) AND (NOT DEFINED SRT_GCC_WARN)) + 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() + + if (DEFINED STDCXX) + if (${STDCXX} EQUAL 98) + set (SRT_GCC_WARN "${SRT_GCC_WARN} -Wno-deprecated") + endif() + endif() +else() + # cpp debugging on Windows :D + #set (SRT_GCC_WARN "/showIncludes") +endif() +message(STATUS "WARNING OPTIONS: ${SRT_GCC_WARN}") + +# -------------------------------------------- +# Post-option variable synchronization +# and option-to-preprocessor-macro transition +# (Optional features) +# -------------------------------------------- + +# Global independent settings +add_definitions( + -D_GNU_SOURCE + -DSRT_VERSION="${SRT_VERSION}" +) + +if (USE_BUSY_WAITING) + string(APPEND FEATURE_REPORT "BUSY_WAITING=ON ") + list(APPEND SRT_EXTRA_CFLAGS "-DSRT_BUSY_WAITING=1") +else() + string(APPEND FEATURE_REPORT "BUSY_WAITING=*OFF ") +endif() + +if (ENABLE_THREAD_DEBUG) + list(APPEND SRT_EXTRA_CFLAGS "-DSRT_ENABLE_THREAD_DEBUG") + string(APPEND FEATURE_REPORT "THREAD_DEBUG ") +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() +list(APPEND SRT_EXTRA_CFLAGS "-DSRT_LOG_SLOWDOWN_FREQ_MS=${SRT_LOG_SLOWDOWN_FREQ_MS}") + +if (USE_GNUSTL) + pkg_check_modules (GNUSTL REQUIRED gnustl) + link_directories(${GNUSTL_LIBRARY_DIRS}) + include_directories(${GNUSTL_INCLUDE_DIRS}) + set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) +endif() + +if (ENABLE_MAXREXMITBW) + list(APPEND SRT_EXTRA_CFLAGS "-DSRT_ENABLE_MAXREXMITBW=1") + string(APPEND FEATURE_REPORT "MAXREXMITBW=ON ") +else() + string(APPEND FEATURE_REPORT "MAXREXMITBW=OFF ") +endif() + +# NOTE USAGE OF ENABLE_HEAVY_LOGGING: +# +# - If defined as 1, add heavy logging to every configuration +# - If defined as 0, do not add heavy logging to any configuration +# - If not defined, add heavy logging only for debug configuration + +if (ENABLE_LOGGING) + # Add -DHVU_ENABLE_LOGGING=1 in any configuration + # Add -DSRT_ENABLE_HAICRYPT_LOGGING=1 if ENABLE_HAICRYPT_LOGGING + # Add -DHVU_ENABLE_HEAVY_LOGGING=1 if required + + set (LOGGING_ENABLE_DEFS "-DHVU_ENABLE_LOGGING=1") + if (ENABLE_HEAVY_LOGGING) + list(APPEND LOGGING_ENABLE_DEFS "-DHVU_ENABLE_HEAVY_LOGGING=1") + string(APPEND FEATURE_REPORT "LOGGING=HEAVY/explicit ") + message(STATUS "HEAVY LOGGING FLAGS: ENABLED (explicit)") + elseif(DEFINED ENABLE_HEAVY_LOGGING AND ENABLE_HEAVY_LOGGING EQUAL 0) + # If explicitly required to be off, do not add even for debug + string(APPEND FEATURE_REPORT "LOGGING=NORMAL ") + message(STATUS "HEAVY LOGGING FLAGS: DISABLED (explicit: '${ENABLE_HEAVY_LOGGING}')") + else() + # Otherwise add this, but only when generation debug mode + string(APPEND FEATURE_REPORT "LOGGING=HEAVY-if-debug ") + list(APPEND LOGGING_ENABLE_DEFS "$<$,$>:HVU_ENABLE_HEAVY_LOGGING=1>") + endif() + + if (ENABLE_HAICRYPT_LOGGING STREQUAL 2) # Allow value 2 for INSECURE DEBUG logging + message(WARNING " *** ENABLED INSECURE HAICRYPT LOGGING - USE FOR TESTING ONLY!!! ***") + endif() + if (ENABLE_HAICRYPT_LOGGING) + list(APPEND LOGGING_ENABLE_DEFS "-DSRT_ENABLE_HAICRYPT_LOGGING=${ENABLE_HAICRYPT_LOGGING}") + endif() +else() + string(APPEND FEATURE_REPORT "LOGGING=DISABLED ") +endif() + +if (ENABLE_STDCXX_SHARED_MUTEX) + add_definitions(-DSRT_ENABLE_STDCXX_SHARED_MUTEX=1) + string(APPEND FEATURE_REPORT "SHARED_MUTEX=stdc++ ") +else() + string(APPEND FEATURE_REPORT "SHARED_MUTEX=srt ") +endif() + +if (ENABLE_GETNAMEINFO) + string(APPEND FEATURE_REPORT "GETNAMEINFO ") + list(APPEND SRT_EXTRA_CFLAGS "-DSRT_ENABLE_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") + string(APPEND FEATURE_REPORT "PKTINFO ") +endif() + +if (ENABLE_BONDING) + list(APPEND SRT_EXTRA_CFLAGS "-DSRT_ENABLE_BONDING=1") + string(APPEND FEATURE_REPORT "BONDING=*ON ") +else() + string(APPEND FEATURE_REPORT "BONDING=OFF ") +endif() + +if (ENABLE_THREAD_CHECK) + if (NOT DEFINED WITH_THREAD_CHECK_INCLUDEDIR) + message(FATAL_ERROR "If ENABLE_THREAD_CHECK, then WITH_THREAD_CHECK_INCLUDEDIR must be defined") + endif() + add_definitions( + -DSRT_ENABLE_THREADCHECK=1 + -DFUGU_PLATFORM=1 + -I${WITH_THREAD_CHECK_INCLUDEDIR} + ) + string(APPEND FEATURE_REPORT "THREADCHECK ") +endif() + +if (ENABLE_CLANG_TSA) + list(APPEND SRT_EXTRA_CFLAGS "-Wthread-safety -Wthread-safety-beta") + add_definitions(-DSRT_ENABLE_CLANG_TSA=1) + string(APPEND FEATURE_REPORT "CLANG_TSA ") +endif() -# 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 (ENABLE_PROFILE) + if (HAVE_COMPILER_GNU_COMPAT) + # They are actually cflags, not definitions, but CMake is stupid enough. + add_definitions(-g -pg) + link_libraries(-g -pg) + string(APPEND FEATURE_REPORT "GNU_PROFILER ") + else() + message(FATAL_ERROR "Profiling option is not supported on this platform") + endif() +endif() -if (NOT ENABLE_LOGGING) - set (ENABLE_HEAVY_LOGGING OFF) - message(STATUS "LOGGING: DISABLED") -else() - if (ENABLE_HEAVY_LOGGING) - message(STATUS "LOGGING: HEAVY") +if (ENABLE_CODE_COVERAGE) + if (HAVE_COMPILER_GNU_COMPAT) + add_definitions(-g -O0 --coverage) + link_libraries(--coverage) + string(APPEND FEATURE_REPORT "GNU_COVERAGE ") else() - message(STATUS "LOGGING: ENABLED") + message(FATAL_ERROR "ENABLE_CODE_COVERAGE: option is not supported on this platform") endif() endif() -if (USE_BUSY_WAITING) - message(STATUS "USE_BUSY_WAITING: ON") - list(APPEND SRT_EXTRA_CFLAGS "-DUSE_BUSY_WAITING=1") +if (ENABLE_CLOEXEC) + add_definitions(-DSRT_ENABLE_CLOEXEC=1) + string(APPEND FEATURE_REPORT "CLOEXEC=*ON ") else() - message(STATUS "USE_BUSY_WAITING: OFF (default)") + add_definitions(-DSRT_ENABLE_CLOEXEC=0) + string(APPEND FEATURE_REPORT "CLOEXEC=OFF ") 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. +if (LINUX) +# This is an option supported only on Linux + add_definitions(-DSRT_ENABLE_BINDTODEVICE) + string(APPEND FEATURE_REPORT "BINDTODEVICE=ON ") +endif() + +if (USE_STATIC_LIBSTDCXX) + if (HAVE_COMPILER_GNU_COMPAT) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++") else() - set(SRT_LOG_SLOWDOWN_FREQ_MS ${SRT_LOG_SLOWDOWN_FREQ_MS_DEFAULT}) + message(FATAL_ERROR "On non-GNU-compat compiler it's not known how to use static C++ standard library.") endif() endif() -if ( CYGWIN AND NOT CYGWIN_USE_POSIX ) - set(WIN32 1) - set(CMAKE_LEGACY_CYGWIN_WIN32 1) - add_definitions(-DWIN32=1 -DCYGWIN=1) - message(STATUS "HAVE CYGWIN. Setting backward compat CMAKE_LEGACY_CYGWIN_WIN32 and -DWIN32") -endif() -if (NOT USE_ENCLIB) - if (USE_GNUTLS) - message("NOTE: USE_GNUTLS is deprecated. Use -DUSE_ENCLIB=gnutls instead.") - set (USE_ENCLIB gnutls) - else() - set (USE_ENCLIB openssl-evp) - endif() +# On Linux pthreads have to be linked even when using C++11 threads +if (ENABLE_STDCXX_SYNC AND NOT LINUX) + message(STATUS "Threads: C++11") +elseif (PTHREAD_LIBRARY AND PTHREAD_INCLUDE_DIR) + message(STATUS "Threads: POSIX; LIB=${PTHREAD_LIBRARY} INC=${PTHREAD_INCLUDE_DIR}") +elseif (MICROSOFT) + # Formal, should never happen + message(FATAL_ERROR "Using POSIX thread API with Windows is no longer supported. Use C++11 threads.") +else () + find_package(Threads REQUIRED) + set(PTHREAD_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) + message(STATUS "Threads: cmake-default: ${PTHREAD_LIBRARY}") endif() -set(USE_ENCLIB "${USE_ENCLIB}" CACHE STRING "The crypto library that SRT uses") -set_property(CACHE USE_ENCLIB PROPERTY STRINGS "openssl" "openssl-evp" "gnutls" "mbedtls" "botan") +# Check the feature of PTHREAD providing pthread_atfork function. +# Regardless of the current thread support library, this is a function +# called as a callback at fork() call. This is required so that SRT +# library can be used with applications that do fork(). -# Make sure DLLs and executables go to the same path regardless of subdirectory -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +if (NOT MICROSOFT AND NOT MINGW) + # Do not enforce pthread library in case of MICROSOFT + # because it doesn't need fork() support (even if someone + # might want to try to compile for Windows with pthreads). -if (NOT DEFINED WITH_COMPILER_TYPE) + set(CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY};${CMAKE_REQUIRED_LIBRARIES}") + unset(CMAKE_REQUIRED_QUIET) - # 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) + check_symbol_exists(pthread_atfork "pthread.h" HAVE_PTHREAD_ATFORK) + if ("${HAVE_PTHREAD_ATFORK}" STREQUAL "1") + add_definitions(-DHAVE_PTHREAD_ATFORK=1) + string(APPEND FEATURE_REPORT "ATFORK=ON ") + endif () endif() -if (NOT USING_DEFAULT_COMPILER_PREFIX OR DEFINED WITH_COMPILER_PREFIX) - message(STATUS "Handling compiler with PREFIX=${WITH_COMPILER_PREFIX} TYPE=${WITH_COMPILER_TYPE}") +if (NOT ENABLE_STDCXX_SYNC) + if (ENABLE_MONOTONIC_CLOCK) + # Require monotonic clock, unless it's explicitly set OFF + list(PREPEND CMAKE_REQUIRED_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}") - parse_compiler_type(${WITH_COMPILER_TYPE} COMPILER_TYPE COMPILER_SUFFIX) + # Note: pthread_condattr_setclock doesn't exist on some systems, at least + # old versions of Android (__ANDROID_API__ < 21) are known to not provide it + check_symbol_exists(pthread_condattr_setclock "pthread.h" HAVE_PTHREAD_CONDATTR_SETCLOCK) + test_requires_clock_gettime(HAVE_MONOTONIC_CLOCK_GETTIME MONOTONIC_CLOCK_LINKLIB) + if (NOT HAVE_MONOTONIC_CLOCK_GETTIME OR NOT HAVE_PTHREAD_CONDATTR_SETCLOCK) + message(FATAL_ERROR "Your platform does not support CLOCK_MONOTONIC. Build with -DENABLE_MONOTONIC_CLOCK=OFF.") + endif() + if (NOT MONOTONIC_CLOCK_LINKLIB STREQUAL "") + set (WITH_EXTRALIBS "${WITH_EXTRALIBS} ${MONOTONIC_CLOCK_LINKLIB}") + endif() + add_definitions(-DSRT_ENABLE_MONOTONIC_CLOCK=1) + string(APPEND FEATURE_REPORT "SYNC=POSIX CLOCK=monotonic ") + message(STATUS "SYNC LIBRARY: POSIX, MONOTONIC CLOCK: ON") + + # Add-unique not supported in cmake, use unload version + list(FIND CMAKE_REQUIRED_QUIET "${PTHREAD_LIBRARY}" HAVE_ALREADY) + if (NOT HAVE_ALREADY) + list(PREPEND CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY}") + endif() + unset(CMAKE_REQUIRED_QUIET) + check_symbol_exists(pthread_condattr_setclock "pthread.h" HAVE_PTHREAD_CONDATTR_SETCLOCK) + message(STATUS "Checking pthread_condattr_setclock: '${HAVE_PTHREAD_CONDATTR_SETCLOCK}'") + if ("${HAVE_PTHREAD_CONDATTR_SETCLOCK}" STREQUAL "1") + string(APPEND FEATURE_REPORT "PTHREAD_COND_CLOCK=monotonic ") + add_definitions(-DHAVE_PTHREAD_CONDATTR_SETCLOCK=1) + else () + string(APPEND FEATURE_REPORT "PTHREAD_COND_CLOCK=system ") + message(WARNING "NOT FOUND pthread_cond_timedwait. pthread_cond_timedwait will use system clock. Recommended -DENABLE_STDCXX_SYNC=ON") + endif () - 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}${COMPILER_TYPE}++${COMPILER_SUFFIX}) - if (${COMPILER_TYPE} STREQUAL clang) - set (HAVE_COMPILER_GNU_COMPAT 1) - endif() + string(APPEND FEATURE_REPORT "SYNC=POSIX CLOCK=SYSTEM PTHREAD_COND_CLOCK=system ") + message(WARNING +"ENABLE_MONOTONIC_CLOCK is OFF. This is dangerous. +SRT may malfunction, if the system time is changed during transmission.") 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}") + string(APPEND FEATURE_REPORT "SYNC=C++std ") endif() -if (DEFINED WITH_SRT_TARGET) - set (TARGET_haisrt ${WITH_SRT_TARGET}) -endif() - # When you use crosscompiling, you have to take care that PKG_CONFIG_PATH # and CMAKE_PREFIX_PATH are set properly. -# symbol exists in win32, but function does not. +# ---------------------------------------- +# Code variants and explicit configuration +# ---------------------------------------- + +# symbol for inet_pton() exists in win32, but function does not. if(WIN32) - if(ENABLE_INET_PTON) - set(CMAKE_REQUIRED_LIBRARIES ws2_32) - check_function_exists(inet_pton HAVE_INET_PTON) - 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) + set(CMAKE_REQUIRED_LIBRARIES ws2_32) + 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() - check_function_exists(inet_pton HAVE_INET_PTON) endif() + +check_function_exists(inet_pton HAVE_INET_PTON) if (DEFINED HAVE_INET_PTON) add_definitions(-DHAVE_INET_PTON=1) endif() -# Defines HAVE_PTHREAD_GETNAME_* and HAVE_PTHREAD_SETNAME_* -include(FindPThreadGetSetName) +# --------------------------- +# Configuration autodetection +# --------------------------- + +# Find functions for getting/setting thread name +# This is done independently on which thread library is in use. This +# facility is not portabie, we are simply trying to use whatever is +# available on particular system. FindPThreadGetSetName() +# -> Defines HAVE_PTHREAD_GETNAME_* and HAVE_PTHREAD_SETNAME_* -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) +# 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 +CheckGCCAtomicIntrinsics() +# HAVE_CXX_ATOMIC +# HAVE_CXX_ATOMIC_STATIC +CheckCXXAtomic() + +if (USE_MUTEX_ATOMIC) + add_definitions(-DATOMIC_USE_SRT_SYNC_MUTEX=1) +endif() + +# Check for std::put_time(): +# Sets: +# HAVE_CXX_STD_PUT_TIME +CheckCXXStdPutTime() +if (HAVE_CXX_STD_PUT_TIME) + add_definitions(-DHAVE_CXX_STD_PUT_TIME=1) endif() + +# ----------------------------------------------------------- +# Configure encryption library dependency and haicrypt module +# ----------------------------------------------------------- + if (ENABLE_ENCRYPTION) + + # XXX Backward compat. Remove if no longer needed. + if (DEFINED OPENSSL_USE_STATIC_LIBS AND "${OPENSSL_USE_STATIC_LIBS}" STREQUAL "ON") + message(WARNING "Use of OPENSSL_USE_STATIC_LIBS as SRT build option here is deprecated. +Please use SRT_USE_OPENSSL_STATIC_LIBS instead.") + set(SRT_USE_OPENSSL_STATIC_LIBS ${OPENSSL_USE_STATIC_LIBS}) + endif() + if ("${USE_ENCLIB}" STREQUAL "gnutls") set (SSL_REQUIRED_MODULES "gnutls nettle") if (WIN32) @@ -377,6 +849,7 @@ if (ENABLE_ENCRYPTION) link_directories( ${SSL_LIBRARY_DIRS} ) + string(APPEND FEATURE_REPORT "ENC=gnutls ") elseif ("${USE_ENCLIB}" STREQUAL "mbedtls") add_definitions(-DUSE_MBEDTLS=1) if ("${SSL_LIBRARY_DIRS}" STREQUAL "") @@ -397,9 +870,10 @@ if (ENABLE_ENCRYPTION) 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}") + set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} "-l${LIB}") endif() endforeach() + string(APPEND FEATURE_REPORT "ENC=mbedtls ") elseif ("${USE_ENCLIB}" STREQUAL "openssl-evp") # Openssl-EVP requires CRYSPR2 add_definitions(-DUSE_OPENSSL_EVP=1 -DCRYSPR2) @@ -407,11 +881,14 @@ if (ENABLE_ENCRYPTION) # Try using pkg-config method first if enabled, # fall back to find_package method otherwise if (USE_OPENSSL_PC) + message(STATUS "openssl: checking through pkg-config") pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) if (SRT_USE_OPENSSL_STATIC_LIBS) # use `pkg-config --static xxx` found libs set(SSL_LIBRARIES ${SSL_STATIC_LIBRARIES}) endif() + else() + message(STATUS "openssl: NOT checking pkg-config, continue with cmake-way") endif() if (SSL_FOUND) # We have some cases when pkg-config is improperly configured @@ -437,11 +914,17 @@ if (ENABLE_ENCRYPTION) set(OPENSSL_USE_STATIC_LIBS True) set(OPENSSL_MSVC_STATIC_RT True) endif() + message(STATUS "Finding OpenSSL. Variables:") + mvar(OPENSSL_ROOT_DIR) + mvar(OPENSSL_CRYPTO_LIBRARY) + mvar(OPENSSL_LIBRARIES) + mvar(SRT_USE_OPENSSL_STATIC_LIBS) 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() + string(APPEND FEATURE_REPORT "ENC=OpenSSL-EVP ") elseif ("${USE_ENCLIB}" STREQUAL "botan") add_definitions(-DUSE_BOTAN=1 -DCRYSPR2) set (SSL_REQUIRED_MODULES "botan") @@ -482,6 +965,7 @@ if (ENABLE_ENCRYPTION) target_compile_features("botan" PRIVATE "cxx_std_20") set (SSL_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}) set (SSL_LIBRARIES "botan") + string(APPEND FEATURE_REPORT "ENC=botan ") else() # openssl # Openssl (Direct-AES API) can use CRYSPR2 add_definitions(-DUSE_OPENSSL=1 -DCRYSPR2) @@ -489,11 +973,14 @@ if (ENABLE_ENCRYPTION) # Try using pkg-config method first if enabled, # fall back to find_package method otherwise if (USE_OPENSSL_PC) + message(STATUS "openssl: checking through pkg-config") pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) if (SRT_USE_OPENSSL_STATIC_LIBS) # use `pkg-config --static xxx` found libs set(SSL_LIBRARIES ${SSL_STATIC_LIBRARIES}) endif() + else() + message(STATUS "openssl: NOT checking pkg-config, continue with cmake-way") endif() if (SSL_FOUND) # We have some cases when pkg-config is improperly configured @@ -524,239 +1011,36 @@ if (ENABLE_ENCRYPTION) set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) message(STATUS "SSL via find_package(OpenSSL): -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") endif() - + string(APPEND FEATURE_REPORT "ENC=OpenSSL ") endif() add_definitions(-DSRT_ENABLE_ENCRYPTION) - message(STATUS "ENCRYPTION: ENABLED, using: ${SSL_REQUIRED_MODULES}") - message (STATUS "SSL libraries: ${SSL_LIBRARIES}") + message (STATUS "SSL libraries: ${SSL_LIBRARIES} modules: ${SSL_REQUIRED_MODULES}") - if (ENABLE_AEAD_API_PREVIEW) + if (ENABLE_AEAD) if (("${USE_ENCLIB}" STREQUAL "openssl-evp") OR ("${USE_ENCLIB}" STREQUAL "botan")) - 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 | botan]!") - 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) - pkg_check_modules (GNUSTL REQUIRED gnustl) - link_directories(${GNUSTL_LIBRARY_DIRS}) - include_directories(${GNUSTL_INCLUDE_DIRS}) - set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) -endif() - -if (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 ) -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") + add_definitions(-DSRT_ENABLE_AEAD) + string(APPEND FEATURE_REPORT "AEAD=ON ") 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") -endif() - -if (USE_STATIC_LIBSTDCXX) - if (HAVE_COMPILER_GNU_COMPAT) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libstdc++") + message(WARNING "ENABLE_AEAD is only available with USE_ENCLIB=[openssl-evp | botan] (turned off)") + set (ENABLE_AEAD OFF) + string(APPEND FEATURE_REPORT "AEAD=OFF ") + endif() else() - message(FATAL_ERROR "On non-GNU-compat compiler it's not known how to use static C++ standard library.") + string(APPEND FEATURE_REPORT "AEAD=OFF ") endif() + + + set (HAICRYPT_FILELIST_MAF "filelist-${USE_ENCLIB}.maf") + + MafReadDir(haicrypt ${HAICRYPT_FILELIST_MAF} + SOURCES SOURCES_haicrypt + PUBLIC_HEADERS HEADERS_haicrypt + PROTECTED_HEADERS HEADERS_haicrypt + ) + +else() + string(APPEND FEATURE_REPORT "ENC=disabled AEAD=N/A ") endif() @@ -772,16 +1056,17 @@ endif() # CMake has only discovered in 3.3 version that some set-finder is # necessary. Using variables for shortcut to a clumsy check syntax. +set(TARGET_srt "srt" CACHE STRING "The name for the SRT library") + set (srt_libspec_shared ${ENABLE_SHARED}) set (srt_libspec_static ${ENABLE_STATIC}) -set (srtpack_libspec_common) +set (srt_library_targets) if (srt_libspec_shared) - list(APPEND srtpack_libspec_common ${TARGET_srt}_shared) - + list(APPEND srt_library_targets ${TARGET_srt}_shared) endif() if (srt_libspec_static) - list(APPEND srtpack_libspec_common ${TARGET_srt}_static) + list(APPEND srt_library_targets ${TARGET_srt}_static) endif() set (SRT_SRC_HAICRYPT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/haicrypt) @@ -789,237 +1074,15 @@ set (SRT_SRC_SRTCORE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/srtcore) set (SRT_SRC_COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/common) set (SRT_SRC_TOOLS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tools) set (SRT_SRC_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/test) - -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") -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(OHOS) - add_definitions(-DLINUX=1) - message(STATUS "DETECTED SYSTEM: OHOS; 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() - -add_definitions( - -D_GNU_SOURCE - -DHAI_PATCH=1 - -DHAI_ENABLE_SRT=1 - -DSRT_VERSION="${SRT_VERSION}" -) - -if (LINUX) -# This is an option supported only on Linux - add_definitions(-DSRT_ENABLE_BINDTODEVICE) -endif() +set (SRT_SRC_LOG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/logging) # 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}) - -if (ENABLE_LOGGING) - list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_LOGGING=1") - if (ENABLE_HEAVY_LOGGING) - list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_HEAVY_LOGGING=1") - endif() - if (ENABLE_HAICRYPT_LOGGING) - if (ENABLE_HAICRYPT_LOGGING STREQUAL 2) # Allow value 2 for INSECURE DEBUG logging - message(WARNING " *** ENABLED INSECURE HAICRYPT LOGGING - USE FOR TESTING ONLY!!! ***") - list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_HAICRYPT_LOGGING=2") - else() - list(APPEND SRT_EXTRA_CFLAGS "-DENABLE_HAICRYPT_LOGGING=1") - endif() - endif() -endif() - -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 - -DFUGU_PLATFORM=1 - -I${WITH_THREAD_CHECK_INCLUDEDIR} - ) -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. - add_definitions(-g -pg) - link_libraries(-g -pg) - else() - message(FATAL_ERROR "Profiling option is not supported on this platform") - endif() -endif() - -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 "ENABLE_CODE_COVERAGE: option is not supported on this platform") - endif() -endif() - -# 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) - - message(WARNING "DEPRECATION WARNING!\nUsing POSIX thread API with Windows is deprecated and will be removed") - - find_package(pthreads QUIET) - - 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) - if (PTHREAD_PACKAGE_INCLUDE_HINT) - message(STATUS "PTHREAD_PACKAGE_INCLUDE_HINT value: ${PTHREAD_PACKAGE_INCLUDE_HINT}") - endif() - - # find pthread header - find_path(PTHREAD_INCLUDE_DIR pthread.h HINTS C:/pthread-win32/include ${PTHREAD_PACKAGE_INCLUDE_HINT}) - - if (PTHREAD_INCLUDE_DIR) - message(STATUS "Pthread include dir: ${PTHREAD_INCLUDE_DIR}") - else() - message(FATAL_ERROR "Failed to find pthread.h. Specify PTHREAD_INCLUDE_DIR.") - 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) - if (PTHREAD_PACKAGE_LIB_HINT) - message(STATUS "PTHREAD_PACKAGE_LIB_HINT value: ${PTHREAD_PACKAGE_LIB_HINT}") - endif() - - #find pthread library - set(PTHREAD_LIB_SUFFIX "") - if (ENABLE_DEBUG) - set(PTHREAD_LIB_SUFFIX "d") - endif () - - set(PTHREAD_COMPILER_FLAG "") - if (MICROSOFT) - set(PTHREAD_COMPILER_FLAG "V") - elseif (MINGW) - set(PTHREAD_COMPILER_FLAG "G") - endif () +include_directories(${SRT_SRC_COMMON_DIR} ${SRT_SRC_SRTCORE_DIR} ${SRT_SRC_HAICRYPT_DIR} ${SRT_SRC_LOG_DIR}) - foreach(EXHAND C CE SE) - foreach(COMPAT 1 2) - list(APPEND PTHREAD_W32_LIBRARY "pthread${PTHREAD_COMPILER_FLAG}${EXHAND}${PTHREAD_LIB_SUFFIX}${COMPAT}") - endforeach() - endforeach() - - find_library(PTHREAD_LIBRARY NAMES ${PTHREAD_W32_LIBRARY} pthread pthread_dll pthread_lib HINTS C:/pthread-win32/lib C:/pthread-win64/lib ${PTHREAD_PACKAGE_LIB_HINT}) - if (PTHREAD_LIBRARY) - message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") - else() - message(FATAL_ERROR "Failed to find pthread library. Specify PTHREAD_LIBRARY.") - endif() - endif() -else () - find_package(Threads REQUIRED) - set(PTHREAD_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) - message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") -endif() - -set(CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY};${CMAKE_REQUIRED_LIBRARIES}") -unset(CMAKE_REQUIRED_QUIET) - -check_symbol_exists(pthread_atfork "pthread.h" HAVE_PTHREAD_ATFORK) -if ("${HAVE_PTHREAD_ATFORK}" STREQUAL "1") - add_definitions(-DHAVE_PTHREAD_ATFORK=1) -endif () - -# To avoid the need for other judgments when ENABLE_STDCXX_SYNC is OFF in the future, this is a separate conditional statement. -if (NOT ENABLE_STDCXX_SYNC AND ENABLE_MONOTONIC_CLOCK) - check_symbol_exists(pthread_condattr_setclock "pthread.h" HAVE_PTHREAD_CONDATTR_SETCLOCK) - message(STATUS "Checking pthread_condattr_setclock: '${HAVE_PTHREAD_CONDATTR_SETCLOCK}'") - if ("${HAVE_PTHREAD_CONDATTR_SETCLOCK}" STREQUAL "1") - message(STATUS "FOUND pthread_condattr_setclock - pthread_cond_timedwait will use monotonic clock") - add_definitions(-DHAVE_PTHREAD_CONDATTR_SETCLOCK=1) - else () - message(WARNING "NOT FOUND pthread_cond_timedwait. pthread_cond_timedwait will use system clock. Recommended -DENABLE_STDCXX_SYNC=ON") - endif () -endif () -# This is required in some projects that add some other sources -# to the SRT library to be compiled together (aka "virtual library"). -if (DEFINED SRT_EXTRA_LIB_INC) - include(${SRT_EXTRA_LIB_INC}.cmake) - # Expected to provide variables: - # - SOURCES_srt_extra - # - EXTRA_stransmit -endif() # --------------------------------------------------------------------------- -# --- -# Target: haicrypt. -# Completing sources and installable headers. Flag settings will follow. -# --- -if (ENABLE_ENCRYPTION) - set (HAICRYPT_FILELIST_MAF "filelist-${USE_ENCLIB}.maf") - - MafReadDir(haicrypt ${HAICRYPT_FILELIST_MAF} - SOURCES SOURCES_haicrypt - PUBLIC_HEADERS HEADERS_haicrypt - PROTECTED_HEADERS HEADERS_haicrypt - ) -endif() - if (WIN32) MafReadDir(common filelist_win32.maf SOURCES SOURCES_common @@ -1038,7 +1101,7 @@ endif() # # ... Some native build systems (such as Xcode) may not like targets that have only object files, # 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") +set(OBJECT_LIB_SUPPORT "${PROJECT_SOURCE_DIR}/scripts/cmake_object_lib_support.c") # NOTE: The "virtual library" is a library specification that cmake # doesn't support (the library of OBJECT type is something in kind of that, @@ -1055,10 +1118,18 @@ set(OBJECT_LIB_SUPPORT "${PROJECT_SOURCE_DIR}/cmake_object_lib_support.c") if (ENABLE_SHARED AND MICROSOFT) #add resource files to shared library, to set DLL metadata on Windows DLLs + # (reference is in srtcore/filelist.maf) set (EXTRA_WIN32_SHARED 1) message(STATUS "WIN32: extra resource file will be added") endif() +# Finally report all features: +message(STATUS ${FEATURE_REPORT}) + +# Variable used in this file: +# - ENABLE_BONDING +# - ENABLE_STDCXX_SYNC +# - EXTRA_WIN32_SHARED MafReadDir(srtcore filelist.maf SOURCES SOURCES_srt PUBLIC_HEADERS HEADERS_srt @@ -1078,65 +1149,96 @@ if(DEFINED ENV{TEAMCITY_VERSION}) message(STATUS "TeamCity build environment detected: Adding build counter to version header") endif() +# Variables used: +# - SRT_VERSION +# - SRT_VERSION_* (MAJOR, MINOR, PATCH) +# - CI_BUILD_NUMBER_STRING 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} ${SOURCES_haicrypt} ${SOURCES_common}) +# ------------------------ +# SRT Library definition +# ------------------------ -if (ENABLE_SHARED) - # Set this to sources as well, as it won't be automatically handled - set_target_properties(srt_virtual PROPERTIES POSITION_INDEPENDENT_CODE 1) -endif() +# Target: srt_virtual +# +# This is an object library used to create both shared and static libraries. + +add_library(srt_virtual OBJECT + ${SOURCES_srt} + ${SOURCES_srt_extra} + ${HEADERS_srt} + ${SOURCES_haicrypt} + ${SOURCES_common} +) 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() + set_target_properties(${targetname} PROPERTIES CXX_STANDARD ${stdcxxspec}) + #message(STATUS "C++ STD: ${targetname}: forced C++${stdcxxspec} standard - portable way") else() - message(STATUS "APP: ${targetname}: using default C++ standard") + message(STATUS "TARGET: ${targetname}: using default C++ standard") endif() endmacro() macro(srt_set_stdc targetname spec) set (stdcspec ${spec}) if (NOT "${stdcspec}" STREQUAL "") - if (CMAKE_VERSION VERSION_LESS "3.1") - target_compile_options(${targetname} PRIVATE -std=c${stdcspec}) - message(STATUS "C STD: ${targetname}: forced C${stdcspec} standard - GNU option: -std=c${stdcspec}") - else() - set_target_properties(${targetname} PROPERTIES C_STANDARD ${stdcspec}) - message(STATUS "C STD: ${targetname}: forced C${stdcspec} standard - portable way") - endif() + set_target_properties(${targetname} PROPERTIES C_STANDARD ${stdcspec}) + #message(STATUS "C STD: ${targetname}: forced C${stdcspec} standard - portable way") else() - message(STATUS "APP: ${targetname}: using default C standard") + #message(STATUS "APP: ${targetname}: using default C standard") endif() endmacro() srt_set_stdcxx(srt_virtual "${USE_CXX_STD_LIB}") srt_set_stdc(srt_virtual "99") +if (ENABLE_ENCRYPTION) + target_include_directories(srt_virtual PRIVATE ${SSL_INCLUDE_DIRS}) + + if ("${USE_ENCLIB}" STREQUAL "botan") + add_dependencies(srt_virtual botan) + endif() +endif() + +if (DEFINED LOGGING_ENABLE_DEFS) + #message(STATUS "LOGGING FLAGS: ${LOGGING_ENABLE_DEFS}") + target_compile_definitions(srt_virtual PUBLIC ${LOGGING_ENABLE_DEFS}) +endif() + + +# All *.o files created for srt_virtual target +# will be collected and used to create shared and static libraries set (VIRTUAL_srt $) +# ------------------------------- +# Shared/static library definition +# (based on srt_virtual target) +# ------------------------------- + if (srt_libspec_shared) - add_library(${TARGET_srt}_shared SHARED ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) - # shared libraries need PIC + # Enable PIC on the virtual library if shared library is enabled at all + # (yes, it means it will be enabled on the static library, too). + # Otherwise it may not be accepted in the dependencies. + set_target_properties(srt_virtual PROPERTIES POSITION_INDEPENDENT_CODE 1) set (CMAKE_POSITION_INDEPENDENT_CODE ON) + + add_library(${TARGET_srt}_shared SHARED ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) 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}.${SRT_VERSION_MINOR}) list (APPEND INSTALL_TARGETS ${TARGET_srt}_shared) if (ENABLE_ENCRYPTION) target_link_libraries(${TARGET_srt}_shared PRIVATE ${SSL_LIBRARIES}) endif() + if (WIN32 AND ENABLE_LOCALIF_WIN32) + target_link_libraries(${TARGET_srt}_shared PRIVATE Iphlpapi) + add_definitions(-DSRT_ENABLE_LOCALIF_WIN32) + endif() if (MICROSOFT) - target_link_libraries(${TARGET_srt}_shared PRIVATE ws2_32.lib) + target_link_libraries(${TARGET_srt}_shared PUBLIC ws2_32.lib) if (NOT (ENABLE_ENCRYPTION AND "${USE_ENCLIB}" STREQUAL "botan")) if (SRT_USE_OPENSSL_STATIC_LIBS) target_link_libraries(${TARGET_srt}_shared PRIVATE crypt32.lib) @@ -1156,6 +1258,10 @@ endif() if (srt_libspec_static) add_library(${TARGET_srt}_static STATIC ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) + if (WIN32 AND ENABLE_LOCALIF_WIN32) + target_link_libraries(${TARGET_srt}_static PRIVATE Iphlpapi) + add_definitions(-DSRT_ENABLE_LOCALIF_WIN32) + endif() # For Windows, leave the name to be "srt_static.lib". # Windows generates two different library files: @@ -1188,8 +1294,8 @@ if (srt_libspec_static) endif() endif() -target_include_directories(srt_virtual PRIVATE ${SSL_INCLUDE_DIRS}) - +# This add things to the declarations of the dependent libraries when +# generating pkg-config. if (MICROSOFT) if (SRT_USE_OPENSSL_STATIC_LIBS) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib crypt32.lib) @@ -1211,12 +1317,11 @@ endif() # as virtual libraries (OBJECT-type) cannot have linkage declarations # transitive or not. -foreach(tar ${srtpack_libspec_common}) +foreach(tar ${srt_library_targets}) message(STATUS "ADDING TRANSITIVE LINK DEP to:${tar} : ${PTHREAD_LIBRARY} ${dep}") target_link_libraries (${tar} PUBLIC ${PTHREAD_LIBRARY} ${dep}) endforeach() - set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${PTHREAD_LIBRARY}) target_compile_definitions(srt_virtual PRIVATE -DSRT_EXPORTS ) @@ -1224,42 +1329,32 @@ if (ENABLE_SHARED) 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 (ENABLE_ENCRYPTION AND "${USE_ENCLIB}" STREQUAL "botan") - add_dependencies(srt_virtual botan) -endif() - -if (srt_libspec_shared) - if (MICROSOFT) - target_link_libraries(${TARGET_srt}_shared PUBLIC Ws2_32.lib) - if (SRT_USE_OPENSSL_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() + +# This can be done simpler - but requires testing with all other toolchains. +if (HAVE_LIBATOMIC) + foreach(tar ${srt_library_targets}) + target_link_libraries(${tar} PUBLIC atomic) + endforeach() +endif() + +# if (HAVE_GCCATOMIC_INTRINSICS_REQUIRES_LIBATOMIC AND HAVE_LIBATOMIC) +# foreach(tar ${srt_library_targets}) +# target_link_libraries(${tar} PUBLIC atomic) +# endforeach() +# 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. @@ -1300,8 +1395,11 @@ endif() join_arguments(SRT_EXTRA_CFLAGS ${SRT_EXTRA_CFLAGS}) -#message(STATUS "Target srt: LIBSPEC: ${srtpack_libspec_common} SOURCES: {${SOURCES_srt}} HEADERS: {${HEADERS_srt}}") +#message(STATUS "Target srt: LIBSPEC: ${srt_library_targets} SOURCES: {${SOURCES_srt}} HEADERS: {${HEADERS_srt}}") +# XXX Decide something about SRT_DEBUG_OPT. This was likely intended +# to be controlled by CMAKE_BUILD_TYPE and ENABLE_DEBUG, but the procedure +# in the beginning that does it adds these flags simply by add_definitions(). set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SRT_DEBUG_OPT} ${SRT_EXTRA_CFLAGS} ${SRT_GCC_WARN}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SRT_DEBUG_OPT} ${SRT_EXTRA_CFLAGS} ${SRT_GCC_WARN}") @@ -1327,104 +1425,119 @@ endif() join_arguments(SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE}) +# Variables used: +# - CMAKE_INSTALL_LIBDIR +# - CMAKE_INSTALL_INCLUDEDIR +# - SRT_VERSION +# - TARGET_srt +# - IFNEEDED_LINK_HAICRYPT +# - IFNEEDED_SRTBASE +# - IFNEEDED_SRT_LDFLAGS +# - SRT_LIBS_PRIVATE +# - SSL_REQUIRED_MODULES 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 +# ------------------------------------- +# Applications, devel, examples, tests. +# ------------------------------------- -# If static is available, link apps against static one. -# Otherwise link against shared one. +## FIXME: transmitmedia.cpp does not build on OpenBSD +## Issue: https://github.com/Haivision/srt/issues/590 +if (${SYSNAME_LC} MATCHES "^openbsd$") + set(ENABLE_APPS OFF) +endif() +# Define `srt_libtarget_for_apps` regardless if ENABLE_APPS is on +# because this variable will be also used for other app types. if (srt_libspec_static) - set (srt_link_library ${TARGET_srt}_static) + set (srt_libtarget_for_apps ${TARGET_srt}_static) if (ENABLE_RELATIVE_LIBPATH) message(STATUS "ENABLE_RELATIVE_LIBPATH=ON will be ignored due to static linking.") endif() elseif(srt_libspec_shared) - set (srt_link_library ${TARGET_srt}_shared) + set (srt_libtarget_for_apps ${TARGET_srt}_shared) else() message(FATAL_ERROR "Either ENABLE_STATIC or ENABLE_SHARED has to be ON!") endif() -macro(srt_add_program_dont_install name) - add_executable(${name} ${ARGN}) - target_include_directories(${name} PRIVATE apps) - target_include_directories(${name} PRIVATE common) -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() +if (ENABLE_APPS) + # If static is available, link apps against static one. + # Otherwise link against shared one. + + macro(srt_add_program_dont_install name) + add_executable(${name} ${ARGN}) + target_include_directories(${name} PRIVATE apps) + target_include_directories(${name} PRIVATE common) + if (WIN32 AND ENABLE_LOCALIF_WIN32) + target_link_libraries(${name} Iphlpapi) + add_definitions(-DSRT_ENABLE_LOCALIF_WIN32) + endif() + endmacro() -macro(srt_make_application name) + 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() - 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. - # set (CMAKE_SKIP_RPATH FALSE) - # set (CMAKE_SKIP_BUILD_RPATH FALSE) - # set (CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) - # set (CMAKE_INSTALL_RPATH "../${CMAKE_INSTALL_LIBDIR}") - # set (CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - # set (FORCE_RPATH BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE) - - if (LINUX AND ENABLE_RELATIVE_LIBPATH AND NOT srt_libspec_static) - # This is only needed on Linux, on Windows (including Cygwin) the library file will - # be placed into the binary directory anyway. - # 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) - - set_target_properties(${name} PROPERTIES ${FORCE_RPATH}) - endif() + macro(srt_make_application name) - target_link_libraries(${name} ${srt_link_library}) - if (USE_GNUSTL) - target_link_libraries(${name} PRIVATE ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) - endif() - if (srt_libspec_static AND CMAKE_DL_LIBS) - target_link_libraries(${name} ${CMAKE_DL_LIBS}) - endif() -endmacro() + 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. + # set (CMAKE_SKIP_RPATH FALSE) + # set (CMAKE_SKIP_BUILD_RPATH FALSE) + # set (CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) + # set (CMAKE_INSTALL_RPATH "../${CMAKE_INSTALL_LIBDIR}") + # set (CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + # set (FORCE_RPATH BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE) + + if (LINUX AND ENABLE_RELATIVE_LIBPATH AND NOT srt_libspec_static) + # This is only needed on Linux, on Windows (including Cygwin) the library file will + # be placed into the binary directory anyway. + # 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) + + set_target_properties(${name} PROPERTIES ${FORCE_RPATH}) + endif() -macro(srt_add_application name) # ARGN=sources... - srt_add_program(${name} apps/${name}.cpp ${ARGN}) - srt_make_application(${name}) - 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() + # Development applications are also using logging; apply flags accordingly + if (DEFINED LOGGING_ENABLE_DEFS) + #message(STATUS "LOGGING FLAGS: ${LOGGING_ENABLE_DEFS}") + target_compile_definitions(${name} PUBLIC ${LOGGING_ENABLE_DEFS}) + endif() -## FIXME: transmitmedia.cpp does not build on OpenBSD -## Issue: https://github.com/Haivision/srt/issues/590 -if (BSD - AND ${SYSNAME_LC} MATCHES "^openbsd$") - set(ENABLE_APPS OFF) -endif() -## The applications currently require c++11. -if (NOT ENABLE_CXX11) - set(ENABLE_APPS OFF) -endif() + target_link_libraries(${name} ${srt_libtarget_for_apps}) + if (USE_GNUSTL) + target_link_libraries(${name} PRIVATE ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) + endif() + if (srt_libspec_static AND CMAKE_DL_LIBS) + target_link_libraries(${name} ${CMAKE_DL_LIBS}) + endif() + endmacro() -if (ENABLE_APPS) + macro(srt_add_application name) # ARGN=sources... + srt_add_program(${name} apps/${name}.cpp ${ARGN}) + srt_make_application(${name}) + 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() message(STATUS "APPS: ENABLED, std=${USE_CXX_STD_APP}") @@ -1474,6 +1587,12 @@ if (ENABLE_APPS) # list of source files in its own Manifest file. MafReadDir(testing ${name}.maf SOURCES SOURCES_app) srt_add_program_dont_install(${name} ${SOURCES_app}) + + # srt_make_application is not called for this, so add accordingly + if (DEFINED LOGGING_ENABLE_DEFS) + #message(STATUS "LOGGING FLAGS: ${LOGGING_ENABLE_DEFS}") + target_compile_definitions(${name} PUBLIC ${LOGGING_ENABLE_DEFS}) + endif() endmacro() srt_add_testprogram(utility-test) @@ -1518,7 +1637,7 @@ if (ENABLE_EXAMPLES) macro(srt_add_example mainsrc) get_filename_component(name ${mainsrc} NAME_WE) srt_add_program_dont_install(${name} examples/${mainsrc} ${ARGN}) - target_link_libraries(${name} ${srt_link_library} ${DEPENDS_srt}) + target_link_libraries(${name} ${srt_libtarget_for_apps} ${DEPENDS_srt}) endmacro() srt_add_example(recvlive.cpp) @@ -1537,26 +1656,18 @@ if (ENABLE_EXAMPLES) srt_add_example(test-c-server.c) -if (ENABLE_BONDING) - srt_add_example(test-c-client-bonding.c) + if (ENABLE_BONDING) + srt_add_example(test-c-client-bonding.c) - srt_add_example(test-c-server-bonding.c) -endif() + srt_add_example(test-c-server-bonding.c) + endif() srt_add_example(testcapi-connect.c) endif() +if (ENABLE_UNITTESTS) -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() - + cmake_policy(SET CMP0057 NEW) # Support the new IN_LIST operator. set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) @@ -1565,6 +1676,10 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) if (${CMAKE_VERSION} VERSION_LESS "3.19.0") find_package(GTest 1.10) else() + # NOTE: CMake will issue a warning here, but there's nothing + # you can do. In older versions, which do satisfy this condition, + # but do not support this feature, it will be a warning, but + # the version is still satisfied as required, so just ignore it. find_package(GTest 1.10...1.12) endif() if (NOT GTEST_FOUND) @@ -1587,22 +1702,21 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) target_compile_definitions(test-srt PRIVATE "-DSRT_TEST_SYSTEM_NAME=\"${CMAKE_SYSTEM_NAME}\"") + # Exceptionally set C++17 standard for tests + srt_set_stdcxx(test-srt 17) + target_link_libraries( test-srt ${GTEST_BOTH_LIBRARIES} - ${srt_link_library} + ${srt_libtarget_for_apps} ${PTHREAD_LIBRARY} - ) + ) - 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() - set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) - if (ENABLE_UNITTESTS_DISCOVERY) + set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) + if (ENABLE_UNITTESTS_DISCOVERY) + if (NOT COMMAND gtest_discover_tests) + message(WARNING "No command gtest_discover_tests provided by GTtest; no test discovery") + else () gtest_discover_tests(test-srt) endif() endif() @@ -1610,7 +1724,6 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) enable_testing() endif() - if(NOT NEED_DESTINATION) install(PROGRAMS scripts/srt-ffplay TYPE BIN) elseif (DEFINED CMAKE_INSTALL_BINDIR) diff --git a/README.md b/README.md index b0df582af..727e75ed9 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ In live streaming configurations, the SRT protocol maintains a constant end-to-e ## Build Instructions -[Linux (Ubuntu/CentOS)](./docs/build/build-linux.md) | [Windows](./docs/build/build-win.md) | [macOS](./docs/build/build-macOS.md) | [iOS](./docs/build/build-iOS.md) | [Android](./docs/build/build-android.md) | [Package Managers](./docs/build/package-managers.md) +[Linux (Ubuntu/CentOS)](./docs/build/build-linux.md) | [Windows](./docs/build/build-win.md) | [macOS](./docs/build/build-macOS.md) | [iOS](./docs/build/build-iOS.md) | [Android](./docs/build/build-android.md) | [Package Managers](./docs/build/package-managers.md) | [Devcontainer/Docker](./docs/build/build-devcontainer.md) ### Requirements diff --git a/apps/apputil.cpp b/apps/apputil.cpp index 22c31521b..0d2623af4 100644 --- a/apps/apputil.cpp +++ b/apps/apputil.cpp @@ -12,14 +12,15 @@ #include #include #include -#include #include #include #include "srt.h" // Required for SRT_SYNC_CLOCK_* definitions. +#include "common.h" #include "apputil.hpp" #include "netinet_any.h" -#include "srt_compat.h" +#include "hvu_compat.h" +#include "ofmt.h" using namespace std; using namespace srt; @@ -144,10 +145,10 @@ sockaddr_any CreateAddr(const string& name, unsigned short port, int pref_family string Join(const vector& in, string sep) { - if ( in.empty() ) + if (in.empty()) return ""; - ostringstream os; + hvu::ofmtbufstream os; os << in[0]; for (auto i = in.begin()+1; i != in.end(); ++i) @@ -389,3 +390,40 @@ void PrintLibVersion() const int patch = srtver % 0x100; cerr << "SRT Library version: " << major << "." << minor << "." << patch << ", clock type: " << SRTClockTypeStr() << endl; } + +bool IsTargetAddrSelf(const sockaddr* boundaddr, const sockaddr* targetaddr) +{ + sockaddr_any bound = boundaddr; + sockaddr_any target = targetaddr; + + if (!bound.isany()) + { + // Bound to a specific local address, so only check if + // this isn't the same address as 'target'. + if (target.equal_address(bound)) + { + return true; + } + } + else + { + // Bound to INADDR_ANY, so check matching with any local IP address + const vector& locals = srt::GetLocalInterfaces(); + + // If any of the above function fails, it will collect + // no local interfaces, so it's impossible to check anything. + // OTOH it should also mean that the network isn't working, + // so it's unlikely, as well as no address should match the + // local address anyway. + for (size_t i = 0; i < locals.size(); ++i) + { + if (locals[i].addr.equal_address(target)) + { + return true; + } + } + } + + return false; +} + diff --git a/apps/apputil.hpp b/apps/apputil.hpp index 1a0b158e0..9242f0c36 100644 --- a/apps/apputil.hpp +++ b/apps/apputil.hpp @@ -336,6 +336,7 @@ std::string OptionHelpItem(const OptionName& o); const char* SRTClockTypeStr(); void PrintLibVersion(); +bool IsTargetAddrSelf(const sockaddr* boundaddr, const sockaddr* targetaddr); namespace srt @@ -344,7 +345,7 @@ namespace srt struct OptionSetterProxy { SRTSOCKET s; - int result = -1; + SRTSTATUS result = SRT_ERROR; OptionSetterProxy(SRTSOCKET ss): s(ss) {} @@ -384,7 +385,7 @@ struct OptionSetterProxy return OptionProxy {*this, opt}; } - operator int() { return result; } + operator SRTSTATUS() { return result; } }; inline OptionSetterProxy setopt(SRTSOCKET socket) diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp index b8aa67b86..461af4713 100644 --- a/apps/socketoptions.hpp +++ b/apps/socketoptions.hpp @@ -58,29 +58,31 @@ struct SocketOption bool applyt(Object socket, std::string value) const; template - static int setso(Object socket, int protocol, int symbol, const void* data, size_t size); + static int setso(Object , int , int , const void* , size_t) + { + typename Object::wrong_version error; + return -1; + } template bool extract(std::string value, OptionValue& val) const; }; template<> -inline int SocketOption::setso(int socket, int /*ignored*/, int sym, const void* data, size_t size) +inline int SocketOption::setso(SRTSOCKET socket, int /*ignored*/, int sym, const void* data, size_t size) { - return srt_setsockopt(socket, 0, SRT_SOCKOPT(sym), data, (int) size); + return (int)srt_setsockflag(socket, SRT_SOCKOPT(sym), data, (int) size); } -#if ENABLE_BONDING template<> inline int SocketOption::setso(SRT_SOCKOPT_CONFIG* obj, int /*ignored*/, int sym, const void* data, size_t size) { - return srt_config_add(obj, SRT_SOCKOPT(sym), data, (int) size); + return (int)srt_config_add(obj, SRT_SOCKOPT(sym), data, (int) size); } -#endif template<> -inline int SocketOption::setso(int socket, int proto, int sym, const void* data, size_t size) +inline int SocketOption::setso(SYSSOCKET socket, int proto, int sym, const void* data, size_t size) { return ::setsockopt(socket, proto, sym, (const char *)data, (int) size); } @@ -184,7 +186,7 @@ inline bool SocketOption::applyt(Object socket, std::string value) const int result = -1; if (extract(value, o)) result = setso(socket, protocol, symbol, o.value, o.size); - return result != -1; + return result != int(SRT_ERROR); } @@ -247,20 +249,12 @@ const SocketOption srt_options [] { { "ipv6only", 0, SRTO_IPV6ONLY, SocketOption::PRE, SocketOption::INT, nullptr }, { "peeridletimeo", 0, SRTO_PEERIDLETIMEO, SocketOption::PRE, SocketOption::INT, nullptr }, { "packetfilter", 0, SRTO_PACKETFILTER, SocketOption::PRE, SocketOption::STRING, nullptr }, -#if ENABLE_BONDING { "groupconnect", 0, SRTO_GROUPCONNECT, SocketOption::PRE, SocketOption::INT, nullptr}, { "groupminstabletimeo", 0, SRTO_GROUPMINSTABLETIMEO, SocketOption::PRE, SocketOption::INT, nullptr}, -#endif -#ifdef SRT_ENABLE_BINDTODEVICE { "bindtodevice", 0, SRTO_BINDTODEVICE, SocketOption::PRE, SocketOption::STRING, nullptr}, -#endif - { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr } -#ifdef ENABLE_AEAD_API_PREVIEW - ,{ "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr } -#endif -#ifdef ENABLE_MAXREXMITBW - ,{ "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr } -#endif + { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr }, + { "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr }, + { "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr } }; } diff --git a/apps/srt-file-transmit.cpp b/apps/srt-file-transmit.cpp index 327ad6809..a2b34532c 100644 --- a/apps/srt-file-transmit.cpp +++ b/apps/srt-file-transmit.cpp @@ -16,6 +16,7 @@ written by #include #endif #include +#include #include #include #include @@ -27,12 +28,11 @@ written by #include #include #include -#include #include +#include #include "apputil.hpp" #include "uriparser.hpp" -#include "logsupport.hpp" #include "socketoptions.hpp" #include "transmitmedia.hpp" #include "verbose.hpp" @@ -44,6 +44,7 @@ written by using namespace std; +using namespace srt; static bool interrupt = false; void OnINT_ForceExit(int) @@ -57,8 +58,8 @@ struct FileTransmitConfig unsigned long chunk_size; bool skip_flushing; bool quiet = false; - srt_logging::LogLevel::type loglevel = srt_logging::LogLevel::error; - set logfas; + hvu::logging::LogLevel::type loglevel = hvu::logging::LogLevel::error; + set logfas; string logfile; int bw_report = 0; int stats_report = 0; @@ -180,7 +181,7 @@ int parse_args(FileTransmitConfig &cfg, int argc, char** argv) return 2; } - cfg.chunk_size = stoul(Option(params, "1456", o_chunk)); + cfg.chunk_size = stoul(Option(params, "0", o_chunk)); cfg.skip_flushing = Option(params, false, o_no_flush); cfg.bw_report = stoi(Option(params, "0", o_bwreport)); cfg.stats_report = stoi(Option(params, "0", o_statsrep)); @@ -206,8 +207,8 @@ int parse_args(FileTransmitConfig &cfg, int argc, char** argv) } cfg.full_stats = Option(params, false, o_statsfull); - cfg.loglevel = SrtParseLogLevel(Option(params, "error", o_loglevel)); - cfg.logfas = SrtParseLogFA(Option(params, "", o_logfa)); + cfg.loglevel = hvu::logging::parse_level(Option(params, "error", o_loglevel)); + cfg.logfas = hvu::logging::parse_fa(srt::logging::logger_config(), Option(params, "", o_logfa)); cfg.logfile = Option(params, "", o_logfile); cfg.quiet = Option(params, false, o_quiet); @@ -309,7 +310,7 @@ bool DoUpload(UriParser& ut, string path, string filename, int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, - tar->GetSRTSocket(), &events)) + tar->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; @@ -349,7 +350,7 @@ bool DoUpload(UriParser& ut, string path, string filename, s = tar->GetSRTSocket(); int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - if (srt_epoll_add_usock(pollid, s, &events)) + if (srt_epoll_add_usock(pollid, s, &events) == SRT_ERROR) { cerr << "Failed to add SRT client to poll" << endl; goto exit; @@ -389,9 +390,11 @@ bool DoUpload(UriParser& ut, string path, string filename, while (n > 0) { int st = tar->Write(buf.data() + shift, n, 0, out_stats); - Verb() << "Upload: " << n << " --> " << st - << (!shift ? string() : "+" + Sprint(shift)); - if (st == SRT_ERROR) + Verb("Upload: ", n, " --> ", st, VerbNoEOL); + if (shift) + Verb("+", shift, VerbNoEOL); + Verb(); + if (st == int(SRT_ERROR)) { cerr << "Upload: SRT error: " << srt_getlasterror_str() << endl; @@ -429,7 +432,7 @@ bool DoUpload(UriParser& ut, string path, string filename, size_t bytes; size_t blocks; int st = srt_getsndbuffer(s, &blocks, &bytes); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str() << endl; @@ -490,7 +493,7 @@ bool DoDownload(UriParser& us, string directory, string filename, int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, - src->GetSRTSocket(), &events)) + src->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT source to poll, " << src->GetSRTSocket() << endl; @@ -528,7 +531,7 @@ bool DoDownload(UriParser& us, string directory, string filename, s = src->GetSRTSocket(); int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; - if (srt_epoll_add_usock(pollid, s, &events)) + if (srt_epoll_add_usock(pollid, s, &events) == SRT_ERROR) { cerr << "Failed to add SRT client to poll" << endl; goto exit; @@ -593,7 +596,7 @@ bool DoDownload(UriParser& us, string directory, string filename, } int n = src->Read(cfg.chunk_size, packet, out_stats); - if (n == SRT_ERROR) + if (n == int(SRT_ERROR)) { cerr << "Download: SRT error: " << srt_getlasterror_str() << endl; goto exit; @@ -681,8 +684,11 @@ int main(int argc, char** argv) // // Set global config variables // - if (cfg.chunk_size != SRT_LIVE_MAX_PLSIZE) + if (cfg.chunk_size != 0) transmit_chunk_size = cfg.chunk_size; + else + transmit_chunk_size = SRT_MAX_PLSIZE_AF_INET; + transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf); transmit_bw_report = cfg.bw_report; transmit_stats_report = cfg.stats_report; @@ -692,7 +698,7 @@ int main(int argc, char** argv) // Set SRT log levels and functional areas // srt_setloglevel(cfg.loglevel); - for (set::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i) + for (set::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i) srt_addlogfa(*i); // diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index 3468c78b5..10b9e99f7 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -65,11 +65,11 @@ #include #include -#include "srt_compat.h" +#include + #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" -#include "logsupport.hpp" #include "transmitmedia.hpp" #include "verbose.hpp" @@ -77,8 +77,7 @@ // to the library. Application using the "installed" library should // use #include -#include // This TEMPORARILY contains extra C++-only SRT API. -#include +#include // because contains the declaration of logger_config using namespace std; @@ -131,8 +130,8 @@ struct LiveTransmitConfig int timeout_mode = 0; int chunk_size = -1; bool quiet = false; - srt_logging::LogLevel::type loglevel = srt_logging::LogLevel::error; - set logfas; + hvu::logging::LogLevel::type loglevel = hvu::logging::LogLevel::error; + set logfas; bool log_internal; string logfile; int bw_report = 0; @@ -248,18 +247,18 @@ int parse_args(LiveTransmitConfig &cfg, int argc, char** argv) cerr << "List of functional areas:\n"; map revmap; - for (auto entry: SrtLogFAList()) - revmap[entry.second] = entry.first; + for (size_t i = 0; i < srt::logging::logger_config().size(); ++i) + revmap[i] = srt::logging::logger_config().name(i); // Each group on a new line - int en10 = 0; + int en6 = 0; for (auto entry: revmap) { cerr << " " << entry.second; - if (entry.first/10 != en10) + if (entry.first/6 != en6) { cerr << endl; - en10 = entry.first/10; + en6 = entry.first/6; } } cerr << endl; @@ -339,8 +338,8 @@ int parse_args(LiveTransmitConfig &cfg, int argc, char** argv) } cfg.full_stats = OptionPresent(params, o_statsfull); - cfg.loglevel = SrtParseLogLevel(Option(params, "warn", o_loglevel)); - cfg.logfas = SrtParseLogFA(Option(params, "", o_logfa)); + cfg.loglevel = hvu::logging::parse_level(Option(params, "warn", o_loglevel)); + cfg.logfas = hvu::logging::parse_fa(srt::logging::logger_config(), Option(params, "", o_logfa)); cfg.log_internal = OptionPresent(params, o_log_internal); cfg.logfile = Option(params, o_logfile); cfg.quiet = OptionPresent(params, o_quiet); @@ -399,9 +398,7 @@ int main(int argc, char** argv) srt_setloglevel(cfg.loglevel); if (!cfg.logfas.empty()) { - srt_resetlogfa(nullptr, 0); - for (set::iterator i = cfg.logfas.begin(); i != cfg.logfas.end(); ++i) - srt_addlogfa(*i); + srt::resetlogfa(cfg.logfas); } // @@ -412,10 +409,10 @@ int main(int argc, char** argv) if (cfg.log_internal) { srt_setlogflags(0 - | SRT_LOGF_DISABLE_TIME - | SRT_LOGF_DISABLE_SEVERITY - | SRT_LOGF_DISABLE_THREADNAME - | SRT_LOGF_DISABLE_EOL + | HVU_LOGF_DISABLE_TIME + | HVU_LOGF_DISABLE_SEVERITY + | HVU_LOGF_DISABLE_THREADNAME + | HVU_LOGF_DISABLE_EOL ); srt_setloghandler(NAME, TestLogHandler); } @@ -519,7 +516,7 @@ int main(int argc, char** argv) { case UriParser::SRT: if (srt_epoll_add_usock(pollid, - src->GetSRTSocket(), &events)) + src->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT source to poll, " << src->GetSRTSocket() << endl; @@ -529,7 +526,7 @@ int main(int argc, char** argv) case UriParser::UDP: case UriParser::RTP: if (srt_epoll_add_ssock(pollid, - src->GetSysSocket(), &events)) + src->GetSysSocket(), &events) == SRT_ERROR) { cerr << "Failed to add " << src->uri.proto() << " source to poll, " << src->GetSysSocket() @@ -541,7 +538,7 @@ int main(int argc, char** argv) { const int con = src->GetSysSocket(); // try to make the standard input non blocking - if (srt_epoll_add_ssock(pollid, con, &events)) + if (srt_epoll_add_ssock(pollid, con, &events) == SRT_ERROR) { cerr << "Failed to add FILE source to poll, " << src->GetSysSocket() << endl; @@ -573,7 +570,7 @@ int main(int argc, char** argv) { case UriParser::SRT: if (srt_epoll_add_usock(pollid, - tar->GetSRTSocket(), &events)) + tar->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; @@ -647,7 +644,7 @@ int main(int argc, char** argv) SRTSOCKET ns = (issource) ? src->GetSRTSocket() : tar->GetSRTSocket(); int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; - if (srt_epoll_add_usock(pollid, ns, &events)) + if (srt_epoll_add_usock(pollid, ns, &events) == SRT_ERROR) { cerr << "Failed to add SRT client to poll, " << ns << endl; @@ -745,7 +742,7 @@ int main(int argc, char** argv) const int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; // Disable OUT event polling when connected if (srt_epoll_update_usock(pollid, - tar->GetSRTSocket(), &events)) + tar->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; @@ -787,15 +784,17 @@ int main(int argc, char** argv) SRTSOCKET sock = src->GetSRTSocket(); if (sock != SRT_INVALID_SOCK) { - for (int n = 0; n < srtrfdslen && !(srcReady = (sock == srtrwfds[n])); n ++); + for (int n = 0; n < srtrfdslen && !(srcReady = (sock == srtrwfds[n])); n ++) + ; } } if (!srcReady && sysrfdslen > 0) { - int sock = src->GetSysSocket(); - if (sock != -1) + SYSSOCKET sock = src->GetSysSocket(); + if (sock != SYSSOCKET_INVALID) { - for (int n = 0; n < sysrfdslen && !(srcReady = (sock == sysrfds[n])); n++); + for (int n = 0; n < sysrfdslen && !(srcReady = (sock == sysrfds[n])); n++) + ; } } } @@ -811,7 +810,7 @@ int main(int argc, char** argv) std::shared_ptr pkt(new MediaPacket(transmit_chunk_size)); const int res = src->Read(transmit_chunk_size, *pkt, out_stats); - if (res == SRT_ERROR && src->uri.type() == UriParser::SRT) + if (res == int(SRT_ERROR) && src->uri.type() == UriParser::SRT) { if (srt_getlasterror(NULL) == SRT_EASYNCRCV) break; @@ -882,29 +881,21 @@ int main(int argc, char** argv) void TestLogHandler(void* opaque, int level, const char* file, int line, const char* area, const char* message) { - char prefix[100] = ""; - if ( opaque ) { -#ifdef _MSC_VER - strncpy_s(prefix, sizeof(prefix), (char*)opaque, _TRUNCATE); -#else - strncpy(prefix, (char*)opaque, sizeof(prefix) - 1); - prefix[sizeof(prefix) - 1] = '\0'; -#endif + std::string prefix; + if (opaque) + { + const char* instr = (const char*)opaque; + size_t len = strlen(instr); + if (len > 10) + len = 10; + prefix = ":" + string(instr, len); } + time_t now; time(&now); - char buf[1024]; - struct tm local = SysLocalTime(now); - size_t pos = strftime(buf, 1024, "[%c ", &local); - -#ifdef _MSC_VER - // That's something weird that happens on Microsoft Visual Studio 2013 - // Trying to keep portability, while every version of MSVS is a different platform. - // On MSVS 2015 there's already a standard-compliant snprintf, whereas _snprintf - // is available on backward compatibility and it doesn't work exactly the same way. -#define snprintf _snprintf -#endif - snprintf(buf+pos, 1024-pos, "%s:%d(%s)]{%d} %s", file, line, area, level, message); + struct tm local = hvu::SysLocalTime(now); - cerr << buf << endl; + cerr << "[" << std::put_time(&local, "%c") << " " << file << ":" << line + << "(" << area << ")]{" << level << "} " << prefix << message + << endl; } diff --git a/apps/srt-tunnel.cpp b/apps/srt-tunnel.cpp index 9ca3e2cc2..8e008f69c 100644 --- a/apps/srt-tunnel.cpp +++ b/apps/srt-tunnel.cpp @@ -27,11 +27,11 @@ #include #include -#include "srt_compat.h" +#include "hvu_compat.h" +#include "hvu_threadname.h" #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" -#include "logsupport.hpp" #include "transmitbase.hpp" // bytevector typedef to avoid collisions #include "verbose.hpp" @@ -39,7 +39,6 @@ // to the library. Application using the "installed" library should // use #include -#include // This TEMPORARILY contains extra C++-only SRT API. #include #include #include @@ -62,13 +61,8 @@ testmedia.cpp using namespace std; using namespace srt; -const srt_logging::LogFA SRT_LOGFA_APP = 10; -namespace srt_logging -{ -Logger applog(SRT_LOGFA_APP, srt_logger_config, "TUNNELAPP"); -} +hvu::logging::Logger applog("app", srt::logging::logger_config(), true, "TUNNELAPP"); -using srt_logging::applog; class Medium { @@ -215,7 +209,6 @@ class Engine int status = 0; Medium::ReadStatus rdst = Medium::RD_ERROR; - UDT::ERRORINFO srtx; public: enum Dir { DIR_IN, DIR_OUT }; @@ -243,7 +236,7 @@ class Engine { Verb() << "START: " << media[DIR_IN]->uri() << " --> " << media[DIR_OUT]->uri(); const std::string thrn = media[DIR_IN]->id() + ">" + media[DIR_OUT]->id(); - srt::ThreadName tn(thrn); + hvu::ThreadName tn(thrn); thr = thread([this]() { Worker(); }); } @@ -420,7 +413,7 @@ void Engine::Worker() class SrtMedium: public Medium { - SRTSOCKET m_socket = SRT_ERROR; + SRTSOCKET m_socket = SRT_INVALID_SOCK; friend class Medium; public: @@ -441,10 +434,10 @@ class SrtMedium: public Medium { Verb() << "Closing SRT socket for " << uri(); lock_guard lk(access); - if (m_socket == SRT_ERROR) + if (m_socket == SRT_INVALID_SOCK) return; srt_close(m_socket); - m_socket = SRT_ERROR; + m_socket = SRT_INVALID_SOCK; } // Forwarded in order to separate the implementation from @@ -470,9 +463,18 @@ class SrtMedium: public Medium using Medium::Error; - static void Error(UDT::ERRORINFO& ri, const string& text) + static void Error(int srterror, int errnov, const string& text) { - throw TransmissionError("ERROR: " + text + ": " + ri.getErrorMessage()); + using namespace hvu; + string message = srt_strerror(srterror, errnov); + + string hm = fmtcat("ERROR #", srterror, ": ", text, ": ", message); + if (errnov == 0) + throw TransmissionError(hm); + else + { + throw TransmissionError(fmtcat(hm, ": #", errnov, ": ", SysStrError(errnov))); + } } ~SrtMedium() override @@ -565,8 +567,7 @@ class TcpMedium: public Medium static void Error(int verrno, const string& text) { - char rbuf[1024]; - throw TransmissionError("ERROR: " + text + ": " + SysStrError(verrno, rbuf, 1024)); + throw TransmissionError("ERROR: " + text + ": " + hvu::SysStrError(verrno)); } virtual ~TcpMedium() @@ -625,19 +626,23 @@ void SrtMedium::CreateListener() sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); - int stat = srt_bind(m_socket, sa.get(), sizeof sa); + SRTSTATUS stat = srt_bind(m_socket, sa.get(), sizeof sa); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { + int errnov = 0; + int result = srt_getlasterror(&errnov); srt_close(m_socket); - Error(UDT::getlasterror(), "srt_bind"); + Error(result, errnov, "srt_bind"); } stat = srt_listen(m_socket, backlog); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { + int errnov = 0; + int result = srt_getlasterror(&errnov); srt_close(m_socket); - Error(UDT::getlasterror(), "srt_listen"); + Error(result, errnov, "srt_listen"); } m_listener = true; @@ -675,9 +680,11 @@ unique_ptr SrtMedium::Accept() { sockaddr_any sa; SRTSOCKET s = srt_accept(m_socket, (sa.get()), (&sa.len)); - if (s == SRT_ERROR) + if (s == SRT_INVALID_SOCK) { - Error(UDT::getlasterror(), "srt_accept"); + int errnov = 0; + int result = srt_getlasterror(&errnov); + Error(result, errnov, "srt_accept"); } ConfigurePost(s); @@ -735,9 +742,13 @@ void SrtMedium::Connect() { sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); - int st = srt_connect(m_socket, sa.get(), sizeof sa); - if (st == SRT_ERROR) - Error(UDT::getlasterror(), "srt_connect"); + SRTSOCKET st = srt_connect(m_socket, sa.get(), sizeof sa); + if (st == SRT_INVALID_SOCK) + { + int errnov = 0; + int result = srt_getlasterror(&errnov); + Error(result, errnov, "srt_connect"); + } ConfigurePost(m_socket); @@ -767,7 +778,7 @@ int SrtMedium::ReadInternal(char* w_buffer, int size) do { st = srt_recv(m_socket, (w_buffer), size); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { int syserr; if (srt_getlasterror(&syserr) == SRT_EASYNCRCV && !m_broken) @@ -886,9 +897,11 @@ Medium::ReadStatus Medium::Read(bytevector& w_output) void SrtMedium::Write(bytevector& w_buffer) { int st = srt_send(m_socket, w_buffer.data(), (int)w_buffer.size()); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { - Error(UDT::getlasterror(), "srt_send"); + int errnov = 0; + int result = srt_getlasterror(&errnov); + Error(result, errnov, "srt_send"); } // This should be ==, whereas > is not possible, but @@ -1116,22 +1129,22 @@ int main( int argc, char** argv ) string loglevel = Option(params, "error", o_loglevel); string logfa = Option(params, "", o_logfa); - srt_logging::LogLevel::type lev = SrtParseLogLevel(loglevel); + auto lev = hvu::logging::parse_level(loglevel); srt::setloglevel(lev); if (logfa == "") { - srt::addlogfa(SRT_LOGFA_APP); + srt::addlogfa(applog.id()); } else { // Add only selected FAs set unknown_fas; - set fas = SrtParseLogFA(logfa, &unknown_fas); + set fas = hvu::logging::parse_fa(srt::logging::logger_config(), logfa, &unknown_fas); srt::resetlogfa(fas); // The general parser doesn't recognize the "app" FA, we check it here. if (unknown_fas.count("app")) - srt::addlogfa(SRT_LOGFA_APP); + srt::addlogfa(applog.id()); } string verbo = Option(params, "no", o_verbose); diff --git a/apps/statswriter.cpp b/apps/statswriter.cpp index f1aa343b5..7eab5dc9b 100644 --- a/apps/statswriter.cpp +++ b/apps/statswriter.cpp @@ -16,10 +16,11 @@ #include #include -#include "statswriter.hpp" -#include "netinet_any.h" -#include "srt_compat.h" +#include +#include +#include "ofmt_iostream.h" +#include "statswriter.hpp" using namespace std; @@ -102,19 +103,24 @@ std::string SrtStatsWriter::print_timestamp() { using namespace std; using namespace std::chrono; + using namespace hvu; const auto systime_now = system_clock::now(); const time_t time_now = system_clock::to_time_t(systime_now); - std::ostringstream output; + ofmtbufstream output; // SysLocalTime returns zeroed tm_now on failure, which is ok for put_time. const tm tm_now = SysLocalTime(time_now); - output << std::put_time(&tm_now, "%FT%T.") << std::setfill('0') << std::setw(6); - const auto since_epoch = systime_now.time_since_epoch(); - const seconds s = duration_cast(since_epoch); - output << duration_cast(since_epoch - s).count(); - output << std::put_time(&tm_now, "%z"); + output << fmt(tm_now, "%FT%T."); + + // Fraction of a second part + const auto us_now = duration_cast(systime_now.time_since_epoch()); + const auto us_rem = us_now - duration_cast(us_now); + output << fmt(us_rem.count(), fmtc().fillzero().width(6)); + + // Timezone + output << fmt(tm_now, "%z"); return output.str(); } #else @@ -146,7 +152,7 @@ class SrtStatsJson : public SrtStatsWriter } public: - string WriteStats(int sid, const CBytePerfMon& mon) override + string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) override { std::ostringstream output; @@ -235,7 +241,7 @@ class SrtStatsCsv : public SrtStatsWriter public: SrtStatsCsv() : first_line_printed(false) {} - string WriteStats(int sid, const CBytePerfMon& mon) override + string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) override { std::ostringstream output; @@ -286,7 +292,7 @@ class SrtStatsCsv : public SrtStatsWriter class SrtStatsCols : public SrtStatsWriter { public: - string WriteStats(int sid, const CBytePerfMon& mon) override + string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) override { std::ostringstream output; output << "======= SRT STATS: sid=" << sid << endl; diff --git a/apps/statswriter.hpp b/apps/statswriter.hpp index 1b4fd4fbc..6b476babf 100644 --- a/apps/statswriter.hpp +++ b/apps/statswriter.hpp @@ -71,7 +71,7 @@ struct SrtStatDataType: public SrtStatData class SrtStatsWriter { public: - virtual std::string WriteStats(int sid, const CBytePerfMon& mon) = 0; + virtual std::string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) = 0; virtual std::string WriteBandwidth(double mbpsBandwidth) = 0; virtual ~SrtStatsWriter() {} @@ -85,7 +85,7 @@ class SrtStatsWriter bool Option(const std::string& key, std::string* rval = nullptr) { - const std::string* out = map_getp(options, key); + const std::string* out = srt::map_getp(options, key); if (!out) return false; diff --git a/apps/support.maf b/apps/support.maf index e5aa77b8e..e19588317 100644 --- a/apps/support.maf +++ b/apps/support.maf @@ -10,8 +10,6 @@ SOURCES apputil.cpp statswriter.cpp -logsupport.cpp -logsupport_appdefs.cpp socketoptions.cpp transmitmedia.cpp uriparser.cpp @@ -19,7 +17,6 @@ verbose.cpp PRIVATE HEADERS apputil.hpp -logsupport.hpp socketoptions.hpp transmitbase.hpp transmitmedia.hpp diff --git a/apps/transmitbase.hpp b/apps/transmitbase.hpp index 7451c59b7..4a28cd4cb 100644 --- a/apps/transmitbase.hpp +++ b/apps/transmitbase.hpp @@ -70,7 +70,7 @@ class Source: public Location }; virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; } - virtual int GetSysSocket() const { return -1; } + virtual SYSSOCKET GetSysSocket() const { return SYSSOCKET_INVALID; } virtual bool MayBlock() const { return false; } virtual bool AcceptNewClient() { return false; } }; @@ -87,7 +87,7 @@ class Target: public Location virtual ~Target() {} virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; } - virtual int GetSysSocket() const { return -1; } + virtual SYSSOCKET GetSysSocket() const { return SYSSOCKET_INVALID; } virtual bool AcceptNewClient() { return false; } }; diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 862875fa2..9f7245be8 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -29,22 +29,27 @@ #include #endif +#define REQUIRE_CXX11 1 + +#include "srt_attr_defs.h" #include "netinet_any.h" +#include "ofmt.h" #include "apputil.hpp" #include "socketoptions.hpp" #include "uriparser.hpp" #include "transmitmedia.hpp" -#include "srt_compat.h" +#include "hvu_compat.h" #include "verbose.hpp" using namespace std; using namespace srt; +using namespace hvu; bool g_stats_are_printed_to_stdout = false; bool transmit_total_stats = false; unsigned long transmit_bw_report = 0; unsigned long transmit_stats_report = 0; -unsigned long transmit_chunk_size = SRT_LIVE_MAX_PLSIZE; +unsigned long transmit_chunk_size = SRT_MAX_PLSIZE_AF_INET6; class FileSource: public Source { @@ -179,6 +184,40 @@ void SrtCommon::InitParameters(string host, map par) m_adapter = host; } + unsigned int max_payload_size = 0; + + // Try to interpret host and adapter first + sockaddr_any host_sa, adapter_sa; + + if (host != "") + { + host_sa = CreateAddr(host); + if (host_sa.family() == AF_UNSPEC) + Error("Failed to interpret 'host' spec: " + host); + + if (host_sa.family() == AF_INET) + max_payload_size = SRT_MAX_PLSIZE_AF_INET; + } + + if (adapter != "" && adapter != host) + { + adapter_sa = CreateAddr(adapter); + + if (adapter_sa.family() == AF_UNSPEC) + Error("Failed to interpret 'adapter' spec: " + adapter); + + if (host_sa.family() != AF_UNSPEC && host_sa.family() != adapter_sa.family()) + { + Error("Both host and adapter specified and they use different IP versions"); + } + + if (max_payload_size == 0 && host_sa.family() == AF_INET) + max_payload_size = SRT_MAX_PLSIZE_AF_INET; + } + + if (!max_payload_size) + max_payload_size = SRT_MAX_PLSIZE_AF_INET6; + if (par.count("tsbpd") && false_names.count(par.at("tsbpd"))) { m_tsbpdmode = false; @@ -195,10 +234,16 @@ void SrtCommon::InitParameters(string host, map par) if ((par.count("transtype") == 0 || par["transtype"] != "file") && transmit_chunk_size > SRT_LIVE_DEF_PLSIZE) { - if (transmit_chunk_size > SRT_LIVE_MAX_PLSIZE) - throw std::runtime_error("Chunk size in live mode exceeds 1456 bytes; this is not supported"); + if (transmit_chunk_size > max_payload_size) + Error(fmtcat("Chunk size in live mode exceeds ", max_payload_size, " bytes; this is not supported")); - par["payloadsize"] = Sprint(transmit_chunk_size); + par["payloadsize"] = fmts(transmit_chunk_size); + } + else + { + // set it so without making sure that it was set to "file". + // worst case it will be rejected in settings + m_transtype = SRTT_FILE; } // Assign the others here. @@ -208,11 +253,11 @@ void SrtCommon::InitParameters(string host, map par) void SrtCommon::PrepareListener(string host, int port, int backlog) { m_bindsock = srt_create_socket(); - if ( m_bindsock == SRT_ERROR ) + if (m_bindsock == SRT_INVALID_SOCK) Error("srt_create_socket"); - int stat = ConfigurePre(m_bindsock); - if ( stat == SRT_ERROR ) + SRTSTATUS stat = ConfigurePre(m_bindsock); + if (stat == SRT_ERROR) Error("ConfigurePre"); sockaddr_any sa = CreateAddr(host, port); @@ -220,7 +265,7 @@ void SrtCommon::PrepareListener(string host, int port, int backlog) Verb() << "Binding a server on " << host << ":" << port << " ..."; stat = srt_bind(m_bindsock, psa, sizeof sa); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { srt_close(m_bindsock); Error("srt_bind"); @@ -229,7 +274,7 @@ void SrtCommon::PrepareListener(string host, int port, int backlog) Verb() << " listen..."; stat = srt_listen(m_bindsock, backlog); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { srt_close(m_bindsock); Error("srt_listen"); @@ -263,6 +308,21 @@ bool SrtCommon::AcceptNewClient() Error("srt_accept"); } + int maxsize = srt_getmaxpayloadsize(m_sock); + if (maxsize == int(SRT_ERROR)) + { + srt_close(m_bindsock); + srt_close(m_sock); + Error("srt_getmaxpayloadsize"); + } + + if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) + { + srt_close(m_bindsock); + srt_close(m_sock); + Error(fmtcat("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + } + // we do one client connection at a time, // so close the listener. srt_close(m_bindsock); @@ -272,8 +332,8 @@ bool SrtCommon::AcceptNewClient() // ConfigurePre is done on bindsock, so any possible Pre flags // are DERIVED by sock. ConfigurePost is done exclusively on sock. - int stat = ConfigurePost(m_sock); - if ( stat == SRT_ERROR ) + SRTSTATUS stat = ConfigurePost(m_sock); + if (stat == SRT_ERROR) Error("ConfigurePost"); return true; @@ -299,14 +359,14 @@ void SrtCommon::Init(string host, int port, map par, bool dir_out } } -int SrtCommon::ConfigurePost(SRTSOCKET sock) +SRTSTATUS SrtCommon::ConfigurePost(SRTSOCKET sock) { bool no = false; - int result = 0; + SRTSTATUS result = SRT_STATUS_OK; if ( m_output_direction ) { result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; if ( m_timeout ) @@ -315,7 +375,7 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) else { result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; if ( m_timeout ) @@ -339,23 +399,23 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) } } - return 0; + return SRT_STATUS_OK; } -int SrtCommon::ConfigurePre(SRTSOCKET sock) +SRTSTATUS SrtCommon::ConfigurePre(SRTSOCKET sock) { - int result = 0; + SRTSTATUS result = SRT_STATUS_OK; bool no = false; if ( !m_tsbpdmode ) { result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; } result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; @@ -380,14 +440,14 @@ int SrtCommon::ConfigurePre(SRTSOCKET sock) return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } void SrtCommon::SetupAdapter(const string& host, int port) { sockaddr_any localsa = CreateAddr(host, port); sockaddr* psa = localsa.get(); - int stat = srt_bind(m_sock, psa, sizeof localsa); + SRTSTATUS stat = srt_bind(m_sock, psa, sizeof localsa); if ( stat == SRT_ERROR ) Error("srt_bind"); } @@ -407,10 +467,10 @@ void SrtCommon::OpenClient(string host, int port) void SrtCommon::PrepareClient() { m_sock = srt_create_socket(); - if ( m_sock == SRT_ERROR ) + if ( m_sock == SRT_INVALID_SOCK) Error("srt_create_socket"); - int stat = ConfigurePre(m_sock); + SRTSTATUS stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); } @@ -418,20 +478,32 @@ void SrtCommon::PrepareClient() void SrtCommon::ConnectClient(string host, int port) { - sockaddr_any sa = CreateAddr(host, port); sockaddr* psa = sa.get(); Verb() << "Connecting to " << host << ":" << port; - int stat = srt_connect(m_sock, psa, sizeof sa); - if ( stat == SRT_ERROR ) + SRTSOCKET cstat = srt_connect(m_sock, psa, sizeof sa); + if (cstat == SRT_INVALID_SOCK) { srt_close(m_sock); Error("srt_connect"); } - stat = ConfigurePost(m_sock); + int maxsize = srt_getmaxpayloadsize(m_sock); + if (maxsize == int(SRT_ERROR)) + { + srt_close(m_sock); + Error("srt_getmaxpayloadsize"); + } + + if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) + { + srt_close(m_sock); + Error(fmtcat("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + } + + SRTSTATUS stat = ConfigurePost(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePost"); } @@ -449,13 +521,13 @@ void SrtCommon::Error(string src) void SrtCommon::OpenRendezvous(string adapter, string host, int port) { m_sock = srt_create_socket(); - if ( m_sock == SRT_ERROR ) + if (m_sock == SRT_INVALID_SOCK) Error("srt_create_socket"); bool yes = true; srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); - int stat = ConfigurePre(m_sock); + SRTSTATUS stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); @@ -480,8 +552,8 @@ void SrtCommon::OpenRendezvous(string adapter, string host, int port) Verb() << "Connecting to " << host << ":" << port; - stat = srt_connect(m_sock, sa.get(), sizeof sa); - if ( stat == SRT_ERROR ) + SRTSOCKET cstat = srt_connect(m_sock, sa.get(), sizeof sa); + if ( cstat == SRT_INVALID_SOCK) { srt_close(m_sock); Error("srt_connect"); @@ -519,10 +591,7 @@ SrtCommon::~SrtCommon() SrtSource::SrtSource(string host, int port, const map& par) { Init(host, port, par, false); - - ostringstream os; - os << host << ":" << port; - hostport_copy = os.str(); + hostport_copy = fmtcat(host, ":"_V, port); } int SrtSource::Read(size_t chunk, MediaPacket& pkt, ostream &out_stats) @@ -565,10 +634,10 @@ int SrtSource::Read(size_t chunk, MediaPacket& pkt, ostream &out_stats) return stat; } -int SrtTarget::ConfigurePre(SRTSOCKET sock) +SRTSTATUS SrtTarget::ConfigurePre(SRTSOCKET sock) { - int result = SrtCommon::ConfigurePre(sock); - if ( result == -1 ) + SRTSTATUS result = SrtCommon::ConfigurePre(sock); + if ( result == SRT_ERROR ) return result; int yes = 1; @@ -577,10 +646,10 @@ int SrtTarget::ConfigurePre(SRTSOCKET sock) // In HSv4 this setting is obligatory; otherwise the SRT handshake // extension will not be done at all. result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; - return 0; + return SRT_STATUS_OK; } int SrtTarget::Write(const char* data, size_t size, int64_t src_time, ostream &out_stats) @@ -590,7 +659,7 @@ int SrtTarget::Write(const char* data, size_t size, int64_t src_time, ostream &o SRT_MSGCTRL ctrl = srt_msgctrl_default; ctrl.srctime = src_time; int stat = srt_sendmsg2(m_sock, data, (int) size, &ctrl); - if (stat == SRT_ERROR) + if (stat == int(SRT_ERROR)) { return stat; } @@ -744,7 +813,7 @@ class ConsoleSource: public Source bool IsOpen() override { return cin.good(); } bool MayBlock() const final { return may_block; } bool End() override { return cin.eof(); } - int GetSysSocket() const override { return fileno(stdin); }; + SYSSOCKET GetSysSocket() const override { return fileno(stdin); }; }; class ConsoleTarget: public Target @@ -773,7 +842,7 @@ class ConsoleTarget: public Target bool IsOpen() override { return cout.good(); } bool Broken() override { return cout.eof(); } - int GetSysSocket() const override { return fileno(stdout); }; + SYSSOCKET GetSysSocket() const override { return fileno(stdout); }; }; template struct Console; @@ -803,18 +872,20 @@ static inline bool IsMulticast(in_addr adr) class UdpCommon { +public: + static const SYSSOCKET SYSSOCKET_INVALID = -1; protected: - int m_sock = -1; + SYSSOCKET m_sock = SYSSOCKET_INVALID; string adapter; - sockaddr_any interface_addr; - sockaddr_any target_addr; - bool is_multicast = false; + sockaddr_any interface_addr; + sockaddr_any target_addr; + bool is_multicast = false; map m_options; void Setup(string host, int port, map attr) { m_sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (m_sock == -1) + if (m_sock == SYSSOCKET_INVALID) Error(SysError(), "UdpCommon::Setup: socket"); int yes = 1; @@ -946,8 +1017,7 @@ class UdpCommon void Error(int err, string src) { - char buf[512]; - string message = SysStrError(err, buf, 512u); + string message = SysStrError(err); cerr << "\nERROR #" << err << ": " << message << endl; @@ -957,11 +1027,11 @@ class UdpCommon ~UdpCommon() { #ifdef _WIN32 - if (m_sock != -1) + if (m_sock != SYSSOCKET_INVALID) { shutdown(m_sock, SD_BOTH); closesocket(m_sock); - m_sock = -1; + m_sock = SYSSOCKET_INVALID; } #else close(m_sock); @@ -1038,10 +1108,10 @@ class UdpSource: public Source, public UdpCommon return stat; } - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } bool End() override { return eof; } - int GetSysSocket() const override { return m_sock; }; + SYSSOCKET GetSysSocket() const override { return m_sock; }; }; class UdpTarget: public Target, public UdpCommon @@ -1083,10 +1153,10 @@ class UdpTarget: public Target, public UdpCommon return stat; } - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } bool Broken() override { return false; } - int GetSysSocket() const override { return m_sock; }; + SYSSOCKET GetSysSocket() const override { return m_sock; }; }; template struct Udp; diff --git a/apps/transmitmedia.hpp b/apps/transmitmedia.hpp index 527f005d9..191119ffe 100644 --- a/apps/transmitmedia.hpp +++ b/apps/transmitmedia.hpp @@ -16,7 +16,6 @@ #include #include "transmitbase.hpp" -#include // Needs access to CUDTException using namespace std; @@ -42,6 +41,7 @@ class SrtCommon string m_mode; string m_adapter; map m_options; // All other options, as provided in the URI + SRT_TRANSTYPE m_transtype = SRTT_LIVE; SRTSOCKET m_sock = SRT_INVALID_SOCK; SRTSOCKET m_bindsock = SRT_INVALID_SOCK; bool IsUsable() { SRT_SOCKSTATUS st = srt_getsockstate(m_sock); return st > SRTS_INIT && st < SRTS_BROKEN; } @@ -63,8 +63,8 @@ class SrtCommon void Error(string src); void Init(string host, int port, map par, bool dir_output); - virtual int ConfigurePost(SRTSOCKET sock); - virtual int ConfigurePre(SRTSOCKET sock); + virtual SRTSTATUS ConfigurePost(SRTSOCKET sock); + virtual SRTSTATUS ConfigurePre(SRTSOCKET sock); void OpenClient(string host, int port); void PrepareClient(); @@ -132,7 +132,7 @@ class SrtTarget: public Target, public SrtCommon SrtTarget() {} - int ConfigurePre(SRTSOCKET sock) override; + SRTSTATUS ConfigurePre(SRTSOCKET sock) override; int Write(const char* data, size_t size, int64_t src_time, ostream &out_stats = cout) override; bool IsOpen() override { return IsUsable(); } bool Broken() override { return IsBroken(); } diff --git a/apps/uriparser.cpp b/apps/uriparser.cpp index 5a5fdf2a1..c143cb9af 100644 --- a/apps/uriparser.cpp +++ b/apps/uriparser.cpp @@ -24,6 +24,7 @@ #endif using namespace std; +using namespace hvu; map g_types; @@ -64,37 +65,37 @@ string UriParser::makeUri() prefix = m_proto + "://"; } - std::ostringstream out; + ofmtbufstream out; - out << prefix << m_host; + out.print(prefix, m_host); if ((m_port == "" || m_port == "0") && m_expect == EXPECT_FILE) { // Do not add port } else { - out << ":" << m_port; + out.print(":"_V, m_port); } if (m_path != "") { if (m_path[0] != '/') - out << "/"; - out << m_path; + out.print("/"_V); + out.print(m_path); } if (!m_mapQuery.empty()) { - out << "?"; + out.print("?"_V); query_it i = m_mapQuery.begin(); for (;;) { - out << i->first << "=" << i->second; + out.print(i->first, "="_V, i->second); ++i; if (i == m_mapQuery.end()) break; - out << "&"; + out.print("&"_V); } } @@ -422,7 +423,7 @@ int main( int argc, char** argv ) for (string& s: args) { vector keyval; - Split(s, '=', back_inserter(keyval)); + srt::Split(s, '=', back_inserter(keyval)); if (keyval.size() < 2) keyval.push_back(""); parser[keyval[0]] = keyval[1]; diff --git a/apps/uriparser.hpp b/apps/uriparser.hpp index 78219750a..ea627501c 100644 --- a/apps/uriparser.hpp +++ b/apps/uriparser.hpp @@ -39,7 +39,7 @@ class UriParser // Some predefined types Type type() const; - typedef MapProxy ParamProxy; + typedef srt::MapProxy ParamProxy; // Operations public: diff --git a/apps/verbose.hpp b/apps/verbose.hpp index 56945bf2c..55cf23073 100644 --- a/apps/verbose.hpp +++ b/apps/verbose.hpp @@ -11,7 +11,7 @@ #ifndef INC_SRT_VERBOSE_HPP #define INC_SRT_VERBOSE_HPP -#include +#include "ofmt_iostream.h" #include "sync.h" namespace Verbose diff --git a/common/devel_util.h b/common/devel_util.h new file mode 100644 index 000000000..4e31af07e --- /dev/null +++ b/common/devel_util.h @@ -0,0 +1,118 @@ +/* + * 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. + *****************************************************************************/ + +// IMPORTANT!!! +// +// This is normally not a part of SRT source files. This is a developer utility +// that allows developers to perform a compile test on a version instrumented +// with type checks. To do that, you can do one of two things: +// +// - configure the compiling process with extra -DSRT_TEST_FORCED_CONSTANT=1 flag +// - unblock the commented out #define in srt.h file for that constant +// +// Note that there's no use of such a compiled code. This is done only so that +// the compiler can detect any misuses of the SRT symbolic type names and +// constants. + + +#include + +template +concept Streamable = requires(OS& os, T value) { + { os << value }; +}; + +template +struct IntWrapper +{ + INT v; + + IntWrapper() {} + explicit IntWrapper(INT val): v(val) {} + + bool operator==(const IntWrapper& x) const + { + return v == x.v; + } + + bool operator!=(const IntWrapper& x) const + { + return !(*this == x); + } + + explicit operator INT() const + { + return v; + } + + bool operator<(const IntWrapper& w) const + { + return v < w.v; + } + + template + requires Streamable + friend Str& operator<<(Str& out, const IntWrapper& x) + { + out << x.v; + return out; + } + + friend std::ostream& operator<<(std::ostream& out, const IntWrapper& x) + { + out << x.v; + return out; + } +}; + +template +struct IntWrapperLoose: IntWrapper +{ + typedef IntWrapper base_t; + explicit IntWrapperLoose(INT val): base_t(val) {} + + bool operator==(const IntWrapper& x) const + { + return this->v == x.v; + } + + friend bool operator==(const IntWrapper& x, const IntWrapperLoose& y) + { + return x.v == y.v; + } + + bool operator==(INT val) const + { + return this->v == val; + } + + friend bool operator==(INT val, const IntWrapperLoose& x) + { + return val == x.v; + } + + operator INT() const + { + return this->v; + } +}; + + +typedef IntWrapper SRTSOCKET; +typedef IntWrapper SRTSTATUS; +typedef IntWrapper SRTRUNSTATUS; +typedef IntWrapperLoose SRTSTATUS_LOOSE; + + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..088f3a234 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,54 @@ +# SRT Build Environment - Ubuntu 22.04 (Jammy) +# This Dockerfile can be used standalone or as part of a devcontainer setup + +FROM ubuntu:22.04 + +# Set non-interactive mode for apt +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies +RUN apt-get update && apt-get install -y \ + # Build essentials + build-essential \ + cmake \ + gcc \ + g++ \ + make \ + pkg-config \ + # SRT dependencies + tclsh \ + libssl-dev \ + # Testing and code coverage + gcovr \ + lcov \ + # Spell checking + python3 \ + python3-pip \ + # Git for source control + git \ + && rm -rf /var/lib/apt/lists/* + +# Install codespell using pip +RUN pip3 install --no-cache-dir codespell + +# Create a non-root user for development +ARG USERNAME=srt-dev +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && apt-get update \ + && apt-get install -y sudo \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME \ + && rm -rf /var/lib/apt/lists/* + +# Set the default user +USER $USERNAME + +# Set working directory +WORKDIR /srt_build_env + +# Default command +CMD ["/bin/bash"] diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 928fd6aaa..4f8a38739 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -19,7 +19,9 @@ | [srt_bind_acquire](#srt_bind_acquire) | Acquires a given UDP socket instead of creating one | | [srt_getsockstate](#srt_getsockstate) | Gets the current status of the socket | | [srt_getsndbuffer](#srt_getsndbuffer) | Retrieves information about the sender buffer | +| [srt_getmaxpayloadsize](#srt_getmaxpayloadsize) | Retrieves the information about the maximum payload size in a single packet | | [srt_close](#srt_close) | Closes the socket or group and frees all used resources | +| [srt_close_withreason](#srt_close_withreason) | Closes the socket or group and frees all used resources (with setting the reason code) | | | |

Connecting

@@ -28,7 +30,7 @@ |:------------------------------------------------- |:-------------------------------------------------------------------------------------------------------------- | | [srt_listen](#srt_listen) | Sets up the listening state on a socket | | [srt_accept](#srt_accept) | Accepts a connection; creates/returns a new socket or group ID | -| [srt_accept_bond](#srt_accept_bond) | Accepts a connection pending on any sockets passed in the `listeners` array
of `nlisteners` size | +| [srt_accept_bond](#srt_accept_bond) | Accepts a connection pending on any sockets passed in the `listeners` array
of `nlisteners` size | | [srt_listen_callback](#srt_listen_callback) | Installs/executes a callback hook on a socket created to handle the incoming connection
on a listening socket | | [srt_connect](#srt_connect) | Connects a socket or a group to a remote party with a specified address and port | | [srt_connect_bind](#srt_connect_bind) | Same as [`srt_bind`](#srt_bind) then [`srt_connect`](#srt_connect) if called with socket [`u`](#u) | @@ -149,30 +151,32 @@ Since SRT v1.5.0. | [srt_rejectreason_str](#srt_rejectreason_str) | Returns a constant string for the reason of the connection rejected, as per given code ID | | [srt_setrejectreason](#srt_setrejectreason) | Sets the rejection code on the socket | | [srt_getrejectreason](#srt_getrejectreason) | Provides a detailed reason for a failed connection attempt | +| [srt_close_getreason](#srt_close_getreason) | Provides a detailed reason for closing a socket | | | |

Rejection Reasons

-| *Rejection Reason* | *Since* | *Description* | -|:-------------------------------------------- |:--------- |:-------------------------------------------------------------------------------------------------------------- | -| [SRT_REJ_UNKNOWN](#SRT_REJ_UNKNOWN) | 1.3.4 | A fallback value for cases when there was no connection rejected | -| [SRT_REJ_SYSTEM](#SRT_REJ_SYSTEM) | 1.3.4 | A system function reported a failure | -| [SRT_REJ_PEER](#SRT_REJ_PEER) | 1.3.4 | The connection has been rejected by peer, but no further details are available | -| [SRT_REJ_RESOURCE](#SRT_REJ_RESOURCE) | 1.3.4 | A problem with resource allocation (usually memory) | -| [SRT_REJ_ROGUE](#SRT_REJ_ROGUE) | 1.3.4 | The data sent by one party to another cannot be properly interpreted | -| [SRT_REJ_BACKLOG](#SRT_REJ_BACKLOG) | 1.3.4 | The listener's backlog has exceeded | -| [SRT_REJ_IPE](#SRT_REJ_IPE) | 1.3.4 | Internal Program Error | -| [SRT_REJ_CLOSE](#SRT_REJ_CLOSE) | 1.3.4 | The listener socket received a request as it is being closed | -| [SRT_REJ_VERSION](#SRT_REJ_VERSION) | 1.3.4 | A party did not satisfy the minimum version requirement that had been set up for a connection | -| [SRT_REJ_RDVCOOKIE](#SRT_REJ_RDVCOOKIE) | 1.3.4 | Rendezvous cookie collision | -| [SRT_REJ_BADSECRET](#SRT_REJ_BADSECRET) | 1.3.4 | Both parties have defined a passphrase for connection and they differ | -| [SRT_REJ_UNSECURE](#SRT_REJ_UNSECURE) | 1.3.4 | Only one connection party has set up a password | -| [SRT_REJ_MESSAGEAPI](#SRT_REJ_MESSAGEAPI) | 1.3.4 | The value for [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on both connection parties | -| [SRT_REJ_FILTER](#SRT_REJ_FILTER) | 1.3.4 | The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option has been set differently on both connection parties | -| [SRT_REJ_GROUP](#SRT_REJ_GROUP) | 1.4.2 | The group type or some group settings are incompatible for both connection parties | -| [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | 1.4.2 | The connection wasn't rejected, but it timed out | -| [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | The connection was rejected due to an unsupported or mismatching encryption mode | -| | | | +| *Rejection Reason* | *Since* | *Description* | +|:-------------------------------------------- |:--------- |:---------------------------------------------------------------------------------------------------------------- | +| [SRT_REJ_UNKNOWN](#SRT_REJ_UNKNOWN) | 1.3.4 | A fallback value for cases when there was no connection rejected | +| [SRT_REJ_SYSTEM](#SRT_REJ_SYSTEM) | 1.3.4 | A system function reported a failure | +| [SRT_REJ_PEER](#SRT_REJ_PEER) | 1.3.4 | The connection has been rejected by peer, but no further details are available | +| [SRT_REJ_RESOURCE](#SRT_REJ_RESOURCE) | 1.3.4 | A problem with resource allocation (usually memory) | +| [SRT_REJ_ROGUE](#SRT_REJ_ROGUE) | 1.3.4 | The data sent by one party to another cannot be properly interpreted | +| [SRT_REJ_BACKLOG](#SRT_REJ_BACKLOG) | 1.3.4 | The listener's backlog has exceeded | +| [SRT_REJ_IPE](#SRT_REJ_IPE) | 1.3.4 | Internal Program Error | +| [SRT_REJ_CLOSE](#SRT_REJ_CLOSE) | 1.3.4 | The listener socket received a request as it is being closed | +| [SRT_REJ_VERSION](#SRT_REJ_VERSION) | 1.3.4 | A party did not satisfy the minimum version requirement that had been set up for a connection | +| [SRT_REJ_RDVCOOKIE](#SRT_REJ_RDVCOOKIE) | 1.3.4 | Rendezvous cookie collision | +| [SRT_REJ_BADSECRET](#SRT_REJ_BADSECRET) | 1.3.4 | Both parties have defined a passphrase for connection and they differ | +| [SRT_REJ_UNSECURE](#SRT_REJ_UNSECURE) | 1.3.4 | Only one connection party has set up a password | +| [SRT_REJ_MESSAGEAPI](#SRT_REJ_MESSAGEAPI) | 1.3.4 | The value for [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on the peer | +| [SRT_REJ_FILTER](#SRT_REJ_FILTER) | 1.3.4 | The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option is set differently on the peer | +| [SRT_REJ_GROUP](#SRT_REJ_GROUP) | 1.4.2 | The group type or some group settings are incompatible for both connection parties | +| [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | 1.4.2 | The connection wasn't rejected, but it timed out | +| [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | The connection was rejected due to an unsupported or mismatching encryption mode | +| [SRT_REJ_CONFIG](#SRT_REJ_CONFIG) | 1.6.0 | The connection was rejected because settings on both parties are in collision and cannot negotiate common values | +| | | | See the full list in [Rejection Reason Codes](./rejection-codes.md). @@ -224,6 +228,33 @@ See the full list in [Rejection Reason Codes](./rejection-codes.md). | | | +## Diagnostics and return types + +The SRT API functions usually report a status of the operation that they attempt to perform. +There are three general possibilities to report a success or failure, possibly with some +extra information: + +1. `SRTSTATUS` is usually an integer value with two possible variants: + * `SRT_STATUS_OK` (value: 0): the operation completed successfully + * `SRT_ERROR` (value: -1): the operation failed + +2. `SRTSOCKET` can be returned by some of the functions, which can be: + * A positive value greater than 0, which is a valid Socket ID value + * `SRT_SOCKID_CONNREQ` for a success report when a Socket ID needs not be returned + * `SRT_INVALID_SOCK` for a failure report + +3. A value of type `int` that should be a positive value or 0 in case of a success, +and the value equal to `SRT_ERROR` (that is, -1) in case of failure. + +In the below function description, functions returning `SRTSTATUS` will not +have the provided return value description, as it always matches the one above. +For all other types the function-specific return value description will be provided. + +If the function returns `SRT_ERROR`, `SRT_INVALID_SOCK` or a value equal to -1 +in case of returning an `int` value, additional error code can be obtained +through the [`srt_getlasterror`](#srt_getlasterror) call. Possible codes for a +particular function are listed in the **Errors** table. + ## Library Initialization @@ -233,7 +264,7 @@ See the full list in [Rejection Reason Codes](./rejection-codes.md). ### srt_startup ``` -int srt_startup(void); +SRTRUNSTATUS srt_startup(void); ``` This function shall be called at the start of an application that uses the SRT @@ -244,10 +275,10 @@ relying on this behavior is strongly discouraged. | Returns | | |:----------------------------- |:--------------------------------------------------------------- | -| 0 | Successfully run, or already started | -| 1 | This is the first startup, but the GC thread is already running | -| -1 | Failed | -| | | +| `SRT_RUN_OK` (0) | Successfully started | +| `SRT_RUN_ALREADY` (1) | The GC thread is already running or it was called once already | +| `SRT_RUN_ERROR` (-1) | Failed | +| | | | Errors | | |:----------------------------- |:--------------------------------------------------------------- | @@ -261,7 +292,7 @@ relying on this behavior is strongly discouraged. ### srt_cleanup ``` -int srt_cleanup(void); +SRTSTATUS srt_cleanup(void); ``` This function cleans up all global SRT resources and shall be called just before @@ -269,10 +300,8 @@ exiting the application that uses the SRT library. This cleanup function will st be called from the C++ global destructor, if not called by the application, although relying on this behavior is strongly discouraged. -| Returns | | -|:----------------------------- |:--------------------------------------------------------------- | -| 0 | A possibility to return other values is reserved for future use | -| | | +Currently this function can only return `SRT_STATUS_OK` and a possibility to return +`SRT_ERROR` is reserved for future use. **IMPORTANT**: Note that the startup/cleanup calls have an instance counter. This means that if you call [`srt_startup`](#srt_startup) multiple times, you need to call the @@ -294,7 +323,9 @@ This means that if you call [`srt_startup`](#srt_startup) multiple times, you ne * [srt_bind_acquire](#srt_bind_acquire) * [srt_getsockstate](#srt_getsockstate) * [srt_getsndbuffer](#srt_getsndbuffer) +* [srt_getmaxpayloadsize](#srt_getmaxpayloadsize) * [srt_close](#srt_close) +* [srt_close_withreason](#srt_close_withreason) ### srt_socket @@ -337,11 +368,11 @@ Note that socket IDs always have the `SRTGROUP_MASK` bit clear. |:----------------------------- |:------------------------------------------------------- | | Socket ID | A valid socket ID on success | | `SRT_INVALID_SOCK` | (`-1`) on error | -| | | +| | | -| Errors | | -|:----------------------------- |:------------------------------------------------------------ | -| [`SRT_ENOBUF`](#srt_enobuf) | Not enough memory to allocate required resources . | +| Errors | | +|:----------------------------- |:-------------------------------------------------- | +| [`SRT_ENOBUF`](#srt_enobuf) | Not enough memory to allocate required resources | | | | **NOTE:** This is probably a design flaw (:warning:   **BUG?**). Usually underlying system @@ -355,7 +386,7 @@ errors are reported by [`SRT_ECONNSETUP`](#srt_econnsetup). ### srt_bind ``` -int srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRTSTATUS srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); ``` Binds a socket to a local address and port. Binding specifies the local network @@ -363,10 +394,11 @@ interface and the UDP port number to be used for the socket. When the local address is a wildcard (`INADDR_ANY` for IPv4 or `in6addr_any` for IPv6), then it's bound to all interfaces. -**IMPORTANT**: When you bind an IPv6 wildcard address, note that the -`SRTO_IPV6ONLY` option must be set on the socket explicitly to 1 or 0 prior to -calling this function. See -[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) for more details. +**IMPORTANT**: In the case of IPv6 wildcard address, this may mean either "all +IPv6 interfaces" or "all IPv4 and IPv6 interfaces", depending on the value of +[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) option. Therefore this +option must be explicitly set to 0 or 1 prior to calling this function, otherwise +(when the default -1 value of this option is left) this function will fail. Binding is necessary for every socket to be used for communication. If the socket is to be used to initiate a connection to a listener socket, which can be done, @@ -413,7 +445,7 @@ binding ("shared binding") is possessed by an SRT socket created in the same application, and: * Its binding address and UDP-related socket options match the socket to be bound. -* Its [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to *true* (default). +* Its [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDR) is set to *true* (default). If none of the free, side and shared binding options is currently possible, this function will fail. If the socket blocking the requested endpoint is an SRT @@ -421,14 +453,15 @@ socket in the current application, it will report the `SRT_EBINDCONFLICT` error, while if it was another socket in the system, or the problem was in the system in general, it will report `SRT_ESOCKFAIL`. Here is the table that shows possible situations: -| Requested binding | vs. Existing bindings... | | | | | -|---------------------|------------------------------|-----------|-----------------------------|---------------|---------------| -| | A.B.C.D | 0.0.0.0 | ::X | :: / V6ONLY=1 | :: / V6ONLY=0 | -| 1.2.3.4 | 1.2.3.4 shareable, else free | blocked | free | free | blocked | -| 0.0.0.0 | blocked | shareable | free | free | blocked | -| 8080::1 | free | free | 8080::1 sharable, else free | blocked | blocked | -| :: / V6ONLY=1 | free | free | blocked | sharable | blocked | -| :: / V6ONLY=0 | blocked | blocked | blocked | blocked | sharable | +| Requested binding | vs. Existing bindings... | | | | | +|---------------------|---------------------------------|-----------|-----------------------------|---------------|---------------| +| | A.B.C.D (explicit IPv4 addr.) | 0.0.0.0 | ::X (explicit IPv6 addr.) | :: / V6ONLY=1 | :: / V6ONLY=0 | +|---------------------|---------------------------------|-----------|-----------------------------|---------------|---------------| +| 1.2.3.4 | shareable if 1.2.3.4, else free | blocked | free | free | blocked | +| 0.0.0.0 | blocked | shareable | free | free | blocked | +| 8080::1 | free | free | 8080::1 sharable, else free | blocked | blocked | +| :: / V6ONLY=1 | free | free | blocked | sharable | blocked | +| :: / V6ONLY=0 | blocked | blocked | blocked | blocked | sharable | Where: @@ -438,7 +471,7 @@ Where: * shareable: This binding can be shared with the requested binding if it's compatible. -* (ADDRESS) shareable, else free: this binding is shareable if the existing binding address is +* shareable if (ADDRESS), else free: this binding is shareable if the existing binding address is equal to the requested ADDRESS. Otherwise it's free. If the binding is shareable, then the operation will succeed if the socket that currently @@ -457,13 +490,9 @@ or set the appropriate source address using **IMPORTANT information about IPv6**: If you are going to bind to the `in6addr_any` IPv6 wildcard address (known as `::`), the `SRTO_IPV6ONLY` option must be first set explicitly to 0 or 1, otherwise the binding -will fail. In all other cases this option is meaningless. See `SRTO_IPV6ONLY` -option for more information. - -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) on error, otherwise 0 | -| | | +will fail. In all other cases this option is meaningless. See +[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) option for more +information. | Errors | | |:---------------------------------------- |:-------------------------------------------------------------------- | @@ -483,7 +512,7 @@ option for more information. ### srt_bind_acquire ``` -int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock); +SRTSTATUS srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock); ``` A version of [`srt_bind`](#srt_bind) that acquires a given UDP socket instead of creating one. @@ -526,7 +555,7 @@ Gets the current status of the socket. Possible states are: ### srt_getsndbuffer ``` -int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); +SRTSTATUS srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); ``` Retrieves information about the sender buffer. @@ -545,20 +574,73 @@ socket needs to be closed asynchronously. --- -### srt_close +### srt_getmaxpayloadsize ``` -int srt_close(SRTSOCKET u); +int srt_getmaxpayloadsize(SRTSOCKET u); +``` + +Returns the maximum number of bytes that fit in a single packet. Useful only in +live mode (when `SRTO_TSBPDMODE` is true). The socket must be bound (see +[srt_bind](#srt_bind)) or connected (see [srt_connect](#srt_connect)) +to use this function. Note that in case when the socket is bound to an IPv6 +wildcard address and it is dual-stack (`SRTO_IPV6ONLY` is set to false), this +function returns the correct value only if the socket is connected, otherwise +it will return the value always as if the connection was made from an IPv6 peer +(including when you call it on a listening socket). + +This function is only useful for the application to check if it is able to use +a payload of certain size in the live mode, or after connection, if the application +can send payloads of certain size. This is useful only in assertions, as if the +[`SRTO_PAYLOADSIZE`](API_socket-options.md#SRTO_PAYLOADSIZE) option is to be +set to a non-default value (for which the one returned by this function is the +maximum value), this option should be modified before connection and on both +parties, regarding the settings applied on the socket. + +The returned value is the maximum number of bytes that can be put in a single +packet regarding: + +* The current MTU size (`SRTO_MSS`) +* The IP version (IPv4 or IPv6) +* The `SRTO_CRYPTOMODE` setting (bytes reserved for AEAD authentication tag) +* The `SRTO_PACKETFILTER` setting (bytes reserved for extra field in a FEC control packet) + +With default options this value should be 1456 for IPv4 and 1444 for IPv6. + + +| Returns | | +|:----------------------------- |:------------------------------------------------- | +| The maximum payload size (>0) | If succeeded | +| `SRT_ERROR` | Usage error | +| | | + +| Errors | | +|:--------------------------------------- |:----------------------------------------------- | +| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | +| [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) | Socket [`u`](#u) is not bound | +| | | + + + +[:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) + +--- + +### srt_close, srt_close_withreason + +``` +SRTSTATUS srt_close(SRTSOCKET u); +SRTSTATUS srt_close_withreason(SRTSOCKET u, int reason); ``` Closes the socket or group and frees all used resources. Note that underlying UDP sockets may be shared between sockets, so these are freed only with the last user closed. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | +**Arguments**: + +* `u`: Socket or group to close +* `reason`: Reason code for closing. You should use numbers from `SRT_CLSC_USER` up. | Errors | | |:------------------------------- |:----------------------------------------------- | @@ -587,7 +669,7 @@ last user closed. ### srt_listen ``` -int srt_listen(SRTSOCKET u, int backlog); +SRTSTATUS srt_listen(SRTSOCKET u, int backlog); ``` This sets up the listening state on a socket with a backlog setting that @@ -602,11 +684,6 @@ be called before [`srt_accept`](#srt_accept) can happen * [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) option allows the listener socket to accept group connections -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0. | -| | | - | Errors | | |:--------------------------------------- |:-------------------------------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | Value of `backlog` is 0 or negative. | @@ -629,41 +706,109 @@ the listener socket to accept group connections SRTSOCKET srt_accept(SRTSOCKET lsn, struct sockaddr* addr, int* addrlen); ``` -Accepts a pending connection, then creates and returns a new socket or -group ID that handles this connection. The group and socket can be -distinguished by checking the `SRTGROUP_MASK` bit on the returned ID. +Extracts the first connection request on the queue of pending connections for +the listening socket, `lsn`, then creates and returns a new socket or group ID +that handles this connection. The group and socket can be distinguished by +checking the `SRTGROUP_MASK` bit on the returned ID. Note that by default group +connections will be rejected - this feature can be only enabled on demand (see +below). -* `lsn`: the listener socket previously configured by [`srt_listen`](#srt_listen) -* `addr`: the IP address and port specification for the remote party +* `lsn`: the listening socket +* `addr`: a location to store the remote IP address and port for the connection * `addrlen`: INPUT: size of `addr` pointed object. OUTPUT: real size of the returned object -**NOTE:** `addr` is allowed to be NULL, in which case it's understood that the -application is not interested in the address from which the connection originated. -Otherwise `addr` should specify an object into which the address will be written, -and `addrlen` must also specify a variable to contain the object size. Note also -that in the case of group connection only the initial connection that -establishes the group connection is returned, together with its address. As -member connections are added or broken within the group, you can obtain this -information through [`srt_group_data`](#srt_group_data) or the data filled by -[`srt_sendmsg2`](#srt_sendmsg) and [`srt_recvmsg2`](#srt_recvmsg2). - -If the `lsn` listener socket is configured for blocking mode -([`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) set to true, default), -the call will block until the incoming connection is ready. Otherwise, the -call always returns immediately. The `SRT_EPOLL_IN` epoll event should be -checked on the `lsn` socket prior to calling this function in that case. - -If the pending connection is a group connection (initiated on the peer side by -calling the connection function using a group ID, and permitted on the listener -socket by the [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) -flag), then the value returned is a group ID. This function then creates a new -group, as well as a new socket for this connection, that will be added to the -group. Once the group is created this way, further connections within the same -group, as well as sockets for them, will be created in the background. The -[`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) event is raised on the `lsn` socket when -a new background connection is attached to the group, although it's usually for -internal use only. +General requirements for a parameter correctness: + +* `lsn` must be first [bound](#srt_bind) and [listening](#srt_listen) + +* `addr` may be NULL, or otherwise it must be a pointer to an object +that can be treated as an instance of `sockaddr_in` or `sockaddr_in6` + +* `addrlen` should be a pointer to a variable set to the size of the object +specified in `addr`, if `addr` is not NULL. Otherwise it's ignored. + +If `addr` is not NULL, the information about the source IP address and +port of the peer will be written into this object. Note that whichever +type of object is expected here (`sockaddr_in` or `sockaddr_in6`), it +depends on the address type used in the `srt_bind` call for `lsn`. +If unsure in a particular situation, it is recommended that you use +`sockaddr_storage` or `srt::sockaddr_any`. + +If the `lsn` listener socket is in the blocking mode (if +[`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) is set to true, +which is default), the call will block until the incoming connection is ready +for extraction. Otherwise, the call always returns immediately, possibly with +failure, if there was no pending connection waiting on the listening socket +`lsn`. + +The listener socket can be checked for any pending connections prior to calling +`srt_accept` by checking the `SRT_EPOLL_ACCEPT` epoll event (which is an alias +to `SRT_EPOLL_IN`). This event might be spurious in certain cases though, for +example, when the connection has been closed by the peer or broken before the +application extracts it. The call to `srt_accept` would then still fail in +such a case. + +In order to allow the listening socket `lsn` to accept a group connection, +the [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) socket option +for the listening socket must be set to 1. Note that single socket connections +can still be reported to that socket. The application can distinguish the socket +and group connection by checking the `SRTGROUP_MASK` bit on the returned +successful value. There are some important differences to single socket +connections: + +1. Accepting a group connection can be done only once per connection, even +though particular member connections can get broken or established while +the group is connected. The actual connection reporter (listener) is a socket, +like before, but once you call `srt_accept` and receive this group ID, it is +the group considered connected, and any member connections of the same group +will be handled in the background. + +2. If a group was extracted from the `srt_accept` call, the address reported in +`addr` parameter is still the address of the connection that has triggered the +group connection extraction. The information about all member links in the +group at the moment can be obtained at any time through +[`srt_group_data`](#srt_group_data) or the data filled by +[`srt_sendmsg2`](#srt_sendmsg2) and [`srt_recvmsg2`](#srt_recvmsg2) +in the [`SRT_MSGCTRL`](#SRT_MSGCTRL) structure. + +3. Listening sockets are not bound to groups anyhow. You can allow multiple +listening sockets to accept group connections and the connection extracted +from the listener, if it is declared to be a group member, will join its +group, no matter which of the listening sockets has received the connection +request. This feature is prone to more tricky rules, however: + + * If you use multiple listener sockets, all of them in blocking mode, + allowed for group connections, and receiving connection requests for + the same group at the moment, and you run one thread per `srt_accept` + call, it is undefined, which of them will extract the group ID + for the connection, but still only one will, while the others will + continue blocking. If you want to use only one thread for accepting + connections from potentially multiple listening sockets in the blocking + mode, you should use [`srt_accept_bond`](#srt_accept_bond) instead. + Note though that this function is actually a wrapper that changes locally + to the nonblocking mode on all these listeners and uses epoll internally. + + * If at the moment multiple listener sockets have received connection + request and you query them all for readiness epoll flags (by calling + an epoll waiting function), all of them will get the `SRT_EPOLL_ACCEPT` + flag set, but still only one of them will return the group ID from the + `srt_accept` call. After this call, from all listener sockets in the + whole application the `SRT_EPOLL_ACCEPT` flag, that was set by the reason + of a pending connection for the same group, will be withdrawn (that is, + it will be cleared if there are no other pending connections). This is + then yet another situation when this flag can be spurious. + +4. If you query a listening socket for epoll flags after the `srt_accept` +function has once returned the group ID, the listening sockets that have +received new member connection requests within that group will report only the +[`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) flag. This flag is edge-triggered-only +because there is no operation you can perform in response in order to clear +this flag. This flag is mostly used internally and the application may use it +if it would like to trigger updating the current group information due to +having one newly added member connection. + + | Returns | | |:----------------------------- |:----------------------------------------------------------------------- | @@ -673,7 +818,7 @@ internal use only. | Errors | | |:--------------------------------- |:----------------------------------------------------------------------- | -| [`SRT_EINVPARAM`](#srt_einvparam) | NULL specified as `addrlen`, when `addr` is not NULL | +| [`SRT_EINVPARAM`](#srt_einvparam) | Invalid `addr` or `addrlen` (see requirements in the beginning) | | [`SRT_EINVSOCK`](#srt_einvsock) | `lsn` designates no valid socket ID. | | [`SRT_ENOLISTEN`](#srt_enolisten) | `lsn` is not set up as a listener ([`srt_listen`](#srt_listen) not called). | | [`SRT_EASYNCRCV`](#srt_easyncrcv) | No connection reported so far. This error is reported only in the non-blocking mode | @@ -723,7 +868,7 @@ calling this function. | Returns | | |:----------------------------- |:---------------------------------------------------------------------- | -| SRT socket
group ID | On success, a valid SRT socket or group ID to be used for transmission | +| SRT socket/group ID | On success, a valid SRT socket or group ID to be used for transmission | | `SRT_INVALID_SOCK` | (-1) on failure | | | | @@ -743,7 +888,7 @@ calling this function. ### srt_listen_callback ``` -int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); +SRTSTATUS srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); ``` This call installs a callback hook, which will be executed on a socket that is @@ -760,12 +905,6 @@ i.e. before `srt_listen` is called. * `hook_fn`: The callback hook function pointer (or NULL to remove the callback) * `hook_opaque`: The pointer value that will be passed to the callback function -| Returns | | -|:----------------------------- |:---------------------------------------------------------- | -| 0 | Successful | -| -1 | Error | -| | | - | Errors | | |:--------------------------------- |:----------------------------------------- | | [`SRT_ECONNSOCK`](#srt_econnsock) | It can't be modified in a connected socket| @@ -831,7 +970,7 @@ database you have to check against the data received in `streamid` or `peeraddr` ### srt_connect ``` -int srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRTSOCKET srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); ``` Connects a socket or a group to a remote party with a specified address and port. @@ -849,8 +988,8 @@ Connects a socket or a group to a remote party with a specified address and port or binding and connection can be done in one function ([`srt_connect_bind`](#srt_connect_bind)), such that it uses a predefined network interface or local outgoing port. This is optional in the case of a caller-listener arrangement, but obligatory for a rendezvous arrangement. -If not used, the binding will be done automatically to `INADDR_ANY` (which binds on all -interfaces) and port 0 (which makes the system assign the port automatically). +If not used, the binding will be done automatically to a wildcard address and port 0. See +[`srt_bind](#srt_bind) for details. 2. This function is used for both connecting to the listening peer in a caller-listener arrangement, and calling the peer in rendezvous mode. For the latter, the @@ -866,16 +1005,21 @@ automatically for every call of this function. mode, you might want to use [`srt_connect_group`](#srt_connect_group) instead. This function also allows you to use additional settings, available only for groups. +The returned value is a socket ID value. When `u` is a socket ID, the returned +is a special value `SRT_SOCKID_CONNREQ`. When `u` is a group ID, the returned +value is the socket ID of the newly created member for the requested link. In +the case of failure, `SRT_INVALID_SOCK` is returned. + | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error | -| 0 | In case when used for [`u`](#u) socket | +| `SRT_INVALID_SOCK` | (-1) in case of error | +| `SRT_SOCKID_CONNREQ` | In case when used for [`u`](#u) socket | | Socket ID | Created for connection for [`u`](#u) group | | | | | Errors | | |:------------------------------------- |:----------------------------------------------------------- | -| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | +| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket or group ID | | [`SRT_ERDVUNBOUND`](#srt_erdvunbound) | Socket [`u`](#u) is in rendezvous mode, but it wasn't bound (see note #2) | | [`SRT_ECONNSOCK`](#srt_econnsock) | Socket [`u`](#u) is already connected | | [`SRT_ECONNREJ`](#srt_econnrej) | Connection has been rejected | @@ -900,7 +1044,9 @@ In the case of "late" failures you can additionally call information. Note that in blocking mode only for the `SRT_ECONNREJ` error this function may return any additional information. In non-blocking mode a detailed "late" failure cannot be distinguished, and therefore it -can also be obtained from this function. +can also be obtained from this function. Note that the connection timeout +error can be also recognized through this call, even though it is reported +by `SRT_ENOSERVER` in the blocking mode. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -910,7 +1056,7 @@ can also be obtained from this function. ### srt_connect_bind ``` -int srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, +SRTSOCKET srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int len); ``` @@ -928,8 +1074,8 @@ first on the automatically created socket for the connection. | Returns | | |:----------------------------- |:-------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error | -| 0 | In case when used for [`u`](#u) socket | +| `SRT_INVALID_SOCK` | (-1) in case of error | +| `SRT_SOCKID_CONNREQ` | In case when used for [`u`](#u) socket | | Socket ID | Created for connection for [`u`](#u) group | | | | @@ -958,7 +1104,7 @@ different families (that is, both `source` and `target` must be `AF_INET` or ### srt_connect_debug ``` -int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); +SRTSOCKET srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); ``` This function is for developers only and can be used for testing. It does the @@ -973,7 +1119,7 @@ is generated randomly. ### srt_rendezvous ``` -int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, +SRTSTATUS srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); ``` Performs a rendezvous connection. This is a shortcut for doing bind locally, @@ -986,11 +1132,6 @@ to true, and doing [`srt_connect`](#srt_connect). * `local_name`: specifies the local network interface and port to bind * `remote_name`: specifies the remote party's IP address and port -| Returns | | -|:----------------------------- |:-------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | - | Errors | | |:------------------------------------- |:-------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket passed as [`u`](#u) designates no valid socket | @@ -1012,7 +1153,7 @@ allowed (that is, both `local_name` and `remote_name` must be `AF_INET` or `AF_I ### srt_connect_callback ``` -int srt_connect_callback(SRTSOCKET u, srt_connect_callback_fn* hook_fn, void* hook_opaque); +SRTSTATUS srt_connect_callback(SRTSOCKET u, srt_connect_callback_fn* hook_fn, void* hook_opaque); ``` This call installs a callback hook, which will be executed on a given [`u`](#u) @@ -1044,16 +1185,10 @@ connection failures. * `hook_opaque`: The pointer value that will be passed to the callback function -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Successful | -| -1 | Error | -| | | - -| Errors | | -|:---------------------------------- |:------------------------------------------| -| [`SRT_ECONNSOCK`](#srt_econnsock) | It can't be modified in a connected socket| -| | | +| Errors | | +|:---------------------------------- |:-------------------------------------------| +| [`SRT_ECONNSOCK`](#srt_econnsock) | It can't be modified in a connected socket | +| | | The callback function signature has the following type definition: @@ -1125,7 +1260,7 @@ where: * `token`: An integer value unique for every connection, or -1 if unused The `srt_prepare_endpoint` sets these fields to default values. After that -you can change the value of `weight` and `config` and `token` fields. The +you can change the value of `weight`, `config` and `token` fields. The `weight` parameter's meaning is dependent on the group type: * BROADCAST: not used @@ -1175,6 +1310,28 @@ where: * `result`: result of the operation (if this operation recently updated this structure) * `token`: A token value set for that connection (see [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG)) +The weight is set to 0 by default by `srt_prepare_endpoint()` - you can set +it to a different value afterwards. The meaning of weight depends on the group +type: + +1. Backup groups: in this case it defines the link priority. The default 0 +value is the lowest priority and greater values declare higher priorities. The +priority for the backup groups determines which link is activated first when +the currently active link is unstable, and which should keep transmitting when +multiple active links are currently stable, or when a new link becomes connected. + +2. Balancing groups with "fixed" algorithm: in this case it defines the +desired link load share. You can think of it as a percentage of link load, +but indeed a load percentage is defined as this weight value divided by a sum +of all weight values from all member links. Note however that the sum is +calculated out of all links that have been successfully connected. The +default 0 is also a special value that defines an "equalized" load share +(it's set to the arithmetic average of the weights from all links). + +The `SRT_SOCKGROUPDATA` structure is used in multiple purposes: + +* Prepare data for connection +* Getting the current member status [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -1269,10 +1426,12 @@ Retrieves the group SRT socket ID that corresponds to the member socket ID `memb | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| `SRTSOCKET` | Corresponding group SRT socket ID of the member socket. | +| `SRTSOCKET` | Corresponding group SRT socket ID of the `member` socket. | | `SRT_INVALID_SOCK` | The socket doesn't exist, it is not a member of any group, or bonding API is disabled. | | | | +In the case of `SRT_INVALID_SOCK`, the error is set to `SRT_EINVPARAM`. + [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -1281,7 +1440,7 @@ Retrieves the group SRT socket ID that corresponds to the member socket ID `memb #### srt_group_data ``` -int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* inoutlen); +SRTSTATUS srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* inoutlen); ``` **Arguments**: @@ -1292,23 +1451,26 @@ int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* in and is set to the filled array's size This function obtains the current member state of the group specified in -`socketgroup`. The `output` should point to an array large enough to hold all -the elements. The `inoutlen` should point to a variable initially set to the size -of the `output` array. The current number of members will be written back to `inoutlen`. +`socketgroup`. -If the size of the `output` array is enough for the current number of members, -the `output` array will be filled with group data and the function will return -the number of elements filled. Otherwise the array will not be filled and -`SRT_ERROR` will be returned. +The `inoutlen` should point to a variable initially set to the size +of the `output` array. The current number of members will be written back to +the variable specified in `inoutlen`. This parameter cannot be NULL. -This function can be used to get the group size by setting `output` to `NULL`, -and providing `socketgroup` and `inoutlen`. +If `output` is specified and the size of the array is at least equal to the +number of group members, the `output` array will be filled with group data. -| Returns | | -|:----------------------------- |:-------------------------------------------------- | -| # of elements | The number of data elements filled, on success | -| -1 | Error | -| | | +If `output` is NULL then the function will only retrieve the number of elements +in `inoutlen`. + +This call will fail and return `SRT_ERROR` if: + +* The `socketgroup` parameter is invalid + +* The `inoutlen` parameter is NULL + +* The size specified in a variable passed via `inoutlen` is less than the number +of group members | Errors | | @@ -1318,13 +1480,13 @@ and providing `socketgroup` and `inoutlen`. | | | -| in:output | in:inoutlen | returns | out:output | out:inoutlen | Error | -|:---------:|:--------------:|:------------:|:----------:|:------------:|:---------------------------------:| -| NULL | NULL | -1 | NULL | NULL | [`SRT_EINVPARAM`](#srt_einvparam) | -| NULL | ptr | 0 | NULL | group.size() | ✖️ | -| ptr | NULL | -1 | ✖️ | NULL | [`SRT_EINVPARAM`](#srt_einvparam) | -| ptr | ≥ group.size | group.size() | group.data | group.size | ✖️ | -| ptr | < group.size | -1 | ✖️ | group.size | [`SRT_ELARGEMSG`](#srt_elargemsg) | +| in:output | in:inoutlen | returns | out:output | out:inoutlen | Error | +|:---------:|:--------------:|:---------------:|:----------:|:------------:|:---------------------------------:| +| ptr | ≥ group.size | `SRT_STATUS_OK` | group.data | group.size() | ✖️ | +| NULL | ptr | `SRT_STATUS_OK` | (unused) | group.size() | ✖️ | +| NULL | NULL | `SRT_ERROR` | (unused) | (not filled) | [`SRT_EINVPARAM`](#srt_einvparam) | +| ptr | NULL | `SRT_ERROR` | (unused) | (not filled) | [`SRT_EINVPARAM`](#srt_einvparam) | +| ptr | < group.size | `SRT_ERROR` | (unused) | group.size() | [`SRT_ELARGEMSG`](#srt_elargemsg) | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -1334,14 +1496,14 @@ and providing `socketgroup` and `inoutlen`. #### srt_connect_group ``` -int srt_connect_group(SRTSOCKET group, - SRT_SOCKGROUPCONFIG name [], int arraysize); +SRTSOCKET srt_connect_group(SRTSOCKET group, + SRT_SOCKGROUPCONFIG links [], int arraysize); ``` This function does almost the same as calling [`srt_connect`](#srt_connect) or [`srt_connect_bind`](#srt_connect_bind) (when the source was specified for [`srt_prepare_endpoint`](#srt_prepare_endpoint)) in a loop for every item specified -in the `name` array. However if blocking mode is being used, the first call to +in the `links` array. However if blocking mode is being used, the first call to [`srt_connect`](#srt_connect) would block until the connection is established, whereas this function blocks until any of the specified connections is established. @@ -1350,21 +1512,21 @@ option), there's no difference, except that the [`SRT_SOCKGROUPCONFIG`](#SRT_SOC structure allows adding extra configuration data used by groups. Note also that this function accepts only groups, not sockets. -The elements of the `name` array need to be prepared with the use of the +The elements of the `links` array need to be prepared with the use of the [`srt_prepare_endpoint`](#srt_prepare_endpoint) function. Note that it is **NOT** required that every target address specified is of the same family. Return value and errors in this function are the same as in [`srt_connect`](#srt_connect), although this function reports success when at least one connection has succeeded. If none has succeeded, this function reports an [`SRT_ECONNLOST`](#srt_econnlost) -error. Particular connection states can be obtained from the `name` -array upon return from the [`errorcode`](#error-codes) field. +error. Particular connection states can be obtained from the `links` array upon +return from the [`errorcode`](#error-codes) field. The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the following meaning: **Input**: -* `id`: unused, should be -1 (default when created by [`srt_prepare_endpoint`](#srt_prepare_endpoint)) +* `id`: unused, should be `SRT_INVALID_SOCK` (default when created by [`srt_prepare_endpoint`](#srt_prepare_endpoint)) * `srcaddr`: address to bind before connecting, if specified (see below for details) * `peeraddr`: target address to connect * `weight`: weight value to be set on the link @@ -1374,7 +1536,7 @@ The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the f **Output**: -* `id`: The socket created for that connection (-1 if failed to create) +* `id`: The socket created for that connection (`SRT_INVALID_SOCK` if failed to create) * `srcaddr`: unchanged * `peeraddr`: unchanged * `weight`: unchanged @@ -1384,8 +1546,8 @@ The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the f | Returns | | |:----------------------------- |:-------------------------------------------------- | -| `SRT_SOCKET` | The socket ID of the first connected member. | -| -1 | Error | +| Socket ID | The socket ID of the first connected member. | +| `SRT_INVALID_SOCK` | Error | | | | @@ -1396,7 +1558,7 @@ The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the f | | | The procedure of connecting for every connection definition specified -in the `name` array is performed the following way: +in the `links` array is performed the following way: 1. The socket for this connection is first created @@ -1423,7 +1585,8 @@ then, and for which the connection attempt has at least successfully started, remain group members, although the function will return immediately with an error status (that is, without waiting for the first successful connection). If your application wants to do any partial recovery from this situation, it can -only use the epoll mechanism to wait for readiness. +only check the current member status via [`srt_group_data`](#srt_group_data) +and wait for group's write readiness (`SRT_EPOLL_OUT`) by using epoll. 2. In any other case, if an error occurs at any stage of the above process, the processing is interrupted for this very array item only, the socket used for it @@ -1431,16 +1594,18 @@ is immediately closed, and the processing of the next elements continues. In the of a connection process, it also passes two stages - parameter check and the process itself. Failure at the parameter check breaks this process, while if the check passes, this item is considered correctly processed, even if the connection -attempt is going to fail later. If this function is called in blocking mode, -it then blocks until at least one connection reports success, or if all of them -fail. The status of connections that continue in the background after this function -exits can then be checked by [`srt_group_data`](#srt_group_data). +attempt is going to fail later. + +If this function is called in blocking mode, it then blocks until at least one +connection reports success, or if all of them fail. The status of connections +that continue in the background after this function exits can then be checked +by [`srt_group_data`](#srt_group_data). As member socket connections are running in the background, for determining if a particular connection has succeeded or failed it is recommended to use [`srt_connect_callback`](#srt_connect_callback). In this case the `token` callback function parameter will be the same as the `token` value used -for the particular item in the `name` connection table. +for the particular item in the `links` connection array. The `token` value doesn't have any limitations except that the -1 value is a "trap representation", that is, when set on input it will make the internals @@ -1542,7 +1707,7 @@ Deletes the configuration object. #### srt_config_add ``` -int srt_config_add(SRT_SOCKOPT_CONFIG* c, SRT_SOCKOPT opt, void* val, int len); +SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG* c, SRT_SOCKOPT opt, void* val, int len); ``` Adds a configuration option to the configuration object. @@ -1572,12 +1737,6 @@ The following options are allowed to be set on the member socket: * [`SRTO_UDP_SNDBUF`](API-socket-options.md#SRTO_UDP_SNDBUF): UDP sender buffer, if this link has a big flight window -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:---------------------------------- |:--------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | This option is not allowed to be set on a socket being a group member. Or if bonding API is disabled. | @@ -1603,15 +1762,11 @@ The following options are allowed to be set on the member socket: ### srt_getpeername ``` -int srt_getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRTSTATUS srt_getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); ``` Retrieves the remote address to which the socket is connected. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | | Errors | | |:------------------------------- |:------------------------------------------------------------------------ | @@ -1626,7 +1781,7 @@ Retrieves the remote address to which the socket is connected. ### srt_getsockname ``` -int srt_getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRTSTATUS srt_getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); ``` Extracts the address to which the socket was bound. Although you should know @@ -1635,10 +1790,6 @@ useful for extracting the local outgoing port number when it was specified as 0 with binding for system autoselection. With this function you can extract the port number after it has been autoselected. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | | Errors | | |:------------------------------- |:---------------------------------------------- | @@ -1667,8 +1818,8 @@ if (res < 0) { ### srt_getsockflag ```c++ -int srt_getsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, void* optval, int* optlen); -int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); +SRTSTATUS srt_getsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, void* optval, int* optlen); +SRTSTATUS srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); ``` Gets the value of the given socket option (from a socket or a group). @@ -1686,11 +1837,6 @@ For most options, it will be the size of an integer. Some options, however, use The application is responsible for allocating sufficient memory space as defined and pointed to by `optval`. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | - | Errors | | |:-------------------------------- |:---------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | @@ -1705,8 +1851,8 @@ The application is responsible for allocating sufficient memory space as defined ### srt_setsockflag ```c++ -int srt_setsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, const void* optval, int optlen); -int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); +SRTSTATUS srt_setsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, const void* optval, int optlen); +SRTSTATUS srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); ``` Sets a value for a socket option in the socket or group. @@ -1723,11 +1869,6 @@ Please note that some of the options can only be set on sockets or only on groups, although most of the options can be set on the groups so that they are then derived by the member sockets. -| Returns | | -|:----------------------------- |:----------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | - | Errors | | |:----------------------------------- |:--------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | @@ -1736,7 +1877,7 @@ are then derived by the member sockets. | [`SRT_ECONNSOCK`](#srt_econnsock) | Tried to set an option with PRE_BIND or PRE restriction on a socket in connecting/listening/connected state. | | | | -**NOTE*: Various other errors may result from problems when setting a +**NOTE**: Various other errors may result from problems when setting a specific option (see option description in [API-socket-options.md](./API-socket-options.md) for details). @@ -1751,7 +1892,7 @@ uint32_t srt_getversion(); ``` Get SRT version value. The version format in hex is 0xXXYYZZ for x.y.z in human -readable form, where x = ("%d", (version>>16) & 0xff), etc. +readable form. E.g. 0x012033 means version 1.20.33. | Returns | | |:----------------------------- |:--------------------------------------------------------- | @@ -1844,6 +1985,15 @@ the array; otherwise both fields are updated to reflect the current connection s of the group. For details, see the [SRT Connection Bonding: Quick Start](../features/bonding-intro.md) and [SRT Connection Bonding: Socket Groups](../features/socket-groups.md) documents. +For more information about `SRT_SOCKGROUPDATA` and obtaining the group +data, please refer to [srt_group_data](#srt_group_data). Note that the +group data filling by `srt_sendmsg2` and `srt_recvmsg2` calls differs in one +aspect to `srt_group_data`: member sockets that were found broken after the +operation will appear in the group data with `SRTS_BROKEN` state once after the +operation was done, although the sockets assigned to these members are already +closed and they are removed as members already. In case of `srt_group_data` +they will not appear at all. + **Helpers for [`SRT_MSGCTRL`](#SRT_MSGCTRL):** ``` @@ -1919,7 +2069,7 @@ single call to this function determines a message's boundaries. |:----------------------------- |:--------------------------------------------------------- | | Size | Size of the data sent, if successful | | `SRT_ERROR` | In case of error (-1) | -| | | +| | | **NOTE**: Note that in **file/stream mode** the returned size may be less than `len`, which means that it didn't send the whole contents of the buffer. You would need to @@ -1965,7 +2115,7 @@ to use either the `UDT::recv` version for **stream mode** and `UDT::recvmsg` for * [`u`](#u): Socket used to send. The socket must be connected for this operation. * `buf`: Points to the buffer to which the payload is copied. -* `len`: Size of the payload specified in `buf`. +* `len`: Size of the available space in `buf`. * `mctrl`: An object of [`SRT_MSGCTRL`](#SRT_MSGCTRL) type that contains extra parameters. @@ -1988,9 +2138,10 @@ number of bytes retrieved will be at most the maximum payload of one MTU. The [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) value configured by the sender is not negotiated, and not known to the receiver. The [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) value set on the SRT receiver -is mainly used for heuristics. However, the receiver is prepared to receive -the whole MTU as configured with [`SRTO_MSS`](API-socket-options.md#SRTO_MSS). -In this mode, however, with default settings of [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) +is mainly used for heuristics and as the minimum size of the buffer in this +call. However, the receiver is prepared to receive the whole MTU as configured +with [`SRTO_MSS`](API-socket-options.md#SRTO_MSS). In this mode, however, with +default settings of [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) and [`SRTO_TLPKTDROP`](API-socket-options.md#SRTO_TLPKTDROP), the message will be received only when its time to play has come, and until then it will be kept in the receiver buffer. Also, when the time to play has come for a message that is next to @@ -1998,16 +2149,18 @@ the currently lost one, it will be delivered and the lost one dropped. | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| Size | Size (\>0) of the data received, if successful. | -| 0 | If the connection has been closed | -| `SRT_ERROR` | (-1) when an error occurs | -| | | +| Size value \> 0 | Size of the data received, if successful. | +| 0 | No more data are available and the connection is broken | +| `SRT_ERROR` (-1) | An error occurred | +| | | | Errors | | |:--------------------------------------------- |:--------------------------------------------------------- | +| [`SRT_EINVPARAM`](#srt_einvparam) | Invalid parameters passed to the function (e.g., NULL buffer pointer). | +| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID. | | [`SRT_ENOCONN`](#srt_enoconn) | Socket [`u`](#u) used for the operation is not connected. | | [`SRT_ECONNLOST`](#srt_econnlost) | Socket [`u`](#u) used for the operation has lost connection (this is reported only if the connection
was unexpectedly broken, not when it was closed by the foreign host). | -| [`SRT_EINVALMSGAPI`](#srt_einvalmsgapi) | Incorrect API usage in **message mode**:
-- **live mode**: size of the buffer is less than [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) | +| [`SRT_EINVALMSGAPI`](#srt_einvalmsgapi) | Incorrect API usage in **message mode**:
**live mode**: the buffer's size specified in `len` is too small to hold a maximum-sized payload. Ensure the buffer is at least [`SRTO_PAYLOADSIZE`](API-socket-options.md#SRTO_PAYLOADSIZE) bytes (default 1316). | | [`SRT_EINVALBUFFERAPI`](#srt_einvalbufferapi) | Incorrect API usage in **stream mode**:
• Currently not in use. File congestion control used for **stream mode** does not restrict
the parameters. :warning:   **???** | | [`SRT_ELARGEMSG`](#srt_elargemsg) | Message to be sent can't fit in the sending buffer (that is, it exceeds the current total space in
the sending buffer in bytes). This means that the sender buffer is too small, or the application
is trying to send a larger message than initially intended. | | [`SRT_EASYNCRCV`](#srt_easyncrcv) | There are no data currently waiting for delivery. This happens only in non-blocking mode
(when [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) is set to false). In blocking mode the call is blocked until the data are ready.
How this is defined, depends on the mode:
• In **live mode** (with [`SRTO_TSBPDMODE`](API-socket-options.md#SRTO_TSBPDMODE) on), at least one packet must be present in the receiver
buffer and its time to play be in the past
• In **file/message mode**, one full message must be available, the next one waiting if there are no
messages with `inorder` = false, or possibly the first message ready with `inorder` = false
• In **file/stream mode**, it is expected to have at least one byte of data still not extracted | @@ -2054,7 +2207,7 @@ You need to pass them to the [`srt_sendfile`](#srt_sendfile) or | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| Size | The size (\>0) of the transmitted data of a file. It may be less than `size`, if the size was greater
than the free space in the buffer, in which case you have to send rest of the file next time. | +| Size value \> 0 | The size of the transmitted data of a file. It may be less than `size`, if the size was greater
than the free space in the buffer, in which case you have to send rest of the file next time. | | -1 | in case of error | | | | @@ -2098,10 +2251,10 @@ the required range already, so for a numbers like 0x7FFFFFF0 and 0x10, for which ### srt_bistats ``` // Performance monitor with Byte counters for better bitrate estimation. -int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); +SRTSTATUS srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // Performance monitor with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. -int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); +SRTSTATUS srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); ``` Reports the current statistics @@ -2117,12 +2270,6 @@ Reports the current statistics of the fields please refer to [SRT Statistics](statistics.md). -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Invalid socket ID provided. @@ -2175,11 +2322,11 @@ function can then be used to block until any readiness status in the whole int srt_epoll_create(void); ``` -Creates a new epoll container. +Creates a new epoll container and returns its identifier (EID). | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| valid EID | Success | +| valid EID >= 0 | Success | | -1 | Failure | | | | @@ -2199,10 +2346,10 @@ Creates a new epoll container. ### srt_epoll_update_ssock ``` -int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); -int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); -int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); -int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); +SRTSTATUS srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); +SRTSTATUS srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); +SRTSTATUS srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); +SRTSTATUS srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); ``` Adds a socket to a container, or updates an existing socket subscription. @@ -2282,12 +2429,6 @@ as level-triggered, you can do two separate subscriptions for the same socket. any possible flag, you must use [`srt_epoll_uwait`](#srt_epoll_uwait). Note that this function doesn't work with system file descriptors. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2306,8 +2447,8 @@ the [`SRT_ECONNSETUP`](#srt_econnsetup) code is predicted. ### srt_epoll_remove_ssock ``` -int srt_epoll_remove_usock(int eid, SRTSOCKET u); -int srt_epoll_remove_ssock(int eid, SYSSOCKET s); +SRTSTATUS srt_epoll_remove_usock(int eid, SRTSOCKET u); +SRTSTATUS srt_epoll_remove_ssock(int eid, SYSSOCKET s); ``` Removes a specified socket from an epoll container and clears all readiness @@ -2316,12 +2457,6 @@ states recorded for that socket. The `_usock` suffix refers to a user socket (SRT socket). The `_ssock` suffix refers to a system socket. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2380,7 +2515,7 @@ the only way to know what kind of error has occurred on the socket. | Returns | | |:----------------------------- |:------------------------------------------------------------ | -| Number | The number (\>0) of ready sockets, of whatever kind (if any) | +| Number \> 0 | The number of ready sockets, of whatever kind (if any) | | -1 | Error | | | | @@ -2419,7 +2554,7 @@ indefinitely until a readiness state occurs. | Returns | | |:----------------------------- |:-------------------------------------------------------------------------------------------------------------------------------------- | -| Number | The number of user socket (SRT socket) state changes that have been reported in `fdsSet`,
if this number isn't greater than `fdsSize` | +| Number \> 0 | The number of user socket (SRT socket) state changes that have been reported in `fdsSet`,
if this number isn't greater than `fdsSize` | | `fdsSize` + 1 | This means that there was not enough space in the output array to report all events.
For events subscribed with the [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) flag only those will be cleared that were reported.
Others will wait for the next call. | | 0 | If no readiness state was found on any socket and the timeout has passed
(this is not possible when waiting indefinitely) | | -1 | Error | @@ -2428,8 +2563,14 @@ indefinitely until a readiness state occurs. | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | -| [`SRT_EINVPARAM`](#srt_einvparam) | One of possible usage errors:
* `fdsSize` is < 0
* `fdsSize` is > 0 and `fdsSet` is a null pointer
* [`eid`](#eid) was subscribed to any system socket | -| | | +| [`SRT_EINVPARAM`](#srt_einvparam) | Usage error (see below) | +| | | + +Usage errors reported as `SRT_EINVPARAM`: + +* `fdsSize` is \< 0 +* `fdsSize` is \> 0 and `fdsSet` is a null pointer +* [`eid`](#eid) was subscribed to any system socket **IMPORTANT**: This function reports timeout by returning 0, not by [`SRT_ETIMEOUT`](#srt_etimeout) error. @@ -2459,18 +2600,12 @@ closed and its state can be verified with a call to [`srt_getsockstate`](#srt_ge ### srt_epoll_clear_usocks ``` -int srt_epoll_clear_usocks(int eid); +SRTSTATUS srt_epoll_clear_usocks(int eid); ``` This function removes all SRT ("user") socket subscriptions from the epoll container identified by [`eid`](#eid). -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2512,7 +2647,7 @@ the general output array is not empty. | Returns | | |:----------------------------- |:-------------------------------------------------------------------------- | | | This function returns the state of the flags at the time before the call | -| -1 | Special value in case when an error occurred | +| `SRT_ERROR` (-1) | Special value in case when an error occurred | | | | | Errors | | @@ -2527,17 +2662,11 @@ the general output array is not empty. ### srt_epoll_release ``` -int srt_epoll_release(int eid); +SRTSTATUS srt_epoll_release(int eid); ``` Deletes the epoll container. -| Returns | | -|:----------------------------- |:-------------------------------------------------------------- | -| | The number (\>0) of ready sockets, of whatever kind (if any) | -| -1 | Error | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2753,7 +2882,7 @@ and `msTimeStamp` value of the `SRT_TRACEBSTATS` (see [SRT Statistics](statistic | Returns | | |:----------------------------- |:--------------------------------------------------------------------------- | | | Connection time in microseconds elapsed since epoch of SRT internal clock | -| -1 | Error | +| `SRT_ERROR` (-1) | Error | | | | | Errors | | @@ -2807,6 +2936,7 @@ to timestamp packets submitted to SRT is not recommended and must be done with a * [srt_getrejectreason](#srt_getrejectreason) * [srt_rejectreason_str](#srt_rejectreason_str) * [srt_setrejectreason](#srt_setrejectreason) +* [srt_close_getreason](#srt_close_getreason) General notes concerning the `getlasterror` diagnostic functions: when an API function ends up with error, this error information is stored in a thread-local @@ -2894,10 +3024,6 @@ For other values below `SRT_REJC_PREDEFINED` it returns the string for [`SRT_REJ_UNKNOWN`](#SRT_REJ_UNKNOWN). For values since `SRT_REJC_PREDEFINED` on, returns "Application-defined rejection reason". -The actual messages assigned to the internal rejection codes, that is, less than -`SRT_REJ_E_SIZE`, can be also obtained from the `srt_rejectreason_msg` array. - - [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- @@ -2905,7 +3031,7 @@ The actual messages assigned to the internal rejection codes, that is, less than ### srt_setrejectreason ``` -int srt_setrejectreason(SRTSOCKET sock, int value); +SRTSTATUS srt_setrejectreason(SRTSOCKET sock, int value); ``` Sets the rejection code on the socket. This call is only useful in the listener @@ -2919,12 +3045,6 @@ can inform the calling side that the resource specified under the `r` key in the StreamID string (see [`SRTO_STREAMID`](API-socket-options.md#SRTO_STREAMID)) is not available - it then sets the value to `SRT_REJC_PREDEFINED + 404`. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Error | -| -1 | Success | -| | | - | Errors | | |:--------------------------------- |:-------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket `sock` is not an ID of a valid socket | @@ -2950,6 +3070,40 @@ used for the connection, the function should also be called when the a numeric code, which can be translated into a message by [`srt_rejectreason_str`](#srt_rejectreason_str). +The returned value is one of the values listed in enum `SRT_REJECT_REASON`. +For an invalid value of `sock` the `SRT_REJ_UNKNOWN` is returned. + +[:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) + +--- + +### srt_close_getreason + +``` +int srt_close_getreason(SRTSOCKET u, SRT_CLOSE_INFO* info); +``` + +Retrieves the reason code for closing the socket. This designates the +very first reason of closing the socket or group (if there could have +been multiple reasons for closing it, only the first one counts). + +Note that this information may be retrieved even if the socket is already +physically closed, but only for up to 10 seconds after that happens (more +precisely, 10 cycles of GC, which run every 1 second) and only up to 10 such +records are remembered (newer closed ones push off the oldest one). + +**Arguments**: + +* `u`: Socket or group that you believe is closed or broken +* `info`: The structure where the reason is written, see [`SRT_CLOSE_INFO`](#srt_close_info) + +Returns 0 in case of success. Returns `SRT_ERROR` (-1) in case of error, +which may be because: + +* `info` is a NULL pointer +* `u` is an `SRT_INVALID_SOCK` value +* `u` was not found in the closed socket database (expired or was pushed off) + [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -2960,7 +3114,8 @@ a numeric code, which can be translated into a message by #### SRT_REJ_UNKNOWN -A fallback value for cases when there was no connection rejected. +A fallback value for cases when there was no connection rejected or the +reason cannot be obtained. #### SRT_REJ_SYSTEM @@ -3077,6 +3232,151 @@ and above is reserved for "predefined codes" (`SRT_REJC_PREDEFINED` value plus adopted HTTP codes). Values above `SRT_REJC_USERDEFINED` are freely defined by the application. +#### SRT_REJ_CRYPTO + +Settings for `SRTO_CRYPTOMODE` on both parties are not compatible with one another. +See [`SRTO_CRYPTOMODE`](API-socket-options.md#SRTO_CRYPTOMODE) for details. + +#### SRT_REJ_CONFIG + +Settings for various transmission parameters that are supposed to be negotiated +during the handshake (in order to agree upon a common value) are under restrictions +that make finding common values for them impossible. Cases include: + +* `SRTO_PAYLOADSIZE`, which is nonzero in live mode, is set to a value that +exceeds the free space in a single packet that results from the value of the +negotiated MSS value + + +[:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) + +--- + +### `SRT_CLOSE_INFO` + +This structure can be used to get the closing reason (simplified definition): + +``` +struct SRT_CLOSE_INFO +{ + SRT_CLOSE_REASON agent; + SRT_CLOSE_REASON peer; + int64_t time; +}; +``` + +Where: + +* `agent`: The reason code set on the agent ("this machine") side of the connection +if the very first reason of closing has happened on the agent. If the closing was +initiated by the peer, this field contains `SRT_CLS_PEER` value + +* `peer`: If `agent` == `SRT_CLS_PEER`, then closing was initiated by peer and this +field contains the value of this reason + +* `time`: Time when closing has happened, in the same convention as the time value +supplied by [`srt_time_now`](#srt_time_now) + +The values for `agent` and `peer` can be internal, out of the below shown list, or +it can be a user code, if the value is at least `SRT_CLSC_USER`. + + +### Closing reasons + +#### SRT_CLS_UNKNOWN + +The reason not set. The value is used as a fallback if the reason wasn't properly set. + +#### SRT_CLS_INTERNAL + +Closed by internal reasons during connection attempt. + +#### SRT_CLS_PEER + +Closed by the peer (the value is to be used on agent). This happens when the closing +action has been initiated by peer through sending the `UMSG_SHUTDOWN` message. + +#### SRT_CLS_RESOURCE + +A problem with resource allocation. + +#### SRT_CLS_ROGUE + +Received wrong data in the packet. This happens when the socket was closed +due to security reasons, when the data in the packet do not match the expected +protocol specification. + +#### SRT_CLS_OVERFLOW + +Emergency close due to receiver buffer's double overflow that has lead to +an irrecoverable situation. This happens when too slow reading data by the +application has caused that first, incoming packets cannot be inserted into +the buffer because the position in the buffer mapped to their sequence number +locates them outside the buffer. If this situation isn't quickly recovered +from, it causes eventually that the sequence number distance between the last +packet still stored in the buffer and the newly incoming packet exceeds the +size of the receiver buffer. This is then an irrecoverable situation and in +result the socket is closed with this code as a reason. + +#### SRT_CLS_IPE + +Internal program error. Currently used if the incoming acknowledge packet +represents the sequence number that has never been sent, or the value is +out of any valid range. + +#### SRT_CLS_API + +The socket has been closed by the API call of `srt_close()`. This code +is set also if the reason value used in `srt_close_withreason()` is +less than `SRT_CLSC_USER`. + +#### SRT_CLS_FALLBACK + +This value is set on the `peer` field in case when the peer runs the SRT +version that does not support this feature. If this feature is supported, +then the peer should send `UMSG_SHUTDOWN` message with the reason value, +which will be then set on the `peer` field. + +#### SRT_CLS_LATE + +Accepted-socket late-rejection or in-handshake rollback. The late rejection +is something that may happen when the listener side responds to the caller +with a proper handshake message, but the caller rejects that message by +some reason. This way, the caller gets closed with a rejection reason, but +at this moment the accepted socket on the listener side considers itself +connected. Therefore the caller socket in this situation sends first +the `UMSG_SHUTDOWN` message to the peer (that is, the accepted socket) +and in result the accepted socket gets closed with this reason code. + +#### SRT_CLS_CLEANUP + +All sockets are being closed due to srt_cleanup() call. + +#### SRT_CLS_DEADLSN + +This is an accepted socket off a dead listener. If the listener +socket has been closed before the accepted socket could be completed, +the socket can be returned as valid, but could not be used due to +having the listener socket closed too early. + +#### SRT_CLS_PEERIDLE + +Peer didn't send any packet for a time of `SRTO_PEERIDLETIMEO`. This +means that the peer idle timeout has been reached while waiting for +any packet incoming from the peer. + +#### SRT_CLS_UNSTABLE + +Requested to be broken as unstable in Backup group. This happens +exclusively in the group of type `SRT_GTYPE_BACKUP` in case when +the link that was used so far for transmission, has become too +slowly responsive, which caused activation of one of the backup +links, and then this link didn't get back to stability in a given +time (the minimum is configured in `SRTO_GROUPMINSTABLETIMEO`), +which caused that the newly activated link has taken over transmission +and the socket using the unstable link has been closed with this +reason code. + [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -3147,7 +3447,8 @@ is no longer usable. #### SRT_ECONNFAIL -General connection failure of unknown details. +General connection failure of unknown details (currently is not reported +directly by any API function and it's reserved for future use). #### SRT_ECONNLOST @@ -3347,9 +3648,18 @@ you can subscribe them later from another thread. #### `SRT_EBINDCONFLICT` -The binding you are attempting to set up a socket with cannot be completed because -it conflicts with another existing binding. This is because an intersecting binding -was found that cannot be reused according to the specification in `srt_bind` call. +The binding you are attempting to set up a socket with, using the `srt_bind` +call, cannot be completed because it conflicts with another existing binding. + +An attempt of binding a socket, in the conditions of having some other socket already +bound to the same port number, can result in one of three possibilities: + +1. The binding is separate to the existing one (succeeds). +2. The binding intersects with the exiting one (fails). +3. The binding is exactly identical to the existing one (see below). + +See the [`srt_bind`](#srt_bind) for a reference about what kinds of binding +addresses can coexist without conflicts. A binding is considered intersecting if the existing binding has the same port and covers at least partially the range as that of the attempted binding. These @@ -3365,30 +3675,33 @@ Example 1: * Socket 1: bind to IPv4 0.0.0.0 * Socket 2: bind to IPv6 :: with `SRTO_IPV6ONLY` = true -* Result: NOT intersecting +* Result: NOT intersecting, allowed to proceed Example 2: * Socket 1: bind to IPv4 1.2.3.4 * Socket 2: bind to IPv4 0.0.0.0 -* Result: intersecting (and conflicting) +* Result: failure: 0.0.0.0 encloses 1.2.3.4, so they are in conflict Example 3: * Socket 1: bind to IPv4 1.2.3.4 * Socket 2: bind to IPv6 :: with `SRTO_IPV6ONLY` = false -* Result: intersecting (and conflicting) +* Result: failure: this encloses all IPv4, so it conflicts with 1.2.3.4 -If any common range coverage is found between the attempted binding specification -(in `srt_bind` call) and the found existing binding with the same port number, -then all of the following conditions must be satisfied between them: +Binding another socket to an endpoint that is already bound by another +socket is possible, and results in a shared binding, as long as the binding +address that is enclosed by this existing binding is exactly identical to +the specified one and all of the following conditions must be satisfied between +them: -1. The `SRTO_REUSEADDR` must be true (default) in both. +1. The `SRTO_REUSEADDR` must be true (default) in both the attempted and +existing bindings. -2. The IP address specification (in case of IPv6, also including the value of -`SRTO_IPV6ONLY` flag) must be exactly identical. +2. The IP address specification, also as a wildcard (in case of IPv6, also +including the value of `SRTO_IPV6ONLY` flag), must be exactly identical. -3. The UDP-specific settings must be identical. +3. The UDP-specific settings (SRT options that map to UDP options) must be identical. If any of these conditions isn't satisfied, the `srt_bind` function results in conflict and report this error. diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index aaa1401ab..ca65b385d 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -549,6 +549,11 @@ allowed must take this into consideration. It's up to the caller of this function to make this distinction and to take appropriate action depending on the type of entity returned. +Note: this flag should be altered **before** calling `srt_listen`. If you do +this after this call, you might have some pending group connections in the +meantime that will be rejected because group connections are not **yet** +allowed on this listener socket. + When this flag is set to 1 on an accepted socket that is passed to the listener callback handler, it means that this socket is created for a group connection and it will become a member of a group. Note that in this case @@ -972,20 +977,88 @@ The default value is 0x010000 (SRT v1.0.0). | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | -| `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 76.. | RW | GSD | - -Maximum Segment Size. Used for buffer allocation and rate calculation using -packet counter assuming fully filled packets. Each party can set its own MSS -value independently. During a handshake the parties exchange MSS values, and -the lowest is used. - -*Generally on the internet MSS is 1500 by default. This is the maximum -size of a UDP packet and can be only decreased, unless you have some unusual -dedicated network settings. MSS is not to be confused with the size of the UDP -payload or SRT payload - this size is the size of the IP packet, including the -UDP and SRT headers* - -THe value of `SRTO_MSS` must not exceed `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`. +| `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 116.. | RW | GSD | + +Maximum Segment Size. This value represents the maximum size of a UDP packet +sent by the system. Therefore the value of `SRTO_MSS` must not exceed the +values of `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`. It is used for buffer +allocation and rate calculation using a packet counter that assumes fully filled +packets. + +This value is a sum of: + +* IP header (20 bytes for IPv4, or 32 bytes for IPv6) +* UDP header (8 bytes) +* SRT header (16 bytes) +* remaining space (as the maximum payload size available for a packet) + +For the default 1500 the "remaining space" is effectively 1456 for IPv4 +and 1444 for IPv6, although it can be limited by nondefault values of some +other socket options. + +Note that the IP version used here is not the domain of the underlying UDP +socket, but the in-transmission IP version. This is effectively IPv4 in the +following cases: + +* when the current socket's binding address is of IPv4 domain +* when the peer's address is an IPv6-mapped-IPv4 address + +The IPv6 transmission case is assumed only if the peer's address is a true IPv6 +address (not IPv4 mapped). It is then not possible to determine the payload +size limit until the connection is established. SRT operations that must +allocate any resources according to this value prior to connecting will assume +IPv4 transmission because this way, in the worst case, they allocate more space +than needed. + +This value can be set on both connection parties independently, but after +connection `SRTO_MSS` gets a negotiated value, which is the lesser of the two. +If this effective value is too small for either of the connection peers, the +connection is rejected (or late-rejected on the caller side). + +This value then controls: + +* The maximum size of the payload in a single UDP packet ("remaining space"). + +* The size of the memory space allocated for a single packet in the sender +and receiver buffers. This value is equal to "SRT header" + "remaining space" +in the IPv4 layout case (1472 bytes per packet for MSS=1500). The reason for it +is that some buffer resources are allocated prior to the connection, so this +value must fit both IPv4 and IPv6 for buffer memory allocation. + +The default value of 1500 corresponds to the standard MTU size for network +devices. It is recommended that this value be set to the maximum MTU size of +the network device that you will use for the connection. + +The recommendations for the value of `SRTO_MSS` differ between file and live modes. + +In live mode a single call to the `srt_send\*` function may only send data that +fit in one packet. This size is defined by the `SRTO_PAYLOADSIZE` option +(default: 1316), and it is also the size of the data in a single UDP packet. To +save memory space, you may want then to set `SRTO_MSS` in live mode to a value +for which the "remaining space" matches the `SRTO_PAYLOADSIZE` value (for the +default value of 1316 this will be 1360 for IPv4 and 1372 for IPv6). For +security reasons, this is not done by default: it may potentially lead to the +inability to read an incoming UDP packet if its size is for some reason bigger +than the negotiated MSS, which may in turn lead to unpredictable behaviour and +hard-to-detect errors. You should set such a value only if the peer is trusted +(that is, you can be certain that you will never receive an oversized UDP +packet over the link used for the connection). You should also consider the +limitations of `SRTO_PAYLOADSIZE`. + +In file mode `SRTO_PAYLOADSIZE` has a special value 0 that means no limit +for sending a single packet, and therefore bigger portions of data are +internally split into smaller portions, each one using the maximum available +"remaining space". The best value of `SRTO_MSS` for this case is then equal to +the current network device's MTU size. Setting a greater value is possible +(maximum for the system API is 65535), but it may lead to packet fragmentation +on the system level. This is highly unwanted in SRT because: + +* SRT also performs its own fragmentation, so it would be counter-productive +* It would use more system resources to no advantage +* SRT is unaware of it, so the resulting statistics would be slightly misleading + +System-level packet fragmentation cannot be reliably turned off, +so the safest approach is to avoid it by using appropriate parameters. [Return to list](#list-of-options) @@ -1082,7 +1155,7 @@ Cases when negotiation succeeds: | fec,cols:10 | fec,cols:10,rows:20 | fec,cols:10,rows:20,arq:onreq,layout:even | fec,layout:staircase | fec,cols:10 | fec,cols:10,rows:1,arq:onreq,layout:staircase -In these cases the configuration is rejected with SRT_REJ_FILTER code: +In these cases the configuration is rejected with `SRT_REJ_FILTER` code: | Peer A | Peer B | Error reason |-----------------------|---------------------|-------------------------- @@ -1137,14 +1210,60 @@ encrypted connection, they have to simply set the same passphrase. | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | -| `SRTO_PAYLOADSIZE` | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | W | GSD | +| `SRTO_PAYLOADSIZE` | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | RW | GSD | + +Sets the mode that determines the limitations on how data is sent, including the maximum +size of payload data sent within a single UDP packet. This option can be only set prior +to connecting, but it can be read also after the connection has been established. + +The default value is 1316 in live mode (which is default) and 0 in file mode (when file +mode is set through the `SRTO_TRANSTYPE` option). + +In file mode (`SRTO_PAYLOADSIZE` = 0) the call to `srt_send\*` is not limited +to the size of a single packet. If necessary, the supplied data will be split +into multiple pieces, each fitting into a single UDP packet. Every data payload +(except the last one in the stream or in the message) will use the maximum +space available in a UDP packet, as determined by `SRTO_MSS` and other settings +that may influence this size (such as [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER) +and [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE)). + +Also when this option is set to 0 prior to connecting, then reading this option +from a connected socket will return the maximum size of the payload that fits +in a single packet according to the current connection parameters. + +In live mode (`SRTO_PAYLOADSIZE` > 0) the value defines the maximum size of: + +* a single call to a sending function (`srt_send\*`) +* the payload supplied in each data packet + +as well as the minimum size of the buffer used for the `srt_recv\*` call. + +This value can be set to a greater value than the default 1316, but the maximum +possible value is limited by the following factors: + +* 1500 bytes is the default MSS (see [`SRTO_MSS`](#SRTO_MSS)), including headers, which occupy: + * 20 bytes for IPv4, or 32 bytes for IPv6 + * 8 bytes for UDP + * 16 bytes for SRT + +This alone gives a limit of 1456 for IPv4 and 1444 for IPv6. This limit may +be further decreased in the following cases: + +* 4 bytes reserved for FEC, if you use the built in FEC packet filter (see [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER)) +* 16 bytes reserved for the authentication tag, if you use AES GCM (see [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE)) + +**WARNING**: The party setting the options will reject a value that is too +large, but note that not every limitation can be checked prior to connection. +This includes: -Sets the maximum declared size of a single call to sending function in Live -mode. When set to 0, there's no limit for a single sending call. +* the MSS value defined by a peer, which may override the MSS set by an agent +* the in-transmission IP version (see [SRTO_MSS](#SRTO_MSS) for details) -For Live mode: Default value is 1316, but can be increased up to 1456. Note that -with the `SRTO_PACKETFILTER` option additional header space is usually required, -which decreases the maximum possible value for `SRTO_PAYLOADSIZE`. +These values also influence the "remaining space" in the packet to be used for +payload. If during the handshake it turns out that this "remaining space" is +less than the value set for `SRTO_PAYLOADSIZE` (including when it remains with +the default value), the connection will be rejected with the `SRT_REJ_SETTINGS` +code. For File mode: Default value is 0 and it's recommended not to be changed. diff --git a/docs/API/API.md b/docs/API/API.md index 3bd303830..24e721987 100644 --- a/docs/API/API.md +++ b/docs/API/API.md @@ -211,7 +211,7 @@ extra data for the operation. Functions with the `msg2` suffix use the `SRT_MSGCTRL` object, and have the following interpretation (except `flags` and `boundary` which are reserved for -future use and should be 0): +future use and should not be set other value than the initial one): - `srt_sendmsg2`: - `msgttl`: [IN] maximum time (in ms) to wait for successful delivery (-1: indefinitely) @@ -227,6 +227,20 @@ future use and should be 0): - `pktseq`: [OUT] packet sequence number (first packet from the message, if it spans multiple UDP packets) - `msgno`: [OUT] message number assigned to the currently received message +- both above [IN] + - grpdata_size: size of the passed grpdata array + +- both above [OUT] + - grpdata: pointer to the array of group data to be filled by the call + - grpdata_size: actual size of the filled array + +**IMPORTANT**: no matter that fields are particularly marked as `[OUT]` (or +unused), values specified there could be used for input under certain +circumstances. Especially in `srt_sendmsg2` you should not reuse existing +objects of `SRT_MSGCTRL` type, but create always new ones and initialize them +with default `srt_msgctl_default` (or overwrite them first with it and set the +desired values anew). + Please note that the `msgttl` and `inorder` arguments and fields in `SRT_MSGCTRL` are meaningful only when you use the message API in file mode (this will be explained later). In live mode, which is the SRT default, packets are always delivered when diff --git a/docs/build/build-container.md b/docs/build/build-container.md new file mode 100644 index 000000000..b8a0d3e1f --- /dev/null +++ b/docs/build/build-container.md @@ -0,0 +1,100 @@ +# Building SRT Using Devcontainer or Docker + +This guide describes how to use the provided Devcontainer or Dockerfile to set up a consistent build environment for SRT. + +## VS Code Devcontainer (Recommended) + +The devcontainer provides the easiest and most integrated development experience with VS Code. + +### Prerequisites + +- [Visual Studio Code](https://code.visualstudio.com/) +- [Docker Desktop](https://www.docker.com/products/docker-desktop) or Docker Engine +- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) for VS Code + +### Steps + +1. Open the SRT repository in VS Code + + ```console + $ code + ``` + +2. Open in Container + + - VS Code should prompt you to "Reopen in Container" + - Alternatively, press `F1` and select `Dev Containers: Reopen in Container` + +3. Run Spell Check + + ```console + $ cd + $ codespell --config scripts/codespell/codespell.cfg + ``` + +4. Build SRT Inside the Container + + ```console + $ mkdir _build && cd _build + $ cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + $ cmake --build . --parallel + ``` + +5. Run Tests + + ```console + $ cd _build + $ ctest --extra-verbose + ``` + +## Dockerfile + +If you prefer not to use VS Code or want to use the Dockerfile independently, you can build and run the container manually. + +### Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop) or Docker Engine + +### Steps + +1. Build the Docker Image + + ```console + $ cd + $ docker build -t srt-build-env -f docker/Dockerfile . + ``` + +2. Run the Container + + Mount the SRT source directory into the container: + ```console + $ docker run -it --rm -v "$(pwd)":/srt_build_env -w /srt_build_env srt-build-env + ``` + +3. Run Spell Check + + ```console + $ cd + $ codespell --config scripts/codespell/codespell.cfg + ``` + +4. Build SRT Inside the Container + + ```console + $ mkdir _build && cd _build + $ cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + $ cmake --build . --parallel + ``` + +5. Run Tests + + ```console + $ cd _build + $ ctest --extra-verbose + ``` + +6. Exit the Container + + ```console + $ exit + ``` diff --git a/docs/build/build-linux.md b/docs/build/build-linux.md index 678e96615..01e242745 100644 --- a/docs/build/build-linux.md +++ b/docs/build/build-linux.md @@ -13,7 +13,7 @@ To uninstall, call `make -n install` to list all the dependencies, and then pass Here is a link to a demo showing how CMake can be used to build SRT: [Quickstart: Running SRT and FFmpeg on Ubuntu](https://www.youtube.com/watch?v=XOtUOVhussc&t=5s). -## Ubuntu 14 +## Ubuntu ```shell sudo apt-get update diff --git a/docs/build/build-options.md b/docs/build/build-options.md index 05fcf8be4..2810370c4 100644 --- a/docs/build/build-options.md +++ b/docs/build/build-options.md @@ -59,32 +59,37 @@ Option details are given further below. | Option Name | Since | Type | Default | Short Description | | :----------------------------------------------------------- | :---: | :-------: | :--------: | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | [`CMAKE_INSTALL_PREFIX`](#cmake_install_prefix) | 1.3.0 | `STRING` | OFF | Standard CMake variable that establishes the root directory for installation, inside of which a GNU/POSIX compatible directory layout will be used. | -| [`CYGWIN_USE_POSIX`](#cygwin_use_posix) | 1.2.0 | `BOOL` | OFF | Determines when to compile on Cygwin using POSIX API. | +| [`ENABLE_AEAD`](#enable_aead) | 1.5.2 | `BOOL` | ON | Enables AEAD preview API (encryption with integrity check). | | [`ENABLE_APPS`](#enable_apps) | 1.3.3 | `BOOL` | ON | Enables compiling sample applications (`srt-live-transmit`, etc.). | -| [`ENABLE_BONDING`](#enable_bonding) | 1.5.0 | `BOOL` | OFF | Enables the [Connection Bonding](../features/bonding-quick-start.md) feature. | -| [`ENABLE_CXX_DEPS`](#enable_cxx_deps) | 1.3.2 | `BOOL` | OFF | The `pkg-confg` file (`srt.pc`) will be generated with the `libstdc++` library as a dependency. | -| [`ENABLE_CXX11`](#enable_cxx11) | 1.2.0 | `BOOL` | ON | Enable compiling in C++11 mode for those parts that may require it. Default: ON except for GCC<4.7 | +| [`ENABLE_BONDING`](#enable_bonding) | 1.5.0 | `BOOL` | ON | Enables the [Connection Bonding](../features/bonding-quick-start.md) feature. | +| [`ENABLE_CLANG_TSA`](#enable_clang_tsa) | 1.5.0 | `BOOL` | OFF | Enables compiling mode handled by Clang compiler using compile-time static Thread Safety Analysis | +| [`ENABLE_CLOEXEC`](#enable_cloexec) | 1.5.0 | `BOOL` | ON | Enables the use of `*_CLOEXEC` flags on system resources (system sockets and polling ids) | | [`ENABLE_CODE_COVERAGE`](#enable_code_coverage) | 1.4.0 | `BOOL` | OFF | Enables instrumentation for code coverage. | -| [`ENABLE_DEBUG`](#enable_debug) | 1.2.0 | `INT` | ON | Allows release/debug control through the `CMAKE_BUILD_TYPE` variable. | +| [`ENABLE_CXX11`](#enable_cxx11) | 1.2.0 | `BOOL` | ON | Enable compiling in C++11 mode for those parts that may require it. Default: ON except for GCC<4.7 | +| [`ENABLE_CXX_DEPS`](#enable_cxx_deps) | 1.3.2 | `BOOL` | OFF | The `pkg-confg` file (`srt.pc`) will be generated with the `libstdc++` library as a dependency. | +| [`ENABLE_CYGWIN_POSIX`](#enable_cygwin_posix) | 1.2.0 | `BOOL` | OFF | Determines when to compile on Cygwin using POSIX API. | +| [`ENABLE_DEBUG`](#enable_debug) | 1.2.0 | `INT` | ON | Allows release/debug control through the `CMAKE_BUILD_TYPE` variable (only for single configuration cmake generators) | | [`ENABLE_ENCRYPTION`](#enable_encryption) | 1.3.3 | `BOOL` | ON | Enables encryption feature, with dependency on an external encryption library. | -| [`ENABLE_AEAD_API_PREVIEW`](#enable_aead_api_preview) | 1.5.2 | `BOOL` | OFF | Enables AEAD preview API (encryption with integrity check). | -| [`ENABLE_MAXREXMITBW`](#enable_maxrexmitbw) | 1.5.3 | `BOOL` | OFF | Enables SRTO_MAXREXMITBW (v1.6.0 API). | -| [`ENABLE_GETNAMEINFO`](#enable_getnameinfo) | 1.3.0 | `BOOL` | OFF | Enables the use of `getnameinfo` to allow using reverse DNS to resolve an internal IP address into a readable internet domain name. | +| [`ENABLE_GETNAMEINFO`](#enable_getnameinfo) | 1.3.0 | `BOOL` | OFF | Enables the use of `getnameinfo` to allow using reverse DNS to resolve an internal IP address into a readable internet domain name (used in logs) | | [`ENABLE_HAICRYPT_LOGGING`](#enable_haicrypt_logging) | 1.3.1 | `BOOL` | OFF | Enables logging in the *haicrypt* module, which serves as a connector to an encryption library. | | [`ENABLE_HEAVY_LOGGING`](#enable_heavy_logging) | 1.3.0 | `BOOL` | OFF | Enables heavy logging instructions in the code that occur often and cover many detailed aspects of library behavior. Default: OFF in release mode. | -| [`ENABLE_INET_PTON`](#enable_inet_pton) | 1.3.2 | `BOOL` | ON | Enables usage of the `inet_pton` function used to resolve the network endpoint name into an IP address. | +| [`ENABLE_LOCALIF_WIN32`](#enable_localif_win32) | 1.2.0 | `BOOL` | ON | Enables local interface extraction on Windows, which requires linking against Iphlpapi.lib | | [`ENABLE_LOGGING`](#enable_logging) | 1.2.0 | `BOOL` | ON | Enables normal logging, including errors. | +| [`ENABLE_MAXREXMITBW`](#enable_maxrexmitbw) | 1.5.3 | `BOOL` | OFF | Enables `SRTO_MAXREXMITBW` (v1.6.0 API). | | [`ENABLE_MONOTONIC_CLOCK`](#enable_monotonic_clock) | 1.4.0 | `BOOL` | ON\* | Enforces the use of `clock_gettime` with a monotonic clock that is independent of the currently set time in the system. | +| [`ENABLE_PKTINFO`](#enable_pktinfo) | 1.5.2 | `BOOL` | ON\* | Enables using `IP_PKTINFO` to allow the listener extracting the target IP address from incoming packets | | [`ENABLE_PROFILE`](#enable_profile) | 1.2.0 | `BOOL` | OFF | Enables code instrumentation for profiling (only for GNU-compatible compilers). | | [`ENABLE_RELATIVE_LIBPATH`](#enable_relative_libpath) | 1.3.2 | `BOOL` | OFF | Enables adding a relative path to a library for linking against a shared SRT library by reaching out to a sibling directory. | | [`ENABLE_SHARED`](#enable_shared--enable_static) | 1.2.0 | `BOOL` | ON | Enables building SRT as a shared library. | | [`ENABLE_SHOW_PROJECT_CONFIG`](#enable_show_project_config) | 1.5.0 | `BOOL` | OFF | When ON, the project configuration is displayed at the end of the CMake Configuration Step. | +| [`ENABLE_SOCK_CLOEXEC`](#enable_sock_cloexec) | 1.4.2 | `BOOL` | ON | Enables SOCK_CLOEXEC flag on sockets to prevent file descriptor leaks to child processes on fork()/exec(). | | [`ENABLE_STATIC`](#enable_shared--enable_static) | 1.3.0 | `BOOL` | ON | Enables building SRT as a static library. | | [`ENABLE_STDCXX_SYNC`](#enable_stdcxx_sync) | 1.4.2 | `BOOL` | ON\* | Enables the standard C++11 `thread` and `chrono` libraries to be used by SRT instead of the `pthreads`. | -| [`ENABLE_PKTINFO`](#enable_pktinfo) | 1.5.2 | `BOOL` | OFF\* | Enables using `IP_PKTINFO` to allow the listener extracting the target IP address from incoming packets | | [`ENABLE_TESTING`](#enable_testing) | 1.3.0 | `BOOL` | OFF | Enables compiling of developer testing applications (`srt-test-live`, etc.). | | [`ENABLE_THREAD_CHECK`](#enable_thread_check) | 1.3.0 | `BOOL` | OFF | Enables `#include `, which implements `THREAD_*` macros" to support better thread debugging. | +| [`ENABLE_THREAD_DEBUG`](#enable_thread_debug) | 1.3.0 | `BOOL` | OFF | Enables debugging on the custom srt::sync::SharedMutex (allows to determine current readers and writers) | | [`ENABLE_UNITTESTS`](#enable_unittests) | 1.3.2 | `BOOL` | OFF | Enables building unit tests. | +| [`ENABLE_UNITTESTS_DISCOVERY`](#enable_unittests_discovery) | 1.3.2 | `BOOL` | ON | Enables unit tests discovery (automatic running when compiling), if unit tests are enabled | | [`OPENSSL_CRYPTO_LIBRARY`](#openssl_crypto_library) | 1.3.0 | `STRING` | OFF | Configures the path to an OpenSSL crypto library. | | [`OPENSSL_INCLUDE_DIR`](#openssl_include_dir) | 1.3.0 | `STRING` | OFF | Configures the path to include files for an OpenSSL library. | | [`OPENSSL_SSL_LIBRARY`](#openssl_ssl_library) | 1.3.0 | `STRING` | OFF | Configures the path to an OpenSSL SSL library. | @@ -94,8 +99,9 @@ Option details are given further below. | [`SRT_LOG_SLOWDOWN_FREQ_MS`](#SRT_LOG_SLOWDOWN_FREQ_MS) | 1.5.2 | `INT` | 1000\* | Reduce the frequency of some frequent logs, milliseconds. | | [`USE_BUSY_WAITING`](#use_busy_waiting) | 1.3.3 | `BOOL` | OFF | Enables more accurate sending times at the cost of potentially higher CPU load. | | [`USE_CXX_STD`](#use_cxx_std) | 1.4.2 | `STRING` | OFF | Enforces using a particular C++ standard (11, 14, 17, etc.) when compiling. | -| [`USE_ENCLIB`](#use_enclib) | 1.3.3 | `STRING` | openssl | Encryption library to be used (`openssl`, `openssl-evp` (since 1.5.1), `gnutls`, `mbedtls`, `botan` (since 1.6.0)). | +| [`USE_ENCLIB`](#use_enclib) | 1.3.3 | `STRING` | openssl | Encryption library to be used (`openssl`, `openssl-evp` (since 1.5.1), `gnutls`, `mbedtls`, `botan` (since 1.6.0)). | | [`USE_GNUSTL`](#use_gnustl) | 1.3.4 | `BOOL` | OFF | Use `pkg-config` with the `gnustl` package name to extract the header and library path for the C++ standard library. | +| [`USE_MUTEX_ATOMIC`](#use_mutex_atomic) | 1.3.3 | `BOOL` | OFF | Use srt::sync::Mutex to implement srt::sync::atomic | | [`USE_OPENSSL_PC`](#use_openssl_pc) | 1.3.0 | `BOOL` | ON | Use `pkg-config` to find OpenSSL libraries. | | [`SRT_USE_OPENSSL_STATIC_LIBS`](#srt_use_openssl_static_libs)| 1.5.0 | `BOOL` | OFF | Link OpenSSL statically. | | [`USE_STATIC_LIBSTDCXX`](#use_static_libstdcxx) | 1.2.0 | `BOOL` | OFF | Enforces linking the SRT library against the static `libstdc++` library. | @@ -136,11 +142,11 @@ SRT build options known to CMake are listed in the For example: -`option(CYGWIN_USE_POSIX "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin." OFF) +`option(ENABLE_CYGWIN_POSIX "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin." OFF) ` With CMake you would specify this option as: -`cmake -DCYGWIN_USE_POSIX=ON` or `cmake -DCYGWIN_USE_POSIX=OFF` +`cmake -DENABLE_CYGWIN_POSIX=ON` or `cmake -DENABLE_CYGWIN_POSIX=OFF` where “-D” is the CMake command to set a build variable to a certain value. @@ -166,18 +172,18 @@ The directly translated options always undergo a simple transformation: * plus (+) symbols are converted to X * when no value is supplied, a default value of 1 is applied -To set the `CYGWIN_USE_POSIX` option using the configure script you would call +To set the `ENABLE_CYGWIN_POSIX` option using the configure script you would call -`configure --cygwin-use-posix` +`configure --enable-cygwin-posix` -which is transformed by the script into `-DCYGWIN-USE-POSIX` and then passed +which is transformed by the script into `-DENABLE_CYGWIN_POSIX=1` and then passed to `cmake` to enable POSIX (set to ON). To disable the option (set to OFF) using the configure script you would call -`configure –-disable-cygwin-use-posix` +`configure –-disable-cygwin-posix` In another example, to enable compiling in C++11 mode with the CMake command -`ENABLE-C++11` using the configure script you would call +option `-DENABLE_CXX11=1` using the configure script you would call `configure --enable-c++11` @@ -198,19 +204,23 @@ equivalent `configure` format. #### CMAKE_INSTALL_PREFIX **`--cmake-install-prefix=`** +**`--prefix=`** -Used to configure an alias to the `--cmake-install-prefix` variable that +This is one of the cmake-builtin variables, but often useful. + +It's used to configure an alias to the `CMAKE_INSTALL_PREFIX` variable that establishes the root directory for installation, inside of which a GNU/POSIX compatible directory layout will be used. As on all known build systems, this defaults to `/usr/local` on GNU/POSIX compatible systems, with lower level GNU/POSIX directories created inside: `/usr/local/bin`,`/usr/local/lib`, etc. -#### CYGWIN_USE_POSIX -**`--cygwin-use-posix`** (default:OFF) +#### ENABLE_AEAD +**`--enable-aead`** (default: OFF) -Set to ON to compile SRT on Cygwin using the POSIX API (otherwise it will use -MinGW environment). +When ON, the AEAD API is enabled. The `ENABLE_ENCRYPTION` must be enabled as well. +The AEAD functionality is only available if either OpenSSL EVP or Botan is selected +as the crypto provider: build option `USE_ENCLIB` set to `openssl-evp` or `botan`. #### ENABLE_APPS @@ -223,21 +233,78 @@ Enables compiling user applications. #### ENABLE_BONDING -**`--enable-bonding`** (default: OFF) +**`--enable-bonding`** (default: ON) Enables the [Connection Bonding](../features/bonding-quick-start.md) feature. -Similar to SMPTE-2022-7 over managed networks, Connection Bonding adds seamless stream protection and hitless failover to the SRT protocol. This technology relies on more than one IP network path to prevent disruption to live video streams in the event of network congestion or outages, maintaining continuity of service. +Similar to SMPTE-2022-7 over managed networks, Connection Bonding adds seamless +stream protection and hitless failover to the SRT protocol. This technology +relies on more than one IP network path to prevent disruption to live video +streams in the event of network congestion or outages, maintaining continuity +of service. -This is accomplished using the [socket groups](../features/socket-groups.md) introduced in [SRT v1.5](https://github.com/Haivision/srt/releases/tag/v1.5.0). The general concept of socket groups means having a group that contains multiple sockets, where one operation for sending one data signal is applied to the group. Single sockets inside the group will take over this operation and do what is necessary to deliver the signal to the receiver. +This is accomplished using the [socket groups](../features/socket-groups.md) +introduced in [SRT v1.5](https://github.com/Haivision/srt/releases/tag/v1.5.0). +The general concept of socket groups means having a group that contains +multiple sockets, where one operation for sending one data signal is applied to +the group. Single sockets inside the group will take over this operation and do +what is necessary to deliver the signal to the receiver. Two modes are supported: -- [Broadcast](../features/socket-groups.md#1-broadcast) - In *Broadcast* mode, data is sent redundantly over all the member links in a group. If one of the links fails or experiences network jitter and/or packet loss, the missing data will be received over another link in the group. Redundant packets are simply discarded at the receiver side. +- [Broadcast](../features/socket-groups.md#1-broadcast) - In *Broadcast* mode, +data is sent redundantly over all the member links in a group. If one of the +links fails or experiences network jitter and/or packet loss, the missing data +will be received over another link in the group. Redundant packets are simply +discarded at the receiver side. + +- [Main/Backup](../features/bonding-main-backup.md) - In *Main/Backup* mode, +only one (main) link at a time is used for data transmission while other +(backup) connections are on standby to ensure the transmission will continue if +the main link fails. The goal of Main/Backup mode is to identify a potential +link break before it happens, thus providing a time window within which to +seamlessly switch to one of the backup links. + +With the Connection Bonding feature disabled, +[bonding API functions](../API/API-functions.md#socket-group-management) +are present, but return an error. + +#### ENABLE_CLANG_TSA +**`--enable-clang-tsa`** (default: OFF) + +Enable the Thread Safety Analysis, if compiling using Clang compiler. This employs +the Clang TSA feature that issues warnings that violate the mutex operation rules. +This is for development only; you may find many warnings with this feature, +although this should be only used as a hint. Some reports may be false +positives, others appear because some specifics can't be really well defined +using these markers, as well as there are still bugs or design flaws in the +Clang TSA feature, depending on the version. + -- [Main/Backup](../features/bonding-main-backup.md) - In *Main/Backup* mode, only one (main) link at a time is used for data transmission while other (backup) connections are on standby to ensure the transmission will continue if the main link fails. The goal of Main/Backup mode is to identify a potential link break before it happens, thus providing a time window within which to seamlessly switch to one of the backup links. +#### ENABLE_CLOEXEC +**`--enable-cloexec`** (default: ON) -With the Connection Bonding feature disabled, [bonding API functions](../API/API-functions.md#socket-group-management) are present, but return an error. +Enables the use of `*_CLOEXEC` flags on system resources (sockets and poll IDs). +This flag is intended to ensure that processes that replace the current one by +the call of a function that creates a new process - `exec*()` (after `fork()`) +or `CreateProcess` on Windows, will not derive the file descriptors that +designate these resources (on Windows, at least not by default). + +Due to portability issues, the code is prepared to autodetect the best available +method to set this flag: + +* For sockets, `SOCK_CLOEXEC` is tried, if available +* For Linux epoll id, `EPOLL_CLOEXEC` is tried, if available +* For Mac's kqueue, `KQUEUE_CLOEXEC` is tried, if available +* If fallback is required, the `ioctl/O_CLOEXEC` or `fcntl/FD_CLOEXEC` is tried +* On Windows `SetHandleInformation/HANDLE_FLAG_INHERIT` is used + + +#### ENABLE_CYGWIN_POSIX +**`--enable-cygwin-posix`** (default:OFF) + +Set to ON to compile SRT on Cygwin using the POSIX API (otherwise it will use +MinGW environment). #### ENABLE_CXX_DEPS @@ -304,30 +371,17 @@ It will be compatible with a peer that has encryption enabled, but just won't use encryption for the connection. -#### ENABLE_AEAD_API_PREVIEW -**`--enable-aead-api-preview`** (default: OFF) - -When ON, the AEAD API is enabled. The `ENABLE_ENCRYPTION` must be enabled as well. -The AEAD functionality is only available if either OpenSSL EVP or Botan is selected -as the crypto provider: -build option `-DUSE_ENCLIB=[openssl-evp | botan]`. - -The AEAD API is to be official in SRT v1.6.0. - -#### ENABLE_MAXREXMITBW -**`--enable-maxrexmitbw`** (default: OFF) - -When ON, the `SRTO_MAXREXMITBW` is enabled (to become official in SRT v1.6.0). - - #### ENABLE_GETNAMEINFO **`--enable-getnameinfo`** (default: OFF) When ON, enables the use of `getnameinfo` with options that allow using reverse DNS to resolve an internal IP address into a readable internet domain name, so -that it can be shown nicely in the log file. This option is turned OFF by default -because it may have an impact on general performance. It is recommended only for -development when testing on a local network. +that it can be shown nicely in the log file. This feature is used only in the +`srt::sockaddr_any::str()` that displays the contained address. + +This option is turned OFF by default because it may have an impact on general +performance. It is recommended only for development when testing on a local +network. #### ENABLE_HAICRYPT_LOGGING @@ -359,18 +413,8 @@ Turning this option ON will allow you to use the `debug` level of logging and ge detailed information as to what happens inside the library. Note, however, that this may influence processing by changing timings, use less preferred thread switching layouts, and generally worsen the functionality and performance of -the library. For these reasons this option is turned OFF by default. - - -#### ENABLE_INET_PTON -**`--enable-inet-pton`** (default: ON) - -When ON, enables usage of the `inet_pton` function by applications, which should -be used to resolve the network endpoint name into an IP address. This may not be -available in some versions of Microsoft Windows, in which case you can change the -setting to OFF. When this option is OFF, however, IP addresses cannot be resolved -by name, as the `inet_pton` function gets a poor-man's simple replacement that can -only resolve numeric IPv4 addresses. +the library. For these reasons this option is turned OFF by default in release +mode. This option is turned ON in debug mode, including when `ENABLE_DEBUG=2`. #### ENABLE_LOGGING @@ -381,6 +425,25 @@ report any runtime information, including errors, through the logging system. Th option may be useful if you suspect the logging system of impairing performance. + +#### ENABLE_LOCALIF_WIN32 +**`--enable-localif-win32`** + +This enables the local interface tracking feature also for Windows. The local +interface tracking feature is built-in in all POSIX systems. On Windows it +requires linking against additional library `Iphlpapi.lib`. This can be turned +off by setting this option to OFF, if there are any problems with linking against +that library or when the system using it would be vulnerable through this +(vulnerabilities were reported on the old Windows Vista systems, if they are +still not patched). + + +#### ENABLE_MAXREXMITBW +**`--enable-maxrexmitbw`** (default: OFF) + +When ON, the `SRTO_MAXREXMITBW` socket option is enabled (to become official in SRT v1.6.0). + + #### ENABLE_MONOTONIC_CLOCK **`--enable-monotonic-clock`** (default: OFF) @@ -463,6 +526,20 @@ When ON, the project configuration is displayed at the end of the CMake configuration step of the build process. +#### ENABLE_SOCK_CLOEXEC +**`--enable-sock-cloexec`** (default: ON) + +When ON, enables the `SOCK_CLOEXEC` flag when creating sockets. This flag causes the socket file descriptor to be automatically closed when a program calls `exec()` family functions to replace the current process with a new program. + +This is an important security and resource management feature that prevents file descriptor leaks to child processes. Without this flag, socket file descriptors remain open in child processes created via `fork()` and `exec()`, which can lead to: + +* Security vulnerabilities where child processes inherit network connections they shouldn't have access to +* Resource leaks where sockets remain open in child processes that don't use them +* Unexpected behavior when child processes inadvertently interfere with parent process network operations + +The `SOCK_CLOEXEC` flag is part of POSIX and is available on most modern Unix-like systems (Linux, BSD variants, etc.). On systems that don't support this flag, SRT will fall back to using `fcntl()` with `FD_CLOEXEC` to achieve the same behavior. + + #### ENABLE_STDCXX_SYNC **`--enable-stdcxx-sync`** (default: OFF) @@ -475,17 +552,29 @@ When ON, this option enables the standard C++ `thread` and `chrono` libraries (available since C++11) to be used by SRT instead of the `pthreads` libraries. #### ENABLE_PKTINFO -**`--enable-pktinfo`** (default: OFF) - -This allows the use of the `IP_PKTINFO` control message to extract the true target IP -address from the incoming UDP packets to a listener bound to "any" address. The "any" -address is defined in IPv4 as 0.0.0.0 (`INADDR_ANY`) and in IPv6 as :: (`in6addr_any`). -Applications usually implement it by clearing the `sockaddr*` structure and only setting -the port number. This true address can then be used to override the source IP address -when sending packets to the peer. This solves the problem where routing rules -in an agent's host send a packet using a different source IP address than the target -IP address in the UDP packet from the peer. The peer will reject such a packet as a -suspected man-in-the-middle (MITM) attempt, leading to a connection failure. +**`--enable-pktinfo`** (default: ON, except for BSD and Windows platforms) + +This option enables the PKTINFO feature that allows to extract the target +address from incoming UDP packets and forcefully set the source address in the +outgoing UDP packets. This helps in cases when such an address set +automatically by the system would be incorrect. If this feature is not +enabled, connections to hosts with some distinct IP address configuration may +be impossible. The problem happens in case when: + +* The listener socket is configured to listen for "any" address +* The host running the listener has at least 2 IP addresses with the same prefix +* The address that the caller is contacting is not the first of these IP addresses + +The "any" address is defined in IPv4 as 0.0.0.0 (`INADDR_ANY`) and in IPv6 as +:: (`in6addr_any`). Applications usually implement it by clearing the +`sockaddr*` structure and only setting the port number. + +When the listener is bound to such an address, the outgoing packet's source +address will be set automatically by the system as the first found and +appropriate for the destination, which may differ to the address to which +the caller has sent the request. If this happens, the connection will be +rejected because the caller will receive the listener's response from a +different IP address than the one to which it sent the request. This problem has been observed where an agent's host has at least two IP addresses that share the same broadcast prefix, and it is being contacted @@ -613,6 +702,20 @@ when it is expected to be revived. [:arrow_up:   Back to List of Build Options](#list-of-build-options) +#### USE_MUTEX_ATOMIC +**`--use-mutex-atomic`** (default: OFF) + +Use srt::sync::Mutex to implement srt::sync::atomic. This is an ultimate +method used in case when no better implementation was found. The code is +probing various methods, such as: + +* C++ standard library +* Compiler-specific method + +If none of these is available, the Mutex-based implementation is used as an +ultimate fallback. This option enforces if, even if a better method can be +found. + #### USE_CXX_STD **`--use-c++-std=`** diff --git a/docs/build/build-win.md b/docs/build/build-win.md index 1a7d7a754..bbce49708 100644 --- a/docs/build/build-win.md +++ b/docs/build/build-win.md @@ -305,5 +305,5 @@ In case NuGet was used to get pre-built pthreads library, provide: ### 3.3. Build SRT ```shell -cmake --build . +cmake --build . --parallel ``` diff --git a/docs/dev/acknowledgement.md b/docs/dev/acknowledgement.md new file mode 100644 index 000000000..34e40dd5d --- /dev/null +++ b/docs/dev/acknowledgement.md @@ -0,0 +1,125 @@ +# Acknowledgement + +Acknowledgement - or ACK for short - is a mechanism of a feedback provided +by the receiver to the sender containing the basic information about the +received status, mainly concerning the sequence number pointing the position +in the stream up to where everything has been correctly received, or at least +considered so. + + +## Receiving ACK - safety considerations + +For safety reasons, we can't take the ACK sequence as a good deal. +This value must be verified and checked if: + +1. The value isn't newer than the last sent, although: + - For backup groups, we can accept ACKs that exceed the sent packets, + however ACKs are NOT EXPECTED on an idle link. This means if such + ACK comes we treat it as an IPE, but as fallback we shift the ACK + position not more than to the last sent packet in the group data + - For multilink-type groups, an ACK is allowed to exceed + the current sent sequence for a link, but still must not exceed the + latest packet sent for the group. Group-excessive ACKs should break + the connection, but ACKs that only exceed the link latest packet + should reset the latest sent sequence and clear the sender buffer +2. The value isn't older than the newest ACK. Such packets may happen, + but due to some random network condition and therefore can't be from + upside treated as a rogue protocol case, only silently skipped. +3. The value doesn't shift ACK by more than the current size of the + sender buffer, unless the buffer is empty. +4. (Proposed) The value doesn't shift ACK by more than 4 times the total + size of the sender buffer, if the buffer is empty, in which case the link + should be immediately broken. + +IMPORTANT: if the sender buffer is empty, then the base sequence number for it +can be set to whatever value. In the old UDT implementation the sender buffer +also didn't manage sequence numbers at all, they were set to packets only when +they were sent. In SRT with the introduction of groups the management of +sequence numbers was necessary for the sender buffer because when a packet is +going to be sent over multiple links at a time (broadcast example), then the +packet with the same payload, to be identified as the same packet against the +receiver application, must go also with the same sequence number - hence +sequence numbers must be dictated at the scheduling time and also be ready to +override the existing sequence number values if they collide with those. + +## Sending ACK and conditions + +ACK is tried to be sent on the SYN timer, and the following conditions are +checked: + +* ACK time has come, that is, the value of `m_tsNextACKTime` is earlier than +the current time. + +* The Congestion Controller defines the maximum packets to elapse since the +last ACK and this number has been exceeded (recorded in `m_iPktCount`). Note +that none of the current CCs ("live" and "file") defines that value. + +* 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_tsNextACKTime`. In this case a "lite ACK" is sent (see the use of +`SEND_LITE_ACK`), which doesn't contain statistical data and nothing more than +just the ACK number. The "fat ACK" packets (with the complete data defined for +the `UMSG_ACK`) will be still sent normally according to the timely rules. + + +## ACK and groups + +In case of groups the ACK is sent: + +* in case of multilink-type groups (Broadcast and potentially Balancing), + ACK is being sent over every connection, just like with multiple sockets +* in case of Backup-type group, ACK is sent only over the currently active links + +1. Special handling for Backup groups + +In the case of a Backup-type group, IDLE links are considered to never +sending any packets, hence nothing is to be acknowledged. The problem is +that normally the buffering activities were interconnected with ACK-ing, +however in case of group reception the common receiver buffering causes +that the fact of having received a packet IN THE BUFFER doesn't simultaneously +mean that the packet was received OVER THIS LINK. + +So, first, check if this link was IDLE. For IDLE links, ACKs should not +be sent at all. + +There is one more small problem though. When a link is being silenced, +then it should turn from RUNNING to IDLE, however the recognition of +this fact is only possible at the moment when the first KEEPALIVE arrives +after the data stop coming. The problem of the wrong ACK could occur +just as well during this period. + +Therefore the best way is, beside rejecting ACK on non-RUNNING links, in case +of RUNNING state, additionally there should be checked if the last sent +sequence exceeds the current last ACK received. If not, also no ACK should be +sent, even if the noncontiguous sequence was shifted. + +The check must be done regardless if anything has arrived over THIS LINK since +the last ACK. This is still being done for the backup group only because only +in case of this group there can happen an immediate stop of the transmission on +one of the links ("silencing"), of which the receiver has no idea. In multilink +type groups you can safely send ACK basing on the latest contiguous sequence in +the buffer because all links are supposed to be active and deliver packets. + +Note also that the IDLE state on the receiver side is only notified upon +reception of KEEPALIVE. Until then it's simply a link that doesn't deliver +data. + +``` +Consider adding a method of recognizing the IDLE links by having the +number of packets received from another link exceed some predefined number or +time, while over the link in question nothing was received. +``` + +It would be nice to do a sanity check if this sequence isn't in the past for +the buffer, but there's no point in doing it by two reasons: + +1. In this implementation the alleged ACK sequence is taken exclusively from +the buffer, so there's no possibility that it took a sequence of the loss being +in the past towards the buffer that was incorrectly removed, which was a +problem in the past. This implementation doesn't look into the loss record at +all. + +2. Taking the start sequence of the buffer requires checking it separately for +the socket and for the group, use separate mutexes etc., and just to do a +sanity check it's not worth a shot. + diff --git a/docs/dev/developers-guide.md b/docs/dev/developers-guide.md index 3b0e1c7b6..3db871cf4 100644 --- a/docs/dev/developers-guide.md +++ b/docs/dev/developers-guide.md @@ -1,16 +1,21 @@ # SRT Developer's Guide -* [Development Setup](#development-setup) - * [Installing Dependencies](#installing-dependencies) - * [Forking SRT on GitHub](#forking-srt-on-github) - * [Building SRT](#building-srt) -* [Project Structure](#project-structure) -* [Versioning](#versioning) -* [Coding Rules](#coding-rules) -* [Submitting an Issue](#submitting-an-issue) -* [Submitting a Pull Request](#submitting-a-pull-request) - * [Commit Message Format](#commit-message-format) -* [Generated files](#generated-files) +- [SRT Developer's Guide](#srt-developers-guide) + - [Development Setup](#development-setup) + - [Installing Dependencies](#installing-dependencies) + - [Forking SRT on GitHub](#forking-srt-on-github) + - [Building SRT](#building-srt) + - [Language standard requirements](#language-standard-requirements) + - [Project Structure](#project-structure) + - [Versioning](#versioning) + - [Coding Rules](#coding-rules) + - [Submitting an Issue](#submitting-an-issue) + - [Submitting a Pull Request](#submitting-a-pull-request) + - [Commit Message Format](#commit-message-format) + - [Generated files](#generated-files) + - [Logging functional areas](#logging-functional-areas) + - [Build options](#build-options) + - [Release and configuration management](#release-and-configuration-management) ## Development Setup @@ -64,7 +69,27 @@ mkdir _build && cd _build cmake .. -DENABLE_UNITTESTS=ON # Build SRT. -cmake --build ./ +cmake --build ./ --parallel +``` + +**Note.** Before submitting a pull request, it's recommended to build and test with the following configuration used in the [GitHub CI workflow](../../.github/workflows): + +```shell +# Configure with CI options to catch potential issues early. +cmake .. \ + -DCMAKE_COMPILE_WARNING_AS_ERROR=ON \ + -DENABLE_STDCXX_SYNC=ON \ + -DENABLE_ENCRYPTION=ON \ + -DENABLE_UNITTESTS=ON \ + -DENABLE_BONDING=ON \ + -DENABLE_TESTING=ON \ + -DENABLE_EXAMPLES=ON \ + -DENABLE_CODE_COVERAGE=ON \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +# Build and run tests. +cmake --build . +ctest --extra-verbose ``` **Note.** If you are using Windows, please refer to [Building SRT for Windows](../build/build-win.md) instructions. @@ -276,6 +301,32 @@ does its best to make sure that no option is missing. Note that some options might be provided by an external dependent script (like `build-gmock`) and therefore mistakenly added to the list. +### Release and configuration management + +The release management follows the rules of the "Git Flow" tool. + +The `master` branch should contain the version on the latest stable release. + +The ongoing development should be merged to `dev` branch. + +New feature branches should be drawn off `dev` branch. They should use +`feature/` prefix. + +Finished feature branches should be merged back to `dev`. + +Once a release is defined, a new release branch is drawn off `dev` +and it has a name with `release/` prefix. All bugfix branches for this +release are merged to this branch, and then `dev` branch updated +with this fix. + +Once the release reaches stability, it is merged to `master`. Any +hotfixes are merged to the release branch, and then `master` is updated, +then so is the `dev` branch. + +The `git-flow` tool can be used to support this workflow, but it can be +also implemented manually. + + [git-setup]: https://help.github.com/articles/set-up-git [github-issues]: https://github.com/Haivision/srt/issues [github-guide-prs]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork diff --git a/docs/dev/development-notes.md b/docs/dev/development-notes.md new file mode 100644 index 000000000..82b9ca946 --- /dev/null +++ b/docs/dev/development-notes.md @@ -0,0 +1,221 @@ +# Introduction + +These are loosely placed notes with some interesting information about the +development solutions and design choices used in various parts in SRT, mainly +extracted from long comments. + +## Common receiver buffer: removed socket-buffer synchronization + +The initial implementation was using multiple sockets, each one with their own +buffer, and the application was being fed with data read from appropriate buffer, +while duplicates from other parallelly received links were discarded. This required +also a specific procedure for TSBPD. + +This was done only for "old bonding" using the app-reader procedure. In the +new bonding all buffer reception and reading ready state update happen +exclusively inside the group. + +Note that this is only true in TSBPD mode. In file mode, the ACK signoff is +still in force, maybe not for the buffer, but still for the read state update, +where the read declaration is done only when ACK has moved the ACK pointer some +sequences in forward. The problem with properly implementing this is that +reading happens from the group and it is done directly from the group buffer +(without any involvement of the socket), but ACK action is a timer-loop action +executed by a socket. These activities happen on two different timers and on +two different moments, therefore likely it must be implemented somehow in the +group. + +Used code: + +``` + if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) + { + // See above explanation for double-checking + SharedLock 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); + } + } +``` + +## TSBPD synchronization on packet arrival + +This is mainly happening in the `CUDT::acquireDataPacket` function, which is +called when the incoming UDP packet has been recognized as containing a payload +for particular socket. The packet should be eventually inserted into the buffer, +but this need not be done, depending on various conditions. + +In TSBPD mode there's also a special thread, which marks packets ready to +retrieve by application; because of that it must sleep until the time comes +for marking the packet ready. The definition of "coming time" is complicated, +through. "Sleeping" is done on a CV because in several situations it must be +notified in order to be prematurely woken up: + +1. There are no packets in the buffer at the moment. In that case the CV +is locked forever, so it must be signaled when the new packet is inserted +into the buffer - or simply when the socket is closed. + +2. There are packets in the buffer, but there's an "initial gap" (the cell 0 +contains no packet, and more empty cells may follow, but there is finally +somewhere a packet). In that case CV is locked timely until the play time +of that existing packet. Therefore the CV must be signaled if a newly +inserted packet is inserted before this earliest packet. Even if the play +time for that packet wouldn't come, TSBPD must simply refresh its state, +that is, either mark the packet ready, or fall asleep again, but this time +up to the time of the newly inserted packet. + +3. There has been already one packet marked as ready for retrieval, but the +application hasn't responded for that yet. Therefore TSBPD sleeps forever +again, and it will have to be woken up by notifying CV when the application +finally extracts that packet, and after extraction there are no more playable +packets at the moment (that is, either there aren't any, so the CV is again +locked forever, or there is a packet with play time in the future, so the +CV will be locked timely). + +One of the important part of this condition is the marker of the "CV locked +forever" state, which is `m_bTsbpdNeedsWakeup` field: + +- `m_bTsbPdNeedsWakeup` is set by TSBPD thread and means that it wishes to be + woken up on every received packet. Hence we signal always if a new packet was + inserted. + +- even if TSBPD doesn't wish to be woken up on every reception (because it + sleeps until the play time of the next deliverable packet), it will be woken up + when `next_tsbpd_avail` is set because it means this time is earlier than the + time until which TSBPD sleeps, so it must be woken up prematurely. It might be + more performant to simply update the sleeping end time of TSBPD, but there's no + way to do it, so we simply wake TSBPD up and count on that it will update its + sleeping settings. + +**To Consider**: as `CUniqueSync` locks `m_RecvLock`, it means that the next +instruction gets run only when TSBPD falls asleep again. Might be a good idea +to record the TSBPD end sleeping time - as an alternative to +`m_bTsbPdNeedsWakeup` - and after locking a mutex check this time again and +compare it against `next_tsbpd_avail`; might be that if this difference is +smaller than "dirac" (could be hard to reliably compare this time, unless it's +set from this very value), there's no need to wake the TSBPD thread because it +will wake up on time requirement at the right time anyway. + +**To Consider**: sleeping on a CV with time causes quite a big CPU usage. In +the previous attempts there was tried safety condition to never sleep forever +and use maximum 1 second sleep as a safety precaution. This had to be removed +(and the risky forever-sleeping, ensured by having always a condition to signal +the CV) because just this addition has caused increased CPU usage by 2%. +Therefore there might be some better method used, maybe based on some event +library, such as `libev`. The CV should still stay there, but it could be +just locking always forever so that it is notified when ready, or even simpler, +as this is only about making the socket ready in the epoll system, and release +the blocking-mode read through another CV, do this exactly thing directly through +the timer functionality. + + +## Discarding acknowledged packets + +When the packet comes in and its sequence number is recognized, it is attempted +to be inserted into the buffer, at the position being an offset towards the +initial sequence of the buffer, but this attempt will not be made and the packet +will be discarded if: + +* The position is negative (the sequence is in the past for the buffer) +* The sequence is in the acknowledged region of the buffer + +There might be a controversy as to whether the packet should be discarded with +regard for the `SRTO_TLPKTDROP` option, which allows to drop packets if they +are not recovered on time, so rejecting a packet basing on the sequence number +seems to risk dropping a packet, which's cell is currently empty. + +This will not happen because if we have a situation that 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. + +The mechanism of the actual dropping the packet is always based on the state, +where the very first cell of the buffer is empty, followed by any first valid +packet, and the play time for that packet has come - until then no packets are +dropped. Then, when TSBPD decides to drop these initial empty cells, we'll +have: (`m_iRcvLastAck <% buffer->getStartSeqNo()`) - and if so, the position in +the buffer will also result in being negative (so, handled by the first +condition). + +The only case when the buffer position is positive, but packet's seq is earlier +than `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. + + +## Receiver buffer + +The receiver buffer is the object that should keep packets until they are +retrieved by the application. Simultaneously it should keep data packets in +their sending order, keep an eye on the losses and provide retransmission +information, and also, depending on configuration, handle dropping. + +The new resolution for providing the memory for incoming packet is based on +the ownership passing and so-called "condensation": the original object that +gets the packet memory to be filled by the read packet from the system is +the multiplexer; they are organized in so-called units. The multiplexer picks +up a unit from its own container, fills in the packet through the call of the +`recvmsg` function, and then dispatches it. Some packets are dispatched in +place and the unit remains in the multiplexer - others, data packets are +passed to the receiver buffer for insertion (which need not succeed), possibly +together with additional packets that could be provided by the packet filter +(if installed). If insertion doesn't happen, the packet is returned to the +multiplexer that provided them, otherwise the unit with the packet inside +is transferred ownership to the receiver buffer. + +Reading the data by the application happens with different conditions +depending on the configuration: + +1. In case of stream reading, the reading is enabled at the time when the ACK +period comes (this triggers readable condition in epoll and CV used in blocking +mode) and available for retrieval are all data since the first cell of the +receiver buffer up to the first gap, or up to the end of the buffer if there +are no loss gaps. Any gap blocks the reading, even if this would block forever. +Extracted is only so much data, as fit in the buffer and are available; in case +of a smaller application's buffer the remaining data are kept in the buffer; +if the border is in the middle of a single packet, the notch is marked to point +the position of continued reading for the next call. + +2. In case of message mode reading, a single message, that is, a consecutive +series of packets that have the same message number, can be retrieved, if it is +complete. Additionally, the `inorder` flag set to true (decided at the sender +side) states that if this message is the first one, it must be completed and +delivered before any other message with inorder flag set is delivered. If this +flag is not set, and a message with that flag unset is reassembled earlier than +any other message, which's part is in the buffer, it is allowed to be extracted +by the application out of order. In that case the message remains in the buffer +with only a special flag marking the already-retrieved status; as then earlier +messages are finally retrieved, units carrying this message will be +decommissioned right after decommission of packets carrying earlier messages. + +3. In case of live mode reading (`SRTO_TSBPDMODE` is true), the following rules +apply: + +a. The application is only allowed to read exactly one whole packet at a time +(the `SRTO_PAYLOADSIZE` option defines the minimum buffer size, even if the +packet happens to be smaller). + +b. The TSBPD mode means that the packet is only allowed to be read when the +play time comes, and until then it stays in the buffer. The play time is +defined as the ETS (expected arrival time = first packet's arrival time plus +the difference between timestamps of this and the first packet) plus latency +plus drift. + +c. If `SRTO_TLPKTDROP` is set (default in live mode), the play time counts +also as the time to drop packets that haven't been recovered and remain as +lost. Dropping is done exclusively when the packet at the very first cell +is empty and the first packet following the initial gap has the play time +already in the past. + +For that reason, the buffer has appropriate offset-pointers that allow +tracking the initial gap and the drop point. + +The internal design of the buffer is described in the +[separate document](receiver-buffer.md). + + + diff --git a/docs/dev/receiver-buffer.md b/docs/dev/receiver-buffer.md new file mode 100644 index 000000000..ce75e605f --- /dev/null +++ b/docs/dev/receiver-buffer.md @@ -0,0 +1,492 @@ +# Receiver buffer + +The receiver buffer in SRT is a circular buffer keeping pointed pieces of memory +containing the data in their desired order, split into packets the way they were +received. + +As it is a circular buffer, it is based on a solid array with fixed size, but +as initial portion of the buffer gets decommissioned, the position of the first +cell may be shifted into the middle of the array. The logical position is +mapped to the physical index by shifting the position and wrapping around the +container size; the index of the logical position 0 - "first cell" - is set to +`m_iStartPos` field. Most of the other meaningful positions are kept as logical +positions, represented as "offset", and so are marked the field names: + +* ...Off : carries an offset - a relative position towards the first cell +* ...Pos : carries the exact (physical) index to the array + +Every cell of the container represents a sequence number of the packet. The +sequence number of the first cell is kept in the `m_iStartSeqNo` field. The +distance between this sequence and incoming sequence is turned into an "offset" +(logical position) and this is then turned into the physical index basing +on `m_iStartPos` and wrapping around the size. If "offset" is outside the +buffer capacity (or negative), the packet is discarded. + +The packets come in as memory blocks already filled with data, which are only +pinned into the cells of the buffer. How the memory management is organized you +can see in a [separate document](receiver-unit-pool.md). + +## Development support + +You might see that there are used `CPos` and `COff` types in the definition. +They are both aliases to `int`, at least in the normal code configuration. +This distinction is for two purposes: to keep the logics separate, where a +value concerns the physical position indexing the `m_entries` container +(`CPos`) and the logical position in the buffer that should span from 0 +up to `hsize()` (`COff`). In this code they are easy to mix up and this ends +with lots of undetectable errors. You can enable then a special configuration +of the code by changing `USE_WRAPPERS` and `USE_OPERATORS` macros to 1. + +The `USE_WRAPPERS` macro enables to use special wrapper classes for these +two types and `USE_OPERATORS` enables additionally overloaded operators that +control the operations for correctness. With these enabled you should try to +compile the code and the compiler will detect every misuse of these types +or invalid application. + + +## Working modes + +On the other end packets are being extracted using various ways depending on +the mode: + +### live mode + +Only one packet at a time, usually the one at position 0 and only if the play +time has come. Play time (known as TSBPD time) is the alleged sending time +recorded in the packet's timestamp converted to local time with the addition +of latency and drift correction. The packet is ready to read if the play time +is in the past. If dropping is enabled, the cell at position 0 is empty, and +there is at least one packet in the buffer that is ready to read, empty cells +are dropped. Otherwise reading will not be available until the packet at +position 0 is available. + +### stow mode (CONCEPT) + +This is identical as live mode, except that the play time is only given as a +reference and if there is a valid packet at cell 0, it is always readable. +Dropping rules remain intact - a next-to-gap packet may still be only delivered +when its play time has come. + +### message mode + +A single message may consist of one or more packets and extraction is possible +only if the whole message is reassembled. Normally only the first found message +in the buffer can be extracted, but there is also allowed out-of-order reading +for messages that have the `inorder` flag cleared - in this case first +reassembled message of that type is allowed to be extracted, even if it follows +an earlier incomplete message; packets for this message are then marked as +"read" so that this message will be skipped when preceding messages are +extracted. + +The messages can be also set expiration time; if this time is exceeded, the +message will not be sent, and if it is incomplete on the receiver side, the +whole message will be discarded. This happens through sending the drop request +by the sender side, which means that packets for that message will not be +sent anymore; in result the whole message is removed from the buffer. + +### stream mode + +Extracted are as much data as available and fit in the given buffer. Available +are all data from the packet at cell 0 up to the first gap or the last packet +ever received (whichever comes first). In case when the buffer is shorter than +the available data and reading would have to stop in the middle of a single +packet, the notch marker is used to mark the position for the next reading. + + +## Terminology + +The buffer may or may not deliver data basing on various conditions, mainly +represented by the regions that embrace the range of buffer cells. + +The buffer has a fixed capacity, but not all cells need to be in use at the +moment. + +* BUSY REGION: It starts with the first (logical) cell and ends with the latest +cell for which a packet has ever been received; naturally empty cells may +happen to be inside this region in case when the packet was lost. + +* SPARE REGION: the region of the buffer with existing cells, but following +the last cell of the Busy Region until the end of capacity. + +Inside the BUSY REGION there are regions that define the rules for packet +delivery, where the most important is the "first cell", that is the cell +at logical index 0 (physical index == `m_iStartPos`): + +* ICR: Initial Contiguous Region: This is present if the first cell contains +a valid packet and it continues until the first gap. If there are no gaps +(no packets were lost or nothing has arrived out of order), ICR is the same +as Busy Region. If the first cell is empty, so is ICR. + +* FIRST GAP: It starts with the empty cell that follows the last cell of ICR, +if ICR is shorter than the Busy Region (including empty), and continues with +consecutive empty cells until the first following filled cell (Drop Target). + +* SCRAP REGION: Region with possibly filled or empty cells. It starts with +the first gap and ends with the end of Busy Region. NOTE that in the scrap +region the very first cell is empty and the very last cell is filled. + +* DROP TARGET: a filled cell that immediately follows the First Gap. May +be not present in case when there's no Scrap Region. + +## Design chart and explanations + +``` + + | BUSY REGION | + | | | | + | ICR | SCRAP REGION | SPARE REGION...-> + ......->| | | | + | | /FIRST-GAP | | + |<------------------- m_entries.size() ---------------------------->| + | |<------------ m_iMaxPosOff ----------->| | + | | | | | | + +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_entries + +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ + | \ FIRST | | | + | CELL | | \__last pkt received + |<------------->| m_iDropOff | + | | | + |<--------->| m_iEndOff | + | + \___ m_iStartPos: first packet position in the buffer + +``` + +## The general buffer's fields + +Beside the `m_entries` array, the buffer contains the fields operating the cicrular +buffer: + +* `m_iStartPos`: Physical index of the first logical cell +* `m_iMaxPosOff`: past-the-end of the BUSY REGION + +And the fields for specific regions: + +* `m_iEndOff`: offset to the past-the-end of ICR. This points always to an +empty cell. If this value is 0, the buffer has an empty ICR. If this value is +equal to `m_iMaxPosOff`, the whole buffer is contiguous (no FIRST GAP or DROP +REGION) + +* `m_iDropOff`: offset to a packet available for retrieval after a drop, that +is, DROP TARGET. If there is no SCRAP REGION, this value is 0, otherwise it +points to a valid packet following the FIRST GAP. + +* `m_iFirstNonreadPos`: physical position to either the first empty cell, or +the first cell with `PB_FIRST` boundary flag (message mode only) + +The DROP TARGET is determined in order to quickly perform the dropping +operation. In case when the last packet has been extracted from the ICR, the +cell 0 is empty. When the decision for an extraction-over-consistency is +undertaken (live mode with too-late-packet-drop enabled, when the play time +comes for the packet at the DROP TARGET), the whole FIRST GAP is removed +from the buffer so the beginning of the buffer shifts to the DROP TARGET, which +becomes the first of ICR this way, and then the valid packet at cell 0 is +extracted. + + +## Entries + +Every cell in `m_entries` array is an object of type `Entry`, which contains: + + * pUnit: handle to the unit (payload memory), or empty handle + * muxID: multiplexer ID that provided the unit (groups only) + * status: + * `EntryState_Empty`: No packet was ever received here (pUnit is empty) + * `EntryState_Avail`: The packet is ready for reading + * `EntryState_Read`: The packet was extracted (out of order) + * `EntryState_Drop`: The packet was requested to drop + +Status values used normally (live and stream mode): + +* `EntryState_Empty`: No packet here (pUnit == NULL). This is the default +state before getting a packet and the state of the units in the spare region + +* `EntryState_Avail`: The packet is ready for reading. It is set just after +the position has been confirmed and the unit pointer written into `pUnit` + +In message mode additionally two other states are possible: + +* `EntryState_Read`: The packet was prematurely extracted by out-of-order +reading. The space must be still occupied because it's in the scrap region +following some fragmented message, which still waits for reassembly (or +dropping) + +* `EntryState_Drop`: The message was requested to be dropped. This usually +happens when the timeout for the message passed and the message is still +not reassembled, or when the message was revoked from the sender buffer +by the peer, so the peer has sent the `UMSG_DROPREQ`, making the packets +no longer recoverable. If this happens, all messages containing dropped +packets are marked Drop state and will no longer be delivered. This is +only happening in case when the drop request referred to cells that follow +any message still waiting for reassembly; if the drop request referred +to a message at the start of the buffer, these cells are simply removed. + + +## Operational rules + +Initially: +``` + m_iStartPos = 0 + m_iEndOff = 0 + m_iDropOff = 0 +``` + +When a packet has arrived, then depending on where it landed: + +1. Position: next to the last received one and newest + +``` + m_iStartPos unchanged. + m_iEndOff shifted by 1 + m_iDropOff unchanged +``` + +2. Position: after a loss, newest. + +``` + m_iStartPos unchanged. + m_iEndOff unchanged. + m_iDropOff: + - set to this packet's position, if m_iDropOff == 0 + - otherwise unchanged +``` + +3. Position: after a loss, but belated (retransmitted) -- not equal to `m_iEndPos` + +``` + m_iStartPos unchanged. + m_iEndOff unchanged. + m_iDropOff: set to this packet's position if: + - m_iDropOff == 0 + - DROP TARGET %> this sequence (still a drop, but updated) + - otherwise unchanged +``` + +4. Position: after a loss, sealing -- seq equal to position of `m_iEndOff` + +``` + m_iStartPos unchanged. + m_iEndOff: set to the first found empty cell since the current position (up to m_iMaxPosOff) + m_iDropOff: + - if m_iEndOff == m_iMaxPosOff, set to 0 + - otherwise search for the first filled cell starting from m_iEndOff (up to m_iMaxPosOff) +``` + +NOTE: + +If there are no "after gap" packets, then `m_iMaxPosOff` == `m_iEndOff`. If +there is one existing packet, then one loss, then one filled packet: + +``` + [*] [ ] [*] {-} + ^ ^ ^ ^ +start / / / +m_iEndOff / / +m_iDropOff^ / +m_iMaxPosOff-^ + +``` +You have: +* `m_iEndOff` = 1 +* `m_iDropOff` = 2 +* `m_iMaxPosOff` = 3 + +The ICR contains 1 packet at position 0, following SCRAP REGION for +positions 1 and 2, and so ends the BUSY REGION. + +Cases for inserting a packet: + +Let's say we have the following possibilities in a general scheme: + + +``` + [D] [C] [B] [A] (insertion cases) + | (start) --- (end) ===[gap]=== (after-loss) ... (max-pos) | +``` + +See the `CRcvBuffer::updatePosInfo` method for detailed implementation. + +### WHEN INSERTING A NEW PACKET: + +If the incoming sequence maps to newpktpos that is: + +* newpktpos <% (start) : discard the packet and exit +* newpktpos %> (size) : report discrepancy, discard and exit +* newpktpos %> (start) and: + * EXISTS: discard and exit (NOTE: could be also < (end)) + +* seq == `m_iMaxPosOff` [A] +``` + --> INC m_iMaxPosOff + * m_iEndOff == previous m_iMaxPosOff + * previous m_iMaxPosOff + 1 == m_iMaxPosOff + --> m_iEndOff = m_iMaxPosOff + --> m_iDropOff == 0 + * otherwise (means the new packet caused a gap) + --> m_iEndOff REMAINS UNCHANGED + --> m_iDropOff = m_iMaxPosOff - 1 +``` +COMMENTARY: + +If this above condition isn't satisfied, then there are gaps, first at +`m_iEndOff`, and `m_iDropOff` is at furthest equal to `m_iMaxPosOff`-1. The +inserted packet is outside both the contiguous region and the following +Scrapped Region, so no updates on `m_iEndOff` and `m_iDropOff` are necessary. + +NOTE: + +SINCE THIS PLACE on, seq cannot be a sequence of an existing packet, +which means that earliest newpktpos is at `m_iEndOff`, up to == `m_iMaxPosOff` - 2. + +``` + * otherwise (newpktpos <% max-pos): + [D]* newpktpos offset == m_iEndOff: + --> (search FIRST GAP and FIRST AFTER-GAP) + --> m_iEndOff: increase until reaching m_iMaxPosOff or an empty cell + * m_iEndOff < m_iMaxPosOff: + --> m_iDropOff = first FILLED packet since m_iEndOff + 1 + * otherwise: + --> m_iDropOff = 0 + [B]* newpktpos offset > m_iDropOff + --> store, but do not update anything + [C]* otherwise (newpktpos offset > m_iEndOff AND < m_iDropOff) + --> store + --> set m_iDropOff = newpktpos offset +``` + +COMMENTARY: + +It is guaratneed that between `m_iEndOff` and `m_iDropOff` there is only a gap +(series of empty cells). So wherever this packet lands, if it's next to +`m_iEndOff` and before `m_iDropOff` it will be the only packet that violates the +gap, hence this can be the only drop pos preceding the previous `m_iDropOff`. + +The information returned to the caller should contain: + +1. Whether adding to the buffer was successful. + +2. Whether the "freshest" retrievable packet has been changed, that is: + * in live mode, a newly added packet has earlier delivery time than one before + * in stream mode, the newly added packet was at cell[0] + * in message mode, if the newly added packet has: + * completed the very first message + * completed any message further than first that has out-of-order flag + +The information about a changed packet is important for the caller in live mode +in order to notify the TSBPD thread. + + +### WHEN CHECKING A PACKET (for readability) + +1. Check the position at `m_iStartPos`. If there is a packet, return info at its +position. Note that this sole packet means readability unconditionally only in +the stream mode. In live mode it must have also the play time in the past, and +for the message mode only if it has `PB_SOLO` boundary flag, or otherwise it +must be followed by packets with the same message number up to the last of them +with `PB_LAST` boundary flag. + +2. If position on `m_iStartPos` is empty, get the value of `m_iDropOff`. + +NOTE THAT: + + * 0 is the trap representation for both `m_iEndOff` and `m_iDropOff`; + they are both 0 in case of an empty buffer, although this is easiest + to check if `m_iMaxPosOff` == 0 + * if there is a packet in the buffer, but the first cell is empty, + then `m_iDropOff` points to this packet, while `m_iEndOff` == 0. + Check then `m_iEndOff` == 0 to recognize it, and if then + `m_iDropOff` != 0, you can read with dropping. + * If cell[0] is valid, there could be only at worst cell[1] empty + and `m_iDropOff` == 2. Note that `m_iDropOff` is not always updated + in case when cell[0] is filled, so you should not check that in this case + +3. In case of time-based checking for live mode, return empty packet info, +if this packet's time is later than given time. + + +### WHEN EXTRACTING A PACKET + +1. Direct extraction is only possible if there is a packet at cell[0]. + +2. If there's no packet at cell[0], the application may request to + drop up to the given packet, or drop the whole message up to + the beginning of the next message. + +3. In message mode, extraction can only extract a full message, so + if there's no full message ready, nothing is extracted (even if the + cell[0] is filled). + +4. When the extraction region is defined, the `m_iStartPos` is shifted + by the number of extracted packets. + +5. After updating of `m_iStartPos`, if `m_iEndOff` would point to 0 or + before the first position, it must be set to 0 and then shifted to the + first found gap or `m_iMaxPosOff`. + +6. `m_iDropOff` must be always updated. If `m_iEndOff` == `m_iMaxPosOff`, + `m_iDropOff` is set to 0. Otherwise start from `m_iEndOff` + and search a filled packet up to `m_iMaxPosOff`. + +7. NOTE: all `*Off` fields are offsets, hence they must be all set anew after + update for `m_iStartPos`. + +## The looping function: `walkEntries` + +This function should be used in all cases when you have a loop over a range +of cells of the buffer. This offers a better performance than a usual looping +over iterated physical position values, while this iteration involves rolling +over the index. This function does all required translations only once per +the call, turning then the loops into one or two separate loops, each one +iterating using the physical position directly, treated as plain integer. + +Even though `CPos` should normally be operated like a circular number, in this +function there's used manual counting because `endoff` defines past-the-end, so +the position shall be allowed to be equal to `hsize()`, which isn't possible +with `incPos()`. + +In the beginning this function calculates the values for `startpos` and +`endpos` by first checking three possible cases against `start_avail`, which is +the size of the distance between the current start position and the end of +container: + +1. Start offset already exceeds `start_avail` + +This means that the position designated by the start offset is already +past the rollover, hence the end offset is also in the "new region". +Needs walking through one fragment of the container only. + +Example: + +* Buffer: capacity=16 startpos=10 (`start_avail` = 6) +* Parameters: startoff=7 endoff=10 +* begin = 10+7 = 17 - 16 = 1; end = 20-16 = 4 +* One loop: [1] - [3] + +2. End offset is less than `start_avail` + +Stating that (verified by assertion, of course) `startoff < endoff`, when +`endoff` fitting in the "old region", so fits the `startoff`. Loop only +once inside the "old region". + +Example: + +* Buffer: capacity=16 startpos=10 +* Parameters: startoff=0 endoff=5 +* begin = 10; end = 10+5 = 15 +* One loop: [10] - [14] + +3. Otherwise we have to walk separately two fragments. + +In all other cases you have one range from the position designated +by the `startoff` up to the end of container, and then the second +loop must start from position 0 and walk up to a position designated +by `endoff`. + +* Example: +* Buffer: capacity=16 startpos=10 +* Parameters: startoff=5 endoff=10 +* begin = 10+4 = 14; end = 10+10 = 20 - 16 = 4 +* First loop: [14] - [15] +* Second loop: [0] - [3] + diff --git a/docs/dev/receiver-unit-pool.md b/docs/dev/receiver-unit-pool.md new file mode 100644 index 000000000..6d29c42a6 --- /dev/null +++ b/docs/dev/receiver-unit-pool.md @@ -0,0 +1,235 @@ +# Unit pool + +This is the facility for managing memory for incoming packets and receiver +buffer. This solution is using passing ownership, but done a bit shadowy +way due to required compatibility with C++03 and inaccessibility of move +semantics. + +## History + +The original facility for memory management for incoming packets and +receiver buffer, as provided for the UDT library was using the +`CUnitQueue` class that was working through leasing the memory blocks +to the receiver buffer. That was working on the following premises: + +* The multiplexer is the owner of the queue and all blocks allocated +by it. + +* The receiver buffer is propertied to the socket, while one multiplexer +can be used by multiple sockets. This means that once the packet buffer +is placed into the socket's receiver buffer, it's leased to this buffer; +once the unit is decommissioned (after the contents have been delivered +to the application) this unit was changing the status to free and could +be reused. + +* The memory was allocated through big single blocks that were split +into single-packet buffer units. That was possible because the multiplexer +was the owner of this memory all the time and the current use status +was changed through leasing. + +This solution had however limitations: + +1. Once the socket is bound (that is, assigned to a multiplexer), it could +since that moment potentially contain packets in its receiver buffer. This +means that the multiplexer is the higher object in the hierarchy and the socket +is bound to it forever. At the same time, the last socket bound to a +multiplexer must drag the multiplexer with itself when deleted, however you +can't "unbind" the socket because the receiver buffer must be deleted +completely (and this way return the leased units) before you delete the +contents of the multiplexer's unit queue. This puts additional limitations on +the socket closing procedure and caused extra troubles with the fix to make +the UDP socket closed as fast as possible. + +2. This solution is not possible to be used together with the a group that +would have its own receiver buffer (earlier versions of SRT were simply reusing +the sockets' buffers and deliver or discard packets directly from the sockets' +buffers). This introduces a nonexistent earlier dependence between the group +and the multiplexer, while a single connection could be closed while the group +still exists and is still capable of delivering packets to the application. +If there is a unit leased to this buffer by the multiplexer being closed at +the moment, then such a multiplexer would have to have its lifetime extended +up to the time when the receiver buffer entry keeping it will decommission it, +otherwise the receiver buffer would contain a dangling pointer to the unit. + +Because of this there was a need for a new receiver buffer memory management +facility. + +## The packet unit pool concept + +The new packet unit pool is based on the following premises: + +* Units are no longer leased, but the ownership can be passed between objects +* Reuse of the decommissioned units is treated as optimization, not limitation + +The following features were then not implemented: + +1. No more leasing: the unit is owned by the container where it is currently +stored. You delete the container - you delete the unit. If the unit gets lost +anywhere in the processing and no one bothered with returning it - the memory +will be simply reclaimed by the system, as usual. + +2. No large block allocation. That was possible with unchanged ownership, but +ownership passing rule requires that every unit is a single object on its own. +The impact it makes on often and repeating unit requests are smoothened by the +use of cache containers. + +The advantage is, however, that the multiplexer and the receiver buffer can be +now decoupled from one another. And the group receiver buffer could be finally +implemented properly. + +Note that theoretically you may think that the old solution still works for the +single sockets and problems are only with the group. That's not true simply +because as a multiplexer can be shared between sockets, it can be just as well +shared between member sockets, or even shared equally between single sockets +and group-member sockets - and this cannot be anyhow limited simply because +this happens on the listener sockets that can accept single connections as well +as group connections, and this socket's multiplexer will be then shared the +described way. In short: the multiplexer reads the packet and doesn't know +where it would have to be passed - this will be known after reading the header +information (after the whole packet, together with the payload, has been read) +and dispatching it to the right socket, begin a member socket or not. + +## Pool cache layers + +The units are cached in the following 3 containers: + +1. Hand (direct) +2. Solid (upper) +3. Condenser (lower) + +Each of them uses the "series" size, which is `SRT_RCV_BUFFER_POOL_MAX_SERIES` +(at this moment, 128). Hand and Condenser are simply 1-level containers of +units, while Solid is a container of containers of units. + +Hand is the container that is for the private use of the object and its worker +thread that contains it; as the only one it's not mutex-protected - the reason +is that the intention is to be affined to exclusively one thread - the one +that will be picking up units from it. If the Hand container is empty, the +refilling procedure is used, which takes one series from the Solid container, +and if this is also empty, simply allocates the memory from the system; even +if this should happen, it always allocates one series of units. + +Solid is a container of containers; each member container contains one series +of units. From this container the whole single container is extracted and +moved (swapped) with the Hand container. Solid is mutex-protected and only +its own mutex is locked for this operation. + +Condenser is a unit container, which should collect units that are returned +to the pool after being decommissioned. Until the whole series of units is +collected, nothing else happens. Otherwise the full-series condenser is +moved (swapped) into the Solid container ("uplifting"). This way the whole +series of units will be available when the Hand container needs it. This +uplifting operation requires locking both lower and upper mutexes. + +It is believed that this layout allows for separation for the unit pickup +for the multiplexer most of the time (it just uses Hand as its private +container without locking anything), while once per 128 pickups it will +have to do reallocation, or locking the upper (Solid) container only. +Locking the upper container will still rarely collide with returning the +units through the condenser, as the upper container will be only locked +when uplifting should happen. + +## Reading and dispatching packets + +Dispatching of the incoming packets happens in the multiplexer's receiver +thread, hence all control information will be executed immediately. So the +packet unit is taken by the multiplexer from the Hand container, filled +by the `recvmsg` call, then dispatched, but without removal yet - this is +not necessary if the packet turns out to be a control packet. In this case +the dispatching procedure will do its job and then this same unit can be +reused for the next reading. + +If the packet turns out to be a data packet however, it is removed from the +container immediately and kept temporarily in a local variable for the time +calling the dispatching function, which **may** take ownership od that unit. +If it didn't, the unit is then returned to the Hand container. This step is +unfortunately necessary by one reason: during the dispatching there's +potentially a packet filter to be executed. One of possible things it may do in +this case is to pick up additional units from the Hand container, therefore +the currently processed data packet must be first removed. The provision to +the filter may ship various combinations of results: + +1. If the incoming packet was a packet-filter control packet, this packet +needs to be interpreted, but not stored any further and should be able to +be immediately reused (returned directly to the Hand container). + +2. If this packet was a valid data packet, the dispatched will attempt to +store it into the receiver buffer. + +3. Regardless of these above, the packet filter, in response to provision +of this packet, may produce additional packets, which should be also +attempted to be inserted into the receiver buffer. The filter will need +to pick up units from the Hand container to store them. + +The result can be zero or more packets to be potentially inserted into +the buffer, of which some may be rejected. Packet units stored in the receiver +buffer get ownership transferred to the receiver buffer, others will be +returned to the Hand container. + +Packets stored in the receiver buffer are since this moment owned by the +buffer and they can be at best only passed ownership back to the multiplexer. + +Units then remain in the receiver buffer until the application's call results +in delivering this data; after that the unit is decommissioned and can be +returned. This process differs between a single socket and a group. + +## Single socket approach + +The version for a single socket, that is, where a receiver buffer may only +contain units provided by exactly one multiplexer, is simpler: the receiver +buffer keeps the pointer to the unit pool object. This pointer is valid as +long as the socket is bound (unbinding is not implemented at this moment, +but it is possible). After reading a packet by the application, the unit +is decommissioned, so the unit's ownership is passed to the multiplexer's +Condenser container. Then the unit may be potentially uplifted to the upper +container, and then picked up to the Hand container. + +## Group approach + +Once we have the single buffer for the group - that is, member sockets do not +have their receiver buffers, and they only operate with the link and the +multiplexer, while data packets are stored in the group's buffer and they are +only extracted by the application from there - there can be units potentially +provided by multiple different multiplexers. + +In general this isn't a problem because the receiver buffer owns the packets +anyway, and it can delete them directly if need be. However we still want to +have the memory allocation process optimized, so it is desired that the buffer +return these units to the very multiplexer, from which they have been +extracted, at least if this is possible. + +However returning them directly could be challenging for the object +persistence rules - regarding the fact that the group must be prepared to +outlive the multiplexer, while keeping the units received from it. Therefore +the very first rule is that every unit stored in the buffer has also an +information about the multiplexer ID from which it has come. Keeping the +pointer to the multiplexer (or be it even the pool) is not possible; this +would require persistence synchronization (such as clearing the pointer in +case when the multiplexer is deleted). So dispatching by multiplexer ID is +inevitable. + +This however is another challenge: doing global mutex locking and dispatching +a multiplexer for every packet to be "condensed" would put too much of a +burden. Therefore the groups are using the so-called "water container" - the +group's private condenser. + +As packets might come from different multiplexers, the water container is +a map with keys being the multiplexer ID and the value being unit containers. +Once a single number of series has been collected (regardless how many +units are stored at particular key), a series condensation is being done, +that is, under a single global lock every multiplexer is dispatched and +if this is succeeded, all units from the container assigned to its ID are +condensed. If the multiplexer is not found by ID, units simply remain +in the water container. Then, after all keys are looped over, the water +container is completely purged (units that were not returned to any +multiplexer simply get deleted here). + +Of course, this means that at once sometimes there's about "half of the +series" returned to the lower container, so only at any next time will +the single series be collected back and uplifted to the upper container. +Still, one of the containers will eventually uplift enough packets so +that the whole series can be reused. Keeping unused units in the pool's +condenser and the group's condenser is the matter of balance between +the usage of memory and CPU; possibly further optimizations for that +process may be needed. + diff --git a/docs/dev/retransmission.md b/docs/dev/retransmission.md new file mode 100644 index 000000000..21bcc5412 --- /dev/null +++ b/docs/dev/retransmission.md @@ -0,0 +1,255 @@ +# Retransmission + +Retransmission is the mechanism of recovering lost packets by sending them +again; also known as ARQ. + +As a derivative from UDT, which was the mechanism for file transmission, this +was essential for the reliability - the stream must be received exactly as +sent, as long as the connection is maintained. + +The live mode with retransmissions is possible, but various changes have to be +applied so that this can cooperate with live mode. + + +## Reporting a loss + +The general mechanism relies on a simple rule: + +* As packets are received, they are tried to be inserted into the receiver +buffer. Packets are rejected if + + * their sequence number would place them outside the current capacity of +the receiver buffer. + + * at the position designated by the sequence number there already is a +packet. + +* Packets that are earlier than the newest one, but still within the range of +the buffer, and the cell at that position points to a missing packet, it's +placed there as recovery. + +* If the sequence number of the packet is the newer than any packet's sequence +number received so far, and it doesn't immediately follow that last sequence, +it's interpreted as a loss, and the receiver side sends the `UMSG_LOSSREPORT` +control packet. That control packet contains the loss range between the +expected new sequence and the predecessor of the inserted packet. + +The loss report is sent immediately, but the loss information is also recorded +in the loss list for further periodic retransmission. + +Note that this mechanism gets a bit complicated if the stream is transmitted +over a group connection and multiple links may deliver packets with different +reception time. This doesn't concern the Backup group because in this group +there's only one link intended for transmitting, but it does concern the +Broadcast (and potentially Balancing) groups. In this case the loss should +be remembered, but not reported, until it's ascertained that the other link +will not deliver the packet detected as lost. + +There's also additional mechanism intended for links where packet reordering +is expected to often occur. This can be set by the `SRTO_LOSSMAXTTL` option +and if set it allows the value of reorder tolerance to grow up to this value. +Reorder tolerance increases with reordering detected and it allows to wait +for so many packets to arrive after the detected loss so that the loss can +be reported. + +## Direct retransmission + +Upon reception of `UMSG_LOSSREPORT`, the sender side records scheduling of +the reported packets for retransmission. This is marked in the sender buffer +as an embedded container, which marks packets eligible for retransmission. +This causes that next time when the sender thread loop asks the socket for +providing the information of the new packet to send, the socket will provide +this packet as the next one to be sent. Retransmitted packets are removed +from the schedule and considered recovered until the receiver party denies it. + +The `SRTO_RETRANSMITALGO` option controls whether any packets are blocked +from being retransmitted, if the time that elapsed since their last +retransmisssion is less than the "optimistic RTT". This mainly applies to the +periodic retransmission. + +## Delaying loss reports + +Originally the earliest versions of SRT have been reported losses always +immediately - and that's in most cases the best approach, which ensures +fastest possible loss recovery, and in case of TLPKTDROP setting (which is +default in live mode), it gives best chances of not dropping the lost packet. + +However, sometimes there are networks, where the user wants to tolerate higher +latency, and it's prone for packet reordering. If this happens often, then a +reordered packet may come in originally, just later, but then a gap in the +sequence numbers may cause it considered a loss before the original packet +has a chance to come. Therefore there is a possibility to set up a tolerance +for reordering through `SRTO_LOSSMAXTTL`. The mechanism triggers in the +`acquireDataPacket` function, where the losses are detected and handled. This +is saved in a separate loss list with their TTL. + +PERFORMANCE CONSIDERATIONS: + +This list is quite inefficient as a data type and finding the candidate to send +`UMSG_LOSSREPORT` 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 dropFromLossLists() of one sequence) + +- first found record with TTL>0 means end of "ready to LOSSREPORT" records + +So, all you have to do is: + + - start with first element and continue with next elements, as long as they have TTL=0 + If so, send the loss report and remove this element. + + - Since the first element that has TTL>0, iterate until the end of container and decrease TTL. + +This will be efficient because the loop to increment one field (without any condition check) +can be quite well optimized. + +Additional action is undertaken in the `CUDT::unlose` function. This is called +for a packet coming in NOT ahead of the newest incoming sequence, normally a +potential loss recovery. The sequence number is then tried to be removed from +both loss records: the general loss record and the fresh loss record. + +Additionally, it checks whether the "latecoming" packet has been sent due to +retransmission or due to reordering, by checking the rexmit flag. If this +packet was surely ORIGINALLY SENT it means that the current network connection +suffers of packet reordering. This way it tries to introduce a dynamic +tolerance by calculating the difference between the current packet reception +sequence and this packet's sequence. This value 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_config.iMaxReorderTolerance`. This value can be set in options - +`SRTO_LOSSMAXTTL`. + + +## Periodic retransmission + +There's only one way to know that the retransmission was successful: when the +`UMSG_ACK` control packet comes in and the declared sequence number covers the +packet that was earlier retransmitted (just like all other packets). Therefore +if a packet was lost again, there must be some mechanism to retry the recovery. + +The `SRTO_NAKREPORT` option controls the "periodic NAK report" done on the +receiver side. If on, there's a handler attached in the receiver/update worker +thread, which is triggered every SYN period (see `CUDT::checkTimers` and +following `CUDT::checkNAKTimer`) and this checks if there are packets still +not recovered. If such information is found, the new `UMSG_LOSSREPORT` control +packet is created with the use of all the ranges of lost packets and sent at +once this time. If it happened that the "repeated" loss report for particular +packet was sent too early (the network didn't have a chance to carry over a +retransmitted packet in such a short time), the blocking mechanism is used +through the `SRTO_RETRANSMITALGO` option. + +Note for the FEC Packet Filter: it may work with "ARQ onreq" mode, that is, it +requests retransmission if the packet was lost and the FEC mechanism could not +recover it. For this kind of packets, direct loss reports are handled as +before, but NAKREPORT is not implemented. The reason for it is that the +structure of the loss list container (`m_pRcvLossList`) is such that it is +expected that the loss records are ordered by sequence numbers (so that two +ranges sticking together are merged in place). Unfortunately in case of +`SRT_ARQ_ONREQ` losses must be recorded as before (at the moment of detection), +but they should not be reported, until confirmed by the filter (that they are +not recoverable, so it uses ARQ as a fallback). 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. + +NAKREPORT still works with FEC in case of setting "ARQ always", but this mode +is more for experiments than real use, as it is a mechanism of "double +certainty" recovery, comparable to broadcast groups - in this case it works +parallelly with FEC itself, so NAKREPORT is working in this mode. + +On the receiver side (see `CUDT::checkRexmitTimer`) the party should wait until +packets are finally acknowledged by receiving `UMSG_ACK`, but in theory it may +happen that it stays in this state forever. There are two failsafe mechanisms +to avoid that: + +1. Sender dropping: `SRTO_TLPKTDROP` option is set, the sender side may decide +that it's so late that the packet to be retransmitted will never make it on +time (assigned to delivery). Therefore the packet is not retransmitted even if +it was requested. Note that if the LOSSREPORT comes for a packet that was +already ACK-ed (points to a position in the buffer that is already in the +past), the sender side will respond with `UMSG_DROPREQ`. + +2. The BLINDREXMIT mechanism, which works in two different modes: LATEREXMIT +and FASTREXMIT. + +The BLINDREXMIT mechanism is simply a mechanism that requires sending again +every single packet between the last received ACK and the very last sent one. +The question is only, when it is decided to do so. The LATEREXMIT is the +original mechanism that existed in SRT for the use in file transmission and +FASTREXMIT was an attempt to do it a bit more efficiently for the live mode. As +the NAKREPORT mechanism was considered efficient enough, currently in live mode +when NAKREPORT and TLPKTDROP are set, FASTREXMIT is not in use. The BLINDREXMIT +is then triggered under the following conditions: + +* 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. + +So, the retransmission is scheduled 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. + + +## Prioritization + +The overall rule in SRT (since UDP codebase) was that the retransmission +candidate is always treated with higher priority than regular (unique) packets. +This isn't always wanted in live mode with TLPKTDROP turned on because the +large retransmission requests may block regular packets from being sent and +finally they can eat up all the latency advantage even up to sending packets +way later than it is required for it to be delivered to the application. + +The method `CUDT::isRegularSendingPriority` checks if the regular packet should +be preferred over retransmitted packet (even if it increases the probability of +getting the lost packet never recovered). + +The following options should be set to take this kind of priority: + +* `SRTO_TLPKTDROP` = true +* `SRTO_MESSAGEAPI` = true + +NOTE: We ignore `SRTO_TSBPDMODE` because it can't be set in stream mode, +although this theoretically enables this for plain message mode with +TLPKTDROP. + +The current solution is simple - the regular packet takes precedence over a +retransmitted packet, if there's at least one packet not yet transmitted first +time. + +This probably isn't the most wanted solution, some more elaborate condition +might be better in some situations. For example, it should be acceptable +that a packet that has very little time to be recovered is sent before a +regular packet that has still STT + Latency time to deliver. The regular +packets should still be favored, but not necessarily at the expense of +dismissing a recovery chance that wouldn't endanger the delivery of a regular +packet. Criteria might be various, for example, the number of scheduled +packets and their late delivery time might be taken into account. + +PROPOSAL: + +A specific time-amount proportion should be applied for cases like this so +that retransmission candidates with very little left time can be preferred +at the small expense of the latency remaining for the new packets, but if +there's a situation with a large portion of retransmission candidates that +can overflow the link and block awaiting packets from timely delivery, the +prioritized should be still unique packets. + diff --git a/docs/dev/sender-buffer.md b/docs/dev/sender-buffer.md new file mode 100644 index 000000000..0060b4f3d --- /dev/null +++ b/docs/dev/sender-buffer.md @@ -0,0 +1,243 @@ +# Sender buffer + +The sender buffer is organized with the cells kept by a container using +the `std::deque` type with additional memory manager. The logics of this +container maintain keeping the packets themselves and the loss information, +but there's no separation of the newly scheduled packets and those kept for +the sake of retransmission. + + +## The cell block + +The `CSndBlock` class represents the single cell in the container and keeps +the scheduled payload together with various characteristic data. Basing on +this there will be created full UDP packets sent over the UDP socket, although +the payload is using a different buffer than the header. + +Note that the `PH_MSGNO` field in the header occupies only several bits for +the message number itself, others are allocated for special flags. + + +## The main container wrapper: `SndPktArray` + +The container keeps the blocks and also additional characteristic data for +the container as a whole. The actual type of the container values is defined +as `SndPktArray::Packet`, which derives from `CSndBlock` and adds several +fields for the sake of other types of mechanisms: + +* `m_iBusy`: the busy counter; if not 0, then the acknowledgement action that +is about to revoke packets from the sender buffer must stop on this packet +and retry only when it is 0 again. See `SndPacket` for further explanations + +* `m_tsNextRexmitTime` : time intended for the next retransmission +* `m_iLossLength` : how many packets since this are retransmit-eligible +* `m_iNextLossGroupOffset` : points to the next index of retransmit-eligible + +These fields will be explained below for the "Sender Loss Structure". + +For the update after scheduling a packet for retransmission, there's a function +to check if the time has come to retransmit a packet, +`updated_rexmit_time_passed`. This function must ensure that +`m_tsNextRexmitTime`, if set, is distant by at least `miniv` towards +`m_tsRexmitTime`; if not, `m_tsNextRexmitTime` will be updated to the minimum +acceptable value, then returns: +- false: the next rexmit time is in the future +- true: the next rexmit time is in the past, or we don't care (if miniv is zero) + +Further fields in `SndPktArray`: + +* `m_Storage`: memory storage. This is a specialized allocator, which keeps +deallocated packets for the need of future allocations in the sensible number +of spare buffers. This keeps buffers for payload that are assigned to elements +kept in the actual container + +* `m_PktQueue`: the packet container, managed internally + +* `m_iCachedSize`: the container size updated with every modification, kept +to avoid mutex locking when the size reading is requested + +* `m_iFirstRexmit`, `m_iLastRexmit`: hooks for the retransmission scheduling +subcontainer, see below. + +* `m_iLossLengthCache`: total number of retransmission-scheduled packets + + +## Retransmission scheduling subcontainer + +This is a singly linked list with hook pointer to the head and tail of the +list, where pointers are the indexes into the `m_PktQueue` container. As +index values change as the container gets modified (actually only on +revocation), the main hooks must be updated, but then those in the container +elements themselves are expressed in relative values, so they don't change +provided that packets in the container are always ordered in their sequence +numbers. + +The `m_iFirstRexmit` and `m_iLastRexmit` fields in `SndPktArray` keep the index +of the first and last retransmission request record. If there are no such +records, both should be -1. The last one is to speed up place search for +inserting newly incoming loss reports. + +The linked list is organized in records (or groups otherwise), that is, what is +being pointed at first is the packet to be retransmitted, but any directly +following packets may belong to a single group. All the required information is +only written into the very first such packet, and this is: + +* `m_iLossLength`: This value is at least 1 in the packet being pointed as the +group head of the scheduled packets - in all other packets it's always 0. The +value designates how many packets directly following this one belong to the +group of scheduled packets. + +* `m_iNextLossGroupOffset`: It's a relative value, that is the difference +between the index of the current packet and index of the packet beginning the +next group. If this record is the last group, this value is 0. + +The fact that packets are in the loss records doesn't yet mean they will be +retransmitted - this is decided by the key field `m_tsNextRexmitTime`. It +defines the time of the next retransmission. If zero-time, this packet is not +to be retransmitted. If future-time, this packet should be skipped when +looking for the packets for retransmission, but the time remains unchanged. +This will be set to zero-time right after picking up for retransmission. + +The possibility to use this field is important for performance when a single +packet must be removed quickly from the list - it's just cleared this field +and then it will be skipped when looking for the next candidate, and if all +preceding packets were removed from the loss retransmission, only then the +whole list structure will be updated and these packets removed from the list. +This happens inside `extractFirstLoss`. This function looks for the first +found loss record, but skips all packets with `m_tsNextRexmitTime` with +nonzero value, and those that might be set as such during the check, as +well as those having this value in the future - although if this kind of +packet was skipped, revocation index will stop here. Then all packets up to the +revocation index will be revoked from the loss list. + +Packets that are requested retransmission are first set `m_tsNextRexmitTime` +to the value of the time that must be in the past to be retransmitted. + +Any insertion also updates the following: + +- `m_iFirstRexmit` is set to the index of the first rexmit packet, or unchanged +if the inserted sequence pair was not the very first + +- `m_iLastRexmit` is set to the first packet of the group, if this was the very +last insertion (if the current inserion was past the previous last one) + +- `Packet::m_iLossLength` is the number of consecutive packets since +this packet that belong to the retransmission-requested. Note that +also only this packet contains a nonzero `m_tsNextRexmitTime` field. + +- `Packet::m_iNextLossGroupOffset` is set to 0 if this was inserted as +the last one, or to the offset between this packet and the nearest packet +beginning the next retransmission group + +Example: + +``` + * * * * * * * + [00][01][02][03][04][05][06][07][08][09][10][11][12][13][14][15] + | | + / / +m_iFirstRexmit = 1 / +m_iLastRexmit = 12 ------------------------------ + +[01]: + m_iLossLength = 2 + m_iNextLossGroupOffset = 5 (6 - 1) + +[06]: + m_iLossLength = 3 + m_iNextLossGroupOffset = 6 (12 - 6) + +[12]: + m_iLossLength = 2 + m_iNextLossGroupOffset = 0 (last group) + +[any other]: + m_iLossLength = 0 + m_iNextLossGroupOffset = 0 + +NOTE: +m_PktQueue[m_iLastRexmit].m_iNextLossGroupOffset == 0 + +``` + +Revocation of the packets from the sender buffer updates the fields: + +- If the series of packets in a loss record are split in half, the first packet +that survives the revocation is updated: the `m_iLossLength` is set to the new +size of the group, `m_iFirstRexmit` is set to 0. + +- If the whole series are revoked, only the `m_iFirstRexmit` field is updated +to the new beginning. + +- If all packets with retransmission requests are effectively removed, +both `m_iFirstRexmit` and `m_iLastRexmit` fields are set to -1. + +- No matter if any groups were removed or not (but unless there are no +loss records), `m_iFirstRexmit` and `m_iLastRexmit` fields are being updated by +decreasing with the number of revoked packets, if they are not set the new +value. + +Expiration of a packet (per TTL, for example) causes `m_tsNextRexmitTime` +to be reset to zero, but no other action is undertaken. The packet is +still in the retransmission request record, just won't be retransmitted. + +Popping a loss does the following: + +- The first packet group, pointed by `m_iFirstRexmit`, is checked and removed + +- Removal means that the next packet is taken as the first one: + + - if `m_iLossLength` == 1, take the packet distant by +`m_iNextLossGroupOffset` + + - if `m_iLossLength` > 1, take the packet distant by 1, set it the + values of this packet's `m_iLossLength` and `m_iNextLossGroupOffset` + decreased by 1 + + - the `m_iFirstRexmit` index is updated to point to this new packet + +- If the packet at this position has `m_tsNextRexmitTime` zero, this +is not reported as retransmission-eligible, but still removed, +that is, after removal the whole procedure starts over + +- If the search with removed expired retransmission packets reaches +a packet with `m_iNextLossGroupOffset == 0`, finally "no rexmit request" +state is reported, same as when `m_iFirstRexmit == -1`. + +- If a removal resulted in removing the last record, whether a valid +retransmission request or not, both `m_iFirstRexmit` and `m_iLastRexmit` +are set to -1. + + +## Buffer zones + +The sender buffer serves for two purposes: + +* Schedule packets to be sent (inter-thread passing from API to sender thread) +* Keeping old sent packets for the sake of prospective retransmission + +By sequence numbers these two ranges could be determined in the sender +buffer as "unique" and "historic" respectively. + +The sender buffer, however, doesn't keep this information. It does keep +the information about the oldest stored sequence number, but all packets +stored there have equal status as for the sender loop. It's the socket +that should keep this information. + +The `extractUniquePacket` method is indeed intended to extract the unique +packet, but which packet is unique, this information should be provided +by the caller. One of the parameters is required to contain the current +sequence number of the last packet that for given socket was considered +unique, and this function attempts to shift this value by 1 and take the +packet at that sequence number. This need not go smoothly due to possible +TTL-expiration of the packets in the message mode, including a possibility +to ride up to the end of filled buffer and find nothing. Still, during this +TTL-expiration skipping, the last sent sequence number is still updated, +so the caller, after returning from this call, should treat this as a good +deal and the sequence number that should be considered as of the last +packet that was sent as unique, even if nothing was effectively extracted. + + + + + diff --git a/docs/dev/utilities.md b/docs/dev/utilities.md new file mode 100644 index 000000000..562b1b724 --- /dev/null +++ b/docs/dev/utilities.md @@ -0,0 +1,506 @@ +Utilities in the SRT library +============================ + +1. Endian utilities +------------------- + +* HtoNLA, NtoHLA, HtoILA, ItoHLA : endian operations on arrays + +Markers: + * H = hardware (endian of the current hanrdware) + * N = network (big endian) + * I = Intel (little endian) + +Following letters L and A mean "long" (32-bit) and "array". The order of +arguments follows the declaration of `memcpy`. + +2. Static bit numbering utility +------------------------------- + +This is something that allows you to turn 32-bit integers into bit fields. +Although bitfields are part of C++ language, they are not designed to be +interchanged with 32-bit numbers, and any attempt to doing it, like by placing +inside a union, for example, is nonportable (order of bitfields inside +same-covering 32-bit integer number is dependent on the endian), so they are +popularly disregarded as useless. Instead the 32-bit numbers with bits +individually selected is preferred, with usually manual playing around with & +and | operators, as well as << and >>. This tool is designed to simplify the +use of them. This can be used to qualify a range of bits inside a 32-bit number +to be a separate number, you can "wrap" it by placing the integer value in the +range of these bits, as well as "unwrap" (extract) it from the given place. For +your own safety, use one prefix to all constants that concern bit ranges +intended to be inside the same "bit container". + +Usage: `typedef Bits MASKTYPE; // MASKTYPE is a name of your choice.` + +Note that rightmost defaults to leftmost, so you can also mark a single bit only. +REMEMBER: leftmost > rightmost because bit 0 is the LEAST significant one. + +With this defined, you can use the following members: + +Static constants: + +- MASKTYPE::mask - to get the `int32_t` value with bitmask (used bits set to 1, others to 0) +- MASKTYPE::offset - to get the lowermost bit number, or number of bits to shift +- MASKTYPE::size - number of bits in the range + +Static methods: + +- `bool MASKTYPE::fit(uint32_t value)` + - check if the value is small enough to be encoded with this range of bits. + For example: if our bitset mask is 00111100, this checks if given value fits in + 00001111 mask (that is, does not exceed <0, 15> range). + +- `uint32_t MASKTYPE::wrap(uint32_t value)` + - gets the value that should be placed in appropriate bit range and + returns a whole 32-bit word that has the value already at specified place. + To create a 32-bit container that contains already all values destined for different + bit ranges, simply use wrap() for each of them and bind them with | operator. + +- MASKTYPE::unwrap(int bitset) + - extracts appropriate bit range and returns them as normal integer value + +Example: +``` + typedef Bits<7, 4> Cipher1; + typedef Bits<3, 0> Cipher2; + + uint8_t value = GetByteValue(); + + uint32_t c[2] = { + Cipher1::unwrap(value), + Cipher2::unwrap(value) + }; + + for (int i = 0; i < 2; ++i) + cout << (c[i] < 10 ? c[i] + '0' : c[i] - 10 + 'A'); + cout << endl; +``` + +2.a. `BIT` macro - for simplifying macro definitions +---------------------------------------------------- + +The `BIT` macro allows to define symbolic constants assigned to bits. + +Example: +``` +#define SRTGROUP_MASK BIT(30) +``` + +Considered were other methods to define it, like: + +* an inline function: requires `constexpr`, available in C++11 +* a user-defined literal, like `30_bit`, available in C++17 + +but this can be used as long as C++03-compatibility must be maintained. + +2.b. `IsSet`: test if a bit is set in a bitmask +----------------------------------------------- + +This function should be used for testing if a runtime value of the type +representing a bit set through a 32-bit integer contains a single bit set. + + +3. DynamicStruct: a simple array that can be only indexed with a dedicated type. +-------------------------------------------------------------------------------- + +* `class DynamicStruct` + +This is something that reminds a structure consisting of fields of the same +type, implemented as an array. It's parametrized by the type of fields and the +type, which's values should be used for indexing (preferably an enum type). +Whatever type is used for indexing, it is converted to `size_t` for indexing +the actual array. + +The user should use it as an array: `ds[DS_NAME]`, stating that `DS_NAME` is of +enum type passed as 3rd parameter. However trying to do ds[0] would cause a +compile error. + + +4. FixedArray: a simple wrapper for a dynamically allocated array. +------------------------------------------------------------------ + +It's a wrapper for a dynamically-allocated array with constant size. The +wrapper provides of all basic operations, `operator[]` as well as basic +container methods: `begin(), end(), data(), size()` to satisfy the concept +of the STL random-access container. Important properties: + +* The size is constant for the lifetime, but it can be runtime-defined. +* You can use your custom type for indexer values in `operator[]`. +* The `operator[]` method is raw, while `at()` checks the index value. + + +5. HeapSet: a partially sorted container using the heap tree concept +-------------------------------------------------------------------- + +Declaration: +``` +template +class HeapSet +``` + +This container implements a concept of a partially sorted container which +guarantees always the element at the head to be the earliest in the sorting +order, and allows elements to be added to the container with partial sorting. +The element is added at the quickest findable position in the tree, while +pulling the earliest element causes tree rebalancing. + +The types for the template instantiation are: + +- NodeType: The type of the value kept in the container (representation of +the contained objects). This type must be a lightweight-value type, so prefer +things like integers, pointers or iterators. There must also exist a trap +representation for this type (a value of "no object"). + +- Access: a class that provides static methods according to the requirements + +The elements kept in this container must provide a node functionality (data +strictly related to this container) and two most important elements of the +node: + +- KEY: This value decides about the sording order of the elements. +- POSITION: This value of `size_t` type defines the current position of the + element in the heap array + +The POSITION is the cache of the index in the internal heap array; it is +being used for moving the element throughout the container if needed, and +so it is also being updated accordingly. For that reason you should never +modify it yourself and always initialize it with the trap representation +value (designaed as `std::string::npos`, also replicated as `npos` constant +inside the HeapSet container), which means that the element is not in the +heap array. + +The NodeType should be a value through which the object in the container is +directly reachable, so for example: +- A pointer to the object - NULL is a trap representation +- A positive integer index in some array - so std::string::npos is a trap +- A list iterator - for that you need to keep some empty list for a trap +- Your own wrapper for any of the above so that it can be same as AccessType + +The AccessType class is only required to contain several static members, which +will be operating on either `NodeType` or `key_type`. The following things must +be provided by the AccessType: + +- `typedef key_type`: the type of the key value field +- `static key_type& key(NodeType)` : provide reference to the key field +- `static size_t& position(NodeType)` : provide reference to the position field +- `static NodeType none()` : return trap representation for NodeType +- `static bool order(key_type left, key_type right)` : true if left < right + +You can just as well keep the same object in multiple HeapSet containers, +you just need to have separate node entries for each one (sharing the same +NodeType is possible, just use different AccessType). + +HeapSet state attributes: + +- `none()` : returns the trap representation for NodeType (as provided by + the AccessType class), for convenience +- `npos` : an internal static constant assigned from std::string::npos +- `raw()` : returns the constant reference to the internal heap array +- `empty(), size()` : same as for the internal array +- `operator[]` : return node at given position (UNCHECKED!) + +Operations: + +- `find_next(key_type k)`: return the node that is the earliest element in the + list, but already later than the given `k` key + (none() if no such element) +- `top()` : return the element at top. Returns `none()` if the heap is empty. +- `top_raw()` : Unchecked version of `top()`, returns the value from the first + element of the internal array; results in UB if it's empty. +- pop() : same as top(), but the element is removed from the list. +- insert() : insert the element into the heap array. The element's position + must be npos first. It's in two versions: + - insert(node): insert the node after you updated the key + - insert(key, node): convenience wrapper for updating and inserting +- erase() : removes the element from the heap array. Returns false if the + element isn't in the array. Updates the position to `npos`. +- update(pos, newkey): update the node at the given position with the new + key and update its position accordingly + + +6. `explicit_t`: Prevent C++ from using default conversion +---------------------------------------------------------- + +Using `explicit_t` instead of `int` as a function argument prevents +the function from being called with any other type, like `bool` or `long`. + + +7. `EqualAny`: shorthand comparison of a single value to multiple values +------------------------------------------------------------------------ + +Usage example: +``` +if (EqualAny(state), ST_CONNECTING, ST_CONNECTED, ST_BROKEN) + ... +``` + +It's a shortened version of: +``` +if (state == ST_CONNECTING || state == ST_CONNECTED || state == ST_BROKEN) + ... +``` + +You need to add `using namespace any_op` inside the function to enable it. + + +8. Unique pointer and movable objects +------------------------------------- + +For C++11 these are aliases: `UniquePtr = std::unique_ptr` and `Move = std::move`. + +For C++03 they are provided with specific definitions resembling partiallty +this functionality. + +Additionally there are two convenience functions to operate with `swap` +(method provided by the object) in order to insert or remove elements +at the back of the container: + +* MoveBack: grab the object into the back side of the container + +* PullBack: swap the last element of the object with the given referenced object + (returns false if the container is empty) + + +9. Map element extraction convenience functionalities +----------------------------------------------------- + +These functions do a similar thing as `m[k]` for an `m` map with given `k` key. +Unlike `operator[]` they do not reinsert a key into the map if it doesn't exist, +instead they return a specific value in this case. They wrap `map::find` call, +but the result is translated to a more convenient value: + +* `map_get(m, k, def = default)`: if not found, returns the given `def` value, + which defaults to the defauilt-constructed mapped type. + +* `map_getp(m, k)`: returns a pointer to the mapped type value, if the key is + present in the map, otherwise returns NULL (nullptr, if C++11). + +* `map_tryinsert`: ensures that the array contains a key and returns the reference. + +This function replaces partially the functionality of std::map::insert. +Differences: + +- inserts only a default value +- returns the reference to the value in the map +- works for value types that are not copyable + +The reference is returned because to return the node you would have +to search for it after using operator[]. + +NOTE: In C++17 it's possible to simply use `map::try_emplace` with only +the key argument and this would do the same thing, while returning a +pair with iterator. + +* `MapProxy`: A proxy object with a reference to map and the key + +Using this type you can create a map-key assignment without modifying +the map. Having that you can do: + + - p.find() - same as map.find(k) + - val = p; - extract the value by assigning to the value type, or default if not found + - p.deflt(defval) - same as above, but return `defval` if not found + - p.exists() - returns true if the key is presnet in the map + - p.dig() - uses `map_tryinsert` and returns its result + + +10. Printable, PrintabeMod: allow formatting a container of values +------------------------------------------------------------------- + +These functions turn a container of printable values into the representing +string with surrounding `[]` and values separated by space. Used in logging. + + +11. Container utilities and algorithms +-------------------------------------- + +* `FilterIf`: a mix of `std::find_if` and `std::transform`: copies the range + defined by first two iterators into the target designated by the output + iterator. The function must have `result_type` type declared inside and + the call to its `operator()` must return a pair of this type and `bool`. + The value of result's `first` is written to the output if in this call the + `second` boolean value is true. + +* `insert_uniq`: a poor-performance insertion to the vector, with first check + if the value is already there, in which case nothing is inserted. + +* `Tie`: similar to `std::tie` for C++03: binds two variables by exposing + their references so that this can be used in the assignment + +* `All`: returns a pair of iterators extracted from `begin()` and `end()`. + This can be used in conjunction with `Tie` by assigning to its result + +* `Size`: a version of std::size from C++11 - for a fixed array it returns + the number of declared elements; for other types it's size() method result. + +* `safe_advance` : same as `std::advance`, but additionally you specify + the iterator beyond which the advancement shall not be done; returned + is the value by which the iterator was really advanced. Only the forward + iterator concept is supported, though; for random-access containers + you should do it manually with checking size() and distance() + +* `FringeValues`: Takes the values from the container and marks in the + output map, how many values of that kind were found. The output map + will then contain only unique values as keys and the value is the + number of found occurrences of this very value + + +12. CallbackHolder +------------------ + +A convenience wrapper for a function pointer with opaque pointer idiom. +An additional macro `CALLBACK_CALL` simplifies passing parameters to +the call regarding the opaque pointer. + + +13. Pass filter utilities +-------------------------- + +* GetPeakRange + +This utility is used in window.cpp where it is required to calculate the median +value basing on the value in the very middle and filtered out values exceeding +its range by 1/8 and 8 times. Returned is a structure that shows the median and +also the lower and upper value used for filtering. + +This calculation does more-less the following: + +1. Having example window: + - 50, 51, 100, 55, 80, 1000, 600, 1500, 1200, 10, 90 + +2. This window is now sorted, but we only know the value in the middle: + - 10, 50, 51, 55, 80, [[90]], 100, 600, 1000, 1200, 1500 + +3. Now calculate: + - lower: `90/8 = 11.25` + - upper: `90*8 = 720` + +4. Now drop those from outside the `` range: + - `10, (11<) [ 50, 51, 55, 80, 90, 100, 600, ] (>720) 1000, 1200, 1500` + +5. Calculate the median from the extracted range. + NOTE: the median is actually repeated once, so size is +1. + + values = { 50, 51, 55, 80, 90, 100, 600 }; + sum = 90 + accumulate(values); ==> 1026 + median = sum/(1 + values.size()); ==> 147 + +For comparison: the overall arithmetic median from this window == 430 + +* AccumulatePassFilter + +This function sums up all values in the array (from p to end), except those +that don't fit in the low- and high-pass filter. Returned is the sum and the +number of elements taken into account, through a pair. + +* AccumulatePassFilterParallel + +This function sums up all values in the array (from p to end) and +simultaneously elements from `para`, assuming that it points to an array of +the same size. The first array is used as a driver for which elements to +include and which to skip, and this is done for both arrays at particular index +position. Returned is the sum of the elements passed from the first array and +from the `para` array, as well as the number of included elements. + + +14. DriftTracer +--------------- + +This is the utility for calculating the drift in SRT, which is measured as the +smoothed average time distance between the expected and actual arrival time of +a packet. + +You should update it with every time read value, so the value is added. Only +up to maximum history is kept in the container. A special value is declared as +"overdrift", which means that the clock skew seems to be serious and should be +taken as a legitimate difference to fix. + +The values of `drift()` and `overdrift()` can be read at any time, however if +you want to rely on the fact that they have been changed lately, you have to +check the return value from update(). + +IMPORTANT: drift() can be called at any time, just remember that this value may +look different than before only if the last update() returned true, which need +not be important for you. + +* CASE: `CLEAR_ON_UPDATE = true` + +overdrift() should be read only immediately after update() returned true. It +will stay available with this value until the next time when update() returns +true, in which case the value will be cleared. Therefore, after calling +update() if it returns true, you should read overdrift() immediately an make +some use of it. Next valid overdrift will be then relative to every previous +overdrift. + +* CASE: `CLEAR_ON_UPDATE = false` + +overdrift() will start from 0, but it will always keep track on any changes in +overdrift. By manipulating the `MAX_DRIFT` parameter you can decide how high the +drift can go relatively to stay below overdrift. + +15. Running Average Utilities +----------------------------- + +* CountIIR(base, newval, factor) + +Returns the new value of the running average, while having the current base +value specified by `base` - if 0, the new value is taken as the new average +value. The new average value is the current base modified by the new value +taken by factor (use 0 - 1 range for this value). + +* `avg_iir(base, newval)` + +This uses base as if it was an average value of the previous `DLEN` values +and adds `newval` to calculate the new average value. + +16. Property accessor definitions +--------------------------------- + +This is a system of turning an existing field into being accessible in specific +mode through extra methods. + +"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.set_prop(x); <-- set the property a value +``` + +Properties might be also chained when setting: + +``` +object.set_prop1(v1).set_prop2(v2).set_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`, for WO, RW and RRW only + +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. + + + + + + diff --git a/docs/features/bonding-intro.md b/docs/features/bonding-intro.md index d455afb57..dc4fac570 100644 --- a/docs/features/bonding-intro.md +++ b/docs/features/bonding-intro.md @@ -21,6 +21,14 @@ used. How the links are utilized within a group depends on the group type. The simplest type, broadcast, utilizes all links at once to send the same data. +There are two main features for which the groups exist: + +1. Redundancy: keep the signal constantly delivered even if one of the +links gets unexpectedly broken. + +2. Balancing: send a stream with a bitrate that would be too high for +a single link, but enough if every link gets only a share of load. + To learn more about socket groups and their abilities, please read the [SRT Socket Groups](socket-groups.md) document. diff --git a/docs/features/socket-groups.md b/docs/features/socket-groups.md index 359c2aae1..9f3d0c451 100644 --- a/docs/features/socket-groups.md +++ b/docs/features/socket-groups.md @@ -30,11 +30,50 @@ The groups types generally split into two categories: This category contains currently only one Multicast type (**CONCEPT! NOT IMPLEMENTED!**). Multicast group has a behavior dependent on the connection side and it is - predicted to be only used in case when the listener side is a stream sender + intended to be used only in the case when the listener side is a stream sender with possibly multiple callers being stream receivers. It utilizes the UDP multicast feature in order to send payloads, while the control communication is still sent over the unicast link. +From the application point of view it is important to remember several rules +concerning groups: + +1. On the caller side the group has to be created, like a socket, and then it's + ready to connect. In distinction to socket, you can connect the group multiple + times. The group is considered connected, if at least one connection has + been successfully established, then other connections can be added at any time. + +2. On the listener side you create the listener socket, and you call + the `srt_accept` function, from which you get the group ID, if that listener + socket has received a group connection request. Once accepted, you get the + connected group this way and every next connection is handled in the background. + +3. Disconnected links are removed from the group and are not reconnected. The + application simply has to connect that link again, if it chooses to do so. + +4. You can remove a single link from the group by simply closing the member + socket. This socket is provided in the group member status table together + with other data that allow to identify particular link. + +In other words, links in socket groups are never "defined" - they can only be +"established". When they get broken, they are simply removed from the group. +It's up to the application to re-establish them. + +The group members can be also in appropriate states. The freshly created member +that is in the process of connecting is in "pending" state. When the connection +is successfully established, it's in "idle" state. Then, when it's used for +transmission, it's in "active" state. If an operation on the link fails at any +stage, it is removed from the group. The "idle" state is differently managed in +various group types: + +* Broadcast and Balancing: The "idle" links are activated once +they are found ready for sending as well as they report readiness for reading - +"idle" is only a temporary state between being freshly connected and being used +for transmission. + +* Main/Backup: the "idle" state can remain for longer time parallelly with +"active" on other links as well as an "active" link may turn into "idle". + ## Details for the Group Types ### 1. Broadcast @@ -55,28 +94,60 @@ Every next link in this group gives then another 100% overhead. ### 2. Main/Backup -This solution is more complicated and more challenging for the settings, -and in contradiction to Broadcast group, it costs some penalties. - -In this group, only one link out of member links is used for transmission -in a normal situation. Other links may start being used when there's happening -an event of "disturbance" on a link, which makes it considered "unstable". This -term is introduced beside "broken" because SRT normally uses 5 seconds to be -sure that the link is broken, and this is way too much to be used as a latency -penalty, if you still want to have a relatively low latency. - -Because of that there's a configurable timeout (with `SRTO_GROUPSTABTIMEO` -option), which is the maximum time distance between two consecutive responses -sent from the receiver back to the sender. If this time was exceeded, the link -is considered unstable. This can mean either some short-living minor -disturbance, as well as that the link is broken, just SRT hasn't a proof of -that yet. - -At the moment when one link becomes unstable, another link is immediately -activated, and all packets that have been kept in the sender buffer since -the last ACK are first sent. Since this moment there are two links active -until the moment when the matter finally resolves - either the unstable -link will become stable again, or it will be broken. +The configuration of this type of groups is somewhat more complicated than with +the other group types. In particular, it may be challenging to arrive at the +optimal settings for a given set of network conditions and desired latency. +Unlike Broadcast group type, there are some penalties, but there are also +advantages. Whereas the overhead for redundancy in the case of Broadcast groups +is 100% per every next redundant link, this is usually kept at a negligible +minimum for Main/Backup groups. + +The idea of the Main/Backup group is to use only one link for transmission +of the data, but be ready to quickly activate the other links, if it turns +out that the currently used link is "likely broken" (by not having received any +packet from the peer for a given timeout). The unstable state is stricter than +broken connection: while broken connection is recognized by response time +exceeding the "peer idle" timeout (`SRTO_PEERIDLETIMEO`, default: 5s), the +unstable state is recognized by exceeding the "group stability" timeout, +which is 10ms of the ACK period with addition of some jitter tolerance (this +value is dependent on the current latency and average-tolerated RTT and the +minimum can be controlled by `SRTO_GROUPMINSTABLETIMEO`). As this still doesn't +mean broken, the transmission continues over multiple links since that time. +Activation of a link means that all packets since the last ACK sequence +are first sent over this link, then it continues with ongoing packets, so that, +if everything goes well (the new link is successfully keeping up with the pace +and any packet loss caused by the initial burst is recovered), the application +should see completely no disturbance due to this new link activation. + +Note that there doesn't happen anything like "switching" of the link. What +happens in response to a detected instability of a link is: + +1. Activate the first found idle link with highest weight. +2. Keep both links transmitting for a short "fresh activation" period. +3. Sort out links that are still not stable: + * A link that is unstable for too long time is forcefully closed + * A link that gets broken is automatically closed +4. If after that there is still more than one link "active", select the best +link to remain active and turn all others into "idle". + +The following may happen with the link, on which the instability has been +detected: + +* The link turns back to stable, so there are multiple stable links +* The link gets really broken, so only the newly activated link transmits +* The link is unstable for too long, so it is forcefully closed + +It may then happen that in result only one link remains stable and there's +nothing more to be done with it. If there remains more "active" link that +were confirmed stable ("temporary broadcast" mode), after a short cooldown +time, out of all currently active links there is selected one that is +considered the "best" (where priority matters, but also the response jitter is +taken into account) and this one continues with the transmission, while all +others are "silenced", that is, transmission over these links is stopped. +On the protocol level it's done through not sending packets anymore over this +link and sending `UMSG_KEEPALIVE` packet at once first. The keepalive packet +will be still later sent automatically as this is simply a single socket +connection currently not used for transmission. The state maintenance always keep up to the following rules: @@ -87,8 +158,8 @@ and remains ready to take over if there is a necessity. b) Unstable links continue to be used no matter that it may mean parallel sending for a short time. This state should last at most as long as it takes -for SRT to determie the link broken - either by getting the link broken by -itself, or by closing the link when it's remaining unstable too long time. +for SRT to determine the link broken - either by breaking the link by +itself, or by closing the link when it has been unstable for too long. This mode allows also to set link priorities - the greater, the more preferred. This priority decides mainly, which link is "best" and which is selected to @@ -144,45 +215,53 @@ any quite probable packet loss that may occur during this process. The idea of balancing means that there are multiple network links used for carrying out the same transmission, however a single input signal should distribute the incoming packets between the links so that one link can -leverage the bandwidth burden of the other. Note that this group is not -directly used as protection - it is normally intended to work with a +leverage the bandwidth burden of the other. Note that this group only +partially can provide the redundancy - it is normally intended to work with a condition that a single link out of all links in the group would not be -able to withstand the bitrate of the signal. In order to utilize a -protection, the mechanism should quickly detect a link as broken so -that packets lost on the broken link can be resent over the others, -but no such mechanism has been provided for balancing group. - -As there could be various ways as to how to implement balancing -algorithm, there's a framework provided to implement various methods, -and two algorithms are currently provided: - -1. `plain` (default). This is a simple round-robin - next link selected -to send the next packet is the oldest used so far. - -2. `window`. This algorithm is performing cyclic measurement of the +able to withstand the bitrate of the signal. So, to stay safe, you need +to make sure that you always have one link that provides the excessive +capacity so that breaking one link doesn't lower the overall capacity +below the requirement for the signal's bitrate. + +Note that the general rule for groups is that it's considered connected always +when at least one member socket remains connected, so when one and the only +link is established or remains in the group, the group is ready for +transmission. So, if the application wants to make sure that a transmission is +balanced between links (where only together can they maintain the bandwidth +capacity required for a signal), it must make sure that all "required" links +are established by monitoring the group data. For example, if you need a +minimum of 3 links to balance the load, you should delay starting the +transmission until all 3 links are established (that is, all of them report +"idle" state), and also stop it (or quickly reconfigure the stream to a lower +bandwidth) in case when a broken link caused that the others do not cover the +required capacity. + +As there could be more than one way to implement a balancing algorithm, there +is a framework for implementing various methods, so that new algorithms are +easier to provide in future. Currently there are two algorithms provided: + +1. `fixed`. This is based on the simple round-robin method, but the usage +of particular link grows invertedly towards the share value, which is +controlled by the `weight` parameter (that is, a link with more weight can +be proportionally more burdened). You can easily think of the weight values as +a percentage of load burden for particular link - however in reality the share +of the load is calculated as a percentage that particular link's weight +comprises among the sum of all weight values. Additionally, a value of 0 is +special and it is translated into the arithmetic average of all non-zero +weighted links, and if all links have weight 0, all links have equal share. Be +careful here though with the non-established and broken links. For example, if +you have 3 links with weight 10, 20 and 30, it results in a load balance of +16.6%, 33.3% and 50% respectively. However if the second link gets broken, +there are then 2 links with 10 and 30, which results in load balance of 25% and +75% respectively. + +2. `window` (default). This algorithm performs cyclic measurement of the minimum flight window and this way determines the "cost of sending" -of a packet over particular link. The link is then "paid" for sending -a packet appropriate "price", which is collected in the link's "pocket". -To send the next packet the link with lowest state of the "pocket" is -selected. The "cost of sending" measurement is being repeated once per -a time with a distance of 16 packets on each link. - -There are possible also other methods and algorithms, like: - -a) Explicit share definition. You declare, how much bandwidth you expect -the links to withstand as a percentage of the signal's bitrate. This -shall not exceed 100%. This is merely like the above Window algorithm, -but the "cost of sending" is defined by this percentage. - -b) Bandwidth measurement. This relies on the fact that the current -sending on particular link should use only some percentage of its -overall possible bandwidth. This requires a reliable way of measuring -the bandwidth, which is currently not good enough yet. This needs to -use a similar method as in "window" algorithm, that is, start with -equal round-robin and then perform actively a measurement and update -the cost of sending by assigning so much of a share of the signal -bitrte as it is represented by the share of the link in the sum of -all maximum bandwidth values from every link. +of a packet over a particular link (the bigger the flight span of the link, +the higher the sending cost). This evaluated cost is then added to the current +burden state of the link, and then the link with lowest burden is selected to +send the next packet. The "cost of sending" measurement is being repeated once +per a time at an interval of 16 packets on each link. ### 4. Multicast (**CONCEPT! NOT IMPLEMENTED!**) @@ -192,7 +271,7 @@ receiving a data stream sent from a stream server by multiple receivers. Multicast sending is using the feature of UDP multicast, however the connection concept is still in force. The concept of multicast groups -is predicted to facilitate the multicast abilities provided by the router +is intended to facilitate the multicast abilities provided by the router in the LAN, while still maintain the advantages of SRT. When you look at the difference that UDP multicast provides you towards diff --git a/examples/recvfile.cpp b/examples/recvfile.cpp index 7f75cd856..1ee4b8409 100644 --- a/examples/recvfile.cpp +++ b/examples/recvfile.cpp @@ -24,7 +24,7 @@ int main(int argc, char* argv[]) // Use this function to initialize the UDT library srt_startup(); - srt_setloglevel(srt_logging::LogLevel::debug); + srt_setloglevel(hvu::logging::LogLevel::debug); struct addrinfo hints, *peer; diff --git a/examples/recvlive.cpp b/examples/recvlive.cpp index e14420a66..82cb94c8f 100644 --- a/examples/recvlive.cpp +++ b/examples/recvlive.cpp @@ -26,7 +26,7 @@ int main(int argc, char* argv[]) // use this function to initialize the UDT library srt_startup(); - srt_setloglevel(srt_logging::LogLevel::debug); + srt_setloglevel(hvu::logging::LogLevel::debug); addrinfo hints; addrinfo* res; diff --git a/examples/recvmsg.cpp b/examples/recvmsg.cpp index 008eb3928..b04ff5b81 100644 --- a/examples/recvmsg.cpp +++ b/examples/recvmsg.cpp @@ -76,7 +76,7 @@ int main(int argc, char* argv[]) // use this function to initialize the UDT library srt_startup(); - srt_setloglevel(srt_logging::LogLevel::debug); + srt_setloglevel(hvu::logging::LogLevel::debug); SRTSOCKET sfd = srt_create_socket(); if (SRT_INVALID_SOCK == sfd) diff --git a/examples/sendmsg.cpp b/examples/sendmsg.cpp index af551849b..9ef34703a 100644 --- a/examples/sendmsg.cpp +++ b/examples/sendmsg.cpp @@ -38,7 +38,7 @@ int main(int argc, char* argv[]) // Use this function to initialize the UDT library srt_startup(); - srt_setloglevel(srt_logging::LogLevel::debug); + srt_setloglevel(hvu::logging::LogLevel::debug); struct addrinfo hints, *peer; @@ -100,7 +100,7 @@ int main(int argc, char* argv[]) // 2. Otherwise the first number is the ID, followed by a space, to be filled in first 4 bytes. // 3. Rest of the characters, up to the end of line, should be put into a solid block and sent at once. - int status = 0; + int status = SRT_STATUS_OK; int ordinal = 1; int lpos = 0; diff --git a/examples/testcapi-connect.c b/examples/testcapi-connect.c index f9037e957..8d584b355 100644 --- a/examples/testcapi-connect.c +++ b/examples/testcapi-connect.c @@ -35,6 +35,8 @@ int main( int argc, char** argv ) return 1; } + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; sa.sin_port = htons(atoi(argv[2])); if ( inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) { diff --git a/haicrypt/cryspr-openssl-evp.c b/haicrypt/cryspr-openssl-evp.c index 02cfb590b..4b1b4e7e1 100644 --- a/haicrypt/cryspr-openssl-evp.c +++ b/haicrypt/cryspr-openssl-evp.c @@ -185,14 +185,14 @@ int crysprOpenSSL_EVP_AES_EcbCipher(bool bEncrypt, /* true:encry f_len = 0; if (0 == EVP_CipherFinal_ex(aes_key, &out_txt[c_len], &f_len)) { -#if ENABLE_HAICRYPT_LOGGING +#if SRT_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*/ +#endif /*SRT_ENABLE_HAICRYPT_LOGGING*/ return -1; } if (outlen_p != NULL) *outlen_p = nblk * CRYSPR_AESBLKSZ; @@ -238,14 +238,14 @@ int crysprOpenSSL_EVP_AES_CtrCipher(bool bEncrypt, /* true:encry f_len = 0; if (0 == EVP_CipherFinal_ex(aes_key, &out_txt[c_len], &f_len)) { -#if ENABLE_HAICRYPT_LOGGING +#if SRT_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*/ +#endif /*SRT_ENABLE_HAICRYPT_LOGGING*/ return -1; } return 0; @@ -306,14 +306,14 @@ int crysprOpenSSL_EVP_AES_GCMCipher(bool bEncrypt, /* true:encry f_len = 0; if (0 == EVP_CipherFinal_ex(aes_key, &out_txt[c_len], &f_len)) { -#if ENABLE_HAICRYPT_LOGGING +#if SRT_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*/ +#endif /*SRT_ENABLE_HAICRYPT_LOGGING*/ return -1; } diff --git a/haicrypt/haicrypt_log.cpp b/haicrypt/haicrypt_log.cpp index 773507196..1e03de4d3 100644 --- a/haicrypt/haicrypt_log.cpp +++ b/haicrypt/haicrypt_log.cpp @@ -8,19 +8,20 @@ * */ -#if ENABLE_HAICRYPT_LOGGING +#if SRT_ENABLE_HAICRYPT_LOGGING #include "haicrypt_log.h" #include "hcrypt.h" #include "haicrypt.h" #include "../srtcore/srt.h" -#include "../srtcore/logging.h" +#include "../srtcore/logger_fas.h" // Attach yourself to the SRT logging configuration -extern srt_logging::LogConfig srt_logger_config; - -// LOGFA symbol defined in srt.h -srt_logging::Logger hclog(SRT_LOGFA_HAICRYPT, srt_logger_config, "SRT.hc"); +// This symbol doesn't need to be external actually because it's exclusively +// used in haicrypt through this interface (or it is used only inside this file). +// The "logger_fas.h" header provides only the configuration object in which +// this one is registered. +hvu::logging::Logger hclog("haicrypt", srt::logging::logger_config(), false, "SRT.hc"); extern "C" { @@ -44,11 +45,11 @@ 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, ...) \ { \ - srt_logging::LogDispatcher& lg = hclog.dispatcher; \ - if (!lg.CheckEnabled()) return -1; \ + hvu::logging::LogDispatcher& lg = hclog.dispatcher; \ + if (!lg.IsEnabled()) return -1; \ va_list ap; \ va_start(ap, format); \ - lg().setloc(file, line, function).vform(format, ap); \ + lg.setloc(file, line, function).vform(format, ap); \ va_end(ap); \ return 0; \ } diff --git a/haicrypt/haicrypt_log.h b/haicrypt/haicrypt_log.h index d788316bf..fb1b64753 100644 --- a/haicrypt/haicrypt_log.h +++ b/haicrypt/haicrypt_log.h @@ -23,7 +23,7 @@ HAICRYPT_DECLARE_LOG_DISPATCHER(LOG_EMERG); #define HCRYPT_LOG_EXIT() #define HCRYPT_LOG(lvl, ...) HaiCrypt_LogF_##lvl (__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) -#if ENABLE_HAICRYPT_LOGGING == 2 +#if SRT_ENABLE_HAICRYPT_LOGGING == 2 #define HCRYPT_DEV 1 #endif diff --git a/haicrypt/hcrypt.c b/haicrypt/hcrypt.c index 62fb6dea0..d566f98d5 100644 --- a/haicrypt/hcrypt.c +++ b/haicrypt/hcrypt.c @@ -31,7 +31,7 @@ written by #include "hcrypt.h" -#if ENABLE_HAICRYPT_LOGGING +#if SRT_ENABLE_HAICRYPT_LOGGING void HaiCrypt_DumpConfig(const HaiCrypt_Cfg* cfg); #else #define HaiCrypt_DumpConfig(x) (void)0 diff --git a/haicrypt/hcrypt.h b/haicrypt/hcrypt.h index 0b298fd8d..24dced1a6 100644 --- a/haicrypt/hcrypt.h +++ b/haicrypt/hcrypt.h @@ -91,7 +91,7 @@ typedef struct hcrypt_Session_str { }km; } hcrypt_Session; -#if ENABLE_HAICRYPT_LOGGING +#if SRT_ENABLE_HAICRYPT_LOGGING #include "haicrypt_log.h" #else diff --git a/logging/README.md b/logging/README.md new file mode 100644 index 000000000..ac9d8402e --- /dev/null +++ b/logging/README.md @@ -0,0 +1,404 @@ +Contents +======== + +This module contains the logging system, plus dependent utilities. + + +Compat utilities +================ + +Header: `hvu_compat.h` +Source: `hvu_compat.c` + +Implements the following functions: + +1. `SysStrError`: replicates the functionality of the `strerror` function +portable way. This function returns a message assigned to the given system +error code. + +The "portable" `strerror` function is not reentrant, and the reentrant +functions are not portable, there are 2 different versions on POSIX systems +and there's a whole new procedure on Windows. This function should cover +this functionality on all supported systems. + +The version for C language requires an output buffer. An extra C++ version +returns it as a string. + +The version with C interface is named `hvu_SysStrError`, everything else is +in the `hvu` namespace. + +2. `SysLocalTime`: returns the `tm` structure for the local time basing on +given value that should be the number of seconds since epoch. This should +replicate the reentrant versions of the `localtime` function. + + +Thread name +=========== + +This is a facility that can be used to name a thread so that the name is +visible in the debugger. This name can be also extracted by the logging +system so that this name is present in the log. + +The `ThreadName::set` can be used to set the current thread's name. This +can be only used in the thread handler function so that the thread can +set the name for itself. + +The `ThreadName::get` can be used to read the current thread name. +The name is read into the buffer of size 64, designated as BUFSIZE static +constant. + +This class can be used to make a temporary current process name change +using the RAII-RRID object: You set the name in the constructor, which +will extract the previous name, then the destructor restores the old name. +This can be used as a trick to name the thread at the time when it starts +so that the name is inherited in the newly spawned thread. + +Note that the use of this class can be not always reliable because it +would have to be stated that during the life time of this object the +thread should not be switched, otherwise the name read by other facilities +could be misleading. A more reliable way is to set the thread name +in the beginning of the thread handler using `ThreadName::set`. + + +OFMT +==== + +This is a header-only library facilitating the on-demand tagged formatting. + +It can be used for internal formatting only (using the wrapper over +`std::stringstream`) and with iostream as well. It provides the `fmt` +function that facilitates the "on-demand tagged API" for formatting. +Example: + +``` +printf("%x : %d", a, b); +``` + +In the bare iostream should be written as: + +``` +cout << hex << a << dec << " : " << b; +``` + +With the on-demand tagged API it has to be written as: + +cout << fmt(a, fmtc().hex()) << " : " << b; + +Additionally, it provides optimizations based on the fact that in +the buffer class you can bypass any formatting for strings, if you +don't use `fmt`, that is, rely on the default formatting. + +``` +hvu::ofmtbufstream out; + +out << fmt(a, fmtc().hex()) << " : " << b; +``` + +Here the `" : "` part will be written to the embedded std::stringstream +using the write() method, not using operator<<, which will bypass the +formatting. + +This facility is used in the logging system and can be also used with +any iostream, both with C++03-only and C++11 API. + +Follow the [OFMT documentation](ofmt.md) for details. + + +Logging system +============== + +This is a logging system, which provides the following features: + +1. Displays the time, thread name, configuration set prefix, functional area +prefix, severity prefix and the log contents in one log line. + +2. Displaying of particular log instructions can be filtered by: + * Setting the minimum severity + * Selecting functional areas + +The use of functional areas is not obligatory - there is always available +a general log, which is always enabled as functional area (it can be +still disabled per severity selection or the whole logging can be turned +off at compile time). + +The logging system consists of two main parts: + +1. Functional part +2. Generated part + +The functional part is the whole logging facility and it provides one +default "general" functional area designated object that you can use +for logging. Additional (selectable) functional areas you can use through +the generated code. + + +Generated files +--------------- + +The generated part is created using the script `generate-fa-files.tcl`. +As argument it requires the configuration file. You should save this +configuration file in your project. The model for this configuration is +provided in `config-model.tcl` file. + +Note that you are obliged to perform the generation in order to be able +to use this library at all, but your configuration may contain the empty +list of the FA (functional areas), if you only plan to use one general FA, and +all configuration items can be reused from the model configuration. + +In this file you can define your all FA entries. For every FA, beside +the description you have the identification name (to find this FA by +string name) and the name prefix. For every FA there will be created +a global variable, which's name consists of ``, where +`` is in this table and `` is common for the whole +configuration defined in `loggers_varsuffix` variable. + + +Basic Usage +----------- + +All these variables can be used then to perform logging at the given +location in the code. So, for example, when you have a FA with prefix +"fa" and the variable suffix is "log", this is to issue a log message +for level "error". + +``` +falog.Error("Wrong value of ", x); +``` + +Note that the printing call form uses the "subsequent arguments" method, +similar to the `print` function in Python or Perl (NOTE: not C++20 one). +In order to use any non-default formatting for numeric values, use the +`fmt` function from the `ofmt.h` header; see above for details. + + +Macros +------ + +This above shown call method is not recommended if you want to be able to +control the logging at compile time to be generally enabled or disabled; +additionally this method doesn't provide the file and line information, +should you need it in your logging line format. Additionally, the variadic +argument version is only available since C++11. + +For that reason there are added additional macros, which get resolved +depending on the macros that you should add to the compile options in your +build definition: + +* `HVU_ENABLE_LOGGING` : if set to 1, enables all logging macros +* `HVU_ENABLE_HEAVY_LOGGING` : if set to 1, enables heavy logging macros + +The normal logging macros are the following: + +* `LOGP`: Sequential logging arguments specification (same as the above instruction) +* `LOGC`: Use the iostream-style `operator <<` to specify arguments +* `IF_LOGGING` : Place the single instruction only if logging is enabled + +Corresponding heavy macros, which do the same thing as these, but only if +`HVU_ENABLE_HEAVY_LOGGING` is set to 1 are: + +* `HLOGP` +* `HLOGC` +* `IF_HEAVY_LOGGING` + +"Heavy" logging is intended for very detailed and often occurring logs, +usually for debug only. It's up to you how you qualify each log; this +system just allows you to turn them all off, without blocking the whole +logging system. + +The `LOGP` macro just forwards to the printing instruction: + +``` +LOGP(falog.Error, "Wrong value of ", x); +``` + +The `LOGC` macro uses the iostream-style operator<<, with `log` symbol +defined locally only for this instruction: + +``` +LOGC(falog.Error, log << "Wrong value of " << x); +``` + +The `LOGC` macro is the only possibility for C++03/C++98. The LOGP is still +available if compiling in this mode, but it accepts only one message argument. + +For convenience these enabler macros enable also the use of the following +convenience macros: + +* `IF_LOGGING( STATEMENT )` +* `IF_HEAVY_LOGGING( STATEMENT )` + +They resolve to the exact instruction placed as a `STATEMENTS` if enabled by +`HVU_ENABLE_LOGGING` or `HVU_ENABLE_HEAVY_LOGGING` respectively, or to nothing +otherwise. This can be used if logging requires preparing some additional data +that are not required if logging is not enabled. Note that this is for a single +instruction only, with semicolon at the end, although commas inside are handled +correctly. + + +Configuration +------------- + +In order to manage your logging configuration during runtime there is provided +the configuration object. Currently it's implemented as a singleton, for which +you define the access function. Note that the singleton is using the C++ system +supported singleton (meaning, guaranteed to be thread-safely initialized). +Theoretically this is not a requirement in C++98 to be supported, but all +compilers have been supporting it since even before C++11 has been defined, +regardless of the standard requirement. You might want to check on your +compiler if the thread-safe global initialization is in force, but this thing +is only known to be unsupported on archaic gcc compilers only. + +In this configuration object you can: + +* set up the log selection +* find the FA id by name +* configure the C++ stream used for log printing +* configure the log handler function (instead of printing on the `cerr` stream) +* configure special format flags + +The name of the type of the configuration object is `hvu::logging::LogConfig`. +The accessor function's name is defined in the logger configuration file +under the `loggers_configname` key. + + +Management +---------- + +All FA objects get their ids (unique and generated) and they can be also +accessed by these IDs as being collected in a configuration object, to which +they should be added during the creation time. The ID can be obtained by + +``` +falog.id() +``` + +IDs can be searched also by name in the configuration object and through +this object they can be also turned on or off. + +Particular logs can be enabled or disabled using two categories: + +1. Level. You can set the highest possible log level. All logs that +are below this level will not be printed. The levels are in this order: + +* Fatal +* Error +* Warn +* Note +* Debug + +These above are the method names that issue printing a log at particular +level. The corresponding level values for configuration as enum labels +of type `hvu::logging::LogLevel::type`: + +* fatal +* error +* warning +* note +* debug + +You can translate a string into this value using `hvu::logging::parse_level`. + +Using the configuration object you can set the maximum level by + +* `set_maxlevel(level)` + +Note that the argument should be the value of `hvu::logging::LogLevel::type` +type, but you can use as well the `LOG_*` values from `` header and +explicitly convert them into this type. See the `logging_api.h` header for +values. Values do not have 1-based resolution, but this isn't a problem if +you set the log level value to a value not present in this list; the comparison +for enabled log bases on the integer value of the `LOG_*` symbol. + +For Windows there's a specific header provided with `LOG_*` symbols, +`windows_syslog.h`. + + +2. Functional Area. + +You can obtain the IDs from the names given by a string with names +separated by comma. All IDs are then collected in a set returned +by `hvu::logging::parse_fa`. This requires the configuration object +because all the names are collected there. These IDs can be then +used to turn on or off the particular functional areas. + +The following functions can be used to configure the enabled FAs: + +* `enable_fa(name, enabled)`: find FA by name and set it enabled or disabled +* `enable_fa(array, arraysize, enabled)`: enable or disable FAs by IDs in the array +* `setup_fa(faset)`: reset enabled FA to only those present in `faset` +* `setup_fa(faset, enabled)`: set only selected FAs enabled or disabled + + +Additional configuration facilities +----------------------------------- + +The following methods are available in `LogConfig` object: + +* `size()` + +Returns the number of functional areas registered in this configuration. + +* `name(id)` + +Returns the name for this FA ID. Empty string is returned if ID is invalid. + +* `find_id(name)` + +Find the FA ID by name. The value should be the positive integer or 0. +If this name is not found, -1 is returned. Note that there is always available +a FA named "general" with ID 0. + +* `set_handler(opaque, handler)` + +Sets the handler function that will be called instead of the default one that +prints into the stream, where `opaque` is a `void*` pointer value to be always +passed to the handler and `handler` is the function to be called with the +following signature: + +``` +typedef void HVU_LOG_HANDLER_FN(void* opaque, int level, const char* file, int line, const char* area, const char* message); +``` + +where: + + * `opaque` is the object as passed to the `set_handler` call + * `level` is the level value as above described + * `file` and `line` are values passed from the `LOGC` or `LOGP` macros + * `area` is the FA prefix, as configured + * `message` is the log message with header + +Note that the header is always present before the message text, and what +this header contains, can be configured in the flags. + +* `set_flags(f)` + +Sets the flags that control the contents of the header; using these you can +turn off particular elements of the log text. This is useful if you want to +format the log message and provide particular parts of the message yourself. + +Flags: + + * `HVU_LOGF_DISABLE_TIME`: Do not add the time when the log instruction was executed + * `HVU_LOGF_DISABLE_THREADNAME`: Do not add the thread name to the log header + * `HVU_LOGF_DISABLE_SEVERITY`: Do not add level marker to the log header + * `HVU_LOGF_DISABLE_EOL`: Do not add the EOL character at the end of `message` + + +Development dependent parts +=========================== + +The logging system uses thread-related facilities for its own purpose. +It doesn't use threads, but it does use thread names, as well as mutexes +and atomics. + +The header file `hvu_sync.h` provides appropriate definitions based on +the C++11 standard library. + +If you want to use it with C++03, you have to provide these facilities +yourself. The SRT library contains a nice wrapper library over the POSIX +threads that provide the appropriate classes that use the same API as +the C++11 standard library - you can use it as an example. In order to +use a header with your definitions, provide its name in `HVU_EXT_INCLUDE_SYNC` +macro in the compilation command line. + + diff --git a/logging/config-model.tcl b/logging/config-model.tcl new file mode 100644 index 000000000..64856c7b5 --- /dev/null +++ b/logging/config-model.tcl @@ -0,0 +1,94 @@ +# +# 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. +#***************************************************************************** + +# This is an example configuration file. + +# To explain how they map to the logging system, here is an example +# of the instruction: + +# LOGP(myfalog.Error, "ERROR: The value ", x, " is wrong!"); +# where: +# - "myfalog" is the logger variable assigned to a functional area, +# - "myfa" is the "display-id" (see below) +# - "log" is the $loggers_varsuffix (see below) +# - "Error" is the severity" +# - remaining arguments are data to be printed in the log +# +# The resulting log will then look like this: +# 10:44:56.123456:WRK3!E:LF.myfa: ERROR: The value -1 is wrong! +# where: +# - initial numbers present the timestamp +# - "WRK3" is the thread name (see hvu_threadname.h) +# - !E - is the severity marker (Error in this case) +# - "LF." is $loggers_prefix (see below) +# - "myfa" is the "display-id" + +# All variable names have loggers_ prefix. All those variables can be then used +# inside the generated file pattern. + +# Logger definitions. +# ------------------- +# This defines the functional areas, each one with symbolic +# name and description. Comments are allowed here, just only for the whole +# line. + +# The content is line-oriented! + +# Structure: { name-id display-id help comment follows } where: +# * name-id: Identifier that can be used to obtain the FA identifier (internal) +# * display-id: This FA will be displayed in the log header with the prefix (see below) +# * remaining text up to the end of line: a description to be placed in a comment +set loggers_table { + external ex External functionality + internal in Internal functionality +} + +# This will be used to construct the variable name. The +# display-id field will be used, followed by this one. +# For example, with this suffix, the "external" FA the logger will be "exlog" +set loggers_varsuffix log + +# OPTIONAL, you may want to create also a link to the general +# logger, for convenience. This time it's a full name of a variable +set loggers_generallink gglog + +# This is the prefix when displaying the FA in the log header. +# For example, for 'external', the prefix will be "LF.ex" +set loggers_prefix "LF." + +# The namespace where the global logger variables and the logger config will +# be defined. Use dot separation rather than ::. +# For example, this one below will be my::ns namespace. +set loggers_namespace my.ns + +# Name of the config object where the loggers will be subscribed. +# This will be the function name that returns the logger config +# object as a singleton. This, together with loggers_namespace, +# will form the logger config accessor as "my::ns::logconfig()". +set loggers_configname logconfig + +# Whether all loggers should be enabled or disabled by default +# Note that this doesn't touch upon the general logger. +set loggers_enabled true + +# Name of the generated header and source file (.cpp and .h suffixes +# will be added to this name). May enclose the directory name. +set loggers_modulename example_logfa_file + +# This contents will be pasted into the generated header and source +# file in the beginning. +set loggers_globalheader { + +// This is an example HVU Logger's generated file. + +} diff --git a/logging/generate-fa-files.tcl b/logging/generate-fa-files.tcl new file mode 100755 index 000000000..dde362522 --- /dev/null +++ b/logging/generate-fa-files.tcl @@ -0,0 +1,421 @@ +#!/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. +#***************************************************************************** + +lassign $argv configfile + +if {$configfile == ""} { + puts stderr "Usage: [file tail $argv0] " + puts stderr "MIND that the script is run in accordance to relative directories defined there" + exit 1 +} + +source $configfile + +# 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 } +# where: +# +# format-model: Text for the whole file. +# - Use $loggers_globalheader in the beginning +# - Use $entries to place loggers_table' entries. +# - Use {%PROCEDURE_NAME} to call a procedure to generate the pattern +# logger-pattern: Pattern for a single logger entry, expanded for all loggers_table +# - $shortname: two-letter name used to compose the logger symbol name (with -log added) +# - $longname: symbolic name suffix +# - $description: to be placed in comments +# +# 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. +# +# NOTE: NO TABS ALLOWED. INDENT WITH SPACES ONLY. +set generation { + + cpp { + + { + $loggers_globalheader + #include "logging.h" + #include "${loggers_modulename_header}" + + $loggers_namespace_begin + ${loggers_config_defn} + ${loggers_generallink_defn} + + $entries + $loggers_namespace_end + } + + { + // $description + hvu::logging::Logger ${shortname}${loggers_varsuffix}("${longname}", ${loggers_config_use}, ${loggers_enabled}, "${loggers_prefix}${shortname}"); + } + } + + h { + { + $loggers_globalheader + #ifndef $loggers_modulename_macroguard + #define $loggers_modulename_macroguard + + #include "logging.h" + + $loggers_namespace_begin + ${loggers_config_decl} + ${loggers_generallink_decl} + + $entries + $loggers_namespace_end + + #endif + } + + { + extern hvu::logging::Logger ${shortname}${loggers_varsuffix}; + } + } +} + +# Post-processing of the configuration + +if {![info exists loggers_modulename_header]} { + set loggers_modulename_header "[file tail $loggers_modulename].h" +} + +set upref [string map {. _} $loggers_namespace] +set ufile [file tail $loggers_modulename] +set loggers_modulename_macroguard [string toupper ${upref}_${ufile}_H] + +proc DefineNamespaceBounds {nsspec r_begin r_end} { + upvar $r_begin begin + upvar $r_end end + + set parts [split $nsspec .] + set ndepth [llength $parts] + + for {set i 0} {$i < $ndepth} {incr i} { + append begin "namespace [lindex $parts $i] \{ " + append end "\} " + } +} + +set loggers_namespace_begin "" +set loggers_namespace_end "" + +DefineNamespaceBounds $loggers_namespace loggers_namespace_begin loggers_namespace_end + +set loggers_config_decl "extern hvu::logging::LogConfig& ${loggers_configname}();" +set loggers_config_defn "hvu::logging::LogConfigSingleton ${loggers_configname}_si; + hvu::logging::LogConfig& ${loggers_configname}() { return ${loggers_configname}_si.instance();}" +set loggers_config_use "${loggers_configname}()" + +set loggers_generallink_decl "" +set loggers_generallink_defn "" + +if {[info exists loggers_generallink]} { + set loggers_generallink_decl "extern hvu::logging::Logger& $loggers_generallink;" + set loggers_generallink_defn "hvu::logging::Logger& ${loggers_generallink} = ${loggers_config_use}.general;" +} + +set pattern_vars [info vars loggers_*] + +# Processing utilities for 'generation' + +proc get_trim_prefix_length {model} { + # The model consists of lines; we state that the + # definition in the configuration should be appropriately + # indented, but this indentation should be removed or at least + # refaxed for the needs of generation. + + # So we take the indentation from the first line and that + # should be the removable indentation. Any deeper indentation + # in any next line should be only the extra indentation. + + set lines [split $model \n] + foreach l $lines { + set tl [string trim $l] + if {$tl == ""} { + continue + } + set tl [string trimleft $l] + + # The size of the indent prefix is the length + # difference between the original and trimmed line + return [expr { [string length $l] - [string length $tl] } ] + } + + return 0 +} + +proc get_indent {l} { + set tl [string trimleft $l] + return [expr {[string length $l] - [string length $tl]} ] +} + +# lprefix: indent size found in the line +# prefixlen: general prefix in the source format +proc indent_size {lprefix prefixlen} { + if {$lprefix < $prefixlen} { + return -1 + } + + return [expr {$lprefix - $prefixlen}] +} + +proc reindent {line prefixlen} { + set lprefix [get_indent $line] + set indent [indent_size $lprefix $prefixlen] + if {$indent == -1} { + # Line is shorter than the original prefix, so return original + return $line + } + + return [string repeat " " $indent][string trimleft $line] +} + + +# 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 outdir [file dirname $loggers_modulename] +if {![file exists $outdir] || ![file isdirectory $outdir]} { + puts stderr "ERROR: The directory for the output files '$outdir' doesn't exist" + 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_entries_from_table {ptabprefix pattern} { + + #puts "PTABPREFIX: '$ptabprefix'" + + foreach v $::pattern_vars { + global $v + } + + # For the [subst] call, use variables + # longname + # shortname + # description + + foreach line [split $::loggers_table \n] { + set line [string trim $line] + + # Skip empty lines and comment lines + if {$line == "" || [string index $line 0] == "#"} { + continue + } + + set description [lassign $line longname shortname] + + # Strip one embedding level + if {[llength $description] == 1} { + set description [lindex $description 0] + } + append entries "${ptabprefix}[string trimleft [subst -nobackslashes $pattern]]\n" + } + + return $entries +} + +proc reindent_all {text indentsize} { + set lines [split $text \n] + + # Find the first non-empty line + for {set ix 0} {$ix < [llength $lines]} {incr ix} { + if {[string trim [lindex $lines $ix]] != ""} { + break + } + } + if {$ix != 0} { + set lines [lrange $lines $ix end] + } + + set remindent [get_indent [lindex $lines 0]] + + set out "" + + foreach l $lines { + set tl [string trimleft $l] + if {$tl == ""} { + append out \n + } else { + append out [string repeat " " $remindent]$tl\n + } + } + return $out +} + +proc generate_file {od target} { + + foreach v $::pattern_vars { + global $v + } + + # Here we have: + # format_model: format for the whole file, should contain $entries + # pattern: pattern for every entry, to be replaced by $entries + 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 indentlen [get_trim_prefix_length $format_model] + + set newformat "" + set trimmed 0 + foreach line [split $format_model \n] { + + + if {[string trim $line] == ""} { + if {$trimmed} { + append newformat "\n" + } + continue + } + set trimmed 1 + + set line [reindent $line $indentlen] + append newformat $line\n + + set ie [string first {$} $line] + if {$ie != -1} { + if {[string range $line $ie end] == {$entries}} { + set ptabprefix [string repeat " " [get_indent $line]] + #puts "CAUGHT ENTRIES: '$line' indent size:[get_indent $line] PTAB '$ptabprefix'" + } + } + } + + set format_model $newformat + unset newformat + } + + set entries "" + + if {[string trim $pattern] != "" } { + + set prevval 0 + set pattern [reindent_all $pattern [string length $ptabprefix]] + + #puts "ENTRIES PATTERN: '$pattern' PTABPREFIX: '$ptabprefix'" + + append entries [generate_entries_from_table $ptabprefix $pattern] + } + + if {$hpattern != ""} { + if {$hpattern == "="} { + set hpattern $pattern + } else { + set hpattern [string trim $hpattern] + } + + # Extra line to separate from the normal entries + append entries "\n" + append entries [generate_entries_from_table $ptabprefix $hpattern] + } + + # --- 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] + set entries [string trimleft [reindent_all $entries [string length $ptabprefix]]] + + if {$format_model == ""} { + set format_model $entries + } + + #puts "ENTRY SUBST: '$format_model'" + + # 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 [dict keys $generation] + +foreach suf $entryfiles { + + set f ${loggers_modulename}.$suf + + # 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 $suf + close $od + if { [file exists $filepath] } { + puts stderr "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/srtcore/srt_compat.c b/logging/hvu_compat.c similarity index 97% rename from srtcore/srt_compat.c rename to logging/hvu_compat.c index 819fe41f5..ba72ed00e 100644 --- a/srtcore/srt_compat.c +++ b/logging/hvu_compat.c @@ -16,8 +16,7 @@ written by // Prevents from misconfiguration through preprocessor. -#include "platform_sys.h" -#include +#include "hvu_compat.h" #include #include @@ -51,7 +50,7 @@ static const char* SysStrError_Fallback(int errnum, char* buf, size_t buflen) // a fallback message will be returned, either as returned by the underlying // function, or crafted by this function as a response to error in an // underlying function. -extern const char * SysStrError(int errnum, char * buf, size_t buflen) +extern const char * hvu_SysStrError(int errnum, char * buf, size_t buflen) { if (buf == NULL || buflen < 4) // Required to put ??? into it as a fallback { diff --git a/srtcore/srt_compat.h b/logging/hvu_compat.h similarity index 82% rename from srtcore/srt_compat.h rename to logging/hvu_compat.h index 46fe6aa88..31cdc2cb9 100644 --- a/srtcore/srt_compat.h +++ b/logging/hvu_compat.h @@ -14,18 +14,17 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC_SRT_COMPAT_H -#define INC_SRT_COMPAT_H +#ifndef INC_HVU_COMPAT_H +#define INC_HVU_COMPAT_H #include -#include #ifdef __cplusplus extern "C" { #endif /* Ensures that we store the error in the buffer and return the buffer. */ -const char * SysStrError(int errnum, char * buf, size_t buflen); +const char * hvu_SysStrError(int errnum, char * buf, size_t buflen); #ifdef __cplusplus } // extern C @@ -36,14 +35,21 @@ const char * SysStrError(int errnum, char * buf, size_t buflen); #include #include +#include + +namespace hvu +{ + inline std::string SysStrError(int errnum) { char buf[1024]; - return SysStrError(errnum, buf, 1024); + return ::hvu_SysStrError(errnum, buf, 1024); } inline struct tm SysLocalTime(time_t tt) { + using namespace std; + struct tm tms; memset(&tms, 0, sizeof tms); #ifdef _WIN32 @@ -60,7 +66,8 @@ inline struct tm SysLocalTime(time_t tt) return tms; } +} #endif // defined C++ -#endif // INC_SRT_COMPAT_H +#endif // macroguard diff --git a/logging/hvu_sync.h b/logging/hvu_sync.h new file mode 100644 index 000000000..5cc6976db --- /dev/null +++ b/logging/hvu_sync.h @@ -0,0 +1,31 @@ +/* + * 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. + *****************************************************************************/ + +// This file should provide the standard way of sync facilities (mutex, scoped lock, atomic, threads) +// This requires, however, at least C++11. For C++98 and C++03 you need to provide some external +// facility. + +#ifndef INC_HVU_SYNC_H +#define INC_HVU_SYNC_H + +#include +#include +#include +#define HVU_EXT_MUTEX std::mutex +#define HVU_EXT_LOCKGUARD std::lock_guard +#define HVU_EXT_ATOMIC std::atomic +#define HVU_EXT_THIS_THREAD std::this_thread + +#endif diff --git a/srtcore/threadname.h b/logging/hvu_threadname.h similarity index 93% rename from srtcore/threadname.h rename to logging/hvu_threadname.h index 6233e36fc..6d6ba8453 100644 --- a/srtcore/threadname.h +++ b/logging/hvu_threadname.h @@ -13,8 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC_SRT_THREADNAME_H -#define INC_SRT_THREADNAME_H +#ifndef INC_HVU_THREADNAME_H +#define INC_HVU_THREADNAME_H // NOTE: // HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H @@ -31,6 +31,11 @@ written by // Linux-MUSL(MUSL-1.1.20 Partial Implementation. See below). // MINGW-W64(4.0.6) +// If not available, this facility will try to get the thread ID +// using C++11 facilities. For C++03 you have to provide: +// - HVU_EXT_INCLUDE_THREAD (For C++11: ) +// - HVU_EXT_THIS_THREAD (For C++11: std::this_thread) + #if defined(HAVE_PTHREAD_GETNAME_NP_IN_PTHREAD_NP_H) \ || defined(HAVE_PTHREAD_SETNAME_NP_IN_PTHREAD_NP_H) #include @@ -56,16 +61,23 @@ written by #include #endif #include +#else + +#ifdef HVU_EXT_INCLUDE_SYNC +#include HVU_EXT_INCLUDE_SYNC +#else +#include "hvu_sync.h" +#endif + +#include + #endif #include #include #include -#include "common.h" -#include "sync.h" - -namespace srt { +namespace hvu { class ThreadName { @@ -94,7 +106,6 @@ class ThreadName 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 @@ -165,7 +176,7 @@ class ThreadName { // The default implementation will simply try to get the thread ID std::ostringstream bs; - bs << "T" << sync::this_thread::get_id(); + bs << "T" << HVU_EXT_THIS_THREAD::get_id(); size_t s = bs.str().copy(output, BUFSIZE - 1); output[s] = '\0'; return true; diff --git a/logging/logging.cpp b/logging/logging.cpp new file mode 100644 index 000000000..884759a1f --- /dev/null +++ b/logging/logging.cpp @@ -0,0 +1,448 @@ +/* + * 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. + *****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "logging_api.h" +#include "logging.h" +#include "hvu_threadname.h" +#include "hvu_compat.h" + + +#if __cplusplus > 201100L +#define HVU_LOG_STATIC_ASSERT(cond, msg) static_assert(cond, msg) +#else +#define HVU_LOG_STATIC_ASSERT(cond, msg) +#endif + +// MSVC likes to pollute things +#undef min +#undef max + +using namespace std; + +namespace hvu +{ +namespace logging +{ + +// NOTE: names are used to be assigned to names, +// but hvu::logging uses only some significant ones +// (here with -> assigned dispatcher symbols): +// +// - fatal/crit -> Fatal; +// - error/err -> Error; +// - warning/warn -> Warn; +// - note/notice -> Note; +// - debug -> Debug; +// +// Special trick to initialize it also in C++03 mode. +struct LevelNamesWrapper +{ + map names; + LevelNamesWrapper(); +}; + +LevelNamesWrapper::LevelNamesWrapper() +{ + // This is based on codes taken from + // This is POSIX standard, so it's not going to change. + // Haivision standard only adds one more severity below + // DEBUG named DEBUG_TRACE to satisfy all possible needs. + + // Using only values replicated in LogLevel::type + names.insert(make_pair("crit", LOG_CRIT )); + names.insert(make_pair("debug", LOG_DEBUG )); + names.insert(make_pair("err", LOG_ERR )); + names.insert(make_pair("error", LOG_ERR )); + names.insert(make_pair("fatal", LOG_CRIT )); + names.insert(make_pair("notice", LOG_NOTICE )); + names.insert(make_pair("note", LOG_NOTICE )); + names.insert(make_pair("warn", LOG_WARNING )); + names.insert(make_pair("warning", LOG_WARNING )); +} + +LogLevel::type parse_level(const std::string& name) +{ + // Values can be of more resolution than hvu::logging uses, + // but it only puts the highest level value. Log messages are + // enabled only if they are on that level or below. + static LevelNamesWrapper level; + + map::iterator i = level.names.find(name); + if (i == level.names.end()) + return LogLevel::invalid; + return LogLevel::type(i->second); +} + +std::set parse_fa(const hvu::logging::LogConfig& config, std::string fa, std::set* punknown) +{ + set fas; + + // The split algo won't work on empty string. + if ( fa == "" ) + return fas; + + // To enable all FAs, you can call enable_fa() with zero array size. + // But for the APIs that require particular FA IDs for various operations + // they need to get the actual numbers. + if ( fa == "all" ) + { + // Start from 1 as general is always on. + for (size_t i = 1; i < config.size(); ++i) + fas.insert(i); + + return fas; + } + + int (*ToLower)(int) = &std::tolower; + transform(fa.begin(), fa.end(), fa.begin(), ToLower); + + vector xfas; + size_t pos = 0, ppos = 0; + for (;;) + { + if ( fa[pos] != ',' ) + { + ++pos; + if ( pos < fa.size() ) + continue; + } + size_t n = pos - ppos; + if ( n != 0 ) + xfas.push_back(fa.substr(ppos, n)); + ++pos; + if ( pos >= fa.size() ) + break; + ppos = pos; + } + + for (size_t i = 0; i < xfas.size(); ++i) + { + fa = xfas[i]; + int faid = config.find_id(fa); + if (faid == -1) + { + if (punknown) + punknown->insert(fa); // If requested, add it back silently + else + cerr << "ERROR: Invalid log functional area spec: '" << fa << "' - skipping\n"; + continue; + } + + fas.insert(faid); + } + + return fas; +} + + +// Note: subscribe() and unsubscribe() functions are being called +// in the global constructor and destructor only, as the +// Logger objects (and inside them also their LogDispatcher) +// are being created. It's not predicted that LogDispatcher +// object are going to be created any other way than as +// global objects. Therefore the construction and destruction +// of them happens always in the main thread. + +void LogConfig::subscribe(LogDispatcher* lg) +{ + vector::iterator p = std::find(loggers.begin(), loggers.end(), lg); + if (p != loggers.end()) + return; // Do not register twice + + loggers.push_back(lg); +} + +void LogConfig::unsubscribe(LogDispatcher* lg) +{ + vector::iterator p = std::find(loggers.begin(), loggers.end(), lg); + if (p != loggers.end()) + { + loggers.erase(p); + } +} + +// This function doesn't have any protection on itself, +// however the API functions from which it is called, call +// it already under a mutex protection. +void LogConfig::updateLoggersState() +{ + for (vector::iterator p = loggers.begin(); + p != loggers.end(); ++p) + { + (*p)->Update(); + } +} + +LogDispatcher::LogDispatcher(int functional_area, bool initially_enabled, + LogConfig& config, LogLevel::type log_level, + const char* level_pfx, const char* logger_pfx /*[[nullable]]*/): + fa(functional_area), + level(log_level), + level_prefix(level_pfx), + enabled(initially_enabled), + src_config(&config) +{ + // The Logger object and the config must be defined in the same + // file because otherwise otherwise the order of initialization cannot + // be ensured. + + // We need to keep the user prefix and level prefix in one table. + // So let's copy initially the level prefix. This one is not + // allowed to be NULL. + + prefix_len = strlen(level_pfx); + memcpy(prefix, level_pfx, prefix_len+1); + + set_prefix(logger_pfx); + + config.subscribe(this); + Update(); +} + +void LogDispatcher::set_prefix(const char* logger_pfx) +{ + size_t level_pfx_len = level_prefix ? strlen(level_prefix) : 0; + const size_t logger_pfx_len = logger_pfx ? strlen(logger_pfx) : 0; + + if (logger_pfx && level_pfx_len + logger_pfx_len + 1 < MAX_PREFIX_SIZE) + { + memcpy(prefix, level_prefix, level_pfx_len); + prefix[level_pfx_len] = ':'; + memcpy(prefix + level_pfx_len + 1, logger_pfx, logger_pfx_len); + prefix_len = level_pfx_len + logger_pfx_len + 1; + prefix[prefix_len] = '\0'; + } + else if (level_prefix) + { + // Prefix too long, so copy only level_pfx and only + // as much as it fits + size_t copylen = std::min(+MAX_PREFIX_SIZE, level_pfx_len); + memcpy(prefix, level_prefix, copylen); + prefix[copylen] = '\0'; + prefix_len = copylen; + } + else + { + prefix[0] = '\0'; + prefix_len = 0; + } +} + +LogDispatcher::~LogDispatcher() +{ + src_config->unsubscribe(this); +} + +void LogDispatcher::Update() +{ + bool enabled_in_fa = src_config->enabled_fa[fa]; + enabled = enabled_in_fa && level <= src_config->max_level; +} + + +// SendLogLine can be compiled normally. It's intermediately used by: +// - Proxy object, which is replaced by DummyProxy when !HVU_ENABLE_LOGGING +// - PrintLogLine, which has empty body when !HVU_ENABLE_LOGGING +void LogDispatcher::SendLogLine(const char* file, int line, const std::string& area, const std::string& msg) +{ + src_config->lock(); + if ( src_config->loghandler_fn ) + { + (*src_config->loghandler_fn)(src_config->loghandler_opaque, int(level), file, line, area.c_str(), msg.c_str()); + } + else if ( src_config->log_stream ) + { + src_config->log_stream->write(msg.data(), msg.size()); + src_config->log_stream->flush(); + } + src_config->unlock(); +} + + +#if HVU_ENABLE_LOGGING + +LogDispatcher::Proxy::Proxy(LogDispatcher& guy) + : that(guy) + , i_file("") + , i_line(0) + , flags(that.src_config->flags) +{ + if (that.IsEnabled()) + { + // Create logger prefix + that.CreateLogLinePrefix(os); + } +} + +LogDispatcher::Proxy::Proxy(LogDispatcher& guy, const char* f, int l, const std::string& a) + : that(guy) + , i_file(f) + , i_line(l) + , flags(that.src_config->flags) +{ + if (that.IsEnabled()) + { + area = a; + // Create logger prefix + that.CreateLogLinePrefix(os); + } +} + +LogDispatcher::Proxy& LogDispatcher::Proxy::vform(const char* fmts, va_list ap) +{ + static const int BUFLEN = 512; + char buf[BUFLEN]; + +#if defined(_MSC_VER) && _MSC_VER < 1900 + int wlen = _vsnprintf(buf, BUFLEN - 1, fmts, ap); +#else + int wlen = vsnprintf(buf, BUFLEN, fmts, ap); +#endif + + if (wlen < 1) // catch both 0 and -1 + { + // ERROR when formatting + const char msg[] = ""; + os.write(msg, sizeof (msg)); + return *this; + } + + // vsnprintf returns the number of characters printed, + // or the size of required buffer, if the buffer was too small + // and it resulted in a truncated string. Ignore truncation, + // just make sure that terminating 0 was properly specified. + size_t len = wlen >= BUFLEN ? BUFLEN - 1 : wlen; + if ( buf[len-1] == '\n' ) + { + // Remove EOL character, should it happen to be at the end. + // The EOL will be added at the end anyway. + --len; + } + + os.write(buf, len); + return *this; +} + +void LogDispatcher::CreateLogLinePrefix(hvu::ofmtbufstream& serr) +{ + using namespace std; + using namespace hvu; + + HVU_LOG_STATIC_ASSERT(hvu::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(HVU_LOGF_DISABLE_TIME)) + { + // Not necessary if sending through the queue. + timeval tv; + gettimeofday(&tv, NULL); + struct tm tm = hvu::SysLocalTime((time_t) tv.tv_sec); + + if (strftime(tmp_buf, sizeof(tmp_buf), "%X.", &tm)) + { + serr << tmp_buf << fmt(tv.tv_usec, fmtc().fillzero().width(6)); + } + } + + // Note: ThreadName::get needs a buffer of size min. ThreadName::BUFSIZE + if (!isset(HVU_LOGF_DISABLE_THREADNAME) && ThreadName::get(tmp_buf)) + { + serr << OFMT_RAWSTR("/") << tmp_buf; + } + + if (!isset(HVU_LOGF_DISABLE_SEVERITY)) + { + serr.write(prefix, prefix_len); // include terminal 0 + } + + serr << OFMT_RAWSTR(": "); +} + +#undef HVU_LOG_STATIC_ASSERT + +std::string LogDispatcher::Proxy::ExtractName(std::string pretty_function) +{ + if ( pretty_function == "" ) + return ""; + size_t pos = pretty_function.find('('); + if ( pos == std::string::npos ) + return pretty_function; // return unchanged. + + pretty_function = pretty_function.substr(0, pos); + + // There are also template instantiations where the instantiating + // parameters are encrypted inside. Therefore, search for the first + // open < and if found, search for symmetric >. + + int depth = 1; + pos = pretty_function.find('<'); + if ( pos != std::string::npos ) + { + size_t end = pos+1; + for(;;) + { + ++pos; + if ( pos == pretty_function.size() ) + { + --pos; + break; + } + if ( pretty_function[pos] == '<' ) + { + ++depth; + continue; + } + + if ( pretty_function[pos] == '>' ) + { + --depth; + if ( depth <= 0 ) + break; + continue; + } + } + + std::string afterpart = pretty_function.substr(pos+1); + pretty_function = pretty_function.substr(0, end) + ">" + afterpart; + } + + // Now see how many :: can be found in the name. + // If this occurs more than once, take the last two. + pos = pretty_function.rfind("::"); + + if ( pos == std::string::npos || pos < 2 ) + return pretty_function; // return whatever this is. No scope name. + + // Find the next occurrence of :: - if found, copy up to it. If not, + // return whatever is found. + pos -= 2; + pos = pretty_function.rfind("::", pos); + if ( pos == std::string::npos ) + return pretty_function; // nothing to cut + + return pretty_function.substr(pos+2); +} +#endif + +}} // (end namespace hvu::logging) + diff --git a/logging/logging.h b/logging/logging.h new file mode 100644 index 000000000..d320e8517 --- /dev/null +++ b/logging/logging.h @@ -0,0 +1,611 @@ +/* + * 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. + *****************************************************************************/ + +// This file contains the interface for the logging system and should be +// included in a file where you are going to use the logging instructions, +// also indirectly through the generated logger FA interface file. + +// Usage: +// +// LOGC(gglog.Note, log << "There are " << note_no << " notes."); +// LOGP(gglog.Note, "There are ", note_no, " notes."); +// +// Where: +// +// LOGC/LOGP: The logger macro. This allows to turn logging off, if not HVU_ENABLE_LOGGING. +// *C: Use the iostream-style formatting with 'log' as the stream marker. +// *P: Use multiple arguments (note: for C++03 only one argument available). +// +// Note that the logger dispatchers ("Note" here) can be also called directly, but +// this way you can't control the logging at compile time (or you have to organize +// it somehow by yourself; this macro allows also to record __FILE__ and __LINE__ +// of the log (although not used by the default format). +// +// gglog.Note("There are ", note_no, " notes."); +// or +// gglog.Note() << "There are " << note_no << " notes."; +// +// Formatting with printf-style is partially supported, but you need to do your own +// wrapper for that, which will do something like: +// +// va_list ap; +// va_start(ap, args); +// gglog.Note.setloc(file, line, function).vform(format, ap); +// va_end(ap); +// +// (Note that file, line and function parameters should be extracted through +// the macro from __FILE__, __LINE__ and __function__ at the macro application). + + +#ifndef INC_HVU_LOGGING_H +#define INC_HVU_LOGGING_H + +// This is for a case when compiling in C++03/C++98 mode. +// In this case you need to provide the definitions like +// below and define HVU_EXT_NOCXX11 to 1. + +#ifndef HVU_EXT_NOCXX11 +#define HVU_EXT_NOCXX11 0 +#define HVU_EXT_INCLUDE_MUTEX +#define HVU_EXT_INCLUDE_ATOMIC +#endif + +#include +#include +#include +#include +#include + +#ifdef HVU_EXT_INCLUDE_SYNC +#include HVU_EXT_INCLUDE_SYNC +#else +#include "hvu_sync.h" +#endif + + +#include +#ifdef _WIN32 +#include "win/wintime.h" +#include +#else +#include +#endif + +#include "logging_api.h" +#include "ofmt.h" + +#if !defined(HVU_ENABLE_LOGGING) +#define HVU_ENABLE_LOGGING 0 +#endif + +#if HVU_ENABLE_LOGGING + +// GENERAL NOTE: All logger functions ADD THEIR OWN \n (EOL). Don't add any your own EOL character. +// The logging system may not add the EOL character, if appropriate flag was set in log settings. +// Anyway, treat the whole contents of eventually formatted message as exactly one line. + +// LOGC uses an iostream-like syntax, using the special 'log' symbol. +// This symbol isn't visible outside the log macro parameters. +// Usage: LOGC(gglog.Debug, log << param1 << param2 << param3); +#define LOGC(logdes, args) if (logdes.IsEnabled()) \ +{ \ + hvu::logging::LogDispatcher::Proxy log(logdes, __FILE__, __LINE__, __FUNCTION__); \ + { (void)(const hvu::logging::LogDispatcher::Proxy&)(args); } \ +} + +// LOGP is C++11 only OR with only one argument. +// Usage: LOGP(gglog.Debug, param1, param2, param3); +#define LOGP(logdes, ...) if (logdes.IsEnabled()) logdes.printloc(__FILE__, __LINE__, __FUNCTION__,##__VA_ARGS__) + +#define IF_LOGGING(instr,...) instr,##__VA_ARGS__ + +#if HVU_ENABLE_HEAVY_LOGGING + +#define HLOGC LOGC +#define HLOGP LOGP +#define IF_HEAVY_LOGGING IF_LOGGING + +#else + +#define HLOGC(...) +#define HLOGP(...) +#define IF_HEAVY_LOGGING(...) (void)0 + +#endif + +#else // IF LOGGING DISABLED + +#define LOGC(...) +#define LOGP(...) +#define HLOGC(...) +#define HLOGP(...) +#define IF_HEAVY_LOGGING(...) (void)0 +#define IF_LOGGING(...) (void)0 + +#endif + +namespace hvu +{ +namespace logging +{ + +// The LogDispatcher class represents the object that is responsible for +// printing the log line. +class LogDispatcher +{ + friend class Logger; + friend class LogConfig; + + int fa; + LogLevel::type level; + static const size_t MAX_PREFIX_SIZE = 32; + const char* level_prefix; // ONLY STATIC CONSTANTS ALLOWED + char prefix[MAX_PREFIX_SIZE+1]; + size_t prefix_len; + HVU_EXT_ATOMIC enabled; + class LogConfig* src_config; + + bool isset(int flg); + +public: + + void set_prefix(const char* prefix); + + LogDispatcher(int functional_area, bool initially_enabled, class LogConfig& config, LogLevel::type log_level, + const char* level_pfx, //NOTE: ONLY STATIC CONSTANTS ALLOWED! + const char* logger_pfx = NULL); + + ~LogDispatcher(); + + void Update(); + + bool IsEnabled() { return enabled; } + + void CreateLogLinePrefix(hvu::ofmtbufstream&); + void SendLogLine(const char* file, int line, const std::string& area, const std::string& sl); + + // log.Debug("This is the ", nth, " time"); <--- C++11 only. + // log.Debug() << "This is the " << nth << " time"; <--- C++03 available. + +#if HAVE_CXX11 + + template + void PrintLogLine(const char* file, int line, const std::string& area, Args&&... args); + + template + void operator()(Args&&... args) + { + PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", args...); + } + + template + void printloc(const char* file, int line, const std::string& area, Args&&... args) + { + PrintLogLine(file, line, area, args...); + } +#else + template + void PrintLogLine(const char* file, int line, const std::string& area, const Arg& arg); + + // For C++03 (older) standard provide only with one argument. + template + void operator()(const Arg& arg) + { + PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg); + } + + void printloc(const char* file, int line, const std::string& area, const std::string& arg1) + { + PrintLogLine(file, line, area, arg1); + } +#endif + +#if HVU_ENABLE_LOGGING + struct Proxy + { + LogDispatcher& that; + + hvu::ofmtbufstream os; + + // CACHE!!! + const char* i_file; + int i_line; + int flags; + std::string area; + + // Left for future. Not sure if it's more convenient + // to use this to translate __PRETTY_FUNCTION__ to + // something short, or just let's leave __FUNCTION__ + // or better __func__. + std::string ExtractName(std::string pretty_function); + + Proxy(LogDispatcher& guy); + Proxy(LogDispatcher& guy, const char* f, int l, const std::string& a); + + // Copy constructor is needed due to noncopyable ostringstream. + // This is used only in creation of the default object, so just + // use the default values, just copy the location cache. + Proxy(const Proxy& p) + : that(p.that) + , i_file(p.i_file) + , i_line(p.i_line) + , flags(p.flags) + , area(p.area) + { + } + + template + Proxy& operator<<(const T& arg) // predicted for temporary objects + { + if ( that.IsEnabled() ) + { + os << arg; + } + return *this; + } + + // Provide explicit overloads for const char* and string + // so that printing them bypasses the formatting facility + + // Special case for atomics, as passing them to the fmt facility + // requires unpacking the real underlying value. + template + Proxy& operator<<(const HVU_EXT_ATOMIC& arg) + { + if (that.IsEnabled()) + { + os << arg.load(); + } + return *this; + } + +#if HAVE_CXX11 + + void dispatch() {} + + template + void dispatch(const Arg1& a1, const Args&... others) + { + *this << a1; + dispatch(others...); + } + + // Special dispatching for atomics must be provided here. + // By some reason, "*this << a1" expression gets dispatched + // to the general version of operator<<, not the overload for + // atomic. Even though the compiler shows Arg1 type as atomic. + template + void dispatch(const HVU_EXT_ATOMIC& a1, const Args&... others) + { + *this << a1.load(); + dispatch(others...); + } + +#endif + ~Proxy() + { + if (that.IsEnabled()) + { + if ((flags & HVU_LOGF_DISABLE_EOL) == 0) + os << OFMT_RAWSTR("\n"); // XXX would be nice to use a symbol for it + + that.SendLogLine(i_file, i_line, area, os.str()); + } + // XXX Consider clearing the 'os' manually + } + + Proxy& vform(const char* fmts, va_list ap); + }; + + + friend struct Proxy; + + Proxy setloc(const char* f, int l, const std::string& a) + { + return Proxy(*this, f, l, a); + } + Proxy operator()() { return Proxy(*this); } + +#else + + // Dummy proxy that does nothing + struct DummyProxy + { + template + DummyProxy& operator<<(const T& ) + { + return *this; + } + + DummyProxy& vform(const char*, va_list) + { + return *this; + } + + }; + + DummyProxy operator()() + { + return DummyProxy(); + } + + DummyProxy setloc(const char* , int , const std::string& ) + { + return DummyProxy(); + } +#endif +}; + +// Proxy is the class provided for the sake of C++03 support +// using the << operator syntax. Ignore it if you only use +// the multi-parameter call. + +#if HVU_ENABLE_LOGGING + + +#endif + +class Logger +{ + friend class LogConfig; + + int m_fa; + Logger(const std::string& idname, class LogConfig& config, bool initially_enabled, const char* logger_pfx, int forced_fa); + +public: + + LogDispatcher Debug; + LogDispatcher Note; + LogDispatcher Warn; + LogDispatcher Error; + LogDispatcher Fatal; + + Logger(const std::string& idname, class LogConfig& config, bool initially_enabled, const char* logger_pfx = NULL); + int id() const { return m_fa; } +}; + +class LogConfig +{ +public: + typedef std::vector fa_flags_t; +private: + + friend class Logger; + friend class LogDispatcher; + char initialized; // Marker to detect uninitialized object + + fa_flags_t enabled_fa; // NOTE: assumed atomic reading + LogLevel::type max_level; // NOTE: assumed atomic reading + std::ostream* log_stream; + HVU_LOG_HANDLER_FN* loghandler_fn; + void* loghandler_opaque; + mutable HVU_EXT_MUTEX config_lock; + int flags; + std::vector loggers; + + // Index of 'names' and 'enabled_fa' come together + // and they are numeric index of the logger. + std::vector names; + +public: + + Logger general; + + size_t size() const { return names.size(); } + + const std::string& name(size_t ix) const + { + static const std::string& emp = ""; + return ix >= names.size() ? emp : names[ix]; + } + + int find_id(const std::string& name) const + { + // Linear search, but we state the number of FAs will be + // relatively low and will only happen in the setup time + // of the program. + for (size_t i = 0; i < names.size(); ++i) + if (names[i] == name) + return int(i); + + return -1; + } + + // Setters + void set_handler(void* opaque, HVU_LOG_HANDLER_FN* fn) + { + HVU_EXT_LOCKGUARD gg(config_lock); + loghandler_fn = fn; + loghandler_opaque = opaque; + } + + void set_flags(int f) + { + HVU_EXT_LOCKGUARD gg(config_lock); + flags = f; + } + + void set_stream(std::ostream& str) + { + HVU_EXT_LOCKGUARD gg(config_lock); + log_stream = &str; + } + + void set_maxlevel(LogLevel::type l) + { + HVU_EXT_LOCKGUARD gg(config_lock); + max_level = l; + updateLoggersState(); + } + + void enable_fa(const std::string& name, bool enabled) + { + HVU_EXT_LOCKGUARD gg(config_lock); + + for (size_t i = 0; i < names.size(); ++i) + if (names[i] == name) + { + enabled_fa[i] = enabled; + break; + } + } + + // XXX You can add the use of std::array in C++11 mode. + void enable_fa(const int* farray, size_t fs, bool enabled) + { + HVU_EXT_LOCKGUARD gg(config_lock); + if (fs == 0) + { + if (enabled) + enabled_fa = fa_flags_t(enabled_fa.size(), enabled); + else + { + enabled_fa = fa_flags_t(enabled_fa.size(), enabled); + + // General can never be disabled. + enabled_fa[0] = true; + } + } + else + { + for (size_t i = 0; i < fs; ++i) + { + size_t fa = farray[i]; + if (fa < enabled_fa.size()) + enabled_fa[fa] = enabled; + } + } + updateLoggersState(); + } + + void setup_fa(const std::set& selected) + { + HVU_EXT_LOCKGUARD gg(config_lock); + for (size_t i = 0; i < enabled_fa.size(); ++i) + enabled_fa[i] = bool(selected.count(i)); + updateLoggersState(); + } + + void setup_fa(const std::set& selected, bool enabled) + { + HVU_EXT_LOCKGUARD gg(config_lock); + std::set::const_iterator i = selected.begin(), e = selected.end(); + for (; i != e; ++i) + if (size_t(*i) < enabled_fa.size()) + enabled_fa[*i] = enabled; + updateLoggersState(); + } + + int generate_fa_id(const std::string& name) + { + HVU_EXT_LOCKGUARD gg(config_lock); + + // 'names' and 'enabled_fa' grow together! + size_t firstfree = names.size(); + names.push_back(name); + enabled_fa.push_back(false); + return int(firstfree); + } + + LogConfig() + : initialized(1) // global objects are 0-initialized + , max_level(LogLevel::warning) + , log_stream(&std::cerr) + , loghandler_fn() + , loghandler_opaque() + , flags() + , general("GENERAL", *this, true, "HVU.gg") + { + // XXX May do some verification code if LogConfig is + // a global variable. + } + + ~LogConfig() + { + } + + // XXX Add TSA markers for lock/unlock + void lock() const { config_lock.lock(); } + void unlock() const { config_lock.unlock(); } + + void subscribe(LogDispatcher*); + void unsubscribe(LogDispatcher*); + void updateLoggersState(); +}; + +struct LogConfigSingleton +{ + LogConfig& instance() + { + static LogConfig this_instance; + return this_instance; + } +}; + +inline Logger::Logger(const std::string& idname, class LogConfig& config, bool initially_enabled, const char* logger_pfx): + m_fa(config.generate_fa_id(idname)), + Debug (m_fa, initially_enabled, config, LogLevel::debug, " D", logger_pfx), + Note (m_fa, initially_enabled, config, LogLevel::note, ".N", logger_pfx), + Warn (m_fa, initially_enabled, config, LogLevel::warning, "!W", logger_pfx), + Error (m_fa, initially_enabled, config, LogLevel::error, "*E", logger_pfx), + Fatal (m_fa, initially_enabled, config, LogLevel::fatal, "!!FATAL!!", logger_pfx) +{ + if (!config.initialized) + { + // Global object initialization problem! + throw std::runtime_error("Config object can be used only if declared in the same file"); + } + config.enabled_fa[m_fa] = initially_enabled; +} + +inline bool LogDispatcher::isset(int flg) { return (src_config->flags & flg) != 0; } + + +#if HAVE_CXX11 + +template +inline void LogDispatcher::PrintLogLine(const char* file, int line, const std::string& area, Args&&... args) +{ + (void)file; + (void)line; + (void)area; +#if HVU_ENABLE_LOGGING + Proxy(*this).dispatch(args...); +#else + (void)sizeof...(args); +#endif +} + +#else // !HAVE_CXX11 + +template +inline void LogDispatcher::PrintLogLine(const char* file, int line, const std::string& area, const Arg& arg) +{ + (void)file; + (void)line; + (void)area; +#if HVU_ENABLE_LOGGING + Proxy(*this) << arg; +#else + (void)(arg); +#endif +} + +#endif // HAVE_CXX11 + +} +} + +#endif // INC_SRT_LOGGING_H diff --git a/srtcore/logging_api.h b/logging/logging_api.h similarity index 62% rename from srtcore/logging_api.h rename to logging/logging_api.h index 4fc3b812b..1836fddfd 100644 --- a/srtcore/logging_api.h +++ b/logging/logging_api.h @@ -13,8 +13,13 @@ written by Haivision Systems Inc. *****************************************************************************/ -#ifndef INC_SRT_LOGGING_API_H -#define INC_SRT_LOGGING_API_H +// This file contains definitions that can be provided for the application that +// would like to control the logging of a library. Part if this can be also used +// in a C application, while you only need C++ code to directly deal with the +// logging system. + +#ifndef INC_HVU_LOGGING_API_H +#define INC_HVU_LOGGING_API_H // These are required for access functions: // - adding FA (requires set) @@ -22,10 +27,11 @@ written by #ifdef __cplusplus #include #include +#include #endif #ifdef _WIN32 -#include "win/syslog_defs.h" +#include "windows_syslog.h" #else #include #endif @@ -36,41 +42,34 @@ written by #define LOG_DEBUG_TRACE 8 #endif // It's unused anyway, just for the record. -#define SRT_LOG_LEVEL_MIN LOG_CRIT -#define SRT_LOG_LEVEL_MAX LOG_DEBUG +#define HVU_LOG_LEVEL_MIN LOG_CRIT +#define HVU_LOG_LEVEL_MAX LOG_DEBUG // Flags -#define SRT_LOGF_DISABLE_TIME 1 -#define SRT_LOGF_DISABLE_THREADNAME 2 -#define SRT_LOGF_DISABLE_SEVERITY 4 -#define SRT_LOGF_DISABLE_EOL 8 +#define HVU_LOGF_DISABLE_TIME 1 +#define HVU_LOGF_DISABLE_THREADNAME 2 +#define HVU_LOGF_DISABLE_SEVERITY 4 +#define HVU_LOGF_DISABLE_EOL 8 -// Handler type. -typedef void SRT_LOG_HANDLER_FN(void* opaque, int level, const char* file, int line, const char* area, const char* message); +// Handler type - provided for C API. +typedef void HVU_LOG_HANDLER_FN(void* opaque, int level, const char* file, int line, const char* area, const char* message); #ifdef __cplusplus -namespace srt_logging +namespace hvu { - - -struct LogFA +namespace logging { -private: - int value; -public: - operator int() const { return value; } - - LogFA(int v): value(v) - { - // Generally this was what it has to be used for. - // Unfortunately it couldn't be agreed with the - //logging_fa_all.insert(v); - } -}; -const LogFA LOGFA_GENERAL = 0; +class LogConfig; +// same as HVU_LOG_HANDLER_FN +typedef void loghandler_fn_t(void* opaque, int level, const char* file, int line, const char* area, const char* message); +// Same as C-API flags +const int LOGF_DISABLE_TIME = 1, + LOGF_DISABLE_THREADNAME = 2, + LOGF_DISABLE_SEVERITY = 4, + LOGF_DISABLE_EOL = 8; namespace LogLevel { @@ -86,6 +85,8 @@ namespace LogLevel enum type { + invalid = -1, + fatal = LOG_CRIT, // Fatal vs. Error: with Error, you can still continue. error = LOG_ERR, @@ -99,9 +100,13 @@ namespace LogLevel }; } +extern LogLevel::type parse_level(const std::string&); +extern std::set parse_fa(const hvu::logging::LogConfig& config, std::string fa, std::set* punknown = NULL); + class Logger; -} +} // /logging +} // /hvu #endif #endif diff --git a/logging/ofmt.h b/logging/ofmt.h new file mode 100644 index 000000000..23a95ce9a --- /dev/null +++ b/logging/ofmt.h @@ -0,0 +1,794 @@ +/* + * 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. + *****************************************************************************/ + +// Formatting library for C++ - C++03 compat version of on-demand tagged format API. +// +// This is a header-only lightweight C++03-compatible formatting library, +// which provides the on-demand tagged format API and iostream-style wrapper +// for FILE type from stdio. It has nothing to do with the rest of the {fmt} +// library, except that it reuses the namespace. + +// USAGE: +// +// 1. Using iostream style: +// +// ofmtbufstream sout; +// +// sout << "Value: " << v << " (" << fmt(v, fmtc().hex().width(2).fillzero()) << ")\n"; +// +// NOTE: When passing a string literal, consider using "Value"_V (C++11 only) +// or OFMT_RAWSTR("Value"). Unfortunately C++ doesn't distinguish "Value" and +// char [20] v = "Value"; both here contain "Value\0", but sizeof(v) for them +// returns the size of the allocated space, not size of the string. Although +// the compiler should expand strlen() in place for literals, note that it +// won't do it if optimizations are turned off. +// +// 2. Using variadic style: +// +// sout.print("Value: ", v, " (", fmt(v, fmtc().hex().width(2).fillzero()), ")\n"); +// +// +// OFMT has also a potential to be used together with iostream, but it requires more +// definition support. This is only the basic fragment to be used with the logging system, +// hence it provides only a wrapper over std::stringstream. + +#ifndef INC_HVU_OFMT_H +#define INC_HVU_OFMT_H + +#include +#include +#include +#include +#include + +#if (defined(__cplusplus) && __cplusplus > 199711L) \ + || (defined(_MSVC_LANG) && _MSVC_LANG > 199711L) // Some earlier versions get this wrong +#define OFMT_HAVE_CXX11 1 +#else +#define OFMT_HAVE_CXX11 0 +#endif + +#if OFMT_HAVE_CXX11 +#include +#endif + +namespace hvu +{ + +template +struct basic_fmtc +{ +protected: + typedef std::basic_ios ios; + + typedef typename ios::fmtflags fmtflg_t; + fmtflg_t fmtflg; + unsigned short widthval; + unsigned short precisionval; + // Find a way to adjust it to wchar_t if need be + char fillval; + + union + { + struct + { + bool widthbit:1; + bool precisionbit:1; + bool leadzerobit:1; + bool fillbit:1; + } flags; + unsigned char allbits; + }; + + // Mimics the ios::flags, althouh as unsafe it's internal. + void setf(fmtflg_t flags, fmtflg_t mask) + { + fmtflg_t old = fmtflg & ~mask; + fmtflg = old | flags; + } + + void setf(fmtflg_t f) + { + fmtflg |= f; + } + +public: + basic_fmtc(): + fmtflg(fmtflg_t()), + widthval(0), + precisionval(6), + fillval(' '), + allbits(0) + { + } + +#define OFMTC_TAG(name, body) basic_fmtc& name () { body; return *this; } +#define OFMTC_TAG_VAL(name, body) basic_fmtc& name (int val) { body; return *this; } +#define OFMTC_TAG_VAL_TYPE(type, name, body) basic_fmtc& name (type val) { body; return *this; } + + OFMTC_TAG_VAL(width, flags.widthbit = true; widthval = std::abs(val)); + OFMTC_TAG_VAL(precision, flags.precisionbit = true; precisionval = std::abs(val)); + OFMTC_TAG_VAL_TYPE(CharType, fill, flags.fillbit = true; fillval = val); + + OFMTC_TAG(left, setf(ios::left, ios::adjustfield)); + OFMTC_TAG(right, setf(ios::right, ios::adjustfield)); + OFMTC_TAG(internal, setf(ios::internal, ios::adjustfield)); + OFMTC_TAG(dec, setf(ios::dec, ios::basefield)); + OFMTC_TAG(hex, setf(ios::hex, ios::basefield)); + OFMTC_TAG(oct, setf(ios::oct, ios::basefield)); + OFMTC_TAG(uhex, setf(ios::hex, ios::basefield); setf(ios::uppercase)); + OFMTC_TAG(uoct, setf(ios::oct, ios::basefield); setf(ios::uppercase)); + OFMTC_TAG(general, (void)0); + OFMTC_TAG(ugeneral, setf(ios::uppercase)); +#if __cplusplus > 201103L + OFMTC_TAG(fhex, setf(ios::fixed | ios::scientific, ios::floatfield)); + OFMTC_TAG(ufhex, setf(ios::uppercase); setf(ios::fixed | ios::scientific, ios::floatfield)); +#endif + OFMTC_TAG(exp, setf(ios::scientific, ios::floatfield)); + OFMTC_TAG(scientific, setf(ios::scientific, ios::floatfield)); + OFMTC_TAG(uexp, setf(ios::scientific, ios::floatfield); setf(ios::uppercase)); + OFMTC_TAG(uscientific, setf(ios::scientific, ios::floatfield); setf(ios::uppercase)); + OFMTC_TAG(fixed, setf(ios::fixed, ios::floatfield)); + OFMTC_TAG(nopos, (void)0); + OFMTC_TAG(showpos, setf(ios::showpos)); + OFMTC_TAG(showbase, setf(ios::showbase)); + OFMTC_TAG(showpoint, setf(ios::showpoint)); + OFMTC_TAG(fillzero, flags.leadzerobit = true); + +#undef OFMTC_TAG +#undef OFMTC_TAG_VAL +#undef OFMTC_TAG_VAL_TYPE + + void apply_detailed(std::basic_ostream& os) const + { + if (flags.widthbit) + os.width(widthval); + + if (flags.precisionbit) + os.precision(precisionval); + + if (flags.leadzerobit) + { + os.setf(ios::internal, ios::adjustfield); + os.fill(os.widen('0')); + } + else if (flags.fillbit) + { + os.fill(os.widen(fillval)); + } + } + + void apply(std::basic_ostream& os) const + { + os.flags(fmtflg); + apply_detailed(os); + } + + void apply_ontop(std::basic_ostream& os) const + { + fmtflg_t oldflags = os.flags(); + + // "unfielded" are flags that are single only. + // + // So, all single-bit only flags should be copied as they are + static const fmtflg_t unfielded = ~(ios::adjustfield | ios::basefield | ios::floatfield); + fmtflg_t newflags = fmtflg | (oldflags & unfielded); + + // For "fielded" flags, copy the value from the existing flags + // only if none of the flags in particular field are set in THIS configuration. + if ((newflags & ios::adjustfield) == 0) + newflags |= oldflags & ios::adjustfield; + if ((newflags & ios::basefield) == 0) + newflags |= oldflags & ios::basefield; + if ((newflags & ios::floatfield) == 0) + newflags |= oldflags & ios::floatfield; + + os.flags(newflags); + + apply_detailed(os); + } + +}; + +typedef basic_fmtc fmtc; +typedef basic_fmtc wfmtc; + +// fmt(val, fmtc().alt().hex().width(10)) + +namespace internal +{ + +// Use this as an overload for operator<< for a stream +// in order to make it support every possible sender produced by fmt(). +template +struct fmt_proxy_template +{ + const Value& val; // ERROR: invalidly declared function? --> + // Iostream manipulators should not be sent to the stream. + // use fmt() with fmtc() instead. + SenderType snd; + + fmt_proxy_template(const Value& v, const SenderType& s): val(v), snd(s) {} + + template + void sendto(OutStream& os) const + { + snd.format_send(val, os); + } +}; + +// Simple sender: fmt(value) +struct snd_simple +{ + snd_simple() {} + + template + void format_send(const Value& val, OutStream& os) const + { + os << val; + } +}; + +// ofmt formatter sender: fmt(value, fmtc().parameters...) +template +struct snd_fmtc +{ +private: + basic_fmtc format_spec; +public: + snd_fmtc(const basic_fmtc& f): format_spec(f) {} + + template + void format_send(const Value& val, OutStream& os) const + { + std::stringstream tmp; + format_spec.apply(tmp); + tmp << val; + os << tmp.rdbuf(); + } +}; + +// same as snd_fmtc, but without isolating the stream +// for the call to fmrx(value, fmtc()...) +template +struct snd_stateous +{ + basic_fmtc format_spec; + snd_stateous(const basic_fmtc& f): format_spec(f) {} + + template + void format_send(const Value& val, OutStream& os) const + { + format_spec.apply_ontop(os); + os << val; + } +}; + +// Facility for using iostream manipulators +// for the call to fmt(value, ios::hex, ios::setw(2) ... ) +// For C++03 only available with up to 2 manipulators. +template +inline void snd_ios_manipulate(Stream& os, const Manip& man) +{ + os << man; +} + +template +inline void snd_ios_manipulate(Stream& os, const std::pair& mans) +{ + os << mans.first << mans.second; +} + +#if OFMT_HAVE_CXX11 +template +struct snd_ios_man_tuple +{ + static void send(Stream& s, const Tuple& t) + { + snd_ios_man_tuple::send(s, t); + snd_ios_manipulate(s, std::get(t)); + } +}; + +template +struct snd_ios_man_tuple<0, Tuple, Stream> +{ + static void send(Stream&, const Tuple&) {} +}; + +template +inline void snd_ios_manipulate(Stream& os, const std::tuple& mans) +{ + typedef std::tuple Tuple; + snd_ios_man_tuple::value, Tuple, Stream>::send(os, mans); +} +#endif + +template +struct snd_ios +{ +private: + const Manip& manip; +public: + snd_ios(const Manip& m): manip(m) {} + + template + void format_send(const Value& val, OutStream& os) const + { + std::stringstream tmp; + snd_ios_manipulate(tmp, manip); + tmp << val; + os << tmp.rdbuf(); + } +}; + +// !!! IMPORTANT !!! +// THIS CLASS IS FOR THE PURPOSE OF DIRECT WRITING TO THE STREAM ONLY. +// DO NOT use this class for any other purpose and use it also with +// EXTREME CARE. +// The only role of this class is to pass the string with KNOWN SIZE +// written in either a string literal or an array of characters to +// the output stream using its `write` method, that is, with bypassing +// any formatting facilities. +struct fmt_stringview +{ +private: + const char* d; + size_t s; + +public: + explicit fmt_stringview(const char* dd, size_t ss): d(dd), s(ss) {} + + const char* data() const { return d; } + size_t size() const { return s; } + + const char* begin() const { return d; } + const char* end() const { return d + s; } +}; + +template +struct check_minus_1 +{ + static const size_t value = N - 1; +}; + +template<> +struct check_minus_1<0> +{ +}; + +// NOTE: DO NOT USE THIS FUNCTION DIRECTLY. +template +inline fmt_stringview CreateRawString_FWD(const char (&ref)[N]) +{ + const char* ptr = ref; + return fmt_stringview(ptr, check_minus_1::value); +} + +} // END: namespace internal + +inline internal::fmt_stringview fmt_rawstr(const char* dd, size_t ss) +{ + return internal::fmt_stringview(dd, ss); +} + +inline internal::fmt_stringview fmt_rawstr(const std::string& s) +{ + return internal::fmt_stringview(s.data(), s.size()); +} + +template inline +internal::fmt_proxy_template fmt(const Value& val) +{ + using namespace internal; + return fmt_proxy_template(val, snd_simple()); +} + +template inline +internal::fmt_proxy_template > fmt(const Value& val, const basic_fmtc& config) +{ + using namespace internal; + return fmt_proxy_template >(val, snd_fmtc(config)); +} + +template inline +internal::fmt_proxy_template > fmtx(const Value& val, const basic_fmtc& config) +{ + using namespace internal; + return fmt_proxy_template >(val, snd_stateous(config)); +} + +template inline +internal::fmt_proxy_template > fmt(const Value& val, const Manip& man) +{ + using namespace internal; + return fmt_proxy_template >(val, snd_ios(man)); +} + +#if OFMT_HAVE_CXX11 + +template inline +internal::fmt_proxy_template> > + fmt(const Value& val, const Manip1& man1, const Manip&... mans) +{ + typedef std::tuple Tuple; + using namespace internal; + + return fmt_proxy_template >(val, snd_ios(Tuple(man1, mans...))); +} + +#else + +template inline +internal::fmt_proxy_template > > fmt(const Value& val, const Manip1& man, const Manip2& man2) +{ + typedef std::pair Tuple; + using namespace internal; + return fmt_proxy_template >(val, snd_ios(Tuple(man, man2))); +} + +#endif + +inline const char* fmt_if(bool value, const char* strue, const char* sfalse) +{ + return value ? strue : sfalse; +} + +// XXX Make basic_ofmtbufstream etc. +class ofmtbufstream +{ + friend class ofmtrefstream; +protected: + std::stringstream buffer; + +public: + ofmtbufstream() {} + + std::ostream& base() { return buffer; } + + // Extra constructor that allows the stream to have some + // initial contents. Only string types supported + ofmtbufstream(const internal::fmt_stringview& s) + { + buffer.write(s.data(), s.size()); + } + + ofmtbufstream(const std::string& s) + { + buffer.write(s.data(), s.size()); + } + + // This is to allow pre-configuration before sending. + // Only use fmtx (not fmt) if you want to keep the state. + void setup(const basic_fmtc& fc) + { + fc.apply(buffer); + } + + void clear() + { + buffer.clear(); + } + + // Expose + ofmtbufstream& write(const char* buf, size_t size) + { + buffer.write(buf, size); + return *this; + } + + ofmtbufstream& operator<<(const char* t) + { + size_t len = std::strlen(t); + buffer.write(t, len); + return *this; + } + + // Treat a fixed-size array just like a pointer + // to the first only and still use strlen(). This + // is because it usually designates a buffer that + // has N as the spare space, so you still need to + // mind the NUL terminator character. For string + // literals you should use OFMT_RAWSTR macro that + // gets the set of pointer and size from the string + // as an array, but also makes sure that the argument + // is a string literal. + // Unfortunately C++ is unable to distinguish the + // fixed array (with spare buffer space) from a string + // literal (which has only one extra termination character). + // The compiler still can usually call strlen at + // compile time, but not if you are in a debug mode. + template + ofmtbufstream& operator<<(const char (&t)[N]) + { + size_t len = std::strlen(t); + buffer.write(t, len); + return *this; + } + + ofmtbufstream& operator<<(const std::string& s) + { + buffer.write(s.data(), s.size()); + return *this; + } + + // XXX Add also a version for std::string_view, if C++17. + ofmtbufstream& operator<<(const internal::fmt_stringview& s) + { + buffer.write(s.data(), s.size()); + return *this; + } + + template + ofmtbufstream& operator<<(const internal::fmt_proxy_template& prox) + { + prox.sendto(buffer); + return *this; + } + + template inline + ofmtbufstream& operator<<(const Value& val) + { + return *this << fmt(val); + } + + // A utility function to send the argument directly + // to the buffer + template inline + ofmtbufstream& forward(const Value& val) + { + buffer << val; + return *this; + } + + ofmtbufstream& operator<<(const ofmtbufstream& source) + { + buffer << source.buffer.rdbuf(); + return *this; + } + + std::string str() const + { + return buffer.str(); + } + +// Additionally for C++11 +#if OFMT_HAVE_CXX11 + void print_chain() + { + } + + template + void print_chain(const Arg1& arg1, const Args&... args) + { + *this << arg1; + print_chain(args...); + } + + template + ofmtbufstream& print(const Args&... args) + { + print_chain(args...); + return *this; + } + + template + ofmtbufstream& puts(const Args&... args) + { + print_chain(args...); + buffer << std::endl; + return *this; + } +#endif +}; + +class ofmtrefstream +{ +protected: + std::ostream& refstream; + +public: + ofmtrefstream(std::ostream& src) : refstream(src) {} + + std::ostream& base() { return refstream; } + + // Expose + ofmtrefstream& write(const char* buf, size_t size) + { + refstream.write(buf, size); + return *this; + } + + ofmtrefstream& operator<<(const char* t) + { + size_t len = std::strlen(t); + this->write(t, len); + return *this; + } + + // Treat a fixed-size array just like a pointer + // to the first only and still use strlen(). This + // is because it usually designates a buffer that + // has N as the spare space, so you still need to + // mind the NUL terminator character. For string + // literals you should use OFMT_RAWSTR macro that + // gets the set of pointer and size from the string + // as an array, but also makes sure that the argument + // is a string literal. + // Unfortunately C++ is unable to distinguish the + // fixed array (with spare buffer space) from a string + // literal (which has only one extra termination character). + // The compiler still can usually call strlen at + // compile time, but not if you are in a debug mode. + template + ofmtrefstream& operator<<(const char (&t)[N]) + { + size_t len = std::strlen(t); + this->write(t, len); + return *this; + } + + ofmtrefstream& operator<<(const std::string& s) + { + this->write(s.data(), s.size()); + return *this; + } + + // XXX Add also a version for std::string_view, if C++17. + ofmtrefstream& operator<<(const internal::fmt_stringview& s) + { + this->write(s.data(), s.size()); + return *this; + } + + template + ofmtrefstream& operator<<(const internal::fmt_proxy_template& prox) + { + prox.sendto(refstream); + return *this; + } + + template inline + ofmtrefstream& operator<<(const Value& val) + { + return *this << fmt(val); + } + + // A utility function to send the argument directly + // to the buffer + template inline + ofmtrefstream& forward(const Value& val) + { + refstream << val; + return *this; + } + + ofmtrefstream& operator<<(const ofmtbufstream& source) + { + refstream << source.buffer.rdbuf(); + return *this; + } + +// Additionally for C++11 +#if (defined(__cplusplus) && __cplusplus > 199711L) \ + || (defined(_MSVC_LANG) && _MSVC_LANG > 199711L) // Some earlier versions get this wrong + void print_chain() + { + } + + template + void print_chain(const Arg1& arg1, const Args&... args) + { + *this << arg1; + print_chain(args...); + } + + template + ofmtrefstream& print(const Args&... args) + { + print_chain(args...); + return *this; + } + + template + ofmtrefstream& puts(const Args&... args) + { + print_chain(args...); + refstream << std::endl; + return *this; + } +#endif +}; + +// Additionally for C++11 +#if (defined(__cplusplus) && __cplusplus > 199711L) \ + || (defined(_MSVC_LANG) && _MSVC_LANG > 199711L) // Some earlier versions get this wrong + +inline internal::fmt_stringview operator""_V(const char* ptr, size_t s) +{ + return internal::fmt_stringview(ptr, s); +} + +template inline +std::string fmtcat(const Args&... args) +{ + ofmtbufstream out; + out.print(args...); + return out.str(); +} + +#else + +// Provide fmtcat for C++03 for up to 4 parameters + +// The 1-argument version is for logical consistency. +template inline +std::string fmtcat(const Arg1& arg1) +{ + return fmts(arg1); +} + +template inline +std::string fmtcat(const Arg1& arg1, const Arg2& arg2) +{ + ofmtbufstream out; + out << arg1 << arg2; + return out.str(); +} + +template inline +std::string fmtcat(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) +{ + ofmtbufstream out; + out << arg1 << arg2 << arg3; + return out.str(); +} + +template inline +std::string fmtcat(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, const Arg4& arg4) +{ + ofmtbufstream out; + out << arg1 << arg2 << arg3 << arg4; + return out.str(); +} + +#endif + +template inline +std::string fmts(const Value& val) +{ + ofmtbufstream out; + out << val; + return out.str(); +} + +template inline +std::string fmts(const Value& val, const fmtc& fmtspec) +{ + ofmtbufstream out; + out << fmt(val, fmtspec); + return out.str(); +} + + +} + +// This prevents the macro from being used with anything else +// than a string literal. Version of ""_V UDL available for C++03. +#define OFMT_RAWSTR(arg) ::hvu::internal::CreateRawString_FWD("" arg) + + + +#endif diff --git a/logging/ofmt.md b/logging/ofmt.md new file mode 100644 index 000000000..0d7123105 --- /dev/null +++ b/logging/ofmt.md @@ -0,0 +1,196 @@ +Introduction +============ + +The iostream library has many advantages over the old C's printf function, +but the design has flaws: the formatting specification exists as a state of +the stream, which can be modified on the fly through manipulators. +For example, once you send `hex` manipulator into the stream, all integers will +be printed in hex since this time, until you change it back to `dec`. +This includes the instruction of sending to a stream happening anyhow next, +including after some instructions or in another function. The rules for +iostream manipulators only state that the `width` parameter is reset to 0, +and only after printing value of a type, for which the `operator<<` is +overloaded in the C++ standard library. All other parameters just get +changed and there's even no method to reset settings to system defaults +(you can do it by doing save-and-restore configuration, but it's on your +head as well to remember the system configuration in the beginning, which +is actually impossible in case of a library). + +To fix this problem, you can use the so-called On-Demand Tagged API. That is, +instead of specifying the configuration by changing the stream's state, you can +choose to just apply a format specification for a single value using the +supplied `fmt` function: + +``` +cout << fmt(x, fmtc().hex()) << " " << y; // y printed as dec +``` + +(Note here that to compile this fragment, you need to include `ofmt_iostream.h`). + +The formatting specification is using the `fmtc` structure to achieve it, +which prevents from polluting the global namespace with too many names +as well as allows to save the configuration in a variable so that it can +be specified for multiple values: + +``` +fmtc hex04 = fmtc().hex().fillzero().width(4); +cout << fmt(a, hex04) << ":" << fmt(b, hex04); +``` + +Although for a single manipulator there is prepared also a simplified version +using the iomanip manipulators: + +``` +cout << fmt(x, hex) << " " << y; // y printed as dec +``` + +(Potentially this can be extended to handle multiple iomanip formatters, +but it's not implemented yet). + +This solution is compatible with C++98/C++03 version. Some alternative, more +comfortable API is provided for C++11. + + +Formatting flags +================ + +The following configuration items are available in `fmtc` type: + +* `width(int field_length)`: set minimum field length to `field_length` +* `precision(int prec)`: floating-point precision +* `fill(char c)`: character to fill unused space of minimum width +* `fillzero()`: same as `.fill('0').internal()` +* `left()`, `right()`, `internal()`: alignment control inside wide field +* `dec()`, `hex()`, `oct()`: set numeric base system (lowercase) +* `uhex()`, `uoct()`: uppercase version of the above (for oct only prefix) +* `fhex()`, `ufhex()`: floating-point hex format (C++11 only) +* `general()`, `ugeneral()`: floating-point value-dependent fixed or scientific selection +* `fixed()`: floating-point fixed format +* `exp()`, `scientific()`: floating-point scientific format (exponental) +* `uexp()`, `uscientific()`: floating-point scientific format (exponental) with uppercase E +* `showbase()`: use `0` prefix for oct and `0x` for hex (`0X` if `uhex`) +* `showpos()`: prefix positive numbers with `+` +* `showpoint()`: add decimal point always, even if fraction part is 0 + +(Note: fillzero() sets the `0` character as filling, with regard to the +`char` or `wchar_t` types, and also sets `internal` adjustment field.) + +Note that all of them are implemented as methods that return "itself", so +you can bind settings in chain: + +``` + fmtc().hex().width(8).fillzero().showbase() +``` + +You can also create local variable for this type so that you can define +the format specification and use in multiple `fmt` calls: + +``` +fmtc phex8 = fmtc().hex().width(8).fillzero().showbase(); +``` + +The `width`, `precision` and `fill` methods have a parameter and this way +they can use as well a runtime value. + +Formatting stream utility +========================= + +The idea for stream manipulation with on-demand tagged API is that there are +overloads for several types known as "string form", in which case this +string is directly written into the stream, with bypassing any formatting. +Everything else is passed through the `fmt` function with no config spec, +that is, it will use default formatting. The `fmt` function returns a proxy +object, which will employ std::stringstream for formatting, and then in order +to send to the output stream it will use buffer-to-buffer copy. + +Hence the `ofmtbufstream` class is provided, which is a wrapper around +`std::stringstream` and should be used instead of it in order to utilize the +on-demand tagged API. Beside the "traditional" overloads for `operator<<`, it +provides also the "print" function, which uses multiple arguments ("puts" +additionally adds the end-of-line, just like the standard C "puts" function +does). This function is only available in C++11 version. + + +Iostream support +================ + +This support is not provided by default, but you can use this also with +iostream classes' instances. You need to include "`ofmt_iostream.h`" for this. +It provides extra overloads for the internal types `internal::fmt_proxy` and +`internal::fmt_simple_proxy` so that the result of the `fmt` function can be +handled. Note that the rules as above described for `ofmtstream` do not apply +here, unless you use `fmt_rawstr()` function for every string. + +This provides also a possibility to use `std::put_time` through a special +overload of the `fmt` function. Usage: + +``` +ofmtbufstream out; +... +typedef std::chrono::system_clock sclock; +std::time_t timenow = sclock::to_time_t(sclock::now()); + +out << "Timestamp: " << fmt(*std::localtime(&timenow), "%F %T"); +``` + +(Note that in C++20 there are some easier way to get from `sclock.now()` to +`std::put_time` and you can easily use it through `fmt` instead of +`std::put_time`). + +If you want to create a similar formatting specifier for your type, this is +needed to be done: + +1. Create your proxy type that will carry over the value and format +specification. You can also provide an overload for `fmt` function with that +type, although all you need to have is a function that will return the proxy +object by value. + +2. Create an partial specialization of `hvu::internal::fmt_simple_proxy` with +this proxy type. Inside you need to provide a `sendto` method template that +will print this value in the passed stream according to the specification. + +Follow the example in `ofmt_iostream.h` that defines `tm_proxy` for `put_time`. + + +Stateous API +============ + +Although the stateous API is a biggest design flaw of iostream, there are +cases sometimes, when you need it - especially if you want to use a tempoarary +buffer of `ofmtbufstream` for a series of data to be printed. In that case +you should use the following: + +* `ofmtbufstream::setup()` to set the formatting specification that should +be permanent +* In case of a need for additional settings at the printed value, use `fmtx` +instead of `fmt` + +The difference between `fmt` and `fmtx` is that the latter applies the +settings to the very stream, on which it operates (rather than on a temporarily +created sub-buffer as `fmt` does), which makes every setting permanent, +and all earlier settings are preserved. This way such settings as `width` +can be applied using `fmtx`, as this setting is always being reset to 0 after +printing a value of a standard type. + + +Additional formatting functions +=============================== + +The `fmt` function returns a proxy that should make the value written into the +stream with appropriate format. Besides there are also other formatting tools: + +* `fmtx`: The "stateous" version of fmt + +* `fmtcat`: a multi-argument function where formatted versions of the arguments +are glued together and returned as `std::string` + +* `fmts`: formats the single value (like with `fmt`) and return it as `std::string`; +the call to `fmts(value, man1)` is equivalent to calling +`fmtcat(fmt(value, man1))` with just one argument + +* `fmt_rawstr`: Turns a string of `std::string` or pointer-length specification +into the `internal::fmt_stringview` type, which can be directly handled by the +`operator<<` overload or `print` method of `ofmtbufstream`. This is also provided +for iostream version. + + diff --git a/logging/ofmt_iostream.h b/logging/ofmt_iostream.h new file mode 100644 index 000000000..6e9eacde7 --- /dev/null +++ b/logging/ofmt_iostream.h @@ -0,0 +1,79 @@ +/* + * 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. + *****************************************************************************/ + +// Formatting library for C++ - C++03 compat version of on-demand tagged format API. +// +// This adds the abilities for formatting to be used with iostream. + +#ifndef INC_SRT_OFMT_IOSTREAM_H +#define INC_SRT_OFMT_IOSTREAM_H + +#include +#include +#include +#include "ofmt.h" + +template< + class Value, + class CharT, + class Sender, + class Traits = std::char_traits +> +inline std::basic_ostream& operator<<( + std::basic_ostream& os, + const hvu::internal::fmt_proxy_template& valproxy +) +{ + valproxy.sendto(os); + return os; +} + + +// Note: if you use iostream and sending to the stream, then +// sending std::string will still use the built-in formatting +// facilities, but you can pass the string through fmt() and +// this way you make a stringview-forwarder and formatting gets +// bypassed. +inline std::ostream& operator<<(std::ostream& os, const hvu::internal::fmt_stringview& v) +{ + os.write(v.data(), v.size()); + return os; +} + +namespace hvu +{ +namespace internal +{ +struct snd_time_tm +{ + const char* format; + + template + void format_send(const Value& val, OutStream& os) const + { + os << std::put_time(&val, format); + } +}; +} + +inline internal::fmt_proxy_template fmt(const struct tm& tim, const char* format) +{ + internal::snd_time_tm p = {format}; + return internal::fmt_proxy_template(tim, p); +} + +} + +#endif diff --git a/common/win/syslog_defs.h b/logging/windows_syslog.h similarity index 100% rename from common/win/syslog_defs.h rename to logging/windows_syslog.h diff --git a/scripts/abi-check.tcl b/scripts/abi-check.tcl old mode 100644 new mode 100755 diff --git a/scripts/build-android/mkmbedtls b/scripts/build-android/mkmbedtls index 176154184..438d9e05a 100755 --- a/scripts/build-android/mkmbedtls +++ b/scripts/build-android/mkmbedtls @@ -23,5 +23,5 @@ cmake -DENABLE_TESTING=Off -DUSE_SHARED_MBEDTLS_LIBRARY=On \ -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 --build . --parallel cmake --install . diff --git a/scripts/build-ios/mksrt-xcf.sh b/scripts/build-ios/mksrt-xcf.sh index 59022cdc1..1cba8cfb7 100755 --- a/scripts/build-ios/mksrt-xcf.sh +++ b/scripts/build-ios/mksrt-xcf.sh @@ -38,7 +38,7 @@ do --cmake-prefix-path=$ssl_path --use-openssl-pc=OFF \ --cmake_install_prefix=$out/$dest - cmake --build . --target install --config Release + cmake --build . --parallel --target install --config Release echo '********************************************************************************' done diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index a65247ccd..cabb7644b 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -62,11 +62,14 @@ if ( $Env:APPVEYOR ) { # would have to be adjusted to this version anyway. if ($VS_VERSION -lt 2019) { + echo "Version < 2019 - replacing C:\OpenSSL-Win* with C:\Openssl-v111-Win*" #appveyor has many openssl installations - place the latest one in the default location unless VS2013 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 + } else { + echo "Version 2019+ - expecting OpenSSL v1.1.1 in C:\OpenSSL-Win*" } } diff --git a/cmake_object_lib_support.c b/scripts/cmake_object_lib_support.c similarity index 100% rename from cmake_object_lib_support.c rename to scripts/cmake_object_lib_support.c diff --git a/scripts/codespell.sh b/scripts/codespell.sh new file mode 100755 index 000000000..ed558ad57 --- /dev/null +++ b/scripts/codespell.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +if [[ -z `type -p codespell` ]]; then + echo >&2 "You need 'codespell' app to run the spell check." + echo >&2 "Follow instructions in CONTRIBUTING.md document" + exit 1 +fi + +function showhelp() +{ + echo "Usage: `basename $0` " + echo "" + echo "WORK MODES:" + echo " * show [default] - only show spelling errors" + echo " * fix - write spelling error fixes directly to files" + echo " * check - like fix, but ask user to confirm ambiguous fix" + echo " * review - like fix, but ask user for every modification" + echo "TARGETS:" + echo " * all [default] - check all files in the repository (including index)" + echo " * changed - check only files currently modified in the repository" +} + + +# Check if the script is run inside the repository view +GIT_TOP=`git rev-parse --show-toplevel` || (echo >&2 "Please run this script in the SRT repository directory"; exit) + +# Forcefully enter the top repo dir; this is the only directory where it should be run +cd $GIT_TOP + +WORKMODE=${1:-show} +EXTRACTION=${2:-all} + +CS_OPTIONS= + +case $WORKMODE in + help | --help) + showhelp + exit + ;; + show) + ;; + + fix) + CS_OPTIONS="-w " + ;; + + check) + CS_OPTIONS="-w -i 2" + ;; + + review) + CS_OPTIONS="-w -i 1" + ;; + + *) + echo >&2 "Unknown mode. Use 'help' to list available options." + exit 1 + ;; +esac + +CS_FILES= + +if [[ $EXTRACTION == "all" ]]; then + CS_FILES="git ls-files" +elif [[ $EXTRACTION == "changed" ]]; then + CS_FILES="git ls-files -m" +else + echo >&2 "Unknown extractopn spec '$EXTRACTION'. Use 'all' or 'changed'" + exit 1 +fi + +# Unfortunately this isn't Tcl, so we need to do it "space safe" way. +declare -a FILELIST +eval FILELIST=( $($CS_FILES | awk "{print \"'\" \$1 \"'\"}") ) + +if [[ -z ${FILELIST[@]} ]]; then + echo "SPELLCHECK: no files listed, not checking." + exit 0 +fi + +#echo Running in files from $CS_FILE: $FILELIST + +codespell --config scripts/codespell/codespell.cfg $CS_OPTIONS ${FILELIST[@]} diff --git a/scripts/codespell/codespell.cfg b/scripts/codespell/codespell.cfg index a33874609..fb34ecd58 100644 --- a/scripts/codespell/codespell.cfg +++ b/scripts/codespell/codespell.cfg @@ -7,8 +7,12 @@ builtin = clear,rare,informal,code # Ignore words listed in this file. ignore-words = ./scripts/codespell/codespell_whitelist.txt +# Skip calls to test - would be nice to skip every macro calls, +# functions, variables etc. - might be new entries might be needed. +ignore-regex = ^\s*(TEST|TEST_F)\([^\)]*\) + # Add custom dictionary file. dictionary = ./scripts/codespell/codespell_dictionary.txt,- # Skip checking files or directories. -skip = ./build/*,./.git/* +skip = ./build/*,./_build/*,./.git/* diff --git a/scripts/codespell/codespell_whitelist.txt b/scripts/codespell/codespell_whitelist.txt index 6e845b75c..eac07bc0c 100644 --- a/scripts/codespell/codespell_whitelist.txt +++ b/scripts/codespell/codespell_whitelist.txt @@ -1,7 +1,9 @@ AnyOther ANS +arithmetics CAS connectIn +copyable deque dur endcode @@ -17,6 +19,7 @@ mut MUSL nd numer +noncopyable od RO stdio diff --git a/scripts/find-first-variable.tcl b/scripts/find-first-variable.tcl new file mode 100755 index 000000000..9da86184a --- /dev/null +++ b/scripts/find-first-variable.tcl @@ -0,0 +1,120 @@ +#!/usr/bin/tclsh + +# A utility script to check the CMakeLists.txt for every +# expression that "potentially could be" a variable and its +# very first occurrence. This is to track if the variable +# wasn't used without being first properly set. + +lassign $argv filename re + +if {$re == ""} { + puts stderr "Usage: [file tail $argv0] " + exit 1 +} + +set f [open $filename r] + +set firstoccur {} + +set lno 0 + +while { [gets $f line] != -1 } { + incr lno + + set line [string trim $line] + if { [string index $line 0] == "#" } { + # Skip comments + continue + } + + set parts "" + set partid "" + + # Cut off parts that are whole strings. Identify which + # parts are strings. Within strings, only ${...} are variables. + + set hasq [string first "\"" $line] + if {$hasq == -1} { + set parts "{$line}" + set partid 0 + } else { + + set prevq 0 + while 1 { + set part [string range $line 0 [expr $hasq-1]] + lappend parts $part + lappend partid 0 + + set prevq [expr $hasq+1] + + set searchfrom $prevq + set cont 1 + while 1 { + set hasq [string first "\"" $line $searchfrom] + if {$hasq != -1} { + # Check if this quote was quoted + if {[string index $line [expr $hasq-1]] == "\\"} { + set searchfrom [expr {$hasq+1}] + continue + } + break + } else { + # Unended quote - treat as ended. + lappend parts [string range $line $prevq end] + lappend partid 1 + set cont 0 + break + } + } + if (!$cont) { + break + } + + # If found, search for the next one. + + lappend parts [string range $line $prevq [expr $hasq-1]] + lappend partid 1 + set prevq [expr $hasq+1] + set hasq [string first "\"" $line $prevq] + + if {$hasq == -1} { + # No next quote - get until the end + lappend parts [string range $line $prevq end] + lappend partid 0 + break + } + } + } + + #puts stderr "LINE: $line" + #puts stderr "PARTS:" + foreach p $parts i $partid { + #set idname [expr {$i ? {string} : {plain}}] + #puts stderr " \[$p\] : $idname" + + if {$i} { + set vars [regexp -inline -all {\$\{\m([A-Z0-9_]*)\M\}} $p] + } else { + set vars [regexp -inline -all {\m([A-Z0-9_]*)\M} $p] + } + #puts stderr " -> $vars" + + foreach {unu v} $vars { + set v [string trim $v] + + # Treat words with less than 3 letters as not a variable. + if {[string length $v] < 3} { + continue + } + + if {[dict exists $firstoccur $v]} { + # Already exists. Skip + continue + } + + dict set firstoccur $v $lno + + puts "$filename:$lno: $v" + } + } +} diff --git a/scripts/generate-logging-defs.tcl b/scripts/generate-logging-defs.tcl deleted file mode 100755 index 66622675d..000000000 --- a/scripts/generate-logging-defs.tcl +++ /dev/null @@ -1,454 +0,0 @@ -#!/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 management" - 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-through. - - # 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/scripts/haiUtil.cmake b/scripts/haiUtil.cmake index 312480e1e..bb13c4201 100644 --- a/scripts/haiUtil.cmake +++ b/scripts/haiUtil.cmake @@ -29,15 +29,29 @@ MACRO(set_version_variables prefix value) set(${prefix}_DEFINESTR "") ENDMACRO(set_version_variables) +MACRO(set_default varname) + if (NOT DEFINED ${varname}) + set(${varname} ${ARGN}) + endif() +ENDMACRO(set_default) + # Sets given variable to 1, if the condition that follows it is satisfied. # Otherwise set it to 0. -MACRO(set_if varname) +MACRO(set1_if varname) IF(${ARGN}) SET(${varname} 1) ELSE(${ARGN}) SET(${varname} 0) ENDIF(${ARGN}) -ENDMACRO(set_if) +ENDMACRO(set1_if) + +MACRO(seton_if varname) + IF(${ARGN}) + SET(${varname} ON) + ELSE(${ARGN}) + SET(${varname} OFF) + ENDIF(${ARGN}) +ENDMACRO(seton_if) FUNCTION(join_arguments outvar) set (output) @@ -49,6 +63,14 @@ FUNCTION(join_arguments outvar) set (${outvar} ${output} PARENT_SCOPE) ENDFUNCTION() +macro(mvar VARNAME) + if (DEFINED ${VARNAME}) + message(STATUS " ${VARNAME}=${${VARNAME}}") + else() + message(STATUS " ${VARNAME} not defined") + endif() +endmacro() + # The directory specifies the location of maffile and # all files specified in the list. MACRO(MafReadDir directory maffile) @@ -219,6 +241,22 @@ function (getVarsWith _prefix _varResult) set (${_varResult} ${_matchedVars} PARENT_SCOPE) endfunction() +function (addDefinitionsFromPrefixed _prefix) + getVarsWith(${_prefix} enforcers) + foreach(ef ${enforcers}) + set (val ${${ef}}) + if (NOT val STREQUAL "") + set(val =${val}) + endif() + string(LENGTH ${_prefix} pflen) + string(LENGTH ${ef} eflen) + math(EXPR alen ${eflen}-${pflen}) + string(SUBSTRING ${ef} ${pflen} ${alen} ef) + message(STATUS "FORCED PP VARIABLE: ${ef}${val}") + add_definitions(-D${ef}${val}) + endforeach() +endfunction() + function (check_testcode_compiles testcode libraries _successful) set (save_required_libraries ${CMAKE_REQUIRED_LIBRARIES}) set (CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${libraries}") @@ -241,6 +279,10 @@ function (test_requires_clock_gettime _enable _linklib) # - CLOCK_MONOTONIC is not available: # _enable = OFF; _linklib = "-". + # Default "not found" state. + set (${_enable} OFF PARENT_SCOPE) + set (${_linklib} "-" PARENT_SCOPE) + set (code " #include int main() { @@ -271,7 +313,7 @@ function (test_requires_clock_gettime _enable _linklib) message(STATUS "CLOCK_MONOTONIC: not available on this system") endfunction() -function (parse_compiler_type wct _type _suffix) +function (srt_parse_compiler_type wct _type _suffix) if (wct STREQUAL "") set(${_type} "" PARENT_SCOPE) set(${_suffix} "" PARENT_SCOPE) @@ -301,3 +343,50 @@ macro (srt_import_parent_options) set (${ef} "${val}") endforeach() endmacro() + +function(srt_map_compiler COMPILER_TYPE OUT_C_COMMAND OUT_CXX_COMMAND) + if (COMPILER_TYPE STREQUAL "gcc") + set (${OUT_C_COMMAND} gcc PARENT_SCOPE) + set (${OUT_CXX_COMMAND} g++ PARENT_SCOPE) + elseif(COMPILER_TYPE STREQUAL "cc") + set (${OUT_C_COMMAND} cc PARENT_SCOPE) + set (${OUT_CXX_COMMAND} c++ PARENT_SCOPE) + elseif(COMPILER_TYPE STREQUAL "icc") + set (${OUT_C_COMMAND} icc PARENT_SCOPE) + set (${OUT_CXX_COMMAND} icpc PARENT_SCOPE) + else() + set (${OUT_C_COMMAND} "${COMPILER_TYPE}" PARENT_SCOPE) + set (${OUT_CXX_COMMAND} "${COMPILER_TYPE}++" PARENT_SCOPE) + endif() +endfunction() + +function(srt_configure_compiler I_TYPE I_PREFIX OUT_C_COMMAND OUT_CXX_COMMAND OUT_COMPILER_TYPE) + srt_parse_compiler_type(${I_TYPE} COMPILER_TYPE COMPILER_SUFFIX) + srt_map_compiler(${COMPILER_TYPE} C_COMMAND CXX_COMMAND GNU_COMPAT) + set (${OUT_C_COMMAND} ${I_PREFIX}${C_COMMAND}${COMPILER_SUFFIX} PARENT_SCOPE) + set (${OUT_CXX_COMMAND} ${I_PREFIX}${CXX_COMMAND}${COMPILER_SUFFIX} PARENT_SCOPE) + set (${OUT_COMPILER_TYPE} ${COMPILER_TYPE} PARENT_SCOPE) +endfunction() + +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() + diff --git a/scripts/logging-config.tcl b/scripts/logging-config.tcl new file mode 100644 index 000000000..d9806c646 --- /dev/null +++ b/scripts/logging-config.tcl @@ -0,0 +1,91 @@ +#* +#* 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. +#***************************************************************************** + +# Logger definitions. +# Comments here allowed, just only for the whole line. + +# Structure: { name-id display-id help-comment } where: +# * name-id: Identifier that can be used to obtain the FA identifier (internal) +# * display-id: This FA will be displayed in the log header with the prefix (see below) +# * help-comment: Unused currently, visible only in this table for now. +set loggers_table { + sockmgmt sm "Socket create/open/close/configure activities" + conn cn "Connection establishment and handshake" + xtimer xt "The checkTimer and around activities" + tsbpd ts "The TsBPD thread" + rsrc rs "System resource allocation and management" + congest cc "Congestion control module" + pfilter pf "Packet filter module" + api_ctrl ac "API part for socket and library management" + que_ctrl qc "Queue control activities" + epoll_upd ei "EPoll, internal update activities" + + api_recv ar "API part for receiving" + buf_recv br "Buffer, receiving side" + que_recv qr "Queue, receiving side" + chn_recv kr "CChannel, receiving side" + grp_recv gr "Group, receiving side" + + api_send as "API part for sending" + buf_send bs "Buffer, sending side" + que_send qs "Queue, sending side" + chn_send ks "CChannel, sending side" + grp_send gs "Group, sending side" + + internal in "Internal activities not connected directly to a socket" + que_mgmt qm "Queue, management part" + chn_mgmt km "CChannel, management part" + grp_mgmt gm "Group, management part" + epoll_api ea "EPoll, API part" +} + +# This is the prefix when displaying the FA in the log header. +# For example, if your display-id is "cc", this will be "${loggers_prefix}cc" +set loggers_prefix "SRT." + +# The namespace where the global logger variables and the logger config will +# be defined. Use dot separation rather than ::. +set loggers_namespace srt.logging + +# Name of the config object where the loggers will be subscribed. +set loggers_configname logger_config + +# Whether all loggers should be enabled or disabled by default +set loggers_enabled true + +# This will be used to construct the variable name. The +# display-id field will be used, followed by this one. +set loggers_varsuffix log + +# OPTIONAL, you may want to create also a link to the general +# logger, for convenience. +set loggers_generallink gglog + +# Name of the generated header and source file (.cpp and .h suffixes +# will be added to this name). May enclose the directory name. +set loggers_modulename srtcore/logger_fas + +# This contents will be pasted into the generated header and source +# file in the beginning. +set loggers_globalheader { + /* + WARNING: Generated from ../logging/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + +} diff --git a/srtcore/udt.h b/srtcore/ATTIC/udt.h similarity index 80% rename from srtcore/udt.h rename to srtcore/ATTIC/udt.h index ee4c02f4d..47693452a 100644 --- a/srtcore/udt.h +++ b/srtcore/ATTIC/udt.h @@ -64,6 +64,10 @@ modified by * file doesn't contain _FUNCTIONS_ predicted to be used in C - see udtc.h */ +// XXX NOTE XXX +// This file remains for reference, but it has been removed from +// public headers and it is not in use anymore. + #ifndef INC_SRT_UDT_H #define INC_SRT_UDT_H @@ -166,22 +170,22 @@ typedef std::set UDSET; SRT_API extern const SRTSOCKET INVALID_SOCK; #undef ERROR -SRT_API extern const int ERROR; +SRT_API extern const SRTSTATUS ERROR; -SRT_API int startup(); -SRT_API int cleanup(); +SRT_API SRTRUNSTATUS startup(); +SRT_API SRTSTATUS 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 SRTSTATUS bind(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSTATUS bind2(SRTSOCKET u, UDPSOCKET udpsock); +SRT_API SRTSTATUS 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 SRTSOCKET connect(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSTATUS close(SRTSOCKET u); +SRT_API SRTSTATUS getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); +SRT_API SRTSTATUS 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); @@ -200,56 +204,29 @@ SRT_API int selectEx(const std::vector& fds, std::vector* 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 SRTSTATUS epoll_add_usock(int eid, SRTSOCKET u, const int* events = NULL); +SRT_API SRTSTATUS epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); +SRT_API SRTSTATUS epoll_remove_usock(int eid, SRTSOCKET u); +SRT_API SRTSTATUS epoll_remove_ssock(int eid, SYSSOCKET s); +SRT_API SRTSTATUS epoll_update_usock(int eid, SRTSOCKET u, const int* events = NULL); +SRT_API SRTSTATUS 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); 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); 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 SRTSTATUS 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 SRTSTATUS bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear = true); SRT_API SRT_SOCKSTATUS getsockstate(SRTSOCKET u); } // namespace UDT -// This is a log configuration used inside SRT. -// Applications using SRT, if they want to use the logging mechanism -// are free to create their own logger configuration objects for their -// 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; } -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 +// XXX Here was the part with srt namespace and srt_logger_config file. +// The latter was moved to common.h. The C++ SRT API parts have been moved +// to srt.h file. // Planned deprecated removal: rel1.6.0 // There's also no portable way possible to enforce a deprecation diff --git a/srtcore/access_control.h b/srtcore/access_control.h index 611e1dad8..18357aeee 100644 --- a/srtcore/access_control.h +++ b/srtcore/access_control.h @@ -66,7 +66,7 @@ written by #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. +#define SRT_REJX_VERSION 1505 // Version not supported - either SRT or the application itself. 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 diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 80d41582b..85d8aac19 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -65,9 +65,8 @@ modified by #include "core.h" #include "epoll.h" #include "logging.h" -#include "threadname.h" +#include "hvu_compat.h" #include "srt.h" -#include "udt.h" #ifdef _WIN32 #include @@ -77,13 +76,21 @@ modified by #pragma warning(error : 4530) #endif +// This is to block the logging instruction, available to be restored on +// development +#define DONT_HLOGC(...) (void)0 + + using namespace std; -using namespace srt_logging; +using namespace srt::logging; using namespace srt::sync; -void srt::CUDTSocket::construct() +namespace srt +{ + +void CUDTSocket::construct() { -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING m_GroupOf = NULL; m_GroupMemberData = NULL; #endif @@ -92,20 +99,35 @@ void srt::CUDTSocket::construct() setupMutex(m_ControlLock, "Control"); } -srt::CUDTSocket::~CUDTSocket() +CUDTSocket::~CUDTSocket() { releaseMutex(m_AcceptLock); releaseCond(m_AcceptCond); releaseMutex(m_ControlLock); } -void srt::CUDTSocket::resetAtFork() +int CUDTSocket::apiAcquire() +{ + int busy = ++m_iBusy; + HLOGC(smlog.Debug, log << "@" << id() << " ACQUIRE; BUSY=" << busy << " {"); + return busy; +} + +int CUDTSocket::apiRelease() +{ + int busy = --m_iBusy; + HLOGC(smlog.Debug, log << "@" << id() << " RELEASE; BUSY=" << busy << " }"); + return busy; +} + +void CUDTSocket::resetAtFork() { m_UDT.resetAtFork(); resetCond(m_AcceptCond); } -SRT_SOCKSTATUS srt::CUDTSocket::getStatus() +SRT_TSA_DISABLED // Uses m_Status that should be guarded, but for reading it is enough to be atomic +SRT_SOCKSTATUS 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. @@ -123,18 +145,23 @@ SRT_SOCKSTATUS srt::CUDTSocket::getStatus() } // [[using locked(m_GlobControlLock)]] -void srt::CUDTSocket::breakSocket_LOCKED() +void CUDTSocket::breakSocket_LOCKED(int reason) { // This function is intended to be called from GC, // under a lock of m_GlobControlLock. m_UDT.m_bBroken = true; + + // SET THIS to true because this function is called always for a socket + // that will never have any chance in the future to be manually closed. + m_UDT.m_bManaged = true; m_UDT.m_iBrokenCounter = 0; - HLOGC(smlog.Debug, log << "@" << m_SocketID << " CLOSING AS SOCKET"); - m_UDT.closeInternal(); + HLOGC(smlog.Debug, log << "@" << m_UDT.m_SocketID << " CLOSING AS SOCKET"); + m_UDT.closeEntity(reason); setClosed(); } -void srt::CUDTSocket::setClosed() +SRT_TSA_DISABLED // Uses m_Status that should be guarded, but for reading it is enough to be atomic +void CUDTSocket::setClosed() { m_Status = SRTS_CLOSED; @@ -145,15 +172,26 @@ void srt::CUDTSocket::setClosed() m_tsClosureTimeStamp = steady_clock::now(); } -void srt::CUDTSocket::setBrokenClosed() +void CUDTSocket::setBrokenClosed() { m_UDT.m_iBrokenCounter = 60; m_UDT.m_bBroken = true; setClosed(); } -bool srt::CUDTSocket::readReady() +bool CUDTSocket::readReady() const { +#if SRT_ENABLE_BONDING + + // If this is a group member socket, then reading happens exclusively from + // the group and the socket is only used as a connection point, packet + // dispatching and single link management. Data buffering and hence ability + // to deliver a packet through API is exclusively the matter of group, + // therefore a single socket is never "read ready". + + if (m_GroupOf) + return false; +#endif if (m_UDT.m_bConnected && m_UDT.isRcvBufferReady()) return true; @@ -163,30 +201,30 @@ bool srt::CUDTSocket::readReady() return broken(); } -bool srt::CUDTSocket::writeReady() const +bool CUDTSocket::writeReady() const { return (m_UDT.m_bConnected && (m_UDT.m_pSndBuffer->getCurrBufSize() < m_UDT.m_config.iSndBufSize)) || broken(); } -bool srt::CUDTSocket::broken() const +bool CUDTSocket::broken() const { return m_UDT.m_bBroken || !m_UDT.m_bConnected; } + //////////////////////////////////////////////////////////////////////////////// -srt::CUDTUnited::CUDTUnited() +CUDTUnited::CUDTUnited() : m_Sockets() , m_GlobControlLock() , m_IDLock() , m_mMultiplexer() , m_pCache(new CCache) - , m_bClosing(false) + , m_bGCClosing(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); @@ -214,14 +252,14 @@ srt::CUDTUnited::CUDTUnited() HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); } -srt::CUDTUnited::~CUDTUnited() +CUDTUnited::~CUDTUnited() { // Call it if it wasn't called already. // This will happen at the end of main() of the application, // when the user didn't call srt_cleanup(). - enterCS(m_InitLock); + m_InitLock.lock(); stopGarbageCollector(); - leaveCS(m_InitLock); + m_InitLock.unlock(); closeAllSockets(); releaseMutex(m_GlobControlLock); releaseMutex(m_IDLock); @@ -243,29 +281,27 @@ srt::CUDTUnited::~CUDTUnited() #endif } -string srt::CUDTUnited::CONID(SRTSOCKET sock) +string CUDTUnited::CONID(SRTSOCKET sock) { - if (sock == 0) + if (int32_t(sock) <= 0) // embraces SRT_INVALID_SOCK, SRT_SOCKID_CONNREQ and illegal negative domain return ""; - std::ostringstream os; - os << "@" << sock << ":"; - return os.str(); + return hvu::fmtcat("@", int(sock), ":"); } -bool srt::CUDTUnited::startGarbageCollector() +bool CUDTUnited::startGarbageCollector() { ScopedLock guard(m_GCStartLock); if (!m_bGCStatus) { - m_bClosing = false; + m_bGCClosing = false; m_bGCStatus = StartThread(m_GCThread, garbageCollect, this, "SRT:GC"); } return m_bGCStatus; } -void srt::CUDTUnited::stopGarbageCollector() +void CUDTUnited::stopGarbageCollector() { ScopedLock guard(m_GCStartLock); @@ -274,20 +310,20 @@ void srt::CUDTUnited::stopGarbageCollector() m_bGCStatus = false; { CUniqueSync gclock (m_GCStopLock, m_GCStopCond); - m_bClosing = true; + m_bGCClosing = true; gclock.notify_all(); } m_GCThread.join(); } } -void srt::CUDTUnited::cleanupAllSockets() +void CUDTUnited::cleanupAllSockets() { for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { CUDTSocket* s = i->second; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (s->m_GroupOf) { s->removeFromGroup(false); @@ -302,7 +338,7 @@ void srt::CUDTUnited::cleanupAllSockets() } if (ls != m_ClosedSockets.end()) { - ls->second->m_QueuedSockets.erase(s->m_SocketID); + ls->second->m_QueuedSockets.erase(s->id()); } s->core().closeAtFork(); s->resetAtFork(); @@ -310,7 +346,7 @@ void srt::CUDTUnited::cleanupAllSockets() } m_Sockets.clear(); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j) { delete j->second; @@ -325,11 +361,18 @@ void srt::CUDTUnited::cleanupAllSockets() m_mMultiplexer.clear(); } - -void srt::CUDTUnited::closeAllSockets() +void CUDTUnited::closeAllSockets() { + // IMPORTANT!!! + // + // This function is called mainly from the global destructor, and as such + // it shall not try to access any other global object than just itself. + // By this reason, LOGGING IS NOT ALLOWED HERE, including printing on stdout + // using iostream. Instructions are comment-blocked so that you can unblock + // them if needed for the development, but this may likely cause a crash on exit. + // remove all sockets and multiplexers - HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquiring control lock..."); + DONT_HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquiring control lock..."); { // Pre-closing: run over all open sockets and close them. @@ -338,14 +381,12 @@ void srt::CUDTUnited::closeAllSockets() for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { CUDTSocket* s = i->second; - s->breakSocket_LOCKED(); + s->breakSocket_LOCKED(SRT_CLS_CLEANUP); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (s->m_GroupOf) { - HLOGC(smlog.Debug, - log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() - << " (IPE?) - REMOVING FROM GROUP"); + DONT_HLOGC(smlog.Debug, log << "@" << s->id() << " IS MEMBER OF $" << s->m_GroupOf->id() << " (IPE?) - REMOVING FROM GROUP"); s->removeFromGroup(false); } #endif @@ -355,24 +396,35 @@ void srt::CUDTUnited::closeAllSockets() { ExclusiveLock glock(m_GlobControlLock); + // Do not do generative expiry removal - there's no chance + // anyone can extract the close reason information since this point on. + m_ClosedDatabase.clear(); + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { CUDTSocket* s = i->second; - m_ClosedSockets[i->first] = s; + // NOTE: not removing the socket from m_Sockets. + // This is a loop over m_Sockets and after this loop ends, + // this whole container will be cleared. + swipeSocket_LOCKED(i->first, s, SWIPE_LATER); - // remove from listener's queue - sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); - if (ls == m_Sockets.end()) + if (s->m_ListenSocket != SRT_SOCKID_CONNREQ) { - ls = m_ClosedSockets.find(s->m_ListenSocket); - if (ls == m_ClosedSockets.end()) - continue; - } + // 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); + DONT_HLOGC(smlog.Debug, log << "@" << s->id() << " removed from queued sockets of listener @" << ls->second->id()); + ls->second->m_AcceptLock.lock(); + ls->second->m_QueuedSockets.erase(s->id()); + ls->second->m_AcceptLock.unlock(); + } } m_Sockets.clear(); @@ -381,7 +433,7 @@ void srt::CUDTUnited::closeAllSockets() j->second->m_tsClosureTimeStamp = steady_clock::time_point(); } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j) { SRTSOCKET id = j->second->m_GroupID; @@ -391,37 +443,49 @@ void srt::CUDTUnited::closeAllSockets() #endif } - HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); + DONT_HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); while (true) { checkBrokenSockets(); - enterCS(m_GlobControlLock); + m_GlobControlLock.lock(); bool empty = m_ClosedSockets.empty(); - leaveCS(m_GlobControlLock); + size_t remmuxer = m_mMultiplexer.size(); +#if 0 // HVU_ENABLE_HEAVY_LOGGING + ostringstream om; + if (remmuxer) + { + om << "["; + for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) + om << " " << i->first; + om << " ]"; - if (empty) - break; + } +#endif + m_GlobControlLock.unlock(); - HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep"); - srt::sync::this_thread::sleep_for(milliseconds_from(1)); - } + if (empty && remmuxer == 0) + break; + DONT_HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets or muxers=" + << remmuxer << om.str() << ", repeating after 0.1s sleep"); + sync::this_thread::sleep_for(milliseconds_from(100)); + } } -int srt::CUDTUnited::startup() +SRTRUNSTATUS CUDTUnited::startup() { ScopedLock gcinit(m_InitLock); m_iInstanceCount++; if (m_bGCStatus) - return (m_iInstanceCount == 1) ? 1 : 0; + return (m_iInstanceCount == 1) ? SRT_RUN_ALREADY : SRT_RUN_OK; else - return startGarbageCollector() ? 0 : -1; + return startGarbageCollector() ? SRT_RUN_OK : SRT_RUN_ERROR; } -int srt::CUDTUnited::cleanupAtFork() +int CUDTUnited::cleanupAtFork() { cleanupAllSockets(); resetThread(&m_GCThread); @@ -433,7 +497,7 @@ int srt::CUDTUnited::cleanupAtFork() return 0; } -int srt::CUDTUnited::cleanup() +SRTSTATUS CUDTUnited::cleanup() { // IMPORTANT!!! // In this function there must be NO LOGGING AT ALL. This function may @@ -446,14 +510,14 @@ int srt::CUDTUnited::cleanup() ScopedLock gcinit(m_InitLock); if (--m_iInstanceCount > 0) - return 0; + return SRT_STATUS_OK; stopGarbageCollector(); closeAllSockets(); - return 0; + return SRT_STATUS_OK; } -SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) +SRTSOCKET CUDTUnited::generateSocketID(bool for_group) { ScopedLock guard(m_IDLock); @@ -497,15 +561,15 @@ SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) int startval = sockval; for (;;) // Roll until an unused value is found { - enterCS(m_GlobControlLock); + m_GlobControlLock.lock(); const bool exists = -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING for_group - ? m_Groups.count(sockval | SRTGROUP_MASK) + ? m_Groups.count(SRTSOCKET(sockval | SRTGROUP_MASK)) : #endif - m_Sockets.count(sockval); - leaveCS(m_GlobControlLock); + m_Sockets.count(SRTSOCKET(sockval)); + m_GlobControlLock.unlock(); if (exists) { @@ -553,10 +617,10 @@ SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) LOGC(smlog.Debug, log << "generateSocketID: " << (for_group ? "(group)" : "") << ": @" << sockval); - return sockval; + return SRTSOCKET(sockval); } -SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) +SRTSOCKET CUDTUnited::newSocket(CUDTSocket** pps, bool managed) { // XXX consider using some replacement of std::unique_ptr // so that exceptions will clean up the object without the @@ -575,7 +639,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) try { - ns->m_SocketID = generateSocketID(); + ns->core().m_SocketID = generateSocketID(); } catch (...) { @@ -583,17 +647,17 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) throw; } ns->m_Status = SRTS_INIT; - ns->m_ListenSocket = 0; - ns->core().m_SocketID = ns->m_SocketID; + ns->m_ListenSocket = SRT_SOCKID_CONNREQ; // A value used for socket if it wasn't listener-spawned ns->core().m_pCache = m_pCache; + ns->core().m_bManaged = managed; try { - HLOGC(smlog.Debug, log << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID); + HLOGC(smlog.Debug, log << CONID(ns->id()) << "newSocket: mapping socket " << ns->id()); // protect the m_Sockets structure. ExclusiveLock cs(m_GlobControlLock); - m_Sockets[ns->m_SocketID] = ns; + m_Sockets[ns->id()] = ns; } catch (...) { @@ -603,11 +667,24 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - startGarbageCollector(); + { + ScopedLock glk (m_InitLock); + startGarbageCollector(); + } if (pps) *pps = ns; - return ns->m_SocketID; + return ns->id(); +} + +// [[using locked(m_GlobControlLock)]] +void CUDTUnited::swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, CUDTUnited::SwipeSocketTerm lateremove) +{ + m_ClosedSockets[id] = s; + if (!lateremove) + { + m_Sockets.erase(id); + } } // XXX NOTE: TSan reports here false positive against the call @@ -616,7 +693,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) // having applied a shared lock on CRcvQueue::m_pListener in // CRcvQueue::worker_ProcessConnectionRequest. As this thread // locks both mutexes as shared, it doesn't form a deadlock. -int srt::CUDTUnited::newConnection(const SRTSOCKET listen, +int CUDTUnited::newConnection(const SRTSOCKET listener, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs, @@ -630,15 +707,15 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // Can't manage this error through an exception because this is // running in the listener loop. - CUDTSocket* ls = locateSocket(listen); + CUDTSocket* ls = locateSocket(listener); if (!ls) { - LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listen << " which DOES NOT EXIST."); + LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listener << " which DOES NOT EXIST."); return -1; } HLOGC(cnlog.Debug, - log << "newConnection: creating new socket after listener @" << listen + log << "newConnection: creating new socket after listener @" << listener << " contacted with backlog=" << ls->m_uiBackLog); // if this connection has already been processed @@ -648,9 +725,10 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, { // last connection from the "peer" address has been broken ns->setClosed(); + HLOGC(cnlog.Debug, log << "newConnection: @" << ns->id() << " broken - deleting from queued"); ScopedLock acceptcg(ls->m_AcceptLock); - ls->m_QueuedSockets.erase(ns->m_SocketID); + ls->m_QueuedSockets.erase(ns->id()); } else { @@ -662,7 +740,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, 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; + w_hs.m_iID = ns->id(); // Report the original UDT because it will be // required to complete the HS data for conclusion response. @@ -681,9 +759,9 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // exceeding backlog, refuse the connection request - enterCS(ls->m_AcceptLock); + ls->m_AcceptLock.lock(); size_t backlog = ls->m_QueuedSockets.size(); - leaveCS(ls->m_AcceptLock); + ls->m_AcceptLock.unlock(); if (backlog >= ls->m_uiBackLog) { w_error = SRT_REJ_BACKLOG; @@ -711,7 +789,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, try { - ns->m_SocketID = generateSocketID(); + ns->core().m_SocketID = generateSocketID(); } catch (const CUDTException&) { @@ -724,13 +802,12 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, return -1; } - ns->m_ListenSocket = listen; - ns->core().m_SocketID = ns->m_SocketID; - ns->m_PeerID = w_hs.m_iID; + ns->m_ListenSocket = listener; + ns->core().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 + log << "newConnection: DATA: lsnid=" << listener << " id=" << ns->id() << " peerid=" << ns->core().m_PeerID << " ISN=" << ns->m_iISN); int error = 0; @@ -753,10 +830,10 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // 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); + "newConnection: incoming " << peer.str() << ", mapping socket " << ns->id()); { ExclusiveLock cg(m_GlobControlLock); - m_Sockets[ns->m_SocketID] = ns; + m_Sockets[ns->id()] = ns; } if (ls->core().m_cbAcceptHook) @@ -781,7 +858,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, throw false; // let it jump directly into the omni exception handler } - ns->core().acceptAndRespond(ls->m_SelfAddr, peer, hspkt, (w_hs)); + ns->core().acceptAndRespond(ls, peer, hspkt, (w_hs)); } catch (...) { @@ -796,23 +873,23 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // 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)); + ns->m_SelfAddr = ns->core().channel()->getSockAddr(); // - 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); + CIPAddress::decode(ns->core().m_piSelfIP, peer, (ns->m_SelfAddr)); { // protect the m_PeerRec structure (and group existence) ExclusiveLock 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); + HLOGC(cnlog.Debug, log << "newConnection: mapping peer " << ns->core().m_PeerID + << " to that socket (" << ns->id() << ")"); + m_PeerRec[ns->getPeerSpec()].insert(ns->id()); - LOGC(cnlog.Note, log << "@" << ns->m_SocketID << " connection on listener @" << listen - << " (" << ns->m_SelfAddr.str() << ") from peer @" << ns->m_PeerID << " (" << peer.str() << ")"); + LOGC(cnlog.Note, log << "@" << ns->id() << " connection on listener @" << listener + << " (" << ns->m_SelfAddr.str() << ") from peer @" << ns->core().m_PeerID << " (" << peer.str() << ")"); } catch (...) { @@ -824,7 +901,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // could be requested deletion in the meantime. This will hold any possible // removal from group and resetting m_GroupOf field. -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (ns->m_GroupOf) { // XXX this might require another check of group type. @@ -837,68 +914,33 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, 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; - } - } + // Acceptance of the group will have to be done through accepting + // of one of the pending sockets. There can be, however, multiple + // such sockets at a time, some of them might get broken before + // being accepted, and therefore we need to make all sockets ready. + // But then, acceptance of a group may happen only once, so if any + // sockets of the same group were submitted to accept, they must + // be removed from the accept queue at this time. + should_submit_to_accept = g->groupPending_LOCKED(); + + // Ok, whether handled in the background, or reported through accept, + // all group-member sockets should be managed. + ns->core().m_bManaged = true; // 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 " + log << "newConnection(GROUP): Socket @" << ns->id() << " 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 PROBLEM!!! 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 subscription 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; + g->setGroupConnected(); + // Keep per-socket sender ready EID only. 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 @@ -910,28 +952,29 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, } else { - HLOGC(cnlog.Debug, log << "newConnection: Socket @" << ns->m_SocketID << " is not in a group"); + HLOGC(cnlog.Debug, log << "newConnection: Socket @" << ns->id() << " is not in a group"); } #endif } if (should_submit_to_accept) { - enterCS(ls->m_AcceptLock); + ls->m_AcceptLock.lock(); try { - ls->m_QueuedSockets[ns->m_SocketID] = ns->m_PeerAddr; + ls->m_QueuedSockets[ns->id()] = ns->m_PeerAddr; + HLOGC(cnlog.Debug, log << "newConnection: Socket @" << ns->id() << " added to queued of @" << ls->id()); } catch (...) { LOGC(cnlog.Error, log << "newConnection: error when queuing socket!"); error = 3; } - leaveCS(ls->m_AcceptLock); + ls->m_AcceptLock.unlock(); - HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->m_SocketID << " submitted for acceptance"); + HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->id() << " 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); + m_EPoll.update_events(listener, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, true); CGlobEvent::triggerEvent(); @@ -947,12 +990,21 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, else { HLOGC(cnlog.Debug, - log << "ACCEPT: new socket @" << ns->m_SocketID + log << "ACCEPT: new socket @" << ns->id() << " 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); + m_EPoll.update_events(listener, ls->core().m_sPollID, SRT_EPOLL_UPDATE, true); +#if SRT_ENABLE_BONDING + // Note that the code in this current IF branch can only be executed in case + // of group members. Otherwise should_submit_to_accept will be always true. + if (ns->m_GroupOf) + { + HLOGC(gmlog.Debug, log << "GROUP UPDATE $" << ns->m_GroupOf->id() << " per connected socket @" << ns->id()); + m_EPoll.update_events(ns->m_GroupOf->id(), ns->m_GroupOf->m_sPollID, SRT_EPOLL_UPDATE, true); + } +#endif CGlobEvent::triggerEvent(); } @@ -960,16 +1012,16 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // XXX the exact value of 'error' is ignored if (error > 0) { -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING 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] << " - " + log << CONID(ns->id()) << "newConnection: connection rejected due to: " << why[error] << " - " << RequestTypeStr(URQFailure(w_error))); #endif - SRTSOCKET id = ns->m_SocketID; - ns->core().closeInternal(); + SRTSOCKET id = ns->id(); + ns->closeInternal(SRT_CLS_LATE); ns->setClosed(); // The mapped socket should be now unmapped to preserve the situation that @@ -980,17 +1032,19 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, { ExclusiveLock cg(m_GlobControlLock); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (ns->m_GroupOf) { HLOGC(smlog.Debug, - log << "@" << ns->m_SocketID << " IS MEMBER OF $" << ns->m_GroupOf->id() + log << "@" << ns->id() << " IS MEMBER OF $" << ns->m_GroupOf->id() << " - REMOVING FROM GROUP"); ns->removeFromGroup(true); } #endif - m_Sockets.erase(id); - m_ClosedSockets[id] = ns; + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(id, ns->core().m_sPollID); + + swipeSocket_LOCKED(id, ns, SWIPE_NOW); } return -1; @@ -999,13 +1053,79 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, return 1; } +// [[using locked_shared(m_GlobControlLock)]] +SRT_EPOLL_T CUDTSocket::getListenerEvents() +{ + // You need to check EVERY socket that has been queued + // and verify its internals. With independent socket the + // matter is simple - if it's present, you light up the + // SRT_EPOLL_ACCEPT flag. + +#if !SRT_ENABLE_BONDING + ScopedLock accept_lock (m_AcceptLock); + + // Make it simplified here - nonempty container = have acceptable sockets. + // Might make sometimes spurious acceptance, but this can also happen when + // the incoming accepted socket was suddenly broken. + return m_QueuedSockets.empty() ? 0 : int(SRT_EPOLL_ACCEPT); + +#else // Could do #endif here, but the compiler would complain about unreachable code. + + map sockets_copy; + { + ScopedLock accept_lock (m_AcceptLock); + sockets_copy = m_QueuedSockets; + } + + // [TSA] NOTE: m_GlobControlLock is required here, but this is applied already + // on this whole function. (see CUDT::addEPoll) + return CUDT::uglobal().checkQueuedSocketsEvents(sockets_copy); + +#endif +} + +#if SRT_ENABLE_BONDING +int CUDTUnited::checkQueuedSocketsEvents(const map& sockets) +{ + SRT_EPOLL_T flags = 0; + + // But with the member sockets an appropriate check must be + // done first: if this socket belongs to a group that is + // already in the connected state, you should light up the + // SRT_EPOLL_UPDATE flag instead. This flag is only for + // internal informing the waiters on the listening sockets + // that they should re-read the group list and re-check readiness. + + // Now we can do lock once and for all + for (map::const_iterator i = sockets.begin(); i != sockets.end(); ++i) + { + CUDTSocket* s = locateSocket_LOCKED(i->first); + if (!s) + continue; // wiped in the meantime - ignore + + // If this pending socket is a group member, but the group + // to which it belongs is NOT waiting to be accepted, then + // light up the UPDATE event only. Light up ACCEPT only if + // this is a single socket, or this single socket has turned + // the mirror group to be first time available for accept(), + // and this accept() hasn't been done yet. + if (s->m_GroupOf && !s->m_GroupOf->groupPending()) + flags |= SRT_EPOLL_UPDATE; + else + flags |= SRT_EPOLL_ACCEPT; + } + + return flags; +} +#endif + // static forwarder -int srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS 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) +SRTSTATUS CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { try { @@ -1018,24 +1138,24 @@ int srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_ return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } -int srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { return uglobal().installConnectHook(lsn, hook, opaq); } -int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) { try { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); k.group->installConnectHook(hook, opaq); - return 0; + return SRT_STATUS_OK; } #endif CUDTSocket* s = locateSocket(u, ERH_THROW); @@ -1047,10 +1167,10 @@ int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_ return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } -SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) +SRT_SOCKSTATUS CUDTUnited::getStatus(const SRTSOCKET u) { // protects the m_Sockets structure SharedLock cg(m_GlobControlLock); @@ -1067,7 +1187,39 @@ SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) return i->second->getStatus(); } -int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) +SRTSTATUS CUDTUnited::getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& info) +{ + // protects the m_Sockets structure + SharedLock cg(m_GlobControlLock); + + // We need to search for the socket in: + // m_Sockets, if it is somehow still alive, + // m_ClosedSockets, if it's when it should be, + // m_ClosedDatabase, if it has been already garbage-collected and deleted. + + sockets_t::const_iterator i = m_Sockets.find(u); + if (i != m_Sockets.end()) + { + i->second->core().copyCloseInfo((info)); + return SRT_STATUS_OK; + } + + i = m_ClosedSockets.find(u); + if (i != m_ClosedSockets.end()) + { + i->second->core().copyCloseInfo((info)); + return SRT_STATUS_OK; + } + + map::iterator c = m_ClosedDatabase.find(u); + if (c == m_ClosedDatabase.end()) + return SRT_ERROR; + + info = c->second.info; + return SRT_STATUS_OK; +} + +SRTSTATUS CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) { ScopedLock cg(s->m_ControlLock); @@ -1084,17 +1236,11 @@ int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - s->core().open(); - updateMux(s, name); - s->m_Status = SRTS_OPENED; - - // copy address information of local node - s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); - - return 0; + bindSocketToMuxer(s, name); + return SRT_STATUS_OK; } -int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) +SRTSTATUS CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) { ScopedLock cg(s->m_ControlLock); @@ -1112,25 +1258,33 @@ int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) // Successfully extracted, so update the size name.len = namelen; + bindSocketToMuxer(s, name, &udpsock); + return SRT_STATUS_OK; +} + +void CUDTUnited::bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& address, UDPSOCKET* psocket) +{ + if (address.hport() == 0 && s->core().m_config.bRendezvous) + throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); s->core().open(); - updateMux(s, name, &udpsock); + updateMux(s, address, psocket); + // -> C(Snd|Rcv)Queue::init + // -> pthread_create(...C(Snd|Rcv)Queue::worker...) s->m_Status = SRTS_OPENED; // copy address information of local node - s->core().m_pSndQueue->m_pChannel->getSockAddr(s->m_SelfAddr); - - return 0; + s->m_SelfAddr = s->core().channel()->getSockAddr(); } -int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) +SRTSTATUS CUDTUnited::listen(const SRTSOCKET u, int backlog) { 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) + if (u == SRT_INVALID_SOCK) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); CUDTSocket* s = locateSocket(u); @@ -1146,7 +1300,7 @@ int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) // do nothing if the socket is already listening if (s->m_Status == SRTS_LISTENING) - return 0; + return SRT_STATUS_OK; // a socket can listen only if is in OPENED status if (s->m_Status != SRTS_OPENED) @@ -1166,10 +1320,10 @@ int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) // if thrown, remains in OPENED state if so. s->m_Status = SRTS_LISTENING; - return 0; + return SRT_STATUS_OK; } -SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) +SRTSOCKET CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) { CEPollDesc* ed = 0; int eid = m_EPoll.create(&ed); @@ -1208,13 +1362,13 @@ SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, i // 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; + SRTSOCKET lsn = st.begin()->first; sockaddr_storage dummy; int outlen = sizeof dummy; return accept(lsn, ((sockaddr*)&dummy), (&outlen)); } -SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen) +SRTSOCKET CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int* pw_addrlen) { if (pw_addr && !pw_addrlen) { @@ -1222,34 +1376,39 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - CUDTSocket* ls = locateSocket(listen); + CUDTSocket* ls; + SocketKeeper keep_ls; - if (ls == NULL) + // We keep the mutex locked for the whole time of instant checks. + // Once they pass, extend the life for the scope by SocketKeeper. { - LOGC(cnlog.Error, log << "srt_accept: invalid listener socket ID value: " << listen); - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - } + SharedLock lkg (m_GlobControlLock); + ls = locateSocket_LOCKED(listen, ERH_THROW); - // 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); - } + // 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); + // 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); + } + + // Artificially acquire by SocketKeeper, to be properly released. + keep_ls.acquire_LOCKED(ls); } - SRTSOCKET u = CUDT::INVALID_SOCK; - bool accepted = false; + SRTSOCKET u = SRT_INVALID_SOCK; + bool accepted = false; // !!only one connection can be set up each time!! while (!accepted) @@ -1275,11 +1434,12 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int { // In case when the address cannot be rewritten, // DO NOT accept, but leave the socket in the queue. - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + break; } } u = b->first; + HLOGC(cnlog.Debug, log << "accept: @" << u << " extracted from @" << ls->id() << " - deleting from queued"); ls->m_QueuedSockets.erase(b); accepted = true; } @@ -1295,10 +1455,26 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, false); } - if (u == CUDT::INVALID_SOCK) +#if SRT_ENABLE_BONDING + int lsn_group_connect = ls->core().m_config.iGroupConnect; +#endif + bool lsn_syn_recv = ls->core().m_config.bSynRecving; + + // NOTE: release() locks m_GlobControlLock. + // Once we extracted the accepted socket, we don't need to keep ls busy. + keep_ls.release(*this); + ls = NULL; // NOT USABLE ANYMORE! + + if (!accepted) // The loop was interrupted + { + LOGC(cnlog.Error, log << "srt_accept: can't extract address - target object too small"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + if (u == SRT_INVALID_SOCK) { // non-blocking receiving, no connection available - if (!ls->core().m_config.bSynRecving) + if (!lsn_syn_recv) { LOGC(cnlog.Error, log << "srt_accept: no pending connection available at the moment"); throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); @@ -1316,14 +1492,16 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int throw CUDTException(MJ_SETUP, MN_CLOSED, 0); } - // Set properly the SRTO_GROUPCONNECT flag + SRT_ASSERT(s->core().m_bConnected); + + // Set properly the SRTO_GROUPCONNECT flag (for general case; may be overridden later) s->core().m_config.iGroupConnect = 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) +#if SRT_ENABLE_BONDING + if (lsn_group_connect == 1 && s->m_GroupOf) { // Put a lock to protect the group against accidental deletion // in the meantime. @@ -1332,12 +1510,23 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int // 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 - + CUDTGroup* g = s->m_GroupOf; // 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(); + g->m_stats.tsLastSampleTime = steady_clock::now(); + + // Ok, now that we have to get the group: + // 1. Get all listeners that have so far reported any pending connection + // for this group. + // 2. THE VERY LISTENER that provided this connection should be only + // checked if it contains ANY FURTHER queued sockets than this. + + HLOGC(cnlog.Debug, log << "accept: reporting group $" << g->m_GroupID << " instead of member socket @" << u); + u = g->m_GroupID; + s->core().m_config.iGroupConnect = 1; // should be derived from ls, but make sure + + vector listeners = g->clearPendingListeners(); + removePendingForGroup(g, listeners, s->id()); } else { @@ -1357,7 +1546,107 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int return u; } -int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) +#if SRT_ENABLE_BONDING + +// [[using locked(m_GlobControlLock)]] +void CUDTUnited::removePendingForGroup(const CUDTGroup* g, const vector& listeners, SRTSOCKET this_socket) +{ + set members; + g->getMemberSockets((members)); + + IF_HEAVY_LOGGING(ostringstream outl); + IF_HEAVY_LOGGING(for (vector::const_iterator lp = listeners.begin(); lp != listeners.end(); ++lp) { outl << " @" << (*lp); }); + + HLOGC(cnlog.Debug, log << "removePendingForGroup: " << listeners.size() << " listeners collected: " << outl.str()); + + // What we need to do is: + // 1. Walk through the listener sockets and check their accept queue. + // 2. Skip a socket that: + // - Is equal to this_socket (was removed from the queue already and triggered group accept) + // - Does not belong to group members (should remain there for other purposes) + // 3. Any member socket found in that listener: + // - this socket must be removed from the queue + // - the listener containing this socket must be added UPDATE event. + + map listeners_to_update; + + for (vector::const_iterator i = listeners.begin(); i != listeners.end(); ++i) + { + CUDTSocket* ls = locateSocket_LOCKED(*i); + if (!ls) + { + HLOGC(cnlog.Debug, log << "Group-pending lsn @" << (*i) << " deleted in the meantime"); + continue; + } + vector swipe_members; + + ScopedLock alk (ls->m_AcceptLock); + + for (map::const_iterator q = ls->m_QueuedSockets.begin(); q != ls->m_QueuedSockets.end(); ++q) + { + HLOGC(cnlog.Debug, log << "Group-pending lsn @" << (*i) << " queued socket @" << q->first << ":"); + // 1. Check if it was the accept-triggering socket + if (q->first == this_socket) + { + listeners_to_update[ls] += 0; + HLOGC(cnlog.Debug, log << "... is the accept-trigger; will only possibly silence the listener"); + continue; + } + + // 2. Check if it was this group's member socket + if (members.find(q->first) == members.end()) + { + // Increase the number of not-member-related sockets to know if + // the read-ready status from the listener should be cleared. + listeners_to_update[ls]++; + HLOGC(cnlog.Debug, log << "... is not a member of $" << g->id() << "; skipping"); + continue; + } + + // 3. Found at least one socket that is this group's member + // and is not the socket that triggered accept. + swipe_members.push_back(q->first); + listeners_to_update[ls] += 0; + HLOGC(cnlog.Debug, log << "... is to be unqueued"); + } + if (ls->m_QueuedSockets.empty()) + { + HLOGC(cnlog.Debug, log << "Group-pending lsn @" << (*i) << ": NO QUEUED SOCKETS"); + } + + for (vector::iterator is = swipe_members.begin(); is != swipe_members.end(); ++is) + { + ls->m_QueuedSockets.erase(*is); + } + } + + // Now; for every listener, which contained at least one socket that is + // this group's member: + // - ADD UPDATE event + // - REMOVE ACCEPT event, if the number of "other sockets" is zero. + + // NOTE: "map" container is used because we need to have unique listener container, + // while the listener may potentially be added multiple times in the loop of queued sockets. + for (map::iterator mi = listeners_to_update.begin(); mi != listeners_to_update.end(); ++mi) + { + CUDTSocket* s; + int nothers; + Tie(s, nothers) = *mi; + + HLOGC(cnlog.Debug, log << "Group-pending lsn @" << s->id() << " had in-group accepted sockets and " << nothers << " other sockets"); + if (nothers == 0) + { + m_EPoll.update_events(s->id(), s->core().m_sPollID, SRT_EPOLL_ACCEPT, false); + } + + m_EPoll.update_events(s->id(), s->core().m_sPollID, SRT_EPOLL_UPDATE, true); + } + +} + +#endif + +SRTSOCKET 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))) @@ -1375,11 +1664,11 @@ int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockadd if (target_addr.len == 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); -#if ENABLE_BONDING +#if SRT_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) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); // Note: forced_isn is ignored when connecting a group. @@ -1400,10 +1689,11 @@ int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockadd // For a single socket, just do bind, then connect bind(s, source_addr); - return connectIn(s, target_addr, SRT_SEQNO_NONE); + connectIn(s, target_addr, SRT_SEQNO_NONE); + return SRT_SOCKID_CONNREQ; } -int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { if (!name || namelen < int(sizeof(sockaddr_in))) { @@ -1415,11 +1705,11 @@ int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namele if (target_addr.len == 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); -#if ENABLE_BONDING +#if SRT_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) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); @@ -1436,31 +1726,29 @@ int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namele if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - return connectIn(s, target_addr, forced_isn); + connectIn(s, target_addr, forced_isn); + return SRT_SOCKID_CONNREQ; } -#if ENABLE_BONDING -int srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) +#if SRT_ENABLE_BONDING +SRTSOCKET CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) { - int gstat = groupConnect(pg, gd, 1); - if (gstat == -1) + SRTSOCKET gstat = groupConnect(pg, gd, 1); + if (gstat == SRT_INVALID_SOCK) { // 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 CUDT::APIError(gd->errorcode), SRT_INVALID_SOCK; } return gstat; } // [[using assert(pg->m_iBusy > 0)]] -int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) +SRTSOCKET CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) { CUDTGroup& g = *pg; SRT_ASSERT(g.m_iBusy > 0); @@ -1482,27 +1770,35 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } } - // 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) + bool block_new_opened; + // Synchronize on simultaneous group-locking { - 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); + ScopedLock glk (*g.exp_groupLock()); + + // 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). + block_new_opened = !g.m_bOpened && g.m_bSynRecving; + // [TSA] may miss the above exp_groupLock() ---^^^^ + const bool was_empty = g.groupEmpty_LOCKED(); + + // 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; + + SRTSOCKET retval = SRT_INVALID_SOCK; int eid = -1; int connect_modes = SRT_EPOLL_CONNECT | SRT_EPOLL_ERR; @@ -1534,7 +1830,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // 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); + SRTSOCKET sid = newSocket(&ns, true); // Create MANAGED socket (auto-deleted when broken) if (pg->m_cbConnectHook) { @@ -1559,7 +1855,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i 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 = "group-derived option: #" + Sprint(g.m_config[i].so); + error_reason = hvu::fmtcat("group-derived option: #", 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()); } @@ -1593,7 +1889,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // Do it after setting all stored options, as some of them may // influence some group data. - srt::groups::SocketData data = srt::groups::prepareSocketData(ns); + groups::SocketData data = groups::prepareSocketData(ns, g.type()); if (targets[tii].token != -1) { // Reuse the token, if specified by the caller @@ -1649,7 +1945,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } else { - targets[tii].id = CUDT::INVALID_SOCK; + targets[tii].id = SRT_INVALID_SOCK; delete ns; m_Sockets.erase(sid); @@ -1666,6 +1962,8 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i ns->core().m_cbPacketArrival.set(ns->m_pUDT, &CUDT::groupPacketArrival); */ + // XXX Check if needed SharedLock cs(m_GlobControlLock); + int isn = g.currentSchedSequence(); // Set it the groupconnect option, as all in-group sockets should have. @@ -1684,7 +1982,6 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // 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 @@ -1711,11 +2008,14 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // 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; + targets[tii].id = SRT_INVALID_SOCK; ExclusiveLock cl(m_GlobControlLock); + + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(ns->id(), ns->core().m_sPollID); ns->removeFromGroup(false); - m_Sockets.erase(ns->m_SocketID); + m_Sockets.erase(ns->id()); // Intercept to delete the socket on failure. delete ns; continue; @@ -1724,10 +2024,12 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i { LOGC(aclog.Fatal, log << "groupConnect: IPE: UNKNOWN EXCEPTION from connectIn"); targets[tii].errorcode = SRT_ESYSOBJ; - targets[tii].id = CUDT::INVALID_SOCK; + targets[tii].id = SRT_INVALID_SOCK; ExclusiveLock cl(m_GlobControlLock); ns->removeFromGroup(false); - m_Sockets.erase(ns->m_SocketID); + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(ns->id(), ns->core().m_sPollID); + m_Sockets.erase(ns->id()); // Intercept to delete the socket on failure. delete ns; @@ -1773,7 +2075,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // set BROKEN or PENDING. f->sndstate = SRT_GST_PENDING; f->rcvstate = SRT_GST_PENDING; - retval = -1; + retval = SRT_INVALID_SOCK; break; } @@ -1806,7 +2108,6 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i 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 { @@ -1821,7 +2122,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } } - if (retval == -1) + if (retval == SRT_INVALID_SOCK) { HLOGC(aclog.Debug, log << "groupConnect: none succeeded as background-spawn, exit with error"); block_new_opened = false; // Avoid executing further while loop @@ -1834,13 +2135,13 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i if (spawned.empty()) { // All were removed due to errors. - retval = -1; + retval = SRT_INVALID_SOCK; 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, + const int estat = srt_epoll_wait(eid, NULL, NULL, // IN/ACCEPT &ready[0], @@ -1852,9 +2153,9 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i NULL); // Sanity check. Shouldn't happen if subs are in sync with spawned. - if (estat == -1) + if (estat == int(SRT_ERROR)) { -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING CUDTException& x = CUDT::getlasterror(); if (x.getErrorCode() != SRT_EPOLLEMPTY) { @@ -1864,7 +2165,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } #endif HLOGC(aclog.Debug, log << "groupConnect: srt_epoll_wait failed - breaking the wait loop"); - retval = -1; + retval = SRT_INVALID_SOCK; break; } @@ -1892,7 +2193,6 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i 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); } } @@ -1932,7 +2232,6 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i epoll_remove_socket_INTERNAL(eid, s); epoll_remove_socket_INTERNAL(g.m_SndEID, s); - epoll_remove_socket_INTERNAL(g.m_RcvEID, s); continue; } @@ -1942,6 +2241,12 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i HLOGC(aclog.Debug, log << "groupConnect: Socket @" << sid << " got CONNECTED as first in the group - reporting"); retval = sid; + + // XXX Race against postConnect/setGroupConnected in the worker thread. + // XXX POTENTIAL BUG: Possibly this supersedes the same setting done from postConnect + // and this way the epoll readiness isn't set. + // In this thread the group is also set connected after the connection process is done. + // Might be that this here isn't required. g.m_bConnected = true; block_new_opened = false; // Interrupt also rolling epoll (outer loop) @@ -1975,7 +2280,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i continue; // This will also automatically remove it from the group and all eids - close(s); + close(s, SRT_CLS_INTERNAL); } // There's no possibility to report a problem on every connection @@ -1986,14 +2291,14 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // 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 reason for particular link. - if (retval == -1) + if (retval == SRT_INVALID_SOCK) 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) +void 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: @@ -2003,22 +2308,14 @@ int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, i 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; + bindSocketToMuxer(s, sockaddr_any(target_addr.family())); } else { @@ -2060,22 +2357,20 @@ int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, i s->m_Status = SRTS_OPENED; throw; } - - return 0; } -int srt::CUDTUnited::close(const SRTSOCKET u) +SRTSTATUS CUDTUnited::close(const SRTSOCKET u, int reason) { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); k.group->close(); deleteGroup(k.group); - return 0; + return SRT_STATUS_OK; } #endif -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING // Wrapping the log into a destructor so that it // is printed AFTER the destructor of SocketKeeper. struct ScopedExitLog @@ -2086,7 +2381,7 @@ int srt::CUDTUnited::close(const SRTSOCKET u) { if (ps) // Could be not acquired by SocketKeeper, occasionally { - HLOGC(smlog.Debug, log << "CUDTUnited::close/end: @" << ps->m_SocketID << " busy=" << ps->isStillBusy()); + HLOGC(smlog.Debug, log << "CUDTUnited::close/end: @" << ps->id() << " busy=" << ps->isStillBusy()); } } }; @@ -2096,20 +2391,25 @@ int srt::CUDTUnited::close(const SRTSOCKET u) IF_HEAVY_LOGGING(ScopedExitLog slog(k.socket)); HLOGC(smlog.Debug, log << "CUDTUnited::close/begin: @" << u << " busy=" << k.socket->isStillBusy()); - return close(k.socket); + SRTSTATUS cstatus = close(k.socket, reason); + HLOGC(smlog.Debug, log << "CUDTUnited::close: internal close status " << cstatus); + + // Releasing under the global lock to avoid even theoretical + // data race. + + k.release(*this); + return cstatus; } -#if ENABLE_BONDING -void srt::CUDTUnited::deleteGroup(CUDTGroup* g) +#if SRT_ENABLE_BONDING +void CUDTUnited::deleteGroup(CUDTGroup* g) { - using srt_logging::gmlog; - - srt::sync::ExclusiveLock cg(m_GlobControlLock); + sync::ExclusiveLock cg(m_GlobControlLock); return deleteGroup_LOCKED(g); } // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) +void CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) { SRT_ASSERT(g->groupEmpty()); @@ -2126,7 +2426,7 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) CUDTSocket* s = i->second; if (s->m_GroupOf == g) { - HLOGC(smlog.Debug, log << "deleteGroup: IPE: existing @" << s->m_SocketID << " points to a dead group!"); + LOGC(smlog.Error, log << "deleteGroup: IPE: existing @" << s->id() << " points to a dead group!"); s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; } @@ -2139,7 +2439,7 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) CUDTSocket* s = i->second; if (s->m_GroupOf == g) { - HLOGC(smlog.Debug, log << "deleteGroup: IPE: closed @" << s->m_SocketID << " points to a dead group!"); + LOGC(smlog.Error, log << "deleteGroup: IPE: closed @" << s->id() << " points to a dead group!"); s->m_GroupOf = NULL; s->m_GroupMemberData = NULL; } @@ -2147,23 +2447,168 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) } #endif -int srt::CUDTUnited::close(CUDTSocket* s) +// [[using locked(m_GlobControlLock)]] +void CUDTUnited::recordCloseReason(CUDTSocket* s) +{ + CloseInfo ci; + ci.info.agent = SRT_CLOSE_REASON(s->core().m_AgentCloseReason.load()); + ci.info.peer = SRT_CLOSE_REASON(s->core().m_PeerCloseReason.load()); + ci.info.time = sync::count_microseconds(s->core().m_CloseTimeStamp.load().time_since_epoch()); + + m_ClosedDatabase[s->id()] = ci; + + // As a DOS attack prevention, do not allow to keep more than 10 records. + // In a normal functioning of the application this shouldn't be necessary, + // but it is still needed that a record of a dead socket is kept for + // 10 gc cycles more to ensure that the application can obtain it even after + // the socket has been physically removed. But if we don't limit the number + // of these records, this could be vulnerable for DOS attack if the user + // forces the application to create and close SRT sockets very quickly. + // Hence remove the oldest record, which can be recognized from the `time` + // field, if the number of records exceeds 10. + if (m_ClosedDatabase.size() > MAX_CLOSE_RECORD_SIZE) + { + // remove the oldest one + // This can only be done by collecting all time info + map which; + + for (map::iterator x = m_ClosedDatabase.begin(); + x != m_ClosedDatabase.end(); ++x) + { + which[x->second.info.time] = x->first; + } + + map::iterator y = which.begin(); + size_t ntodel = m_ClosedDatabase.size() - MAX_CLOSE_RECORD_SIZE; + for (size_t i = 0; i < ntodel; ++i) + { + // Sanity check - should never happen because it's unlikely + // that two different sockets were closed exactly at the same + // nanosecond time. + if (y == which.end()) + break; + + m_ClosedDatabase.erase(y->second); + ++y; + } + } +} + +bool CUDTSocket::closeInternal(int reason) ATR_NOEXCEPT +{ + bool done = m_UDT.closeEntity(reason); + breakNonAcceptedSockets(); // XXX necessary? + + return done; +} + +void CUDTSocket::breakNonAcceptedSockets() +{ + // In case of a listener socket, close also all incoming connection + // sockets that have not been extracted as accepted. + + vector accepted; + if (m_UDT.m_bListening) + { + HLOGC(smlog.Debug, log << "breakNonAcceptedSockets: @" << m_UDT.id() << " CHECKING ACCEPTED LEAKS:"); + ScopedLock lk (m_AcceptLock); + + for (map::iterator q = m_QueuedSockets.begin(); + q != m_QueuedSockets.end(); ++ q) + { + accepted.push_back(q->first); + } + } + + if (!accepted.empty()) + { + HLOGC(smlog.Debug, log << "breakNonAcceptedSockets: found " << accepted.size() << " leaky accepted sockets"); + for (vector::iterator i = accepted.begin(); i != accepted.end(); ++i) + { + CUDTUnited::SocketKeeper sk(m_UDT.uglobal(), *i); + if (sk.socket) + { + sk.socket->m_UDT.m_bBroken = true; + sk.socket->m_UDT.m_iBrokenCounter = 0; + sk.socket->m_UDT.m_bClosing = true; + sk.socket->m_Status = SRTS_CLOSING; + } + } + } + else + { + HLOGC(smlog.Debug, log << "breakNonAcceptedSockets: no queued sockets"); + } +} + +SRTSTATUS CUDTUnited::close(CUDTSocket* s, int reason) { // Set the closing flag BEFORE you attempt to acquire s->setBreaking(); HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSE. Acquiring control lock"); ScopedLock socket_cg(s->m_ControlLock); + + // The check for whether m_pRcvQueue isn't NULL is safe enough; + // it can either be NULL after socket creation and without binding + // and then once it's assigned, it's never reset to NULL even when + // destroying the socket. + CUDT& e = s->core(); + + // Allow the socket to be closed by gc, if needed. + e.m_bManaged = true; + + // Status is required to make sure that the socket passed through + // the updateMux() and inside installMuxer() calls so that m_pRcvQueue + // has been set to a non-NULL value. The value itself can't be checked + // as such because it causes a data race. All checked data here are atomic. + SRT_SOCKSTATUS st = s->m_Status; + if (e.m_bConnecting && !e.m_bConnected && st >= SRTS_OPENED) + { + // Workaround for a design flaw. + // It's to work around the case when the socket is being + // closed in another thread while it's in the process of + // connecting in the blocking mode, that is, it runs the + // loop in `CUDT::startConnect` whole time under the lock + // of CUDT::m_ConnectionLock and CUDTSocket::m_ControlLock + // this way blocking the `srt_close` API call from continuing. + // We are setting here the m_bClosing flag prematurely so + // that the loop may check this flag periodically and exit + // immediately if it's set. + // + // The problem is that this flag shall NOT be set in case + // when you have a CONNECTED socket because not only isn't it + // not a problem in this case, but also it additionally + // turns the socket in a "confused" state in which it skips + // vital part of closing itself and therefore runs an infinite + // loop when trying to purge the sender buffer of the closing + // socket. + // + // XXX Consider refax on CUDT::startConnect and removing the + // connecting loop there and replace the "blocking mode specific" + // connecting procedure with delegation to the receiver queue, + // which will be then common with non-blocking mode, and synchronize + // the blocking through a CV. + + e.m_bClosing = true; + + // XXX Kicking rcv q is no longer necessary. This was kicking the CV + // that was sleeping on packet reception in CRcvQueue::m_mBuffer, + // which was only used for communication with the blocking-mode + // caller in original code. This code is now removed and the + // blocking mode is using non-blocking mode with stalling on CV. + } + 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; + SRTSOCKET u = s->id(); if (s->m_Status == SRTS_LISTENING) { if (s->core().m_bBroken) - return 0; + return SRT_STATUS_OK; s->m_tsClosureTimeStamp = steady_clock::now(); s->core().m_bBroken = true; @@ -2178,11 +2623,40 @@ int srt::CUDTUnited::close(CUDTSocket* s) // is currently occupying (due to blocked slot in the RcvQueue). HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSING (removing listener immediately)"); + s->breakNonAcceptedSockets(); + + // Do not lock m_GlobControlLock for that call; this would deadlock. + // We also get the ID of the muxer, not the muxer object because to get + // the muxer object you need to lock m_GlobControlLock. The ID may exist + // without a multiplexer and we have a guarantee it will not be reused + // for a long enough time. Worst case scenario, it won't be dispatched + // to a multiplexer - already under a lock, of course. s->core().notListening(); - s->m_Status = SRTS_CLOSING; + + { + // Need to protect the existence of the multiplexer. + // Multiple threads are allowed to dispose it and only + // one can succeed. But in this case here we need it + // out possibly immediately. + ExclusiveLock manager_cg(m_GlobControlLock); + CMultiplexer* mux = tryUnbindClosedSocket(s->id()); + s->m_Status = SRTS_CLOSING; + + // As the listener that contains no spawned-off accepted + // socket is being closed, it's withdrawn from the muxer. + // This is the only way how it can be checked that this + // multiplexer has lost all its sockets and therefore + // should be deleted. + + // WARNING: checkRemoveMux is like "delete this". + if (mux) + checkRemoveMux(*mux); + } // broadcast all "accept" waiting CSync::lock_notify_all(s->m_AcceptCond, s->m_AcceptLock); + + s->core().setAgentCloseReason(reason); } else { @@ -2192,7 +2666,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) // 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(); + s->closeInternal(reason); // synchronize with garbage collection. HLOGC(smlog.Debug, @@ -2213,22 +2687,40 @@ int srt::CUDTUnited::close(CUDTSocket* s) 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; + return SRT_STATUS_OK; } s = i->second; s->setClosed(); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (s->m_GroupOf) { HLOGC(smlog.Debug, - log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); + log << "@" << s->id() << " 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; + recordCloseReason(s); + + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(s->id(), s->core().m_sPollID); + + swipeSocket_LOCKED(s->id(), s, SWIPE_NOW); + + // Run right now the function that should attempt to delete the socket. + // XXX Right now it will never work because the busy lock is applied on + // the whole code calling this function, and with this lock, removal will + // never happen. + CMultiplexer* mux = tryUnbindClosedSocket(u); + if (mux && mux->tryCloseIfEmpty()) + { + // NOTE: ONLY AFTER stopping the workers can the SOCKET be deleted, + // even after moving to closed and being unbound! + SRT_ASSERT(mux->empty()); + checkRemoveMux(*mux); + } + HLOGC(smlog.Debug, log << "@" << u << "U::close: Socket MOVED TO CLOSED for collecting later."); CGlobEvent::triggerEvent(); @@ -2311,10 +2803,10 @@ int srt::CUDTUnited::close(CUDTSocket* s) */ CSync::notify_one_relaxed(m_GCStopCond); - return 0; + return SRT_STATUS_OK; } -void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) +void CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -2338,7 +2830,7 @@ void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_ *pw_namelen = len; } -void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) +void CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) { if (!pw_name || !pw_namelen) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -2362,7 +2854,40 @@ void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_ *pw_namelen = len; } -int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) +void CUDTUnited::getsockdevname(const SRTSOCKET u, char* pw_name, size_t* 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 vector& locals = GetLocalInterfaces(); + + for (vector::const_iterator i = locals.begin(); i != locals.end(); ++i) + { + if (i->addr.equal_address(s->m_SelfAddr)) + { + if (*pw_namelen < i->name.size() + 1) + throw CUDTException(MJ_NOTSUP, MN_INVAL); + memcpy((pw_name), i->name.c_str(), i->name.size()+1); + *pw_namelen = i->name.size(); + return; + } + } + + *pw_namelen = 0; // report empty one +} + +int CUDTUnited::select(std::set* readfds, std::set* writefds, std::set* exceptfds, const timeval* timeout) { const steady_clock::time_point entertime = steady_clock::now(); @@ -2425,7 +2950,7 @@ int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSE if (s->readReady() || s->m_Status == SRTS_CLOSED) { - rs.insert(s->m_SocketID); + rs.insert(s->id()); ++count; } } @@ -2437,7 +2962,7 @@ int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSE if (s->writeReady() || s->m_Status == SRTS_CLOSED) { - ws.insert(s->m_SocketID); + ws.insert(s->id()); ++count; } } @@ -2466,7 +2991,7 @@ int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSE return count; } -int srt::CUDTUnited::selectEx(const vector& fds, +int CUDTUnited::selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, @@ -2492,7 +3017,13 @@ int srt::CUDTUnited::selectEx(const vector& fds, { CUDTSocket* s = locateSocket(*i); - if ((!s) || s->core().m_bBroken || (s->m_Status == SRTS_CLOSED)) + if ((!s) + || s->core().m_bBroken + || (s->m_Status == SRTS_CLOSED) +#if SRT_ENABLE_BONDING + || s->m_GroupOf +#endif + ) { if (exceptfds) { @@ -2504,10 +3035,10 @@ int srt::CUDTUnited::selectEx(const vector& fds, if (readfds) { - if ((s->core().m_bConnected && s->core().m_pRcvBuffer->isRcvDataReady()) || + if ((s->core().m_bConnected && s->core().isRcvBufferReady()) || (s->core().m_bListening && (s->m_QueuedSockets.size() > 0))) { - readfds->push_back(s->m_SocketID); + readfds->push_back(s->id()); ++count; } } @@ -2517,7 +3048,7 @@ int srt::CUDTUnited::selectEx(const vector& fds, if (s->core().m_bConnected && (s->core().m_pSndBuffer->getCurrBufSize() < s->core().m_config.iSndBufSize)) { - writefds->push_back(s->m_SocketID); + writefds->push_back(s->id()); ++count; } } @@ -2532,64 +3063,65 @@ int srt::CUDTUnited::selectEx(const vector& fds, return count; } -int srt::CUDTUnited::epoll_create() +int CUDTUnited::epoll_create() { return m_EPoll.create(); } -int srt::CUDTUnited::epoll_clear_usocks(int eid) +void 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) +void CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { - int ret = -1; -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); - ret = m_EPoll.update_usock(eid, u, events); + m_EPoll.update_usock(eid, u, events); k.group->addEPoll(eid); - return 0; + return; } #endif - CUDTSocket* s = locateSocket(u); - if (s) - { - ret = epoll_add_usock_INTERNAL(eid, s, events); - } - else + // The call to epoll_add_usock_INTERNAL is expected + // to be called under m_GlobControlLock, so use this lock here, too. { - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + SharedLock cs (m_GlobControlLock); + CUDTSocket* s = locateSocket_LOCKED(u); + if (s) + { + 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) +void CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) { - int ret = m_EPoll.update_usock(eid, s->m_SocketID, events); + m_EPoll.update_usock(eid, s->id(), events); s->core().addEPoll(eid); - return ret; } -int srt::CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +void 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) +void 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) +void 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. @@ -2605,31 +3137,29 @@ int srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) 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; + m_EPoll.update_usock(eid, ent->id(), &no_events); } // Needed internal access! -int srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) +void 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) +#if SRT_ENABLE_BONDING +void 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) +void CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) { CUDTSocket* s = 0; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING CUDTGroup* g = 0; - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); g = k.group; @@ -2649,55 +3179,54 @@ int srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) return m_EPoll.update_usock(eid, u, &no_events); } -int srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) +void 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) +int 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) +int32_t CUDTUnited::epoll_set(int eid, int32_t flags) { return m_EPoll.setflags(eid, flags); } -int srt::CUDTUnited::epoll_release(const int eid) +void CUDTUnited::epoll_release(const int eid) { return m_EPoll.release(eid); } -srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) +CUDTSocket* CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) { SharedLock 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; + return locateSocket_LOCKED(u, erh); } // [[using locked(m_GlobControlLock)]]; -srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) +CUDTSocket* CUDTUnited::locateSocket_LOCKED(SRTSOCKET u, ErrorHandling erh) { sockets_t::iterator i = m_Sockets.find(u); if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) { - return NULL; + if (erh == ERH_RETURN) + return NULL; + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); } return i->second; } -#if ENABLE_BONDING -srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) +CMultiplexer* CUDTUnited::locateMultiplexer_LOCKED(int32_t mid) +{ + return map_getp(m_mMultiplexer, mid); +} + +#if SRT_ENABLE_BONDING +CUDTGroup* CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) { SharedLock cg(m_GlobControlLock); @@ -2714,7 +3243,7 @@ srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling e return i->second; } -srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) +CUDTGroup* CUDTUnited::acquireSocketsGroup(CUDTSocket* s) { SharedLock cg(m_GlobControlLock); CUDTGroup* g = s->m_GroupOf; @@ -2728,7 +3257,7 @@ srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) } #endif -srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh) +CUDTSocket* CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh) { SharedLock cg(m_GlobControlLock); @@ -2744,7 +3273,7 @@ srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling return s; } -bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) +bool CUDTUnited::acquireSocket(CUDTSocket* s) { // Note that before using this function you must be certain // that the socket isn't broken already and it still has at least @@ -2758,7 +3287,7 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) // Keep the lock so that no one changes anything in the meantime. // If the socket m_Status == SRTS_CLOSED (set by setClosed()), then // this socket is no longer present in the m_Sockets container - if (s->m_Status >= SRTS_BROKEN) + if (s->m_Status >= SRTS_CLOSED) { s->apiRelease(); return false; @@ -2767,7 +3296,15 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) return true; } -srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) +void CUDTUnited::releaseSocket(CUDTSocket* s) +{ + SRT_ASSERT(s && s->isStillBusy() > 0); + + SharedLock cg(m_GlobControlLock); + s->apiRelease(); +} + +CUDTSocket* CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) { SharedLock cg(m_GlobControlLock); @@ -2791,11 +3328,11 @@ srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRT return NULL; } -void srt::CUDTUnited::checkBrokenSockets() +void CUDTUnited::checkBrokenSockets() { ExclusiveLock cg(m_GlobControlLock); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING vector delgids; for (groups_t::iterator i = m_ClosedGroups.begin(); i != m_ClosedGroups.end(); ++i) @@ -2828,8 +3365,18 @@ void srt::CUDTUnited::checkBrokenSockets() for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { CUDTSocket* s = i->second; - if (!s->core().m_bBroken) + CUDT& c = s->core(); + if (!c.m_bBroken) + continue; + + if (!m_bGCClosing && !c.m_bManaged) + { + HLOGC(cnlog.Debug, log << "Socket @" << s->id() << " isn't managed and wasn't explicitly closed - NOT collecting"); continue; + } + + HLOGC(cnlog.Debug, log << "Socket @" << s->id() << " considered wiped: managed=" << + c.m_bManaged << " broken=" << c.m_bBroken << " closing=" << c.m_bClosing); if (s->m_Status == SRTS_LISTENING) { @@ -2840,12 +3387,18 @@ void srt::CUDTUnited::checkBrokenSockets() continue; } else + + // Additional note on group receiver: with the new group + // receiver m_pRcvBuffer in the socket core is NULL always, + // but that's not a problem - you can close the member socket + // safely without worrying about reading data because they are + // in the group anyway. { CUDT& u = s->core(); - enterCS(u.m_RcvBufferLock); + u.m_RcvBufferLock.lock(); bool has_avail_packets = u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets(); - leaveCS(u.m_RcvBufferLock); + u.m_RcvBufferLock.unlock(); if (has_avail_packets) { @@ -2859,34 +3412,48 @@ void srt::CUDTUnited::checkBrokenSockets() } } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (s->m_GroupOf) { HLOGC(smlog.Debug, - log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); + log << "@" << s->id() << " 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); + // Note that this will not override the value that has been already + // set by some other functionality, only set it when not yet set. + s->core().setAgentCloseReason(SRT_CLS_INTERNAL); + + recordCloseReason(s); + // 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()) + // NOTE: removal from m_SocketID POSTPONED + // to loop over removal of all from the `tbc` list + swipeSocket_LOCKED(i->first, s, SWIPE_LATER); + + if (s->m_ListenSocket != SRT_SOCKID_CONNREQ) { - ls = m_ClosedSockets.find(s->m_ListenSocket); - if (ls == m_ClosedSockets.end()) - continue; - } + // 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); + HLOGC(smlog.Debug, log << "checkBrokenSockets: removing queued socket: @" << s->id() + << " from listener @" << ls->second->id()); + ls->second->m_AcceptLock.lock(); + ls->second->m_QueuedSockets.erase(s->id()); + ls->second->m_AcceptLock.unlock(); + } } for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j) @@ -2902,7 +3469,7 @@ void srt::CUDTUnited::checkBrokenSockets() // other conditions applying on the socket that prevent it from being deleted. if (ps->isStillBusy()) { - HLOGC(smlog.Debug, log << "checkBrokenSockets: @" << ps->m_SocketID << " is still busy, SKIPPING THIS CYCLE."); + HLOGC(smlog.Debug, log << "checkBrokenSockets: @" << ps->id() << " is still busy, SKIPPING THIS CYCLE."); continue; } @@ -2915,11 +3482,15 @@ void srt::CUDTUnited::checkBrokenSockets() if ((!u.m_pSndBuffer) || (0 == u.m_pSndBuffer->getCurrBufSize()) || (u.m_tsLingerExpiration <= steady_clock::now())) { - HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED qualified @" << ps->m_SocketID); + HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED linger-expired @" << ps->id()); u.m_tsLingerExpiration = steady_clock::time_point(); u.m_bClosing = true; ps->m_tsClosureTimeStamp = steady_clock::now(); } + else + { + HLOGC(smlog.Debug, log << "checkBrokenSockets: linger; remains @" << ps->id()); + } } // timeout 1 second to destroy a socket AND it has been removed from @@ -2928,16 +3499,11 @@ void srt::CUDTUnited::checkBrokenSockets() const steady_clock::duration closed_ago = now - ps->m_tsClosureTimeStamp.load(); if (closed_ago > seconds_from(1)) { - CRNode* rnode = u.m_pRNode; - if (!rnode || !rnode->m_bOnList) - { - HLOGC(smlog.Debug, - log << "checkBrokenSockets: @" << ps->m_SocketID << " closed " - << FormatDuration(closed_ago) << " ago and removed from RcvQ - will remove"); + HLOGC(smlog.Debug, log << "checkBrokenSockets: @" << ps->id() << " 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); - } + // HLOGC(smlog.Debug, log << "will unref socket: " << j->first); + tbr.push_back(j->first); } } @@ -2947,78 +3513,142 @@ void srt::CUDTUnited::checkBrokenSockets() // remove those timeout sockets for (vector::iterator l = tbr.begin(); l != tbr.end(); ++l) - removeSocket(*l); + { + CMultiplexer* mux = tryRemoveClosedSocket(*l); + if (mux) + { + // NOTE: existing mux doesn't mean that mux is empty! + // It only means that the socket in `l` has been deleted. + checkRemoveMux(*mux); + } + } HLOGC(smlog.Debug, log << "checkBrokenSockets: after removal: m_ClosedSockets.size()=" << m_ClosedSockets.size()); } // [[using locked(m_GlobControlLock)]] -void srt::CUDTUnited::removeSocket(const SRTSOCKET u) +void CUDTUnited::closeLeakyAcceptSockets(CUDTSocket* s) +{ + ScopedLock cg(s->m_AcceptLock); + + // if it is a listener, close all un-accepted sockets in its queue + // and remove them later + for (map::iterator q = s->m_QueuedSockets.begin(); + q != s->m_QueuedSockets.end(); ++ q) + { + sockets_t::iterator si = m_Sockets.find(q->first); + if (si == m_Sockets.end()) + { + // gone in the meantime + LOGC(smlog.Error, + log << "closeLeakyAcceptSockets: IPE? socket @" << (q->first) << " being queued for listener socket @" + << s->id() << " is GONE in the meantime ???"); + continue; + } + + CUDTSocket* as = si->second; + + as->breakSocket_LOCKED(SRT_CLS_DEADLSN); + + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(as->id(), as->core().m_sPollID); + + swipeSocket_LOCKED(q->first, as, SWIPE_NOW); + } +} + +// Unbind the socket, and if it was the only user of the multiplexer, delete it +// (otherwise there would be no one to delete it later). If this is not +// possible, keep it bound and let this be repeated in the GC. The goal is to +// free the bindpoint when closing a socket, IF POSSIBLE. +// [[using locked(m_GlobControlLock)]] +CMultiplexer* CUDTUnited::tryUnbindClosedSocket(const SRTSOCKET u) { sockets_t::iterator i = m_ClosedSockets.find(u); // invalid socket ID if (i == m_ClosedSockets.end()) - return; + return NULL; - CUDTSocket* const s = i->second; + CUDTSocket* 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; + // (just in case, this should be wiped out already) + m_EPoll.wipe_usock(u, s->core().m_sPollID); - CRNode* rn = s->core().m_pRNode; - if (rn && rn->m_bOnList) - return; + // IMPORTANT!!! + // + // The order of deletion must be: first delete socket, then multiplexer. + // The socket keeps the objects of CUnit type that belong to the multiplexer's + // unit queue, so the socket must free them first before the multiplexer is deleted. + const int mid = s->m_iMuxID; + if (mid == -1) + { + HLOGC(smlog.Debug, log << CONID(u) << "has NO MUXER ASSOCIATED, ok."); + return NULL; + } + + CMultiplexer* mux = map_getp(m_mMultiplexer, mid); + if (!mux) + { + LOGC(smlog.Fatal, log << "IPE: MUXER id=" << mid << " NOT FOUND!"); + return NULL; + } + + // NOTE: This function must be obligatory called before attempting + // to call CMultiplexer::stop() - unbinding shall never happen from + // a multiplexer's worker thread; that would be a self-destruction. + if (mux->isSelfDestructAttempt()) + { + LOGC(smlog.Error, log << "tryUnbindClosedSocket: IPE: ATTEMPTING TO CALL from a worker thread - NOT REMOVING"); + return NULL; + } + + // Unpin this socket from the multiplexer. + s->m_iMuxID = -1; + mux->deleteSocket(u); + + // XXX HERE PURGE THE SENDER AND RECEIVER BUFFERS !!! + // (You can't leave socket with active buffer cells borrowed from + // multiplexer after multiplexer has been detached). + // XXX NOTE: no longer true when switched to CPacketUnitPool. + s->clearBuffers(); + + HLOGC(smlog.Debug, log << CONID(u) << "deleted from MUXER and cleared muxer ID, BUT NOT CLOSED"); + + return mux; +} + +// [[using locked(m_GlobControlLock)]] +CMultiplexer* CUDTUnited::tryRemoveClosedSocket(const SRTSOCKET u) +{ + sockets_t::iterator i = m_ClosedSockets.find(u); + + // invalid socket ID + if (i == m_ClosedSockets.end()) + return NULL; + + CUDTSocket* s = i->second; + + IF_HEAVY_LOGGING(SRTSOCKET id = s->id()); if (s->isStillBusy()) { - HLOGC(smlog.Debug, log << "@" << s->m_SocketID << " is still busy, NOT deleting"); - return; + HLOGC(smlog.Debug, log << "@" << id << " is still busy, NOT deleting"); + return NULL; } - HLOGC(smlog.Note, log << "@" << s->m_SocketID << " busy=" << s->isStillBusy()); + HLOGC(smlog.Debug, log << "@" << id << " busy=" << s->isStillBusy()); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (s->m_GroupOf) { HLOGC(smlog.Debug, - log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() << " - REMOVING FROM GROUP"); + log << "@" << id << " 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 (map::iterator q = s->m_QueuedSockets.begin(); - q != s->m_QueuedSockets.end(); ++ q) - { - sockets_t::iterator si = m_Sockets.find(q->first); - if (si == m_Sockets.end()) - { - // gone in the meantime - LOGC(smlog.Error, - log << "removeSocket: IPE? socket @" << (q->first) << " being queued for listener socket @" - << s->m_SocketID << " is GONE in the meantime ???"); - continue; - } - CUDTSocket* as = si->second; - - as->breakSocket_LOCKED(); - m_ClosedSockets[q->first] = as; - m_Sockets.erase(q->first); - } - } + closeLeakyAcceptSockets(s); // remove from peer rec map >::iterator j = m_PeerRec.find(s->getPeerSpec()); @@ -3034,10 +3664,8 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) * 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); + // (just in case, this should be wiped out already) + m_EPoll.wipe_usock(u, s->core().m_sPollID); // XXX This below section can unlock m_GlobControlLock // just for calling CUDT::closeInternal(), which is needed @@ -3049,69 +3677,180 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) // // Report: P04-1.28, P04-2.27, P04-2.50, P04-2.55 - HLOGC(smlog.Debug, log << "GC/removeSocket: closing associated UDT @" << u); - leaveCS(m_GlobControlLock); - s->core().closeInternal(); - enterCS(m_GlobControlLock); - HLOGC(smlog.Debug, log << "GC/removeSocket: DELETING SOCKET @" << u); - delete s; - HLOGC(smlog.Debug, log << "GC/removeSocket: socket @" << u << " DELETED. Checking muxer."); + HLOGC(smlog.Debug, log << "GC/tryRemoveClosedSocket: closing associated UDT @" << u); - if (mid == -1) + m_GlobControlLock.unlock(); + s->closeInternal(SRT_CLS_INTERNAL); + m_GlobControlLock.lock(); + + // Find again after re-acquired mutex + i = m_ClosedSockets.find(u); + if (i == m_ClosedSockets.end()) { - HLOGC(smlog.Debug, log << "GC/removeSocket: no muxer found, finishing."); - return; + // Someone else has deleted it already. + HLOGC(smlog.Debug, log << "@" << id << " has been deleted by other thread - exiting"); + return NULL; } - map::iterator m; - m = m_mMultiplexer.find(mid); - if (m == m_mMultiplexer.end()) + // Check again after reacquisition + if (s->isStillBusy()) { - LOGC(smlog.Fatal, log << "IPE: For socket @" << u << " MUXER id=" << mid << " NOT FOUND!"); - return; + HLOGC(smlog.Debug, log << "@" << id << " is still busy, NOT deleting"); + return NULL; } - CMultiplexer& mx = m->second; + // delete this one + // IMPORTANT!!! After erasing the entry in m_ClosedSockets + // the socket must be deleted. If deletion is by any reason not possible, + // the socket must stay in m_ClosedSockets so that the next GC cycle can + // try again. + m_ClosedSockets.erase(i); - mx.m_iRefCount--; - HLOGC(smlog.Debug, log << "unrefing underlying muxer " << mid << " for @" << u << ", ref=" << mx.m_iRefCount); - if (0 == mx.m_iRefCount) + // IMPORTANT!!! + // + // The order of deletion must be: first delete socket, then multiplexer. + // The receiver buffer shares the use of CUnits from the multiplexer's unit queue, + // which is assigned to the multiplexer because this is where the incoming + // UDP packets are placed. The receiver buffer must be first deleted and + // so unreference all CUnits. Then the multiplexer can be deleted and drag all + // CUnits with itself. + const int mid = s->m_iMuxID; + CMultiplexer* mux = NULL; + if (mid == -1) { - HLOGC(smlog.Debug, - log << "MUXER id=" << mid << " lost last socket @" << u << " - deleting muxer bound to port " - << mx.m_pChannel->bindAddressAny().hport()); + HLOGC(smlog.Debug, log << CONID(u) << "has NO MUXER ASSOCIATED, ok."); + } + else + { + mux = map_getp(m_mMultiplexer, mid); + if (!mux) + { + LOGC(smlog.Fatal, log << "IPE: MUXER id=" << mid << " NOT FOUND!"); + } + else + { + // Unpin this socket from the multiplexer. + s->m_iMuxID = -1; + mux->deleteSocket(u); + HLOGC(smlog.Debug, log << CONID(u) << "deleted from MUXER and cleared muxer ID"); + } + } + HLOGC(smlog.Debug, log << "GC/tryRemoveClosedSocket: DELETING SOCKET @" << u); + delete s; + HLOGC(smlog.Debug, log << "GC/tryRemoveClosedSocket: socket @" << u << " DELETED. Checking muxer id=" << mid); + + // If deleted a socket, this must return the multiplexer because this is the + // last moment when it can be deleted (otherwise it would be leaked). + return mux; +} + +/// Check after removal of a socket from the multiplexer if it was the +/// last one and hence the multiplexer itself should be removed. +/// +/// @param mid Muxer ID that identifies the multiplexer in the socket +/// @param u Socket ID that was the last multiplexer's user (logging only) +// [[using locked(m_GlobControlLock)]] +void CUDTUnited::checkRemoveMux(CMultiplexer& mx) +{ + const int mid = mx.id(); + HLOGC(smlog.Debug, log << "checkRemoveMux: unrefing muxer " << mid << ", with " << mx.nsockets() << " sockets"); + if (mx.empty()) + { + HLOGC(smlog.Debug, log << "MUXER id=" << mid << " lost last socket - deleting muxer bound to " + << mx.channel()->bindAddressAny().str()); // 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); + mx.setClosing(); + + if (mx.reserveDisposal()) + { + CGlobEvent::triggerEvent(); // make sure no hangs when exiting workers + HLOGC(smlog.Debug, log << "... RESERVED for disposal. Stopping threads.."); + // Disposal reserved to this thread. Now you can safely + // unlock m_GlobControlLock and be sure that no other thread + // is going to dispose this multiplexer. Some may attempt to also + // reserve disposal, but they will fail. + { + m_GlobControlLock.unlock(); + mx.stopWorkers(); + HLOGC(smlog.Debug, log << "... Worker threads stopped, reacquiring mutex.."); + m_GlobControlLock.lock(); + } + // After re-locking m_GlobControlLock we are certain + // that the privilege of deleting this multiplexer is still + // on this thread. + HLOGC(smlog.Debug, log << "... Muxer destroyed, removing"); + m_mMultiplexer.erase(mid); + } + else + { + HLOGC(smlog.Debug, log << "... NOT RESERVED to disposal, already reserved"); + // Some other thread has already reserved disposal for itself + // hence promising to dispose this multiplexer. You can safely leave + // it here. + } + } + else + { +#if HVU_ENABLE_HEAVY_LOGGING + string users = mx.nsockets() ? mx.testAllSocketsClear() : string(); + + LOGC(smlog.Debug, log << "MUXER id=" << mid << " has still " << mx.nsockets() << " users" << users); +#endif + } +} + +void CUDTUnited::checkTemporaryDatabases() +{ + ExclusiveLock cg(m_GlobControlLock); + + // It's not very efficient to collect first the keys of all + // elements to remove and then remove from the map by key. + + // In C++20 this is possible by doing + // m_ClosedDatabase.erase_if([](auto& c) { return --c.generation <= 0; }); + // but nothing equivalent in the earlier standards. + + vector expired; + + for (map::iterator c = m_ClosedDatabase.begin(); + c != m_ClosedDatabase.end(); ++c) + { + --c->second.generation; + if (c->second.generation <= 0) + expired.push_back(c->first); } + + for (vector::iterator i = expired.begin(); i != expired.end(); ++i) + m_ClosedDatabase.erase(*i); } -void srt::CUDTUnited::configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af) +// Muxer in this function is added a socket to its lists and pinning +// it into the socket, but does not modify any multiplexer's data. +void CUDTUnited::installMuxer(CUDTSocket* pw_s, CMultiplexer* fw_pm) { - 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; + pw_s->core().m_pMuxer = fw_pm; + pw_s->m_iMuxID = fw_pm->id(); + + pw_s->m_SelfAddr = fw_pm->selfAddr(); + fw_pm->addSocket(pw_s); } -uint16_t srt::CUDTUnited::installMuxer(CUDTSocket* w_s, CMultiplexer& fw_sm) +#if HVU_ENABLE_LOGGING +inline static const char* IPv6OnlyStr(int val) { - 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(); + if (val == 0) + return "IPv4+IPv6"; + if (val == 1) + return "IPv6-only"; + return "UNSET"; } +#endif -bool srt::CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, +bool CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket) { if (muxaddr.family() != AF_INET6) @@ -3123,14 +3862,19 @@ bool srt::CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSr return true; // If set explicitly, then it must be equal to the one of found muxer. - return cfgSocket.iIpV6Only == cfgMuxer.iIpV6Only; + if (cfgSocket.iIpV6Only != cfgMuxer.iIpV6Only) + { + LOGC(smlog.Error, log << "inet6SettingsCompat: incompatible IPv6: muxer=" + << IPv6OnlyStr(cfgMuxer.iIpV6Only) << " socket=" << IPv6OnlyStr(cfgSocket.iIpV6Only)); + return false; + } } // 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) +bool CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket) { if (!cfgMuxer.bReuseAddr) { @@ -3145,11 +3889,10 @@ bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, cons return false; } -void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) +void CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) { - const int port = reqaddr.hport(); ExclusiveLock 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 @@ -3157,296 +3900,279 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons // 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 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: + CMultiplexer* pmux = findSuitableMuxer(s, reqaddr); + if (pmux) + { + HLOGC(smlog.Debug, log << "bind: reusing multiplexer for " << pmux->selfAddr().str()); + // reuse the existing multiplexer + installMuxer((s), (pmux)); + return; + } + } + // We state that if the user has passed their own UDP socket, + // it is either bound already - and did so without any conflicts + // with the existing SRT socket's multiplexer - or is not bound. - // 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. + // a new multiplexer is needed + int muxid = (int32_t)s->id(); - // 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. + try + { + std::pair is = map_tryinsert(m_mMultiplexer, muxid); - bool reuse_attempt = false; - for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) + // Should be impossible, but must be prevented. + // NOTE: map::insert with a pair simply ignores the passed value, + // if the key is already found. + if (!is.second) { - CMultiplexer const& m = i->second; + LOGC(smlog.Error, log << "IPE: Trying to add multiplexer with id=" << muxid << " which is already busy"); + throw CUDTException(MJ_NOTSUP, MN_ISBOUND); + } + CMultiplexer& m = is.first; + m.configure(int32_t(s->id()), s->core().m_config, reqaddr, udpsock); + installMuxer((s), (&m)); + } + catch (const CUDTException& x) + { + HLOGC(smlog.Debug, log << "installMuxer: FAILED; removing multiplexer: ERROR #" << x.getErrorCode() + << ": " << x.getErrorMessage() << ": errno=" << x.getErrno() << ": " << hvu::SysStrError(x.getErrno())); + m_mMultiplexer.erase(muxid); + throw; + } + catch (...) + { + HLOGC(smlog.Debug, log << "installMuxer: FAILED; removing multiplexer (IPE: UNKNOWN EXCEPTION)"); + m_mMultiplexer.erase(muxid); + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } - // 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; - } + HLOGC(smlog.Debug, log << "bind: creating new multiplexer bound to " << reqaddr.str()); +} + +CMultiplexer* CUDTUnited::findSuitableMuxer(CUDTSocket* s, const sockaddr_any& reqaddr) +{ + // 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: - // 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)); + // 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 const& m = i->second; + + sockaddr_any mux_addr = m.selfAddr(); + + // Check if the address was reset. If so, this means this muxer is + // about to be deleted, so definitely don't use it. + if (mux_addr.family() == AF_UNSPEC) + continue; + + // First, we need to find a multiplexer with the same port. + if (mux_addr.hport() != port) + { HLOGC(smlog.Debug, - log << "bind: Found existing muxer @" << m.m_iID << " : " << mux_addr.str() << " - check against " - << reqaddr.str()); + log << "bind: muxer @" << m.id() << " found, but for port " << mux_addr.hport() + << " (requested port: " << port << ")"); + continue; + } + + HLOGC(smlog.Debug, + log << "bind: Found existing muxer @" << m.id() << " : " << mux_addr.str() << " - check against " + << reqaddr.str()); - if (mux_addr.isany()) + // 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. + + if (mux_addr.isany()) + { + if (mux_addr.family() == AF_INET6) { - 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.cfg().iIpV6Only == 1) { - // 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. + // 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.id() + << " is :: v6only - requested IPv4 ANY is NOT IN THE WAY. Searching on."); + continue; + } + + // Candidate is AF_INET6 - if (m.m_mcfg.iIpV6Only == 1) + if (cfgSocket.iIpV6Only != 1 || !reqaddr.isany()) { - // 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. + // 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); } - else if (m.m_mcfg.iIpV6Only == 0) + + // Otherwise, MATCH. + } + else if (m.cfg().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) { - // 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); - } + // MATCH } - else // Case -1, by unknown reason. Accept only with -1 setting, others are conflict. + else { - 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); - } + // 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 // muxer is IPv4 wildcard + else // Case -1, by unknown reason. Accept only with -1 setting, others are conflict. { - // 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.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == -1) { - 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); - } + // MATCH } - else // AF_INET6 + else { - 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); - } + 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); } } - - 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 existing 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) + else // muxer is IPv4 wildcard { - // - if the channel settings match, it can be reused - if (channelSettingsMatch(m.m_mcfg, cfgSocket) && inet6SettingsCompat(mux_addr, m.m_mcfg, reqaddr, cfgSocket)) + // 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) { - HLOGC(smlog.Debug, log << "bind: reusing multiplexer for port " << port); - // reuse the existing multiplexer - ++i->second.m_iRefCount; - installMuxer((s), (i->second)); - return; + 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 + else // AF_INET6 { - // - 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 (cfgSocket.iIpV6Only == 1 || !reqaddr.isany()) + { + // PASS + HLOGC(smlog.Debug, log << "bind: muxer @" << m.id() + << " 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); + } } } - // 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); + reuse_attempt = true; + HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable"); } - } - - - - // 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) + // Muxer address is NOT a wildcard, so conflicts only with WILDCARD of the same type + else if (reqaddr.isany() && reqaddr.family() == mux_addr.family()) { - // In this case, reqaddr contains the address - // that has been extracted already from the - // given socket - m.m_pChannel->attach(*udpsock, reqaddr); + LOGC(smlog.Error, + log << "bind: Wildcard address: " << reqaddr.str() + << " conflicts with existing IP binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } - else if (reqaddr.empty()) + // If this is bound to a certain address, AND: + else if (mux_addr.equal_address(reqaddr)) { - // 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()); + // - the address is the same as reqaddr + reuse_attempt = true; + HLOGC(smlog.Debug, log << "bind: same IP address - multiplexer reusable"); } 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); + 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. - // 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) + // If this is a reusage attempt: + if (reuse_attempt) { - // 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); + // - if the channel settings match, it can be reused + if (channelSettingsMatch(m.cfg(), cfgSocket) + && inet6SettingsCompat(mux_addr, m.cfg(), reqaddr, cfgSocket)) + { + return &i->second; + } + // - 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. - 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); - swap(m_mMultiplexer[m.m_iID],m); - } - catch (const CUDTException&) - { - m.destroy(); - throw; - } - catch (...) - { - m.destroy(); - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + // 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); } - HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << port); + HLOGC(smlog.Debug, log << "bind: No suitable multiplexer for " << reqaddr.str() << " - can go on with new one"); + + // No suitable muxer found - create a new multiplexer. + return NULL; } // This function is going to find a multiplexer for the port contained @@ -3454,14 +4180,14 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons // 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) +bool CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) { ExclusiveLock 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() + log << "updateListenerMux: finding muxer of listener socket @" << ls->id() << " muxid=" << ls->m_iMuxID + << " bound=" << ls->m_SelfAddr.str() << " FOR @" << s->id() << " addr=" << s->m_SelfAddr.str() << "_->_" << s->m_PeerAddr.str()); // First thing that should be certain here is that there should exist @@ -3490,21 +4216,20 @@ bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) { 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"); +#if HVU_ENABLE_HEAVY_LOGGING + hvu::ofmtbufstream that_muxer; + that_muxer << "id=" << m.id() << " addr=" << m.selfAddr().str(); #endif - if (m.m_iPort == port) + if (m.selfAddr().hport() == port) { - HLOGC(smlog.Debug, log << "updateListenerMux: reusing muxer: " << that_muxer.str()); - if (m.m_iIPversion == s->m_PeerAddr.family()) + HLOGC(smlog.Debug, log << "updateListenerMux: reusing muxer: " << that_muxer); + if (m.selfAddr().family() == s->m_PeerAddr.family()) { mux = &m; // best match break; } - else if (m.m_iIPversion == AF_INET6) + else if (m.selfAddr().family() == AF_INET6) { // Allowed fallback case when we only need an accepted socket. fallback = &m; @@ -3512,14 +4237,14 @@ bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) } else { - HLOGC(smlog.Debug, log << "updateListenerMux: SKIPPING muxer: " << that_muxer.str()); + HLOGC(smlog.Debug, log << "updateListenerMux: SKIPPING muxer: " << that_muxer); } } 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) + if (fallback->cfg().iIpV6Only == 0) { HLOGC(smlog.Warn, log << "updateListenerMux: reusing multiplexer from different family"); mux = fallback; @@ -3531,17 +4256,14 @@ bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) 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; + installMuxer((s), (mux)); return true; } return false; } -void* srt::CUDTUnited::garbageCollect(void* p) +void* CUDTUnited::garbageCollect(void* p) { CUDTUnited* self = (CUDTUnited*)p; @@ -3549,21 +4271,25 @@ void* srt::CUDTUnited::garbageCollect(void* p) UniqueLock gclock(self->m_GCStopLock); - while (!self->m_bClosing) + // START LIBRARY RUNNING LOOP + while (!self->m_bGCClosing) { INCREMENT_THREAD_ITERATIONS(); self->checkBrokenSockets(); + self->checkTemporaryDatabases(); HLOGC(inlog.Debug, log << "GC: sleep 1 s"); self->m_GCStopCond.wait_for(gclock, seconds_from(1)); } + // END. + THREAD_EXIT(); return NULL; } //////////////////////////////////////////////////////////////////////////////// -int srt::CUDT::startup() +SRTRUNSTATUS CUDT::startup() { #if HAVE_PTHREAD_ATFORK static bool registered = false; @@ -3576,12 +4302,12 @@ int srt::CUDT::startup() return uglobal().startup(); } -int srt::CUDT::cleanup() +SRTSTATUS CUDT::cleanup() { return uglobal().cleanup(); } -int srt::CUDT::cleanupAtFork() +int CUDT::cleanupAtFork() { CUDTUnited &context = uglobal(); context.cleanupAtFork(); @@ -3590,7 +4316,7 @@ int srt::CUDT::cleanupAtFork() return context.startup(); } -SRTSOCKET srt::CUDT::socket() +SRTSOCKET CUDT::socket() { try { @@ -3598,51 +4324,58 @@ SRTSOCKET srt::CUDT::socket() } catch (const CUDTException& e) { - SetThreadLocalError(e); - return INVALID_SOCK; + APIError a(e); + return SRT_INVALID_SOCK; } catch (const bad_alloc&) { - SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; + APIError a(MJ_SYSTEMRES, MN_MEMORY, 0); + return SRT_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; + APIError a(MJ_UNKNOWN, MN_NONE, 0); + return SRT_INVALID_SOCK; } } -srt::CUDT::APIError::APIError(const CUDTException& e) +CUDT::APIError::APIError(const CUDTException& e) { SetThreadLocalError(e); } -srt::CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) +CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) { SetThreadLocalError(CUDTException(mj, mn, syserr)); } -#if ENABLE_BONDING +CUDT::APIError::APIError(int errorcode) +{ + CodeMajor mj = CodeMajor(errorcode / 1000); + CodeMinor mn = CodeMinor(errorcode % 1000); + SetThreadLocalError(CUDTException(mj, mn, 0)); +} + +#if SRT_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) +CUDTGroup& CUDTUnited::newGroup(const int type) { - const SRTSOCKET id = uglobal().generateSocketID(true); + const SRTSOCKET id = generateSocketID(true); // Now map the group - return uglobal().addGroup(id, SRT_GROUP_TYPE(type)).set_id(id); + return addGroup(id, SRT_GROUP_TYPE(type)).set_id(id); } -SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) +SRTSOCKET CUDT::createGroup(SRT_GROUP_TYPE gt) { try { - srt::sync::ExclusiveLock globlock(uglobal().m_GlobControlLock); - return newGroup(gt).id(); + sync::ExclusiveLock globlock(uglobal().m_GlobControlLock); + return uglobal().newGroup(gt).id(); // Note: potentially, after this function exits, the group // could be deleted, immediately, from a separate thread (though // unlikely because the other thread would need some handle to @@ -3651,19 +4384,17 @@ SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (...) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } - - return SRT_INVALID_SOCK; } // [[using locked(m_ControlLock)]] // [[using locked(CUDT::s_UDTUnited.m_GlobControlLock)]] -void srt::CUDTSocket::removeFromGroup(bool broken) +void CUDTSocket::removeFromGroup(bool broken) { CUDTGroup* g = m_GroupOf; if (g) @@ -3676,7 +4407,7 @@ void srt::CUDTSocket::removeFromGroup(bool broken) m_GroupOf = NULL; m_GroupMemberData = NULL; - bool still_have = g->remove(m_SocketID); + bool still_have = g->remove(id()); if (broken) { // Activate the SRT_EPOLL_UPDATE event on the group @@ -3688,26 +4419,26 @@ void srt::CUDTSocket::removeFromGroup(bool broken) } HLOGC(smlog.Debug, - log << "removeFromGroup: socket @" << m_SocketID << " NO LONGER A MEMBER of $" << g->id() << "; group is " + log << "removeFromGroup: socket @" << id() << " NO LONGER A MEMBER of $" << g->id() << "; group is " << (still_have ? "still ACTIVE" : "now EMPTY")); } } -SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) +SRTSOCKET CUDT::getGroupOfSocket(SRTSOCKET socket) { // Lock this for the whole function as we need the group // to persist the call. SharedLock glock(uglobal().m_GlobControlLock); CUDTSocket* s = uglobal().locateSocket_LOCKED(socket); if (!s || !s->m_GroupOf) - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; return s->m_GroupOf->id(); } -int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) { - if ((groupid & SRTGROUP_MASK) == 0 || !psize) + if (!CUDT::isgroup(groupid) || !psize) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } @@ -3723,7 +4454,7 @@ int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* } #endif -int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) +SRTSTATUS CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) { try { @@ -3757,7 +4488,7 @@ int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) } } -int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) +SRTSTATUS CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) { try { @@ -3782,7 +4513,7 @@ int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) } } -int srt::CUDT::listen(SRTSOCKET u, int backlog) +SRTSTATUS CUDT::listen(SRTSOCKET u, int backlog) { try { @@ -3803,7 +4534,7 @@ int srt::CUDT::listen(SRTSOCKET u, int backlog) } } -SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) +SRTSOCKET CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) { try { @@ -3812,22 +4543,22 @@ SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t catch (const CUDTException& e) { SetThreadLocalError(e); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } catch (bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; + return SRT_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; + return SRT_INVALID_SOCK; } } -SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) +SRTSOCKET CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) { try { @@ -3836,22 +4567,22 @@ SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) catch (const CUDTException& e) { SetThreadLocalError(e); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } catch (const bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; + return SRT_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; + return SRT_INVALID_SOCK; } } -int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) +SRTSOCKET CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) { try { @@ -3859,29 +4590,29 @@ int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0), SRT_INVALID_SOCK; } } -#if ENABLE_BONDING -int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) +#if SRT_ENABLE_BONDING +SRTSOCKET CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) { if (arraysize <= 0) - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; - if ((grp & SRTGROUP_MASK) == 0) + if (!CUDT::isgroup(grp)) { // connectLinks accepts only GROUP id, not socket id. - return APIError(MJ_NOTSUP, MN_SIDINVAL, 0); + return APIError(MJ_NOTSUP, MN_SIDINVAL, 0), SRT_INVALID_SOCK; } try @@ -3891,21 +4622,21 @@ int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int ar } catch (CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0), SRT_INVALID_SOCK; } } #endif -int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { try { @@ -3913,24 +4644,24 @@ int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t f } catch (const CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0), SRT_INVALID_SOCK; } } -int srt::CUDT::close(SRTSOCKET u) +SRTSTATUS CUDT::close(SRTSOCKET u, int reason) { try { - return uglobal().close(u); + return uglobal().close(u, reason); } catch (const CUDTException& e) { @@ -3943,12 +4674,12 @@ int srt::CUDT::close(SRTSOCKET u) } } -int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) +SRTSTATUS CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) { try { uglobal().getpeername(u, name, namelen); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -3961,12 +4692,30 @@ int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) } } -int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) +SRTSTATUS CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) { try { uglobal().getsockname(u, name, namelen); - return 0; + return SRT_STATUS_OK; + } + 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); + } +} + +SRTSTATUS CUDT::getsockdevname(SRTSOCKET u, char* name, size_t* namelen) +{ + try + { + uglobal().getsockdevname(u, name, namelen); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -3979,7 +4728,7 @@ int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) } } -int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) +SRTSTATUS CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) { if (!pw_optval || !pw_optlen) { @@ -3988,18 +4737,18 @@ int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval try { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); k.group->getOpt(optname, (pw_optval), (*pw_optlen)); - return 0; + return SRT_STATUS_OK; } #endif CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); udt.getOpt(optname, (pw_optval), (*pw_optlen)); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4012,25 +4761,25 @@ int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval } } -int srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) +SRTSTATUS CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) { if (!optval || optlen < 0) return APIError(MJ_NOTSUP, MN_INVAL, 0); try { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); k.group->setOpt(optname, optval, optlen); - return 0; + return SRT_STATUS_OK; } #endif CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); udt.setOpt(optname, optval, optlen); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4043,7 +4792,7 @@ int srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* opt } } -int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int) +int CUDT::send(SRTSOCKET u, const char* buf, int len, int) { SRT_MSGCTRL mctrl = srt_msgctrl_default; return sendmsg2(u, buf, len, (mctrl)); @@ -4051,7 +4800,7 @@ int srt::CUDT::send(SRTSOCKET u, const char* buf, int len, int) // --> CUDT::recv moved down -int srt::CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) +int 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; @@ -4060,12 +4809,12 @@ int srt::CUDT::sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inor return sendmsg2(u, buf, len, (mctrl)); } -int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) +int CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) { try { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); return k.group->send(buf, len, (w_m)); @@ -4076,27 +4825,27 @@ int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::recv(SRTSOCKET u, char* buf, int len, int) +int CUDT::recv(SRTSOCKET u, char* buf, int len, int) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int ret = recvmsg2(u, buf, len, (mctrl)); return ret; } -int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) +int CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int ret = recvmsg2(u, buf, len, (mctrl)); @@ -4104,12 +4853,12 @@ int srt::CUDT::recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) return ret; } -int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) +int CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) { try { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); return k.group->recv(buf, len, (w_m)); @@ -4120,16 +4869,16 @@ int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) +int64_t CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) { try { @@ -4138,20 +4887,20 @@ int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "sendfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) +int64_t CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) { try { @@ -4159,20 +4908,20 @@ int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "recvfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) +int CUDT::select(int, std::set* readfds, std::set* writefds, std::set* exceptfds, const timeval* timeout) { if ((!readfds) && (!writefds) && (!exceptfds)) { - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } try @@ -4181,20 +4930,20 @@ int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "select: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::selectEx(const vector& fds, +int CUDT::selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, @@ -4202,7 +4951,7 @@ int srt::CUDT::selectEx(const vector& fds, { if ((!readfds) && (!writefds) && (!exceptfds)) { - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } try @@ -4211,20 +4960,20 @@ int srt::CUDT::selectEx(const vector& fds, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "selectEx: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN); + return APIError(MJ_UNKNOWN).as(); } } -int srt::CUDT::epoll_create() +int CUDT::epoll_create() { try { @@ -4232,20 +4981,21 @@ int srt::CUDT::epoll_create() } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } 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); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::epoll_clear_usocks(int eid) +SRTSTATUS CUDT::epoll_clear_usocks(int eid) { try { - return uglobal().epoll_clear_usocks(eid); + uglobal().epoll_clear_usocks(eid); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4259,11 +5009,12 @@ int srt::CUDT::epoll_clear_usocks(int eid) } } -int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) +SRTSTATUS CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { try { - return uglobal().epoll_add_usock(eid, u, events); + uglobal().epoll_add_usock(eid, u, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4276,11 +5027,12 @@ int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* even } } -int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +SRTSTATUS CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { try { - return uglobal().epoll_add_ssock(eid, s, events); + uglobal().epoll_add_ssock(eid, s, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4293,11 +5045,12 @@ int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* even } } -int srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) +SRTSTATUS CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) { try { - return uglobal().epoll_add_usock(eid, u, events); + uglobal().epoll_add_usock(eid, u, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4311,11 +5064,12 @@ int srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* e } } -int srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +SRTSTATUS CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) { try { - return uglobal().epoll_update_ssock(eid, s, events); + uglobal().epoll_update_ssock(eid, s, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4329,11 +5083,12 @@ int srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* e } } -int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) +SRTSTATUS CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) { try { - return uglobal().epoll_remove_usock(eid, u); + uglobal().epoll_remove_usock(eid, u); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4347,11 +5102,12 @@ int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) } } -int srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) +SRTSTATUS CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) { try { - return uglobal().epoll_remove_ssock(eid, s); + uglobal().epoll_remove_ssock(eid, s); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4365,7 +5121,7 @@ int srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) } } -int srt::CUDT::epoll_wait(const int eid, +int CUDT::epoll_wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, @@ -4378,16 +5134,16 @@ int srt::CUDT::epoll_wait(const int eid, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } 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); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) +int CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { try { @@ -4395,16 +5151,16 @@ int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } 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); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int32_t srt::CUDT::epoll_set(const int eid, int32_t flags) +int32_t CUDT::epoll_set(const int eid, int32_t flags) { try { @@ -4412,20 +5168,21 @@ int32_t srt::CUDT::epoll_set(const int eid, int32_t flags) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } 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); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::epoll_release(const int eid) +SRTSTATUS CUDT::epoll_release(const int eid) { try { - return uglobal().epoll_release(eid); + uglobal().epoll_release(eid); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4438,15 +5195,15 @@ int srt::CUDT::epoll_release(const int eid) } } -srt::CUDTException& srt::CUDT::getlasterror() +CUDTException& CUDT::getlasterror() { return GetThreadLocalError(); } -int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) +SRTSTATUS CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) { -#if ENABLE_BONDING - if (u & SRTGROUP_MASK) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) return groupsockbstats(u, perf, clear); #endif @@ -4454,7 +5211,7 @@ int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instanta { CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); udt.bstats(perf, clear, instantaneous); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4467,30 +5224,30 @@ int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instanta } } -#if ENABLE_BONDING -int srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) +#if SRT_ENABLE_BONDING +SRTSTATUS CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) { try { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); k.group->bstatsSocket(perf, clear); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { SetThreadLocalError(e); - return ERROR; + return SRT_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; + return SRT_ERROR; } } #endif -srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u) +CUDT* CUDT::getUDTHandle(SRTSOCKET u) { try { @@ -4509,22 +5266,12 @@ srt::CUDT* srt::CUDT::getUDTHandle(SRTSOCKET u) } } -vector srt::CUDT::existingSockets() -{ - vector out; - 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 srt::CUDT::getsockstate(SRTSOCKET u) +SRT_SOCKSTATUS CUDT::getsockstate(SRTSOCKET u) { try { -#if ENABLE_BONDING - if (isgroup(u)) +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); return k.group->getStatus(); @@ -4545,192 +5292,71 @@ SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) } } -//////////////////////////////////////////////////////////////////////////////// - -namespace UDT -{ - -int startup() -{ - return srt::CUDT::startup(); -} - -int cleanup() -{ - return srt::CUDT::cleanup(); -} - -int bind(SRTSOCKET u, const struct sockaddr* name, int namelen) -{ - return srt::CUDT::bind(u, name, namelen); -} - -int bind2(SRTSOCKET u, UDPSOCKET udpsock) -{ - return srt::CUDT::bind(u, udpsock); -} - -int listen(SRTSOCKET u, int backlog) -{ - return srt::CUDT::listen(u, backlog); -} - -SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen) -{ - return srt::CUDT::accept(u, addr, addrlen); -} - -int connect(SRTSOCKET u, const struct sockaddr* name, int namelen) -{ - return srt::CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); -} - -int close(SRTSOCKET u) -{ - return srt::CUDT::close(u); -} - -int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen) -{ - return srt::CUDT::getpeername(u, name, namelen); -} - -int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen) -{ - return srt::CUDT::getsockname(u, name, namelen); -} - -int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen) -{ - return srt::CUDT::getsockopt(u, level, optname, optval, optlen); -} - -int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int 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) -{ - return srt::CUDT::connect(u, name, namelen, forced_isn); -} - -int send(SRTSOCKET u, const char* buf, int len, int flags) -{ - return srt::CUDT::send(u, buf, len, flags); -} - -int recv(SRTSOCKET u, char* buf, int len, int flags) -{ - return srt::CUDT::recv(u, buf, len, flags); -} - -int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl, bool inorder, int64_t srctime) -{ - return srt::CUDT::sendmsg(u, buf, len, ttl, inorder, srctime); -} - -int recvmsg(SRTSOCKET u, char* buf, int len, int64_t& srctime) -{ - return srt::CUDT::recvmsg(u, buf, len, srctime); -} - -int recvmsg(SRTSOCKET u, char* buf, int len) +int CUDT::getMaxPayloadSize(SRTSOCKET id) { - int64_t srctime; - return srt::CUDT::recvmsg(u, buf, len, srctime); + return uglobal().getMaxPayloadSize(id); } -int64_t sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) +int CUDTUnited::getMaxPayloadSize(SRTSOCKET id) { - return srt::CUDT::sendfile(u, ifs, offset, size, block); -} - -int64_t recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int 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) -{ - fstream ifs(path, ios::binary | ios::in); - int64_t ret = srt::CUDT::sendfile(u, ifs, *offset, size, block); - ifs.close(); - return ret; -} + CUDTSocket* s = locateSocket(id); + if (!s) + { + return CUDT::APIError(MJ_NOTSUP, MN_SIDINVAL).as(); + } -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 = srt::CUDT::recvfile(u, ofs, *offset, size, block); - ofs.close(); - return ret; -} + if (s->m_SelfAddr.family() == AF_UNSPEC) + { + return CUDT::APIError(MJ_NOTSUP, MN_ISUNBOUND).as(); + } -int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout) -{ - return srt::CUDT::select(nfds, readfds, writefds, exceptfds, timeout); -} + int fam = s->m_SelfAddr.family(); + CUDT& u = s->core(); -int selectEx(const vector& fds, - vector* readfds, - vector* writefds, - vector* exceptfds, - int64_t msTimeOut) -{ - return srt::CUDT::selectEx(fds, readfds, writefds, exceptfds, msTimeOut); -} + std::string errmsg; + int extra = u.m_config.extraPayloadReserve((errmsg)); + if (extra == -1) + { + LOGP(aclog.Error, errmsg); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL).as(); + } -int epoll_create() -{ - return srt::CUDT::epoll_create(); -} + // Prefer transfer IP version, if defined. This is defined after + // the connection is established. Note that the call is rejected + // if the socket isn't bound, be it explicitly or implicitly by + // calling srt_connect(). + if (u.m_TransferIPVersion != AF_UNSPEC) + fam = u.m_TransferIPVersion; -int epoll_clear_usocks(int eid) -{ - return srt::CUDT::epoll_clear_usocks(eid); -} + int payload_size = u.m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(fam) - extra; -int epoll_add_usock(int eid, SRTSOCKET u, const int* events) -{ - return srt::CUDT::epoll_add_usock(eid, u, events); + return payload_size; } -int epoll_add_ssock(int eid, SYSSOCKET s, const int* events) +string CUDTUnited::testSocketsClear() { - return srt::CUDT::epoll_add_ssock(eid, s, events); -} + std::ostringstream out; -int epoll_update_usock(int eid, SRTSOCKET u, const int* events) -{ - return srt::CUDT::epoll_update_usock(eid, u, events); -} + SharedLock lk (m_GlobControlLock); -int epoll_update_ssock(int eid, SYSSOCKET s, const int* events) -{ - return srt::CUDT::epoll_update_ssock(eid, s, events); -} + // The multiplexer should be empty, but even if it isn't by some reason + // (some sockets were not yet wiped out by gc), it should contain empty its own containers. + for (std::map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) + { + std::string remain = i->second.testAllSocketsClear(); + if (!remain.empty()) + out << " *" << remain << "*"; -int epoll_remove_usock(int eid, SRTSOCKET u) -{ - return srt::CUDT::epoll_remove_usock(eid, u); -} + if (!i->second.empty()) + out << " ^DANG^" << i->second.id() << "^"; + } -int epoll_remove_ssock(int eid, SYSSOCKET s) -{ - return srt::CUDT::epoll_remove_ssock(eid, s); -} + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + out << " !" << i->first; + } -int epoll_wait(int eid, - set* readfds, - set* writefds, - int64_t msTimeOut, - set* lrfds, - set* lwfds) -{ - return srt::CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); + return out.str(); } template @@ -4752,7 +5378,7 @@ inline void set_result(set* val, int* num, SOCKTYPE* fds) } } -int epoll_wait2(int eid, +int CUDT::epoll_wait2(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, @@ -4786,7 +5412,7 @@ int epoll_wait2(int eid, if ((lwfds != NULL) && (lwnum != NULL)) lwval = &lwset; - int ret = srt::CUDT::epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval); + int ret = epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval); if (ret > 0) { // set::const_iterator i; @@ -4804,108 +5430,47 @@ int epoll_wait2(int eid, return ret; } -int epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) -{ - return srt::CUDT::epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); -} - -int epoll_release(int eid) -{ - return srt::CUDT::epoll_release(eid); -} - -ERRORINFO& getlasterror() -{ - return srt::CUDT::getlasterror(); -} - -int getlasterror_code() -{ - return srt::CUDT::getlasterror().getErrorCode(); -} - -const char* getlasterror_desc() -{ - return srt::CUDT::getlasterror().getErrorMessage(); -} - -int getlasterror_errno() -{ - return srt::CUDT::getlasterror().getErrno(); -} - -// Get error string of a given error code -const char* geterror_desc(int code, int err) -{ - srt::CUDTException e(CodeMajor(code / 1000), CodeMinor(code % 1000), err); - return (e.getErrorMessage()); -} - -int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear) -{ - return srt::CUDT::bstats(u, perf, clear); -} - -SRT_SOCKSTATUS getsockstate(SRTSOCKET u) +void setloglevel(hvu::logging::LogLevel::type ll) { - return srt::CUDT::getsockstate(u); + srt::logging::logger_config().set_maxlevel(ll); } -} // namespace UDT - -namespace srt -{ - -void setloglevel(LogLevel::type ll) +void addlogfa(int fa) { - ScopedLock gg(srt_logger_config.mutex); - srt_logger_config.max_level = ll; + srt_addlogfa(fa); } -void addlogfa(LogFA fa) +void dellogfa(int fa) { - ScopedLock gg(srt_logger_config.mutex); - srt_logger_config.enabled_fa.set(fa, true); + srt_dellogfa(fa); } -void dellogfa(LogFA fa) +void resetlogfa(set fas) { - ScopedLock gg(srt_logger_config.mutex); - srt_logger_config.enabled_fa.set(fa, false); -} + std::vector faval; + std::copy(fas.begin(), fas.end(), std::back_inserter(faval)); -void resetlogfa(set fas) -{ - 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)); + srt_resetlogfa(&faval[0], faval.size()); } void resetlogfa(const int* fara, size_t fara_size) { - 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); + srt_resetlogfa(fara, fara_size); } void setlogstream(std::ostream& stream) { - ScopedLock gg(srt_logger_config.mutex); - srt_logger_config.log_stream = &stream; + srt::logging::logger_config().set_stream(stream); } -void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) +void setloghandler(void* opaque, HVU_LOG_HANDLER_FN* handler) { - ScopedLock gg(srt_logger_config.mutex); - srt_logger_config.loghandler_opaque = opaque; - srt_logger_config.loghandler_fn = handler; + srt::logging::logger_config().set_handler(opaque, handler); } void setlogflags(int flags) { - ScopedLock gg(srt_logger_config.mutex); - srt_logger_config.flags = flags; + srt::logging::logger_config().set_flags(flags); } SRT_API bool setstreamid(SRTSOCKET u, const std::string& sid) @@ -4922,7 +5487,7 @@ int getrejectreason(SRTSOCKET u) return CUDT::rejectReason(u); } -int setrejectreason(SRTSOCKET u, int value) +SRTSTATUS setrejectreason(SRTSOCKET u, int value) { return CUDT::rejectReason(u, value); } diff --git a/srtcore/api.h b/srtcore/api.h index 9f5e560bc..fe08947c5 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -57,14 +57,13 @@ modified by #include #include #include "netinet_any.h" -#include "udt.h" #include "packet.h" #include "queue.h" #include "cache.h" #include "epoll.h" #include "handshake.h" #include "core.h" -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING #include "group.h" #endif @@ -83,10 +82,8 @@ class CUDTSocket public: CUDTSocket() : m_Status(SRTS_INIT) - , m_SocketID(0) - , m_ListenSocket(0) - , m_PeerID(0) -#if ENABLE_BONDING + , m_ListenSocket(SRT_SOCKID_CONNREQ) +#if SRT_ENABLE_BONDING , m_GroupMemberData() , m_GroupOf() #endif @@ -102,10 +99,8 @@ class CUDTSocket CUDTSocket(const CUDTSocket& ancestor) : m_Status(SRTS_INIT) - , m_SocketID(0) - , m_ListenSocket(0) - , m_PeerID(0) -#if ENABLE_BONDING + , m_ListenSocket(SRT_SOCKID_CONNREQ) +#if SRT_ENABLE_BONDING , m_GroupMemberData() , m_GroupOf() #endif @@ -125,10 +120,10 @@ class CUDTSocket void construct(); private: - srt::sync::atomic m_iBusy; + sync::atomic m_iBusy; public: - void apiAcquire() { ++m_iBusy; } - void apiRelease() { --m_iBusy; } + int apiAcquire(); + int apiRelease(); int isStillBusy() const { @@ -136,7 +131,10 @@ class CUDTSocket } - SRT_ATTR_GUARDED_BY(m_ControlLock) + // Controversial whether it should stand. This lock is mainly + // for API things connected to this socket, while status is also + // set as atomic to allow multi-thread access. + // SRT_TSA_GUARDED_BY(m_ControlLock) sync::atomic m_Status; //< current socket state /// Time when the socket is closed. @@ -148,14 +146,14 @@ class CUDTSocket sync::AtomicClock m_tsClosureTimeStamp; sockaddr_any m_SelfAddr; //< local address of the socket + + // XXX THIS FIELD IS DUPLICATED IN CUDT!!! sockaddr_any m_PeerAddr; //< peer address of the socket - SRTSOCKET m_SocketID; //< socket ID SRTSOCKET m_ListenSocket; //< ID of the listener socket; 0 means this is an independent socket - 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 +#if SRT_ENABLE_BONDING + sync::atomic 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 @@ -165,6 +163,16 @@ class CUDTSocket CUDT m_UDT; //< internal SRT socket logic public: + // This needs to remove all packets from the buffers. + // This is necessary to cut ties to the borrowed buffer units + // from the multiplexer. + void clearBuffers() + { + m_UDT.clearBuffers(); + } + + SRTSOCKET id() const { return m_UDT.id(); } + std::map m_QueuedSockets; //< set of connections waiting for accept() sync::Condition m_AcceptCond; //< used to block "accept" call @@ -172,6 +180,11 @@ class CUDTSocket unsigned int m_uiBackLog; //< maximum number of connections in queue + // NOTE: Can't apply TSA attribute here because it is dependent + // on definitions not available at the moment. + // [[using locked_shared(CUDT::uglobal().m_GlobControlLock)]] + SRT_EPOLL_T getListenerEvents(); + // XXX A refactoring might be needed here. // There are no reasons found why the socket can't contain a list iterator to a @@ -190,8 +203,8 @@ class CUDTSocket CUDT& core() { return m_UDT; } const CUDT& core() const { return m_UDT; } - static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) { return (int64_t(id) << 30) + isn; } - int64_t getPeerSpec() { return getPeerSpec(m_PeerID, m_iISN); } + static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) { return (int64_t(int32_t(id)) << 30) + isn; } + int64_t getPeerSpec() { return getPeerSpec(core().m_PeerID, m_iISN); } SRT_SOCKSTATUS getStatus(); @@ -200,7 +213,7 @@ class CUDTSocket /// 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(); + void breakSocket_LOCKED(int reason); /// This makes the socket no longer capable of performing any transmission /// operation, but continues to be responsive in the connection in order @@ -216,9 +229,12 @@ class CUDTSocket core().m_bClosing = true; } + bool closeInternal(int reason) ATR_NOEXCEPT; + void setBreaking() { core().m_bBreaking = true; + core().notifyBlockingConnect(); } /// This does the same as setClosed, plus sets the m_bBroken to true. @@ -227,12 +243,15 @@ class CUDTSocket void setBrokenClosed(); void removeFromGroup(bool broken); + void breakNonAcceptedSockets(); + // Instrumentally used by select() and also required for non-blocking // mode check in groups - bool readReady(); + bool readReady() const; bool writeReady() const; bool broken() const; + private: CUDTSocket& operator=(const CUDTSocket&); }; @@ -243,7 +262,8 @@ class CUDTUnited { friend class CUDT; friend class CUDTGroup; - friend class CRendezvousQueue; + friend class CRcvQueue; + friend class CRcvBuffer; friend class CCryptoControl; public: @@ -252,6 +272,8 @@ class CUDTUnited // Public constants static const int32_t MAX_SOCKET_VAL = SRTGROUP_MASK - 1; // maximum value for a regular socket + static const int MAX_CLOSE_RECORD_TTL = 10; + static const size_t MAX_CLOSE_RECORD_SIZE = 10; public: enum ErrorHandling @@ -264,17 +286,32 @@ class CUDTUnited /// initialize the UDT library. /// @return 0 if success, otherwise -1 is returned. - int startup(); + SRTRUNSTATUS startup(); /// release the UDT library. /// @return 0 if success, otherwise -1 is returned. - int cleanup(); + SRTSTATUS cleanup(); + + SRT_TSA_DISABLED int cleanupAtFork(); /// 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); + SRTSOCKET newSocket(CUDTSocket** pps = NULL, bool managed = false); + + enum SwipeSocketTerm { SWIPE_NOW = 0, SWIPE_LATER = 1 }; + /// Removes the socket from the global socket container + /// and place it in the socket trashcan. The socket should + /// remain there until all still pending activities are + /// finished and there are no more users of this socket. + /// Note that the swiped socket is no longer dispatchable + /// by id. + /// @param id socket ID to swipe. + /// @param s pointer to the socket to swipe. + /// @param action only add to closed list or remove completely + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + void swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, SwipeSocketTerm); /// Create (listener-side) a new socket associated with the incoming connection request. /// @param [in] listen the listening socket ID. @@ -293,8 +330,16 @@ class CUDTUnited 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); +#if SRT_ENABLE_BONDING + SRT_TSA_NEEDS_LOCKED_SHARED(m_GlobControlLock) + int checkQueuedSocketsEvents(const std::map& sockets); + + SRT_TSA_NEEDS_LOCKED_SHARED(m_GlobControlLock) + void removePendingForGroup(const CUDTGroup* g, const std::vector& listeners, SRTSOCKET this_socket); +#endif + + SRTSTATUS installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); + SRTSTATUS 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. @@ -303,48 +348,54 @@ class CUDTUnited // socket APIs - int bind(CUDTSocket* u, const sockaddr_any& name); - int bind(CUDTSocket* u, UDPSOCKET udpsock); - int listen(const SRTSOCKET u, int backlog); + SRTSTATUS bind(CUDTSocket* u, const sockaddr_any& name); + SRTSTATUS bind(CUDTSocket* u, UDPSOCKET udpsock); + SRTSTATUS 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); + SRTSOCKET connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int tarlen); + SRTSOCKET connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + void connectIn(CUDTSocket* s, const sockaddr_any& target, int32_t forced_isn); +#if SRT_ENABLE_BONDING + SRTSOCKET groupConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG targets[], int arraysize); + SRTSOCKET singleMemberConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG* target); #endif - int close(const SRTSOCKET u); - int close(CUDTSocket* s); + SRTSTATUS close(const SRTSOCKET u, int reason); + SRTSTATUS close(CUDTSocket* s, int reason); 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); + void getsockdevname(const SRTSOCKET u, char* name, size_t* namelen); + int select(std::set* readfds, std::set* writefds, std::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_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); + void epoll_clear_usocks(int eid); + void epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + + // Uncertain. This function requires that `s` not get deleted while calling, + // but containers need not be locked, if this can be ensured by other means. + //SRT_TSA_NEEDS_LOCKED_SHARED(m_GlobControlLock) + void epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events); + void epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + void 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); + void epoll_remove_entity(const int eid, EntityType* ent); + void epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* ent); +#if SRT_ENABLE_BONDING + void 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); + void epoll_remove_ssock(const int eid, const SYSSOCKET s); + void 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); + void epoll_release(const int eid); -#if ENABLE_BONDING - SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_GlobControlLock) +#if SRT_ENABLE_BONDING + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) CUDTGroup& addGroup(SRTSOCKET id, SRT_GROUP_TYPE type) { // This only ensures that the element exists. @@ -363,10 +414,19 @@ class CUDTUnited return *g; } + // 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 cross-interface conflicts. + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + CUDTGroup& newGroup(const int type); + + SRT_TSA_NEEDS_NONLOCKED(m_GlobControlLock) void deleteGroup(CUDTGroup* g); + + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) void deleteGroup_LOCKED(CUDTGroup* g); - SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_GlobControlLock) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) CUDTGroup* findPeerGroup_LOCKED(SRTSOCKET peergroup) { for (groups_t::iterator i = m_Groups.begin(); i != m_Groups.end(); ++i) @@ -380,6 +440,31 @@ class CUDTUnited CEPoll& epoll_ref() { return m_EPoll; } + std::string testSocketsClear(); + + // Debug/development support + std::vector getSockets() + { + sync::SharedLock locked(m_GlobControlLock); + + std::vector output; + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + output.push_back(i->first); + + return output; + } + + std::vector getClosedSockets() + { + sync::SharedLock locked(m_GlobControlLock); + + std::vector output; + for (sockets_t::iterator i = m_ClosedSockets.begin(); i != m_ClosedSockets.end(); ++i) + output.push_back(i->first); + + return output; + } + private: /// Generates a new socket ID. This function starts from a randomly /// generated value (at initialization time) and goes backward with @@ -403,40 +488,56 @@ class CUDTUnited /// @throw CUDTException if after rolling over all possible ID values nothing can be returned SRTSOCKET generateSocketID(bool group = false); -private: +public: typedef std::map sockets_t; // stores all the socket structures - SRT_ATTR_GUARDED_BY(m_GlobControlLock) +private: + SRT_TSA_GUARDED_BY(m_GlobControlLock) sockets_t m_Sockets; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING typedef std::map groups_t; - SRT_ATTR_GUARDED_BY(m_GlobControlLock) + SRT_TSA_GUARDED_BY(m_GlobControlLock) groups_t m_Groups; #endif + // XXX Desired, but blocked because the older clang compilers + // do not handle this declaration correctly. Unblock in devel builds + // for checking. + // SRT_TSA_LOCK_ORDERS_AFTER(CUDT::m_ConnectionLock) + SRT_TSA_LOCK_ORDERS_BEFORE(CMultiplexer::m_SocketsLock) sync::SharedMutex m_GlobControlLock; // used to synchronize UDT API sync::Mutex m_IDLock; // used to synchronize ID generation - SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID - SRTSOCKET m_SocketIDGenerator_init; // Keeps track of the very first one + int32_t m_SocketIDGenerator; // seed to generate a new unique socket ID + int32_t m_SocketIDGenerator_init; // Keeps track of the very first one - SRT_ATTR_GUARDED_BY(m_GlobControlLock) + SRT_TSA_GUARDED_BY(m_GlobControlLock) std::map > m_PeerRec; // record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn private: friend struct FLookupSocketWithEvent_LOCKED; + SRT_TSA_NEEDS_NONLOCKED(m_GlobControlLock) 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); + SRT_TSA_NEEDS_LOCKED_SHARED(m_GlobControlLock) + CUDTSocket* locateSocket_LOCKED(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); CUDTSocket* locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn); -#if ENABLE_BONDING + SRT_TSA_NEEDS_LOCKED_SHARED(m_GlobControlLock) + CMultiplexer* locateMultiplexer_LOCKED(int32_t muxid); + + int getMaxPayloadSize(SRTSOCKET u); + +#if SRT_ENABLE_BONDING + SRT_TSA_NEEDS_NONLOCKED(m_GlobControlLock) CUDTGroup* locateAcquireGroup(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); + + SRT_TSA_NEEDS_NONLOCKED(m_GlobControlLock) CUDTGroup* acquireSocketsGroup(CUDTSocket* s); struct GroupKeeper @@ -469,8 +570,17 @@ class CUDTUnited CUDTSocket* locateAcquireSocket(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); bool acquireSocket(CUDTSocket* s); + void releaseSocket(CUDTSocket* s); + + SRT_TSA_NEEDS_LOCKED(m_InitLock) bool startGarbageCollector(); + + SRT_TSA_NEEDS_LOCKED(m_InitLock) void stopGarbageCollector(); + + // This function has disabled TSA because this ia a part + // of fork handler and hence only one thread is active. + SRT_TSA_DISABLED void cleanupAllSockets(); void closeAllSockets(); @@ -492,6 +602,12 @@ class CUDTUnited acquire(glob, s); } + void acquire_LOCKED(CUDTSocket* s) + { + socket = s; + s->apiAcquire(); + } + // Note: acquire doesn't check if the keeper already keeps anything. // This is only for a use together with an empty constructor. bool acquire(CUDTUnited& glob, CUDTSocket* s) @@ -507,6 +623,16 @@ class CUDTUnited return caught; } + bool release(CUDTUnited& glob) + { + if (!socket) + return false; + + glob.releaseSocket(socket); + socket = NULL; + return true; + } + ~SocketKeeper() { if (socket) @@ -519,12 +645,20 @@ class CUDTUnited private: + void bindSocketToMuxer(CUDTSocket* s, const sockaddr_any& address, UDPSOCKET* psocket = NULL) + SRT_TSA_NEEDS_LOCKED(s->m_ControlLock); + void updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* = NULL); bool updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + void checkRemoveMux(CMultiplexer&); + // Utility functions for updateMux - void configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af); - uint16_t installMuxer(CUDTSocket* w_s, CMultiplexer& sm); + void installMuxer(CUDTSocket* w_s, CMultiplexer* sm); + + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + CMultiplexer* findSuitableMuxer(CUDTSocket* s, const sockaddr_any& reqaddr); /// @brief Checks if channel configuration matches the socket configuration. /// @param cfgMuxer multiplexer configuration. @@ -535,42 +669,74 @@ class CUDTUnited const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket); private: - SRT_ATTR_GUARDED_BY(m_GlobControlLock) + SRT_TSA_GUARDED_BY(m_GlobControlLock) std::map m_mMultiplexer; // UDP multiplexer /// UDT network information cache. /// Existence is guarded by m_GlobControlLock, but the cache itself is thread-safe. - SRT_ATTR_GUARDED_BY(m_GlobControlLock) + SRT_TSA_PT_GUARDED_BY(m_GlobControlLock) CCache* const m_pCache; private: - srt::sync::atomic m_bClosing; + sync::atomic m_bGCClosing; sync::Mutex m_GCStartLock; sync::Mutex m_GCStopLock; sync::Condition m_GCStopCond; sync::Mutex m_InitLock; - SRT_ATTR_GUARDED_BY(m_InitLock) + SRT_TSA_GUARDED_BY(m_InitLock) int m_iInstanceCount; // number of startup() called by application - SRT_ATTR_GUARDED_BY(m_InitLock) - bool m_bGCStatus; // if the GC thread is working (true) + SRT_TSA_GUARDED_BY(m_InitLock) + sync::atomic m_bGCStatus; // if the GC thread is working (true) - SRT_ATTR_GUARDED_BY(m_InitLock) + SRT_TSA_GUARDED_BY(m_InitLock) sync::CThread m_GCThread; static void* garbageCollect(void*); - SRT_ATTR_GUARDED_BY(m_GlobControlLock) + SRT_TSA_GUARDED_BY(m_GlobControlLock) sockets_t m_ClosedSockets; // temporarily store closed sockets -#if ENABLE_BONDING - SRT_ATTR_GUARDED_BY(m_GlobControlLock) +#if SRT_ENABLE_BONDING + SRT_TSA_GUARDED_BY(m_GlobControlLock) groups_t m_ClosedGroups; #endif void checkBrokenSockets(); - void removeSocket(const SRTSOCKET u); + + // Attempts to remove the socket that is already closed. + // Returns non-null multiplexer if this multiplexer was + // holding this socket and removal has succeeded. + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + CMultiplexer* tryRemoveClosedSocket(const SRTSOCKET u); + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + CMultiplexer* tryRemoveClosedSocket(CUDTSocket* s); + + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + CMultiplexer* tryUnbindClosedSocket(const SRTSOCKET u); CEPoll m_EPoll; // handling epoll data structures and events + struct CloseInfo + { + SRT_CLOSE_INFO info; + int generation; + + // The value here defines how many GC rolls it takes + // to remove the record. As GC rolls every 1 second, + // this is more-less the number of seconds this record + // will be alive AFTER you close the socket. + CloseInfo(): info(), generation(MAX_CLOSE_RECORD_TTL) {} + }; + std::map m_ClosedDatabase; + + void checkTemporaryDatabases(); + void recordCloseReason(CUDTSocket* s); + + SRT_TSA_NEEDS_LOCKED(m_GlobControlLock) + void closeLeakyAcceptSockets(CUDTSocket* s); + +public: + SRTSTATUS getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& info); + private: CUDTUnited(const CUDTUnited&); CUDTUnited& operator=(const CUDTUnited&); diff --git a/srtcore/atomic_clock.h b/srtcore/atomic_clock.h index e01012313..840d35252 100644 --- a/srtcore/atomic_clock.h +++ b/srtcore/atomic_clock.h @@ -73,6 +73,12 @@ class AtomicClock dur.store(uint64_t(d.time_since_epoch().count())); } + void compare_exchange(const time_point_type& exp, const time_point_type& toset) + { + uint64_t val = exp.time_since_epoch().count(); + dur.compare_exchange(val, toset.time_since_epoch().count()); + } + AtomicClock& operator=(const time_point_type& s) { dur = s.time_since_epoch().count(); diff --git a/srtcore/atomic_msvc.h b/srtcore/atomic_msvc.h index 735c7a660..13d7a40e5 100644 --- a/srtcore/atomic_msvc.h +++ b/srtcore/atomic_msvc.h @@ -25,11 +25,14 @@ // 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. +// Changes: SRT namespace and some extensions. #ifndef SRT_SYNC_ATOMIC_MSVC_H_ #define SRT_SYNC_ATOMIC_MSVC_H_ +// NOTE: MSVC is only allowed to compile in C++11 mode. +#include + // Define which functions we need (don't include ). extern "C" { short _InterlockedIncrement16(short volatile*); @@ -76,12 +79,13 @@ __int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64); namespace srt { namespace sync { namespace msvc { -template -struct interlocked { + +template +struct interlocked_imp { }; template -struct interlocked { +struct interlocked_imp { static inline T increment(T volatile* x) { // There's no _InterlockedIncrement8(). char old_val, new_val; @@ -127,7 +131,7 @@ struct interlocked { }; template -struct interlocked { +struct interlocked_imp { static inline T increment(T volatile* x) { return static_cast( _InterlockedIncrement16(reinterpret_cast(x))); @@ -155,7 +159,7 @@ struct interlocked { }; template -struct interlocked { +struct interlocked_imp { static inline T increment(T volatile* x) { return static_cast( _InterlockedIncrement(reinterpret_cast(x))); @@ -182,7 +186,7 @@ struct interlocked { }; template -struct interlocked { +struct interlocked_imp { static inline T increment(T volatile* x) { #if defined(_M_X64) return static_cast( @@ -243,6 +247,34 @@ struct interlocked { #endif // _M_X64 } }; + +template +struct interlocked_imp { + static V* compare_exchange(V* volatile* x, + V* new_val, + V* expected_val) + { + return static_cast( + _InterlockedCompareExchangePointer(reinterpret_cast(x), + static_cast(new_val), + static_cast(expected_val))); + } + + static V* exchange(V* volatile* x, V* new_val) + { + return static_cast(_InterlockedExchangePointer( + reinterpret_cast(x), static_cast(new_val))); + } +}; + + +template +using interlocked = interlocked_imp::value || std::is_enum::value, + std::is_pointer::value> ; + + } // namespace msvc } // namespace sync } // namespace srt diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index ae4898eb2..ab8b79cb8 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -45,17 +45,18 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "buffer_rcv.h" +#include "api.h" +#include "group.h" #include "logging.h" +#include "logger_fas.h" using namespace std; using namespace srt::sync; -using namespace srt_logging; -namespace srt_logging -{ - extern Logger brlog; -} +using namespace srt::logging; +using namespace hvu; #define rbuflog brlog namespace srt { @@ -75,152 +76,400 @@ namespace { #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; +void CRcvBuffer::checkInitial() +{ + SRT_ASSERT(m_entries.size() < size_t(std::numeric_limits::max())); // All position pointers are integers +} - return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; +std::pair CRcvBuffer::clear() +{ + SeqNo new_seq = m_iStartSeqNo + m_iMaxPosOff; + // Can be optimized by only iterating m_iMaxPosOff from m_iStartPos. + int ncleared = 0; + int nleft = 0; + for (FixedArray::iterator it = m_entries.begin(); it != m_entries.end(); ++it) + { + bool filled = it->status != EntryState_Empty; + if (releaseUnit(*it)) + { + ++ncleared; + } + else if (filled) + { + ++nleft; + } } -} + m_iStartSeqNo = new_seq; + HLOGC(brlog.Debug, log << "CRcvBuffer: CLEAR: removed=" << ncleared << " left=" << nleft << " units"); -/* - * 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(0) -{ - SRT_ASSERT(size < size_t(std::numeric_limits::max())); // All position pointers are integers + return std::make_pair(ncleared, nleft); } 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 (m_pUnitPool) { - if (!it->pUnit) - continue; - - m_pUnitQueue->makeUnitFree(it->pUnit); - it->pUnit = NULL; + for (FixedArray::iterator it = m_entries.begin(); it != m_entries.end(); ++it) + { + Entry& u = *it; + if (!u.pUnit) + continue; + +#if USE_RECEIVER_UNIT_POOL + m_pUnitPool->returnUnit( (u.pUnit) ); +#else + m_pUnitPool->makeUnitFree(u.pUnit); + u.pUnit = NULL; +#endif + + u.status = EntryState_Empty; + u.muxID = -1; + } } + + // If the buffer was for a group, simply leave these + // units and let them be deleted by the destructor of + // m_entries, which will simply delete this memory + // (instead of giving it back to the multiplexer). } -int CRcvBuffer::insert(CUnit* unit) +void CRcvBuffer::debugShowState(const char* source SRT_ATR_UNUSED) { - SRT_ASSERT(unit != NULL); + HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source + << ") start=" << m_iStartPos + << " end=+" << m_iEndOff + << " drop=+" << m_iDropOff + << " max-off=+" << m_iMaxPosOff + << " seq[start]=%" << m_iStartSeqNo.val()); +} + +CRcvBuffer::InsertInfo CRcvBuffer::insert(UnitHandle& unit, int32_t muxid) +{ + SRT_ASSERT(unit); const int32_t seqno = unit->m_Packet.getSeqNo(); - const int offset = CSeqNo::seqoff(m_iStartSeqNo, seqno); + const COff offset = COff(SeqNo(seqno) - m_iStartSeqNo); + + // DO NOT use unit to access packet since that point on; + // the value may be moved to another pointer. + const CPacket& packet = unit->m_Packet; 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 << " msgno " << packet.getMsgSeq(m_bPeerRexmitFlag)); IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); - if (offset < 0) + if (offset < COff(0)) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); - return -2; + return InsertInfo(InsertInfo::BELATED); } + IF_HEAVY_LOGGING(string debug_source = fmtcat("insert %", seqno)); - if (offset >= (int)capacity()) + if (offset >= COff(capacity())) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); - return -3; + + InsertInfo ireport (InsertInfo::DISCREPANCY); + getAvailInfo((ireport)); + + IF_HEAVY_LOGGING(debugShowState((debug_source + " overflow").c_str())); + + return ireport; } // 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); + SRT_ASSERT((m_iStartPos + offset) / hsize() < 2); - const int pos = (m_iStartPos + offset) % m_szSize; - if (offset >= m_iMaxPosOff) - m_iMaxPosOff = offset + 1; + const COff prev_max_off = m_iMaxPosOff; + const CPos newpktpos = accessPos(offset); - // Packet already exists - SRT_ASSERT(pos >= 0 && pos < int(m_szSize)); - if (m_entries[pos].status != EntryState_Empty) + SRT_ASSERT(newpktpos >= 0 && newpktpos < int(hsize())); + if (m_entries[newpktpos].status != EntryState_Empty) // Packet already exists { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); - return -1; + IF_HEAVY_LOGGING(debugShowState((debug_source + " redundant").c_str())); + return InsertInfo(InsertInfo::REDUNDANT); } - SRT_ASSERT(m_entries[pos].pUnit == NULL); + SRT_ASSERT(!m_entries[newpktpos].pUnit); - m_pUnitQueue->makeUnitTaken(unit); - m_entries[pos].pUnit = unit; - m_entries[pos].status = EntryState_Avail; - countBytes(1, (int)unit->m_Packet.getLength()); + acquireUnitAt(newpktpos, (unit)); // moving + m_entries[newpktpos].muxID = muxid; + countBytes(1, (int)packet.getLength()); + HLOGC(qrlog.Debug, log << "CRcvQueue: PACKET ACQUIRED by the buffer, total " << m_iPktsCount); + + // Set to a value, if due to insertion there was added + // a packet that is earlier to be retrieved than the earliest + // currently available packet. + time_point earlier_time = updatePosInfo(packet, prev_max_off, offset); + + InsertInfo ireport (InsertInfo::INSERTED); + ireport.first_time = earlier_time; // 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()) + if (!m_tsbpd.isEnabled() && m_bMessageAPI && !packet.getMsgOrderFlag()) { - ++m_numOutOfOrderPackets; - onInsertNotInOrderPacket(pos); + ++m_numNonOrderPackets; + onInsertNonOrderPacket(newpktpos); } updateNonreadPos(); + + // This updates only the first_seq and avail_range fields. + getAvailInfo((ireport)); + IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); - return 0; + IF_HEAVY_LOGGING(debugShowState((debug_source + " ok").c_str())); + + return ireport; +} + +void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) +{ + // This finds the first possible available packet, which is + // preferably at cell 0, but if not available, try also with + // given fallback position, if it's set + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + const CPacket* pkt = &packetAt(m_iStartPos); + SRT_ASSERT(pkt); + w_if.avail_range = m_iEndOff; + w_if.first_seq = SeqNo(pkt->getSeqNo()); + return; + } + + // If not the first position, probe the skipped positions: + // - for live mode, check the DROP position + // (for potential after-drop reading) + // - for message mode, check the non-order message position + // (for potential out-of-oder message delivery) + + const CPacket* pkt = NULL; + if (m_tsbpd.isEnabled()) + { + // With TSBPD you can rely on drop position, if set + // Drop position must point always to a valid packet. + // Drop position must start from +1; 0 means no drop. + if (m_iDropOff) + { + pkt = &packetAt(incPos(m_iStartPos, m_iDropOff)); + SRT_ASSERT(pkt); + } + } + else + { + // Message-mode: try non-order read position. + if (m_iFirstNonOrderMsgPos != CPos_TRAP) + { + pkt = &packetAt(m_iFirstNonOrderMsgPos); + SRT_ASSERT(pkt); + } + } + + if (!pkt) + { + // This is default, but set just in case + // The default seq is SRT_SEQNO_NONE. + w_if.avail_range = COff(0); + return; + } + + // TODO: we know that at least 1 packet is available, but only + // with m_iEndOff we know where the true range is. This could also + // be implemented for message mode, but still this would employ + // a separate begin-end range declared for a complete out-of-order + // message. + w_if.avail_range = COff(1); + w_if.first_seq = SeqNo(pkt->getSeqNo()); +} + + +// This function is called exclusively after packet insertion. +// This will update also m_iEndOff and m_iDropOff fields (the latter +// regardless of the TSBPD mode). +CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CPacket& pkt, const COff prev_max_off, const COff offset) +{ + time_point earlier_time; + + const bool extended_end = prev_max_off != m_iMaxPosOff; + + // Update flags + // Case [A]: insertion of the packet has extended the busy region. + if (extended_end) + { + // THIS means that the buffer WAS CONTIGUOUS BEFORE. + if (m_iEndOff == prev_max_off) + { + // THIS means that the new packet didn't CAUSE a gap + if (m_iMaxPosOff == prev_max_off + 1) + { + // This means that m_iEndOff now shifts by 1, + // and m_iDropOff is set to 0 as there's no gap. + m_iEndOff = m_iMaxPosOff; + m_iDropOff = 0; + } + else + { + // Otherwise we have a drop-after-gap candidate + // which is the currently inserted packet. + // Therefore m_iEndOff STAYS WHERE IT IS. + m_iDropOff = m_iMaxPosOff - 1; + } + } + } + // + // Since this place, every 'offset' is in the range + // between m_iEndOff (inclusive) and m_iMaxPosOff. + else if (offset == m_iEndOff) + { + // Case [D]: inserted a packet at the first gap following the + // contiguous region. This makes a potential to extend the + // contiguous region and we need to find its end. + + // If insertion happened at the very first packet, it is the + // new earliest packet now. In any other situation under this + // condition there's some contiguous packet range preceding + // this position. + if (m_iEndOff == 0) + { + earlier_time = getPktTsbPdTime(pkt.getMsgTimeStamp()); + } + + updateGapInfo(); + } + else if (offset < m_iDropOff) + { + // Case [C]: the newly inserted packet precedes the + // previous earliest delivery position after drop, + // that is, there is now a "better" after-drop delivery + // candidate. + + // New position updated a valid packet on an earlier + // position than the drop position was before, although still + // following a gap. + // + // We know it because if the position has filled a gap following + // a valid packet, this preceding valid packet would be pointed + // by m_iDropOff, or it would point to some earlier packet in a + // contiguous series of valid packets following a gap, hence + // the above condition wouldn't be satisfied. + m_iDropOff = offset; + + // If there's an inserted packet BEFORE drop-pos (which makes it + // a new drop-pos), while the very first packet is absent (the + // below condition), it means we have a new earliest-available + // packet. Otherwise we would have only a newly updated drop + // position, but still following some earlier contiguous range + // of valid packets - so it's earlier than previous drop, but + // not earlier than the earliest packet. + if (m_iEndOff == 0) + { + earlier_time = getPktTsbPdTime(pkt.getMsgTimeStamp()); + } + } + // OTHERWISE: case [B] in which nothing is to be updated. + + return earlier_time; } +// This function is called when the m_iEndOff has been set to a new +// position and the m_iDropOff should be calculated since that position again. +void CRcvBuffer::updateGapInfo() +{ + COff from = m_iEndOff; + SRT_ASSERT(m_entries.at(incPos(m_iStartPos, m_iMaxPosOff)).status == EntryState_Empty); + + CPos pos = incPos(m_iStartPos, from); + + if (m_entries[pos].status == EntryState_Avail) + { + CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); + + // XXX turn this loop into a statement based on walkEntries() + for (; pos != end_pos; pos = incPos(pos)) + { + if (m_entries[pos].status != EntryState_Avail) + break; + } + + m_iEndOff = offPos(m_iStartPos, pos); + } + + // XXX This should be this way, but there are still inconsistencies + // in the message code. + //USE: SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status == EntryState_Empty); + SRT_ASSERT(m_entries.at(incPos(m_iStartPos, m_iEndOff)).status != EntryState_Avail); + + // XXX Controversy: m_iDropOff is only used in case when SRTO_TLPKTDROP + // is set. This option is not handled in message mode, only in live mode. + // Dropping by packet makes sense only in case of packetwise reading, + // which isn't the case of neither stream nor message mode. + if (!m_tsbpd.isEnabled()) + { + m_iDropOff = 0; + return; + } + + // Do not touch m_iDropOff if it's still beside the contiguous + // region. DO NOT SEARCH for m_iDropOff if m_iEndOff is max + // because this means that the whole buffer is contiguous. + // That would simply find nothing and only uselessly burden the + // performance by searching for a not present empty cell. + + // Also check if the current drop position is a readable packet. + // If not, start over. + CPos drop_pos = incPos(m_iStartPos, m_iDropOff); + + if (m_iDropOff < m_iEndOff || m_entries[drop_pos].status != EntryState_Avail) + { + m_iDropOff = 0; + if (m_iEndOff < m_iMaxPosOff) + { + CPos start = incPos(m_iStartPos, m_iEndOff + 1), + end = incPos(m_iStartPos, m_iEndOff); + + for (CPos i = start; i != end; i = incPos(i)) + { + if (m_entries[i].status == EntryState_Avail) + { + m_iDropOff = offPos(m_iStartPos, i); + break; + } + } + + // Must be found somewhere, worst case at the position + // of m_iMaxPosOff-1. If no finding loop caught it somehow, + // it will remain at 0. The case when you have empty packets + // in the busy range is only with message mode after reading + // packets out-of-order, but this doesn't use tsbpd mode. + SRT_ASSERT(m_iDropOff != 0); + } + } +} + +/// Request to remove from the receiver buffer +/// all packets with earlier sequence than @a seqno. +/// (Meaning, the packet with given sequence shall +/// be the first packet in the buffer after the operation). std::pair 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); + COff len = COff(SeqNo(seqno) - m_iStartSeqNo); if (len <= 0) { IF_RCVBUF_DEBUG(scoped_log.ss << ". Nothing to drop."); return std::make_pair(0, 0); } - m_iMaxPosOff -= len; - if (m_iMaxPosOff < 0) - m_iMaxPosOff = 0; + m_iMaxPosOff = decOff(m_iMaxPosOff, len); + m_iEndOff = decOff(m_iEndOff, len); + m_iDropOff = decOff(m_iDropOff, len); int iNumDropped = 0; // Number of dropped packets that were missing. int iNumDiscarded = 0; // The number of dropped packets that existed in the buffer. @@ -229,30 +478,34 @@ std::pair CRcvBuffer::dropUpTo(int32_t seqno) // Note! Dropping a EntryState_Read must not be counted as a drop because it was read. // Note! Dropping a EntryState_Drop must not be counted as a drop because it was already dropped and counted earlier. if (m_entries[m_iStartPos].status == EntryState_Avail) - ++iNumDiscarded; + ++iNumDiscarded; else if (m_entries[m_iStartPos].status == EntryState_Empty) - ++iNumDropped; + ++iNumDropped; 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); + SRT_ASSERT(!m_entries[m_iStartPos].pUnit && m_entries[m_iStartPos].status == EntryState_Empty); m_iStartPos = incPos(m_iStartPos); --len; } // Update positions - m_iStartSeqNo = seqno; + m_iStartSeqNo.set(seqno); // Move forward if there are "read/drop" entries. + // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); + updateGapInfo(); + // 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 (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + if (!isInUsedRange(m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; updateNonreadPos(); } if (!m_tsbpd.isEnabled() && m_bMessageAPI) - updateFirstReadableOutOfOrder(); + updateFirstReadableNonOrder(); + IF_HEAVY_LOGGING(debugShowState(fmtcat("drop %", seqno).c_str())); return std::make_pair(iNumDropped, iNumDiscarded); } @@ -261,7 +514,7 @@ int CRcvBuffer::dropAll() if (empty()) return 0; - const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); + const int32_t end_seqno = CSeqNo::incseq(m_iStartSeqNo.val(), m_iMaxPosOff); const std::pair numDropped = dropUpTo(end_seqno); return numDropped.first + numDropped.second; } @@ -272,26 +525,32 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropMessage(): %(" << seqnolo << " - " << seqnohi << ")" << " #" << msgno << " actionOnExisting=" << actionOnExisting << " m_iStartSeqNo=%" << m_iStartSeqNo); + if (msgno < 0) // Note that only SRT_MSGNO_CONTROL is allowed in the protocol. + { + LOGC(rbuflog.Error, log << "EPE: received UMSG_DROPREQ with msgflag field set to a negative value!"); + } // 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); + const int offset_a = SeqNo(seqnolo) - m_iStartSeqNo; + const int offset_b = SeqNo(seqnohi) - m_iStartSeqNo; if (offset_b < 0) { - LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " - << seqnohi << "]. Buffer start " << m_iStartSeqNo << "."); + HLOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " + << seqnohi << "]. Buffer start " << m_iStartSeqNo.val() << "."); return 0; } const bool bKeepExisting = (actionOnExisting == KEEP_EXISTING); - int minDroppedOffset = -1; + COff 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); + const COff start_off = COff(max(0, offset_a)); + const CPos start_pos = incPos(m_iStartPos, start_off); + const COff end_off = COff(min((int) hsize() - 1, offset_b + 1)); + const CPos 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)) + + // XXX use walkEntries() for a loop + for (CPos i = start_pos; i != end_pos; i = incPos(i)) { // Check if the unit was already dropped earlier. if (m_entries[i].status == EntryState_Drop) @@ -306,7 +565,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro if (bKeepExisting && bnd == PB_SOLO) { bDropByMsgNo = false; // Solo packet, don't search for the rest of the message. - LOGC(rbuflog.Debug, + HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO packet %" << packetAt(i).getSeqNo() << "."); continue; @@ -332,6 +591,14 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro minDroppedOffset = offPos(m_iStartPos, i); } + if (end_off > m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage: requested to drop up to %" << seqnohi + << " with highest in the buffer %" << CSeqNo::incseq(m_iStartSeqNo.val(), end_off) + << " - updating the busy region"); + m_iMaxPosOff = end_off; + } + if (bDropByMsgNo) { // If msgno is specified, potentially not the whole message was dropped using seqno range. @@ -339,8 +606,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro // 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)) + const CPos stop_pos = decPos(m_iStartPos); + for (CPos 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. @@ -349,7 +616,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro const PacketBoundary bnd = packetAt(i).getMsgBoundary(); const int32_t msgseq = packetAt(i).getMsgSeq(m_bPeerRexmitFlag); if (msgseq != msgno) - break; + break; if (bKeepExisting && bnd == PB_SOLO) { @@ -372,9 +639,26 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro IF_RCVBUF_DEBUG(scoped_log.ss << " iDropCnt " << iDropCnt); } + if (iDropCnt) + { + // We don't need the drop position, if we allow to drop messages by number + // and with that value we risk that drop was pointing to a dropped packet. + // Theoretically to make it consistent we need to shift the value to the + // next found packet, but we don't need this information if we use the message + // mode (because drop-by-packet is not supported in this mode) and this + // will burden the performance for nothing. + m_iDropOff = 0; + } + // Check if units before m_iFirstNonreadPos are dropped. const bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); releaseNextFillerEntries(); + + updateGapInfo(); + + IF_HEAVY_LOGGING(debugShowState( + ("dropmsg off %" + fmts(seqnolo) + " #" + fmts(msgno)).c_str())); + if (needUpdateNonreadPos) { m_iFirstNonreadPos = m_iStartPos; @@ -382,24 +666,46 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableOutOfOrder()) - m_iFirstReadableOutOfOrder = -1; - updateFirstReadableOutOfOrder(); + if (!checkFirstReadableNonOrder()) + m_iFirstNonOrderMsgPos = CPos_TRAP; + updateFirstReadableNonOrder(); } + IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + fmts(seqnolo)).c_str())); return iDropCnt; } -int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) +bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const +{ + if (m_iEndOff == 0) + { + // Initial contiguous region empty (including empty buffer). + HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo.val()); + w_seq = m_iStartSeqNo.val(); + return m_iMaxPosOff > 0; + } + + w_seq = CSeqNo::incseq(m_iStartSeqNo.val(), m_iEndOff); + + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << m_iEndOff + << " maxD=" << m_iMaxPosOff + << " base=%" << m_iStartSeqNo.val() + << " end=%" << w_seq); + + return (m_iEndOff < m_iMaxPosOff); +} + +int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL& w_msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); - if (!canReadInOrder && m_iFirstReadableOutOfOrder < 0) + if (!canReadInOrder && m_iFirstNonOrderMsgPos == CPos_TRAP) { LOGC(rbuflog.Warn, log << "CRcvBuffer.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); return 0; } - const int readPos = canReadInOrder ? m_iStartPos : m_iFirstReadableOutOfOrder; + const CPos readPos = canReadInOrder ? m_iStartPos : m_iFirstNonOrderMsgPos; + const bool isReadingFromStart = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::readMessage. m_iStartSeqNo " << m_iStartSeqNo << " m_iStartPos " << m_iStartPos << " readPos " << readPos); @@ -408,11 +714,25 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) 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)) + + int32_t out_seqlo = SRT_SEQNO_NONE; + int32_t out_seqhi = SRT_SEQNO_NONE; + + // As we have a green light for reading, it is already known that + // we're going to either remove or extract packets from the buffer, + // so drop position won't count anymore. + // + // The END position should be updated, that is: + // - remain just updated by the shifted start position if it's still ahead + // - recalculated from 0 again otherwise + m_iDropOff = 0; + int nskipped = 0; + + // XXX use walkEntries() + for (CPos i = readPos;; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); - if (!m_entries[i].pUnit) + if (!m_entries.at(i).pUnit) { LOGC(rbuflog.Error, log << "CRcvBuffer::readMessage(): null packet encountered."); break; @@ -422,6 +742,11 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) const size_t pktsize = packet.getLength(); const int32_t pktseqno = packet.getSeqNo(); + if (out_seqlo == SRT_SEQNO_NONE) + out_seqlo = pktseqno; + + out_seqhi = pktseqno; + // unitsize can be zero const size_t unitsize = std::min(remain, pktsize); memcpy(dst, packet.m_pcData, unitsize); @@ -434,28 +759,26 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (m_tsbpd.isEnabled()) updateTsbPdTimeBase(packet.getMsgTimeStamp()); - if (m_numOutOfOrderPackets && !packet.getMsgOrderFlag()) - --m_numOutOfOrderPackets; + if (m_numNonOrderPackets && !packet.getMsgOrderFlag()) + --m_numNonOrderPackets; const bool pbLast = packet.getMsgBoundary() & PB_LAST; - if (msgctrl && (packet.getMsgBoundary() & PB_FIRST)) + if (packet.getMsgBoundary() & PB_FIRST) { - msgctrl->msgno = packet.getMsgSeq(m_bPeerRexmitFlag); + w_msgctrl.msgno = packet.getMsgSeq(m_bPeerRexmitFlag); } - if (msgctrl && pbLast) + if (pbLast) { - msgctrl->srctime = count_microseconds(getPktTsbPdTime(packet.getMsgTimeStamp()).time_since_epoch()); + w_msgctrl.srctime = count_microseconds(getPktTsbPdTime(packet.getMsgTimeStamp()).time_since_epoch()); } - if (msgctrl) - msgctrl->pktseq = pktseqno; + w_msgctrl.pktseq = pktseqno; - releaseUnitInPos(i); - if (updateStartPos) + releaseUnitAt(i); + if (isReadingFromStart) { m_iStartPos = incPos(i); - --m_iMaxPosOff; - SRT_ASSERT(m_iMaxPosOff >= 0); - m_iStartSeqNo = CSeqNo::incseq(pktseqno); + m_iStartSeqNo = SeqNo(pktseqno) + 1; + ++nskipped; } else { @@ -465,26 +788,48 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (pbLast) { - if (readPos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + if (readPos == m_iFirstNonOrderMsgPos) + { + m_iFirstNonOrderMsgPos = CPos_TRAP; + m_iDropOff = 0; // always set to 0 in this mode. + } break; } } + if (nskipped) + { + // This means that m_iStartPos HAS BEEN shifted by that many packets. + // Update offset variables + m_iMaxPosOff = m_iMaxPosOff - nskipped; + + // This is checked as the PB_LAST flag marked packet should still + // be extracted in the existing period. + SRT_ASSERT(m_iMaxPosOff >= 0); + + m_iEndOff = decOff(m_iEndOff, nskipped); + if (m_iDropOff) + m_iDropOff = decOff(m_iDropOff, nskipped); + } countBytes(-pkts_read, -bytes_extracted); releaseNextFillerEntries(); - if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + // This will update the end position + updateGapInfo(); + + if (!isInUsedRange(m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; //updateNonreadPos(); } if (!m_tsbpd.isEnabled()) - // We need updateFirstReadableOutOfOrder() here even if we are reading inorder, - // in case readable inorder packets are all read out. - updateFirstReadableOutOfOrder(); + { + // We need updateFirstReadableNonOrder() here even if we are reading inorder, + // in case when readable inorder packets are all read out. + updateFirstReadableNonOrder(); + } const int bytes_read = int(dst - data); if (bytes_read < bytes_extracted) @@ -494,6 +839,10 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) IF_RCVBUF_DEBUG(scoped_log.ss << " pldi64 " << *reinterpret_cast(data)); + if (pw_seqrange) + *pw_seqrange = make_pair(out_seqlo, out_seqhi); + + IF_HEAVY_LOGGING(debugShowState("readmsg")); return bytes_read; } @@ -527,23 +876,24 @@ namespace { int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) { - int p = m_iStartPos; - const int end_pos = m_iFirstNonreadPos; + CPos p = m_iStartPos; + const CPos 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; + // XXX use walkEntries to extract the data, then update them in group while ((p != end_pos) && (rs > 0)) { if (!m_entries[p].pUnit) { - p = incPos(p); + // REDUNDANT? p = incPos(p); // Return abandons the loop anyway. LOGC(rbuflog.Error, log << "readBufferTo: IPE: NULL unit found in file transmission"); return -1; } - const srt::CPacket& pkt = packetAt(p); + const CPacket& pkt = packetAt(p); if (bTsbPdEnabled) { @@ -554,7 +904,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) << " PKT TS=" << FormatTime(tsPlay)); if ((tsPlay > now)) - break; /* too early for this unit, return whatever was copied */ + break; // too early for this unit, return whatever was copied } const int pktlen = (int)pkt.getLength(); @@ -566,14 +916,17 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) if (rs >= remain_pktlen) { - releaseUnitInPos(p); + releaseUnitAt(p); p = incPos(p); m_iNotch = 0; m_iStartPos = p; --m_iMaxPosOff; SRT_ASSERT(m_iMaxPosOff >= 0); - m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + m_iEndOff = decOff(m_iEndOff, 1); + m_iDropOff = decOff(m_iDropOff, 1); + + m_iStartSeqNo = m_iStartSeqNo.inc(); } else m_iNotch += rs; @@ -588,16 +941,18 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) // 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)) + if (!isInUsedRange( 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); + LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos + << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); } + IF_HEAVY_LOGGING(debugShowState("readbuf")); return iBytesRead; } @@ -613,15 +968,12 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) bool CRcvBuffer::hasAvailablePackets() const { - return hasReadableInorderPkts() || (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + return hasReadableInorderPkts() || (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != CPos_TRAP); } int CRcvBuffer::getRcvDataSize() const { - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - - return int(m_szSize + m_iFirstNonreadPos - m_iStartPos); + return offPos(m_iStartPos, m_iFirstNonreadPos); } int CRcvBuffer::getTimespan_ms() const @@ -632,27 +984,27 @@ int CRcvBuffer::getTimespan_ms() const if (m_iMaxPosOff == 0) return 0; - int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); + CPos 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) + SRT_ASSERT(m_entries[lastpos].pUnit || m_entries[lastpos].status == EntryState_Drop); + while (!m_entries[lastpos].pUnit && lastpos != m_iStartPos) { lastpos = decPos(lastpos); } - - if (m_entries[lastpos].pUnit == NULL) + + if (!m_entries[lastpos].pUnit) return 0; - int startpos = m_iStartPos; - while (m_entries[startpos].pUnit == NULL && startpos != lastpos) + CPos startpos = m_iStartPos; + while (!m_entries[startpos].pUnit && startpos != lastpos) { startpos = incPos(startpos); } - if (m_entries[startpos].pUnit == NULL) + if (!m_entries[startpos].pUnit) return 0; const steady_clock::time_point startstamp = @@ -668,7 +1020,6 @@ int CRcvBuffer::getTimespan_ms() const int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) const { - ScopedLock lck(m_BytesCountLock); bytes = m_iBytesCount; timespan = getTimespan_ms(); return m_iPktsCount; @@ -676,33 +1027,42 @@ int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) const 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; + // Default: no packet available. + PacketInfo pi = { SRT_SEQNO_NONE, false, time_point() }; - const CPacket& packet = packetAt(i); - const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; - return info; + const CPacket* pkt = NULL; + + // Very first packet available with no gap. + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + SRT_ASSERT(m_entries[m_iStartPos].pUnit); + pkt = &packetAt(m_iStartPos); + } + // If not, get the information from the drop + else if (m_iDropOff) + { + CPos drop_pos = incPos(m_iStartPos, m_iDropOff); + SRT_ASSERT(m_entries[drop_pos].pUnit); + pkt = &packetAt(drop_pos); + pi.seq_gap = true; // Available, but after a drop. + } + else + { + // If none of them point to a valid packet, + // there is no packet available; + return pi; } - const PacketInfo info = { -1, false, time_point() }; - return info; + pi.seqno = pkt->getSeqNo(); + pi.tsbpd_time = getPktTsbPdTime(pkt->getMsgTimeStamp()); + return pi; } 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; + const COff nonread_off = offPos(m_iStartPos, m_iFirstNonreadPos); + const SeqNo seqno_last = m_iStartSeqNo + nonread_off; + return std::pair(m_iStartSeqNo.val(), seqno_last.val()); } bool CRcvBuffer::isRcvDataReady(time_point time_now) const @@ -713,8 +1073,8 @@ bool CRcvBuffer::isRcvDataReady(time_point time_now) const if (haveInorderPackets) return true; - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - return (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); + return (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != CPos_TRAP); } if (!haveInorderPackets) @@ -738,11 +1098,11 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no 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_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); + if (m_iFirstNonOrderMsgPos != CPos_TRAP) { - SRT_ASSERT(m_numOutOfOrderPackets > 0); - const CPacket& packet = packetAt(m_iFirstReadableOutOfOrder); + SRT_ASSERT(m_numNonOrderPackets > 0); + const CPacket& packet = packetAt(m_iFirstNonOrderMsgPos); const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -763,12 +1123,11 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no int32_t CRcvBuffer::getFirstNonreadSeqNo() const { const int offset = offPos(m_iStartPos, m_iFirstNonreadPos); - return CSeqNo::incseq(m_iStartSeqNo, offset); + return m_iStartSeqNo.inc(offset).val(); } 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 @@ -776,19 +1135,50 @@ void CRcvBuffer::countBytes(int pkts, int bytes) if (!m_uAvgPayloadSz) m_uAvgPayloadSz = bytes; else - m_uAvgPayloadSz = avg_iir<100>(m_uAvgPayloadSz, (unsigned) bytes); + m_uAvgPayloadSz = avg_iir<100, unsigned>(m_uAvgPayloadSz, bytes); } } -void CRcvBuffer::releaseUnitInPos(int pos) +#if USE_RECEIVER_UNIT_POOL +void CRcvBuffer::acquireUnitAt(CPos pos, UnitHandle& unit) { - CUnit* tmp = m_entries[pos].pUnit; - m_entries[pos] = Entry(); // pUnit = NULL; status = Empty - if (tmp != NULL) - m_pUnitQueue->makeUnitFree(tmp); + SRT_ASSERT(m_entries[pos].pUnit.get() == NULL); + + // Swapping is acquisition. + std::swap(m_entries[pos].pUnit, unit); + m_entries[pos].status = EntryState_Avail; } -bool CRcvBuffer::dropUnitInPos(int pos) +#else +void CRcvBuffer::acquireUnitAt(CPos pos, UnitHandle& unit) +{ + acquireUnit( (unit) ); + m_entries[pos].pUnit = unit; + m_entries[pos].status = EntryState_Avail; + unit = NULL; // To maintain compat with the new version +} +#endif + +void CRcvBuffer::releaseUnitAt(CPos pos) +{ + releaseUnit(m_entries[pos]); +} + +bool CRcvBuffer::releaseUnit(Entry& u) +{ + bool condensed = false; + if (u.pUnit) + { + // Report -1 because the number of packets will be decreased right after returning from this function. + HLOGC(qrlog.Debug, log << "CRcvQueue: PACKET RELEASED by the buffer, total " << (m_iPktsCount - 1)); + condensed = condenseUnit( (u.pUnit), u.muxID ); + } + u.status = EntryState_Empty; + u.muxID = -1; + return condensed; +} + +bool CRcvBuffer::dropUnitInPos(CPos pos) { if (!m_entries[pos].pUnit) return false; @@ -798,27 +1188,109 @@ bool CRcvBuffer::dropUnitInPos(int pos) } else if (m_bMessageAPI && !packetAt(pos).getMsgOrderFlag()) { - --m_numOutOfOrderPackets; - if (pos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + --m_numNonOrderPackets; + if (pos == m_iFirstNonOrderMsgPos) + m_iFirstNonOrderMsgPos = CPos_TRAP; } - releaseUnitInPos(pos); + releaseUnitAt(pos); return true; } -void CRcvBuffer::releaseNextFillerEntries() +int CRcvBuffer::releaseNextFillerEntries() { - int pos = m_iStartPos; + CPos pos = m_iStartPos; + int nskipped = 0; + while (m_entries[pos].status == EntryState_Read || m_entries[pos].status == EntryState_Drop) { - m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); - releaseUnitInPos(pos); + if (nskipped == m_iMaxPosOff) + { + // This should never happen. All the previously read- or drop-marked + // packets should be contained in the range up to m_iMaxPosOff. Do not + // let the buffer ride any further and report the problem. Still stay there. + LOGC(rbuflog.Error, log << "releaseNextFillerEntries: IPE: Read/Drop status outside the busy range!"); + break; + } + + m_iStartSeqNo = m_iStartSeqNo.inc(); + releaseUnitAt(pos); pos = incPos(pos); m_iStartPos = pos; - --m_iMaxPosOff; - if (m_iMaxPosOff < 0) - m_iMaxPosOff = 0; + ++nskipped; + } + + if (!nskipped) + { + return nskipped; + } + + m_iMaxPosOff = m_iMaxPosOff - nskipped; + m_iEndOff = decOff(m_iEndOff, nskipped); + + // Drop off will be updated after that call, if needed. + m_iDropOff = 0; + + // XXX Not sure if this code has any chance to get executed, but then + // the message consistency can not be really ensured if we also need to + // rely on the other party that the dropped message sequence number really + // corresponds to the message identity. Treat this as EPE-prevention. + + // If it happened that after removal of the dropped packets the + // VERY FIRST packet doesn't have PB_FIRST flag (e.g. it's a scrapped + // message), remove all packets belonging to that message. + if (m_iMaxPosOff + && m_entries[m_iStartPos].pUnit + && m_entries[m_iStartPos].status == EntryState_Avail + && (packetAt(m_iStartPos).getMsgBoundary() & PB_FIRST) == 0) + { + const int32_t msgno = packetAt(m_iStartPos).getMsgSeq(); + + const CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); + CPos next_pos = incPos(m_iStartPos, 1); + ++nskipped; + + m_iStartSeqNo = m_iStartSeqNo.inc(); + releaseUnitAt(m_iStartPos); + m_iStartPos = next_pos; + + // Delete only contiguous packets. Empty cell may potentially + // be a packet of a different message, so keep it. + + // We believe that this will end after a few items and never reach end_pos + for (CPos i = next_pos; i != end_pos; i = next_pos) + { + // Keep that value for convenience. + next_pos = incPos(i); + + // Empty cell - keep it. + // NOTE: Dropped cells should have been removed already, + // and we state that a dropped cell cannot follow the valid cell. + if (!m_entries[i].pUnit) + break; + + const CPacket& pkt = packetAt(i); + + // Different message - keep it. + if (pkt.getMsgSeq() != msgno) + break; + + ++nskipped; + + m_iStartSeqNo = m_iStartSeqNo.inc(); + releaseUnitAt(i); + m_iStartPos = next_pos; + --m_iMaxPosOff; + + // You might have reached also the last one, + // while having that message id. + if ((pkt.getMsgBoundary() & PB_LAST) != 0) + { + break; + } + } } + + return nskipped; } // TODO: Is this function complete? There are some comments left inside. @@ -827,21 +1299,31 @@ 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. + const CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. - int pos = m_iFirstNonreadPos; + CPos pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) { + // Message API AND packet at [m_iFirstNonreadPos] is PB_SUBSEQUENT or PB_LAST. if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; - for (int i = pos; i != end_pos; i = incPos(i)) + // Continue the OUTER loop, when + // - There is an AVAIL unit at the [m_iFirstNonreadPos] cell + // - [m_iFirstNonreadPos] has set PB_FIRST (also PB_SOLO) + + for (CPos i = pos; i != end_pos; i = incPos(i)) { + // Reach the end_pos or the end of sequence of available packets if (!m_entries[i].pUnit || m_entries[pos].status != EntryState_Avail) { break; } + // m_iFirstNonreadPos is moved to the first position BEHIND + // the PB_LAST packet of the message. There's no guaratnee that + // the cell at this position isn't empty. + // Check PB_LAST only in message mode. if (!m_bMessageAPI || packetAt(i).getMsgBoundary() & PB_LAST) { @@ -857,9 +1339,9 @@ void CRcvBuffer::updateNonreadPos() } } -int CRcvBuffer::findLastMessagePkt() +CPos CRcvBuffer::findLastMessagePkt() { - for (int i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) + for (CPos i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); @@ -869,12 +1351,12 @@ int CRcvBuffer::findLastMessagePkt() } } - return -1; + return CPos_TRAP; } -void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) +void CRcvBuffer::onInsertNonOrderPacket(CPos insertPos) { - if (m_numOutOfOrderPackets == 0) + if (m_numNonOrderPackets == 0) return; // If the following condition is true, there is already a packet, @@ -883,7 +1365,7 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) // // 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) + if (m_iFirstNonOrderMsgPos != CPos_TRAP) return; // Just a sanity check. This function is called when a new packet is added. @@ -896,34 +1378,34 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) //if ((boundary & PB_FIRST) && (boundary & PB_LAST)) //{ // // This packet can be read out of order - // m_iFirstReadableOutOfOrder = insertPos; + // m_iFirstNonOrderMsgPos = 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)); + const bool hasLast = (boundary & PB_LAST) || (scanNonOrderMessageRight(insertPos, msgNo) != CPos_TRAP); if (!hasLast) return; - const int firstPktPos = (boundary & PB_FIRST) + const CPos firstPktPos = (boundary & PB_FIRST) ? insertPos - : scanNotInOrderMessageLeft(insertPos, msgNo); - if (firstPktPos < 0) + : scanNonOrderMessageLeft(insertPos, msgNo); + if (firstPktPos == CPos_TRAP) return; - m_iFirstReadableOutOfOrder = firstPktPos; + m_iFirstNonOrderMsgPos = firstPktPos; return; } -bool CRcvBuffer::checkFirstReadableOutOfOrder() +bool CRcvBuffer::checkFirstReadableNonOrder() { - if (m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder < 0 || m_iMaxPosOff == 0) + if (m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos == CPos_TRAP || m_iMaxPosOff == COff(0)) return false; - const int endPos = incPos(m_iStartPos, m_iMaxPosOff); + const CPos endPos = incPos(m_iStartPos, m_iMaxPosOff); int msgno = -1; - for (int pos = m_iFirstReadableOutOfOrder; pos != endPos; pos = incPos(pos)) + for (CPos pos = m_iFirstNonOrderMsgPos; pos != endPos; pos = incPos(pos)) // ++pos) { if (!m_entries[pos].pUnit) return false; @@ -944,30 +1426,33 @@ bool CRcvBuffer::checkFirstReadableOutOfOrder() return false; } -void CRcvBuffer::updateFirstReadableOutOfOrder() +void CRcvBuffer::updateFirstReadableNonOrder() { - if (hasReadableInorderPkts() || m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder >= 0) + if (hasReadableInorderPkts() || m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos != CPos_TRAP) return; if (m_iMaxPosOff == 0) return; // TODO: unused variable outOfOrderPktsRemain? - int outOfOrderPktsRemain = (int) m_numOutOfOrderPackets; + int outOfOrderPktsRemain = (int) m_numNonOrderPackets; // 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; + const CPos lastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); - int posFirst = -1; - int posLast = -1; + CPos posFirst = CPos_TRAP; + CPos posLast = CPos_TRAP; int msgNo = -1; - for (int pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) + // XXX use walkEntries() + + for (CPos pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) { if (!m_entries[pos].pUnit) { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -975,7 +1460,8 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() if (pkt.getMsgOrderFlag()) // Skip in order packet { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -990,13 +1476,14 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } if (boundary & PB_LAST) { - m_iFirstReadableOutOfOrder = posFirst; + m_iFirstNonOrderMsgPos = posFirst; return; } @@ -1007,15 +1494,16 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() return; } -int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const +CPos CRcvBuffer::scanNonOrderMessageRight(const CPos 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; + const CPos lastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (startPos == lastPos) - return -1; + return CPos_TRAP; - int pos = startPos; + // XXX use walkEntries() + CPos pos = startPos; do { pos = incPos(pos); @@ -1027,7 +1515,7 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { LOGC(rbuflog.Error, log << "Missing PB_LAST packet for msgNo " << msgNo); - return -1; + return CPos_TRAP; } const PacketBoundary boundary = pkt.getMsgBoundary(); @@ -1035,30 +1523,31 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const return pos; } while (pos != lastPos); - return -1; + return CPos_TRAP; } -int CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const +CPos CRcvBuffer::scanNonOrderMessageLeft(const CPos 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; + return CPos_TRAP; - int pos = startPos; + // XXX use walkEntries() + CPos pos = startPos; do { pos = decPos(pos); if (!m_entries[pos].pUnit) - return -1; + return CPos_TRAP; const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { LOGC(rbuflog.Error, log << "Missing PB_FIRST packet for msgNo " << msgNo); - return -1; + return CPos_TRAP; } const PacketBoundary boundary = pkt.getMsgBoundary(); @@ -1066,7 +1555,7 @@ int CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const return pos; } while (pos != m_iStartPos); - return -1; + return CPos_TRAP; } bool CRcvBuffer::addRcvTsbPdDriftSample(uint32_t usTimestamp, const time_point& tsPktArrival, int usRTTSample) @@ -1104,14 +1593,14 @@ void CRcvBuffer::updateTsbPdTimeBase(uint32_t usPktTimestamp) m_tsbpd.updateBaseTime(usPktTimestamp); } -string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const +string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& tsNow) const { stringstream ss; - ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo + ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo.val() << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; - ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; + ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << hsize() << " pkts. "; if (m_tsbpd.isEnabled() && m_iMaxPosOff > 0) { @@ -1120,7 +1609,7 @@ string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNo if (!is_zero(nextValidPkt.tsbpd_time)) { ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; - const int iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + const CPos iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (m_entries[iLastPos].pUnit) { ss << ", timespan "; @@ -1169,4 +1658,165 @@ void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) m_mavg.update(now, pkts, bytes, timespan_ms); } +int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) +{ + // This means that there are no lost seqs at all, no matter + // from which position they would have to be checked. + if (m_iEndOff == m_iMaxPosOff) + return SRT_SEQNO_NONE; + + COff offset = COff(SeqNo(fromseq) - m_iStartSeqNo); + + // Check if it's still inside the buffer. + // Skip the region from 0 to m_iEndOff because this + // region is by definition contiguous and contains no loss. + if (offset < m_iEndOff || offset >= m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset << " for %" << fromseq + << " (with max=" << m_iMaxPosOff << ") - NO LOSS FOUND"); + return SRT_SEQNO_NONE; + } + + // Check if this offset is equal to m_iEndOff. If it is, + // then you have the loss sequence exactly the one that + // was passed. Skip now, pw_end was not requested. + if (offset == m_iEndOff) + { + if (pw_end) + { + // If the offset is exactly at m_iEndOff, then + // m_iDropOff will mark the end of gap. + if (m_iDropOff) + *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), m_iDropOff); + else + { + LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: drop-off=0 while seq-off == end-off != max-off"); + *pw_end = fromseq; + } + } + return fromseq; + } + + // XXX REPLACE the loops below with walkEntries + int ret_seq = SRT_SEQNO_NONE; + int loss_off = 0; + // Now find the first empty position since here, + // up to m_iMaxPosOff. Checking against m_iDropOff + // makes no sense because if it is not 0, you'll + // find it earlier by checking packet presence. + for (int off = offset; off < m_iMaxPosOff; ++off) + { + CPos ipos = incPos(m_iStartPos, off); + if (m_entries[ipos].status == EntryState_Empty) + { + ret_seq = CSeqNo::incseq(m_iStartSeqNo.val(), off); + loss_off = off; + break; + } + } + + if (ret_seq == SRT_SEQNO_NONE) + { + // This is theoretically possible if we search from behind m_iEndOff, + // after m_iDropOff. This simply means that we are trying to search + // behind the last gap in the buffer. + return ret_seq; + } + + // We get this position, so search for the end of gap + if (pw_end) + { + for (int off = loss_off+1; off < m_iMaxPosOff; ++off) + { + CPos ipos = incPos(m_iStartPos, off); + if (m_entries[ipos].status != EntryState_Empty) + { + *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), off); + return ret_seq; + } + } + + // Should not be possible to not find an existing packet + // following the gap, otherwise there would be no gap. + LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: gap since %" << ret_seq << " not covered by existing packet"); + *pw_end = ret_seq; + } + return ret_seq; +} + +bool CRcvBuffer::condenseUnit(UnitHandle& w_u, int32_t muxid) +{ + if (m_pUnitPool) + { + HLOGC(brlog.Debug, log << "condenseUnit: single socket bound to a queue - giving back unit"); +#if USE_RECEIVER_UNIT_POOL + m_pUnitPool->returnUnit( (w_u) ); +#else + m_pUnitPool->makeUnitFree(w_u); + w_u = NULL; +#endif + return true; // will always succeed to return unit + } + +#if SRT_ENABLE_BONDING + if (m_pGroup) + { + m_pGroup->returnUnit((w_u), muxid); + } +#else + (void)muxid; +#endif + + HLOGC(brlog.Debug, log << "condenseUnit: found muxer id=" << muxid << " NOT FOUND - will delete unit"); +#if USE_RECEIVER_UNIT_POOL + // The muxer may no longer exist, in which case just delete. + CRcvBuffer::UnitHandle trash; + trash.swap(w_u); +#endif + + // In case of the old CUnitQueue just do nothing. + // If the multiplexer isn't found, the unit queue is already freed + // and not even unit's contents should be accessed. + + return false; +} + + +void CRcvBuffer::getUnitSeriesInfo(int32_t fromseq, size_t maxsize, std::vector& w_sources) +{ + const int offset = SeqNo(fromseq) - m_iStartSeqNo; + + // Check if it's still inside the buffer + if (offset < 0 || offset >= m_iMaxPosOff) + return; + + // All you need to do is to check if there's a valid packet + // at given position + size_t pass = 0; + for (int off = offset; off < m_iMaxPosOff; ++off) + { + int pos = incPos(m_iStartPos, off); + if (m_entries[pos].pUnit) + { + w_sources.push_back(m_entries[pos].muxID); + ++pass; + if (pass == maxsize) + break; + } + } +} + +std::string CRcvBuffer::Entry::debug() +{ + static const char* const state_names[4] = {"EMPTY", "AVAIL", "READ", "DROPPED"}; + using namespace hvu; + + const char* stat = "INVALID"; + + if (int(status) >= 0 && int(status) <= 3) + stat = state_names[status]; + + return fmtcat(stat, ": ", pUnit ? pUnit->m_Packet.Info() : string("(no packet)")); +} + } // namespace srt diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index eac1c7c47..d05c27b83 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -15,54 +15,340 @@ #include "common.h" #include "queue.h" #include "tsbpd_time.h" +#include "utilities.h" + +#define USE_WRAPPERS 0 +#define USE_OPERATORS 0 namespace srt { + // [[asif export import group:default]]; + class CUDTGroup; -/* - * 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_: - */ +// DEVELOPMENT TOOL - TO BE MOVED ELSEWHERE (like common.h) + +// NOTE: This below series of definitions for CPos and COff +// are here for development support only, but they are not in +// use in the release code - there CPos and COff are aliases to int. +#if USE_WRAPPERS +struct CPos +{ + int value; +#if USE_OPERATORS + const size_t* psize; + int isize() const {return *psize;} +#endif + +#if USE_OPERATORS + explicit CPos(const size_t* ps SRT_ATR_UNUSED, int val) + : value(val) + , psize(ps) + {} + +#else + explicit CPos(int val): value(val) {} +#endif + + int val() const { return value; } + explicit operator int() const {return value;} + + CPos(const CPos& src): value(src.value) +#if USE_OPERATORS + , psize(src.psize) +#endif + {} + CPos& operator=(const CPos& src) + { +#if USE_OPERATORS + psize = src.psize; +#endif + value = src.value; + return *this; + } + +#if USE_OPERATORS + int cmp(CPos other, CPos start) const + { + int pos2 = value; + int pos1 = other.value; + + const int off1 = pos1 >= start.value ? pos1 - start.value : pos1 + start.isize() - start.value; + const int off2 = pos2 >= start.value ? pos2 - start.value : pos2 + start.isize() - start.value; + + return off2 - off1; + } + + CPos& operator--() + { + if (value == 0) + value = isize() - 1; + else + --value; + return *this; + } + + CPos& operator++() + { + ++value; + if (value == isize()) + value = 0; + return *this; + } +#endif + + bool operator == (CPos other) const { return value == other.value; } + bool operator != (CPos other) const { return value != other.value; } +}; + +struct COff +{ + int value; + explicit COff(int v): value(v) {} + COff& operator=(int v) { value = v; return *this; } + + int val() const { return value; } + explicit operator int() const {return value;} + + COff& operator--() { --value; return *this; } + COff& operator++() { ++value; return *this; } + + COff operator--(int) { int v = value; --value; return COff(v); } + COff operator++(int) { int v = value; ++value; return COff(v); } + + COff operator+(COff other) const { return COff(value + other.value); } + COff operator-(COff other) const { return COff(value - other.value); } + COff& operator+=(COff other) { value += other.value; return *this;} + COff& operator-=(COff other) { value -= other.value; return *this;} + + bool operator == (COff other) const { return value == other.value; } + bool operator != (COff other) const { return value != other.value; } + bool operator < (COff other) const { return value < other.value; } + bool operator > (COff other) const { return value > other.value; } + bool operator <= (COff other) const { return value <= other.value; } + bool operator >= (COff other) const { return value >= other.value; } + + // Exceptionally allow modifications of COff by a bare integer + COff operator+(int other) const { return COff(value + other); } + COff operator-(int other) const { return COff(value - other); } + COff& operator+=(int other) { value += other; return *this;} + COff& operator-=(int other) { value -= other; return *this;} + + bool operator == (int other) const { return value == other; } + bool operator != (int other) const { return value != other; } + bool operator < (int other) const { return value < other; } + bool operator > (int other) const { return value > other; } + bool operator <= (int other) const { return value <= other; } + bool operator >= (int other) const { return value >= other; } + + friend bool operator == (int value, COff that) { return value == that.value; } + friend bool operator != (int value, COff that) { return value != that.value; } + friend bool operator < (int value, COff that) { return value < that.value; } + friend bool operator > (int value, COff that) { return value > that.value; } + friend bool operator <= (int value, COff that) { return value <= that.value; } + friend bool operator >= (int value, COff that) { return value >= that.value; } + + operator bool() const { return value != 0; } +}; + +#if USE_OPERATORS + +inline CPos operator+(const CPos& pos, COff off) +{ + int val = pos.value + off.value; + while (val >= pos.isize()) + val -= pos.isize(); + return CPos(pos.psize, val); +} + +inline CPos operator-(const CPos& pos, COff off) +{ + int val = pos.value - off.value; + while (val < 0) + val += pos.isize(); + return CPos(pos.psize, val); +} + +// Should verify that CPos use the same size! +inline COff operator-(CPos later, CPos earlier) +{ + if (later.value < earlier.value) + return COff(later.value + later.isize() - earlier.value); + + return COff(later.value - earlier.value); +} + +inline CSeqNo operator+(CSeqNo seq, COff off) +{ + int32_t val = CSeqNo::incseq(seq.val(), off.val()); + return CSeqNo(val); +} + +inline CSeqNo operator-(CSeqNo seq, COff off) +{ + int32_t val = CSeqNo::decseq(seq.val(), off.val()); + return CSeqNo(val); +} + + +#endif +const CPos CPos_TRAP (-1); + +#else +typedef int CPos; +typedef int COff; +const int CPos_TRAP = -1; +#endif + +// Design description: see in docs/dev: +// - development-notes.md for the general information +// - receiver-buffer.md for the detailed design description +// - receiver-unit-pool.md for the description of CPacketUnitPool class CRcvBuffer { typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; + void checkInitial(); + public: - CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool bMessageAPI); +#if USE_RECEIVER_UNIT_POOL + typedef CPacketUnitPool::UnitPtr UnitHandle; + typedef CPacketUnitPool::Unit Unit; + typedef CPacketUnitPool UnitQueue; +#else + typedef CUnit* UnitHandle; + typedef CUnit Unit; + typedef CUnitQueue UnitQueue; +#endif + + // Due to still required C++03 compat, we can't use delegating constructors. All constructors + // are copy-pasted through PP. +#define CONSTRUCT_RCV_BUFFER(...) \ + m_entries(size), \ + m_iStartPos(0), \ + m_iMaxPosOff(0), \ + __VA_ARGS__, \ + m_iStartSeqNo(initSeqNo), \ + m_iEndOff(0), \ + m_iDropOff(0), \ + m_iFirstNonreadPos(0), \ + m_iNotch(0), \ + m_numNonOrderPackets(0), \ + m_iFirstNonOrderMsgPos(CPos_TRAP), \ + m_bPeerRexmitFlag(true), \ + m_bMessageAPI(bMessageAPI), \ + m_iBytesCount(0), \ + m_iPktsCount(0), \ + m_uAvgPayloadSz(0) \ + { checkInitial(); } + + CRcvBuffer(int initSeqNo, size_t size, CMultiplexer* single_muxer, bool bMessageAPI): +#if SRT_ENABLE_BONDING + CONSTRUCT_RCV_BUFFER(m_pUnitPool(single_muxer->getBufferQueue()), m_pGroup(NULL)); +#else + CONSTRUCT_RCV_BUFFER(m_pUnitPool(single_muxer->getBufferQueue())); +#endif + + // FOR TESTING PURPOSES ONLY - to avoid creating a CMultiplexer object in order to test the buffer. + CRcvBuffer(int initSeqNo, size_t size, UnitQueue* single_queue, bool bMessageAPI): +#if SRT_ENABLE_BONDING + CONSTRUCT_RCV_BUFFER(m_pUnitPool(single_queue) , m_pGroup(NULL)); +#else + CONSTRUCT_RCV_BUFFER(m_pUnitPool(single_queue)); +#endif + +#if SRT_ENABLE_BONDING + // For groups - this can collect units from various pools, so the source + // pool will be specified when returning the unit. + CRcvBuffer(int initSeqNo, size_t size, CUDTGroup* group, bool bMessageAPI): + CONSTRUCT_RCV_BUFFER(m_pUnitPool(NULL), m_pGroup(group)); +#endif +#undef CONSTRUCT_RCV_BUFFER ~CRcvBuffer(); public: - /// Insert a unit into the buffer. - /// Similar to CRcvBuffer::addData(CUnit* unit, int offset) + + void debugShowState(const char* source); + + struct InsertInfo + { + enum Result { INSERTED = 0, REDUNDANT = -1, BELATED = -2, DISCREPANCY = -3 } result; + + // Below fields are valid only if result == INSERTED. Otherwise they have trap repro. + + SeqNo first_seq; // sequence of the first available readable packet + time_point first_time; // Time of the new, earlier packet that appeared ready, or null-time if this didn't change. + COff avail_range; + + InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, int range = 0, + time_point fp_time = time_point()) + : result(r), first_seq(fp_seq), first_time(fp_time), avail_range(range) + { + } + + InsertInfo() + : result(REDUNDANT), first_seq(SRT_SEQNO_NONE), avail_range(0) + { + } + + }; + + // + // Condenser: the facility used to recover packets released from the buffer. + // + + void acquireUnit(UnitHandle& unit) + { +#if USE_RECEIVER_UNIT_POOL + // Nothing to do; the unit is taken over ownership + (void)unit; +#else + unit->m_pParentQueue->makeUnitTaken(unit); +#endif + } + + bool condenseUnit(UnitHandle& unit, int32_t muxid); + + /// Inserts the unit with the data packet into the receiver buffer. + /// The result inform about the situation with the packet attempted + /// to be inserted and the readability of the buffer. + /// + /// @param [PASS] unit The unit that should be placed in the buffer + /// + /// @return The InsertInfo structure where: + /// * result: the result of insertion, which is: + /// * INSERTED: successfully placed in the buffer + /// * REDUNDANT: not placed, the packet is already there + /// * BELATED: not placed, its sequence is in the past + /// * DISCREPANCY: not placed, the sequence is far future or OOTB + /// * first_seq: the earliest sequence number now avail for reading + /// * avail_range: how many packets are available for reading (1 if unknown) + /// * first_time: the play time of the earliest read-available packet + /// If there is no available packet for reading, first_seq == SRT_SEQNO_NONE. + /// + InsertInfo insert(UnitHandle& unit, int muxid); + + time_point updatePosInfo(const CPacket& pkt, const COff prev_max_off, const COff offset); + void getAvailInfo(InsertInfo& w_if); + + /// Update the values of `m_iEndPos` and `m_iDropPos` in + /// case when `m_iEndPos` was updated to a position of a + /// nonempty cell. + /// + /// This function should be called after having m_iEndPos + /// has somehow be set to position of a non-empty cell. + /// This can happen by two reasons: /// - /// @param [in] unit pointer to a data unit containing new packet - /// @param [in] offset offset from last ACK point. + /// - the cell has been filled by incoming packet + /// - the value has been reset due to shifted m_iStartPos /// - /// @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); + /// This means that you have to search for a new gap and + /// update the m_iEndPos and m_iDropPos fields, or set them + /// both to the end of range if there are no loss gaps. + /// + void updateGapInfo(); /// 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 @@ -98,16 +384,28 @@ class CRcvBuffer /// @return the number of packets actually dropped. int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting); + /// Extract the "expected next" packet sequence. + /// Extract the past-the-end sequence for the first packet + /// that is expected to arrive next with preserving the packet order. + /// If the buffer is empty or the very first cell is lacking a packet, + /// it returns the sequence assigned to the first cell. Otherwise it + /// returns the sequence representing the first empty cell (the next + /// cell to the last received packet, if there are no loss-holes). + /// @param [out] w_seq: returns the sequence (always valid) + /// @return true if this sequence is followed by any valid packets + bool getContiguousEnd(int32_t& w_seq) const; + /// Read the whole message from one or several packets. /// - /// @param [in,out] data buffer to write the message into. + /// @param [out] data buffer to write the message into. /// @param [in] len size of the buffer. - /// @param [in,out] message control data + /// @param [out,opt] message control data to be filled + /// @param [out,opt] pw_seqrange range of sequence numbers for packets belonging to the message /// /// @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); + int readMessage(char* data, size_t len, SRT_MSGCTRL& msgctrl, std::pair* pw_seqrange = NULL); /// Read acknowledged data into a user buffer. /// @param [in, out] dst pointer to the target user buffer. @@ -123,24 +421,25 @@ class CRcvBuffer public: /// Get the starting position of the buffer as a packet sequence number. - int getStartSeqNo() const { return m_iStartSeqNo; } + int32_t getStartSeqNo() const { return m_iStartSeqNo.val(); } /// 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; } + void setStartSeqNo(int32_t seqno) { m_iStartSeqNo.set(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 + size_t getAvailSize(int32_t 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(); + const int32_t iRBufSeqNo = m_iStartSeqNo.val(); if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo + //if (iRBufSeqNo >= CSeqNo(iFirstUnackSeqNo)) { // Full capacity is available. return capacity(); @@ -198,11 +497,12 @@ class CRcvBuffer /// @note CSeqNo::seqoff(first, second) is 0 if nothing to read. std::pair getAvailablePacketsRange() const; - size_t countReadable() const; + int32_t getFirstLossSeq(int32_t fromseq, int32_t* opt_end = NULL); + void getUnitSeriesInfo(int32_t fromseq, size_t maxsize, std::vector& w_sources); bool empty() const { - return (m_iMaxPosOff == 0); + return (m_iMaxPosOff == COff(0)); } /// Returns the currently used number of cells, including @@ -221,11 +521,11 @@ class CRcvBuffer /// 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 continuously. + /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if hsize() entries are added continuously. /// TODO: Old receiver buffer capacity returns the actual size. Check for conflicts. size_t capacity() const { - return m_szSize - 1; + return hsize() - 1; } int64_t getDrift() const { return m_tsbpd.drift(); } @@ -250,59 +550,101 @@ class CRcvBuffer return m_tsbpd.getInternalTimeBase(w_timebase, w_wrp, w_udrift); } + // Remove everything that is currently in the buffer + // and shift the earliest sequence number past the last one. + std::pair clear(); + 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); } - - /// @brief Compares the two positions in the receiver buffer relative to the starting position. - /// @param pos2 a position in the receiver buffer. - /// @param pos1 a position in the receiver buffer. - /// @return a positive value if pos2 is ahead of pos1; a negative value, if pos2 is behind pos1; otherwise returns 0. - inline int cmpPos(int pos2, int pos1) const + size_t hsize() const { return m_entries.size(); } + + CPos incPos(CPos pos, COff inc = COff(1)) const //LIKE: (pos + inc) % hsize() + { + CPos sum = pos + inc; + const CPos posmax = hsize(); + if (sum >= posmax) + return sum - posmax; + return sum; + } + + CPos decPos(CPos pos) const { - // XXX maybe not the best implementation, but this keeps up to the rule. - // Maybe use m_iMaxPosOff to ensure a position is not behind the m_iStartPos. - 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; + int diff = pos - 1; + if (diff >= 0) + { + return CPos(diff); + } + return CPos(hsize() - 1); + } - return off2 - off1; + COff offPos(CPos pos1, CPos pos2) const + { + int diff = pos2 - pos1; + if (diff >= 0) + { + return COff(diff); + } + return COff(hsize() + diff); + } + + static COff decOff(COff val, int shift) + { + int ival = val - shift; + if (ival < 0) + return COff(0); + return COff(ival); + } + + bool isInUsedRange(CPos iFirstNonreadPos) + { + if (iFirstNonreadPos == m_iStartPos) + return true; + + // DECODE the iFirstNonreadPos + int diff = iFirstNonreadPos - m_iStartPos; + if (diff < 0) + diff += hsize(); + + return diff <= m_iMaxPosOff; } // 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; } + CPacket& packetAt(CPos pos) { return m_entries[pos].pUnit->m_Packet; } + const CPacket& packetAt(CPos pos) const { return m_entries[pos].pUnit->m_Packet; } + + int startPos() const { return m_iStartPos; } private: void countBytes(int pkts, int bytes); void updateNonreadPos(); - void releaseUnitInPos(int pos); + void releaseUnitAt(CPos position); + void acquireUnitAt(CPos position, UnitHandle& unit); /// @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); + bool dropUnitInPos(CPos pos); /// Release entries following the current buffer position if they were already /// read out of order (EntryState_Read) or dropped (EntryState_Drop). - void releaseNextFillerEntries(); + /// + /// @return the range for which the start pos has been shifted + int releaseNextFillerEntries(); bool hasReadableInorderPkts() const { return (m_iFirstNonreadPos != m_iStartPos); } /// Find position of the last packet of the message. - int findLastMessagePkt(); + CPos 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; + void onInsertNonOrderPacket(CPos insertpos); + // Check if m_iFirstNonOrderMsgPos is still readable. + bool checkFirstReadableNonOrder(); + void updateFirstReadableNonOrder(); + CPos scanNonOrderMessageRight(CPos startPos, int msgNo) const; + CPos scanNonOrderMessageLeft(CPos startPos, int msgNo) const; typedef bool copy_to_dst_f(char* data, int len, int dst_offset, void* arg); @@ -316,57 +658,190 @@ class CRcvBuffer /// @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; - // }; - +public: // Public access needed for UT enum EntryStatus { - EntryState_Empty, //< No CUnit record. + EntryState_Empty = 0, //< 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() +#if USE_RECEIVER_UNIT_POOL + : pUnit() +#else : pUnit(NULL) +#endif + , muxID(-1) , status(EntryState_Empty) {} - CUnit* pUnit; + Entry(UnitHandle& unit, EntryStatus es = EntryState_Avail) + : status(es) + {std::swap(unit, pUnit);} + + UnitHandle pUnit; + int muxID; // if group-buffer, search for muxer providing it. EntryStatus status; + + std::string debug(); }; + bool releaseUnit(Entry&); typedef FixedArray entries_t; - entries_t m_entries; + typedef entries_t::value_type value_type; + + enum LoopStatus { BREAK, CONTINUE }; + + typedef LoopStatus entry_fn(value_type& e); + + /// walkEntries: loop over the range of entries executing a function + /// @param startoff The first position to operate + /// @param endoff The past-the-end of the last position + /// @param fn Function to call matching the signature of @a entry_fn + /// @return endoff, if all iterations passed, or earlier offset, if interrupted + /// + /// Walks over elements of m_entries in logical order and executes @a fn + /// on each of them. The called @a fn should return BREAK if the loop + /// should stop after this iteration and CONTINUE otherwise. + template + COff walkEntries(COff startoff, COff endoff, Callable fn) + { + SRT_ASSERT(startoff <= endoff && endoff <= COff(hsize())); + + if (startoff == endoff) + return CONTINUE; + + CPos startpos, endpos; + + int start_avail = hsize() - m_iStartPos; + bool two_loops = false; + if (startoff > start_avail) // already off-range, so is endoff + { + int offshift = endoff - startoff; + startpos = m_iStartPos + startoff - hsize(); + endpos = startpos + offshift; + } + else if (endoff < start_avail) // endoff fits, and so does startoff + { + startpos = m_iStartPos + startoff; + endpos = m_iStartPos + endoff; + } + else // We have a split-region. + { + startpos = m_iStartPos + startoff; + endpos = m_iStartPos + endoff - hsize(); + two_loops = true; + } + + if (!two_loops) + { + for (CPos i = startpos; i < endpos; ++i) + { + value_type& e = m_entries[i]; + LoopStatus st = fn(e); + if (st == BREAK) + return (i - startpos) + startoff; + } + return endoff; + } - const size_t m_szSize; // size of the array of units (buffer) - CUnitQueue* m_pUnitQueue; // the shared unit queue + for (CPos i = startpos; i < CPos(hsize()); ++i) + { + value_type& e = m_entries[i]; + LoopStatus st = fn(e); + if (st == BREAK) + return (i - startpos) + startoff; + } + + for (CPos i = CPos(0); i < endpos; ++i) + { + value_type& e = m_entries[i]; + LoopStatus st = fn(e); + if (st == BREAK) + return (i + hsize() - startpos) + startoff; + } - 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 + return endoff; + } + + struct ClearGapEntries + { + LoopStatus operator()(value_type& v) const + { + v = value_type(); + return CONTINUE; + } + }; + + CPos accessPos(COff offset) + { + if (offset >= m_iMaxPosOff) + { + walkEntries(m_iMaxPosOff, offset, ClearGapEntries()); + m_iMaxPosOff = offset + COff(1); + } + + return incPos(m_iStartPos, offset); + } + + value_type& access(COff offset) + { + return m_entries[accessPos(offset)]; + } + + void drop(COff offset) + { + if (offset >= m_iMaxPosOff) + { + walkEntries(0, m_iMaxPosOff, ClearGapEntries()); + + // Clear all + m_iStartPos = 0; + m_iMaxPosOff = 0; + return; + } + + walkEntries(0, offset, ClearGapEntries()); + m_iStartPos = incPos(m_iStartPos, offset); + m_iMaxPosOff = m_iMaxPosOff - offset; + } + +private: + + // Cirtular Container-oriented fields + entries_t m_entries; + CPos m_iStartPos; // the head position for I/O (inclusive) + + // ATOMIC: sometimes this value is checked for buffer emptiness + sync::atomic m_iMaxPosOff; // the furthest data position + + // Additional fields + // Either of these two should be set to a valid value, depending + // on what kind of API entity is using the buffer. + UnitQueue* m_pUnitPool; +#if SRT_ENABLE_BONDING + CUDTGroup* m_pGroup; +#endif + + // ATOMIC because getStartSeqNo() may be called from other thread + // than CUDT's receiver worker thread. Even if it's not a problem + // if this value is a bit outdated, it must be read solid. + SeqNoT< sync::atomic > m_iStartSeqNo; + COff m_iEndOff; // past-the-end of the contiguous region since m_iStartOff + COff m_iDropOff; // points past m_iEndOff to the first deliverable after a gap; value 0 if no first gap + CPos m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) 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 + size_t m_numNonOrderPackets; // The number of stored packets with "inorder" flag set to false + + /// Points to the first packet of a message that has out-of-order flag + /// and is complete (all packets from first to last are in the buffer). + /// If there is no such message in the buffer, it contains -1. + CPos m_iFirstNonOrderMsgPos; bool m_bPeerRexmitFlag; // Needed to read message number correctly const bool m_bMessageAPI; // Operation mode flag: message or stream. @@ -379,7 +854,7 @@ class CRcvBuffer /// @return 0 void setTsbPdMode(const time_point& timebase, bool wrap, duration delay); - void setPeerRexmitFlag(bool flag) { m_bPeerRexmitFlag = flag; } + void setPeerRexmitFlag(bool flag) { m_bPeerRexmitFlag = flag; } void applyGroupTime(const time_point& timebase, bool wrp, uint32_t delay, const duration& udrift); @@ -396,7 +871,7 @@ class CRcvBuffer /// 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; + std::string strFullnessState(int32_t iFirstUnackSeqNo, const time_point& tsNow) const; private: CTsbpdTime m_tsbpd; @@ -404,11 +879,9 @@ class CRcvBuffer 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 + sync::atomic m_uAvgPayloadSz; // Average payload size for dropped bytes estimation }; } // namespace srt diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 2db82a686..1b7efdcf6 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -53,123 +53,74 @@ modified by #include "platform_sys.h" #include +#include // for debug purposes #include "buffer_snd.h" #include "packet.h" #include "core.h" // provides some constants +#include "crypto.h" +#include "utilities.h" #include "logging.h" namespace srt { using namespace std; -using namespace srt_logging; +using namespace srt::logging; using namespace sync; -CSndBuffer::CSndBuffer(int ip_family, 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) - , m_rateEstimator(ip_family) -{ - // 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; +CSndBuffer::CSndBuffer(size_t pktsize, size_t slicesize, size_t mss, size_t headersize, size_t reservedsize, int flwsize SRT_ATR_UNUSED) : + m_BufLock(), + m_iBlockLen(mss - headersize), + m_iReservedSize(reservedsize), + m_iSndLastDataAck(SRT_SEQNO_NONE), + m_iSndUpdateAck(SRT_SEQNO_NONE), + m_iNextMsgNo(1), + m_iBytesCount(0), + m_iCapacity(pktsize), + // To avoid performance degradation during the transmission, + // we allocate in advance all required blocks so that they are + // picked up from the storage when required. + m_Packets(m_iBlockLen, m_iCapacity, slicesize) +{ + m_rateEstimator.setHeaderSize(headersize); - setupMutex(m_BufLock, "Buf"); + initialize(); + setupMutex(m_BufLock, "SndBuf"); } -CSndBuffer::~CSndBuffer() +void CSndBuffer::initialize() { - 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); + // If any further initialization is needed } -void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) +EncryptionKeySpec CSndBuffer::addBuffer(const char* data, int len, CCryptoControl& w_crypto, time_point& w_origintime, 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); + const int iNumBlocks = number_slices(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) + if (m_iSndLastDataAck == SRT_SEQNO_NONE) { - HLOGC(bslog.Debug, log << "addBuffer: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers..."); - increase(); + m_iSndLastDataAck = m_iSndUpdateAck = w_seqno; } 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"); + log << "addBuffer: needs=" << iNumBlocks << " buffers for " << len << " bytes. Taken=" + << m_Packets.size() << "/" << m_iCapacity); // 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; + w_origintime = m_tsLastOriginTime; if (w_msgno == SRT_MSGNO_NONE) // DEFAULT-UNCHANGED msgno supplied { @@ -182,82 +133,65 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) m_iNextMsgNo = w_msgno; } - for (int i = 0; i < iNumBlocks; ++i) + EncryptionKeySpec kflg = EK_NOENC; + + for (int i = 0; i < iNumBlocks; ++i) // only 1 normally in live mode { int pktlen = len - i * iPktLen; if (pktlen > iPktLen) pktlen = iPktLen; + // This will never fail; it is believed that if the buffer size reached + // defined capacity, addBuffer will not be called. + SndPktArray::Packet& p = m_Packets.push(); + 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; + << " size=" << pktlen << " TO BUFFER:" << (void*)p.m_pcData); + memcpy((p.m_pcData), data + i * iPktLen, pktlen); + p.m_iLength = pktlen; - s->m_iSeqNo = w_seqno; + p.m_iSeqNo = w_seqno; w_seqno = CSeqNo::incseq(w_seqno); + p.m_iMsgNoBitset = m_iNextMsgNo | inorder | PacketBoundaryBitsIfRange(0, i, iNumBlocks); + + kflg = checkEncryption(p, w_crypto); + if (kflg == EK_ERROR) + { + m_Packets.unpush(); + LOGC(qslog.Error, log << CONID() << "ENCRYPT FAILED - packet won't be stored, size=" << p.m_iLength); + return EK_ERROR; + } + + p.m_iTTL = ttl; + p.m_tsRexmitTime = time_point(); + p.m_tsOriginTime = m_tsLastOriginTime; + } - 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 results 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 = 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; + return kflg; } -int CSndBuffer::addBufferFromFile(fstream& ifs, int len) +EncryptionKeySpec CSndBuffer::addBufferFromFile(fstream& ifs, int len, CCryptoControl& w_crypto, int64_t& w_consumed) { 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"); + const int iNumBlocks = number_slices(len, iPktLen); - // dynamically increase sender buffer - while (iNumBlocks + m_iCount >= m_iSize) - { - HLOGC(bslog.Debug, - log << "addBufferFromFile: ... still lacking " << (iNumBlocks + m_iCount - m_iSize) << " buffers..."); - increase(); - } + ScopedLock bufferguard(m_BufLock); HLOGC(bslog.Debug, - log << CONID() << "addBufferFromFile: adding " << iPktLen << " packets (" << len - << " bytes) to send, msgno=" << m_iNextMsgNo); + log << "addBufferFromFile: size=" << m_Packets.size() << " reserved=" << m_iCapacity << " needs=" << iPktLen + << " buffers for " << len << " bytes, msg #" << m_iNextMsgNo); + + EncryptionKeySpec kflg = EK_NOENC; - Block* s = m_pLastBlock; int total = 0; for (int i = 0; i < iNumBlocks; ++i) { @@ -268,330 +202,473 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) if (pktlen > iPktLen) pktlen = iPktLen; + SndPktArray::Packet& p = m_Packets.push(); + HLOGC(bslog.Debug, log << "addBufferFromFile: reading from=" << (i * iPktLen) << " size=" << pktlen - << " TO BUFFER:" << (void*)s->m_pcData); - ifs.read(s->m_pcData, pktlen); + << " TO BUFFER:" << (void*)p.m_pcData); + ifs.read(p.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; + p.m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask | PacketBoundaryBitsIfRange(0, i, iNumBlocks); + + kflg = checkEncryption(p, w_crypto); + if (kflg == EK_ERROR) + { + m_Packets.unpush(); + LOGC(qslog.Error, log << CONID() << "ENCRYPT FAILED - packet won't be stored, size=" << p.m_iLength); + return EK_ERROR; + } + + p.m_iLength = pktlen; + p.m_iTTL = SRT_MSGTTL_INF; total += pktlen; } - m_pLastBlock = s; - enterCS(m_BufLock); - m_iCount = m_iCount + iNumBlocks; m_iBytesCount += total; - leaveCS(m_BufLock); - m_iNextMsgNo++; if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) m_iNextMsgNo = 1; - return total; + w_consumed = total; + return kflg; } -int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, int kflgs, int& w_seqnoinc) +EncryptionKeySpec CSndBuffer::checkEncryption(SndPktArray::Packet& w_p, CCryptoControl& w_crypto) { - int readlen = 0; - w_seqnoinc = 0; + EncryptionKeySpec f = w_crypto.getSndCryptoFlags(); + if (f == EK_NOENC || f == EK_ERROR) + return f; // need no encryption || invalid settings + int32_t fake_header[SRT_PH_E_SIZE] = {w_p.m_iSeqNo, w_p.m_iMsgNoBitset, 0, 0}; + if (w_crypto.encrypt(fake_header, (w_p.m_pcData), (w_p.m_iLength)) != ENCS_CLEAR) + { + return EK_ERROR; + } + + w_p.m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(f); + return f; +} + +int CSndBuffer::extractUniquePacket(CSndPacket& w_packet, time_point& w_srctime, int32_t& w_lastseqno, time_point& w_nextuniquets) +{ 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.set_seqno(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.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); - w_srctime = m_pCurrBlock->m_tsOriginTime; - m_pCurrBlock = m_pCurrBlock->m_pNext; + w_nextuniquets = time_point(); // default: none + + // 1. Empty buffer + if (m_Packets.size() == 0) + { + HLOGC(bslog.Debug, log << "extractUniquePacket: sender buffer empty"); + return READ_NONE; + } + + // 2. [unique] [unique] [unique] ([LAST] is in the past) + int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_lastseqno) + 1; + if (offset < 0) // offset could become 0 when the sequence differ by -1; this value is ok. + { + HLOGC(bslog.Debug, log << "extractUniquePacket: negative offset for ++%" << w_lastseqno + << " - shifting to earliest %" << m_iSndLastDataAck.load()); + offset = 0; + } + // 3. [old] [old] [LAST] (no new packets) + else if (offset >= int(m_Packets.size())) + { + // No packet stored after [LAST] + HLOGC(bslog.Debug, log << "extractUniquePacket: for last %" << w_lastseqno << " no next packet; " + << (offset == int(m_Packets.size()) ? "no new packet %" : "SEQUENCE NUMBER EXCEEDED: %") + << CSeqNo::incseq(m_iSndLastDataAck, offset) << " base %" << m_iSndLastDataAck); + return READ_NONE; + } + + time_point entertime = steady_clock::now(); - if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - w_srctime) > p->m_iTTL)) + // REPEATABLE BLOCK + // In the block there will be skipped the TTL-expired messages, if any. + const int packets_size = m_Packets.size(); // prevent re-reading atomic + for ( ; offset < packets_size; ++offset) + { + SndPktArray::Packet* p = &m_Packets[offset]; + w_lastseqno = p->m_iSeqNo; // Will be set again if TTL-skipped + w_srctime = p->m_tsOriginTime; + + if (p->stale(entertime)) { - 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; + // Note: the packet is no longer unique, even though it was never sent. + LOGC(bslog.Warn, log << CONID() << "CSndBuffer: skipping packet %" << p->m_iSeqNo << " #" << p->getMsgSeq() + << " with TTL=" << p->m_iTTL); + + // Just in case, but unique packets should have this field always 0. + p->m_tsNextRexmitTime = time_point(); continue; } - HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: picked up packet to send: size=" << readlen - << " #" << w_packet.getMsgSeq() - << " %" << w_packet.seqno() - << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); + // IMPORTANT: we rewrite w_packet.pkt from p, but p IS NOT w_packet.pkt. + // Only the payload buffer will be shared and that's why its acquired. + w_packet.pkt.m_pcData = p->m_pcData; + w_packet.pkt.setLength(p->m_iLength, m_iBlockLen); + w_packet.acquire((*p), this); - break; - } + w_packet.pkt.set_seqno(p->m_iSeqNo); + w_packet.pkt.set_msgflags(p->m_iMsgNoBitset); - return readlen; -} + HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: UNIQUE packet to send: size=" << p->m_iLength + << " #" << w_packet.pkt.getMsgSeq() + << " %" << w_packet.pkt.seqno() + << " !" << BufferStamp(w_packet.pkt.m_pcData, w_packet.pkt.getLength())); -CSndBuffer::time_point CSndBuffer::peekNextOriginal() const -{ - ScopedLock bufferguard(m_BufLock); - if (m_pCurrBlock == m_pLastBlock) - return time_point(); + if (offset + 1 < packets_size) // WE DO HAVE a next packet after this one + w_nextuniquets = m_Packets[offset+1].m_tsOriginTime; + + return p->m_iLength; + } - return m_pCurrBlock->m_tsOriginTime; + return 0; // marker (should be unreachable) } -int32_t CSndBuffer::getMsgNoAt(const int offset) +int32_t CSndBuffer::getMsgNoAtSeq(const int32_t seqno) { 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)); - } + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); - if (offset >= m_iCount) + if (offset < 0 || offset >= int(m_Packets.size())) { // Prevent accessing the last "marker" block LOGC(bslog.Error, - log << "CSndBuffer::getMsgNoAt: IPE: offset=" << offset << " not found, max offset=" << m_iCount); + log << "CSndBuffer::getMsgNoAtSeq: IPE: for %" << seqno << " offset=" << offset + << " outside container; max offset=" << m_Packets.size()); 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; - } + return m_Packets[offset].getMsgSeq(); +} + +// XXX Likely unused (left for the use by tests) +int CSndBuffer::readOldPacket(int32_t seqno, CSndPacket& w_sndpkt, steady_clock::time_point& w_srctime, DropRange& w_drop) +{ + ScopedLock bufferguard(m_BufLock); - if (!p) + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + if (offset < 0 || offset >= int(m_Packets.size())) { - 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; + LOGC(bslog.Error, log << "CSndBuffer::readOldPacket: for %" << seqno << " offset " << offset << " out of buffer (earliest: %" + << m_iSndLastDataAck << ")!"); + return READ_NONE; } - 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)); + // Unlike receiver buffer, in the sender buffer packets are always stored + // one after another and there are no gaps. Checking the valid range of offset + // suffices to grant existence of a packet. - return p->getMsgSeq(); + w_sndpkt.pkt.set_seqno(seqno); + + return readPacketInternal(offset, (w_sndpkt), (w_srctime), (w_drop)); } -int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) +int CSndBuffer::readPacketInternal(int offset, CSndPacket& w_sndpkt, steady_clock::time_point& w_srctime, DropRange& w_drop) { - // NOTE: w_packet.m_iSeqNo is expected to be set to the value - // of the sequence number with which this packet should be sent. + SndPktArray::Packet* p = &m_Packets[offset]; - 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 READ_NONE; - } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING const int32_t first_seq = p->m_iSeqNo; int32_t last_seq = p->m_iSeqNo; #endif // This is rexmit request, so the packet should have the sequence number // already set when it was once sent uniquely. - SRT_ASSERT(p->m_iSeqNo == w_packet.seqno()); + SRT_ASSERT(p->m_iSeqNo == w_sndpkt.pkt.seqno()); - // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. + if (p->stale(steady_clock::now())) + { + int same_msgno = p->getMsgSeq(); - // 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 packet field to the message id that should be dropped as - // a whole. + int lastx = offset; - // 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. + // This loop may run also 0 times if we have one message per packet. + // This takes also into account that a user may force the message + // number to be also the same as in the previous call. + for (int i = offset+1; i < int(m_Packets.size()); ++i) + { + if (m_Packets[i].getMsgSeq() != same_msgno) + break; - // 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). + // In the meantime, as it goes, revoke it from the retransmission schedule + m_Packets[i].m_tsNextRexmitTime = time_point(); - if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL)) - { - w_drop.msgno = p->getMsgSeq(); - int msglen = 1; - p = p->m_pNext; - bool move = false; - while (p != m_pLastBlock && w_drop.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; - msglen++; + lastx = i; } - HLOGC(qslog.Debug, - log << "CSndBuffer::readData: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " - << msglen << " packets to drop with #" << w_drop.msgno); - - // Theoretically as the seq numbers are being tracked, you should be able - // to simply take the sequence number from the block. But this is a new - // feature and should be only used after refax for the sender buffer to - // make it manage the sequence numbers inside, instead of by CUDT::m_iSndLastDataAck. - w_drop.seqno[DropRange::BEGIN] = w_packet.seqno(); - w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_packet.seqno(), msglen - 1); - - // Note the rules: here `p` is pointing to the first block AFTER the - // message to be dropped, so the end sequence should be one behind - // the one for p. Note that the loop rolls until hitting the first - // packet that doesn't belong to the message or m_pLastBlock, which - // is past-the-end for the occupied range in the sender buffer. - SRT_ASSERT(w_drop.seqno[DropRange::END] == CSeqNo::decseq(p->m_iSeqNo)); + HLOGC(bslog.Debug, + log << "CSndBuffer::extractUniquePacket: due to TTL exceeded, %(" << first_seq << " - " << last_seq << "), " + << (1 + lastx - offset) << " packets to drop with #" << w_drop.msgno); + + m_Packets.remove_loss(lastx); + w_drop.msgno = p->getMsgSeq(); + + w_drop.seqno[DropRange::BEGIN] = w_sndpkt.pkt.seqno(); + w_drop.seqno[DropRange::END] = CSeqNo::incseq(w_sndpkt.pkt.seqno(), lastx - offset); + + // We let the caller handle it, while we state no packet delivered. + // NOTE: the expiration of a message doesn't imply recovation from the buffer. + // Revocation will still happen on ACK. return READ_DROP; } - w_packet.m_pcData = p->m_pcData; + w_sndpkt.pkt.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.set_msgflags(p->m_iMsgNoBitset); + w_sndpkt.pkt.setLength(readlen, m_iBlockLen); + + // We state that the requested seqno refers to a historical (not unique) + // packet, hence the encryption action has encrypted the data and updated + // the flags. + w_sndpkt.pkt.set_msgflags(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.seqno() + ++p->m_iBusy; + w_sndpkt.acquire_busy(p->m_iSeqNo, this); + + HLOGC(bslog.Debug, + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_sndpkt.pkt.seqno() << " size=" << readlen << " to send [REXMIT]"); return readlen; } -sync::steady_clock::time_point CSndBuffer::getPacketRexmitTime(const int offset) +sync::steady_clock::time_point CSndBuffer::getRexmitTime(const int32_t seqno) { 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) + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + if (offset < 0 || offset >= int(m_Packets.size())) + return sync::steady_clock::time_point(); + + return m_Packets[offset].m_tsRexmitTime; +} + +/// Get the first sequence for retransmission and clean up invalid losses. +/// +/// @param [IN] min_rexmit_interval Minimum interval required since the last rexmit (will skip too early request) +/// @param [INOUT] w_current_seqno Current latest sent seqno, to be updated if skipping caused it to be in the past +/// @param [OUT] w_sndpkt The packet extracted and locked (prevented from removal until the reference expires) +/// @param [OUT] w_tsOrigin Sending time of the packet +/// @param [OUT] w_drops Collected drop information to remove packets that had to be dropped + +/// This extracts the first packet eligible for retransmission, bypassing and +/// taking care of those that are in the forgotten region, as well as required +/// to be rejected. Look into the loss list and drop all sequences that are +/// already revoked from the sender buffer, send the drop request if needed, and +/// return the sender buffer offset for the next packet to retransmit, or -1 if +/// there is no retransmission candidate at the moment. + +int CSndBuffer::extractFirstRexmitPacket(const duration& min_rexmit_interval, int32_t& w_current_seqno, CSndPacket& w_sndpkt, + sync::steady_clock::time_point& w_tsOrigin, std::vector& w_drops) +{ + ScopedLock bufferguard(m_BufLock); + + int seq = SRT_SEQNO_NONE; + + int offset = -1; + int payload = 0; // default: no packet extracted + + HLOGC(qslog.Debug, log << "REXMIT: looking for loss report since %" << m_iSndLastDataAck << "..."); + + // REPEATABLE BLOCK (not a real loop) + // The call to readPacketInternal may result in a drop request, which must be + // handled and then the call repeated, until it returns a valid packet + // or no packet to retransmit. + for (;;) { - SRT_ASSERT(p); - p = p->m_pNext; + // This is preferably done only once; exceptionally it may be + // repeated if it turns out that the message has expired + // (a feature used exclusively in message-mode). + offset = m_Packets.extractFirstLoss(min_rexmit_interval); + + // No loss found - return 0: no lost packets extracted. + if (offset == -1) + { + HLOGC(qslog.Debug, log << "REXMIT: no loss found"); + break; + } + + seq = CSeqNo::incseq(m_iSndLastDataAck, offset); + + HLOGC(qslog.Debug, log << "REXMIT: got %" << seq << ", requesting that packet from sndbuf with first %" + << firstSeqNo()); + + // Extract the packet from the sender buffer that is mapped to the expected sequence + // number, bypassing and taking care of those that are decided to be dropped. + + typedef CSndBuffer::DropRange DropRange; + DropRange buffer_drop; + + w_sndpkt.pkt.set_seqno(seq); + + // Might be that if you read THIS packet, it results in a drop request. + // BUT: if you got drop request for this 'offset' (sequence effectively), then + // you won't get this sequence again. Forget this then and pick up the next loss candidate. + payload = readPacketInternal(offset, (w_sndpkt), (w_tsOrigin), (buffer_drop)); + if (payload == CSndBuffer::READ_DROP) + { + SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); + + HLOGC(qslog.Debug, + log << "... loss-reported packets expired in SndBuf - requesting DROP: #" + << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " + << buffer_drop.seqno[DropRange::END] << ")"); + w_drops.push_back(buffer_drop); + + // skip all dropped packets + const int32_t latest = buffer_drop.seqno[DropRange::END]; + w_current_seqno = w_current_seqno == SRT_SEQNO_NONE ? latest : CSeqNo::maxseq(w_current_seqno, latest); + continue; + } + + break; } - SRT_ASSERT(p); - return p->m_tsRexmitTime; + return payload; } -void CSndBuffer::ackData(int offset) +void CSndBuffer::releasePacket(int32_t seqno) { ScopedLock bufferguard(m_BufLock); - bool move = false; - for (int i = 0; i < offset; ++i) + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + if (offset < 0 || offset >= int(m_Packets.size())) + { + // If a release attempt is done, it usually means that this sequence + // IS PRESENT in the buffer; such a situation shouldn't happen. + LOGC(bslog.Fatal, log << "IPE: CSndBuffer::releasePacket:%" << seqno << " with in-buffer %" + << m_iSndLastDataAck << "+" << m_Packets.size() << " - OUT OF RANGE REQUEST"); + return; + } + + if (m_Packets[offset].m_iBusy <= 0) { - m_iBytesCount -= m_pFirstBlock->m_iLength; - if (m_pFirstBlock == m_pCurrBlock) - move = true; - m_pFirstBlock = m_pFirstBlock->m_pNext; + // This is OOS or memover case. + LOGC(bslog.Fatal, log << "IPE: CSndBuffer::releasePacket:%" << seqno << " IS NOT BUSY"); + return; } - if (move) - m_pCurrBlock = m_pFirstBlock; - m_iCount = m_iCount - offset; + --m_Packets[offset].m_iBusy; + + // After releasing this packet try to revoke as many packets + // as possible, up to m_iSndUpdateAck. + IF_HEAVY_LOGGING(bool logged = false); + if (m_iSndUpdateAck != SRT_SEQNO_NONE && m_iSndUpdateAck != m_iSndLastDataAck) + { + int latest_offset = CSeqNo::seqoff(m_iSndLastDataAck, m_iSndUpdateAck); + if (latest_offset > 0) + { + int removed = m_Packets.pop(latest_offset); + m_iSndLastDataAck = CSeqNo::incseq(m_iSndLastDataAck, removed); + HLOGC(bslog.Debug, log << "CSndBuffer::releasePacket %" << seqno << ": ACK-revoked " << removed + << " more packets up to %" << m_iSndLastDataAck); + IF_HEAVY_LOGGING(logged = true); + } + } + IF_HEAVY_LOGGING(if (!logged) LOGC(bslog.Debug, log << "CSndBuffer::releasePacket: %" << seqno << ": non+ pkts revoked")); +} + +CSndBuffer::RevokeStatus CSndBuffer::revoke(int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + // Check for disallowed "crazy" value. + if (abs(offset) > CSeqNo::m_iSeqNoTH / 2 + 1) + return RVK_ROGUE; + + // IF distance between m_iSndLastDataAck and ack is nonempty... + if (offset <= 0) + return RVK_PAST; + + // NOTE: offset points to the first packet that should remain + // in the buffer, hence it's already a past-the-end for the revoked. + // The call is also safe for calling with excessive value of offset. + int popped_up_to = m_Packets.pop(offset); + if (popped_up_to == offset) + { + m_iSndLastDataAck = seqno; + m_iSndUpdateAck = seqno; + HLOGC(bslog.Debug, log << "CSndBuffer::revoke: all up to ACK %" << seqno); + } + else + { + // We have removed less packets than required because some were + // currently reserved as busy, therefore only remember the original + // sequence number so that these packets are removed later. + m_iSndUpdateAck = seqno; + m_iSndLastDataAck = CSeqNo::incseq(m_iSndLastDataAck, popped_up_to); + HLOGC(bslog.Debug, log << "CSndBuffer::revoke: ONLY UP TO first busy %" << m_iSndLastDataAck + << " with postponed ACK %" << m_iSndUpdateAck); + } updAvgBufSize(steady_clock::now()); + return RVK_OK; } -int CSndBuffer::getCurrBufSize() const +bool CSndBuffer::cancelLostSeq(int32_t seq) { - return m_iCount; + ScopedLock bufferguard(m_BufLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); + if (offset < 0 || offset >= int(m_Packets.size())) + return false; + + return m_Packets.clear_loss(offset); } -int CSndBuffer::getMaxPacketLen() const +void CSndBuffer::removeLossUpTo(int32_t seqno) +{ + ScopedLock bufferguard(m_BufLock); + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seqno); + + if (offset < 0 || offset >= int(m_Packets.size())) + return; + + m_Packets.remove_loss(offset); +} + +int CSndBuffer::insertLoss(int32_t seqlo, int32_t seqhi, const time_point& pt) { - return m_iBlockLen - m_iAuthTagSize; + ScopedLock bufferguard(m_BufLock); + int offset_lo = CSeqNo::seqoff(m_iSndLastDataAck, seqlo); + int offset_hi = CSeqNo::seqoff(m_iSndLastDataAck, seqhi); + + return m_Packets.insert_loss(offset_lo, offset_hi, is_zero(pt) ? steady_clock::now(): pt); } -int CSndBuffer::countNumPacketsRequired(int iPldLen) const +int CSndBuffer::getLossLength() { - const int iPktLen = getMaxPacketLen(); - return countNumPacketsRequired(iPldLen, iPktLen); + return m_Packets.loss_length(); } -int CSndBuffer::countNumPacketsRequired(int iPldLen, int iPktLen) const +int CSndBuffer::getCurrBufSize() const { - return (iPldLen + iPktLen - 1) / iPktLen; + return m_Packets.size(); } -namespace { -int round_val(double val) +int CSndBuffer::getMaxPacketLen() const { - return static_cast(round(val)); + return m_iBlockLen - m_iReservedSize; } + +int CSndBuffer::countNumPacketsRequired(int iPldLen) const +{ + const int iPktLen = getMaxPacketLen(); + return number_slices(iPldLen, iPktLen); } int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) @@ -605,9 +682,9 @@ int CSndBuffer::getAvgBufSize(int& w_bytes, int& w_tsp) // 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()); + w_bytes = m_mavg.bytes() + 0.49; + w_tsp = m_mavg.timespan_ms() + 0.49; + return int(m_mavg.pkts() + 0.49); } void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) @@ -617,11 +694,11 @@ void CSndBuffer::updAvgBufSize(const steady_clock::time_point& now) int bytes = 0; int timespan_ms = 0; - const int pkts = getCurrBufSize((bytes), (timespan_ms)); + const int pkts = getBufferStats((bytes), (timespan_ms)); m_mavg.update(now, pkts, bytes, timespan_ms); } -int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) const +int CSndBuffer::getBufferStats(int& w_bytes, int& w_timespan) const { w_bytes = m_iBytesCount; /* @@ -629,48 +706,56 @@ int CSndBuffer::getCurrBufSize(int& w_bytes, int& w_timespan) const * 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; + if (m_Packets.empty()) + w_timespan = 0; + else + w_timespan = count_milliseconds(m_tsLastOriginTime - m_Packets[0].m_tsOriginTime) + 1; - return m_iCount; + + return m_Packets.size(); } CSndBuffer::duration CSndBuffer::getBufferingDelay(const time_point& tnow) const { ScopedLock lck(m_BufLock); - SRT_ASSERT(m_pFirstBlock); - if (m_iCount == 0) + + if (m_Packets.empty()) return duration(0); - return tnow - m_pFirstBlock->m_tsOriginTime; + return tnow - m_Packets[0].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; + int dbytes = 0; + int32_t msgno = 0; + // Reach out to the position that is less than too_late_time, + // counting the bytes + size_t i; + for (i = 0; i < m_Packets.size(); ++i) + { + SndPktArray::Packet& p = m_Packets[i]; + // Stop on first busy or young enough + if (p.m_iBusy || p.m_tsOriginTime >= too_late_time) + break; + dbytes += p.m_iLength; + msgno = p.getMsgSeq(); } - if (move) + // Now delete all these packets from the container + if (i) { - m_pCurrBlock = m_pFirstBlock; + // As the loop stopped on first busy, we ignore the return value + // because there should be no busy packets in this range. + m_Packets.pop(i); + + const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, i); + m_iSndLastDataAck = fakeack; } - m_iCount = m_iCount - dpkts; - m_iBytesCount -= dbytes; - w_bytes = dbytes; + w_bytes = dbytes; // even if 0 // We report the increased number towards the last ever seen // by the loop, as this last one is the last received. So remained @@ -679,71 +764,992 @@ int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_ updAvgBufSize(steady_clock::now()); - return (dpkts); + return i; +} + +int CSndBuffer::dropAll(int& w_bytes) +{ + ScopedLock bufferguard(m_BufLock); + // clear all + + int dpkts = m_Packets.size(); + m_Packets.clear(); + w_bytes = m_iBytesCount; + m_iBytesCount = 0; + + updAvgBufSize(steady_clock::now()); + return dpkts; +} + +CSndBuffer::~CSndBuffer() +{ + releaseMutex(m_BufLock); +} + +string CSndBuffer::show(int32_t lastsent_seqno) const +{ + using namespace hvu; + ofmtbufstream out; + + int minw = 2; + if (m_Packets.size() > 99) + minw = 3; + else if (m_Packets.size() > 999) + minw = 4; + + fmtc findex = fmtc().width(minw).fillzero(); + + int unique_index = -1; + if (lastsent_seqno != SRT_SEQNO_NONE) + { + unique_index = CSeqNo::seqoff(m_iSndLastDataAck, lastsent_seqno); + if (unique_index < 0 || unique_index >= int(m_Packets.size())) + unique_index = -1; + } + + ScopedLock bufferguard(m_BufLock); + + SndPktArray::PacketShowState st; + for (size_t i = 0; i < m_Packets.size(); ++i) + { + int seqno = CSeqNo::incseq(m_iSndLastDataAck, i); + out << "[" << fmt(i, findex) << "]%" << seqno << ": "; + m_Packets.showline(i, unique_index, (st), (out)); + out.base() << endl; + } + + return out.str(); +} + + +// SndPktArray implementation + +int SndPktArray::next_loss(int current_loss) +{ + if (current_loss == -1) + return -1; + + SRT_ASSERT(current_loss < int(m_PktQueue.size())); + + Packet& p = m_PktQueue[current_loss]; + SRT_ASSERT(p.m_iLossLength > 0); + + if (p.m_iNextLossGroupOffset == 0) + return -1; // The last loss + + SRT_ASSERT(p.m_iLossLength < p.m_iNextLossGroupOffset); + + SRT_ASSERT(current_loss + p.m_iNextLossGroupOffset < int(m_PktQueue.size())); + + return current_loss + p.m_iNextLossGroupOffset; +} + +void SndPktArray::remove_loss(int last_to_clear) +{ + // This is going to remove the loss records since the first one + // up to the packet designated by the last_to_clear offset (same as pop()). + + // this empty() is just formally - with empty m_iFirstRexmit should be moreover -1 + if (m_iFirstRexmit == -1 || m_PktQueue.empty()) // no loss records + return; // last is also -1 in this situation + + const int LASTX = int(m_PktQueue.size()) - 1; + + // Handle special case: if last_to_clear is the last index in the container, + // simply remove everything. Just update all the loss nodes. + if (last_to_clear >= LASTX) + { + for (int loss = m_iFirstRexmit, next; loss != -1; loss = next) + { + // use safe-loop rule because the node data will be cleared here. + next = next_loss(loss); + SndPktArray::Packet& p = m_PktQueue[loss]; + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + } + m_iFirstRexmit = -1; + m_iLastRexmit= -1; + m_iLossLengthCache = 0; + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + return; + } + + // The iteration rule here: + // Make calculations on the indexes with unchanged relative values. + // That is, just clear the records that point to a less index than last_to_clear. + // Values of m_iFirstRexmit and m_iLastRexmit still refer to the unchanged + // indexes in the container, just must be outside the removed region. + int removed_loss_length = 0; + int first_to_clear = -1; + for (;;) + { + if (last_to_clear < m_iFirstRexmit) + { + // Found at THIS RECORD (possibly having dismissed earlier ones) + // that it's already in the non-revoked region. + + // Update with the length of every loss record removed in this loop. + m_iLossLengthCache = m_iLossLengthCache - removed_loss_length; + // That's it, nothing more to do. + break; + } + + if (first_to_clear == -1) + first_to_clear = m_iFirstRexmit; + + // Ride until you find a split-in-half record, + // a new record beyond last_to_clear, or no more records. + SndPktArray::Packet& p = m_PktQueue[m_iFirstRexmit]; + + int last_index = m_iFirstRexmit + p.m_iLossLength - 1; + if (last_to_clear < last_index) + { + // split-in-half case. This will be the last on which the operation is done. + + int new_beginning = last_to_clear + 1; // The case when last_to_clear == m_PktQueue.size() - 1 is handled already + SRT_ASSERT(new_beginning > m_iFirstRexmit); + SRT_ASSERT(new_beginning < int(m_PktQueue.size())); + + int revoked_length_fragment = new_beginning - m_iFirstRexmit; + + // Now shift the position + bool is_last = false; + m_PktQueue[new_beginning].m_iLossLength = p.m_iLossLength - revoked_length_fragment; + if (p.m_iNextLossGroupOffset) + { + int next_index = m_iFirstRexmit + p.m_iNextLossGroupOffset; + // Replicate the distance at the new index + m_PktQueue[new_beginning].m_iNextLossGroupOffset = next_index - new_beginning; + SRT_ASSERT(new_beginning + m_PktQueue[new_beginning].m_iNextLossGroupOffset < int(m_PktQueue.size())); + } + else + { + // No next group, this is the last one. + m_PktQueue[new_beginning].m_iNextLossGroupOffset = 0; + is_last = true; + } + + // Cancel the previous first node + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + + // NOTE: the new values of m_iFirstRexmit and m_iLastRexmit set here + // are valid indexes AFTER REMOVAL of the revoked elements from m_PktQueue. + m_iFirstRexmit = new_beginning; + if (is_last) + m_iLastRexmit = m_iFirstRexmit; + // If not last, there is some record next to first which remains last. + + // Removed were all previous completely skipped record before length last_to_clear, + // plus a fragment of the record that was split in half. + m_iLossLengthCache = m_iLossLengthCache - removed_loss_length - revoked_length_fragment; + + break; + } + + // Check if this one was the last record; if so, we have cleared all. + if (p.m_iNextLossGroupOffset == 0) + { + p.m_iLossLength = 0; + m_iFirstRexmit = -1; + m_iLastRexmit = -1; + m_iLossLengthCache = 0; + break; + } + + // Remaining case: the whole record is below last_to_clear (so remove it and try next) + // Remove means that you need to clear this packet from being a hook of + // a loss record, and move m_iFirstRexmit to the next record. + removed_loss_length += p.m_iLossLength; + m_iFirstRexmit += p.m_iNextLossGroupOffset; + + p.m_iLossLength = 0; + p.m_iNextLossGroupOffset = 0; + } + + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); +} + +bool SndPktArray::clear_loss(int index) +{ + // Just access the record. Now return false only + // if it turns out that it has never been rexmit-scheduled. + SndPktArray::Packet& p = m_PktQueue[index]; + if (is_zero(p.m_tsNextRexmitTime)) + return false; + + p.m_tsNextRexmitTime = time_point(); + return true; +} + +void SndPktArray::clear() +{ + // pop() will do erase(begin(), end()) in this case, + // but will also destroy every packet. + pop(size()); +} + +SndPktArray::Packet& SndPktArray::push() +{ + m_PktQueue.push_back(Packet()); + m_iCachedSize = m_PktQueue.size(); + + Packet& that = m_PktQueue.back(); + + IF_HEAVY_LOGGING(size_t storage_before = m_Storage.storage.size()); + + // Allocate the packet payload space + that.m_iBusy = 0; + that.m_iLength = m_Storage.blocksize; + that.m_pcData = m_Storage.get(); + + HLOGC(bslog.Debug, log << "SndPktArray::push: new buffer (" + << (storage_before == m_Storage.storage.size() ? "ALLOCATED" : "RECYCLED") + << "), active " << m_iCachedSize.load() << ", archived " << m_Storage.storage.size() << " buffers"); + + // Return as is - without initialized fields. + return that; } -void CSndBuffer::increase() +// 'n' is the past-the-end index for removal +size_t SndPktArray::pop(size_t n) { - int unitsize = m_pBuffer->m_iSize; + if (m_PktQueue.empty() || !n) + return 0; // The size is also unchanged + + if (n > m_PktQueue.size()) + n = m_PktQueue.size(); + + // We consider that this call clears off losses in the container + // calls from 0 to n (inc). - // new physical buffer - Buffer* nbuf = NULL; - try + // NOTE: Losses are removed anyway, regardless of the busy status. + remove_loss(n-1); // remove_loss includes given index + + deque::iterator i = m_PktQueue.begin(), first_stay = i + n; + for (; i != first_stay; ++i) { - nbuf = new Buffer; - nbuf->m_pcData = new char[unitsize * m_iBlockLen]; + // Stop at first busy. + if (i->m_iBusy) + { + //prematurely interrupted; update n. + first_stay = i; + n = std::distance(m_PktQueue.begin(), first_stay); + break; + } + // Deallocate storage + m_Storage.put(i->m_pcData); } - catch (...) + + m_PktQueue.erase(m_PktQueue.begin(), first_stay); + m_iCachedSize = m_PktQueue.size(); + + HLOGC(bslog.Debug, log << "SndPktArray::pop: released " << n << " buffers, active " + << m_iCachedSize.load() << ", archived " << m_Storage.storage.size() << " buffers"); + + // These are indexes into the m_PktQueue container, so with + // removed n elements, their position in the container also + // gets shifted by n. + if (m_iFirstRexmit != -1) { - delete nbuf; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + // After remove_loss(), these indexes were updated so that they + // do not (*should* not) refer to any elements earlier than n. + SRT_ASSERT(m_iFirstRexmit >= int(n)); + SRT_ASSERT(m_iLastRexmit >= m_iFirstRexmit); + + m_iFirstRexmit -= n; + m_iLastRexmit -= n; } - 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; + return n; +} - // new packet blocks - Block* nblk = NULL; - try +// Destructor does the same as pop(size()), except that we can´t deny deletion +// for any reason. If m_iBusy found, all we can do is to issue an error log, +// but deletion must still happen otherwise it will be a leak. +SndPktArray::~SndPktArray() +{ + for (deque::iterator i = m_PktQueue.begin(); + i != m_PktQueue.end(); ++i) { - nblk = new Block; + // Stop at first busy. + if (i->m_iBusy) + { + LOGC(bslog.Fatal, log << "IPE: CSndBuffer.Array packet =" << distance(m_PktQueue.begin(), i) + << " %" << i->m_iSeqNo << " HAS STILL " << i->m_iBusy << " USERS!"); + } + // Deallocate storage + m_Storage.put(i->m_pcData); } - catch (...) + + m_PktQueue.clear(); +} + +bool SndPktArray::insert_loss(int offset_lo, int offset_hi, const time_point& next_rexmit_time) +{ + // Can't install loss to an empty container + if (m_PktQueue.empty()) { - delete nblk; - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + HLOGC(bslog.Debug, log << "insert_loss: no packets, no loss inserted"); + return false; } - Block* pb = nblk; - for (int i = 1; i < unitsize; ++i) + + // Fix the indexes if they are out of bound. Note that they can potentially + // be very far from the point, but any rollovers should be ignored (there's + // not much we can do about it). Check only if this really is lo-hi relationship + // and whether at least a fragment of the range is in the buffer. + + if (offset_lo > offset_hi || offset_hi < 0 || offset_lo >= int(m_PktQueue.size())) { - pb->m_pNext = new Block; - pb = pb->m_pNext; + HLOGC(bslog.Debug, log << "insert_loss: invalid offset range " << offset_lo << "..." << offset_hi + << " with size=" << m_PktQueue.size()); + return false; } - // insert the new blocks onto the existing one - pb->m_pNext = m_pLastBlock->m_pNext; - m_pLastBlock->m_pNext = nblk; + if (offset_lo < 0) + { + offset_lo = 0; + } - pb = nblk; - char* pc = nbuf->m_pcData; - for (int i = 0; i < unitsize; ++i) + // It was checked that size() is at least 1 + if (offset_hi >= int(m_PktQueue.size())) { - pb->m_pcData = pc; - pb = pb->m_pNext; - pc += m_iBlockLen; + offset_hi = m_PktQueue.size() - 1; } - m_iSize += unitsize; + HLOGC(bslog.Debug, log << "insert_loss: INSERTING offset " << offset_lo << "..." << offset_hi); - HLOGC(bslog.Debug, - log << "CSndBuffer: BUFFER FULL - adding " << (unitsize * m_iBlockLen) << " bytes spread to " << unitsize - << " blocks" - << " (total size: " << m_iSize << " bytes)"); + int loss_length = offset_hi - offset_lo + 1; + + // Ok, check now where the position is towards the + // existing records. + // + // First: check if there are no records yet. + if (m_iFirstRexmit == -1) + { + // Add just one record and mark in both. + SndPktArray::Packet& p = m_PktQueue[offset_lo]; + p.m_iNextLossGroupOffset = 0; + p.m_iLossLength = loss_length; + m_iFirstRexmit = m_iLastRexmit = offset_lo; + + m_iLossLengthCache = loss_length; + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + + HLOGC(bslog.Debug, log << "insert_loss: 1&1 record: " + << offset_lo << "..." << offset_hi << " (" << loss_length << " cells)"); + + SRT_ASSERT(offset_lo + loss_length <= int(m_PktQueue.size())); + + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + return true; + } + + // Now we have at least one element, so we treat this now as + // a general case, where we need to find the following: + // - ranges that are before offset_hi + // - ranges that are after offset_lo + // - if no such ranges on any side, set up new_first and new_last + // All other ranges are "joint"; all need to be removed + // and a new node should be defined. + + // NOTE: all local variables from here on must have suffix: + // - _index : position of a meaningful element + // - _shift : relative offset between two indexes + // - _end : the past-the-end INDEX value (an element following + // the last element in the range) + + int last_node_end = getEndIndex(m_iLastRexmit); + int offset_end = offset_hi + 1; + + // Step 1: determine the surrounding ranges. + int before_node_index = -1,// last node disjoint before the current one + after_node_index = -1, // first node disjoint after the current one + lowest_inserted_index = offset_lo, + highest_inserted_index = offset_hi; + + vector removed_node_indexes; + + // 1a. Disjoint preceding/succeeding ranges + + bool outside_disjoint = false, outside_disjoint_front = false; + + if (offset_lo < m_iFirstRexmit) + { + // if offset_end == m_iFirstRexmit, they are glued together! + if (offset_end < m_iFirstRexmit) + { + // We have the very first node. So, all nodes are disjoint after. + after_node_index = m_iFirstRexmit; + outside_disjoint = true; + outside_disjoint_front = true; + } + } + else if (offset_hi > last_node_end) + { + if (offset_lo > last_node_end) + { + before_node_index = m_iLastRexmit; + outside_disjoint = true; + } + } + + // Ok, handle the outside disjoint case now; there's no need + // to do any looping in this case, just hook up the nodes. + if (outside_disjoint) + { + int extra_length; + if (outside_disjoint_front) + { + int previous_first_index = m_iFirstRexmit; + m_iFirstRexmit = offset_lo; + extra_length = setupNode(offset_lo, offset_hi, previous_first_index); + HLOGC(bslog.Debug, log << "insert_loss: DISJOINT front: [INSERTED] | " << previous_first_index); + } + else // outside disjoint back + { + int previous_last_index = m_iLastRexmit; + m_iLastRexmit = offset_lo; + extra_length = offset_hi - offset_lo + 1; + + HLOGC(bslog.Debug, log << "insert_loss: DISJOINT back: " << previous_last_index << "..." + << (getEndIndex(previous_last_index)-1) << " | [INSERTED]"); + + // Length remains unchanged; just pin in the new last one. + m_PktQueue[previous_last_index].m_iNextLossGroupOffset = offset_lo - previous_last_index; + setupNode(offset_lo, offset_hi); + } + + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + + m_iLossLengthCache = m_iLossLengthCache + extra_length; + return true; + } + + // Now we need to walk through the elements to separate cases: + // - PREDECESSOR: node with end < offset_lo + // - SUCCESSOR: node with first > offset_hi + 1 (offset_end) + // - OVERLAPPING: nodes (possibly series) that satisfy none of the above. + // While searching for overlapping you may find, after cutting off + // all PREDECESSOR, that the next node is a SUCCESSOR. If this is found, + // it's a MIDDLE-DISJOINT and should be handled in place. + + // The loop doesn't have the same body in both cases, so use a disjoint loop. + + // Immutables: + // Inserted range is overlapping or sticking to any of the existing ranges. + // NOTE: embraces cases when: + // - offset_hi == 2, m_iFirstRexmit == 3 (adjacent) + // - offset_hi == 0, m_iFirstRexmit == 0 (then it will be 0 >= -1) + SRT_ASSERT(offset_hi >= m_iFirstRexmit - 1 && offset_lo <= last_node_end); + + int iloss = m_iFirstRexmit, iloss_end = m_iFirstRexmit; // complaints + + // Collect all nodes that precede the inserted one first. + for (; iloss != -1; iloss = next_loss(iloss)) + { + iloss_end = getEndIndex(iloss); + + // [iloss ... ] iloss_end) | offset_lo ... + if (iloss_end < offset_lo) + { + // PREDECESSOR. + // Continue, but rewrite that as last found such record + before_node_index = iloss; + // NOTE: this node stays as is. + } + // [offset_lo ... offset_hi] | [iloss ... iloss_end) + else if (iloss > offset_end) + { + // MIDDLE-DISJOINT. + // HANDLE IT HERE, as this is also a simple insertion. + // - before_node_index: the node to which this is inserted as next. + // - new_next_index: the node inserted to this a next + int new_next_index = iloss; + int added_length = setupNode(offset_lo, offset_hi, new_next_index); + + // Should be not possible because a case when m_iFirstRexmit > offset_hi + // is already handled as "very first" (one of outside_disjoint). + SRT_ASSERT(before_node_index != -1); + + HLOGC(bslog.Debug, log << "insert_loss: DISJOINT middle: ..." + << (getEndIndex(before_node_index)-1) << " | [INSERTED] | " << new_next_index); + + m_PktQueue[before_node_index].m_iNextLossGroupOffset = offset_lo - before_node_index; + + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + + m_iLossLengthCache = m_iLossLengthCache + added_length; + return true; + } + // [iloss ... | offset_lo ... offset_hi | iloss_end) + // or + // [iloss ... | offset_lo | iloss_end) ... offset_hi + // or + // offset_lo ... | [iloss ... iloss_end] ... offset_hi + // + // We don't care so far where offset_hi is towards the rest of the ranges. + // That's about to be seen in the next loop continuation. + else + { + // By elimination, this is OVERLAPPING. + // Stop here and note the earliest index + lowest_inserted_index = std::min(iloss, offset_lo); + break; + } + } + + // One special case can be handled here: if the newly inserted range + // is completely covered by the node pointed by iloss. + if (offset_lo >= iloss && offset_end <= iloss_end) + { + HLOGC(bslog.Debug, log << "insert_loss: SWALLOW: " << iloss << "..." << (iloss_end-1)); + // Just update the time for the requested range, but do nothing else. + // The inserted records completely overlap with the existing ones, + // so no changes are necessary, except updating the retransmission time. + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + return true; + } + + // Here we have hit the first node OVERLAPPING with the inserted one, + // possibly one of the series. Continue looping to find the first + // following disjoint, if any. + for (; iloss != -1; iloss = next_loss(iloss)) + { + if (iloss > offset_end) + { + // This may never happen, if there's no following disjoint. + // This will not happen in the first iteration (handled already). + after_node_index = iloss; + break; + } + removed_node_indexes.push_back(iloss); + + iloss_end = getEndIndex(iloss); + highest_inserted_index = std::max(offset_end, iloss_end) - 1; + } + + // Current situation: + // + // [predecessors...; before_node_index...end] | + // [lowest_inserted_index ... highest_inserted_index] | + // [after_node_index...end; successors...] + // If no predecessors, before_node_index == -1. + // If no successors, after_node_index == -1. + + // 1. remove all nodes qualified as overlapping (even if the first + // one begins the newly inserted range). + int removed_length = 0; + IF_HEAVY_LOGGING(int removed_nodes_n = removed_node_indexes.size()); + for (size_t i = 0; i < removed_node_indexes.size(); ++i) + { + int x = removed_node_indexes[i]; + removed_length += m_PktQueue[x].m_iLossLength; + m_PktQueue[x].m_iLossLength = 0; + m_PktQueue[x].m_iNextLossGroupOffset = 0; + } + + // 2. Insert a new node at `lowest_inserted_index` length up to `highest_inserted_index` + int inserted_length = highest_inserted_index - lowest_inserted_index + 1; + + // We do not insert empty ranges anyway. + SRT_ASSERT(inserted_length > 0); + + // Could be false, unless we have handled the "swallow" case already. + SRT_ASSERT(inserted_length > removed_length); + + HLOGC(bslog.Debug, log << "insert_loss: REPLACED " << removed_nodes_n << " nodes with new " + << lowest_inserted_index << "..." << highest_inserted_index << " FOLLOWS:" << after_node_index + << " PRECEDES: " << before_node_index << "..." << (getEndIndex(before_node_index)-1)); + + m_PktQueue[lowest_inserted_index].m_iLossLength = inserted_length; + + // 6. if `after_node_index`, set it as next to this - otherwise set 0 next + if (after_node_index != -1) + { + m_PktQueue[lowest_inserted_index].m_iNextLossGroupOffset = after_node_index - lowest_inserted_index; + } + else + { + m_PktQueue[lowest_inserted_index].m_iNextLossGroupOffset = 0; + // This one is the very last then. + m_iLastRexmit = lowest_inserted_index; + } + + // 5. if `before_node_index`, set this one as next to it. + if (before_node_index != -1) + { + m_PktQueue[before_node_index].m_iNextLossGroupOffset = lowest_inserted_index - before_node_index; + } + else + { + m_iFirstRexmit = lowest_inserted_index; + } + + // Update the length + m_iLossLengthCache = m_iLossLengthCache + inserted_length - removed_length; + + // Set the rexmit time only to the range that was requested to be inserted, + // even if this is effectively a fragment of a record. + update_next_rexmit_time(offset_lo, offset_hi, next_rexmit_time); + string msg SRT_ATR_UNUSED; + SRT_ASSERT(validateLossIntegrity((msg))); + return true; } +bool SndPktArray::validateLossIntegrity(std::string& w_message) +{ + if (m_iFirstRexmit == -1) + { + w_message = "Only first empty"; + return m_iLastRexmit == -1; + } + + // Now First is not -1, last must be this one or later + if (m_iLastRexmit == -1) + { + w_message = "Only last empty"; + return false; + } + + // Ok, both have values, check relationship + if (m_iFirstRexmit > m_iLastRexmit) + { + w_message = "FIRST > LAST inconsistency!"; + return false; + } + + // Now trace the whole buffer if elements are consistent: + // - all elements that are not loss nodes, must have len & next = 0 + // - nodes that mark loss area, must have their data > 0, or last should be == 0 + + // First, take the easiest part if there's only one loss. + bool result = true; + if (m_iFirstRexmit == m_iLastRexmit) + { + for (size_t i = 0; i < m_PktQueue.size(); ++i) + { + SndPktArray::Packet& p = m_PktQueue[i]; + if (int(i) == m_iFirstRexmit) + { + // For this, check if the length is > 0 and if + // if fits in the container, also next must be 0. + if (p.m_iNextLossGroupOffset != 0 + || p.m_iLossLength < 1 + || (p.m_iLossLength + i) > m_PktQueue.size()) + { + w_message += "WRONG DATA at (the only) loss position; "; + result = false; + } + // But still check the others + } + else + { + // If this isn't the node marking element, all must be 0. + if (p.m_iNextLossGroupOffset != 0 + || p.m_iLossLength != 0) + { + w_message += hvu::fmtcat("Non-node element ", i, " has wrong data; "); + } + } + } + return result; + } + + // Now trace everything since the beginning, using states. + PacketShowState st; + + hvu::ofmtbufstream os; + + int last_node = m_iFirstRexmit; + for (size_t i = 0; i < m_PktQueue.size(); ++i) + { + SndPktArray::Packet& p = m_PktQueue[i]; + + if (st.next_loss_begin == -1) + { + // Before any loss report yet. + if (int(i) == m_iFirstRexmit) + { + // Hit the first one. Check and record. + // First record must have next because we have handled the single case already + if (p.m_iLossLength < 1 || p.m_iNextLossGroupOffset < 2) + { + os << "FIRST@multiple hit wrong data: len=" << p.m_iLossLength + << " off=" << p.m_iNextLossGroupOffset << " ; "; + result = false; + } + + // Check if exceeds container + int remaining_length = int(m_PktQueue.size()) - i; + int next_index = i + p.m_iNextLossGroupOffset; + + if (next_index >= int(m_PktQueue.size()) || p.m_iLossLength > remaining_length) + { + os << "FIRST@multiple: wrong offset data; "; + result = false; + } + + // Still, we caught the first record, so update + // the state. + st.next_loss_begin = next_index; + st.remain_loss_group = p.m_iLossLength; // it includes [i] ! + continue; + } + + // No data with no previous record yet. + if (p.m_iLossLength != 0 || p.m_iNextLossGroupOffset != 0) + { + os << "WRONG DATA on = int(m_PktQueue.size()) || p.m_iLossLength > remaining_length) + { + os << "AT #" << i << ": wrong offset data; "; + result = false; + } + + if (st.remain_loss_group) + { + os << "AT #" << i << ": still expected " << st.remain_loss_group << " loss packets; "; + result = false; + } + + last_node = i; + + // Still, we caught the first record, so update + // the state. + st.next_loss_begin = i + p.m_iNextLossGroupOffset; + st.remain_loss_group = p.m_iLossLength; // it includes [i] ! + continue; + } + + if (st.remain_loss_group) + { + // update the current one + --st.remain_loss_group; + + // If the counter has reached 0, this is the + // first cell past the loss record, but it is + // still a separation record, so it must be 0 as well. + } + + // Anyways, we expect here no node data + if (p.m_iLossLength || p.m_iNextLossGroupOffset) + { + os << "AT #" << i << ", group remain " << st.remain_loss_group << ", unexpected nonzero node data; "; + result = false; + } + } + + if (last_node != m_iLastRexmit) + { + os << "LAST found " << last_node << " != last=" << m_iLastRexmit << "; "; + result = false; + } + + if (!result) + w_message = os.str(); + + return result; + +} + +int SndPktArray::extractFirstLoss(const duration& miniv) +{ + // No loss at all + if (m_iFirstRexmit == -1) + return -1; + + // Note that records may have: + // - "zombie": marked for non-eligible by cleared time - remove if possible + // - "too new": such time that time + miniv > now() - this record must remain + // + // If during the search there was no "too new" record found yet, remove + // every "zombie" on the way, and also the extracted record. If any "too + // new" was found, do not remove anything anymore and the qualified record + // should be also marked "zombie" (none found is also possible). + int stop_revoke = -1; + + time_point now = steady_clock::now(); + + int last_cleared = -1; + + bool skipped_too_new = false; + + // Walk over the container to find the valid loss sequence + for (int loss_begin = m_iFirstRexmit; loss_begin != -1; loss_begin = next_loss(loss_begin)) + { + int loss_end = loss_begin + m_PktQueue[loss_begin].m_iLossLength; + + for (int i = loss_begin; i != loss_end; ++i) + { + SndPktArray::Packet& p = m_PktQueue[i]; + if (!is_zero(p.m_tsNextRexmitTime)) + { + // Ok, so this cell will be taken, but it might be the future. + if (!p.updated_rexmit_time_passed(now, miniv)) + { + if (stop_revoke == -1 && i > 0) + stop_revoke = i - 1; + skipped_too_new = true; + HLOGC(qslog.Debug, log << "... skipped +" << i << " - too early by " + << FormatDurationAuto(now + miniv - p.m_tsNextRexmitTime)); + continue; + } + + // Clear that packet from being rexmit-eligible. + p.m_tsNextRexmitTime = time_point(); + + if (stop_revoke == -1) + { + HLOGC(qslog.Debug, log << "... FOUND +" << i << " - removing up to this one"); + remove_loss(i); // Remove all previous loss records, including this one + } + else + { + HLOGC(qslog.Debug, log << "... FOUND +" << i << " - removing up to +" << stop_revoke); + remove_loss(stop_revoke); + } + return i; + } + else + { + HLOGC(qslog.Debug, log << "... skipped +" << i << " - cleared earlier"); + if (!skipped_too_new) + last_cleared = i; + } + // If it was cleared, continue searching. + } + } + + if (last_cleared != -1) + remove_loss(last_cleared); + + return -1; +} + +// Debug support +string SndPktArray::show_external(int32_t seqno, int32_t lastsent_seqno) const +{ + using namespace hvu; + ofmtbufstream out; + + int minw = 2; + if (size() > 99) + minw = 3; + else if (size() > 999) + minw = 4; + + fmtc findex = fmtc().width(minw).fillzero(); + + int unique_index = -1; + if (lastsent_seqno != SRT_SEQNO_NONE) + { + unique_index = CSeqNo::seqoff(seqno, lastsent_seqno); + if (unique_index < 0 || unique_index >= int(size())) + unique_index = -1; + } + + SndPktArray::PacketShowState st; + for (size_t i = 0; i < size(); ++i) + { + seqno = CSeqNo::incseq(seqno); + out << "[" << fmt(i, findex) << "]%" << seqno << ": "; + showline(i, unique_index, (st), (out)); + out.base() << endl; + } + + return out.str(); +} + +void SndPktArray::showline(int index, int unique_index, PacketShowState& st, hvu::ofmtbufstream& out) const +{ + const Packet& p = m_PktQueue[index]; + + if (is_zero(st.begin_time)) + st.begin_time = steady_clock::now(); + + out << p.m_iLength << "!" << BufferStamp(p.m_pcData, p.m_iLength); + + // Check beginning of the new loss group + if (index == m_iFirstRexmit) + { + if (st.remain_loss_group || st.next_loss_begin != -1) + out << " *** UNEXPECTED rem=" << st.remain_loss_group << " next=" << st.next_loss_begin << " at first=" << m_iFirstRexmit; + + // Configure context object + st.remain_loss_group = p.m_iLossLength; + st.next_loss_begin = p.m_iNextLossGroupOffset ? index + p.m_iNextLossGroupOffset : -1; + if (st.remain_loss_group == 0) + out << " *** UNEXPECTED index=" << index << " marked next, but length=0!"; + } + else if (index == st.next_loss_begin) + { + if (st.remain_loss_group) + out << " *** UNEXPECTED rem=" << st.remain_loss_group << " next=" << st.next_loss_begin << " at first=" << m_iFirstRexmit; + + // Configure context object + st.remain_loss_group = p.m_iLossLength; + st.next_loss_begin = p.m_iNextLossGroupOffset ? index + p.m_iNextLossGroupOffset : -1; + if (st.remain_loss_group == 0) + out << " *** UNEXPECTED index=" << index << " marked next, but length=0!"; + } + else + { + if (p.m_iLossLength || p.m_iNextLossGroupOffset) + out << " *** UNEXPECTED subseq loss-len=" << p.m_iLossLength << " next=" << p.m_iNextLossGroupOffset; + } + + if (st.remain_loss_group) + { + out << " L."; + if (is_zero(p.m_tsNextRexmitTime)) + out << "0"; + else + out << FormatDurationAuto(st.begin_time - p.m_tsNextRexmitTime); + + out << "/" << st.remain_loss_group; + + --st.remain_loss_group; + } + + if (index >= unique_index) + { + out << " NEW"; + } + + if (p.m_iBusy) + { + out << " <" << p.m_iBusy << ">"; + } +} + + } // namespace srt diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index afd52110b..16b95aa2e 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -53,29 +53,274 @@ modified by #ifndef INC_SRT_BUFFER_SND_H #define INC_SRT_BUFFER_SND_H +#include + #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 +// import .crypto:default +namespace srt { class CCryptoControl; } namespace srt { +class CSndBuffer; + +struct CSndBlock +{ + typedef sync::steady_clock::time_point time_point; + 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 and special bit flags + int32_t m_iSeqNo; //< sequence number for scheduling + time_point m_tsOriginTime; //< 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) XXX change to duration? + + int32_t getMsgSeq() const + { + return m_iMsgNoBitset & MSGNO_SEQ::mask; + } + + bool stale(const time_point& since) const + { + if (m_iTTL <= 0) + return false; + + int64_t elapsed_ms = sync::count_milliseconds(since - m_tsOriginTime); + return elapsed_ms > m_iTTL; + } +}; + +struct SndPktArray +{ + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; + + // Note: this structure has no constructor, so fields must be updated + // upon creation. Currently only m_PktQueue.push_back() calls do this. + struct Packet: CSndBlock + { + time_point m_tsNextRexmitTime; + int m_iLossLength; + int m_iNextLossGroupOffset; + int m_iBusy; + + bool updated_rexmit_time_passed(const time_point& now, const duration& miniv) + { + // We don't check this; just make sure about that during the call + // --- [[assert (!is_zero(m_tsNextRexmitTime)]] + + // 1. Fix m_tsNextRexmitTime if it's too early after m_tsRexmitTime. + // 2. After that, check if m_tsNextRexmitTime is in the past. + if (miniv != duration() && !sync::is_zero(m_tsRexmitTime)) + { + const duration rxiv = m_tsNextRexmitTime - m_tsRexmitTime; + if (rxiv < miniv) + m_tsNextRexmitTime = m_tsRexmitTime + miniv; + } + + return m_tsNextRexmitTime < now; + } + }; + +private: + + /// Packet memory cache + BufferedMessageStorage m_Storage; + + /// Container for the packets; managed internally with data consistency. + std::deque m_PktQueue; + + /// Kept in sync with m_PktQueue.size() to allow calling size() without locking. + sync::atomic m_iCachedSize; + + // Retransmission list fields: + int m_iFirstRexmit;//< Index of the first record; -1 if no losses. + int m_iLastRexmit; //< Index of the last record; -1 if no losses. + + /// Cached loss length. This is rarely required, but algorithms + /// for NAK report use it to determine the period. + sync::atomic m_iLossLengthCache; + +public: + + SndPktArray(size_t payload_len, size_t max_packets, size_t reserved): + m_Storage(payload_len, max_packets), + m_iCachedSize(0), + m_iFirstRexmit(-1), + m_iLastRexmit(-1), + m_iLossLengthCache(0) + { + m_Storage.reserve(reserved); + } + + ~SndPktArray(); + + // Expose for TESTING PURPOSES + int first_loss() const { return m_iFirstRexmit; } + int last_loss() const { return m_iLastRexmit; } + + void force_next_time(int offset, const time_point& newtime) + { + // NOTE: offset is not checked here. Use for testing only! + m_PktQueue[offset].m_tsNextRexmitTime = newtime; + } + + Packet& push(); + + // This reverses the last push() operation - removes the packet + // that was previously added by push(). + void unpush() + { + SRT_ASSERT(!m_PktQueue.empty()); + if (m_PktQueue.empty()) + return; + + Packet& pkt_to_delete = m_PktQueue.back(); + m_Storage.put(pkt_to_delete.m_pcData); + + // pkt_to_delete will be invalidated here + m_PktQueue.pop_back(); + } + size_t pop(size_t n = 1); + + void remove_loss(int n); + + bool clear_loss(int index); + + bool insert_loss(int ixlo, int ixhi, const time_point& nowtime = sync::steady_clock::now()); + + void update_next_rexmit_time(int ixlo, int ixhi, const time_point& time) + { + for (int i = ixlo; i <= ixhi; ++i) + { + // Do not override existing time value, only set anew if 0 + if (sync::is_zero(m_PktQueue[i].m_tsNextRexmitTime)) + m_PktQueue[i].m_tsNextRexmitTime = time; + } + } + + int next_loss(int current_loss); + + int loss_length() const { return m_iLossLengthCache; } + + int extractFirstLoss(const duration& min_interval = duration()); + + size_t size() const + { + return m_iCachedSize; + } + bool empty() const { return m_iCachedSize == 0; } + + void clear(); + + // NOTE: operator[] is unchecked. Use indirectly. + Packet& operator[](size_t index) { return m_PktQueue[index]; } + const Packet& operator[](size_t index) const { return m_PktQueue[index]; } + + int setupNode(int first_node_index, int last_node_index, int next_node_index = -1) + { + int next_index_shift = next_node_index == -1 ? 0 : next_node_index - first_node_index; + m_PktQueue[first_node_index].m_iNextLossGroupOffset = next_index_shift; + m_PktQueue[first_node_index].m_iLossLength = last_node_index - first_node_index + 1; + return m_PktQueue[first_node_index].m_iLossLength; + } + + int getEndIndex(int first_index) + { + return first_index + m_PktQueue[first_index].m_iLossLength; + } + + int getLastIndex(int first_index) // will return -1 if not a node. + { + int end = getEndIndex(first_index); + return end == first_index ? -1 : end - 1; + } + + void linkPreviousNode(int previous_node_index, int next_node_index) + { + m_PktQueue[previous_node_index].m_iNextLossGroupOffset = next_node_index - previous_node_index; + } + + void clearNode(int x) + { + m_PktQueue[x].m_iLossLength = 0; + m_PktQueue[x].m_iNextLossGroupOffset = 0; + } + + // testing purposes + void clearAllLoss() + { + for (int loss = m_iFirstRexmit, next = loss; loss != -1; loss = next) + { + next = next_loss(loss); + clearNode(loss); + } + m_iFirstRexmit = -1; + m_iLastRexmit = -1; + } + + // Helper state struct used in showline() only. + struct PacketShowState + { + time_point begin_time; + int remain_loss_group; // SIZE. 1+ if any loss noted, 0 if no loss. + int next_loss_begin; // INDEX. -1 if no loss pointed + + PacketShowState(): remain_loss_group(0), next_loss_begin(-1) {} + }; + + void showline(int index, int uniaue_index, PacketShowState& st, hvu::ofmtbufstream& out) const; + + std::string show_external(int32_t seqno, int32_t lastsent_seqno = SRT_SEQNO_NONE) const; + + // Debug/assert only + bool validateLossIntegrity(std::string& msg); +}; + + +struct CSndPacket +{ + CPacket pkt; // real contents + CSndBuffer* srcbuf; // NULL if this object doesn't lock a packet + int32_t seqno; // seq representing the packet in sndbuf, or SRT_SEQNO_NONE if no packet + + CSndPacket(): srcbuf(NULL), seqno(SRT_SEQNO_NONE) {} + + // This function should be called by the sender buffer AFTER + // it has updated the busy flag, and still under buffer and ack lock. + void acquire_busy(int32_t seq, CSndBuffer* buf) + { + seqno = seq; + srcbuf = buf; + } + + void acquire(SndPktArray::Packet& pkt, CSndBuffer* buf) + { + ++pkt.m_iBusy; + acquire_busy(pkt.m_iSeqNo, buf); + } + + // Release the binding, withdraw the busy flag from the sender + // buffer cell assigned to seqno, and try to revoke as much cells + // as possible, up to first busy and the registered last ACK. + void release(); + + ~CSndPacket() + { + release(); + } +}; + class CSndBuffer { typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; + friend struct CSndPacket; + public: // XXX There's currently no way to access the socket ID set for // whatever the buffer is currently working for. Required to find @@ -83,11 +328,18 @@ class CSndBuffer // 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 ip_family, int size, int maxpld, int authtag); + // We have the following split for a single packet: + // + // [ ----------------------------- MSS ---------------------------------------------] + // [HEADER(IP-dependent)][ ................... PAYLOAD .................. ][reserved] + + CSndBuffer(size_t pktsize, // size limit in packets (of payload size) + size_t slicesize, // size of the single memory chunk for payload buffers + size_t mss, // value of the MSS (default: 1500, take from settings) + size_t headersize, // size of the packet header (IP version dependent) + size_t reservedsize, // Size reserved in the payload, but not for the transferred data + int flow_window_size // required for loss list init + ); ~CSndBuffer(); public: @@ -103,36 +355,39 @@ class CSndBuffer /// - 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 + /// + /// IMPORTANT: all facilities that check the buffer size by getSndBufSize() + /// must be called in THE SAME THREAD as addBuffer(). And only this thread + /// should be allowed to add packets to this buffer. + /// /// @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); + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + EncryptionKeySpec addBuffer(const char* data, int len, CCryptoControl& crypto, time_point& w_origintime, 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); + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + EncryptionKeySpec addBufferFromFile(std::fstream& ifs, int len, CCryptoControl& crypto, int64_t& w_consumed); - // Special values that can be returned by readData. + EncryptionKeySpec checkEncryption(SndPktArray::Packet& p, CCryptoControl& crypto); + + // Special values that can be returned by extractUniquePacket. static const int READ_NONE = 0; static const int READ_DROP = -1; - /// 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. + /// Get access to the packet at the next unique position. + /// + /// @param [out] w_packet Object to keep the packet + /// @param [out] w_origintime Scheduling time of the packet + /// @param [inout] w_lastseqno Sequence number of last unique packet; updated in the call + /// @param [out] w_nextuniquets Set to the time of the packet next to extracted unique or zero time if no such packet /// @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; + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + int extractUniquePacket(CSndPacket& w_packet, time_point& w_origintime, int32_t& w_lastseqno, time_point& w_nextuniquets); struct DropRange { @@ -140,143 +395,168 @@ class CSndBuffer int32_t seqno[2]; int32_t msgno; }; - /// Find data position to pack a DATA packet for a retransmission. - /// IMPORTANT: @a packet is [in,out] because it is expected to get set - /// the sequence number of the packet expected to be sent next. The sender - /// buffer normally doesn't handle sequence numbers and the consistency - /// between the sequence number of a packet already sent and kept in the - /// buffer is achieved by having the sequence number recorded in the - /// CUDT::m_iSndLastDataAck field that should represent the oldest packet - /// still in the buffer. - /// @param [in] offset offset from the last ACK point (backward sequence number difference) - /// @param [in,out] w_packet storage for the packet, preinitialized with sequence number - /// @param [out] w_origintime origin time stamp of the message - /// @param [out] w_drop the drop information in case when dropping is to be done instead - /// @retval >0 Length of the data read. - /// @retval READ_NONE No data available or @a offset points out of the buffer occupied space. - /// @retval READ_DROP The call requested data drop due to TTL exceeded, to be handled first. - SRT_ATTR_EXCLUDES(m_BufLock) - int readData(const int offset, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + + // THIS IS FOR TESTING PURPOSES ONLY. In the normal code + // the retransmission extraction is done through extractFirstRexmitPacket, + // which extracts the packet marked loss in the buffer and fills the packet in one call. + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + int readOldPacket(int32_t seqno, CSndPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + int extractFirstRexmitPacket(const duration& min_rexmit_interval, int32_t& w_current_seqno, CSndPacket& w_sndpkt, + sync::steady_clock::time_point& w_tsOrigin, std::vector& w_drops); + +private: + SRT_TSA_NEEDS_LOCKED(m_BufLock) + int readPacketInternal(int offset, CSndPacket& w_packet, time_point& w_origintime, DropRange& w_drop); + +public: /// 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); + // SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + // time_point getPacketRexmitTime(const int offset); + + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) + time_point getRexmitTime(int32_t seqno); /// 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); + int32_t getMsgNoAtSeq(int32_t seqno); - void ackData(int offset); + enum RevokeStatus + { + /// The ACK sequence is in the past already; nothing to be done + RVK_PAST = 0, + /// Successfully revoked; go on with other updates + RVK_OK = 1, + /// The sequence is out of the acceptable range + RVK_ROGUE = -1 + }; + RevokeStatus revoke(int32_t upto_seqno); // upto_seqno = past-the-end! /// 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) + SRT_TSA_NEEDS_NONLOCKED(m_BufLock) int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time); + int dropAll(int& bytes); + + void clear() + { + int dummy; + dropAll((dummy)); + } - void updAvgBufSize(const time_point& time); int getAvgBufSize(int& bytes, int& timespan); - int getCurrBufSize(int& bytes, int& timespan) const; + int getCurrBufSize(int& bytes, int& timespan) const + { + sync::ScopedLock lk (m_BufLock); + return getBufferStats((bytes), (timespan)); + } - /// Het maximum payload length per packet. - int getMaxPacketLen() const; + /// Retrieve input bitrate in bytes per second + int getInputRate() const { return m_rateEstimator.getInputRate(); } - /// @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; + void enableRateEstimationIf(bool enable) { m_rateEstimator.resetInputRateSmpPeriod(!enable); } - /// @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; + void saveEstimation(CRateEstimator& w_est) + { + w_est.saveFrom(m_rateEstimator); + } + + void restoreEstimation(const CRateEstimator& r) + { + m_rateEstimator.restoreFrom(r); + } /// @brief Get the buffering delay of the oldest message in the buffer. /// @return the delay value. - SRT_ATTR_EXCLUDES(m_BufLock) + SRT_TSA_NEEDS_NONLOCKED(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(); } + /// Get maximum payload length per packet. + int getMaxPacketLen() const; - void resetInputRateSmpPeriod(bool disable = false) { m_rateEstimator.resetInputRateSmpPeriod(disable); } + int32_t firstSeqNo() const { return m_iSndLastDataAck; } - const CRateEstimator& getRateEstimator() const { return m_rateEstimator; } + // Required in group sequence override + void overrideFirstSeqNo(int32_t seq) { m_iSndLastDataAck = seq; m_iSndUpdateAck = SRT_SEQNO_NONE; } - void setRateEstimator(const CRateEstimator& other) { m_rateEstimator = other; } + // Sender loss list management methods + void removeLossUpTo(int32_t seqno); + int insertLoss(int32_t lo, int32_t hi, const sync::steady_clock::time_point& pt = sync::steady_clock::time_point()); -private: - void increase(); + // For testing purposes only. Not used in the code. + int32_t popLostSeq(DropRange&); -private: - mutable sync::Mutex m_BufLock; // used to synchronize buffer operation + int getLossLength(); - struct Block - { - char* m_pcData; // pointer to the data block - int m_iLength; // payload length of the block (excluding auth tag). + bool cancelLostSeq(int32_t seq); - 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) + /// @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; - Block* m_pNext; // next block + std::string show(int32_t lastsent_seqno = SRT_SEQNO_NONE) const; - 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; - } +private: - } * m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock; + void initialize(); - // 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) + SRT_TSA_NEEDS_LOCKED(m_BufLock) + void updAvgBufSize(const time_point& time); - struct Buffer - { - char* m_pcData; // buffer - int m_iSize; // size - Buffer* m_pNext; // next buffer - } * m_pBuffer; // physical buffer + // SENDER BUFFER FUNCTIONAL FIELDS - int32_t m_iNextMsgNo; // next message number + mutable sync::Mutex m_BufLock; // used to synchronize buffer operation - int m_iSize; // buffer size (number of packets) + // Note: as constants, these fields do not need mutex protection + // also when they are used in calculations. 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). - - // NOTE: This is atomic AND under lock because the function getCurrBufSize() - // is returning it WITHOUT locking. Modification, however, must stay under - // a lock. - sync::atomic m_iCount; // number of used blocks + const int m_iReservedSize; // Authentication tag size (if GCM is enabled). + sync::atomic m_iSndLastDataAck; // seqno of the packet in cell [0]. + sync::atomic m_iSndUpdateAck; // seqno up to which the last ACK was received (%>= m_iSndLastDataAck) + int32_t m_iNextMsgNo; // next message number to be set to a packet newly added at the end int m_iBytesCount; // number of payload bytes in queue time_point m_tsLastOriginTime; AvgBufSize m_mavg; CRateEstimator m_rateEstimator; -private: + void releasePacket(int32_t seqno); + + /// Buffer capacity (maximum size), used intermediately and in initialization only. + int m_iCapacity; + + SndPktArray m_Packets; + + SRT_TSA_NEEDS_LOCKED(m_BufLock) + int getBufferStats(int& bytes, int& timespan) const; + + + + // deleted copyers CSndBuffer(const CSndBuffer&); CSndBuffer& operator=(const CSndBuffer&); }; +inline void CSndPacket::release() +{ + if (!srcbuf || seqno == SRT_SEQNO_NONE) + return; + + srcbuf->releasePacket(seqno); + seqno = SRT_SEQNO_NONE; + srcbuf = NULL; +} + } // namespace srt #endif diff --git a/srtcore/buffer_tools.cpp b/srtcore/buffer_tools.cpp index e809b952e..006c8b2f8 100644 --- a/srtcore/buffer_tools.cpp +++ b/srtcore/buffer_tools.cpp @@ -53,13 +53,13 @@ modified by #include "platform_sys.h" #include "buffer_tools.h" #include "packet.h" -#include "logger_defs.h" +#include "logger_fas.h" #include "utilities.h" namespace srt { using namespace std; -using namespace srt_logging; +using namespace srt::logging; using namespace sync; // You can change this value at build config by using "ENFORCE" options. @@ -102,23 +102,28 @@ void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms); } -CRateEstimator::CRateEstimator(int /*family*/) +CRateEstimator::CRateEstimator() : m_iInRatePktsCount(0) , m_iInRateBytesCount(0) - , m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) + , m_ullInRatePeriod_us(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) , m_iInRateBps(INPUTRATE_INITIAL_BYTESPS) - , m_iFullHeaderSize(CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE) + , m_iFullHeaderSize(CPacket::udpHeaderSize(AF_INET) + CPacket::HDR_SIZE) {} +void CRateEstimator::setHeaderSize(size_t size) +{ + m_iFullHeaderSize = size; +} + void CRateEstimator::setInputRateSmpPeriod(int period) { - m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation + m_ullInRatePeriod_us = (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) + if (m_ullInRatePeriod_us == 0) return; if (is_zero(m_tsInRateStartTime)) @@ -136,10 +141,10 @@ void CRateEstimator::updateInputRate(const time_point& time, int pkts, int bytes 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 bool early_update = (m_ullInRatePeriod_us < 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) + if (!early_update && period_us <= m_ullInRatePeriod_us) return; // Required Byte/sec rate (payload + headers) @@ -155,123 +160,5 @@ void CRateEstimator::updateInputRate(const time_point& time, int pkts, int bytes 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 + CPacket::HDR_SIZE * sum.m_iPktsCount) * 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 += (int) bytes; - m_Samples[m_iCurSampleIdx].m_iPktsCount += pkts; -} - -int CSndRateEstimator::getCurrentRate() const -{ - SRT_ASSERT(m_iCurSampleIdx >= 0 && m_iCurSampleIdx < NUM_PERIODS); - const Sample& s = m_Samples[m_iCurSampleIdx]; - return (int) avg_iir<16, unsigned long long>(m_iRateBps, (CPacket::HDR_SIZE * s.m_iPktsCount + s.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; -} - -} +} // namespace srt diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h index 1c90a56a1..e24688fbb 100644 --- a/srtcore/buffer_tools.h +++ b/srtcore/buffer_tools.h @@ -94,10 +94,11 @@ class CRateEstimator typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; public: - CRateEstimator(int family); + CRateEstimator(); + void setHeaderSize(size_t size); public: - uint64_t getInRatePeriod() const { return m_InRatePeriod; } + uint64_t getInRatePeriod() const { return m_ullInRatePeriod_us; } /// Retrieve input bitrate in bytes per second int getInputRate() const { return m_iInRateBps; } @@ -112,7 +113,27 @@ class CRateEstimator void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); } -private: // Constants + void updateFrom(const CRateEstimator& other) + { +#define IMPORT(field) field = other.field + IMPORT(m_iInRatePktsCount); + IMPORT(m_iInRateBytesCount); + IMPORT(m_tsInRateStartTime); + IMPORT(m_ullInRatePeriod_us); + IMPORT(m_iInRateBps); +#undef IMPORT + } + + template + void saveFrom(const Saver& o) { updateFrom(o); } + + template + void restoreFrom(const Saver& o) { updateFrom(o); } + +private: + CRateEstimator& operator=(const CRateEstimator&); // in C++11: = delete + + // 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 @@ -122,82 +143,12 @@ class CRateEstimator 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 + uint64_t m_ullInRatePeriod_us; // usec int m_iInRateBps; // Input Rate in Bytes/sec int m_iFullHeaderSize; }; -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 with 16-byte packet header. - int getRate() const { return m_iRateBps; } - - /// Retrieve estimated bitrate in bytes per second (with 16-byte packet header) - /// including 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 sample. - int m_iFirstSampleIdx; //< Index of the first sample. - int m_iCurSampleIdx; //< Index of the current sample being collected. - int m_iRateBps; //< Rate in Bytes/sec. -}; - // Utility class for bandwidth limitation class CShaper { diff --git a/srtcore/byte_order.h b/srtcore/byte_order.h new file mode 100644 index 000000000..9dded704d --- /dev/null +++ b/srtcore/byte_order.h @@ -0,0 +1,193 @@ + +// Copied from: https://gist.github.com/panzi/6856583 +// License: Public Domain. + +#ifndef INC_HVU_BYTE_ORDER_H +#define INC_HVU_BYTE_ORDER_H + +#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__) + +# define __WINDOWS__ + +#endif + +#if defined(__linux__) || defined(__CYGWIN__) || defined(__GNU__) || defined(__GLIBC__) + +# include + +// GLIBC-2.8 and earlier does not provide these macros. +// See http://linux.die.net/man/3/endian +// From https://gist.github.com/panzi/6856583 +# if defined(__GLIBC__) \ + && ( !defined(__GLIBC_MINOR__) \ + || ((__GLIBC__ < 2) \ + || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 9))) ) +# include +# if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN) + +# define htole32(x) (x) +# define le32toh(x) (x) + +# elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) + +# define htole16(x) ((((((uint16_t)(x)) >> 8))|((((uint16_t)(x)) << 8))) +# define le16toh(x) ((((((uint16_t)(x)) >> 8))|((((uint16_t)(x)) << 8))) + +# define htole32(x) (((uint32_t)htole16(((uint16_t)(((uint32_t)(x)) >> 16)))) | (((uint32_t)htole16(((uint16_t)(x)))) << 16)) +# define le32toh(x) (((uint32_t)le16toh(((uint16_t)(((uint32_t)(x)) >> 16)))) | (((uint32_t)le16toh(((uint16_t)(x)))) << 16)) + +# else +# error Byte Order not supported or not defined. +# endif +# endif + +#elif defined(__APPLE__) + +# include + +# define htobe16(x) OSSwapHostToBigInt16(x) +# define htole16(x) OSSwapHostToLittleInt16(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +# define le16toh(x) OSSwapLittleToHostInt16(x) + +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htole32(x) OSSwapHostToLittleInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) + +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htole64(x) OSSwapHostToLittleInt64(x) +# define be64toh(x) OSSwapBigToHostInt64(x) +# define le64toh(x) OSSwapLittleToHostInt64(x) + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#elif defined(__OpenBSD__) + +# include + +#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) + +# include + +#ifndef be16toh +# define be16toh(x) betoh16(x) +#endif +#ifndef le16toh +# define le16toh(x) letoh16(x) +#endif + +#ifndef be32toh +# define be32toh(x) betoh32(x) +#endif +#ifndef le32toh +# define le32toh(x) letoh32(x) +#endif + +#ifndef be64toh +# define be64toh(x) betoh64(x) +#endif +#ifndef le64toh +# 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 + +# if BYTE_ORDER == LITTLE_ENDIAN + +# define htobe16(x) htons(x) +# define htole16(x) (x) +# define be16toh(x) ntohs(x) +# define le16toh(x) (x) + +# define htobe32(x) htonl(x) +# define htole32(x) (x) +# define be32toh(x) ntohl(x) +# define le32toh(x) (x) + +# define htobe64(x) htonll(x) +# define htole64(x) (x) +# define be64toh(x) ntohll(x) +# define le64toh(x) (x) + +# elif BYTE_ORDER == BIG_ENDIAN + + /* that would be xbox 360 */ +# define htobe16(x) (x) +# define htole16(x) __builtin_bswap16(x) +# define be16toh(x) (x) +# define le16toh(x) __builtin_bswap16(x) + +# define htobe32(x) (x) +# define htole32(x) __builtin_bswap32(x) +# define be32toh(x) (x) +# define le32toh(x) __builtin_bswap32(x) + +# define htobe64(x) (x) +# define htole64(x) __builtin_bswap64(x) +# define be64toh(x) (x) +# define le64toh(x) __builtin_bswap64(x) + +# else + +# error byte order not supported + +# endif // BYTE_ORDER + +# define __BYTE_ORDER BYTE_ORDER +# define __BIG_ENDIAN BIG_ENDIAN +# define __LITTLE_ENDIAN LITTLE_ENDIAN +# define __PDP_ENDIAN PDP_ENDIAN + +#else + +# error Endian: platform not supported + +#endif // Platform-dependent macro + +#endif // INC_HVU_BYTE_ORDER_H diff --git a/srtcore/cache.cpp b/srtcore/cache.cpp index 2c6f624c4..425899b93 100644 --- a/srtcore/cache.cpp +++ b/srtcore/cache.cpp @@ -46,7 +46,10 @@ written by using namespace std; -srt::CInfoBlock& srt::CInfoBlock::copyFrom(const CInfoBlock& obj) +namespace srt +{ + +CInfoBlock& CInfoBlock::copyFrom(const CInfoBlock& obj) { std::copy(obj.m_piIP, obj.m_piIP + 4, m_piIP); m_iIPversion = obj.m_iIPversion; @@ -61,7 +64,7 @@ srt::CInfoBlock& srt::CInfoBlock::copyFrom(const CInfoBlock& obj) return *this; } -bool srt::CInfoBlock::operator==(const CInfoBlock& obj) const +bool CInfoBlock::operator==(const CInfoBlock& obj) const { if (m_iIPversion != obj.m_iIPversion) return false; @@ -78,7 +81,7 @@ bool srt::CInfoBlock::operator==(const CInfoBlock& obj) const return true; } -srt::CInfoBlock* srt::CInfoBlock::clone() +CInfoBlock* CInfoBlock::clone() { CInfoBlock* obj = new CInfoBlock; @@ -95,7 +98,7 @@ srt::CInfoBlock* srt::CInfoBlock::clone() return obj; } -int srt::CInfoBlock::getKey() +int CInfoBlock::getKey() { if (m_iIPversion == AF_INET) return m_piIP[0]; @@ -103,7 +106,7 @@ int srt::CInfoBlock::getKey() return m_piIP[0] + m_piIP[1] + m_piIP[2] + m_piIP[3]; } -void srt::CInfoBlock::convert(const sockaddr_any& addr, uint32_t aw_ip[4]) +void CInfoBlock::convert(const sockaddr_any& addr, uint32_t aw_ip[4]) { if (addr.family() == AF_INET) { @@ -115,3 +118,5 @@ void srt::CInfoBlock::convert(const sockaddr_any& addr, uint32_t aw_ip[4]) memcpy((aw_ip), addr.sin6.sin6_addr.s6_addr, sizeof addr.sin6.sin6_addr.s6_addr); } } + +} // END namespace srt diff --git a/srtcore/cache.h b/srtcore/cache.h index d7f586c20..88f51fd65 100644 --- a/srtcore/cache.h +++ b/srtcore/cache.h @@ -46,7 +46,6 @@ written by #include "sync.h" #include "netinet_any.h" -#include "udt.h" namespace srt { @@ -88,7 +87,7 @@ class CCache { m_vHashPtr.resize(m_iHashSize); // Exception: -> CUDTUnited ctor - srt::sync::setupMutex(m_Lock, "Cache"); + sync::setupMutex(m_Lock, "Cache"); } ~CCache() { clear(); } @@ -100,7 +99,7 @@ class CCache int lookup(T* data) { - srt::sync::ScopedLock cacheguard(m_Lock); + sync::ScopedLock cacheguard(m_Lock); int key = data->getKey(); if (key < 0) @@ -128,7 +127,7 @@ class CCache int update(T* data) { - srt::sync::ScopedLock cacheguard(m_Lock); + sync::ScopedLock cacheguard(m_Lock); int key = data->getKey(); if (key < 0) @@ -226,7 +225,7 @@ class CCache int m_iHashSize; int m_iCurrSize; - srt::sync::Mutex m_Lock; + sync::Mutex m_Lock; private: CCache(const CCache&); diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 81f082a1f..2faf36225 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -53,11 +53,11 @@ modified by #include "platform_sys.h" #include #include // Logging -#include +#include #include #include "channel.h" -#include "core.h" // srt_logging:kmlog +#include "core.h" // srt::logging::kmlog #include "packet.h" #include "logging.h" #include "netinet_any.h" @@ -68,7 +68,8 @@ typedef int socklen_t; #endif using namespace std; -using namespace srt_logging; +using namespace srt::logging; +using namespace hvu; // ofmt namespace srt { @@ -79,14 +80,45 @@ namespace srt static const int INVALID_SOCKET = -1; #endif -#if ENABLE_SOCK_CLOEXEC + +// CREATING A SOCKET, possibly with CLOEXEC flag +// (portability solution) + +// MAIN INTERFACE: +// +// SYSSOCKET createUDPSocket_asneeded(int family); +// +// Creates a UDP/DGRAM socket. The CLOEXEC flag should be set +// on it, if configured so. On error, throws CUDTException. +// +// Depending on platform and runtime conditions, it can do: +// +// 1. Create a socket without CLOEXEC (if not needed) +// 2. Create a socket with CLOEXEC flag set +// 3. Create a socket without CLOEXEC set and this flag will be +// set later using the set_cloexec() function, defined below. + +// SIGNATURE: +// +// int set_cloexec(SYSSOCKET fd, int set) +// +// RETURNS: int error_code, as defined in +// - POSIX, as int errno +// - Windows, as return value of WSAGetLastError +// It is considered that 0 is the value of "no error" +// or "success" otherwise. +// +// SYSSOCKET is defined in srt.h as +// - SOCKET on Windows +// - int on POSIX + #ifndef _WIN32 -#if defined(_AIX) || defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || \ +#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) +static int set_cloexec(SYSSOCKET fd, int set) { int r; @@ -101,7 +133,7 @@ static int set_cloexec(int fd, int set) } #else // Set the CLOEXEC flag using fcntl() function -static int set_cloexec(int fd, int set) +static int set_cloexec(SYSSOCKET fd, int set) { int flags; int r; @@ -132,11 +164,77 @@ static int set_cloexec(int fd, int set) return 0; } #endif // if defined(_AIX) ... + +#else // _WIN32 + +static int set_cloexec(SYSSOCKET fd, int set) +{ + if (!::SetHandleInformation((HANDLE)fd, HANDLE_FLAG_INHERIT, set)) + return NET_ERROR; + return 0; +} + #endif // ifndef _WIN32 -#endif // if ENABLE_CLOEXEC -} // namespace srt -srt::CChannel::CChannel() +// Creates a socket without requesting the CLOEXEC flag. +static inline SYSSOCKET createUDPSocket(int family) +{ + SYSSOCKET s = ::socket(family, SOCK_DGRAM, IPPROTO_UDP); + if (s == INVALID_SOCKET) + throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); + return s; +} + +// Creates a socket with or without CLOEXEC flag, depending on +// what is possible. Returns: +// .first: created socket +// .second: true, if the set_cloexec needs to be still called on it +static inline pair createUDPSocket_try_cloexec(int family) +{ + // Try with SOCK_CLOEXEC, if this flag is available at compile time +#if defined(SOCK_CLOEXEC) + SYSSOCKET s = ::socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP); + if (s != INVALID_SOCKET) + { + return make_pair(s, false); + } + // If this failed, fallback HERE. +#endif + // If failed or not available, just create socket and report + // the need to set it later. + return make_pair(createUDPSocket(family), true); +} + +// Creates a socket that will have the CLOEXEC flag set if needed. +// If CLOEXEC flag was needed, this fill return the socket with +// this flag set - one way or another. +static inline SYSSOCKET createUDPSocket_asneeded(int family) +{ +#if SRT_ENABLE_CLOEXEC + // Create a socket, preferably with CLOEXEC flag. + // 'setafter' means that socket is there, but the CLOEXEC + // flag must be set separately. + SYSSOCKET s; + bool setafter; + Tie(s, setafter) = createUDPSocket_try_cloexec(family); + + if (setafter) + { + int ret_err = set_cloexec(s, 1); + if (ret_err != 0) + { + throw CUDTException(MJ_SETUP, MN_NONE, ret_err); + } + } + return s; +#else + return createUDPSocket(family); +#endif +} + +//----------------------------------- + +CChannel::CChannel() : m_iSocket(INVALID_SOCKET) #ifdef SRT_ENABLE_PKTINFO , m_bBindMasked(true) @@ -157,65 +255,28 @@ srt::CChannel::CChannel() #endif } -srt::CChannel::~CChannel() {} +CChannel::~CChannel() {} -void srt::CChannel::createSocket(int family) +void 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 + // Creates the socket, regarding any required default flags + m_iSocket = createUDPSocket_asneeded(family); - if (m_iSocket == INVALID_SOCKET) - throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); - -#if ENABLE_SOCK_CLOEXEC - - if (cloexec_flag) + if (m_mcfg.iIpV6Only != -1 && family == AF_INET6) // (not an error if it fails) { -#ifdef _WIN32 - // XXX ::SetHandleInformation(hInputWrite, HANDLE_FLAG_INHERIT, 0) -#else - if (0 != set_cloexec(m_iSocket, 1)) - { - throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); - } -#endif //_WIN32 - } -#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 + const int res SRT_ATR_UNUSED = ::setsockopt(m_iSocket, + IPPROTO_IPV6, IPV6_V6ONLY, + (const char*)&m_mcfg.iIpV6Only, sizeof m_mcfg.iIpV6Only); 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)); + << SysStrError(NET_ERROR)); } -#endif // ENABLE_LOGGING } } -void srt::CChannel::open(const sockaddr_any& addr) +void CChannel::open(const sockaddr_any& addr) { createSocket(addr.family()); socklen_t namelen = addr.size(); @@ -232,7 +293,7 @@ void srt::CChannel::open(const sockaddr_any& addr) setUDPSockOpt(); } -void srt::CChannel::open(int family) +void CChannel::open(int family) { createSocket(family); @@ -280,7 +341,7 @@ void srt::CChannel::open(int family) setUDPSockOpt(); } -void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr) +void CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr) { // The getsockname() call is done before calling it and the // result is placed into udpsocks_addr. @@ -303,7 +364,7 @@ static inline string fmt_alt(bool value, const string& label, const string& unla return value ? label : unlabel; } -void srt::CChannel::setUDPSockOpt() +void CChannel::setUDPSockOpt() { #if defined(SUNOS) { @@ -580,40 +641,52 @@ void srt::CChannel::setUDPSockOpt() #endif } -void srt::CChannel::close() const +void CChannel::close() { + UDPSOCKET oldsocket = m_iSocket.load(); + if (oldsocket == INVALID_SOCKET) + return; + + m_iSocket = INVALID_SOCKET; + + // Closing a socket that another thread is using for reading may be dangerous. + // Using shutdown first to allow simultaneous recvmsg calls to be properly cleaned. + // This is according to the recommendation; thread sanitizer still reports this as race. + #ifndef _WIN32 - ::close(m_iSocket); + ::shutdown(oldsocket, SHUT_RDWR); + ::close(oldsocket); #else - ::closesocket(m_iSocket); + ::shutdown(oldsocket, SD_BOTH); + ::closesocket(oldsocket); #endif } -int srt::CChannel::getSndBufSize() +int CChannel::getSndBufSize() { socklen_t size = (socklen_t)sizeof m_mcfg.iUDPSndBufSize; ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_mcfg.iUDPSndBufSize, &size); return m_mcfg.iUDPSndBufSize; } -int srt::CChannel::getRcvBufSize() +int CChannel::getRcvBufSize() { 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 srt::CChannel::setConfig(const CSrtMuxerConfig& config) +void CChannel::setConfig(const CSrtMuxerConfig& config) { m_mcfg = config; } -void srt::CChannel::getSocketOption(int level, int option, char* pw_dataptr, socklen_t& w_len, int& w_status) +void CChannel::getSocketOption(int level, int option, char* pw_dataptr, socklen_t& w_len, int& w_status) { w_status = ::getsockopt(m_iSocket, level, option, (pw_dataptr), (&w_len)); } -int srt::CChannel::getIpTTL() const +int CChannel::getIpTTL() const { if (m_iSocket == INVALID_SOCKET) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -636,7 +709,7 @@ int srt::CChannel::getIpTTL() const return m_mcfg.iIpTTL; } -int srt::CChannel::getIpToS() const +int CChannel::getIpToS() const { if (m_iSocket == INVALID_SOCKET) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -662,7 +735,7 @@ int srt::CChannel::getIpToS() const } #ifdef SRT_ENABLE_BINDTODEVICE -bool srt::CChannel::getBind(char* dst, size_t len) +bool CChannel::getBind(char* dst, size_t len) const { if (m_iSocket == INVALID_SOCKET) return false; // No socket to get data from @@ -680,7 +753,7 @@ bool srt::CChannel::getBind(char* dst, size_t len) } #endif -int srt::CChannel::ioctlQuery(int type SRT_ATR_UNUSED) const +int CChannel::ioctlQuery(int type SRT_ATR_UNUSED) const { #if defined(unix) || defined(__APPLE__) int value = 0; @@ -691,7 +764,7 @@ int srt::CChannel::ioctlQuery(int type SRT_ATR_UNUSED) const return -1; } -int srt::CChannel::sockoptQuery(int level SRT_ATR_UNUSED, int option SRT_ATR_UNUSED) const +int CChannel::sockoptQuery(int level SRT_ATR_UNUSED, int option SRT_ATR_UNUSED) const { #if defined(unix) || defined(__APPLE__) int value = 0; @@ -703,30 +776,37 @@ int srt::CChannel::sockoptQuery(int level SRT_ATR_UNUSED, int option SRT_ATR_UNU return -1; } -void srt::CChannel::getSockAddr(sockaddr_any& w_addr) const +sockaddr_any CChannel::getSockAddr() const { + sockaddr_any addr; // 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; + ::getsockname(m_iSocket, (addr.get()), (&addr.syslen())); + return addr; } -void srt::CChannel::getPeerAddr(sockaddr_any& w_addr) const +sockaddr_any CChannel::getPeerAddr() const { - socklen_t namelen = (socklen_t)w_addr.storage_size(); - ::getpeername(m_iSocket, (w_addr.get()), (&namelen)); - w_addr.len = namelen; + sockaddr_any addr; + ::getpeername(m_iSocket, (addr.get()), (&addr.syslen())); + return addr; } -int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const sockaddr_any& source_addr SRT_ATR_UNUSED) const +int CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const CNetworkInterface& source_ni SRT_ATR_UNUSED) const { -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING ostringstream dsrc; #ifdef SRT_ENABLE_PKTINFO - dsrc << " sourceIP=" << (m_bBindMasked && !source_addr.isany() ? source_addr.str() : "default"); + if (m_bBindMasked && !source_ni.address.isany()) + { + dsrc << " sourceNI=" << source_ni.str(); + } + else + { + dsrc << " sourceNI=default"; + } #endif LOGC(kslog.Debug, @@ -777,7 +857,7 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka if (dcounter > 8) { // Make a random number in the range between 8 and 24 - const int rnd = srt::sync::genRandomInt(8, 24); + const int rnd = sync::genRandomInt(8, 24); if (dcounter > rnd) { @@ -809,15 +889,15 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka // 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. char mh_crtl_buf[sizeof(CMSGNodeIPv4) + sizeof(CMSGNodeIPv6)]; - if (m_bBindMasked && source_addr.family() != AF_UNSPEC && !source_addr.isany()) + if (m_bBindMasked && source_ni.address.family() != AF_UNSPEC && !source_ni.address.isany()) { - if (!setSourceAddress(mh, mh_crtl_buf, source_addr)) + if (!setSourceAddress(mh, mh_crtl_buf, source_ni)) { - LOGC(kslog.Error, log << "CChannel::setSourceAddress: source address invalid family #" << source_addr.family() << ", NOT setting."); + LOGC(kslog.Error, log << "CChannel::setSourceAddress: source address invalid family #" << source_ni.address.family() << ", NOT setting."); } else { - HLOGC(kslog.Debug, log << "CChannel::setSourceAddress: setting as " << source_addr.str()); + HLOGC(kslog.Debug, log << "CChannel::setSourceAddress: setting as " << source_ni.str()); have_set_src = true; } } @@ -831,7 +911,7 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka } mh.msg_flags = 0; - const int res = (int)::sendmsg(m_iSocket, &mh, 0); + const int res = (int)::sendmsg(m_iSocket.load(), &mh, 0); #else class WSAEventRef { @@ -857,7 +937,7 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka private: WSAEVENT e; }; -#if !defined(__MINGW32__) && defined(ENABLE_CXX11) +#if !defined(__MINGW32__) && defined(HAVE_CXX11) thread_local WSAEventRef lEvent; #else WSAEventRef lEvent; @@ -868,14 +948,14 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka DWORD size = (DWORD)(packet.m_PacketVector[0].size() + packet.m_PacketVector[1].size()); int addrsize = addr.size(); - int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, &overlapped, NULL); + int res = ::WSASendTo(m_iSocket.load(), (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, &overlapped, NULL); if (res == SOCKET_ERROR) { if (NET_ERROR == WSA_IO_PENDING) { DWORD dwFlags = 0; - const bool bCompleted = WSAGetOverlappedResult(m_iSocket, &overlapped, &size, TRUE, &dwFlags); + const bool bCompleted = WSAGetOverlappedResult(m_iSocket.load(), &overlapped, &size, TRUE, &dwFlags); if (bCompleted) res = 0; else @@ -896,7 +976,7 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka return res; } -srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const +EReadStatus CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet) const { EReadStatus status = RST_OK; int msg_flags = 0; @@ -906,10 +986,10 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet fd_set set; timeval tv; FD_ZERO(&set); - FD_SET(m_iSocket, &set); + FD_SET(m_iSocket.load(), &set); tv.tv_sec = 0; tv.tv_usec = 10000; - const int select_ret = ::select((int)m_iSocket + 1, &set, NULL, &set, &tv); + 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 #endif @@ -954,7 +1034,7 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet mh.msg_flags = 0; - recv_size = (int)::recvmsg(m_iSocket, (&mh), 0); + recv_size = (int)::recvmsg(m_iSocket.load(), (&mh), 0); msg_flags = mh.msg_flags; } @@ -1033,7 +1113,7 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet DWORD size = (DWORD)(CPacket::HDR_SIZE + w_packet.getLength()); int addrsize = w_addr.size(); - recv_ret = ::WSARecvFrom(m_iSocket, + recv_ret = ::WSARecvFrom(m_iSocket.load(), ((LPWSABUF)w_packet.m_PacketVector), 2, (&size), @@ -1105,7 +1185,7 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet // packet was received, so the packet will be then retransmitted. if (msg_flags != 0) { -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::ostringstream flg; @@ -1135,7 +1215,8 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet #endif HLOGC(krlog.Debug, - log << CONID() << "NET ERROR: packet size=" << recv_size << " msg_flags=0x" << hex << msg_flags + log << CONID() << "NET ERROR: packet size=" << recv_size << " msg_flags=0x" + << fmt(msg_flags, hex) << ", detected flags:" << flg.str()); #endif status = RST_AGAIN; @@ -1151,3 +1232,5 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet w_packet.setLength(-1); return status; } + +} // namespace srt diff --git a/srtcore/channel.h b/srtcore/channel.h index e12310001..55554dede 100644 --- a/srtcore/channel.h +++ b/srtcore/channel.h @@ -53,7 +53,6 @@ modified by #define INC_SRT_CHANNEL_H #include "platform_sys.h" -#include "udt.h" #include "packet.h" #include "socketconfig.h" #include "netinet_any.h" @@ -89,7 +88,7 @@ class CChannel /// Disconnect and close the UDP entity. - void close() const; + void close(); /// Get the UDP sending buffer size. /// @return Current UDP sending buffer size. @@ -104,12 +103,12 @@ class CChannel /// 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; + sockaddr_any getSockAddr() const; /// 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; + sockaddr_any getPeerAddr() const; /// Send a packet to the given address. /// @param [in] addr pointer to the destination address. @@ -117,14 +116,14 @@ class CChannel /// @param [in] src source address to sent on an outgoing packet (if not ANY) /// @return Actual size of data sent. - int sendto(const sockaddr_any& addr, srt::CPacket& packet, const sockaddr_any& src) const; + int sendto(const sockaddr_any& addr, CPacket& packet, const CNetworkInterface& src) 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. - EReadStatus recvfrom(sockaddr_any& addr, srt::CPacket& packet) const; + EReadStatus recvfrom(sockaddr_any& addr, CPacket& packet) const; void setConfig(const CSrtMuxerConfig& config); @@ -155,7 +154,7 @@ class CChannel int getIpToS() const; #ifdef SRT_ENABLE_BINDTODEVICE - bool getBind(char* dst, size_t len); + bool getBind(char* dst, size_t len) const; #endif int ioctlQuery(int type) const; @@ -168,7 +167,7 @@ class CChannel void setUDPSockOpt(); private: - UDPSOCKET m_iSocket; // socket descriptor + sync::atomic m_iSocket; // socket descriptor // Mutable because when querying original settings // this comprises the cache for extracted values, @@ -205,7 +204,7 @@ class CChannel cmsghdr hdr; }; - sockaddr_any getTargetAddress(const msghdr& msg) const + CNetworkInterface getTargetAddress(const msghdr& msg) const { // Loop through IP header messages cmsghdr* cmsg; @@ -219,33 +218,33 @@ class CChannel { in_pktinfo dest_ip; memcpy(&dest_ip, CMSG_DATA(cmsg), sizeof(struct in_pktinfo)); - return sockaddr_any(dest_ip.ipi_addr, 0); + return CNetworkInterface(dest_ip.ipi_addr, dest_ip.ipi_ifindex); } if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { in6_pktinfo dest_ip; memcpy(&dest_ip, CMSG_DATA(cmsg), sizeof(struct in6_pktinfo)); - return sockaddr_any(dest_ip.ipi6_addr, 0); + return CNetworkInterface(dest_ip.ipi6_addr, dest_ip.ipi6_ifindex); } } // Fallback for an error - return sockaddr_any(m_BindAddr.family()); + return CNetworkInterface(); } // 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, char *buf, const sockaddr_any& adr) const + bool setSourceAddress(msghdr& mh, char *buf, const CNetworkInterface& ni) 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) + if (ni.address.family() == AF_INET) { mh.msg_control = (void *) buf; mh.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); @@ -254,16 +253,16 @@ class CChannel 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; - pktinfo.ipi_ifindex = 0; - pktinfo.ipi_spec_dst = adr.sin.sin_addr; + pktinfo.ipi_ifindex = ni.interface_index; + pktinfo.ipi_spec_dst = ni.address.sin.sin_addr; memcpy(CMSG_DATA(cmsg_send), &pktinfo, sizeof(in_pktinfo)); return true; } - if (adr.family() == AF_INET6) + if (ni.address.family() == AF_INET6) { mh.msg_control = buf; mh.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); @@ -274,8 +273,8 @@ class CChannel 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; + pktinfo->ipi6_ifindex = ni.interface_index; + pktinfo->ipi6_addr = ni.address.sin6.sin6_addr; return true; } diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 23b341313..b978549cb 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -60,32 +60,34 @@ modified by #include #include #include -#include "udt.h" + +#if _WIN32 + #if SRT_ENABLE_LOCALIF_WIN32 + #include + #endif +#else + #include +#endif + #include "md5.h" #include "common.h" #include "netinet_any.h" #include "logging.h" #include "packet.h" -#include "threadname.h" -#include "logger_defs.h" +#include "logger_fas.h" +#include "handshake.h" using namespace std; using namespace srt::sync; -using namespace srt_logging; - -namespace srt_logging -{ -extern Logger inlog; -} +using namespace srt::logging; namespace srt { const char* strerror_get_message(size_t major, size_t minor); -} // namespace srt -srt::CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): +CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): m_iMajor(major), m_iMinor(minor) { @@ -96,30 +98,30 @@ m_iMinor(minor) HLOGC(aclog.Debug, log << "CREATED SRT EXCEPTION: " << (1000*major+minor) << " errno=" << m_iErrno); } -const char* srt::CUDTException::getErrorMessage() const ATR_NOTHROW +const char* CUDTException::getErrorMessage() const ATR_NOTHROW { return strerror_get_message(m_iMajor, m_iMinor); } -std::string srt::CUDTException::getErrorString() const +string CUDTException::getErrorString() const { return getErrorMessage(); } #define UDT_XCODE(mj, mn) (int(mj)*1000)+int(mn) -int srt::CUDTException::getErrorCode() const +int CUDTException::getErrorCode() const { return UDT_XCODE(m_iMajor, m_iMinor); } -int srt::CUDTException::getErrno() const +int CUDTException::getErrno() const { return m_iErrno; } -void srt::CUDTException::clear() +void CUDTException::clear() { m_iMajor = MJ_SUCCESS; m_iMinor = MN_NONE; @@ -128,36 +130,7 @@ void srt::CUDTException::clear() #undef UDT_XCODE -// -bool srt::CIPAddress::ipcmp(const sockaddr* addr1, const sockaddr* addr2, int ver) -{ - if (AF_INET == ver) - { - sockaddr_in* a1 = (sockaddr_in*)addr1; - sockaddr_in* a2 = (sockaddr_in*)addr2; - - if ((a1->sin_port == a2->sin_port) && (a1->sin_addr.s_addr == a2->sin_addr.s_addr)) - return true; - } - else - { - sockaddr_in6* a1 = (sockaddr_in6*)addr1; - sockaddr_in6* a2 = (sockaddr_in6*)addr2; - - if (a1->sin6_port == a2->sin6_port) - { - for (int i = 0; i < 16; ++ i) - if (*((char*)&(a1->sin6_addr) + i) != *((char*)&(a2->sin6_addr) + i)) - return false; - - return true; - } - } - - return false; -} - -void srt::CIPAddress::ntop(const sockaddr_any& addr, uint32_t ip[4]) +void CIPAddress::encode(const sockaddr_any& addr, uint32_t (&ip)[4]) { if (addr.family() == AF_INET) { @@ -172,7 +145,6 @@ void srt::CIPAddress::ntop(const sockaddr_any& addr, uint32_t ip[4]) } } -namespace srt { bool checkMappedIPv4(const uint16_t* addr) { static const uint16_t ipv4on6_model [8] = @@ -187,13 +159,10 @@ bool checkMappedIPv4(const uint16_t* addr) return std::equal(mbegin, mend, addr); } -} -// 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) +// This function gets w_addr by reference because it only overwrites the address part. +void CIPAddress::decode(const uint32_t (&ip)[4], const sockaddr_any& peer, sockaddr_any& w_addr) { - //using ::srt_logging::inlog; uint32_t* target_ipv4_addr = NULL; if (peer.family() == AF_INET) @@ -279,15 +248,17 @@ void srt::CIPAddress::pton(sockaddr_any& w_addr, const uint32_t ip[4], const soc } 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); +#if HVU_ENABLE_LOGGING + using namespace hvu; + + ofmtbufstream peeraddr_form; + fmtc hex04 = fmtc().hex().fillzero().width(4); + peeraddr_form << fmt(peeraddr16[0], hex04); + for (int i = 1; i < 8; ++i) + peeraddr_form << ":" << fmt(peeraddr16[i], hex04); + + LOGC(inlog.Error, log << "pton: IPE or net error: can't determine IPv4 carryover format: " << peeraddr_form); +#endif *target_ipv4_addr = 0; if (peer.family() != AF_INET) { @@ -297,65 +268,53 @@ void srt::CIPAddress::pton(sockaddr_any& w_addr, const uint32_t ip[4], const soc w_addr.sin6.sin6_addr.s6_addr[11] = 0; } } -} +} -namespace srt { -static string ShowIP4(const sockaddr_in* sin) +static inline void PrintIPv4(uint32_t aval, hvu::ofmtbufstream& os) { - ostringstream os; - union - { - in_addr sinaddr; - unsigned char ip[4]; - }; - sinaddr = sin->sin_addr; - - os << int(ip[0]); - os << "."; - os << int(ip[1]); - os << "."; - os << int(ip[2]); - os << "."; - os << int(ip[3]); - return os.str(); + typedef Bits<8+8+8+7, 8+8+8> q0; + typedef Bits<8+8+7, 8+8> q1; + typedef Bits<8+7, 8> q2; + typedef Bits<7, 0> q3; + + os << q3::unwrap(aval) << "."; + os << q2::unwrap(aval) << "."; + os << q1::unwrap(aval) << "."; + os << q0::unwrap(aval); } -static string ShowIP6(const sockaddr_in6* sin) +std::string CIPAddress::show(const uint32_t (&ip)[4]) { - ostringstream os; - os.setf(ios::uppercase); + const uint16_t* peeraddr16 = (uint16_t*)ip; + const bool is_mapped_ipv4 = checkMappedIPv4(peeraddr16); - bool sep = false; - for (size_t i = 0; i < 16; ++i) - { - int v = sin->sin6_addr.s6_addr[i]; - if ( v ) - { - if ( sep ) - os << ":"; + using namespace hvu; - os << hex << v; - sep = true; - } + ofmtbufstream out; + if (is_mapped_ipv4) + { + out << "::FFFF:"; + PrintIPv4(ip[3], (out)); + } + // Check SRT IPv4 format. + else if ((ip[1] | ip[2] | ip[3]) == 0) + { + PrintIPv4(ip[0], (out)); + out << "[SRT]"; } - - return os.str(); -} - -string CIPAddress::show(const sockaddr* adr) -{ - if ( adr->sa_family == AF_INET ) - return ShowIP4((const sockaddr_in*)adr); - else if ( adr->sa_family == AF_INET6 ) - return ShowIP6((const sockaddr_in6*)adr); else - return "(unsupported sockaddr type)"; + { + fmtc hex04 = fmtc().hex().fillzero().width(4); + out << fmt(peeraddr16[0], hex04); + for (int i = 1; i < 8; ++i) + out << ":" << fmt(peeraddr16[i], hex04); + } + + return out.str(); } -} // namespace srt -// -void srt::CMD5::compute(const char* input, unsigned char result[16]) +void CMD5::compute(const char* input, unsigned char result[16]) { md5_state_t state; @@ -364,11 +323,8 @@ void srt::CMD5::compute(const char* input, unsigned char result[16]) md5_finish(&state, result); } -namespace srt { -std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) +string MessageTypeStr(UDTMessageType mt, uint32_t extt) { - using std::string; - static const char* const udt_types [] = { "handshake", "keepalive", @@ -408,7 +364,7 @@ std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) return udt_types[mt]; } -std::string ConnectStatusStr(EConnectStatus cst) +string ConnectStatusStr(EConnectStatus cst) { return cst == CONN_CONTINUE ? "INDUCED/CONCLUDING" @@ -420,7 +376,7 @@ std::string ConnectStatusStr(EConnectStatus cst) : "REJECTED"; } -std::string TransmissionEventStr(ETransmissionEvent ev) +string TransmissionEventStr(ETransmissionEvent ev) { static const char* const vals [] = { @@ -475,7 +431,7 @@ string FormatLossArray(const vector< pair >& lra) { int len = CSeqNo::seqoff(i->first, i->second); os << "%" << i->first; - if (len > 1) + if (len > 0) os << "+" << len; os << " "; } @@ -523,15 +479,87 @@ ostream& PrintEpollEvent(ostream& os, int events, int et_events) return os; } -} // namespace srt -namespace srt_logging +vector GetLocalInterfaces() { + vector locals; +#ifdef _WIN32 + // If not enabled, simply an empty local vector will be returned + #if SRT_ENABLE_LOCALIF_WIN32 + ULONG flags = GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_ALL_INTERFACES; + ULONG outBufLen = 0; + + // This function doesn't allocate memory by itself, you have to do it + // yourself, worst case when it's too small, the size will be corrected + // and the function will do nothing. So, simply, call the function with + // always too little 0 size and make it show the correct one. + GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &outBufLen); + // Ignore errors. Check errors on the real call. + // (Have doubts about this "max" here, as VC reports errors when + // using std::max, so it will likely resolve to a macro - hope this + // won't cause portability problems, this code is Windows only. + + // Good, now we can allocate memory + PIP_ADAPTER_ADDRESSES pAddresses = (PIP_ADAPTER_ADDRESSES)::operator new(outBufLen); + ULONG st = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &outBufLen); + if (st == ERROR_SUCCESS) + { + for (PIP_ADAPTER_ADDRESSES i = pAddresses; i; i = pAddresses->Next) + { + string name = i->AdapterName; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = pAddresses->FirstUnicastAddress; + while (pUnicast) + { + LocalInterface a; + if (pUnicast->Address.lpSockaddr) + a.addr = pUnicast->Address.lpSockaddr; + if (a.addr.len > 0) + { + // DO NOT collect addresses that are not of + // AF_INET or AF_INET6 family. + a.name = name; + locals.push_back(a); + } + pUnicast = pUnicast->Next; + } + } + } + + ::operator delete(pAddresses); + #endif + +#else + // Use POSIX method: getifaddrs + struct ifaddrs* pif, * pifa; + int st = getifaddrs(&pifa); + if (st == 0) + { + for (pif = pifa; pif; pif = pif->ifa_next) + { + LocalInterface i; + if (pif->ifa_addr) + i.addr = pif->ifa_addr; + if (i.addr.len > 0) + { + // DO NOT collect addresses that are not of + // AF_INET or AF_INET6 family. + i.name = pif->ifa_name ? pif->ifa_name : ""; + locals.push_back(i); + } + } + } + + freeifaddrs(pifa); +#endif + return locals; +} + + // Value display utilities // (also useful for applications) -std::string SockStatusStr(SRT_SOCKSTATUS s) +string SockStatusStr(SRT_SOCKSTATUS s) { if (int(s) < int(SRTS_INIT) || int(s) > int(SRTS_NONEXIST)) return "???"; @@ -539,7 +567,7 @@ std::string SockStatusStr(SRT_SOCKSTATUS s) static struct AutoMap { // Values start from 1, so do -1 to avoid empty cell - std::string names[int(SRTS_NONEXIST)-1+1]; + string names[int(SRTS_NONEXIST)-1+1]; AutoMap() { @@ -560,15 +588,14 @@ std::string SockStatusStr(SRT_SOCKSTATUS s) return names.names[int(s)-1]; } -#if ENABLE_BONDING -std::string MemberStatusStr(SRT_MEMBERSTATUS s) +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]; + string names[int(SRT_GST_BROKEN)+1]; AutoMap() { @@ -583,8 +610,35 @@ std::string MemberStatusStr(SRT_MEMBERSTATUS s) return names.names[int(s)]; } -#endif +string SrtCmdName(int cmd) +{ + if (cmd < 0 || cmd >= SRT_CMD_E_SIZE) + return "???"; + + static struct AutoMap + { + string names[SRT_CMD_E_SIZE]; -} // (end namespace srt_logging) + AutoMap() + { + names[0] = "noext"; // Use special case for 0 +#define SINI(statename) names[SRT_CMD_##statename] = #statename + SINI(HSREQ); + SINI(HSRSP); + SINI(KMREQ); + SINI(KMRSP); + SINI(SID); + SINI(CONGESTION); + SINI(FILTER); + SINI(GROUP); +#undef SINI + } + } names; + + return names.names[cmd]; +} + + +} // namespace srt diff --git a/srtcore/common.h b/srtcore/common.h index 5a53c05e6..abea7f108 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -54,6 +54,7 @@ modified by #define INC_SRT_COMMON_H #include +#include #include #include #ifndef _WIN32 @@ -77,36 +78,87 @@ modified by #define NET_ERROR WSAGetLastError() #endif -#ifdef _DEBUG +// SRT_ENABLE_THREADCHECK is set in CMakeLists.txt #if defined(SRT_ENABLE_THREADCHECK) #include "threadcheck.h" -#define SRT_ASSERT(cond) ASSERT(cond) -#else -#include -#define SRT_ASSERT(cond) assert(cond) -#endif #else -#define SRT_ASSERT(cond) +#define THREAD_STATE_INIT(name) +#define THREAD_EXIT() +#define THREAD_PAUSED() +#define THREAD_RESUMED() +#define INCREMENT_THREAD_ITERATIONS() #endif +// Defined here because it relies on SRT_ASSERT macro provided in utilities.h +#define SRT_ASSERT_AFFINITY(id) SRT_ASSERT(::srt::sync::CheckAffinity(id)) + +namespace srt +{ + #if HAVE_FULL_CXX11 #define SRT_STATIC_ASSERT(cond, msg) static_assert(cond, msg) #else -#define SRT_STATIC_ASSERT(cond, msg) -#endif -#include +// Kinda poor-man's replacement +template +struct StaticAssertHolder +{ + typedef typename FakeType::ok fail; +}; -namespace srt_logging +template +struct StaticAssertHolder { - std::string SockStatusStr(SRT_SOCKSTATUS s); -#if ENABLE_BONDING - std::string MemberStatusStr(SRT_MEMBERSTATUS s); -#endif + static const bool ok = true; +}; + +template +static inline bool StaticAssertCheck() +{ + StaticAssertHolder object; + return object.ok; } -namespace srt +// NOTE that in this replacement we can't do anything with the message. +// We can only count on that the compiler will point you this macro as the source. +// You can also rely on the line number attached to the name in case of unfriendly compilers. +// The use of __LINE__ is just a trick to allow creation of a new type symbol in every place where it's used, +// and both inside a function and in the global space. +#define SRT_PP_CONCAT_IN(x, y) x ## y +#define SRT_PP_CONCAT(x, y) SRT_PP_CONCAT_IN(x, y) +#define SRT_STATIC_ASSERT(condition, message) struct SRT_PP_CONCAT(StaticAssertImp_,__LINE__) { bool ok; SRT_PP_CONCAT(StaticAssertImp_,__LINE__) (): ok(::srt::StaticAssertCheck< (condition) >()) {}} + +#endif + +struct CNetworkInterface { + sockaddr_any address; + int interface_index; + + template + CNetworkInterface(const InAddrType& sa, int index) + : address(sa, 0) + , interface_index(index) + { + } + + CNetworkInterface() // blank fallback + : address(AF_UNSPEC) + , interface_index(0) + { + } + + std::string str() const + { + return hvu::fmtcat(address.str(), "/", interface_index); + } +}; + + +std::string SockStatusStr(SRT_SOCKSTATUS s); +std::string MemberStatusStr(SRT_MEMBERSTATUS s); +std::string SrtCmdName(int cmd); + // Class CUDTException exposed for C++ API. // This is actually useless, unless you'd use a DIRECT C++ API, @@ -299,7 +351,7 @@ 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::SRTT(). + TEV_ACKACK, // --> UDT does only RTT sync, can be read from CUDT::avgRTT(). 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 @@ -338,7 +390,7 @@ struct EventVariant enum Type {UNDEFINED, PACKET, ARRAY, ACK, STAGE, INIT} type; union U { - const srt::CPacket* packet; + const CPacket* packet; int32_t ack; struct { @@ -357,7 +409,7 @@ struct EventVariant // 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) + explicit EventVariant(const CPacket* arg) { type = PACKET; u.packet = arg; @@ -446,7 +498,7 @@ class EventArgType; // use a full-templated version. TBD. template<> struct EventVariant::VariantFor { - typedef const srt::CPacket* type; + typedef const CPacket* type; static type U::*field() {return &U::packet;} }; @@ -563,71 +615,20 @@ struct EventSlot }; -// UDT Sequence Number 0 - (2^31 - 1) +// Sequence Numbers 0 - (2^31 - 1) + +// CONVENTION USED IN THE COMMENTS: -// seqcmp: compare two seq#, considering the wrapping -// 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 +// Operations done on all kinds of cirtulcar 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 +// * or / are not available. class CSeqNo { - int32_t value; - public: - explicit CSeqNo(int32_t v): value(v) {} - - // Comparison - bool operator == (const CSeqNo& other) const { return other.value == value; } - bool operator < (const CSeqNo& other) const - { - return seqcmp(value, other.value) < 0; - } - - // 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; - } - - // circular arithmetic - 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)); - } - - friend CSeqNo operator+(const CSeqNo& c1, int off) - { - return CSeqNo(incseq(c1.value, off)); - } - - friend CSeqNo operator+(int off, const CSeqNo& c1) - { - return CSeqNo(incseq(c1.value, off)); - } - - 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. /// Returns a negative value if seq1 < seq2, @@ -640,7 +641,7 @@ class CSeqNo /// /// 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) + 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, @@ -652,7 +653,7 @@ class CSeqNo /// 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) + static int seqlen(int32_t seq1, int32_t seq2) { SRT_ASSERT(seq1 >= 0 && seq1 <= m_iMaxSeqNo); SRT_ASSERT(seq2 >= 0 && seq2 <= m_iMaxSeqNo); @@ -669,7 +670,7 @@ class CSeqNo /// Note: this function does more calculations than seqcmp, so it should /// be used if you need the exact distance between two sequences. If /// you are only interested with their relationship, use seqcmp. - inline static int seqoff(int32_t seq1, int32_t seq2) + static int seqoff(int32_t seq1, int32_t seq2) { if (abs(seq1 - seq2) < m_iSeqNoTH) return seq2 - seq1; @@ -680,24 +681,24 @@ class CSeqNo return seq2 - seq1 + m_iMaxSeqNo + 1; } - inline static int32_t incseq(int32_t seq) + static int32_t incseq(int32_t seq) {return (seq == m_iMaxSeqNo) ? 0 : seq + 1;} - inline static int32_t decseq(int32_t seq) + static int32_t decseq(int32_t seq) {return (seq == 0) ? m_iMaxSeqNo : seq - 1;} - inline static int32_t incseq(int32_t seq, int32_t inc) + static int32_t incseq(int32_t seq, int32_t inc) {return (m_iMaxSeqNo - seq >= inc) ? seq + inc : seq - m_iMaxSeqNo + inc - 1;} // m_iMaxSeqNo >= inc + sec --- inc + sec <= m_iMaxSeqNo // if inc + sec > m_iMaxSeqNo then return seq + inc - (m_iMaxSeqNo+1) - inline static int32_t decseq(int32_t seq, int32_t dec) + static int32_t decseq(int32_t seq, int32_t dec) { // Check if seq - dec < 0, but before it would have happened if ( seq < dec ) { int32_t left = dec - seq; // This is so many that is left after dragging dec to 0 - // So now decrement the (m_iMaxSeqNo+1) by "left" + // So now decrement the (m_iMaxSeqNo+1) by "left" return m_iMaxSeqNo - left + 1; } return seq - dec; @@ -715,6 +716,111 @@ class CSeqNo static const int32_t m_iMaxSeqNo = 0x7FFFFFFF; // maximum sequence number used in UDT }; +// We allow to use alternatively some rich integer, +// although it must be int32_t-based. +template +class SeqNoT: public CSeqNo +{ + CoreType value; + +public: + + explicit SeqNoT(int32_t v): value(v) {} + + // Setter for a case when operator= would be misleading + // and types cannot be agreed upon. + void set(int32_t val) + { + value = val; + } + + template + void set(const SeqNoT& val) + { + value = val.value; + } + + template + SeqNoT& operator=(const SeqNoT& o) + { + value = o.val(); + return *this; + } + + int32_t val() const { return value; } + + // Comparison + template + bool operator == (const SeqNoT& other) const { return other.value == value; } + + template + bool operator < (const SeqNoT& other) const + { + return seqcmp(value, other.value) < 0; + } + + // The std::rel_ops namespace cannot be "imported" + // as a whole into the class - it can only be used + // in the application code. + template + bool operator != (const SeqNoT& other) const { return other.value != value; } + template + bool operator > (const SeqNoT& other) const { return other < *this; } + template + bool operator >= (const SeqNoT& other) const + { + return seqcmp(value, other.value) >= 0; + } + template + bool operator <=(const SeqNoT& other) const + { + return seqcmp(value, other.value) <= 0; + } + + // circular arithmetic + template + friend int operator-(const SeqNoT& c1, const SeqNoT& c2) + { + return seqoff(c2.val(), c1.value); + } + + friend SeqNoT operator-(const SeqNoT& c1, int off) + { + return SeqNoT(decseq(c1.value, off)); + } + + friend SeqNoT operator+(const SeqNoT& c1, int off) + { + return SeqNoT(incseq(c1.value, off)); + } + + friend SeqNoT operator+(int off, const SeqNoT& c1) + { + return SeqNoT(incseq(c1.value, off)); + } + + SeqNoT& operator++() + { + value = incseq(value); + return *this; + } + + SeqNoT operator++(int) + { + SeqNoT old = *this; + value = incseq(value); + return old; + } + + SRT_ATR_NODISCARD SeqNoT inc() const { return SeqNoT(incseq(value)); } + SRT_ATR_NODISCARD SeqNoT dec() const { return SeqNoT(decseq(value)); } + SRT_ATR_NODISCARD SeqNoT inc(int32_t i) const { return SeqNoT(incseq(value, i)); } + + SRT_ATR_NODISCARD SeqNoT dec(int32_t i) const { return SeqNoT(decseq(value, i)); } +}; + +typedef SeqNoT SeqNo; + //////////////////////////////////////////////////////////////////////////////// // UDT ACK Sub-sequence Number: 0 - (2^31 - 1) @@ -722,6 +828,11 @@ class CSeqNo class CAckNo { public: + // 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. inline static int32_t incack(int32_t ackno) {return (ackno == m_iMaxAckSeqNo) ? 0 : ackno + 1;} @@ -858,12 +969,23 @@ class RollNumber struct CIPAddress { - static bool ipcmp(const struct sockaddr* addr1, const struct sockaddr* addr2, 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); + static void encode(const struct sockaddr_any& addr, uint32_t (&ip)[4]); + static void decode(const uint32_t (&ip)[4], const sockaddr_any& peer, sockaddr_any& w_addr); + + // NOTE: This function could return hvu::ofmtbufstream, but the enclosed + // std::stringstream is not copyable before C++11. + static std::string show(const uint32_t (&ip)[4]); }; +bool checkMappedIPv4(const uint16_t* sa); + +inline bool checkMappedIPv4(const sockaddr_in6& sa) +{ + const uint16_t* addr = reinterpret_cast(&sa.sin6_addr.s6_addr); + return checkMappedIPv4(addr); +} + + //////////////////////////////////////////////////////////////////////////////// struct CMD5 @@ -1396,49 +1518,105 @@ 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) - { + std::string sparts[3]; // "1" "6" "0" + if (!v || !Split(v, '.', sparts, 3)) return 0; - } - return SrtVersion(major, minor, patch); + int parts[3]; + for (int i = 0; i < 3; ++i) + { + int ival = atoi(sparts[i].c_str()); + if (ival == 0) + { + if (sparts[i] != "0") + return 0; + } + parts[i] = ival; + } + return SrtVersion(parts[0], parts[1], parts[2]); } inline std::string SrtVersionString(int version) { + hvu::ofmtbufstream out; + int patch = version % 0x100; int minor = (version/0x100)%0x100; int major = version/0x10000; - - 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; + out << major << "." << minor << "." << patch; + return out.str(); } bool SrtParseConfig(const std::string& s, SrtConfig& w_config); -bool checkMappedIPv4(const uint16_t* sa); - -inline bool checkMappedIPv4(const sockaddr_in6& sa) -{ - const uint16_t* addr = reinterpret_cast(&sa.sin6_addr.s6_addr); - return checkMappedIPv4(addr); -} - std::string FormatLossArray(const std::vector< std::pair >& lra); std::ostream& PrintEpollEvent(std::ostream& os, int events, int et_events = 0); std::string FormatValue(int value, int factor, const char* unit); +struct LocalInterface +{ + sockaddr_any addr; + std::string name; +}; + +std::vector GetLocalInterfaces(); + + +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; + } + + // Reserve nblocks of messages. Still, do not exceed + void reserve(size_t nblocks = 0) + { + if (nblocks == 0 || nblocks + storage.size() > maxstorage) + nblocks = maxstorage; + + for (size_t i = 0; i < nblocks; ++i) + { + char* block = new char[blocksize]; + storage.push_back(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]; + } +}; + } // namespace srt #endif diff --git a/srtcore/congctl.cpp b/srtcore/congctl.cpp index 9bc43db8b..57c3aec32 100644 --- a/srtcore/congctl.cpp +++ b/srtcore/congctl.cpp @@ -35,7 +35,7 @@ using namespace std; using namespace srt::sync; -using namespace srt_logging; +using namespace srt::logging; namespace srt { @@ -60,8 +60,8 @@ void SrtCongestion::Check() class LiveCC: public SrtCongestionControlBase { - srt::sync::atomic m_llSndMaxBW; //Max bandwidth (bytes/sec) - srt::sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit + sync::atomic m_llSndMaxBW; //Max bandwidth (bytes/sec) + sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit size_t m_zMaxPayloadSize; size_t m_zHeaderSize; @@ -370,11 +370,11 @@ class FileCC : public SrtCongestionControlBase } else { - m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); + m_dPktSndPeriod = m_dCWndSize / (m_parent->avgRTT() + 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->SRTT() << " RCIV=" << m_iRCInterval); + << m_parent->avgRTT() << " RCIV=" << m_iRCInterval); } } else @@ -386,9 +386,9 @@ class FileCC : public SrtCongestionControlBase } else { - m_dCWndSize = m_parent->deliveryRate() / 1000000.0 * (m_parent->SRTT() + m_iRCInterval) + 16; + m_dCWndSize = m_parent->deliveryRate() / 1000000.0 * (m_parent->avgRTT() + m_iRCInterval) + 16; HLOGC(cclog.Debug, log << "FileCC: UPD (speed mode) wndsize=" - << m_dCWndSize << "/" << m_dMaxCWndSize << " RTT = " << m_parent->SRTT() + << m_dCWndSize << "/" << m_dMaxCWndSize << " RTT = " << m_parent->avgRTT() << " sndperiod=" << m_dPktSndPeriod << "us. deliverRate = " << m_parent->deliveryRate() << " pkts/s)"); } @@ -429,7 +429,7 @@ class FileCC : public SrtCongestionControlBase } } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING // Try to do reverse-calculation for m_dPktSndPeriod, as per minSP below // sndperiod = mega / (maxbw / MSS) // 1/sndperiod = (maxbw/MSS) / mega @@ -439,8 +439,8 @@ class FileCC : public SrtCongestionControlBase #if defined(unix) && defined (SRT_ENABLE_SYSTEMBUFFER_TRACE) // Check the outgoing system queue level - int udp_buffer_size = m_parent->sndQueue()->sockoptQuery(SOL_SOCKET, SO_SNDBUF); - int udp_buffer_level = m_parent->sndQueue()->ioctlQuery(TIOCOUTQ); + int udp_buffer_size = m_parent->m_pChannel->sockoptQuery(SOL_SOCKET, SO_SNDBUF); + int udp_buffer_level = m_parent->m_pChannel->ioctlQuery(TIOCOUTQ); int udp_buffer_free = udp_buffer_size - udp_buffer_level; #else int udp_buffer_free = -1; @@ -494,9 +494,9 @@ class FileCC : public SrtCongestionControlBase } else { - m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); + m_dPktSndPeriod = m_dCWndSize / (m_parent->avgRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: LOSS, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS wndsize/(RTT+RCIV) (RTT=" - << m_parent->SRTT() << " RCIV=" << m_iRCInterval << ")"); + << m_parent->avgRTT() << " RCIV=" << m_iRCInterval << ")"); } } @@ -504,7 +504,7 @@ class FileCC : public SrtCongestionControlBase m_bLoss = true; // TODO: const int pktsInFlight = CSeqNo::seqoff(m_iLastAck, m_parent->sndSeqNo()); - const int pktsInFlight = static_cast(m_parent->SRTT() / m_dPktSndPeriod); + const int pktsInFlight = static_cast(m_parent->avgRTT() / m_dPktSndPeriod); const int numPktsLost = m_parent->sndLossLength(); const int lost_pcent_x10 = pktsInFlight > 0 ? (numPktsLost * 1000) / pktsInFlight : 0; @@ -593,9 +593,9 @@ class FileCC : public SrtCongestionControlBase } else { - m_dPktSndPeriod = m_dCWndSize / (m_parent->SRTT() + m_iRCInterval); + m_dPktSndPeriod = m_dCWndSize / (m_parent->avgRTT() + m_iRCInterval); HLOGC(cclog.Debug, log << "FileCC: CHKTIMER, SLOWSTART:OFF, sndperiod=" << m_dPktSndPeriod << "us AS wndsize/(RTT+RCIV) (wndsize=" - << m_dCWndSize << " RTT=" << m_parent->SRTT() << " RCIV=" << m_iRCInterval << ")"); + << m_dCWndSize << " RTT=" << m_parent->avgRTT() << " RCIV=" << m_iRCInterval << ")"); } } else diff --git a/srtcore/congctl.h b/srtcore/congctl.h index a2264b594..2bdf7a9ed 100644 --- a/srtcore/congctl.h +++ b/srtcore/congctl.h @@ -20,7 +20,7 @@ namespace srt { class CUDT; class SrtCongestionControlBase; -typedef SrtCongestionControlBase* srtcc_create_t(srt::CUDT* parent); +typedef SrtCongestionControlBase* srtcc_create_t(CUDT* parent); class SrtCongestion { @@ -98,7 +98,7 @@ class SrtCongestion // 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(srt::CUDT* parent); + bool configure(CUDT* parent); // This function will intentionally delete the contained object. // This makes future calls to ready() return false. Calling @@ -136,7 +136,7 @@ class SrtCongestionControlBase { protected: // Here can be some common fields - srt::CUDT* m_parent; + CUDT* m_parent; double m_dPktSndPeriod; double m_dCWndSize; @@ -147,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->SRTT() instead. + //int m_RTT; // NOT REQUIRED. Use m_parent->avgRTT() 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(srt::CUDT* parent); + SrtCongestionControlBase(CUDT* parent); public: // This could be also made abstract, but this causes a linkage @@ -193,7 +193,7 @@ class SrtCongestionControlBase // 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 srt::CPacket&) + virtual bool needsQuickACK(const CPacket&) { return false; } diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 7b110d432..efa869d76 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -50,17 +50,31 @@ modified by Haivision Systems Inc. *****************************************************************************/ +// Set this to 5 to fake "lost" handshake packets 5 times in a row +#define SRT_ENABLE_FAKE_LOSS_HS 0 + #include "platform_sys.h" +#include "srt_attr_defs.h" // Linux specific #ifdef SRT_ENABLE_BINDTODEVICE #include #endif +// Again, just in case when some "smart guy" provided such a global macro +#ifdef min +#undef min +#endif +#ifdef max +#undef max +#endif + #include #include +#include #include #include +#include "srt_attr_defs.h" #include "srt.h" #include "access_control.h" // Required for SRT_REJX_FALLBACK #include "queue.h" @@ -69,55 +83,51 @@ modified by #include "logging.h" #include "crypto.h" #include "logging_api.h" // Required due to containing extern srt_logger_config -#include "logger_defs.h" +#include "logger_fas.h" -#if !HAVE_CXX11 -// for pthread_once -#include -#endif +using namespace std; +using namespace srt::sync; +using namespace srt::logging; +using namespace hvu; // ofmt -// Again, just in case when some "smart guy" provided such a global macro -#ifdef min -#undef min -#endif -#ifdef max -#undef max +#if HVU_ENABLE_LOGGING +static const char* const s_hs_side[] = { "DRAW", "INITIATOR", "RESPONDER" }; #endif -using namespace std; -using namespace srt; -using namespace srt::sync; -using namespace srt_logging; +// XXX For testing: use common loss list for also broadcast groups. +#define BROADCAST_COMMON_SND_LOSS 1 +namespace srt +{ -const SRTSOCKET UDT::INVALID_SOCK = srt::CUDT::INVALID_SOCK; -const int UDT::ERROR = srt::CUDT::ERROR; +static inline char fmt_onoff(bool val) { return val ? '+' : '-'; } +// Mark unused because it's only used in HLOGC +SRT_ATR_UNUSED static inline const char* fmt_yesno(bool val) { return val ? "yes" : "no"; } -//#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */ -#define SRT_CMD_HSREQ_MINSZ 8 /* Minimum Compatible (1.x.x) packet size (bytes) */ -#define SRT_CMD_HSREQ_SZ 12 /* Current version packet size */ -#if SRT_CMD_HSREQ_SZ > SRT_CMD_MAXSZ -#error SRT_CMD_MAXSZ too small -#endif -/* Handshake Request (Network Order) - 0[31..0]: SRT version SRT_DEF_VERSION - 1[31..0]: Options 0 [ | SRT_OPT_TSBPDSND ][ | SRT_OPT_HAICRYPT ] - 2[31..16]: TsbPD resv 0 - 2[15..0]: TsbPD delay [0..60000] msec -*/ +const size_t SRT_CMD_HSREQ_MINSZ = 8; // Minimum Compatible (1.x.x) packet size (bytes) +const size_t SRT_CMD_HSREQ_SZ = 12; // Current version packet size + +SRT_STATIC_ASSERT(SRT_CMD_HSREQ_SZ <= SRT_CMD_MAXSZ, "error: SRT_CMD_MAXSZ too small"); + +// Handshake Request (Network Order) +// 0[31..0]: SRT version SRT_DEF_VERSION +// 1[31..0]: Options 0 [ | SRT_OPT_TSBPDSND ][ | SRT_OPT_HAICRYPT ] +// 2[31..16]: TsbPD resv 0 +// 2[15..0]: TsbPD delay [0..60000] msec +// //#define SRT_CMD_HSRSP 2 /* SRT Handshake Response (receiver) */ -#define SRT_CMD_HSRSP_MINSZ 8 /* Minimum Compatible (1.x.x) packet size (bytes) */ -#define SRT_CMD_HSRSP_SZ 12 /* Current version packet size */ -#if SRT_CMD_HSRSP_SZ > SRT_CMD_MAXSZ -#error SRT_CMD_MAXSZ too small -#endif -/* Handshake Response (Network Order) - 0[31..0]: SRT version SRT_DEF_VERSION - 1[31..0]: Options 0 [ | SRT_OPT_TSBPDRCV [| SRT_OPT_TLPKTDROP ]][ | SRT_OPT_HAICRYPT] - [ | SRT_OPT_NAKREPORT ] [ | SRT_OPT_REXMITFLG ] - 2[31..16]: TsbPD resv 0 - 2[15..0]: TsbPD delay [0..60000] msec -*/ +const size_t SRT_CMD_HSRSP_MINSZ = 8; // Minimum Compatible (1.x.x) packet size (bytes) +const size_t SRT_CMD_HSRSP_SZ = 12; // Current version packet size */ + +SRT_STATIC_ASSERT(SRT_CMD_HSRSP_SZ <= SRT_CMD_MAXSZ, " error: SRT_CMD_MAXSZ too small"); + +// Handshake Response (Network Order) +// 0[31..0]: SRT version SRT_DEF_VERSION +// 1[31..0]: Options 0 [ | SRT_OPT_TSBPDRCV [| SRT_OPT_TLPKTDROP ]][ | SRT_OPT_HAICRYPT] +// [ | SRT_OPT_NAKREPORT ] [ | SRT_OPT_REXMITFLG ] +// 2[31..16]: TsbPD resv 0 +// 2[15..0]: TsbPD delay [0..60000] msec +// // IMPORTANT!!! This array must be ordered by value, because std::binary_search is performed on it! extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = { @@ -132,10 +142,8 @@ extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = { SRTO_SNDDROPDELAY, SRTO_DRIFTTRACER, SRTO_MININPUTBW, - SRTO_LOSSMAXTTL -#ifdef ENABLE_MAXREXMITBW - ,SRTO_MAXREXMITBW -#endif + SRTO_LOSSMAXTTL, + SRTO_MAXREXMITBW }; const int32_t @@ -144,9 +152,6 @@ const int32_t SRTO_POST_SPEC = BIT(2); //< executes some action after setting the option -namespace srt -{ - struct SrtOptionAction { int flags[SRTO_E_SIZE]; @@ -202,13 +207,13 @@ struct SrtOptionAction #ifdef SRT_ENABLE_BINDTODEVICE flags[SRTO_BINDTODEVICE] = SRTO_R_PREBIND; #endif -#if ENABLE_BONDING +#if SRT_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 +#ifdef SRT_ENABLE_AEAD flags[SRTO_CRYPTOMODE] = SRTO_R_PRE; #endif @@ -230,37 +235,14 @@ struct SrtOptionAction const SrtOptionAction s_sockopt_action; -} // namespace srt - -#if HAVE_CXX11 - -CUDTUnited& srt::CUDT::uglobal() +CUDTUnited& 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 - -#ifdef ENABLE_RATE_MEASUREMENT -void srt::RateMeasurement::pickup(const clock_time& time) +#ifdef SRT_ENABLE_RATE_MEASUREMENT +void RateMeasurement::pickup(const clock_time& time) { // Check if the time has passed the period. clock_interval diff; @@ -387,11 +369,11 @@ void srt::RateMeasurement::pickup(const clock_time& time) } #endif -void srt::CUDT::construct() +SRT_TSA_DISABLED +void CUDT::construct() { m_pSndBuffer = NULL; m_pRcvBuffer = NULL; - m_pSndLossList = NULL; m_pRcvLossList = NULL; m_iReorderTolerance = 0; // How many times so far the packet considered lost has been received @@ -399,15 +381,14 @@ void srt::CUDT::construct() m_iConsecEarlyDelivery = 0; m_iConsecOrderedDelivery = 0; - m_pSndQueue = NULL; - m_pRcvQueue = NULL; - m_pSNode = NULL; - m_pRNode = NULL; + m_pMuxer = NULL; + m_MuxNode = SocketHolder::none(); // Mark that it doesn't belong to any multiplexer + m_TransferIPVersion = AF_UNSPEC; // Will be set after connection // Will be reset to 0 for HSv5, this value is important for HSv4. m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; - m_PeerID = 0; + m_PeerID = SRT_SOCKID_CONNREQ; m_bOpened = false; m_bListening = false; m_bConnecting = false; @@ -431,9 +412,13 @@ void srt::CUDT::construct() m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; m_bBufferWasFull = false; + m_bManaged = false; + + m_iSndMinFlightSpan = -1; // -1 value means "not measured". Normally all current values of -1 are rejected. + // (note that flight == 0 is still a valid value) // Will be updated on first send -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW m_zSndAveragePacketSize = 0; m_zSndMaxPacketSize = 0; #endif @@ -445,21 +430,34 @@ void srt::CUDT::construct() // m_cbPacketArrival.set(this, &CUDT::defaultPacketArrival); } -srt::CUDT::CUDT(CUDTSocket* parent) +CUDT::CUDT(CUDTSocket* parent) : m_parent(parent) -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW // , m_SndRexmitRate(sync::steady_clock::now()) , m_SndRexmitShaper(CSrtConfig::DEF_MSS) #endif , m_iISN(-1) , m_iPeerISN(-1) + + // Explicitly calling constructors of Mutex for the sake of thread sanitizer + // (without this it cannot precisely declare, where particular mutex was created + // and makes it hard to identify the mutex it reported). + , m_RcvTsbPdStartupLock() + , m_ConnectionLock() + , m_SendBlockLock() + , m_RcvBufferLock() + , m_RecvAckLock() + , m_RecvLock() + , m_SendLock() + , m_RcvLossLock() + , m_StatsLock() { construct(); (void)SRT_DEF_VERSION; // Runtime fields -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING m_HSGroupType = SRT_GTYPE_UNDEFINED; #endif m_bTLPktDrop = true; // Too-late Packet Drop @@ -475,14 +473,25 @@ srt::CUDT::CUDT(CUDTSocket* parent) } -srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) +CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) : m_parent(parent) -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW // , m_SndRexmitRate(sync::steady_clock::now()) , m_SndRexmitShaper(CSrtConfig::DEF_MSS) #endif , m_iISN(-1) , m_iPeerISN(-1) + + // Explicit mutex + , m_RcvTsbPdStartupLock() + , m_ConnectionLock() + , m_SendBlockLock() + , m_RcvBufferLock() + , m_RecvAckLock() + , m_RecvLock() + , m_SendLock() + , m_RcvLossLock() + , m_StatsLock() { construct(); @@ -518,7 +527,7 @@ srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) m_pCache = ancestor.m_pCache; } -srt::CUDT::~CUDT() +CUDT::~CUDT() { // release mutex/condition variables destroySynch(); @@ -526,13 +535,10 @@ srt::CUDT::~CUDT() // destroy the data structures delete m_pSndBuffer; delete m_pRcvBuffer; - delete m_pSndLossList; delete m_pRcvLossList; - delete m_pSNode; - delete m_pRNode; } -void srt::CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) +void CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -591,7 +597,7 @@ void srt::CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) } } -void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) +void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) { ScopedLock cg(m_ConnectionLock); @@ -700,7 +706,7 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) optlen = sizeof(int32_t); break; -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW case SRTO_MAXREXMITBW: if (size_t(optlen) < sizeof(m_config.llMaxRexmitBW)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -721,10 +727,10 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) event |= SRT_EPOLL_ERR; else { - enterCS(m_RecvLock); + m_RecvLock.lock(); if (m_pRcvBuffer && isRcvBufferReady()) event |= SRT_EPOLL_IN; - leaveCS(m_RecvLock); + m_RecvLock.unlock(); if (m_pSndBuffer && (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize())) event |= SRT_EPOLL_OUT; } @@ -744,9 +750,8 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) case SRTO_RCVDATA: if (m_pRcvBuffer) { - enterCS(m_RecvLock); + ScopedLock lck(m_RcvBufferLock); *(int32_t *)optval = m_pRcvBuffer->getRcvDataSize(); - leaveCS(m_RecvLock); } else *(int32_t *)optval = 0; @@ -755,7 +760,7 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) case SRTO_IPTTL: if (m_bOpened) - *(int32_t *)optval = m_pSndQueue->getIpTTL(); + *(int32_t *)optval = channel()->getIpTTL(); else *(int32_t *)optval = m_config.iIpTTL; optlen = sizeof(int32_t); @@ -763,7 +768,7 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) case SRTO_IPTOS: if (m_bOpened) - *(int32_t *)optval = m_pSndQueue->getIpToS(); + *(int32_t *)optval = channel()->getIpToS(); else *(int32_t *)optval = m_config.iIpToS; optlen = sizeof(int32_t); @@ -774,7 +779,7 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) if (optlen < IFNAMSIZ) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - if (m_bOpened && m_pSndQueue->getBind(((char*)optval), optlen)) + if (m_bOpened && channel()->getBind(((char*)optval), optlen)) { optlen = strlen((char*)optval); break; @@ -832,36 +837,25 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) break; 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. + *(int32_t *)optval = (int32_t) m_CryptoControl.keylen(); // Running Key length.(may be 0) optlen = sizeof(int32_t); break; case SRTO_KMSTATE: - if (!m_pCryptoControl) - *(int32_t *)optval = SRT_KM_S_UNSECURED; - else if (m_config.bDataSender) - *(int32_t *)optval = m_pCryptoControl->m_SndKmState; + if (m_config.bDataSender) + *(int32_t *)optval = m_CryptoControl.kmState().snd; else - *(int32_t *)optval = m_pCryptoControl->m_RcvKmState; + *(int32_t *)optval = m_CryptoControl.kmState().rcv; optlen = sizeof(int32_t); break; case SRTO_SNDKMSTATE: // State imposed by Agent depending on PW and KMX - if (m_pCryptoControl) - *(int32_t *)optval = m_pCryptoControl->m_SndKmState; - else - *(int32_t *)optval = SRT_KM_S_UNSECURED; + *(int32_t *)optval = m_CryptoControl.kmState().snd; optlen = sizeof(int32_t); break; case SRTO_RCVKMSTATE: // State returned by Peer as informed during KMX - if (m_pCryptoControl) - *(int32_t *)optval = m_pCryptoControl->m_RcvKmState; - else - *(int32_t *)optval = SRT_KM_S_UNSECURED; + *(int32_t *)optval = m_CryptoControl.kmState().rcv; optlen = sizeof(int32_t); break; @@ -936,7 +930,7 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) *(int*)optval = (int)m_config.uKmPreAnnouncePkt; break; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING case SRTO_GROUPCONNECT: optlen = sizeof (int); *(int*)optval = m_config.iGroupConnect; @@ -980,12 +974,9 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) *(int32_t *)optval = m_config.iRetransmitAlgo; optlen = sizeof(int32_t); break; -#ifdef ENABLE_AEAD_API_PREVIEW +#ifdef SRT_ENABLE_AEAD case SRTO_CRYPTOMODE: - if (m_pCryptoControl) - *(int32_t*)optval = m_pCryptoControl->getCryptoMode(); - else - *(int32_t*)optval = m_config.iCryptoMode; + *(int32_t*)optval = m_CryptoControl.getCryptoMode(); optlen = sizeof(int32_t); break; #endif @@ -996,8 +987,8 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) } -#if ENABLE_BONDING -SRT_ERRNO srt::CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt) +#if SRT_ENABLE_BONDING +SRT_ERRNO CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt) { SRT_SOCKOPT this_opt = SRTO_VERSION; for (size_t i = 0; i < opt.options.size(); ++i) @@ -1011,7 +1002,7 @@ SRT_ERRNO srt::CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt) } #endif -bool srt::CUDT::setstreamid(SRTSOCKET u, const std::string &sid) +bool CUDT::setstreamid(SRTSOCKET u, const std::string &sid) { CUDT *that = getUDTHandle(u); if (!that) @@ -1027,7 +1018,7 @@ bool srt::CUDT::setstreamid(SRTSOCKET u, const std::string &sid) return true; } -string srt::CUDT::getstreamid(SRTSOCKET u) +string CUDT::getstreamid(SRTSOCKET u) { CUDT *that = getUDTHandle(u); if (!that) @@ -1039,9 +1030,9 @@ string srt::CUDT::getstreamid(SRTSOCKET u) // XXX REFACTOR: Make common code for CUDT constructor and clearData, // possibly using CUDT::construct. // Initial sequence number, loss, acknowledgement, etc. -void srt::CUDT::clearData() +void CUDT::clearData() { - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE - CPacket::HDR_SIZE; + const size_t full_hdr_size = CPacket::udpHeaderSize(AF_INET) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); @@ -1091,26 +1082,19 @@ void srt::CUDT::clearData() m_tsRcvPeerStartTime = steady_clock::time_point(); } -void srt::CUDT::open() +//This function is being called at the moment when no +// thread-related facilities for this socket are running. +// Hence thread checking is disabled. + +SRT_TSA_DISABLED +void CUDT::open() { + // XXX The above comment says there are no other threads + // for that socket running at the moment...? ScopedLock cg(m_ConnectionLock); clearData(); - // structures for queue - if (m_pSNode == NULL) - m_pSNode = new CSNode; - 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_tsTimeStamp = steady_clock::now(); - m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL; - m_pRNode->m_bOnList = false; - // Set initial values of smoothed RTT and RTT variance. m_iSRTT = INITIAL_RTT; m_iRTTVar = INITIAL_RTTVAR; @@ -1124,13 +1108,14 @@ void srt::CUDT::open() 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 +#if SRT_ENABLE_BONDING m_tsUnstableSince = steady_clock::time_point(); m_tsFreshActivation = steady_clock::time_point(); m_tsWarySince = steady_clock::time_point(); @@ -1147,7 +1132,7 @@ void srt::CUDT::open() m_bOpened = true; } -void srt::CUDT::setListenState() +void CUDT::setListenState() { if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); @@ -1164,7 +1149,7 @@ void srt::CUDT::setListenState() if (m_bListening.compare_exchange(false, true)) { // if there is already another socket listening on the same port - if (!m_pRcvQueue->setListener(this)) + if (!m_pMuxer->setListener(this)) { // Failed here, so m_bListening = false; @@ -1178,7 +1163,7 @@ void srt::CUDT::setListenState() // the listener could have failed. Therefore check // again if the listener was set successfully, and // if the listening point is still free, try again. - CUDT* current = m_pRcvQueue->getListener(); + CUDT* current = m_pMuxer->getListener(); if (current == NULL) { continue; @@ -1194,7 +1179,7 @@ void srt::CUDT::setListenState() } } -size_t srt::CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version) +size_t CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version) { if (srtlen < SRT_HS_E_SIZE) { @@ -1222,7 +1207,7 @@ size_t srt::CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgt } } -size_t srt::CUDT::fillSrtHandshake_HSREQ(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) +size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // INITIATOR sends HSREQ. @@ -1285,7 +1270,7 @@ size_t srt::CUDT::fillSrtHandshake_HSREQ(uint32_t *aw_srtdata, size_t /* srtlen return 3; } -size_t srt::CUDT::fillSrtHandshake_HSRSP(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) +size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *aw_srtdata, size_t /* srtlen - unused */, int hs_version) { // Setting m_tsRcvPeerStartTime is done in processSrtMsg_HSREQ(), so // this condition will be skipped only if this function is called without @@ -1395,8 +1380,9 @@ size_t srt::CUDT::fillSrtHandshake_HSRSP(uint32_t *aw_srtdata, size_t /* srtlen return 3; } -size_t srt::CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) +size_t CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) { + // [TSA] handshakeVersion refers to m_ConnRes, which is last modified in the HS. size_t srtlen = fillSrtHandshake(srtdata, size, cmd, handshakeVersion()); HLOGC(cnlog.Debug, log << "CMD:" << MessageTypeStr(UMSG_EXT, cmd) << "(" << cmd << ") Len:" << int(srtlen * sizeof(int32_t)) @@ -1408,7 +1394,7 @@ size_t srt::CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size) return srtlen; } -void srt::CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, size_t srtlen_in) +void CUDT::sendSrtMsg(int cmd, const uint32_t *srtdata_in, size_t srtlen_in) { CPacket srtpkt; int32_t srtcmd = (int32_t)cmd; @@ -1442,7 +1428,7 @@ void srt::CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, size_t srtlen_in) * Pre-swap to cancel it. */ HtoNLA(srtdata, srtdata_in, srtlen); - m_pCryptoControl->updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT + m_CryptoControl.updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT break; @@ -1459,7 +1445,7 @@ void srt::CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, size_t srtlen_in) } } -size_t srt::CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const string& str) +size_t CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const string& str) { uint32_t* space = pcmdspec + 1; size_t wordsize = (str.size() + 3) / 4; @@ -1475,10 +1461,10 @@ size_t srt::CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const strin return wordsize; } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING // [[using locked(m_parent->m_ControlLock)]] // [[using locked(s_UDTUnited.m_GlobControlLock)]] -size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) +size_t CUDT::fillHsExtGroup(uint32_t* pcmdspec) { SRT_ASSERT(m_parent->m_GroupOf != NULL); uint32_t* space = pcmdspec + 1; @@ -1493,7 +1479,7 @@ size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) // 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 +#if HVU_ENABLE_HEAVY_LOGGING m_parent->m_GroupOf->debugMasterData(m_SocketID); #endif @@ -1502,9 +1488,9 @@ size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) 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); + | SrtHSRequest::HS_GROUP_WEIGHT::wrap(m_parent->m_GroupMemberData.load()->weight); - const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(id), dataword }; + const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(int(id)), dataword }; memcpy((space), storedata, sizeof storedata); const size_t ra_size = Size(storedata); @@ -1514,11 +1500,11 @@ size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) } #endif -size_t srt::CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) +size_t CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) { uint32_t* space = pcmdspec + 1; - size_t msglen = m_pCryptoControl->getKmMsg_size(ki); + size_t msglen = m_CryptoControl.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); @@ -1529,7 +1515,7 @@ size_t srt::CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) // Copy the key - do the endian inversion because another endian inversion // will be done for every control message before sending, and this KM message // is ALREADY in network order. - const uint32_t* keydata = reinterpret_cast(m_pCryptoControl->getKmMsg_data(ki)); + const uint32_t* keydata = reinterpret_cast(m_CryptoControl.getKmMsg_data(ki)); HLOGC(cnlog.Debug, log << CONID() << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size @@ -1543,7 +1529,7 @@ size_t srt::CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki) return ra_size; } -size_t srt::CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, size_t kmdata_wordsize) +size_t 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}; @@ -1563,8 +1549,11 @@ size_t srt::CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, siz 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. + // NOTE: these fields are made atomic so that they can be safely written, + // but formally this should require lock on m_ConnectionLock. + + // Agent has PW, but Peer won't decrypt; Peer won't encrypt as well. + m_CryptoControl.setKMState(SRT_KM_S_NOSECRET, SRT_KM_S_UNSECURED); } else { @@ -1592,7 +1581,7 @@ size_t srt::CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, siz // PREREQUISITE: // pkt must be set the buffer and configured for UMSG_HANDSHAKE. // Note that this function replaces also serialization for the HSv4. -bool srt::CUDT::createSrtHandshake( +bool CUDT::createSrtHandshake( int srths_cmd, int srtkm_cmd, const uint32_t* kmdata, @@ -1608,12 +1597,12 @@ bool srt::CUDT::createSrtHandshake( { w_hs.m_iVersion = HS_VERSION_UDT4; w_hs.m_iType = UDT_DGRAM; - if (w_hs.m_extension) + if (w_hs.m_extensionType != 0) { // Should be impossible LOGC(cnlog.Error, log << CONID() << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing..."); - w_hs.m_extension = false; + w_hs.m_extensionType = 0; } } else @@ -1643,7 +1632,7 @@ bool srt::CUDT::createSrtHandshake( log << CONID() << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. " "BLOCKING extensions."); - w_hs.m_extension = false; + w_hs.m_extensionType = 0; } // The situation when this function is called without requested extensions @@ -1674,7 +1663,7 @@ bool srt::CUDT::createSrtHandshake( // values > URQ_CONCLUSION include also error types // 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 (!w_hs.m_extension) + if (w_hs.m_extensionType == 0) { // Serialize only the basic handshake, if this is predicted for // Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND. @@ -1768,33 +1757,22 @@ bool srt::CUDT::createSrtHandshake( logext << ",KMX"; } -#if ENABLE_BONDING +#if SRT_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: accessing 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"; + // Needs locking only in order to prevent unordered reading of the field. + // If the group is being closed simultaneously, it will then go on as if + // it was without the group, but wrong situation will be also detected later. + SharedLock gsh (uglobal().m_GlobControlLock); + + 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 @@ -1883,7 +1861,7 @@ bool srt::CUDT::createSrtHandshake( << offset << " filter size=" << ra_size << " space left: " << (total_ra_size - offset)); } -#if ENABLE_BONDING +#if SRT_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 connection belongs to a group @@ -1932,13 +1910,16 @@ bool srt::CUDT::createSrtHandshake( 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)) + if (!m_CryptoControl.initialized() && (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 << "."); + << " Socket state: " + << fmt_onoff(m_bConnected) << "connected, " + << fmt_onoff(m_bConnecting) << "connecting, " + << fmt_onoff(m_bBroken) << "broken, " + << fmt_onoff(m_bClosing) << "closing."); return false; } @@ -1948,10 +1929,10 @@ bool srt::CUDT::createSrtHandshake( for (size_t ki = 0; ki < 2; ++ki) { // Skip those that have expired - if (!m_pCryptoControl->getKmMsg_needSend(ki, false)) + if (!m_CryptoControl.getKmMsg_needSend(ki, false)) continue; - m_pCryptoControl->getKmMsg_markSent(ki, false); + m_CryptoControl.getKmMsg_markSent(ki, false); offset += ra_size + 1; ra_size = fillHsExtKMREQ(p + offset - 1, ki); @@ -2095,20 +2076,20 @@ class RttTracer ~RttTracer() { - srt::sync::ScopedLock lck(m_mtx); + sync::ScopedLock lck(m_mtx); m_fout.close(); } - void trace(const srt::sync::steady_clock::time_point& currtime, + void trace(const 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); + sync::ScopedLock lck(m_mtx); create_file(); - m_fout << srt::sync::FormatTimeSys(currtime) << ","; - m_fout << srt::sync::FormatTime(currtime) << ","; + m_fout << sync::FormatTimeSys(currtime) << ","; + m_fout << sync::FormatTime(currtime) << ","; m_fout << event << ","; m_fout << rtt_sample << ","; m_fout << rttvar_sample << ","; @@ -2132,7 +2113,7 @@ class RttTracer if (m_fout.is_open()) return; - std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); + std::string str_tnow = sync::FormatTimeSys(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, '_'); @@ -2146,7 +2127,7 @@ class RttTracer } private: - srt::sync::Mutex m_mtx; + sync::Mutex m_mtx; std::ofstream m_fout; }; @@ -2154,7 +2135,7 @@ RttTracer s_rtt_trace; #endif -bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) +bool CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData; size_t len = ctrlpkt->getLength(); @@ -2184,7 +2165,7 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t srtdata_out[SRTDATA_MAXSIZE]; size_t len_out = 0; - res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, + res = m_CryptoControl.processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, (srtdata_out), (len_out)); if (res == SRT_CMD_KMRSP) { @@ -2206,6 +2187,7 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { HLOGC(cnlog.Debug, log << CONID() << "KMREQ -> requested to send KMRSP length=" << len_out); } + // XXX [TSA] Check if m_ConnectionLock can be applied here sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); } // XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now. @@ -2221,7 +2203,7 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) case SRT_CMD_KMRSP: { // KMRSP doesn't expect any following action - m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion); + m_CryptoControl.processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion); return true; // nothing to do } @@ -2233,12 +2215,13 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) return true; // Send the message that the message handler requested. + // XXX [TSA] Check if m_ConnectionLock can be applied here sendSrtMsg(res); return true; } -int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) +int 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. @@ -2266,8 +2249,9 @@ int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint } LOGC(cnlog.Debug, 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])); + << " vers=0x" << fmt(srtdata[SRT_HS_VERSION], hex) + << " opts=0x" << fmt(srtdata[SRT_HS_FLAGS], hex) + << " delay=" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; @@ -2388,7 +2372,7 @@ int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint // SRT_HS_LATENCY_SND is the value that the peer proposes to be the // value used by agent when receiving data. We take this as a local latency value. - peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); + peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(latencystr); } // Use the maximum latency out of latency from our settings and the latency @@ -2453,7 +2437,7 @@ int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint return SRT_CMD_HSRSP; } -int srt::CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv) +int 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.3.0 @@ -2501,8 +2485,8 @@ int srt::CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; HLOGC(cnlog.Debug, log << "HSRSP/rcv: Version: " << SrtVersionString(m_uPeerSrtVersion) - << " Flags: SND:" << setw(8) << setfill('0') << hex << m_uPeerSrtFlags - << setw(0) << " (" << SrtFlagString(m_uPeerSrtFlags) << ")"); + << " Flags: SND:" << fmt(m_uPeerSrtFlags, fmtc().hex().fillzero().width(8)) + << " (" << SrtFlagString(m_uPeerSrtFlags) << ")"); // Basic version check if (m_uPeerSrtVersion < m_config.uMinimumPeerSrtVersion) { @@ -2601,7 +2585,8 @@ int srt::CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint } // This function is called only when the URQ_CONCLUSION handshake has been received from the peer. -bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, +// The 'ls' parameter is marked unused to avoid warning when SRT_ENABLE_BONDING == 0. +bool CUDT::interpretSrtHandshake(CUDTSocket* lsn SRT_ATR_UNUSED, const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data SRT_ATR_UNUSED, size_t* pw_len) @@ -2612,6 +2597,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, // 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. + // [TSA] m_ConnRes can be only modified in THIS thread. if (m_ConnRes.m_iVersion > HS_VERSION_UDT4 && hs.m_iVersion == 0) { m_RejectReason = SRT_REJ_PEER; @@ -2767,7 +2753,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting KMREQ/RSP type extension"); #ifdef SRT_ENABLE_ENCRYPTION - if (!m_pCryptoControl->hasPassphrase()) + if (!m_CryptoControl.hasPassphrase()) { if (m_config.bEnforcedEnc) { @@ -2810,7 +2796,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return false; } - int res = m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, m_uPeerSrtVersion, + int res = m_CryptoControl.processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, m_uPeerSrtVersion, (out_data), (*pw_len)); if (res != SRT_CMD_KMRSP) { @@ -2823,8 +2809,8 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } if (*pw_len == 1) { -#ifdef ENABLE_AEAD_API_PREVIEW - if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADCRYPTOMODE) +#ifdef SRT_ENABLE_AEAD + if (m_CryptoControl.kmState().rcv == SRT_KM_S_BADCRYPTOMODE) { // Cryptographic modes mismatch. Not acceptable at all. m_RejectReason = SRT_REJ_CRYPTO; @@ -2839,7 +2825,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, // This is inacceptable in case of strict encryption. if (m_config.bEnforcedEnc) { - if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADSECRET) + if (m_CryptoControl.kmState().rcv == SRT_KM_S_BADSECRET) { m_RejectReason = SRT_REJ_BADSECRET; } @@ -2857,13 +2843,13 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } else if (cmd == SRT_CMD_KMRSP) { - int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion); + int res = m_CryptoControl.processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion); if (m_config.bEnforcedEnc && res == -1) { - if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADSECRET) + if (m_CryptoControl.kmState().snd == SRT_KM_S_BADSECRET) m_RejectReason = SRT_REJ_BADSECRET; -#ifdef ENABLE_AEAD_API_PREVIEW - else if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADCRYPTOMODE) +#ifdef SRT_ENABLE_AEAD + else if (m_CryptoControl.kmState().snd == SRT_KM_S_BADCRYPTOMODE) m_RejectReason = SRT_REJ_CRYPTO; #endif else @@ -3064,7 +3050,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return false; } } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING else if ( cmd == SRT_CMD_GROUP ) { // Note that this will fire in both cases: @@ -3084,7 +3070,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, size_t groupdata_size = used_bytelen / GRPD_FIELD_SIZE; memcpy(groupdata, begin+1, used_bytelen); - if (!interpretGroup(groupdata, groupdata_size, hsreq_type_cmd) ) + if (!interpretGroup(lsn, groupdata, groupdata_size, hsreq_type_cmd) ) { // m_RejectReason handled inside interpretGroup(). return false; @@ -3133,9 +3119,9 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, // 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. + m_CryptoControl.createFakeSndContext(); + // Because Peer did not send KMX, though Agent has pw ; Peer has no PW, as has sent no KMREQ. + m_CryptoControl.setKMState(SRT_KM_S_NOSECRET, SRT_KM_S_UNSECURED); return true; } @@ -3177,10 +3163,15 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, LOGC(cnlog.Warn, log << CONID() << "HS EXT: Agent has configured packetfilter, but peer didn't request it"); } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING + bool group_already = false; + { + SharedLock sgl (uglobal().m_GlobControlLock); + group_already = m_parent->m_GroupOf; + } // 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) + if (m_SrtHsSide == HSD_INITIATOR && group_already) { // XXX Later probably needs to check if this group REQUIRES the group // response. Currently this implements the bonding-category group, and this @@ -3201,7 +3192,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return true; } -bool srt::CUDT::checkApplyFilterConfig(const std::string &confstr) +bool CUDT::checkApplyFilterConfig(const std::string &confstr) { SrtFilterConfig cfg; if (!ParseFilterConfig(confstr, (cfg))) @@ -3272,7 +3263,7 @@ bool srt::CUDT::checkApplyFilterConfig(const std::string &confstr) // XXX Using less maximum payload size of IPv4 and IPv6; this is only about the payload size // for live. - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size; + size_t efc_max_payload_size = SRT_MAX_PLSIZE_AF_INET6 - cfg.extra_size; if (m_config.zExpPayloadSize > efc_max_payload_size) { LOGC(cnlog.Warn, @@ -3284,8 +3275,8 @@ bool srt::CUDT::checkApplyFilterConfig(const std::string &confstr) 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) +#if SRT_ENABLE_BONDING +bool CUDT::interpretGroup(CUDTSocket* lsn, 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, @@ -3293,7 +3284,7 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A // for consistency and possibly changes in future. // We are granted these two fields do exist - SRTSOCKET grpid = groupdata[GRPD_GROUPID]; + SRTSOCKET grpid = SRTSOCKET(groupdata[GRPD_GROUPID]); uint32_t gd = groupdata[GRPD_GROUPDATA]; SRT_GROUP_TYPE gtp = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); @@ -3316,7 +3307,7 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A return false; } - if ((grpid & SRTGROUP_MASK) == 0) + if (!isgroup(grpid)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS/GROUP: socket ID passed as a group ID is not a group ID"); @@ -3328,12 +3319,12 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A // 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"}; +#if HVU_ENABLE_HEAVY_LOGGING HLOGC(cnlog.Debug, - log << CONID() << "interpretGroup: STATE: HsSide=" << hs_side_name[m_SrtHsSide] + log << CONID() << "interpretGroup: STATE: HsSide=" << s_hs_side[m_SrtHsSide] << " HS MSG: " << MessageTypeStr(UMSG_EXT, hsreq_type_cmd) << " $" << grpid << " type=" << gtp - << " weight=" << link_weight << " flags=0x" << std::hex << link_flags); + << " weight=" << link_weight + << " flags=0x" << fmt(link_flags, hex)); #endif // XXX Here are two separate possibilities: @@ -3352,6 +3343,13 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A return false; } + if (m_bTsbPd) + { + HLOGC(cnlog.Debug, log << "interpretGroup: socket TSBPD=on, switching to GROUP TSBPD"); + m_bGroupTsbPd = true; + m_bTsbPd = false; + } + if (m_SrtHsSide == HSD_INITIATOR) { // Here we'll be only using a pre-existing group, so shared is enough. @@ -3408,10 +3406,10 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A log << CONID() << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() << ", copying characteristic data"); - // The call to syncWithSocket is copying + // The call to syncWithFirstSocket 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); + pg->syncWithFirstSocket(*this, HSD_INITIATOR); } // Otherwise the peer id must be the same as existing, otherwise // this group is considered already bound to another peer group. @@ -3452,9 +3450,10 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A // mirror group and join it. Later on, the HS response will be sent // and its group ID will be added to the HS extensions as mirror group // ID to the peer. + SRTSOCKET listener = lsn ? lsn->id(): SRT_INVALID_SOCK; - SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags); - if (lgid == 0) + SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags, listener); + if (lgid == SRT_SOCKID_CONNREQ) return true; // already done if (lgid == SRT_INVALID_SOCK) @@ -3487,12 +3486,16 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A } #endif -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING // NOTE: This function is called only in one place and it's done // exclusively on the listener side (HSD_RESPONDER, HSv5+). // [[using locked(s_UDTUnited.m_GlobControlLock)]] -SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags) +// XXX +// SRT_TSA_NEEDS_LOCKED(CUDTUnited::m_GlobControlLock) +// Can't be set because clang-tsa doesn't honor friend declarations +// Alternatively you can move it to CUDTSocket class. +SRTSOCKET CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags, SRTSOCKET listener) { // Note: This function will lock pg->m_GroupLock! @@ -3504,7 +3507,7 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 // 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); + CUDTGroup* gp = uglobal().findPeerGroup_LOCKED(peergroup); // [TSA] see initial note bool was_empty = true; if (gp) { @@ -3513,11 +3516,13 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 LOGC(gmlog.Error, log << CONID() << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp << " agent group=$" << gp->id() << " type" << gp->type()); - return -1; + return SRT_INVALID_SOCK; } HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id()); + gp->updatePending(listener); + if (!gp->groupEmpty()) was_empty = false; } @@ -3525,19 +3530,19 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 { try { - gp = &newGroup(gtp); + gp = &uglobal().newGroup(gtp); } catch (...) { // Expected exceptions are only those referring to system resources - return -1; + return SRT_INVALID_SOCK; } if (!gp->applyFlags(link_flags, m_SrtHsSide)) { // Wrong settings. Must reject. Delete group. uglobal().deleteGroup_LOCKED(gp); - return -1; + return SRT_INVALID_SOCK; } gp->set_peerid(peergroup); @@ -3546,7 +3551,8 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 // 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(); + // It's also set pending and it stays this way until accepted. + gp->setOpenPending(listener); HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: no group has peer=$" << peergroup << " - creating new mirror group $" @@ -3562,7 +3568,7 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 if (was_empty) { - gp->syncWithSocket(s->core(), HSD_RESPONDER); + gp->syncWithFirstSocket(s->core(), HSD_RESPONDER); } } @@ -3581,10 +3587,10 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 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; + return SRT_SOCKID_CONNREQ; } - s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); + s->m_GroupMemberData = gp->add(groups::prepareSocketData(s, gp->type())); s->m_GroupOf = gp; m_HSGroupType = gtp; @@ -3593,9 +3599,11 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 return gp->id(); } -void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) +//[[using GroupKeeper(gp)]] +void CUDT::synchronizeWithGroup(CUDTGroup* gp) { - ScopedLock gl (*gp->exp_groupLock()); + if (gp->isClosing()) + return; // We have blocked here the process of connecting a new // socket and adding anything new to the group, so no such @@ -3605,7 +3613,13 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) start_time = m_stats.tsStartTime; peer_start_time = m_tsRcvPeerStartTime; - if (!gp->applyGroupTime((start_time), (peer_start_time))) + bool first_time = false; + { + ScopedLock gl (*gp->exp_groupLock()); + first_time = gp->applyGroupTime((start_time), (peer_start_time)); + } + + if (!first_time) { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: ST=" << FormatTime(m_stats.tsStartTime) << " -> " @@ -3622,54 +3636,23 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) << " PST=" << FormatTime(m_tsRcvPeerStartTime)); } - 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))) - { - // 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: + // These are the values that are normally set initially by setters. + int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck; + // [TSA] NOTE: Here is theoretically also the buffer lock required, but this + // function is called from acceptAndRespond when no modifications in the + // buffer or reading from any thread are for the time being possible. - // - 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 + first_time = false; { - HLOGC(gmlog.Debug, - log << CONID() << "AFTER HS: (GROUP, but " - << (m_bTsbPd ? "FIRST SOCKET is initialized normally)" : "no TSBPD set)")); - updateSrtRcvSettings(); + ScopedLock gl (*gp->exp_groupLock()); + first_time = gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn)); } - // 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))) + if (!first_time) { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn - << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) << ") SND=%" << m_iSndLastAck + << " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck) << ") SND=%" << m_iSndLastAck.load() << " -> %" << snd_isn << " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")"); setInitialRcvSeq(rcv_isn); setInitialSndSeq(snd_isn); @@ -3678,15 +3661,27 @@ void srt::CUDT::synchronizeWithGroup(CUDTGroup* gp) { HLOGC(gmlog.Debug, log << CONID() << "synchronizeWithGroup: DEFINED ISN: RCV=%" << m_iRcvLastAck << " SND=%" - << m_iSndLastAck); + << m_iSndLastAck.load()); } } #endif -void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) +void CUDT::registerConnector(const sockaddr_any& addr, const steady_clock::time_point& ttl) { - ScopedLock cg (m_ConnectionLock); + HLOGC(cnlog.Debug, + log << "registerConnector: adding @" << id() << " addr=" << addr.str() << " TTL=" << FormatTime(ttl)); + + CMultiplexer::CRL r; + r.m_iID = id(); + r.m_pUDT = this; + r.m_PeerAddr = addr; + r.m_tsTTL = ttl; + m_pMuxer->registerCRL(r); +} + +void CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) +{ HLOGC(aclog.Debug, log << CONID() << "startConnect: -> " << serv_addr.str() << (m_config.bSynRecving ? " (SYNCHRONOUS)" : " (ASYNCHRONOUS)") << "..."); @@ -3699,9 +3694,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) if (m_bConnecting || m_bConnected) throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - // record peer/server address 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 @@ -3711,7 +3704,10 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) ttl *= 10; const steady_clock::time_point ttl_time = steady_clock::now() + ttl; - m_pRcvQueue->registerConnector(m_SocketID, this, serv_addr, ttl_time); + + registerConnector(serv_addr, ttl_time); + + UniqueLock cg (m_ConnectionLock); // 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 @@ -3772,7 +3768,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // 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)); + CIPAddress::encode(serv_addr, (m_ConnReq.m_piPeerIP)); if (forced_isn == SRT_SEQNO_NONE) { @@ -3803,7 +3799,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // control of methods.) // ID = 0, connection request - reqpkt.set_id(0); + reqpkt.set_id(SRT_SOCKID_CONNREQ); size_t hs_size = m_iMaxSRTPayloadSize; m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); @@ -3832,14 +3828,14 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // 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); + channel()->sendto(serv_addr, reqpkt, m_SourceAddr); // /// //// ---> CONTINUE TO: .CUDT::processConnectRequest() /// (Take the part under condition: hs.m_iReqType == URQ_INDUCTION) - //// <--- RETURN WHEN: m_pSndQueue->sendto() is called. - //// .... SKIP UNTIL m_pRcvQueue->recvfrom() HERE.... + //// <--- RETURN WHEN: channel()->sendto() is called. + //// .... SKIP UNTIL m_RcvQueue.recvfrom() HERE.... //// (the first "sendto" will not be called due to being too early) /// // @@ -3849,257 +3845,81 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) ////////////////////////////////////////////////////// if (!m_config.bSynRecving) { - HLOGC(cnlog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker"); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Exiting srt_connect() now."); 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 - // next incoming packet. - CPacket response; - response.setControl(UMSG_HANDSHAKE); - response.allocate(m_iMaxSRTPayloadSize); + // That's it; now we need to wait until the Receiver Worker thread reports readiness. + cg.unlock(); - 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()); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE DETECTED. Entering wait until RcvQ:worker finishes"); + // SYNCHRONOUS VERSION: wait until the background process reports connection. + CUniqueSync sendblock_cc (m_SendBlockLock, m_SendBlockCond); - while (!m_bClosing && !m_bBreaking) + // Check every 1 second until either connected or broken. + sync::steady_clock::time_point waiting_since = sync::steady_clock::now(); + for (;;) { - 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 + SRT_ATR_UNUSED bool signaled = sendblock_cc.wait_for(seconds_from(1)); + // We don't care by what reasons it was interrupted. + // Act according to the flags. + HLOGC(cnlog.Debug, log << CONID() << "startConnect: sync wait interrupted on " << + (signaled ? "SIGNAL" : "TIMEOUT")); - // 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_tsLastReqTime to 0, - // which will result of this condition to trigger immediately in - // the next iteration. - if (count_milliseconds(tdiff) > 250) + try { - HLOGC(cnlog.Debug, - log << CONID() << "startConnect: LOOP: time to send (" << count_milliseconds(tdiff) - << " > 250 ms). size=" << reqpkt.getLength()); - - if (m_config.bRendezvous) - reqpkt.set_id(m_ConnRes.m_iID); - -#if ENABLE_HEAVY_LOGGING + if (m_RejectReason > SRT_REJ_UNKNOWN) { - CHandShake debughs; - debughs.load_from(reqpkt.m_pcData, reqpkt.getLength()); - HLOGC(cnlog.Debug, - log << CONID() << "startConnect: REQ-TIME HIGH." - << " cont/sending HS to peer: " << debughs.show()); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. Rejection detected - exiting"); + // Rejection code is also internally used to designate timeout. + // This is only needed to report correct error code. + if (m_RejectReason == SRT_REJ_TIMEOUT) + throw CUDTException(MJ_SETUP, MN_TIMEOUT); + throw CUDTException(MJ_SETUP, MN_REJECTED); } -#endif - - m_tsLastReqTime = local_tnow; - setPacketTS(reqpkt, local_tnow); - m_pSndQueue->sendto(serv_addr, reqpkt, use_source_adr); - } - else - { - 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, (response)) > 0) - { - use_source_adr = response.udpDestAddr(); - - 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 - // should check that and craft the URQ_CONCLUSION handshake, in which - // case this function returns CONN_CONTINUE. As an extra action taken - // for that case, we set the SECURING mode if encryption requested, - // and serialize again the handshake, possibly together with HS extension - // blocks, if HSv5 peer responded. The serialized handshake will be then - // sent again, as the loop is repeated. - // - the peer responded with URQ_CONCLUSION. This handshake was accepted - // as a connection, and for >= HSv5 the HS extension blocks have been - // also read and interpreted. In this case this function returns: - // - CONN_ACCEPT, if everything was correct - break this loop and return normally - // - CONN_REJECT in case of any problems with the delivered handshake - // (incorrect data or data conflict) - throw error exception - // - the peer responded with any of URQ_ERROR_*. - throw error exception - // - // The error exception should make the API connect() function fail, if blocking - // or mark the failure for that socket in epoll, if non-blocking. - - if (cst == CONN_RENDEZVOUS) - { - // When this function returned CONN_RENDEZVOUS, this requires - // very special processing for the Rendezvous-v5 algorithm. This MAY - // involve also preparing a new handshake form, also interpreting the - // SRT handshake extension and crafting SRT handshake extension for the - // peer, which should be next sent. When this function returns CONN_CONTINUE, - // 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(&response, serv_addr, RST_OK, (reqpkt)); - if (cst == CONN_CONTINUE) - continue; - - 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 (m_bBroken) + { + HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. BROKEN detected - exiting"); + throw CUDTException(MJ_CONNECTION, MN_CONNLOST); } - if (cst == CONN_REJECT) + if (m_bClosing || m_bBreaking) { - HLOGC(cnlog.Debug, - log << CONID() << "startConnect: REJECTED by processConnectResponse - sending SHUTDOWN"); - sendCtrl(UMSG_SHUTDOWN); + HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. CLOSED detected - exiting"); + throw CUDTException(MJ_SETUP, MN_CLOSED); } - if (cst != CONN_CONTINUE && cst != CONN_CONFUSED) - break; // --> OUTSIDE-LOOP - - // IMPORTANT - // [[using assert(m_pCryptoControl != nullptr)]]; - - // 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. - - // First, set the size back to the original size, m_iMaxSRTPayloadSize because - // this is the size of the originally allocated space. It might have been - // shrunk by serializing the INDUCTION handshake (which was required before - // sending this packet to the output queue) and therefore be too - // small to store the CONCLUSION handshake (with HSv5 extensions). - reqpkt.setLength(m_iMaxSRTPayloadSize); - - 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 - // turned into conclusion, but was sending still the original - // induction handshake challenge message. It was working only - // thanks to that simultaneously there were being sent handshake - // messages from a separate thread (CSndQueue::worker) from - // RendezvousQueue, this time serialized properly, which caused - // that with blocking mode there was a kinda initial "drunk - // passenger with taxi driver talk" until the RendezvousQueue sends - // (when "the time comes") the right CONCLUSION handshake - // challenge message. - // - // 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(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (reqpkt), (m_ConnReq))) + if (m_bConnected) { - LOGC(cnlog.Warn, log << CONID() << "createSrtHandshake failed - REJECTING."); - cst = CONN_REJECT; + HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. CONNECTED detected - exit with success"); break; } - // These last 2 parameters designate the buffer, which is in use only for SRT_CMD_KMRSP. - // If m_ConnReq.m_iVersion == HS_VERSION_UDT4, this function will do nothing, - // except just serializing the UDT handshake. - // The trick is that the HS challenge is with version HS_VERSION_UDT4, but the - // listener should respond with HS_VERSION_SRT1, if it is HSv5 capable. - } - 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(cnlog.Error, - log << CONID() - << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!"); - break; - } -#endif - - 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; + // Wait only up until connection timeout + if (sync::steady_clock::now() - waiting_since > m_config.tdConnTimeOut) + { + HLOGC(cnlog.Debug, log << CONID() << "startConnect: SYNC MODE. TIMEOUT detected - exiting"); + throw CUDTException(MJ_SETUP, MN_TIMEOUT); + } } - } - - // <--- OUTSIDE-LOOP - // Here will fall the break when not CONN_CONTINUE. - // CONN_RENDEZVOUS is handled by processRendezvous. - // CONN_ACCEPT will skip this and pass on. - if (cst == CONN_REJECT) - { - e = CUDTException(MJ_SETUP, MN_REJECTED, 0); - } - - if (e.getErrorCode() == 0) - { - if (m_bClosing || m_bBreaking) // if the socket is closed before connection... - e = CUDTException(MJ_SETUP, MN_CLOSED, 0); - else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected + catch (...) { - m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); - e = CUDTException(MJ_SETUP, MN_REJECTED, 0); + m_bConnecting = false; + m_pMuxer->removeConnector(m_SocketID); + throw; } - else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // security check - e = CUDTException(MJ_SETUP, MN_SECURITY, 0); - } - - if (e.getErrorCode() != 0) - { - m_bConnecting = false; - // The process is to be abnormally terminated, remove the connector - // now because most likely no other processing part has done anything with it. - m_pRcvQueue->removeConnector(m_SocketID); - throw e; } - HLOGC(cnlog.Debug, - log << CONID() << "startConnect: handshake exchange succeeded. sourceIP=" << m_SourceAddr.str()); - // Parameters at the end. HLOGC(cnlog.Debug, - log << CONID() << "startConnect: END. Parameters: mss=" << m_config.iMSS + log << CONID() << "startConnect: success. Parameters: sourceIP=" << m_SourceAddr.str() << " mss=" << m_config.iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize() << " cwnd-size=" << m_CongCtl->cgWindowSize() - << " rtt=" << m_iSRTT << " bw=" << m_iBandwidth); + << " rtt=" << m_iSRTT.load() << " bw=" << m_iBandwidth.load()); } // Asynchronous connection -EConnectStatus srt::CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT +EConnectStatus CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT { EConnectStatus cst = CONN_CONTINUE; CUDTException e; @@ -4116,7 +3936,7 @@ EConnectStatus srt::CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NO return cst; } -bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, +bool CUDT::processAsyncConnectRequest(EReadStatus rst, EConnectStatus cst, const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr) @@ -4134,11 +3954,11 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, const steady_clock::time_point now = steady_clock::now(); setPacketTS(reqpkt, now); - HLOGC(cnlog.Debug, - log << CONID() << "processAsyncConnectRequest: REQ-TIME: HIGH. Should prevent too quick responses."); m_tsLastReqTime = now; // ID = 0, connection request - reqpkt.set_id(!m_config.bRendezvous ? 0 : m_ConnRes.m_iID); + reqpkt.set_id(!m_config.bRendezvous ? SRT_SOCKID_CONNREQ : m_ConnRes.m_iID); + HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: REQ-TIME: HIGH. Address to @" + << reqpkt.id() << " peer=" << m_ConnRes.m_iID); bool status = true; @@ -4155,7 +3975,8 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. " - "Done."); + "Done."); + notifyBlockingConnect(); return true; } @@ -4172,9 +3993,11 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, sendRendezvousRejection(serv_addr, (reqpkt)); status = false; } + notifyBlockingConnect(); } else if (cst == CONN_REJECT) { + notifyBlockingConnect(); // m_RejectReason already set at worker_ProcessAddressedPacket. LOGC(cnlog.Warn, log << CONID() << "processAsyncConnectRequest: REJECT reported from HS processing: " @@ -4205,6 +4028,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, if (!status) { + notifyBlockingConnect(); return false; /* XXX Shouldn't it send a single response packet for the rejection? // Set the version to 0 as "handshake rejection" status and serialize it @@ -4218,14 +4042,14 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, 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, reqpkt, m_SourceAddr); + channel()->sendto(serv_addr, reqpkt, m_SourceAddr); return status; } -void srt::CUDT::sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& r_rsppkt) +void 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); + m_ConnReq.m_iReqType = URQFailure(m_RejectReason); // [TSA] update thread affinity // Assumed that r_rsppkt refers to a packet object that was already prepared // to be used for storing the handshake there. @@ -4234,43 +4058,52 @@ void srt::CUDT::sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& 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) << ")"); + << " for reject reason code " << m_RejectReason.load() << " (" << srt_rejectreason_str(m_RejectReason) << ")"); setPacketTS(r_rsppkt, steady_clock::now()); - m_pSndQueue->sendto(serv_addr, r_rsppkt, m_SourceAddr); + channel()->sendto(serv_addr, r_rsppkt, m_SourceAddr); } -enum HandshakeSide srt::CUDT::compareCookies(int32_t req, int32_t res) +HandshakeSide CUDT::compareCookies(int32_t req, int32_t res) { + IF_HEAVY_LOGGING(fmtc hex08 = fmtc().uhex().fillzero().width(8)); + // XXX ROUND VERSION on 32-bit if (req < res) { - LOGC(gglog.Note, log << hex << setfill('0') << uppercase - << "REQ: " << setw(8) << req - << " RES: " << setw(8) << res << " - result: RESPONDER"); + HLOGC(cnlog.Debug, log << "compareCookies: " + << "REQ: " << fmt(req, hex08) + << " RES: " << fmt(res, hex08) + << " - result: RESPONDER"); return HSD_RESPONDER; } else if (req > res) { - LOGC(gglog.Note, log << hex << setfill('0') << uppercase - << "REQ: " << setw(8) << req - << " RES: " << setw(8) << res << " - result: INITIATOR"); + HLOGC(cnlog.Debug, log << "compareCookies: " + << "REQ: " << fmt(req, hex08) + << " RES: " << fmt(res, hex08) + << " - result: INITIATOR"); return HSD_INITIATOR; } - return HSD_DRAW; + else + { + return HSD_DRAW; + } } -enum HandshakeSide srt::CUDT::backwardCompatibleCookieContest(int32_t req, int32_t res) +// NOTE: This function remains here for historical reasons only. This is how this +// was last done in the version 1.5.5. For reference only. +HandshakeSide CUDT::backwardCompatibleCookieContest(int32_t req, int32_t res) { const int64_t xreq = int64_t(req); const int64_t xres = int64_t(res); const int64_t contest = xreq - xres; - LOGC(cnlog.Debug, log << "cookieContest: agent=" << req + IF_HEAVY_LOGGING(fmtc hex64 = fmtc().uhex().fillzero().width(16)); + HLOGC(cnlog.Debug, log << "cookieContest: agent=" << req << " peer=" << res - << hex << uppercase << setfill('0') // PERSISTENT flags - << " X64: "<< setw(16) << xreq - << " vs. " << setw(16) << xres - << " DIFF: " << setw(16) << contest); + << " X64: " << fmt(xreq, hex64) + << " vs. " << fmt(xres, hex64) + << " DIFF: " << fmt(contest, hex64)); if ((contest & 0xFFFFFFFF) == 0) { @@ -4287,7 +4120,7 @@ enum HandshakeSide srt::CUDT::backwardCompatibleCookieContest(int32_t req, int32 return HSD_INITIATOR; } -void srt::CUDT::cookieContest() +void CUDT::cookieContest() { if (m_SrtHsSide != HSD_DRAW) return; @@ -4296,13 +4129,12 @@ void srt::CUDT::cookieContest() // 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) { - LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie + HLOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie << " - ERROR: zero not allowed!"); return; } - m_SrtHsSide = backwardCompatibleCookieContest(m_ConnReq.m_iCookie, m_ConnRes.m_iCookie); - + m_SrtHsSide = compareCookies(m_ConnReq.m_iCookie, m_ConnRes.m_iCookie); } // This function should complete the data for KMX needed for an out-of-band @@ -4310,7 +4142,7 @@ void srt::CUDT::cookieContest() // - 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) +EConnectStatus 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. @@ -4318,21 +4150,25 @@ EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatas 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) + if (!m_CryptoControl.initialized()) { 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 << "."); + << " Socket state: " + << fmt_onoff(m_bConnected) << "connected, " + << fmt_onoff(m_bConnecting) << "connecting, " + << fmt_onoff(m_bBroken) << "broken, " + << fmt_onoff(m_bOpened) << "opened, " + << fmt_onoff(m_bClosing) << "closing."); 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); + size_t msgsize = m_CryptoControl.getKmMsg_size(0); if (msgsize == 0) { - switch (m_pCryptoControl->m_RcvKmState) + switch (m_CryptoControl.kmState().rcv) { // 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. @@ -4341,11 +4177,12 @@ EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatas { HLOGC(cnlog.Debug, log << CONID() << "craftKmResponse: No KMX recorded, status = " - << KmStateStr(m_pCryptoControl->m_RcvKmState) << ". Respond it."); + << KmStateStr(m_CryptoControl.kmState().rcv) << ". 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)); + SRT_KM_STATE rstate = m_CryptoControl.kmState().rcv; + memcpy((aw_kmdata), &rstate, sizeof(int32_t)); w_kmdatasize = 1; } break; // Treat as ACCEPT in general; might change to REJECT on enforced-encryption @@ -4361,8 +4198,8 @@ EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatas // - 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)); + << KmStateStr(m_CryptoControl.kmState().rcv) + << " SND=" << KmStateStr(m_CryptoControl.kmState().snd)); return CONN_REJECT; } break; @@ -4382,7 +4219,7 @@ EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatas 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); + memcpy((aw_kmdata), m_CryptoControl.getKmMsg_data(0), msgsize); } } else @@ -4394,7 +4231,15 @@ EConnectStatus srt::CUDT::craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatas return CONN_ACCEPT; } -EConnectStatus srt::CUDT::processRendezvous( +#if HVU_ENABLE_HEAVY_LOGGING +// NOTE VALUES: +// none = 0 +// SRT_CMD_HSREQ = 1 +// SRT_CMD_HSRSP = 2 +static std::string s_hs_ext_side[3] = {"no", "HSREQ", "HSRSP"}; +#endif + +EConnectStatus CUDT::processRendezvous( const CPacket* pResponse /*[[nullable]]*/, const sockaddr_any& serv_addr, EReadStatus rst, CPacket& w_reqpkt) { @@ -4427,9 +4272,79 @@ EConnectStatus srt::CUDT::processRendezvous( // for further processing here. 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((rsp_type), (needs_extension), (needs_hsrsp)); + + if (m_ConnRes.m_iReqType == URQ_CONCLUSION) + { + // ext_flags are interpreted here as extension markers. + // Expected is that: + // if m_SrtHsSide == HSD_RESPONDER, expected is ext_flags != 0 (WITH extensions AND of HSREQ type). + // if m_SrtHsSide == HSD_INITIATOR, expected is: + // - ext_flags == 0, if this is the very first response (at least it hasn´t received any HSREQ yet) + // - ext_flags != 0, HSREQ flag is set, and extensions contain HSRSP. + + HandshakeSide expected_side = m_SrtHsSide; + + HLOGC(cnlog.Debug, log << "processRendezvous: {" << s_hs_side[m_SrtHsSide] << "}[" << CHandShake::RdvStateStr(m_RdvState) << "] " + << " receives CONCLUSION/" << SrtCmdName(m_ConnRes.m_extensionType) << " flags:" << fmt(ext_flags, fmtc().uhex().fillzero().width(4)) + << " expects " << (expected_side == HSD_INITIATOR ? "HSRSP/noext" : "HSREQ")); + + // XXX While checking for flags, you might also check for the SRT Version. + // This however requires breaking up the process of handshake parsing so that + // all is parsed completely into temporary containers and only then interpreted so that + // details, including those available in the extensions, are available at any time of processing. + + // m_SrtHsSide is now either HSD_INITIATOR or HSD_RESPONDER; all others were already handled above. + if (m_SrtHsSide == HSD_INITIATOR) + { + if (ext_flags != 0) + { + // We have received hs/conclusion/ext from a side that should have + // designated itself as RESPONDER. Check if this hs HSRSP. + if (m_ConnRes.m_extensionType == SRT_CMD_HSREQ) + { + HLOGC(cnlog.Debug, log << "processRendezvous: HS SIDE:INITIATOR, received HS with extension:HSREQ - COLLISION!"); + // !!! COLLISION !!! + expected_side = HSD_RESPONDER; + } + } + } + else + { + // HSD_RESPONDER - expected is that ext_flags is set and type is HSREQ + if (m_ConnRes.m_extensionType != SRT_CMD_HSREQ) + { + HLOGC(cnlog.Debug, log << "processRendezvous: HS SIDE:RESPONDER, received HS with extension:" << SrtCmdName(m_ConnRes.m_extensionType) << " - COLLISION!"); + // !!! COLLISION !!! + expected_side = HSD_INITIATOR; + } + } + + // In all other cases we have an error and a "side collision" case is in order. + // We deem that: + // - if both sides are >= 1.6.0, this isn't possible, unless it's a DRAW, which was handled already. + // - if this WAS the case, we state this is an old version with a bug on the cookie resolution; in that + // case, you should simply ADAPT YOURSELF to the resolution of the other side, as this doesn't matter, + // the other side will not understand anyway, while this will at least allow connection and proper + // side resolution. + + if (expected_side != m_SrtHsSide) + { + LOGC(cnlog.Error, log << CONID() << "COOKIE COLLISION: {" << s_hs_side[m_SrtHsSide] << "} gets " << SrtCmdName(m_ConnRes.m_extensionType) + << " - ADAPTING to the peer, switching to {" << s_hs_side[expected_side] << "}"); + m_SrtHsSide = expected_side; + } + } + + int tosend_ext_type = 0; + if (ext_flags) + { + if (m_SrtHsSide == HSD_INITIATOR) + tosend_ext_type = SRT_CMD_HSREQ; + else + tosend_ext_type = SRT_CMD_HSRSP; + } + + rendezvousSwitchState((rsp_type), (tosend_ext_type)); if (rsp_type > URQ_FAILURE_TYPES) { m_RejectReason = RejectReasonForURQ(rsp_type); @@ -4446,27 +4361,27 @@ EConnectStatus srt::CUDT::processRendezvous( // 2. The agent is loser in initiated state, it interprets incoming HSREQ and creates HSRSP // 3. The agent is winner in attention or fine state, it sends HSREQ extension m_ConnReq.m_iReqType = rsp_type; - m_ConnReq.m_extension = needs_extension; + m_ConnReq.m_extensionType = tosend_ext_type; - // This must be done before prepareConnectionObjects(), because it sets ISN and m_iMaxSRTPayloadSize needed to create buffers. + // This must be done before prepareBuffers(), because it sets ISN and m_iMaxSRTPayloadSize needed to create buffers. if (!applyResponseSettings(pResponse)) { LOGC(cnlog.Error, log << CONID() << "processRendezvous: peer settings rejected"); return CONN_REJECT; } - // 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)) + // The CryptoControl must be created before interpreting and creating HSv5 + // extensions because it will be used there. + if (!createCrypter(m_SrtHsSide)) { // m_RejectReason already handled HLOGC(cnlog.Debug, - log << CONID() << "processRendezvous: rejecting due to problems in prepareConnectionObjects."); + log << CONID() << "processRendezvous: rejecting due to problems in createCrypter."); return CONN_REJECT; } // Case 2. - if (needs_hsrsp) + if (tosend_ext_type == SRT_CMD_HSRSP) { // This means that we have received HSREQ extension with the handshake, so we need to interpret // it and craft the response. @@ -4483,13 +4398,20 @@ EConnectStatus srt::CUDT::processRendezvous( return CONN_REJECT; } - if (!interpretSrtHandshake(m_ConnRes, *pResponse, kmdata, &kmdatasize)) + if (!interpretSrtHandshake(NULL, m_ConnRes, *pResponse, kmdata, &kmdatasize)) { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW."); return CONN_REJECT; } + if (!prepareBuffers(NULL)) + { + HLOGC(cnlog.Debug, + log << "processRendezvous: rejecting due to problems in prepareBuffers REQ-TIME: LOW."); + return CONN_REJECT; + } + updateAfterSrtHandshake(HS_VERSION_SRT1); // Pass on, inform about the shortened response-waiting period. @@ -4506,7 +4428,7 @@ EConnectStatus srt::CUDT::processRendezvous( // 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; + m_ConnReq.m_extensionType = tosend_ext_type; HLOGC(cnlog.Debug, log << CONID() @@ -4550,19 +4472,26 @@ EConnectStatus srt::CUDT::processRendezvous( { HLOGC(cnlog.Debug, log << CONID() << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"); - if (!interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0)) + if (!interpretSrtHandshake(NULL, 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; } + + if (!prepareBuffers(NULL)) + { + HLOGC(cnlog.Debug, + log << "processRendezvous: rejecting due to problems in prepareBuffers REQ-TIME: LOW."); + return CONN_REJECT; + } } // This should be false, make a kinda assert here. - if (needs_extension) + if (tosend_ext_type) { LOGC(cnlog.Fatal, log << CONID() << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS"); - m_ConnReq.m_extension = false; + m_ConnReq.m_extensionType = 0; } updateAfterSrtHandshake(HS_VERSION_SRT1); } @@ -4579,15 +4508,15 @@ EConnectStatus srt::CUDT::processRendezvous( else { HLOGC(cnlog.Debug, - log << CONID() << "... WILL SEND " << RequestTypeStr(rsp_type) << " " - << (m_ConnReq.m_extension ? "with" : "without") << " SRT HS extensions"); + log << CONID() << "... WILL SEND " << RequestTypeStr(rsp_type) << " with" + << s_hs_ext_side[m_ConnReq.m_extensionType] << " SRT HS extensions"); } // This marks the information for the serializer that // the SRT handshake extension is required. // Rest of the data will be filled together with // serialization. - m_ConnReq.m_extension = needs_extension; + m_ConnReq.m_extensionType = tosend_ext_type; w_reqpkt.setLength(m_iMaxSRTPayloadSize); if (m_RdvState == CHandShake::RDV_CONNECTED) @@ -4649,7 +4578,7 @@ EConnectStatus srt::CUDT::processRendezvous( log << CONID() << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH."); - m_pSndQueue->sendto(serv_addr, w_reqpkt, m_SourceAddr); + channel()->sendto(serv_addr, w_reqpkt, m_SourceAddr); return CONN_ACCEPT; } @@ -4671,7 +4600,7 @@ EConnectStatus srt::CUDT::processRendezvous( } // [[using locked(m_ConnectionLock)]]; -EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTException* eout) ATR_NOEXCEPT +EConnectStatus CUDT::processConnectResponse(const CPacket& response, CUDTException* eout) ATR_NOEXCEPT { // NOTE: ASSUMED LOCK ON: m_ConnectionLock. @@ -4694,7 +4623,6 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx // 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_config.bDataSender ? HSD_INITIATOR : HSD_RESPONDER; // (defined here due to 'goto' below). @@ -4830,6 +4758,8 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous(). } + // XXX BELOW CODE is for handling HSv4, should return error instead. + 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 @@ -4846,7 +4776,7 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx // For HSv5, make the cookie contest and basing on this decide, which party // should provide the HSREQ/KMREQ attachment. - if (!createCrypter(hsd, false /* unidirectional */)) + if (!createCrypter(hsd)) { m_RejectReason = SRT_REJ_RESOURCE; m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE); @@ -4871,8 +4801,9 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx if (m_ConnRes.m_iReqType == URQ_INDUCTION) { 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 + log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" + << fmt(m_ConnRes.m_iCookie, hex) + << " version:" << m_ConnRes.m_iVersion << "), sending CONCLUSION HS with this cookie"); m_ConnReq.m_iCookie = m_ConnRes.m_iCookie; @@ -4907,18 +4838,17 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx // the SRT handshake extension is required. // Rest of the data will be filled together with // serialization. - m_ConnReq.m_extension = true; + m_ConnReq.m_extensionType = SRT_CMD_HSREQ; // For HSv5, the caller is INITIATOR and the listener is RESPONDER. // The m_config.bDataSender value should be completely ignored and the // connection is always bidirectional. - bidirectional = true; hsd = HSD_INITIATOR; m_SrtHsSide = hsd; } m_tsLastReqTime = steady_clock::time_point(); - if (!createCrypter(hsd, bidirectional)) + if (!createCrypter(hsd)) { m_RejectReason = SRT_REJ_RESOURCE; return CONN_REJECT; @@ -4929,10 +4859,12 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx } } - return postConnect(&response, false, eout); + EConnectStatus cst = postConnect(&response, false, eout); + notifyBlockingConnect(); + return cst; } -bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) ATR_NOEXCEPT +bool CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) ATR_NOEXCEPT { if (!m_ConnRes.valid()) { @@ -4941,15 +4873,34 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT return false; } + m_TransferIPVersion = m_PeerAddr.family(); + if (m_PeerAddr.family() == AF_INET6) + { + // Check if the m_PeerAddr's address is a mapped IPv4. If so, + // define Transfer IP version as 4 because this one will be used. + if (checkMappedIPv4(m_PeerAddr.sin6)) + m_TransferIPVersion = AF_INET; + } + // Re-configure according to the negotiated values. m_config.iMSS = m_ConnRes.m_iMSS; - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; + const size_t full_hdr_size = CPacket::udpHeaderSize(m_TransferIPVersion) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; + if (m_iMaxSRTPayloadSize < int(m_config.zExpPayloadSize)) + { + LOGC(cnlog.Error, log << CONID() << "applyResponseSettings: negotiated MSS=" << m_config.iMSS + << " leaves too little payload space " << m_iMaxSRTPayloadSize << " for configured payload size " + << m_config.zExpPayloadSize); + m_RejectReason = SRT_REJ_CONFIG; + return false; + } HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); - m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; - const int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; + + // NOTE: m_RcvAckLock required, but this is here allowed because not all threads run yet + m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; // [TSA] init part + const int udpsize = m_config.iMSS - CPacket::udpHeaderSize(m_TransferIPVersion); m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; m_iPeerISN = m_ConnRes.m_iISN; @@ -4970,16 +4921,34 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT return true; } -EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, CUDTException *eout) ATR_NOEXCEPT +void CUDT::notifyBlockingConnect() +{ + if (m_config.bSynRecving) + { + CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); + } +} + +EConnectStatus CUDT::postConnect(const CPacket* pResponse, bool rendezvous, CUDTException *eout) ATR_NOEXCEPT { if (m_ConnRes.m_iVersion < HS_VERSION_SRT1) m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly in SRT HS. + // This part fictionally "loses" incoming conclusion HS given number of times. +#if SRT_ENABLE_FAKE_LOSS_HS > 0 + static int fail_count = SRT_ENABLE_FAKE_LOSS_HS; + if (--fail_count) + { + LOGC(cnlog.Note, log << "postConnect: FAKE LOSS HS conclusion message"); + return CONN_CONTINUE; + } +#endif + // 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); + HLOGC(cnlog.Debug, log << CONID() << "postConnect: packet:" << fmt_yesno(pResponse) << " rendezvous:" << fmt_yesno(rendezvous)); // The "local storage depleted" case shouldn't happen here, but // this is a theoretical path that needs prevention. bool ok = pResponse; @@ -4995,30 +4964,19 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, // [[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 - // function. In fact, prepareConnectionObjects() represents the code that was - // done separately in processConnectResponse() and acceptAndRespond(), so this way - // this code is now common. Now acceptAndRespond() does "manually" something similar - // to applyResponseSettings(), just a little bit differently. This SHOULD be made - // common as a part of refactoring job, just needs a bit more time. - // - // Currently just this function must be called always BEFORE prepareConnectionObjects + // NOTE: THIS function must be called before calling createCrypter and prepareBuffers. + // Currently just this function must be called always BEFORE createCrypter and prepareBuffers // everywhere except acceptAndRespond(). 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". - // (&&: 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. ok = ok && pResponse->isControl(); - ok = ok && interpretSrtHandshake(m_ConnRes, *pResponse, 0, 0); + + // Must be called before interpretSrtHandshake() to create the CryptoControl. + ok = ok && createCrypter(m_SrtHsSide); + ok = ok && interpretSrtHandshake(NULL, m_ConnRes, *pResponse, 0, 0); ok = ok && prepareBuffers(eout); if (!ok) @@ -5035,8 +4993,12 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, bool have_group = false; { -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING SharedLock cl (uglobal().m_GlobControlLock); + // Mutex-protection is likely not needed here because + // the group will not be deleted without having the socket + // present, and the socket closure, should it happen in another + // thread, will have to wait for released m_ConnectionLock. CUDTGroup* g = m_parent->m_GroupOf; if (g) { @@ -5094,9 +5056,9 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, // but prevent it from setting it as connected. m_bConnected = true; + HLOGC(cnlog.Debug, log << CONID() << "postConnect: setReceiver"); // register this socket for receiving data packets - m_pRNode->m_bOnList = true; - m_pRcvQueue->setNewEntry(this); + m_pMuxer->setReceiver(this); } // XXX Problem around CONN_CONFUSED! @@ -5111,15 +5073,15 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, // Remove from rendezvous queue (in this particular case it's // actually removing the socket that undergoes asynchronous HS processing). - // Removing at THIS point because since when setNewEntry is called, + // Removing at THIS point because since when setReceiver is called, // the next iteration in the CRcvQueue::worker loop will be dispatching // packets normally, as within-connection, so the "connector" won't // play any role since this time. - // The connector, however, must stay alive until the setNewEntry is called + // The connector, however, must stay alive until the setReceiver is called // 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); + m_pMuxer->removeConnector(m_SocketID); // Ok, no more things to be done as per "clear connecting state" if (!s) @@ -5137,11 +5099,11 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, // 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->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); - CIPAddress::pton((s->m_SelfAddr), s->core().m_piSelfIP, m_PeerAddr); + s->m_SelfAddr = s->core().channel()->getSockAddr(); + CIPAddress::decode(s->core().m_piSelfIP, m_PeerAddr, (s->m_SelfAddr)); //int token = -1; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING { SharedLock cl (uglobal().m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; @@ -5158,7 +5120,7 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, // 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"); + HLOGC(cnlog.Debug, log << "group: Socket @" << m_SocketID << " fresh connected, setting IDLE"); groups::SocketData* gi = m_parent->m_GroupMemberData; gi->sndstate = SRT_GST_IDLE; @@ -5203,7 +5165,7 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, return CONN_ACCEPT; } -void srt::CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield) +void CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield) { int enc_flags = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(typefield); @@ -5250,13 +5212,13 @@ void srt::CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32 } // Rendezvous -void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_extension, bool& w_needs_hsrsp) +void CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, int& w_tosend_ext_type) { UDTRequestType req = m_ConnRes.m_iReqType; int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); bool has_extension = !!hs_flags; // it holds flags, if no flags, there are no extensions. - const HandshakeSide &hsd = m_SrtHsSide; + const HandshakeSide& hsd = m_SrtHsSide; // Note important possibilities that are considered here: // 1. The serial arrangement. This happens when one party has missed the @@ -5289,12 +5251,11 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e // (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. - w_needs_extension = false; - w_needs_hsrsp = false; + w_tosend_ext_type = 0; string reason; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING HLOGC(cnlog.Debug, log << CONID() << "rendezvousSwitchState: HS: " << m_ConnRes.show()); @@ -5302,22 +5263,20 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e { CHandShake::RendezvousState ost; UDTRequestType orq; - const CHandShake::RendezvousState &nst; - const UDTRequestType & nrq; - bool & needext; - bool & needrsp; - string & reason; + const CHandShake::RendezvousState& nst; + const UDTRequestType& nrq; + int& exttype; + string& reason; ~LogAtTheEnd() { 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)); + << RequestTypeStr(nrq) << "] " << "ext: " << s_hs_ext_side[exttype] + << (reason == "" ? string() : " reason:" + reason)); } - } l_logend = {m_RdvState, req, m_RdvState, w_rsptype, w_needs_extension, w_needs_hsrsp, reason}; + } l_logend = {m_RdvState, req, m_RdvState, w_rsptype, w_tosend_ext_type, reason}; #endif @@ -5330,12 +5289,24 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e { if (req == URQ_WAVEAHAND) { + // Exception: send waveahand in response to force the other party + // declare itself. NOTE: + // - in 1.6.0 there is no possibility to resolve both sides with the same HSD + // - such a possibility exists in an earlier version and this way this will be + // detected by forcing it to send their conclusion first. + if (hsd == HSD_RESPONDER) + { + w_rsptype = URQ_WAVEAHAND; + w_tosend_ext_type = 0; + return; + } + m_RdvState = CHandShake::RDV_ATTENTION; // NOTE: if this->isWinner(), attach HSREQ w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) - w_needs_extension = true; + w_tosend_ext_type = SRT_CMD_HSREQ; return; } @@ -5344,16 +5315,15 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e m_RdvState = CHandShake::RDV_FINE; w_rsptype = URQ_CONCLUSION; - w_needs_extension = true; // (see below - this needs to craft either HSREQ or HSRSP) + // (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) - w_needs_hsrsp = true; + w_tosend_ext_type = hsd == HSD_RESPONDER ? SRT_CMD_HSRSP : SRT_CMD_HSREQ; return; } } - reason = "WAVING -> WAVEAHAND or CONCLUSION"; - break; + reason = "WAVING -> WAVEAHAND or CONCLUSION"; + break; case CHandShake::RDV_ATTENTION: { @@ -5366,7 +5336,7 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e // retry with URQ_CONCLUSION, as normally. w_rsptype = URQ_CONCLUSION; if (hsd == HSD_INITIATOR) - w_needs_extension = true; + w_tosend_ext_type = SRT_CMD_HSREQ; return; } @@ -5383,10 +5353,10 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e { HLOGC(cnlog.Debug, log << CONID() - << "rendezvousSwitchState: {INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got " - "CONCLUSION, remain in [ATTENTION]"); + << "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 + w_tosend_ext_type = SRT_CMD_HSREQ; return; } m_RdvState = CHandShake::RDV_CONNECTED; @@ -5403,17 +5373,16 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e { LOGC(cnlog.Warn, log << CONID() - << "rendezvousSwitchState: (IPE!){RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got " - "CONCLUSION, remain in [ATTENTION]"); + << "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) + w_tosend_ext_type = 0; // If you received WITHOUT extensions, respond WITHOUT extensions (wait + // for the right message) return; } m_RdvState = CHandShake::RDV_INITIATED; w_rsptype = URQ_CONCLUSION; - w_needs_extension = true; - w_needs_hsrsp = true; + w_tosend_ext_type = SRT_CMD_HSRSP; return; } @@ -5450,8 +5419,7 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e // inform the other party that we need the conclusion message once again. // The ATTENTION state should be maintained. w_rsptype = URQ_CONCLUSION; - w_needs_extension = true; - w_needs_hsrsp = true; + w_tosend_ext_type = SRT_CMD_HSRSP; return; } } @@ -5507,8 +5475,7 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e w_rsptype = URQ_CONCLUSION; // initiator should send HSREQ, responder HSRSP, // in both cases extension is needed - w_needs_extension = true; - w_needs_hsrsp = hsd == HSD_RESPONDER; + w_tosend_ext_type = (hsd == HSD_RESPONDER) ? SRT_CMD_HSRSP : SRT_CMD_HSREQ; return; } @@ -5532,8 +5499,9 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e } } - reason = "FINE -> CONCLUSION(agreement), AGREEMENT(done)"; - break; + reason = "FINE -> CONCLUSION(agreement), AGREEMENT(done)"; + break; + case CHandShake::RDV_INITIATED: { // In this state we just wait for URQ_AGREEMENT, which should cause it to @@ -5566,8 +5534,7 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e log << CONID() << "rendezvousSwitchState: {RESPONDER}[INITIATED] awaits AGREEMENT, " "got CONCLUSION, sending CONCLUSION+HSRSP"); - w_needs_extension = true; - w_needs_hsrsp = true; + w_tosend_ext_type = SRT_CMD_HSRSP; return; } @@ -5590,14 +5557,13 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e << "rendezvousSwitchState: {INITIATOR}[INITIATED] awaits AGREEMENT, " "got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP"); } - w_needs_extension = true; - w_needs_hsrsp = true; + w_tosend_ext_type = SRT_CMD_HSRSP; return; } } - reason = "INITIATED -> AGREEMENT(done)"; - break; + reason = "INITIATED -> AGREEMENT(done)"; + break; case CHandShake::RDV_CONNECTED: // Do nothing. This theoretically should never happen. @@ -5616,15 +5582,21 @@ void srt::CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_e * This thread runs only if TsbPd mode is enabled * Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay. */ -void * srt::CUDT::tsbpd(void* param) +void * CUDT::tsbpd(void* param) { CUDT* self = (CUDT*)param; THREAD_STATE_INIT("SRT:TsbPd"); -#if ENABLE_BONDING - // Make the TSBPD thread a "client" of the group, - // which will ensure that the group will not be physically + if (!self->m_pRcvBuffer) + { + LOGC(tslog.Fatal, log << "IPE: started CUDT::tsbpd() thread without socket's receiver buffer (if group member, use GLat instead)"); + THREAD_EXIT(); + return 0; + } +#if SRT_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); @@ -5638,22 +5610,19 @@ void * srt::CUDT::tsbpd(void* param) { steady_clock::time_point tsNextDelivery; // Next packet delivery time bool rxready = false; -#if ENABLE_BONDING - bool shall_update_group = false; -#endif INCREMENT_THREAD_ITERATIONS(); - enterCS(self->m_RcvBufferLock); + self->m_RcvBufferLock.lock(); const steady_clock::time_point tnow = steady_clock::now(); self->m_pRcvBuffer->updRcvAvgDataSize(tnow); - const srt::CRcvBuffer::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); + const CRcvBuffer::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING if (info.seqno == SRT_SEQNO_NONE) { HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: NO PACKETS"); @@ -5677,25 +5646,23 @@ void * srt::CUDT::tsbpd(void* param) rxready = true; if (info.seq_gap) { + // XXX TSA: Requires lock on m_RcvBufferLock (locked already by enterCS) const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); -#if ENABLE_BONDING - shall_update_group = true; -#endif -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" << iDropCnt << " packets) playable at " << FormatTime(info.tsbpd_time) << " delayed " - << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) << " ms"); + << (timediff_us / 1000) << "." << fmt(timediff_us % 1000, fmtc().fixed().fillzero().width(3)) << " ms"); #endif string why; if (self->frequentLogAllowed(FREQLOGFA_RCV_DROPPED, tnow, (why))) { 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 " << why); + << " delayed for " << (timediff_us / 1000) << "." + << fmt(timediff_us % 1000, fmtc().fixed().fillzero().width(3)) << " ms " << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE else @@ -5708,7 +5675,7 @@ void * srt::CUDT::tsbpd(void* param) tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } } - leaveCS(self->m_RcvBufferLock); + self->m_RcvBufferLock.unlock(); if (rxready) { @@ -5727,54 +5694,12 @@ void * srt::CUDT::tsbpd(void* param) * Set EPOLL_IN to wakeup any thread waiting on epoll */ self->uglobal().m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true); -#if ENABLE_BONDING - // If this is NULL, it means: - // - the socket never was a group member - // - the socket was a group member, but: - // - was just removed as a part of closure - // - and will never be member of the group anymore - - // If this is not NULL, it means: - // - This socket is currently member of the group - // - This socket WAS a member of the group, though possibly removed from it already, BUT: - // - the group that this socket IS OR WAS member of is in the GroupKeeper - // - the GroupKeeper prevents the group from being deleted - // - it is then completely safe to access the group here, - // EVEN IF THE SOCKET THAT WAS ITS MEMBER IS BEING DELETED. - - // It is ensured that the group object exists here because GroupKeeper - // keeps it busy, even if you just closed the socket, remove it as a member - // or even the group is empty and was explicitly closed. - if (gkeeper.group) - { - // 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); - } - } // After re-acquisition of the m_RecvLock, re-check the closing flag if (self->m_bClosing) { break; } -#endif CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } @@ -5799,7 +5724,8 @@ void * srt::CUDT::tsbpd(void* param) THREAD_PAUSED(); bWokeUpOnSignal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP on " << (bWokeUpOnSignal? "SIGNAL" : "TIMEOUIT") << "!!!"); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP on " << (bWokeUpOnSignal? "SIGNAL" : "TIMEOUT") + << ". NOW=" << FormatTime(steady_clock::now())); } else { @@ -5819,18 +5745,16 @@ void * srt::CUDT::tsbpd(void* param) THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP on ACK (signal only). NOW=" << FormatTime(steady_clock::now())); } - - HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: WAKE UP [" << (bWokeUpOnSignal ? "signal" : "timeout") << "]!!! - " - << "NOW=" << FormatTime(steady_clock::now())); } THREAD_EXIT(); HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING"); return NULL; } -int srt::CUDT::rcvDropTooLateUpTo(int seqno, DropReason reason) +// This is to be called from tsbpd(). +int CUDT::rcvDropTooLateUpTo(int seqno, DropReason reason) { // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. if (CSeqNo::seqcmp(seqno, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) @@ -5847,24 +5771,24 @@ int srt::CUDT::rcvDropTooLateUpTo(int seqno, DropReason reason) const int iDropStatCnt = (reason == DROP_DISCARD) ? iDropCnt : iDropCntTotal; if (iDropStatCnt > 0) { - enterCS(m_StatsLock); + m_StatsLock.lock(); // Estimate dropped bytes from average payload size. const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); m_stats.rcvr.dropped.count(stats::BytesPackets(iDropStatCnt * avgpayloadsz, (uint32_t)iDropStatCnt)); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } return iDropCntTotal; } -void srt::CUDT::setInitialRcvSeq(int32_t isn) +void CUDT::setInitialRcvSeq(int32_t isn) { m_iRcvLastAck = isn; -#ifdef ENABLE_LOGGING - m_iDebugPrevLastAck = isn; -#endif + IF_HEAVY_LOGGING(m_iDebugPrevLastAck = isn); m_iRcvLastAckAck = isn; m_iRcvCurrSeqNo = CSeqNo::decseq(isn); + HLOGC(cnlog.Debug, log << "setInitialRcvSeq: ACK: %" << isn << " last-recv %" << CSeqNo::decseq(isn)); + sync::ScopedLock rb(m_RcvBufferLock); if (m_pRcvBuffer) { @@ -5881,80 +5805,67 @@ void srt::CUDT::setInitialRcvSeq(int32_t 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_pCryptoControl) - { - HLOGC(rslog.Debug, log << CONID() << "prepareConnectionObjects: (lazy) already created."); - return true; - } - - // 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. - // If this is a rendezvous connection with HSv5, the handshake role - // is taken from m_SrtHsSide field. - if (hsd == HSD_DRAW) - { - if (bidirectional) - { - hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR. - } - else - { - 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; -} - -int srt::CUDT::getAuthTagSize() const +// Called from tsbpd(). +int CUDT::getAuthTagSize() const { - if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + if (m_CryptoControl.getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) return HAICRYPT_AUTHTAG_MAX; return 0; } -bool srt::CUDT::prepareBuffers(CUDTException* eout) +bool CUDT::prepareBuffers(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) { HLOGC(rslog.Debug, log << CONID() << "prepareBuffers: (lazy) already created."); return true; } - + try { + // XXX SND buffer may allocate more memory, but must set the size of a single + // packet that fits the transmission for the overall connection. For any mixed 4-6 + // connection it should be the less size, that is, for IPv6 + +#if SRT_ENABLE_BONDING + // Keep the per-socket receiver buffer and receiver loss list empty. + // Reception will be redirected to the group directly. + const bool isgroup = m_parent->m_GroupOf; +#else + const bool isgroup = false; +#endif // 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 = getAuthTagSize(); SRT_ASSERT(m_iMaxSRTPayloadSize != 0); - - HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << m_iMaxSRTPayloadSize - << " snd-bufsize=" << 32 + SRT_ASSERT(m_TransferIPVersion != AF_UNSPEC); + // IMPORTANT: + // The m_iMaxSRTPayloadSize is the size of the payload in the "SRT packet" that can be sent + // over the current connection - which means that if both parties are IPv6, then the maximum size + // is the one for IPv6 (1444). If any party is IPv4, this maximum size is 1456. + // The family as the first argument is something different - it's for the header size in order + // to calculate rate and statistics. + + int snd_header_size = CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); + int snd_payload_size SRT_ATR_UNUSED = m_config.iMSS - snd_header_size; + SRT_ASSERT(m_iMaxSRTPayloadSize <= snd_payload_size); + + HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << snd_payload_size + << " snd-bufsize=" << 32 << " TF-IPv" + << (m_TransferIPVersion == AF_INET6 ? "6" : m_TransferIPVersion == AF_INET ? "4" : "???") << " authtag=" << authtag); - m_pSndBuffer = new CSndBuffer(AF_INET, 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_pSndBuffer = new CSndBuffer (m_config.iSndBufSize, 32, m_config.iMSS, snd_header_size, authtag, m_iFlowWindowSize); + if (!isgroup) + { + SRT_ASSERT(m_iISN != SRT_SEQNO_NONE); + m_pRcvBuffer = new CRcvBuffer(m_iISN, m_config.iRcvBufSize, m_pMuxer, m_config.bMessageAPI); + } + // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space. m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } catch (...) @@ -5968,7 +5879,7 @@ bool srt::CUDT::prepareBuffers(CUDTException* eout) return true; } -void srt::CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) +void CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) { // this is a response handshake w_hs.m_iReqType = URQ_CONCLUSION; @@ -5981,25 +5892,43 @@ void srt::CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) // 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; + w_hs.m_extensionType = (m_SrtHsSide == HSD_INITIATOR) ? SRT_CMD_HSREQ : SRT_CMD_HSRSP; } - CIPAddress::ntop(peer, (w_hs.m_piPeerIP)); + CIPAddress::encode(peer, (w_hs.m_piPeerIP)); } -void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) +void CUDT::acceptAndRespond(CUDTSocket* lsn, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: setting up data according to handshake"); + const sockaddr_any& agent = lsn->m_SelfAddr; ScopedLock cg(m_ConnectionLock); m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS + m_TransferIPVersion = peer.family(); + if (peer.family() == AF_INET6) + { + // Check if the peer's address is a mapped IPv4. If so, + // define Transfer IP version as 4 because this one will be used. + if (checkMappedIPv4(peer.sin6)) + m_TransferIPVersion = AF_INET; + } + // Uses the smaller MSS between the peers m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; + const size_t full_hdr_size = CPacket::udpHeaderSize(m_TransferIPVersion) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; + if (m_iMaxSRTPayloadSize < int(m_config.zExpPayloadSize)) + { + LOGC(cnlog.Error, log << CONID() << "acceptAndRespond: negotiated MSS=" << m_config.iMSS + << " leaves too little payload space " << m_iMaxSRTPayloadSize << " for configured payload size " + << m_config.zExpPayloadSize); + m_RejectReason = SRT_REJ_CONFIG; + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + } HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); @@ -6020,19 +5949,22 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // get local IP address and send the peer its IP address (because UDP cannot get local IP address) memcpy((m_piSelfIP), w_hs.m_piPeerIP, sizeof m_piSelfIP); m_parent->m_SelfAddr = agent; - CIPAddress::pton((m_parent->m_SelfAddr), m_piSelfIP, peer); + CIPAddress::decode(m_piSelfIP, peer, (m_parent->m_SelfAddr)); rewriteHandshakeData(peer, (w_hs)); + m_TransferIPVersion = peer.family(); + if (peer.family() == AF_INET6) + { + // Check if the peer's address is a mapped IPv4. If so, + // define Transfer IP version as 4 because this one will be used. + if (checkMappedIPv4(peer.sin6)) + m_TransferIPVersion = AF_INET; + } - // Prepare all structures - if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) + if (!createCrypter(HSD_RESPONDER)) { - 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. - // + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: createCrypter failed - responding with REJECT."); // 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); @@ -6057,13 +5989,9 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& 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(w_hs, hspkt, (kmdata), (&kmdatasize))) + if (!interpretSrtHandshake(lsn, w_hs, hspkt, (kmdata), (&kmdatasize))) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT."); @@ -6077,7 +6005,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING m_ConnectionLock.unlock(); // The socket and the group are only linked to each other after interpretSrtHandshake(..) has been called. // Keep the group alive for the lifetime of this function, @@ -6092,7 +6020,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& if (!prepareBuffers(NULL)) { HLOGC(cnlog.Debug, - log << CONID() << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT."); + log << CONID() << "acceptAndRespond: prepareBuffers failed - responding with REJECT."); // If the SRT buffers failed to be allocated, // the connection must be rejected. // @@ -6107,7 +6035,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& bool have_group = false; { -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING CUDTGroup* g = group_keeper.group; if (g) { @@ -6140,13 +6068,33 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // And of course, it is connected. m_bConnected = true; - // Register this socket for receiving data packets. - m_pRNode->m_bOnList = true; - m_pRcvQueue->setNewEntry(this); + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: setReceiver"); // Save the handshake in m_ConnRes in case when needs repeating. m_ConnRes = w_hs; + // Connection lock will be used with Muxer content locked when doing + // checkTimers during connection. + m_ConnectionLock.unlock(); + // Register this socket for receiving data packets. + m_pMuxer->setReceiver(this); + m_ConnectionLock.lock(); // lock-back required because used here by ScopedLock + + // NOTE: UNBLOCK THIS instruction in order to cause the final + // handshake to be missed and cause the problem solved in PR #417. + // When missed this message, the caller should not accept packets + // coming as connected, but continue repeated handshake until finally + // received the listener's handshake. + //return; + + if (!createSendHSResponse(kmdata, kmdatasize, hspkt.udpDestAddr(), (w_hs))) + { + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + } +} + +bool CUDT::createSendHSResponse(uint32_t* kmdata, size_t kmdatasize, const CNetworkInterface& hsaddr, CHandShake& w_hs) ATR_NOTHROW +{ // Send the response to the peer, see listen() for more discussions // about this. // TODO: Here create CONCLUSION RESPONSE with: @@ -6161,26 +6109,25 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // This will serialize the handshake according to its current form. HLOGC(cnlog.Debug, - log << CONID() - << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); + log << CONID() << "createSendHSResponse: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (w_hs))) { - LOGC(cnlog.Error, log << CONID() << "acceptAndRespond: error creating handshake response"); - throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + LOGC(cnlog.Error, log << CONID() << "createSendHSResponse: error creating handshake response"); + return false; } // We can safely assign it here stating that this has passed the cookie test. - m_SourceAddr = hspkt.udpDestAddr(); + m_SourceAddr = hsaddr; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING { // To make sure what REALLY is being sent, parse back the handshake // data that have been just written into the buffer. CHandShake debughs; debughs.load_from(rsppkt.m_pcData, rsppkt.getLength()); HLOGC(cnlog.Debug, - log << CONID() << "acceptAndRespond: sending HS from agent @" - << debughs.m_iID << " to peer @" << rsppkt.id() + log << CONID() << "createSendHSResponse: sending HS from agent @" + << debughs.m_iID << " to peer @" << m_PeerID // <-- will be used by addressAndSend << "HS:" << debughs.show() << " sourceIP=" << m_SourceAddr.str()); } @@ -6192,9 +6139,10 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // coming as connected, but continue repeated handshake until finally // received the listener's handshake. addressAndSend((rsppkt)); + return true; } -bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::string& w_why) +bool CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::string& w_why) { #ifndef SRT_LOG_SLOWDOWN_FREQ_MS #define SRT_LOG_SLOWDOWN_FREQ_MS 1000 @@ -6220,12 +6168,12 @@ bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::st const int supr = m_aSuppressedMsg[logid]; if (supr > 0) - w_why = Sprint("++SUPPRESSED: ", supr); + w_why = fmtcat("++SUPPRESSED: ", supr); m_aSuppressedMsg[logid] = 0; } else { - w_why = Sprint("Too early - last one was ", FormatDuration(tnow - m_tsLogSlowDown[logid].load())); + w_why = fmtcat("Too early - last one was ", FormatDuration(tnow - m_tsLogSlowDown[logid].load())); // Set YOUR OWN bit, atomically. m_LogSlowDownExpired |= uint8_t(BIT(logid)); ++m_aSuppressedMsg[logid]; @@ -6242,35 +6190,30 @@ bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::st // 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 srt::CUDT::createCrypter(HandshakeSide side, bool bidirectional) +bool CUDT::createCrypter(HandshakeSide side) { // Lazy initialization - if (m_pCryptoControl) + if (m_CryptoControl.initialized()) return true; // Write back this value, when it was just determined. m_SrtHsSide = side; - 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_config.CryptoSecret); + m_CryptoControl.setCryptoSecret(m_config.CryptoSecret); const bool useGcm153 = m_uPeerSrtVersion <= SrtVersion(1, 5, 3); - if (bidirectional || m_config.bDataSender) - { - HLOGC(rslog.Debug, log << CONID() << "createCrypter: setting RCV/SND KeyLen=" << m_config.iSndCryptoKeyLen); - m_pCryptoControl->setCryptoKeylen(m_config.iSndCryptoKeyLen); - } + HLOGC(rslog.Debug, log << CONID() << "createCrypter: setting RCV/SND KeyLen=" << m_config.iSndCryptoKeyLen); + m_CryptoControl.setCryptoKeylen(m_config.iSndCryptoKeyLen); - return m_pCryptoControl->init(side, m_config, bidirectional, useGcm153); + return m_CryptoControl.init(m_SocketID, side, m_config, useGcm153); } -SRT_REJECT_REASON srt::CUDT::setupCC() +SRT_REJECT_REASON CUDT::setupCC() { // Prepare configuration object, // Create the CCC object and configure it. @@ -6307,7 +6250,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC() { // 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()); + status = m_PacketFilter.configure(this, m_config.sPacketFilterConfig.str()); } catch (CUDTException& ) { @@ -6334,23 +6277,22 @@ SRT_REJECT_REASON srt::CUDT::setupCC() // Update timers const steady_clock::time_point currtime = steady_clock::now(); - m_tsLastRspTime.store(currtime); + m_tsLastRspTime.store(currtime); // [TSA] initial m_tsNextACKTime.store(currtime + m_tdACKInterval); m_tsNextNAKTime.store(currtime + m_tdNAKInterval); m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); -#ifdef ENABLE_RATE_MEASUREMENT - // XXX NOTE: use IPv4 or IPv6 as applicable! +#ifdef SRT_ENABLE_RATE_MEASUREMENT HLOGC(bslog.Debug, log << CONID() << "RATE-MEASUREMENT: initializing time TS=" << FormatTime(currtime)); - m_SndRegularMeasurement.init(currtime, CPacket::UDP_HDR_SIZE); - m_SndRexmitMeasurement.init(currtime, CPacket::UDP_HDR_SIZE); + m_SndRegularMeasurement.init(currtime, CPacket::udpHeaderSize(m_TransferIPVersion)); + m_SndRexmitMeasurement.init(currtime, CPacket::udpHeaderSize(m_TransferIPVersion)); #endif - 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); + HLOGC(rslog.Debug, log << CONID() << "setupCC: setting parameters: mss=" << m_config.iMSS + << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize.load() + << " rcvrate=" << m_iDeliveryRate.load() << "p/s (" << m_iByteDeliveryRate.load() << "B/S)" + << " rtt=" << m_iSRTT.load() << " bw=" << m_iBandwidth.load()); if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET))) { @@ -6360,7 +6302,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC() return SRT_REJ_UNKNOWN; } -void srt::CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timebase) +void 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 @@ -6370,7 +6312,7 @@ void srt::CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timeb if (m_iSndHsRetryCnt <= 0) { - HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); + //HLOGC(cnlog.Debug, log << CONID() << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt); return; } @@ -6412,13 +6354,12 @@ void srt::CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timeb sendSrtMsg(SRT_CMD_HSREQ); } -void srt::CUDT::checkSndTimers() +void CUDT::checkSndTimers() { if (m_SrtHsSide == HSD_INITIATOR) { HLOGC(cnlog.Debug, log << CONID() << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase"); - // Legacy method for HSREQ, only if initiator. considerLegacySrtHandshake(m_tsSndHsLastTime + microseconds_from(m_iSRTT * 3 / 2)); } else @@ -6431,20 +6372,10 @@ void srt::CUDT::checkSndTimers() // 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()); + m_CryptoControl.sendKeysToPeer(this, avgRTT()); } -void srt::CUDT::checkSndKMRefresh() -{ - // Do not apply the regenerated key to the to the receiver context. - const bool bidir = false; - if (m_pCryptoControl) - m_pCryptoControl->regenCryptoKm(this, bidir); -} - -void srt::CUDT::addressAndSend(CPacket& w_pkt) +void CUDT::addressAndSend(CPacket& w_pkt) { w_pkt.set_id(m_PeerID); setPacketTS(w_pkt, steady_clock::now()); @@ -6454,12 +6385,13 @@ void srt::CUDT::addressAndSend(CPacket& w_pkt) // 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); + SRT_ASSERT(channel()); + channel()->sendto(m_PeerAddr, w_pkt, m_SourceAddr); } // [[using maybe_locked(m_GlobControlLock, if called from breakSocket_LOCKED, usually from GC)]] // [[using maybe_locked(m_parent->m_ControlLock, if called from srt_close())]] -bool srt::CUDT::closeInternal() ATR_NOEXCEPT +bool CUDT::closeEntity(int reason) ATR_NOEXCEPT { // NOTE: this function is called from within the garbage collector thread. @@ -6473,7 +6405,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT // 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"); + HLOGC(smlog.Debug, log << CONID() << "closeEntity: closing socket"); if (m_config.Linger.l_onoff != 0) { @@ -6494,7 +6426,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT m_tsLingerExpiration = entertime + seconds_from(m_config.Linger.l_linger); HLOGC(smlog.Debug, - log << CONID() << "CUDT::close: linger-nonblocking, setting expire time T=" + log << CONID() << "CUDT::closeEntity: linger-nonblocking, setting expire time T=" << FormatTime(m_tsLingerExpiration)); return false; @@ -6511,9 +6443,17 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT } } + // Some calls of closeInternal pass UNKNOWN here, which means + // that they don't want to change the code. It should have been + // set already somewhere else, however. + if (reason != SRT_CLS_UNKNOWN) + { + setAgentCloseReason(reason); + } + // remove this socket from the snd queue if (m_bConnected) - m_pSndQueue->m_pSndUList->remove(this); + m_pMuxer->removeSender(this); /* * update_events below useless @@ -6523,42 +6463,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT * 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. - 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 - 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); + uglobal().m_EPoll.wipe_usock(m_SocketID, m_sPollID); // XXX What's this, could any of the above actions make it !m_bOpened? if (!m_bOpened) @@ -6571,6 +6476,11 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE (closing=true). Acquiring connection lock"); + // XXX m_ConnectionLock should precede m_GlobControlLock, + // so it could be a potential deadlock. Consider making sure that + // any potential connection processing is impossible on a socket + // that has m_bClosing flag set and so locking m_ConnectionLock is + // not necessary. ScopedLock connectguard(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. @@ -6581,7 +6491,9 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT if (m_bListening) { m_bListening = false; - bool removed SRT_ATR_UNUSED = m_pRcvQueue->removeListener(this); + bool removed SRT_ATR_UNUSED = m_pMuxer->removeListener(this); + // NOTE: removeListener removes this socket as listener in the multiplexer, + // but DOES NOT remove the socket from the multiplerxer (YET). if (!removed) { LOGC(smlog.Error, log << CONID() << "CLOSING: IPE: listening=true but listener removal failed!"); @@ -6589,7 +6501,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT } else if (m_bConnecting) { - m_pRcvQueue->removeConnector(m_SocketID); + m_pMuxer->removeConnector(m_SocketID); } if (m_bConnected) @@ -6597,7 +6509,8 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT if (!m_bShutdown) { HLOGC(smlog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer @" << m_PeerID); - sendCtrl(UMSG_SHUTDOWN); + int32_t shdata[1] = { reason }; + sendCtrl(UMSG_SHUTDOWN, NULL, shdata, sizeof shdata); } // Store current connection information. @@ -6616,36 +6529,34 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT m_bConnected = false; } - HLOGC(smlog.Debug, log << CONID() << "CLOSING, joining send/receive threads"); + HLOGC(smlog.Debug, log << CONID() << "closeEntity: joining send/receive threads"); // waiting all send and recv calls to stop ScopedLock sendguard(m_SendLock); ScopedLock recvguard(m_RecvLock); - // Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt((packet)) + // Locking m_RcvBufferLock to protect calling to m_CryptoControl.decrypt((packet)) // from the processData(...) function while resetting Crypto Control. - enterCS(m_RcvBufferLock); - if (m_pCryptoControl) - m_pCryptoControl->close(); - - m_pCryptoControl.reset(); - leaveCS(m_RcvBufferLock); + ScopedLock rblock (m_RcvBufferLock); + m_CryptoControl.close(); m_uPeerSrtVersion = SRT_VERSION_UNK; m_tsRcvPeerStartTime = steady_clock::time_point(); - m_bOpened = false; + m_bConnecting = false; + m_bConnected = false; + HLOGC(smlog.Debug, log << CONID() << "closeEntity: done."); return true; } -bool srt::CUDT::closeAtFork() ATR_NOEXCEPT +bool CUDT::closeAtFork() ATR_NOEXCEPT { m_bShutdown = true; - return closeInternal(); + return closeEntity(SRT_CLS_CLEANUP); } -int srt::CUDT::receiveBuffer(char *data, int len) +int CUDT::receiveBuffer(char *data, int len) { if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0); @@ -6741,9 +6652,9 @@ int srt::CUDT::receiveBuffer(char *data, int len) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - enterCS(m_RcvBufferLock); + m_RcvBufferLock.lock(); const int res = m_pRcvBuffer->readBuffer(data, len); - leaveCS(m_RcvBufferLock); + m_RcvBufferLock.unlock(); /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) @@ -6770,7 +6681,7 @@ int srt::CUDT::receiveBuffer(char *data, int len) // [[using maybe_locked(CUDTGroup::m_GroupLock, m_parent->m_GroupOf != NULL)]]; // [[using locked(m_SendLock)]]; -int srt::CUDT::sndDropTooLate() +int CUDT::sndDropTooLate() { if (!m_bPeerTLPktDrop) return 0; @@ -6800,6 +6711,9 @@ int srt::CUDT::sndDropTooLate() // protect packet retransmission ScopedLock rcvlck(m_RecvAckLock); + + IF_HEAVY_LOGGING(const int32_t realack = m_pSndBuffer->firstSeqNo()); + int dbytes; int32_t first_msgno; const int dpkts = m_pSndBuffer->dropLateData((dbytes), (first_msgno), tnow - milliseconds_from(threshold_ms)); @@ -6809,48 +6723,25 @@ int srt::CUDT::sndDropTooLate() m_iFlowWindowSize = m_iFlowWindowSize + dpkts; // If some packets were dropped update stats, socket state, loss list and the parent group if any. - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndr.dropped.count(stats::BytesPackets((uint64_t) dbytes, (uint32_t) dpkts)); - leaveCS(m_StatsLock); - - IF_HEAVY_LOGGING(const int32_t realack = m_iSndLastDataAck); - const int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); + m_StatsLock.unlock(); - m_iSndLastAck = fakeack; - m_iSndLastDataAck = fakeack; + // NOTE: This sequence number involves also reserved data, if any. + m_iSndLastAck = m_pSndBuffer->firstSeqNo(); - 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) - { - m_iSndCurrSeqNo = minlastack; - } + m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, m_iSndLastAck); HLOGC(qslog.Debug, - log << CONID() << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo << ") n=" << dpkts << "pkt " << dbytes + log << CONID() << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo.load() << ") n=" << dpkts << "pkt " << dbytes << "B, span=" << buffdelay_ms << " ms, FIRST #" << first_msgno); -#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 SRT_ENABLE_BONDING + // NOTE: Assumed that as called indirectly from CUDTGroup::send, the + // lock on CUDTGroup::m_GroupLock is applied already there. 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 @@ -6858,7 +6749,7 @@ int srt::CUDT::sndDropTooLate() return dpkts; } -int srt::CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64_t srctime) +int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64_t srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; mctrl.msgttl = msttl; @@ -6870,7 +6761,7 @@ int srt::CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64 // [[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) +int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) { // throw an exception if not connected if (m_bBroken || m_bClosing) @@ -6940,15 +6831,12 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } - /* XXX - This might be worth preserving for several occasions, but it - must be at least conditional because it breaks backward compat. - if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK()) + if (!m_CryptoControl.isSndEncryptionOK()) { - LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending - rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); + 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); } - */ UniqueLock sendguard(m_SendLock); @@ -6961,7 +6849,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) } // sndDropTooLate(...) may lock m_RecvAckLock - // to modify m_pSndBuffer and m_pSndLossList + // to modify m_pSndBuffer const int iPktsTLDropped SRT_ATR_UNUSED = sndDropTooLate(); // For MESSAGE API the minimum outgoing buffer space required is @@ -6986,12 +6874,12 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) { // wait here during a blocking sending - UniqueLock sendblock_lock (m_SendBlockLock); + CUniqueSync sendblock_cc (m_SendBlockLock, m_SendBlockCond); if (m_config.iSndTimeOut < 0) { while (stillConnected() && sndBuffersLeft() < iNumPktsRequired && m_bPeerHealth) - m_SendBlockCond.wait(sendblock_lock); + sendblock_cc.wait(); } else { @@ -7000,7 +6888,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) THREAD_PAUSED(); while (stillConnected() && sndBuffersLeft() < iNumPktsRequired && m_bPeerHealth) { - if (!m_SendBlockCond.wait_until(sendblock_lock, exptime)) + if (!sendblock_cc.wait_until(exptime)) break; } THREAD_RESUMED(); @@ -7079,23 +6967,29 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) IF_HEAVY_LOGGING(steady_clock::time_point ts_srctime = steady_clock::time_point() + microseconds_from(w_mctrl.srctime)); -#if ENABLE_BONDING +#if SRT_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) + // an attempt to call srt_sendmsg2 for a single (also member) socket + // has been rejected, and so the pktseq field has been set by the + // internal group sender function. + + // NOTE 2: it is assumed that if m_GroupOf is not NULL this means + // that this function is called under m_parent->m_GroupOf->m_GroupLock locked. + if (m_parent->m_GroupOf) { - if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0) + if (w_mctrl.pktseq != SRT_SEQNO_NONE + && m_iSndNextSeqNo != SRT_SEQNO_NONE) { - HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq - << " OLDER THAN next expected %" << seqno << " - FAKE-SENDING."); - return size; + 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 @@ -7111,9 +7005,10 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) 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)); + << " !" << BufferStamp(data, size)); - if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(m_stats.tsStartTime.time_since_epoch())) + time_point start_time = m_stats.tsStartTime; + if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(start_time.time_since_epoch())) { LOGC(aslog.Error, log << CONID() << "Wrong source time was provided. Sending is rejected."); @@ -7134,7 +7029,19 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // - 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)); + time_point last_origin_time; + EncryptionKeySpec kflg = m_pSndBuffer->addBuffer(data, size, (m_CryptoControl), (last_origin_time), (w_mctrl)); + if (kflg == EK_ERROR) + { + LOGC(aslog.Error, log << CONID() << "sendmsg2: ENCRYPTION NOT POSSIBLE, rejecting request"); + throw CUDTException(MJ_SETUP, MN_SECURITY); + } + if (kflg != EK_NOENC) + updateCryptoOnSending(); + + if (m_tsSndNextUnique.load() == time_point()) + m_tsSndNextUnique = last_origin_time; + m_iSndNextSeqNo = w_mctrl.pktseq; w_mctrl.pktseq = seqno; @@ -7150,8 +7057,8 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) } // 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::DONT_RESCHEDULE); + // m_pMuxer->sndUList()->pop may lock CSndUList::m_ListLock and then m_RecvAckLock + m_pMuxer->updateSendNormal(m_parent); #ifdef SRT_ENABLE_ECN // IF there was a packet drop on the sender side, report congestion to the app. @@ -7166,13 +7073,36 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) return size; } -int srt::CUDT::recv(char* data, int len) +void CUDT::updateCryptoOnSending() +{ + int keyindex[2] = {-1, -1}; + if (!m_CryptoControl.regenCryptoKm( (keyindex) )) + return; + + for (int i = 0; i < 2; ++i) + { + int ki = keyindex[i]; + if (ki == -1) + break; + + const CCryptoControl::KmMessage* km = m_CryptoControl.getKmMsg(ki); + + HLOGC(qslog.Debug, log << "after regenCryptoKm: SENDING ki=" << ki << " len=" << km->bytesize() + << " retry(updated)=" << km->iPeerRetry); + + sendSrtMsg(SRT_CMD_KMREQ, + reinterpret_cast(km->bytedata()), + km->bytesize() / sizeof(uint32_t)); + } +} + +int CUDT::recv(char* data, int len) { SRT_MSGCTRL mctrl = srt_msgctrl_default; return recvmsg2(data, len, (mctrl)); } -int srt::CUDT::recvmsg(char* data, int len, int64_t& srctime) +int CUDT::recvmsg(char* data, int len, int64_t& srctime) { SRT_MSGCTRL mctrl = srt_msgctrl_default; int res = recvmsg2(data, len, (mctrl)); @@ -7183,12 +7113,12 @@ int srt::CUDT::recvmsg(char* data, int len, int64_t& srctime) // [[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) +int 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 ENABLE_BONDING +#if SRT_ENABLE_BONDING if (m_parent->m_GroupOf && m_parent->m_GroupOf->isGroupReceiver()) { LOGP(arlog.Error, "recv*: This socket is a receiver group member. Use group ID, NOT socket ID."); @@ -7212,23 +7142,37 @@ int srt::CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl) } // [[using locked(m_RcvBufferLock)]] -size_t srt::CUDT::getAvailRcvBufferSizeNoLock() const +size_t CUDT::getAvailRcvBufferSizeNoLock() const { + // This function shall not be called on group member sockets. +#if SRT_ENABLE_BONDING + SRT_ASSERT(m_parent->m_GroupOf == NULL); + if (m_parent->m_GroupOf) + return 0; +#endif return m_pRcvBuffer->getAvailSize(m_iRcvLastAck); } -bool srt::CUDT::isRcvBufferReady() const +bool CUDT::isRcvBufferReady() const { +#if SRT_ENABLE_BONDING + // This function shall not be called on group member sockets. + // It's hard to untangle every use of this function without refaxing + // the epoll system, so we just ignore it in release mode. + SRT_ASSERT(m_parent->m_GroupOf == NULL); + if (m_parent->m_GroupOf) + return false; +#endif ScopedLock lck(m_RcvBufferLock); return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } -bool srt::CUDT::isRcvBufferReadyNoLock() const +bool CUDT::isRcvBufferReadyNoLock() const { return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } -bool srt::CUDT::isRcvBufferFull() const +bool CUDT::isRcvBufferFull() const { ScopedLock lck(m_RcvBufferLock); return m_pRcvBuffer->full(); @@ -7238,7 +7182,7 @@ bool srt::CUDT::isRcvBufferFull() const // - 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) +int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_exception) { // Recvmsg isn't restricted to the congctl type, it's the most // basic method of passing the data. You can retrieve data as @@ -7254,6 +7198,12 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0); +#if SRT_ENABLE_BONDING + // This function shall not be used on group member sockets. + if (m_parent->m_GroupOf) + throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI); +#endif + UniqueLock recvguard (m_RecvLock); CSync tscond (m_RcvTsbPdCond, recvguard); @@ -7273,11 +7223,11 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ if (m_bBroken || m_bClosing) { HLOGC(arlog.Debug, log << CONID() << "receiveMessage: CONNECTION BROKEN - reading from recv buffer just for formality"); - enterCS(m_RcvBufferLock); + m_RcvBufferLock.lock(); const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) - ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + ? m_pRcvBuffer->readMessage((data), len, (w_mctrl)) : 0; - leaveCS(m_RcvBufferLock); + m_RcvBufferLock.unlock(); // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) @@ -7302,7 +7252,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ return 0; // Forced to return error instead of throwing exception. if (!by_exception) - return APIError(MJ_CONNECTION, MN_CONNLOST, 0); + return APIError(MJ_CONNECTION, MN_CONNLOST, 0), int(SRT_ERROR); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else @@ -7312,11 +7262,11 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ if (!m_config.bSynRecving) { HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN ASYNC MODE. Going to extract payload size=" << len); - enterCS(m_RcvBufferLock); + m_RcvBufferLock.lock(); const int res = (m_pRcvBuffer->isRcvDataReady(steady_clock::now())) - ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) + ? m_pRcvBuffer->readMessage((data), len, (w_mctrl)) : 0; - leaveCS(m_RcvBufferLock); + m_RcvBufferLock.unlock(); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); if (res == 0) @@ -7358,9 +7308,10 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ 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 +#if HVU_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)"); + HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " + << bw << "Mbps (" << m_iBandwidth.load() << " buffers per second)"); #endif } return res; @@ -7432,16 +7383,16 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ << " NMSG " << m_pRcvBuffer->getRcvMsgNum()); */ - enterCS(m_RcvBufferLock); - res = m_pRcvBuffer->readMessage((data), len, &w_mctrl); - leaveCS(m_RcvBufferLock); + m_RcvBufferLock.lock(); + res = m_pRcvBuffer->readMessage((data), len, (w_mctrl)); + m_RcvBufferLock.unlock(); HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); if (m_bBroken || m_bClosing) { // Forced to return 0 instead of throwing exception. if (!by_exception) - return APIError(MJ_CONNECTION, MN_CONNLOST, 0); + return APIError(MJ_CONNECTION, MN_CONNLOST, 0).as(); if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -7450,7 +7401,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ { // Forced to return -1 instead of throwing exception. if (!by_exception) - return APIError(MJ_CONNECTION, MN_NOCONN, 0); + return APIError(MJ_CONNECTION, MN_NOCONN, 0).as(); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } } while ((res == 0) && !timeout); @@ -7482,14 +7433,14 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ { // Forced to return -1 instead of throwing exception. if (!by_exception) - return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0); + return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0).as(); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } return res; } -int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) +int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block) { if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -7502,11 +7453,10 @@ int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int blo 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()) + if (!m_CryptoControl.isSndEncryptionOK()) { LOGC(aslog.Error, - log << CONID() - << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); + log << CONID() << "Encryption is required, but the peer did not supply correct credentials. Sending rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0); } @@ -7582,16 +7532,29 @@ int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int blo throw CUDTException(MJ_PEERERROR); } + time_point now = steady_clock::now(); + // record total time used for sending if (m_pSndBuffer->getCurrBufSize() == 0) { ScopedLock lock(m_StatsLock); - m_stats.sndDurationCounter = steady_clock::now(); + m_stats.sndDurationCounter = now; } { ScopedLock recvAckLock(m_RecvAckLock); - const int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize); + int64_t sentsize; + EncryptionKeySpec kflg = m_pSndBuffer->addBufferFromFile((ifs), unitsize, (m_CryptoControl), (sentsize)); + if (kflg == EK_ERROR) + { + LOGC(aslog.Error, log << CONID() << "sendfile: ENCRYPTION NOT POSSIBLE, rejecting request"); + throw CUDTException(MJ_SETUP, MN_SECURITY); + } + if (kflg != EK_NOENC) + updateCryptoOnSending(); + + if (m_tsSndNextUnique.load() == time_point()) + m_tsSndNextUnique = now; if (sentsize > 0) { @@ -7603,17 +7566,18 @@ int64_t srt::CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int blo { // write is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); + // XXX shouldn't it BREAK here??? } } // insert this socket to snd list if it is not on the list yet - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + m_pMuxer->updateSendNormal(m_parent); } return size - tosend; } -int64_t srt::CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block) +int64_t 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); @@ -7715,9 +7679,9 @@ int64_t srt::CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int blo } unitsize = int((torecv > block) ? block : torecv); - enterCS(m_RcvBufferLock); + m_RcvBufferLock.lock(); recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize); - leaveCS(m_RcvBufferLock); + m_RcvBufferLock.unlock(); if (recvsize > 0) { @@ -7735,22 +7699,25 @@ int64_t srt::CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int blo return size - torecv; } -void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) +void 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); - const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + const int pktHdrSize = CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion == AF_UNSPEC ? AF_INET : m_TransferIPVersion); { + m_RecvAckLock.lock(); int32_t flight_span = getFlightSpan(); + m_RecvAckLock.unlock(); ScopedLock statsguard(m_StatsLock); - const steady_clock::time_point currtime = steady_clock::now(); + const time_point currtime = steady_clock::now(), + start_time = m_stats.tsStartTime; - perf->msTimeStamp = count_milliseconds(currtime - m_stats.tsStartTime); + perf->msTimeStamp = count_milliseconds(currtime - start_time); perf->pktSent = m_stats.sndr.sent.trace.count(); perf->pktSentUnique = m_stats.sndr.sentUnique.trace.count(); perf->pktRecv = m_stats.rcvr.recvd.trace.count(); @@ -7853,7 +7820,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize)); - if (tryEnterCS(m_ConnectionLock)) + if (m_ConnectionLock.try_lock()) { if (m_pSndBuffer) { @@ -7899,7 +7866,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) perf->msRcvBuf = 0; } - leaveCS(m_ConnectionLock); + m_ConnectionLock.unlock(); } else { @@ -7913,7 +7880,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) } } -bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) +bool 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 @@ -7968,8 +7935,7 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) else { // 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); + m_pSndBuffer->enableRateEstimationIf(bw == 0); } HLOGC(rslog.Debug, @@ -7986,6 +7952,8 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) { // Specific part done when MaxBW is set to 0 (auto) and InputBW is 0. // This requests internal input rate sampling. + // XXX NOTE: This is called with affinity to receiver worker thread, + // but these values can be altered by setOpt() from the main thread. if (m_config.llMaxBW == 0 && m_config.llInputBW == 0) { // Get auto-calculated input rate, Bytes per second @@ -8019,11 +7987,11 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) m_tdSendInterval = microseconds_from((int64_t)m_CongCtl->pktSndPeriod_us()); const double cgwindow = m_CongCtl->cgWindowSize(); m_iCongestionWindow = (int) cgwindow; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING HLOGC(rslog.Debug, log << CONID() << "updateCC: updated values from congctl: interval=" << FormatDuration(m_tdSendInterval) << " (cfg:" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" - << std::setprecision(3) << cgwindow); + << fmt(cgwindow, fmtc().precision(7))); #endif } @@ -8032,7 +8000,7 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) return true; } -void srt::CUDT::initSynch() +void CUDT::initSynch() { setupMutex(m_SendBlockLock, "SendBlock"); setupCond(m_SendBlockCond, "SendBlock"); @@ -8047,7 +8015,7 @@ void srt::CUDT::initSynch() setupCond(m_RcvTsbPdCond, "RcvTsbPd"); } -void srt::CUDT::destroySynch() +void CUDT::destroySynch() { releaseMutex(m_SendBlockLock); @@ -8071,14 +8039,15 @@ void srt::CUDT::destroySynch() m_RcvTsbPdCond.notify_all(); releaseCond(m_RcvTsbPdCond); } -void srt::CUDT::resetAtFork() + +void CUDT::resetAtFork() { resetCond(m_SendBlockCond); resetCond(m_RecvDataCond); resetCond(m_RcvTsbPdCond); } -void srt::CUDT::releaseSynch() +void CUDT::releaseSynch() { SRT_ASSERT(m_bClosing); if (!m_bClosing) @@ -8089,8 +8058,8 @@ void srt::CUDT::releaseSynch() // wake up user calls CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); - enterCS(m_SendLock); - leaveCS(m_SendLock); + m_SendLock.lock(); + m_SendLock.unlock(); // Awake tsbpd() and srt_recv*(..) threads for them to check m_bClosing. CSync::lock_notify_all(m_RecvDataCond, m_RecvLock); @@ -8099,21 +8068,20 @@ void srt::CUDT::releaseSynch() // Azquiring m_RcvTsbPdStartupLock protects race in starting // the tsbpd() thread in CUDT::processData(). // Wait for tsbpd() thread to finish. - enterCS(m_RcvTsbPdStartupLock); + m_RcvTsbPdStartupLock.lock(); if (m_RcvTsbPdThread.joinable()) { m_RcvTsbPdThread.join(); } - leaveCS(m_RcvTsbPdStartupLock); + m_RcvTsbPdStartupLock.unlock(); // 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); + m_RecvLock.lock(); + m_RecvLock.unlock(); } -namespace srt { -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING static void DebugAck(string hdr, int prev, int ack) { if (!prev) @@ -8143,9 +8111,8 @@ static void DebugAck(string hdr, int prev, int ack) #else static inline void DebugAck(string, int, int) {} #endif -} -void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size) +void CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size) { CPacket ctrlpkt; setPacketTS(ctrlpkt, steady_clock::now()); @@ -8163,12 +8130,13 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement ctrlpkt.pack(pkttype, lparam); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_LOSSREPORT: // 011 - Loss Report { + ScopedLock lock(m_RcvLossLock); // Explicitly defined lost sequences if (rparam) { @@ -8178,16 +8146,15 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp ctrlpkt.pack(pkttype, NULL, lossdata, bytes); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.rcvr.sentNak.count(1); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } // 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 @@ -8199,11 +8166,11 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp { ctrlpkt.pack(pkttype, NULL, data, losslen * 4); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.rcvr.sentNak.count(1); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } delete[] data; @@ -8229,7 +8196,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp case UMSG_CGWARNING: // 100 - Congestion Warning ctrlpkt.pack(pkttype); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); m_tsLastWarningTime = steady_clock::now(); @@ -8238,37 +8205,37 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp case UMSG_KEEPALIVE: // 001 - Keep-alive ctrlpkt.pack(pkttype); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_HANDSHAKE: // 000 - Handshake ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake)); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_SHUTDOWN: // 101 - Shutdown - if (m_PeerID == 0) // Don't send SHUTDOWN if we don't know peer ID. + if (m_PeerID == SRT_SOCKID_CONNREQ) // Don't send SHUTDOWN if we don't know peer ID. break; - ctrlpkt.pack(pkttype); + ctrlpkt.pack(pkttype, NULL, rparam, size); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_DROPREQ: // 111 - Msg drop request ctrlpkt.pack(pkttype, lparam, rparam, 8); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error ctrlpkt.pack(pkttype, lparam); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; @@ -8284,12 +8251,35 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp m_tsLastSndTime.store(steady_clock::now()); } -// [[using locked(m_RcvBufferLock)]] -bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) +bool CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) { +// Only with "new bonding" try to extract this information from the group. +// Otherwise stay with the usual per-socket check. +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + CUDTGroup* g = m_parent->m_GroupOf; + if (g) + { + return g->getFirstNoncontSequence((w_seq), (w_log_reason)); + } + // NOTE: AFTER making sure it's not a group member, check if it is not one + // because it is being currently closed. + if (m_bClosing || m_bBroken || m_bBreaking) + return false; +#endif + + SRT_ASSERT(!! m_pRcvBuffer); + if (!m_pRcvBuffer) + { + LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); + return false; + } if (m_config.bTSBPD || !m_config.bMessageAPI) { - // The getFirstNonreadSeqNo() function retuens the sequence number of the first packet + // NOTE: it's not only about protecting the buffer itself, it's also protecting + // the section where the m_iRcvCurrSeqNo is updated. + ScopedLock buflock (m_RcvBufferLock); + // The getFirstNonreadSeqNo() function returns the sequence number of the first packet // that cannot be read. In cases when a message can consist of several data packets, // an existing packet of partially available message also cannot be read. // If TSBPD mode is enabled, a message must consist of a single data packet only. @@ -8301,209 +8291,181 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) if (CSeqNo::seqcmp(w_seq, iNextSeqNo) > 0) { - LOGC(xtlog.Error, log << "IPE: NONCONT-SEQUENCE: RCV buffer first non-read %" << w_seq << ", RCV latest seqno %" << m_iRcvCurrSeqNo); + LOGC(xtlog.Error, log << "IPE: NONCONT-SEQUENCE: RCV buffer first non-read %" + << w_seq << ", RCV latest seqno %" << m_iRcvCurrSeqNo.load()); w_seq = iNextSeqNo; } return true; } - - { - 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"; - + + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "expected next"; + HLOGC(xtlog.Debug, log << CONID() << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); return true; } - -int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) +int CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { 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_HEAVY_LOGGING(local_prevack = m_iDebugPrevLastAck); - // 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); + // NOTE: the below calls do locking on m_RcvBufferLock. + // Hence up to the handling of lite ACK, the scoped lock is not applied. // 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); + const bool bNeedFullAck = (m_bBufferWasFull && !isRcvBufferFull()); int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). - + string reason; // just for "a reason" of giving particular % for ACK if (!getFirstNoncontSequence((ack), (reason))) return nbsent; + // Lock the group existence until this function ends. This will be useful + // also on other places. +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); + + // bonding : group buffering if member + const bool group_buffering = gkeeper.group; + + if (group_buffering) + { + // See note: "ACK and groups" in development notes. + if (gkeeper.group->type() == SRT_GTYPE_BACKUP) + { + // Lock the GlobControlLock to avoid consideration for a broken link. + SharedLock glk (uglobal().m_GlobControlLock); + + groups::SocketData* pd = m_parent->m_GroupMemberData; + if (!m_bOpened || m_bClosing || !pd) + return 0; + + if (pd->rcvstate != SRT_GST_RUNNING) + { + // Do not send ACK for non-RUNNING links + return 0; + } + + // So, check now if anything has arrived over THIS LINK since the last ACK. + int32_t pe_recv_seq = CSeqNo::incseq(m_iRcvCurrSeqNo); + if (CSeqNo::seqcmp(ack, pe_recv_seq) > 0) + { + if (CSeqNo::seqcmp(m_iRcvLastAck, pe_recv_seq) >= 0) + { + HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: grp/BACKUP: buf-ACK %" << ack + << " exceeds last-rcv %" << m_iRcvCurrSeqNo << " == last ack %" << m_iRcvLastAck + << " - NOT SENDING (considered pending IDLE)"); + return 0; + } + + HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: grp/BACKUP: buf-ACK %" << ack + << "exceeds last-rcv %" << m_iRcvCurrSeqNo << " %> last ack %" << m_iRcvLastAck + << " - sending FIXED ack %" << pe_recv_seq); + ack = pe_recv_seq; + } + } + } + +#else + // no bonding : no group buffering + const bool group_buffering = false; +#endif + + if (m_iRcvLastAckAck == ack && !bNeedFullAck) { - HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); - return nbsent; + HLOGC(xtlog.Debug, + log << CONID() << "sendCtrlAck: last ACK %" << ack << "(" << reason << ") == last ACKACK; NOT sending."); + 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.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); - DebugAck(CONID() + "sendCtrl(lite): ", local_prevack, ack); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + DebugAck(CONID() + "sendCtrlAck(lite): ", local_prevack, ack); return nbsent; } - // IF ack %> m_iRcvLastAck + int avail_receiver_buffer_size = 0; +#if SRT_ENABLE_BONDING + // NOTE: for a case when this was a single socket, this will be updated below + if (group_buffering) + avail_receiver_buffer_size = gkeeper.group->getAvailBufSize(ack); +#endif + // There are new received packets to acknowledge, update related information. + UniqueLock bufflock(m_RcvBufferLock); + + // IF ack %> m_iRcvLastAck 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)"); - } + 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 + IF_HEAVY_LOGGING(m_iDebugPrevLastAck = ack); 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) +#if SRT_ENABLE_BONDING + if (gkeeper.group) // Prevent unnecessary locking { - // 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. SharedLock glock (uglobal().m_GlobControlLock); - // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, - // but this is an intended order. + // UPDATE the IDLE links - BACKUP type only. 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. + // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, + // but this is an intended order. 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_bTsbPdNeedsWakeup) - tslcc.notify_one(); - } - else + // Signalling m_RecvDataCond is not done when TSBPD is on. + // "Big portion" readiness update with ACK is FILE mode only. + if (!m_bTsbPd) { { +#if SRT_ENABLE_BONDING + // DO NOT check nor enable reading when a group member - group member sockets are never ready to read. + // XXX This is for the case of a group connection that is not TSBPD; the same thing + // should be done in the group, if this socket is a member. + SRT_ASSERT( bool(m_parent->m_GroupOf) != bool(m_pRcvBuffer) ); + const bool canread = m_pRcvBuffer != NULL; +#else + const bool canread = true; +#endif + 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 (canread && isRcvBufferReady()) { if (m_config.bSynRecving) { - // signal a waiting "recv" call if there is any data available - rdcc.notify_one(); + rdcc.notify_one(); // blocking mode release the srt_recv* call } - // 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 - SharedLock 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(); } + // HERE m_RcvBufferLock is locked back } else if (ack == m_iRcvLastAck && !bNeedFullAck) { @@ -8512,15 +8474,15 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) (microseconds_from(m_iSRTT + 4 * m_iRTTVar))) { HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat"); + log << CONID() << "sendCtrlAck: 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(" << reason << ") %" - << ack << " <% last %" << m_iRcvLastAck); + LOGC(xtlog.Error, log << CONID() << "sendCtrlAck: IPE: curr(" << reason << ") %" << ack + << " <% last %" << m_iRcvLastAck); return nbsent; } @@ -8534,16 +8496,15 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // 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. + // NOTE: if (group_buffering), then this value was extracted already above + if (!group_buffering) + avail_receiver_buffer_size = (int) getAvailRcvBufferSizeNoLock(); + 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(); + data[ACKD_BUFFERLEFT] = avail_receiver_buffer_size; m_bBufferWasFull = data[ACKD_BUFFERLEFT] == 0; if (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval) { @@ -8579,97 +8540,100 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) ctrlpkt.set_id(m_PeerID); setPacketTS(ctrlpkt, steady_clock::now()); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); - DebugAck(CONID() + "sendCtrl(UMSG_ACK): ", local_prevack, ack); + nbsent = channel()->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + DebugAck(CONID() + "sendCtrlAck: ", local_prevack, ack); m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.rcvr.sentAck.count(1); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } else { - HLOGC(xtlog.Debug, log << CONID() << "sendCtrl(UMSG_ACK): " << "ACK %" << m_iRcvLastAck + HLOGC(xtlog.Debug, log << CONID() << "sendCtrlAck: " << "ACK %" << m_iRcvLastAck << " <=% ACKACK %" << m_iRcvLastAckAck << " - NOT SENDING ACK"); } return nbsent; } -void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) +bool CUDT::revokeACKedSequences(int32_t ackdata_seqno, int32_t& w_last_sent_seqno) { -#if ENABLE_BONDING - // This is for the call of CSndBuffer::getMsgNoAt that returns - // this value as a notfound-trap. + // NOTE: ackdata_seqno is the sequence number of the packet next to the last + // received one (past-the-end so to say). + w_last_sent_seqno = m_iSndCurrSeqNo; + + // See "Receiving ACK - safety considerations" in docs/dev/acknowledgement.md + // NOTE: If this is set to false, the link will get broken immediately. + bool valid_sndbuf_revoke = true; + +#if SRT_ENABLE_BONDING + // This is a cache of the value to be updated for BACKUP groups; this value + // is a trap value that declares no need to update. int32_t msgno_at_last_acked_seq = SRT_MSGNO_CONTROL; - bool is_group = m_parent->m_GroupOf; + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); #endif - // Update sender's loss list and acknowledge packets in the sender's buffer { // m_RecvAckLock protects sender's loss list and epoll ScopedLock ack_lock(m_RecvAckLock); - const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno); - // IF distance between m_iSndLastDataAck and ack is nonempty... - if (offset <= 0) - return; + // acknowledge the sending buffer (remove data that predate 'ack') + // False is returned if this seqno is already revoked. + CSndBuffer::RevokeStatus rvk = m_pSndBuffer->revoke(ackdata_seqno); + if (rvk == CSndBuffer::RVK_PAST) + { + HLOGP(inlog.Debug, "ACK from the past, not checking sender buffer"); + return true; // from the past, but still acceptable as received ACK + } + else if (rvk == CSndBuffer::RVK_ROGUE) + { + HLOGC(inlog.Debug, log << "EPE: ACK %" << ackdata_seqno << " with snd first %" + << m_pSndBuffer->firstSeqNo() << ": CRAZY!"); + return false; + } - // update sending variables - m_iSndLastDataAck = ackdata_seqno; + // Rephrase this - revoke() need not remove all up to ackdata_seqno + // if there are reserved cells by sender buffer. + // XXX SRT_ASSERT(m_pSndBuffer->firstSeqNo() == ackdata_seqno); -#if ENABLE_BONDING - if (is_group) +#if SRT_ENABLE_BONDING + if (gkeeper.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. + msgno_at_last_acked_seq = m_pSndBuffer->getMsgNoAtSeq(CSeqNo::decseq(ackdata_seqno)); } #endif - // remove any loss that predates 'ack' (not to be considered loss anymore) - 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 uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); CGlobEvent::triggerEvent(); } -#if ENABLE_BONDING - if (is_group) +#if SRT_ENABLE_BONDING + if (gkeeper.group) { - // m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only - // be done now that m_RecvAckLock is unlocked. + // m_RecvAckLock is ordered AFTER m_GlobControlLock SharedLock 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 + // ackMessage is also called in 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. + // NOTE: ackMessage also accepts and ignores SRT_MSGNO_CONTROL. m_parent->m_GroupOf->ackMessage(msgno_at_last_acked_seq); } } #endif + HLOGC(inlog.Debug, log << "ACK: kicking the send schedule/cond"); + // insert this socket to snd list if it is not on the list yet - const steady_clock::time_point currtime = steady_clock::now(); - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE, currtime); + const steady_clock::time_point currtime = m_pMuxer->updateSendNormal(m_parent); if (m_config.bSynSending) { @@ -8677,14 +8641,16 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) } // record total time used for sending - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndDuration += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter); m_stats.sndDurationCounter = currtime; - leaveCS(m_StatsLock); + m_StatsLock.unlock(); + + return valid_sndbuf_revoke; } -void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) +void 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_seqno = ackdata[ACKD_RCVLASTACK]; @@ -8697,16 +8663,28 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ // 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)"); + << " " << fmt(ackdata_seqno, hex) << " (IGNORED)"); return; } const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, - log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck - << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); - - updateSndLossListOnACK(ackdata_seqno); + log << CONID() << "ACK covers: " << m_pSndBuffer->firstSeqNo() << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck + << "]" << (isLiteAck ? "[LITE]" : "[FULL]") << " last-sent=%" << m_iSndCurrSeqNo); + + // last_sent_seqno is the value of m_iSndCurrSeqNo in general, + // but for multi-link groups (broadcast and balancing) this should + // use the value that is remembered in the group and represents the + // latest sequence sent for the group, no matter through which link + // it was sent. + int32_t last_sent_seqno; + if (!revokeACKedSequences(ackdata_seqno, (last_sent_seqno))) + { + LOGC(inlog.Error, log << "ACK: IPE/EPE: %" << ackdata_seqno << " considered rogue. BREAKING."); + m_bBroken = true; + m_iBrokenCounter = 0; + return; + } // Process a lite ACK if (isLiteAck) @@ -8716,49 +8694,53 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ { m_iFlowWindowSize = m_iFlowWindowSize - CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno); m_iSndLastAck = ackdata_seqno; - + m_iSndMinFlightSpan = getFlightSpan(); m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } return; } - // Decide to send ACKACK or not - { - // Sequence number of the ACK packet - const int32_t ack_seqno = ctrlpkt.getAckSeqNo(); + const int32_t ack_seqno = ctrlpkt.getAckSeqNo(); - // Send ACK acknowledgement (UMSG_ACKACK). - // 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. - if ((currtime - m_SndLastAck2Time > microseconds_from(COMM_SYN_INTERVAL_US)) || (ack_seqno == m_iSndLastAck2)) - { - sendCtrl(UMSG_ACKACK, &ack_seqno); - m_iSndLastAck2 = ack_seqno; - m_SndLastAck2Time = currtime; - } + // 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. + if ((currtime - m_SndLastAck2Time > microseconds_from(COMM_SYN_INTERVAL_US)) || (ack_seqno == m_iSndLastAck2)) + { + sendCtrl(UMSG_ACKACK, &ack_seqno); + m_iSndLastAck2 = ack_seqno; + m_SndLastAck2Time = currtime; } - // - // Begin of the new code with TLPKTDROP. - // - // Protect packet retransmission { UniqueLock ack_lock(m_RecvAckLock); - // Check the validation of the ack - if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) + // XXX The problem is that this lock was intended to protect also + // the value of m_iSndCurrSeqNo from being modified in the meantime. + // In the current implementation we need the value of either this, or + // a similar field in the group data, which carries the latest possible + // sent sequence number. As this was turned into a variable last_sent_seqno + // this can be now modified in between. + // + // This might be fixed here by simply taking an "offline" value from the + // group, while taking the latest of this value from socket and group, + // this time under a lock. + + if (CSeqNo::seqcmp(last_sent_seqno, m_iSndCurrSeqNo) < 0) + last_sent_seqno = m_iSndCurrSeqNo; + + if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(last_sent_seqno)) > 0) { ack_lock.unlock(); // 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) << "!"); + << last_sent_seqno << " by " << (CSeqNo::seqoff(last_sent_seqno, ackdata_seqno) - 1) << "! - BREAKING"); m_bBroken = true; m_iBrokenCounter = 0; + setAgentCloseReason(SRT_CLS_IPE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! @@ -8768,17 +8750,18 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow); - const bool bWasStuck = cwnd1<= getFlightSpan(); + 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_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ackdata_seqno; + m_iSndMinFlightSpan = getFlightSpan(); m_tsLastRspAckTime = currtime; - m_iReXmitCount = 1; // Reset re-transmit count since last ACK + m_iReXmitCount = 1; // Reset re-transmit count since last ACK const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); if (bWasStuck && cwnd > getFlightSpan()) { - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + m_pMuxer->updateSendNormal(m_parent); HLOGC(gglog.Debug, log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); @@ -8788,13 +8771,14 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ /* * 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). + * Therefore, a distinct ack state is used for received Ack (m_iSndLastFullAck) + * and ack position in send buffer (CSndBuffer::m_iSndLastDataAck). * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) * occurs, 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 ackdata_seqno %>= m_iSndLastFullAck if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) { // discard it if it is a repeated ACK @@ -8802,11 +8786,9 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ } m_iSndLastFullAck = ackdata_seqno; } - // - // END of the new code with TLPKTDROP - // -#if ENABLE_BONDING - if (m_parent->m_GroupOf) + +#if SRT_ENABLE_BONDING + if (!m_bClosing && m_parent->m_GroupOf) { SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) @@ -8850,9 +8832,9 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ { // Suppose transmission is bidirectional if sender is also receiving // data packets. - enterCS(m_StatsLock); + m_StatsLock.lock(); const bool bPktsReceived = m_stats.rcvr.recvd.total.count() != 0; - leaveCS(m_StatsLock); + m_StatsLock.unlock(); if (bPktsReceived) // Transmission is bidirectional. { @@ -8899,21 +8881,6 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ m_stats.recvTotal, m_iSRTT, m_iRTTVar); #endif - /* Version-dependent fields: - * Original UDT (total size: ACKD_TOTAL_SIZE_SMALL): - * ACKD_RCVLASTACK - * ACKD_RTT - * ACKD_RTTVAR - * ACKD_BUFFERLEFT - * Additional UDT fields, not always attached: - * ACKD_RCVSPEED - * ACKD_BANDWIDTH - * SRT extension since v1.0.1: - * ACKD_RCVRATE - * SRT extension in v1.0.2 only: - * ACKD_XMRATE_VER102_ONLY - */ - if (acksize > ACKD_TOTAL_SIZE_SMALL) { // This means that ACKD_RCVSPEED and ACKD_BANDWIDTH fields are available. @@ -8932,22 +8899,16 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ 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; - // ^^ This has been removed because with the SrtCongestion class - // instead of reading the m_iRcvRate local field this will read - // cudt->deliveryRate() instead. } updateCC(TEV_ACK, EventVariant(ackdata_seqno)); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndr.recvdAck.count(1); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } -void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) +void CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) { int32_t ack = 0; @@ -8961,7 +8922,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr string why; if (frequentLogAllowed(FREQLOGFA_ACKACK_OUTOFORDER, tsArrival, (why))) { - LOGC(inlog.Note, + HLOGC(inlog.Debug, log << CONID() << "ACKACK out of order, skipping RTT calculation " << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo << ", RTT (EWMA): " << m_iSRTT << ")." << why); @@ -9020,24 +8981,29 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr updateCC(TEV_ACKACK, EventVariant(ack)); + bool drift_updated_already = false; + +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gk (uglobal(), m_parent); + + // Group receiver in use - see if the drift update should + // be done in the group. If so, don't check anything in the socket + if (gk.group) + { + drift_updated_already = true; + gk.group->addGroupDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); + } +#endif + // 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) + if (!drift_updated_already && m_config.bDriftTracer && m_pRcvBuffer) { -#if ENABLE_BONDING - ExclusiveLock glock(uglobal().m_GlobControlLock); // XXX not too excessive? - const bool drift_updated = -#endif - m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); - -#if ENABLE_BONDING - if (drift_updated && m_parent->m_GroupOf) - m_parent->m_GroupOf->synchronizeDrift(this); -#endif + const bool drift_updated SRT_ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); } // Update last ACK that has been received by the sender @@ -9045,7 +9011,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr m_iRcvLastAckAck = ack; } -void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) +void CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData); const size_t losslist_len = ctrlpkt.getLength() / 4; @@ -9056,13 +9022,35 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // when logging is forcefully off. int32_t wrong_loss SRT_ATR_UNUSED = SRT_SEQNO_NONE; - // protect packet retransmission { +#if SRT_ENABLE_BONDING + // Keep the group from disappearing in the meantime + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); + typedef vector< pair > losses_t; + losses_t losses; +#else + // This is off in the new-bonding because + // with new-bonding we'll be only collecting the losses + // in the temporary container and then add them all to + // the right loss list, and only there the locking will + // be necessary. + // + // Note that below there are applications of the loss + // sequence done either WITH bonding, which require no + // locking, or WITHOUT bonding, which require locking. + // + // XXX Consider complete removal of the non-bonding + // alternative and apply the new bonding-friendly method + // to all cases. In this case this lock would be removed. + + // protect packet retransmission ScopedLock ack_lock(m_RecvAckLock); +#endif // decode loss list message and insert loss into the sender loss list for (int i = 0, n = (int)losslist_len; i < n; ++i) { + int num = 0; // For stats // IF the loss is a range if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) { @@ -9088,13 +9076,12 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) 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); + num = m_pSndBuffer->insertLoss(losslist_lo, losslist_hi, steady_clock::now()); } // ELSE losslist_lo %< m_iSndLastAck else @@ -9118,7 +9105,7 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding " << m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list"); - num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); + num = m_pSndBuffer->insertLoss(m_iSndLastAck, losslist_hi, steady_clock::now()); dropreq_hi = CSeqNo::decseq(m_iSndLastAck); IF_HEAVY_LOGGING(drop_type = "partially"); } @@ -9138,9 +9125,9 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair)); } - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndr.lost.count(num); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } // ELSE the loss is a single seq else @@ -9160,11 +9147,11 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding %" << losslist[i] << " (1 packet) to loss list"); - const int num = m_pSndLossList->insert(losslist[i], losslist[i]); + num = m_pSndBuffer->insertLoss(losslist[i], losslist[i], steady_clock::now()); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndr.lost.count(num); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } // ELSE loss_seq %< m_iSndLastAck else @@ -9188,10 +9175,11 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { LOGC(inlog.Warn, log << CONID() << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo - << " vs loss %" << wrong_loss); + << " vs loss %" << wrong_loss << " - BREAKING"); // this should not happen: attack or bug m_bBroken = true; m_iBrokenCounter = 0; + setAgentCloseReason(SRT_CLS_ROGUE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! @@ -9199,14 +9187,24 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) } // the lost packet (retransmission) should be sent out immediately - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); - enterCS(m_StatsLock); + // NOTE: There was a change here towards UDT; update was done as "Fast" + // because for a loss it should have been qualified as high priority. + // But in the live mode with too many lost packets it could result in + // holding head of the line for too long, while in file mode it doesn't + // really improve the transmission efficiency (because it doesn't use + // NAKREPORT, so the receiver won't repeat lossreport command), and the + // blind rexmit mode is laterexmit (the sender will repeat sending + // unacknowledged packets only when it has reached the limit with + // nothing more to withdraw from the sender buffer). + m_pMuxer->updateSendNormal(m_parent); + + m_StatsLock.lock(); m_stats.sndr.recvdNak.count(1); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); } -void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) +void CUDT::processCtrlHS(const CPacket& ctrlpkt) { CHandShake req; req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); @@ -9224,34 +9222,34 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) // - 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; + CHandShake tosend_hs; + tosend_hs.m_iISN = m_iISN; + tosend_hs.m_iMSS = m_config.iMSS; + tosend_hs.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; + tosend_hs.m_iReqType = (!m_config.bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT; + tosend_hs.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... + tosend_hs.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener... const int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType); if (hs_flags != 0) // has SRT extensions { HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType) << " WITH SRT ext"); - have_hsreq = interpretSrtHandshake(req, ctrlpkt, (kmdata), (&kmdatasize)); + have_hsreq = interpretSrtHandshake(NULL, req, ctrlpkt, (kmdata), (&kmdatasize)); if (!have_hsreq) { - initdata.m_iVersion = 0; + tosend_hs.m_iVersion = 0; m_RejectReason = SRT_REJ_ROGUE; - initdata.m_iReqType = URQFailure(m_RejectReason); + tosend_hs.m_iReqType = URQFailure(m_RejectReason); } else { @@ -9270,9 +9268,9 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) // 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; + have_hsreq = tosend_hs.m_iReqType == URQ_CONCLUSION; HLOGC(inlog.Debug, - log << CONID() << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType) + log << CONID() << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(tosend_hs.m_iReqType) << " kmdatasize=" << kmdatasize); } } @@ -9283,14 +9281,14 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) } else { - initdata.m_iVersion = HS_VERSION_UDT4; + tosend_hs.m_iVersion = HS_VERSION_UDT4; kmdatasize = 0; // HSv4 doesn't add any extensions, no KMX } - initdata.m_extension = have_hsreq; + tosend_hs.m_extensionType = have_hsreq ? SRT_CMD_HSRSP : 0; HLOGC(inlog.Debug, - log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType) + log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(tosend_hs.m_iReqType) << (have_hsreq ? " WITH SRT HS response extensions" : "")); CPacket rsppkt; @@ -9299,14 +9297,14 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) // 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. - enterCS(m_ConnectionLock); - bool create_ok = createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (initdata)); - leaveCS(m_ConnectionLock); + m_ConnectionLock.lock(); + bool create_ok = createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (tosend_hs)); + m_ConnectionLock.unlock(); if (create_ok) { rsppkt.set_id(m_PeerID); setPacketTS(rsppkt, steady_clock::now()); - const int nbsent = m_pSndQueue->sendto(m_PeerAddr, rsppkt, m_SourceAddr); + const int nbsent = channel()->sendto(m_PeerAddr, rsppkt, m_SourceAddr); if (nbsent) { m_tsLastSndTime.store(steady_clock::now()); @@ -9319,10 +9317,38 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) } } -void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) +void CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { + typedef int32_t expected_t[2]; + if (ctrlpkt.getLength() < sizeof (expected_t)) + { + // We ALLOW packets that are bigger than this to allow + // future extensions, this just interprets the part that + // is expected, and reject only those that don't carry + // even the required data. + LOGC(brlog.Error, log << CONID() << "EPE: Wrong size of the DROPREQ message: " << ctrlpkt.getLength() + << " - expected >=" << sizeof(expected_t)); + return; + } + + int32_t msgno = ctrlpkt.getMsgSeq(m_bPeerRexmitFlag); + + // Check for rogue message + if (msgno == SRT_MSGNO_NONE) + { + LOGC(brlog.Warn, log << CONID() << "ROGUE DROPREQ detected with #NONE - fallback: fixing to #CONTROL"); + msgno = SRT_MSGNO_CONTROL; + } + const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; +#if SRT_ENABLE_BONDING + + // NOTE: a connected socket that once had a buffer cannot + // lose it before being closed. An unconnected socket (including broken) + // cannot be dispatched the UMSG_DROPREQ message to. + if (!m_parent->m_GroupOf && m_pRcvBuffer) +#endif { CUniqueSync rcvtscc (m_RecvLock, m_RcvTsbPdCond); // With both TLPktDrop and TsbPd enabled, a message always consists only of one packet. @@ -9331,9 +9357,8 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) // 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); + const int iDropCnt = m_pRcvBuffer->dropMessage(dropdata[0], dropdata[1], msgno, CRcvBuffer::KEEP_EXISTING); if (iDropCnt > 0) { @@ -9343,7 +9368,7 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) if (frequentLogAllowed(FREQLOGFA_RCV_DROPPED, tnow, (why))) { LOGC(brlog.Warn, log << CONID() << "RCV-DROPPED " << iDropCnt << " packet(s), seqno range %" - << dropdata[0] << "-%" << dropdata[1] << ", msgno " << ctrlpkt.getMsgSeq(using_rexmit_flag) + << dropdata[0] << "-%" << dropdata[1] << ", #" << ctrlpkt.getMsgSeq(m_bPeerRexmitFlag) << " (SND DROP REQUEST). " << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE @@ -9358,14 +9383,27 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); } } - // When the drop request was received, it means that there are - // packets for which there will never be ACK sent; if the TSBPD thread - // is currently in the ACK-waiting state, it may never exit. - if (m_bTsbPd) - { - HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); - rcvtscc.notify_one(); - } + + // NOTE: + // PREVIOUSLY done: notify on rcvtscc if m_bTsbPd + // OLD COMMENT: + // // 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. + // + // Likely this is no longer necessary because: + // + // 1. If there's a play-ready packet, either in cell 0 or + // after a drop, TSBPD is sleeping timely, up to the play-time + // of the next ready packet (and the drop region concerned here + // is still a gap to be skipped for this). + // 2. TSBPD sleeps forever when the buffer is empty, in which case + // it will be always woken up on packet reception (see m_bTsbPdNeedsWakeup). + // And dropping won't happen in that case anyway. Note that the drop + // request will not drop packets that are already received. + // 3. TSBPD sleeps forever when the API call didn't extract the + // data that are ready to play. This isn't a problem if nothing + // except the API call would wake it up. } dropFromLossLists(dropdata[0], dropdata[1]); @@ -9386,8 +9424,31 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) } } -void srt::CUDT::processCtrlShutdown() +void CUDT::processCtrlShutdown(const CPacket& ctrlpkt) { + const uint32_t* data = (const uint32_t*) ctrlpkt.m_pcData; + const size_t data_len = ctrlpkt.getLength() / 4; + + int reason = 0; + + // This condition should be ALWAYS satisfied, it's only + // a sanity check before reading the data. Versions that + // do not support close reason will simply send 0 here because + // it's the padding 0 that is provided in every command + // that is not expected to carry any "body". It is acceptable + // that the old versions simply send 0 here, but then you + // can't have the UNKNOWN value in any of close reason + // fields because it means that it wasn't set. + if (data_len > 0) + { + reason = data[0]; + } + + if (reason == 0) + { + setPeerCloseReason(SRT_CLS_FALLBACK); + } + m_bShutdown = true; m_bClosing = true; m_bBroken = true; @@ -9399,7 +9460,7 @@ void srt::CUDT::processCtrlShutdown() completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! } -void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) +void CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) @@ -9429,17 +9490,20 @@ void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) } } -void srt::CUDT::processCtrl(const CPacket &ctrlpkt) +void 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; + m_tsLastRspTime = currtime; // XXX Requires lock m_RecvAckLock? HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.id()); + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) + << ") socket=@" << ctrlpkt.id() + << " arg=" << ctrlpkt.getAckSeqNo() << "/0x" << fmt(ctrlpkt.getAckSeqNo(), hex)); + // XXX [TSA] This function may need to lock m_ConnectionLock switch (ctrlpkt.getType()) { case UMSG_ACK: // 010 - Acknowledgement @@ -9473,7 +9537,7 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) break; case UMSG_SHUTDOWN: // 101 - Shutdown - processCtrlShutdown(); + processCtrlShutdown(ctrlpkt); break; case UMSG_DROPREQ: // 111 - Msg drop request @@ -9499,8 +9563,19 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) } } -void srt::CUDT::updateSrtRcvSettings() +// Called only for the old buffer with groups (XXX so might be it's not necessary) +void CUDT::updateSrtRcvSettings() { +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + + if (gk.group) + { + // TSBPD mode in case of groups will be set during syncWithFirstSocket. + return; + } + +#endif // 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 @@ -9509,12 +9584,12 @@ void srt::CUDT::updateSrtRcvSettings() // (unlike in normal situation when reading directly from socket), however // its time to play shall be properly defined. ScopedLock lock(m_RecvLock); + // XXX ALSO: m_RcvBufferLock ??? // 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) + if (m_bTsbPd) { m_pRcvBuffer->setTsbPdMode(m_tsRcvPeerStartTime, false, milliseconds_from(m_iTsbPdDelay_ms)); @@ -9529,7 +9604,7 @@ void srt::CUDT::updateSrtRcvSettings() } } -void srt::CUDT::updateSrtSndSettings() +void CUDT::updateSrtSndSettings() { if (m_bPeerTsbPd) { @@ -9551,7 +9626,7 @@ void srt::CUDT::updateSrtSndSettings() } } -void srt::CUDT::updateAfterSrtHandshake(int hsv) +void CUDT::updateAfterSrtHandshake(int hsv) { HLOGC(cnlog.Debug, log << CONID() << "updateAfterSrtHandshake: HS version " << hsv); // This is blocked from being run in the "app reader" version because here @@ -9568,16 +9643,15 @@ void srt::CUDT::updateAfterSrtHandshake(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 +#if HVU_ENABLE_HEAVY_LOGGING +#if SRT_ENABLE_BONDING string grpspec; if (m_parent->m_GroupOf) { SharedLock glock (uglobal().m_GlobControlLock); grpspec = m_parent->m_GroupOf - ? " group=$" + Sprint(m_parent->m_GroupOf->id()) + ? fmtcat(" group=$", m_parent->m_GroupOf->id()) : string(); } #else @@ -9586,7 +9660,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) HLOGC(cnlog.Debug, log << CONID() << "updateAfterSrtHandshake: version=" << m_ConnRes.m_iVersion - << " side=" << hs_side[m_SrtHsSide] << grpspec); + << " side=" << s_hs_side[m_SrtHsSide] << grpspec); #endif if (hsv > HS_VERSION_UDT4) @@ -9606,213 +9680,72 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) } } -// XXX During sender buffer refax turn this into either returning -// a sequence number or move it to the sender buffer facility. -// [[using locked (m_RecvAckLock)]] -std::pair srt::CUDT::getCleanRexmitOffset() +bool CUDT::retransmissionRateFit(size_t granted_size) { - // This function is required to look into the loss list and - // drop all sequences that are already revoked from the sender - // buffer, send the drop request if needed, and return the sender - // buffer offset for the next packet to retransmit, or -1 if - // there is no retransmission candidate at the moment. - - for (;;) - { - // REPEATABLE because we might need to drop this scheduled seq. - - // Pick up the first sequence. - int32_t seq = m_pSndLossList->popLostSeq(); - if (seq == SRT_SEQNO_NONE) - { - // No loss reports, we are clear here. - return std::make_pair(SRT_SEQNO_NONE, -1); - } - - int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); - if (offset < 0) - { - // 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(qslog.Error, log << CONID() - << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() " - << seq << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset - << ". Continue, request DROP"); - - int32_t oldest_outdated_seq = seq; - // We have received the first outdated sequence. - // Loop on to find out more. - // Interrupt on the first non-outdated sequence - // or when the loss list gets empty. - - for (;;) - { - seq = m_pSndLossList->popLostSeq(); - if (seq == SRT_SEQNO_NONE) - { - offset = -1; // set it because this will be returned. - break; - } - - offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); - if (offset >= 0) - break; - } - - // Now we have 'seq' that is either non-sequence - // or a valid sequence. We can safely exit with this - // value from this branch. We just need to manage - // the drop report. - - // 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] = { - oldest_outdated_seq, - CSeqNo::decseq(m_iSndLastDataAck) - }; - - HLOGC(qslog.Debug, - log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" - << seqpair[0] << " - " << seqpair[1] << ") (" << (-offset) << " packets)"); - - // See interpretation in processCtrlDropReq(). We don't know the message number, - // so we request that the drop be exclusively sequence number based. - int32_t msgno = SRT_MSGNO_CONTROL; - sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); - } - - // We have exit anyway with the right sequence or no sequence. - if (seq == SRT_SEQNO_NONE) - return std::make_pair(SRT_SEQNO_NONE, -1); +#ifdef SRT_ENABLE_MAXREXMITBW + bool maxrexmitbw_enabled = m_config.llMaxRexmitBW >= 0; + if (!maxrexmitbw_enabled) + return true; - // For the right sequence though check also the right time. - const steady_clock::time_point time_now = steady_clock::now(); - if (!checkRexmitRightTime(offset, time_now)) - { - // Ignore this, even though valid, sequence, and pick up - // the next from the list. - continue; - } +#ifdef SRT_ENABLE_RATE_MEASUREMENT +#define IF_RATE_MEA(expr) expr +#else +#define IF_RATE_MEA(expr) +#endif - // Ok, we have what we needed one way or another. - return std::make_pair(seq, offset); - } -} + if (granted_size == 0) + granted_size = m_zSndAveragePacketSize; -// [[using locked (m_RecvAckLock)]] -bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time) -{ - // If not configured, the time is always right - if (!m_bPeerNakReport || m_config.iRetransmitAlgo == 0) - return true; + IF_RATE_MEA( IF_HEAVY_LOGGING(const int64_t iRexmitRateMeasured = m_SndRexmitMeasurement.rateBytes()); ) (void)0; - const steady_clock::time_point time_nak = current_time - microseconds_from(m_iSRTT - 4 * m_iRTTVar); - const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); + size_t len = granted_size + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); - if (tsLastRexmit >= time_nak) + if (!m_SndRexmitShaper.enoughTokens(len)) { -#if ENABLE_HEAVY_LOGGING - ostringstream last_rextime; - if (is_zero(tsLastRexmit)) - { - last_rextime << "REXMIT FIRST TIME"; - } - else - { - last_rextime << "REXMITTED " << FormatDuration(current_time - tsLastRexmit) - << " ago at " << FormatTime(tsLastRexmit); - } - HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << CSeqNo::incseq(m_iSndLastDataAck, offset) - << " " << last_rextime.str() - << ", RTT: ~" << FormatValue(m_iSRTT, 1000, "ms") - << " <>" << FormatValue(m_iRTTVar, 1000, "") - << " now=" << FormatTime(current_time)); -#endif + HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED pkt est/len=" << len << " exceeds " << m_SndRexmitShaper.ntokens() + << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() + IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) + ); return false; } - return true; -} - - -// [[using locked (m_RecvAckLock)]] -int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, - srt::sync::steady_clock::time_point& w_tsOrigin) -{ - // REPEATABLE BLOCK (not a real loop) - for (;;) - { - typedef CSndBuffer::DropRange DropRange; - DropRange buffer_drop; - w_packet.set_seqno(seqno); - - const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_tsOrigin), (buffer_drop)); - if (payload == CSndBuffer::READ_DROP) - { - SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); - - HLOGC(qslog.Debug, - log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #" - << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " - << buffer_drop.seqno[DropRange::END] << ")"); - sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno)); - - // skip all dropped packets - m_pSndLossList->removeUpTo(buffer_drop.seqno[DropRange::END]); - m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); - continue; - } - - if (payload == CSndBuffer::READ_NONE) - { - LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << w_packet.seqno() << " NOT FOUND in the sender buffer"); - return 0; - } - - return payload; - } + HLOGC(qslog.Debug, log << "REXMIT-SH: ALLOWED pkt est/len=" << len << " allowed, budget " << m_SndRexmitShaper.ntokens() + << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() + IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) + ); +#undef IF_RATE_MEA +#else + (void) granted_size; // fake use +#endif + return true; } -int srt::CUDT::packLostData(CPacket& w_packet) +void CUDT::retransmissionConsumeLength(size_t payload_size) { -#if ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW + bool maxrexmitbw_enabled = m_config.llMaxRexmitBW >= 0; + if (!maxrexmitbw_enabled) + return; -#ifdef ENABLE_RATE_MEASUREMENT -#define IF_RATE_MEA(expr) expr + // Token consumption will only happen when the retransmission + // effectively happens. + size_t network_size = payload_size + CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion); + m_SndRexmitShaper.consumeTokens(network_size); + HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens()); #else -#define IF_RATE_MEA(expr) + (void) payload_size; // fake use #endif +} - // XXX NOTE: If you refactor the sender buffer so that the random access - // is possible, you might be able to extract the exact packet size to - // check for enough tokens and consume them in one step. - - bool maxrexmitbw_enabled = m_config.llMaxRexmitBW >= 0; - if (maxrexmitbw_enabled) - { - IF_RATE_MEA( IF_HEAVY_LOGGING(const int64_t iRexmitRateMeasured = m_SndRexmitMeasurement.rateBytes()); ) - size_t granted_size = m_zSndAveragePacketSize; - size_t len = granted_size + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; - - if (!m_SndRexmitShaper.enoughTokens(len)) - { - HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED pkt est/len=" << len << " exceeds " << m_SndRexmitShaper.ntokens() - << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() - IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) - ); - return 0; - } - - HLOGC(qslog.Debug, log << "REXMIT-SH: ALLOWED pkt est/len=" << len << " allowed, budget " << m_SndRexmitShaper.ntokens() - << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() - IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) - ); - } - -#undef IF_RATE_MEA +int CUDT::packLostData(CSndPacket& w_sndpkt) +{ +#ifdef SRT_ENABLE_MAXREXMITBW + // XXX Here we use 0 so that it picks up the average sent packet size. + // Consider adding a functionality to m_pSndBuffer to get the real size + // of the next retransmission candidate. + if (!retransmissionRateFit(0)) + return 0; #endif // Original sending time of the packet, to be stamped after filling. @@ -9821,53 +9754,64 @@ int srt::CUDT::packLostData(CPacket& w_packet) { // protect m_iSndLastDataAck from updating by ACK processing ScopedLock ackguard(m_RecvAckLock); - int32_t seqno; - int offset; + typedef CSndBuffer::DropRange DropRange; - // Get the first sequence for retransmission, bypassing and taking care of - // those that are in the forgotten region, as well as required to be rejected. - Tie2(seqno, offset) = getCleanRexmitOffset(); + std::vector drops; - if (seqno == SRT_SEQNO_NONE) - return 0; + int32_t snd_curr_seqno_proxy = SRT_SEQNO_NONE; // May need to skip all TTL-expired drops + const int payload = m_pSndBuffer->extractFirstRexmitPacket(minRexmitInterval(), (snd_curr_seqno_proxy), (w_sndpkt), (tsOrigin), (drops)); + if (snd_curr_seqno_proxy != SRT_SEQNO_NONE) + m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, snd_curr_seqno_proxy); + + if (!drops.empty()) + { + for (size_t i = 0; i < drops.size(); ++i) + { + CSndBuffer::DropRange& buffer_drop = drops[i]; + int32_t seqpair[2] = { + buffer_drop.seqno[DropRange::BEGIN], + buffer_drop.seqno[DropRange::END] + }; + + HLOGC(qslog.Debug, + log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" + << seqpair[0] << " - " << seqpair[1] << ")"); + + // See interpretation in processCtrlDropReq(). We don't know the message number, + // so we request that the drop be exclusively sequence number based. + int32_t msgno = SRT_MSGNO_CONTROL; + sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); + } + } - // Extract the packet from the sender buffer that is mapped to the expected sequence - // number, bypassing and taking care of those that are decided to be dropped. - const int payload = extractCleanRexmitPacket(seqno, offset, (w_packet), (tsOrigin)); if (payload <= 0) return 0; } + CPacket& w_packet = w_sndpkt.pkt; + HLOGC(qslog.Debug, log << CONID() << "packed REXMIT packet %" << w_packet.seqno() << " size=" << w_packet.getLength() - << " - still " << m_pSndLossList->getLossLength() << " LOSS ENTRIES left"); + << " - still " << m_pSndBuffer->getLossLength() << " LOSS ENTRIES left"); // POST-EXTRACTION length fixes. // 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) + if (m_CryptoControl.getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) { w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX); } -#if ENABLE_MAXREXMITBW - if (maxrexmitbw_enabled) - { - // Token consumption will only happen when the retransmission - // effectively happens. - // XXX NOTE: In version 1.6.0 use the IP-version dependent value for UDP_HDR_SIZE - size_t network_size = w_packet.getLength() + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; - m_SndRexmitShaper.consumeTokens(network_size); - HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens()); - } +#ifdef SRT_ENABLE_MAXREXMITBW + retransmissionConsumeLength(w_packet.getLength()); #endif - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndr.sentRetrans.count(w_packet.getLength()); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); // Despite the contextual interpretation of packet.m_iMsgNo around - // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular + // CSndBuffer::readOldPacket (extractUniquePacket doesn't return -1), in this particular // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. // So, set here the rexmit flag if the peer understands it. if (m_bPeerRexmitFlag) @@ -9876,14 +9820,7 @@ int srt::CUDT::packLostData(CPacket& w_packet) } setDataPacketTS(w_packet, tsOrigin); -#ifdef ENABLE_MAXREXMITBW - // XXX OLD rexmit measurement - // m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength()); -#endif - - // XXX Consider calculating the total packet length once you have a possibility - // to get the in-connection used IP version. -#ifdef ENABLE_RATE_MEASUREMENT +#ifdef SRT_ENABLE_RATE_MEASUREMENT m_SndRexmitMeasurement.dataUpdate(1, w_packet.getLength()); HLOGC(bslog.Debug, log << "RateMeasurement: REXMIT, pkt-size=" << w_packet.getLength() << " - COLLECTED pkts=" << m_SndRexmitMeasurement.m_nInstaPackets @@ -9896,7 +9833,7 @@ int srt::CUDT::packLostData(CPacket& w_packet) #if SRT_DEBUG_TRACE_SND class snd_logger { - typedef srt::sync::steady_clock steady_clock; + typedef sync::steady_clock steady_clock; public: snd_logger() {} @@ -9909,7 +9846,7 @@ class snd_logger struct { - typedef srt::sync::steady_clock steady_clock; + typedef sync::steady_clock steady_clock; long long usElapsed; steady_clock::time_point tsNow; int usSRTT; @@ -9925,7 +9862,7 @@ class snd_logger void trace() { - using namespace srt::sync; + using namespace sync; ScopedLock lck(m_mtx); create_file(); @@ -9955,8 +9892,8 @@ class snd_logger if (m_fout.is_open()) return; - m_start_time = srt::sync::steady_clock::now(); - std::string str_tnow = srt::sync::FormatTimeSys(m_start_time); + m_start_time = sync::steady_clock::now(); + std::string str_tnow = sync::FormatTimeSys(m_start_time); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part while (str_tnow.find(':') != std::string::npos) { @@ -9971,36 +9908,44 @@ class snd_logger } private: - srt::sync::Mutex m_mtx; + sync::Mutex m_mtx; std::ofstream m_fout; - srt::sync::steady_clock::time_point m_start_time; + 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) +void CUDT::setPacketTS(CPacket& p, const time_point& ts) { - enterCS(m_StatsLock); + SRT_ASSERT(!is_zero(ts)); + m_StatsLock.lock(); const time_point tsStart = m_stats.tsStartTime; - leaveCS(m_StatsLock); - p.set_timestamp(makeTS(ts, tsStart)); + m_StatsLock.unlock(); + setPacketTS(p, tsStart, ts); } -void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) +void CUDT::setDataPacketTS(CPacket& p, const time_point& ts) { - enterCS(m_StatsLock); + m_StatsLock.lock(); const time_point tsStart = m_stats.tsStartTime; - leaveCS(m_StatsLock); + m_StatsLock.unlock(); if (!m_bPeerTsbPd) { // If TSBPD is disabled, use the current time as the source (timestamp using the sending time). - p.set_timestamp(makeTS(steady_clock::now(), tsStart)); + setPacketTS(p, tsStart, steady_clock::now()); return; } + SRT_ASSERT(!sync::is_zero(ts)); + // TODO: Might be better for performance to ensure this condition is always false, and just use SRT_ASSERT here. + // XXX The condition for having ts always in the future towards tsStart + // can be ensured at the scheduling time, that is, the only possibility exists + // that a user supply a timestamp that is in the past towards the start time. + // The sending function already rejects such a sending request and reports an error. + // When this is rejected, there's no way that ts < tsStart. if (ts < tsStart) { p.set_timestamp(makeTS(steady_clock::now(), tsStart)); @@ -10012,37 +9957,16 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) } // Use the provided source time for the timestamp. - p.set_timestamp(makeTS(ts, tsStart)); + setPacketTS(p, tsStart, ts); } -bool srt::CUDT::isRegularSendingPriority() +// See docs/dev/retransmission.md for details. +bool CUDT::isRegularSendingPriority() { - // In order to have regular packets take precedence over retransmitted: - // - SRTO_TLPKTDROP = true - // - SRTO_MESSAGEAPI = true - // NOTE: - // - tlpktdrop is ignored in stream mode - // - messageapi without tlpktdrop is possible in non-live message mode - // - Live mode without tlpktdrop is possible and in this mode also the - // retransmitted packets should have priority. if (!m_bPeerTLPktDrop || !m_config.bMessageAPI) return false; - // XXX NOTE: the current solution is simple - the regular packet takes precedence - // over a retransmitted packet, if there is at least one such packet already - // scheduled. - // - // This probably isn't the most wanted solution, some more elaborate condition - // might be better in some situations. For example, it should be acceptable - // that a packet that has very little time to be recovered is sent before a - // regular packet that has still STT + Latency time to deliver. The regular - // packets should still be favored, but not necessarily at the expense of - // dismissing a recovery chance that wouldn't endanger the delivery of a regular - // packet. Criteria might be various, for example, the number of scheduled - // packets and their late delivery time might be taken into account. - const time_point tsNextPacket = m_pSndBuffer->peekNextOriginal(); - - if (tsNextPacket != time_point()) + if (m_tsSndNextUnique.load() != time_point()) { // Have regular packet and we decided they have a priority. HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED because regular packets have priority"); @@ -10051,8 +9975,7 @@ bool srt::CUDT::isRegularSendingPriority() return false; } -namespace srt { -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW static sync::steady_clock::duration optimisticSingleTripDuration(int rttval, int rttvar) { int lowrtt = rttval - rttvar; @@ -10072,9 +9995,9 @@ static sync::steady_clock::duration optimisticSingleTripDuration(int rttval, int return sync::steady_clock::duration(); } #endif -} -void srt::CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) + +void CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) { const time_point tnow SRT_ATR_UNUSED = steady_clock::now(); @@ -10084,9 +10007,10 @@ void srt::CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) // 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; + const time_point start_time = m_stats.tsStartTime; g_snd_logger.state.tsNow = tnow; - g_snd_logger.state.usElapsed = count_microseconds(tnow - m_stats.tsStartTime); + g_snd_logger.state.usElapsed = count_microseconds(tnow - start_time); g_snd_logger.state.usSRTT = m_iSRTT; g_snd_logger.state.usRTTVar = m_iRTTVar; g_snd_logger.state.msSndBuffSpan = buffdelay_ms; @@ -10096,7 +10020,7 @@ void srt::CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) g_snd_logger.state.canRexmit = can_rexmit; #endif -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW /* OLD rate estimator; CODE for packLostData()! m_SndRexmitRate.addSample(tnow, 0, 0); // Update the estimation. const int64_t iRexmitRateBps = m_SndRexmitRate.getRate(); @@ -10126,7 +10050,7 @@ void srt::CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) } -bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, sockaddr_any& w_src_addr) +bool CUDT::packData(CSndPacket& w_sndpkt, steady_clock::time_point& w_nexttime, CNetworkInterface& w_src_addr) { int payload = 0; bool probe = false; @@ -10134,6 +10058,11 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime const steady_clock::time_point enter_time = steady_clock::now(); +#if SRT_ENABLE_BONDING && BROADCAST_COMMON_SND_LOSS + // Prevent the group from deletion, if any. + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); +#endif + w_nexttime = enter_time; if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime) @@ -10156,8 +10085,8 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // for being sent and sending this packet has a prioriry over retransmission candidate. if (!isRegularSendingPriority()) { - payload = packLostData((w_packet)); -#if ENABLE_MAXREXMITBW + payload = packLostData((w_sndpkt)); +#ifdef SRT_ENABLE_MAXREXMITBW if (payload == 0) { HLOGC(qslog.Debug, log << "REXMIT-SH: no rexmit required, remain " << m_SndRexmitShaper.ntokens() << " tokens"); @@ -10172,32 +10101,38 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // Updates the data that will be next used in packLostData() in next calls. updateSenderMeasurements(payload != 0); + CPacket& w_packet = w_sndpkt.pkt; + IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) if (payload > 0) { IF_HEAVY_LOGGING(reason = "reXmit"); } - else if (m_PacketFilter && - m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags(), (w_packet))) + else if (m_PacketFilter && // XXX m_iSndCurrSeqNo requires locking m_RcvAckLock, although it's only modified in Snd thread + m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_CryptoControl.getSndCryptoFlags(), (w_sndpkt.pkt))) { HLOGC(qslog.Debug, log << CONID() << "filter: filter/CTL packet ready - packing instead of data."); - payload = (int) w_packet.getLength(); + payload = (int) w_sndpkt.pkt.getLength(); IF_HEAVY_LOGGING(reason = "filter"); + // just in case - w_sndpkt does not refer to any reserved packet in the sender buffer + w_sndpkt.srcbuf = NULL; + w_sndpkt.seqno = SRT_SEQNO_NONE; + // Stats ScopedLock lg(m_StatsLock); m_stats.sndr.sentFilterExtra.count(1); } else { - if (!packUniqueData(w_packet)) + if (!packUniqueData((w_sndpkt))) { m_tsNextSendTime = steady_clock::time_point(); - m_tdSendTimeDiff = steady_clock::duration(); + m_tdSendTimeDiff = steady_clock::duration::zero(); return false; } -#if ENABLE_MAXREXMITBW +#if SRT_ENABLE_MAXREXMITBW if (m_zSndAveragePacketSize > 0) { m_zSndAveragePacketSize = avg_iir<16>(m_zSndAveragePacketSize, w_packet.getLength()); @@ -10226,10 +10161,10 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime m_PacketFilter.feedSource((w_packet)); } -#if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() +#if HVU_ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, - log << CONID() << "packData: " << reason << " packet seq=" << w_packet.seqno() << " (ACK=" << m_iSndLastAck - << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); + log << CONID() << "packData: " << reason << " packet %" << w_packet.seqno() << " (ACK=%" << m_iSndLastAck + << " ACKDATA=%" << m_pSndBuffer->firstSeqNo() << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif // Fix keepalive @@ -10249,11 +10184,13 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // different thread than the rest of the signals. // m_pSndTimeWindow->onPktSent(w_packet.timestamp()); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.sndr.sent.count(payload); if (new_packet_packed) m_stats.sndr.sentUnique.count(payload); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); + + IF_HEAVY_LOGGING(std::string nexttime_reason); const duration sendint = m_tdSendInterval; if (probe) @@ -10263,11 +10200,13 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // Sending earlier, need to adjust the pace later on. m_tdSendTimeDiff = m_tdSendTimeDiff.load() - sendint; probe = false; + IF_HEAVY_LOGGING(nexttime_reason = "probe-pair"); } else { -#if USE_BUSY_WAITING +#if SRT_BUSY_WAITING m_tsNextSendTime = enter_time + m_tdSendInterval.load(); + IF_HEAVY_LOGGING(nexttime_reason = "busy-waiting"); #else const duration sendbrw = m_tdSendTimeDiff; @@ -10279,25 +10218,27 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // ATOMIC NOTE: this is the only thread that // modifies this field m_tdSendTimeDiff = sendbrw - sendint; + IF_HEAVY_LOGGING(nexttime_reason = "immediate(undertime " + FormatDurationAuto(m_tdSendTimeDiff) + ")"); } else { m_tsNextSendTime = enter_time + (sendint - sendbrw); m_tdSendTimeDiff = duration(); + IF_HEAVY_LOGGING(nexttime_reason = "delayed(remain " + FormatDurationAuto(sendint - sendbrw) + ")"); } #endif } - HLOGC(qslog.Debug, log << "packData: Setting source address: " << m_SourceAddr.str()); + HLOGC(qslog.Debug, log << "packData: src.addr=" << m_SourceAddr.str() << " next.snd.time=" + << FormatTime(m_tsNextSendTime) << " reason=" << nexttime_reason); 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) +bool CUDT::packUniqueData(CSndPacket& w_sndpkt) { int current_sequence_number; // reflexing variable - int kflg; time_point tsOrigin; int pld_size; @@ -10309,43 +10250,36 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) if (cwnd <= flightspan) { HLOGC(qslog.Debug, - log << CONID() << "packUniqueData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_iCongestionWindow - << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan); + log << CONID() << "packUniqueData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_iCongestionWindow + << ")=" << 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. - kflg = m_pCryptoControl->getSndCryptoFlags(); - int pktskipseqno = 0; - 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"); - } + current_sequence_number = m_iSndCurrSeqNo; // PROXY for atomic; ALSO needed later. + time_point next_unique_ts; + pld_size = m_pSndBuffer->extractUniquePacket((w_sndpkt), (tsOrigin), (current_sequence_number), (next_unique_ts)); + IF_HEAVY_LOGGING(int32_t prev = m_iSndCurrSeqNo); + + m_iSndCurrSeqNo = current_sequence_number; + m_tsSndNextUnique = next_unique_ts; if (pld_size == 0) { - HLOGC(qslog.Debug, log << "packUniqueData: nothing extracted from the buffer"); + HLOGC(qslog.Debug, log << "packUniqueData: nothing extracted from the buffer; skipped " + << CSeqNo::seqoff(prev, current_sequence_number)); 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); - current_sequence_number = m_iSndCurrSeqNo; + HLOGC(qslog.Debug, log << "packUniqueData: extracted %" << current_sequence_number + << " after previous %" << m_iSndCurrSeqNo.load() << " (diff: " + << CSeqNo::seqoff(m_iSndCurrSeqNo, current_sequence_number) << ")"); } -#if ENABLE_BONDING - // Fortunately the group itself isn't being accessed. - if (m_parent->m_GroupOf) + CPacket& w_packet = w_sndpkt.pkt; + +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gk(uglobal(), m_parent); + if (!m_bClosing && gk.group) { const int packetspan = CSeqNo::seqoff(current_sequence_number, w_packet.seqno()); if (packetspan > 0) @@ -10392,19 +10326,23 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) ScopedLock ackguard(m_RecvAckLock); m_iSndCurrSeqNo = w_packet.seqno(); m_iSndLastAck = w_packet.seqno(); - m_iSndLastDataAck = w_packet.seqno(); + m_pSndBuffer->overrideFirstSeqNo(w_packet.seqno()); m_iSndLastFullAck = w_packet.seqno(); m_iSndLastAck2 = w_packet.seqno(); } else if (packetspan < 0) { LOGC(qslog.Error, - log << CONID() << "IPE: packData: SCHEDULING sequence " << w_packet.seqno() + log << CONID() << "IPE: packUniqueData: SCHEDULING sequence " << w_packet.seqno() << " is behind of EXTRACTION sequence " << current_sequence_number << ", 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; } + + int32_t upd SRT_ATR_UNUSED = gk.group->updateSentSeq(m_iSndCurrSeqNo); + HLOGC(qslog.Debug, log << CONID() << "packUniqueData: last sent seq for socket: %" << m_iSndCurrSeqNo + << " group: %" << upd); } else #endif @@ -10422,29 +10360,13 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) w_packet.set_id(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(); - } - #if SRT_DEBUG_TRACE_SND g_snd_logger.state.iPktSeqno = w_packet.seqno(); - g_snd_logger.state.isRetransmitted = w_packet.getRexmitFlag(); + g_snd_logger.state.isRetransmitted = w_packet.getRexmitFlag(); g_snd_logger.trace(); #endif -#ifdef ENABLE_RATE_MEASUREMENT +#ifdef SRT_ENABLE_RATE_MEASUREMENT HLOGC(bslog.Debug, log << "RATE-MEASUREMENT: REGULAR, pkt-size=" << w_packet.getLength()); m_SndRegularMeasurement.dataUpdate(1, w_packet.getLength()); #endif @@ -10452,10 +10374,12 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) return true; } -// This is a close request, but called from the -void srt::CUDT::processClose() +// This is a close request, but called from the handler of the +// buffer overflow in live mode. +void CUDT::processClose() { - sendCtrl(UMSG_SHUTDOWN); + uint32_t res[1] = { SRT_CLS_OVERFLOW }; + sendCtrl(UMSG_SHUTDOWN, NULL, res, sizeof res); m_bShutdown = true; m_bClosing = true; @@ -10479,7 +10403,7 @@ void srt::CUDT::processClose() CGlobEvent::triggerEvent(); } -void srt::CUDT::sendLossReport(const std::vector > &loss_seqs) +void CUDT::sendLossReport(const std::vector > &loss_seqs) { vector seqbuffer; seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic @@ -10506,7 +10430,7 @@ void srt::CUDT::sendLossReport(const std::vector > & } -bool srt::CUDT::overrideSndSeqNo(int32_t seq) +bool CUDT::overrideSndSeqNo(int32_t seq) { // This function is intended to be called from the socket // group management functions to synchronize the sequence in @@ -10531,7 +10455,7 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) // 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); + const int diff = SeqNo(seq) - SeqNo(m_iSndCurrSeqNo); if (diff < 0 || diff > CSeqNo::m_iSeqNoTH) { LOGC(gslog.Error, log << CONID() << "IPE: Overriding with seq %" << seq << " DISCREPANCY against current %" @@ -10539,7 +10463,15 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) return false; } - // + int dbytes; + const int dpkts SRT_ATR_UNUSED = m_pSndBuffer->dropAll((dbytes)); + + m_StatsLock.lock(); + m_stats.sndr.dropped.count(dbytes);; + m_StatsLock.unlock(); + + m_pSndBuffer->removeLossUpTo(CSeqNo::decseq(seq)); + // 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. @@ -10553,30 +10485,41 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo - << " (unchanged)"); + << " (unchanged) dropped-pkts=" << dpkts); return true; } -int srt::CUDT::checkLazySpawnTsbPdThread() +int CUDT::checkLazySpawnTsbPdThread() { - const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; - if (!need_tsbpd) +#if SRT_ENABLE_BONDING + const bool need_tsbpd = m_bTsbPd; + const bool need_group_tsbpd = m_bGroupTsbPd && !m_bTsbPd; + + // Just in case, make sure that they cannot be set + // together as one. The above statement contains a fallback + // for that case. + SRT_ASSERT(!(m_bTsbPd && m_bGroupTsbPd)); + +#else + const bool need_tsbpd = m_bTsbPd; + const bool need_group_tsbpd = false; +#endif + if (!need_tsbpd && !need_group_tsbpd) return 0; ScopedLock lock(m_RcvTsbPdStartupLock); - if (!m_RcvTsbPdThread.joinable()) + if (need_tsbpd && !m_RcvTsbPdThread.joinable()) { if (m_bClosing) // Check m_bClosing to protect join() in CUDT::releaseSync(). return -1; HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread"); -#if ENABLE_HEAVY_LOGGING - std::ostringstream tns1, tns2; +#if HVU_ENABLE_HEAVY_LOGGING + ofmtbufstream buf; // 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(); + string s = fmts(m_SocketID, fmtc().fillzero().width(2)); + buf << "SRT:TsbPd:@" << s.substr(s.size()-2, 2); + const string thname = buf.str(); #else const string thname = "SRT:TsbPd"; #endif @@ -10584,18 +10527,61 @@ int srt::CUDT::checkLazySpawnTsbPdThread() return -1; } +#if SRT_ENABLE_BONDING + if (need_group_tsbpd) + { + SharedLock glock(uglobal().m_GlobControlLock); + if (m_bClosing) + return -1; + + // Also, just in case, check if the socket is associated + // when group tsbpd is needed. + SRT_ASSERT(m_parent->m_GroupOf || !m_bGroupTsbPd); + + // Shipped to the group function because this will + // likely require groupwise locking. + return m_parent->m_GroupOf->checkLazySpawnTsbPdThread(); + } +#endif + return 0; } -CUDT::time_point srt::CUDT::getPktTsbPdTime(void*, const CPacket& packet) +#if SRT_ENABLE_BONDING +CUDT::time_point CUDT::getPktTsbPdTime(CUDTGroup* grp, const CPacket& packet) +{ + steady_clock::time_point pts; + + // Block this for a case of new-bonding group, as m_pRcvBuffer is NULL there. + if (grp) + { + pts = grp->getPktTsbPdTime(packet.getMsgTimeStamp()); + } + else if (!m_pRcvBuffer) + { + // Somehow we have dispatched to a previous member socket, + // that was already removed from the group, which means that + // it is being closed now. Pretend nothing has been dispatched. + pts = steady_clock::time_point() + milliseconds_from(m_iTsbPdDelay_ms); + } + else + { + pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); + } + + return pts; +} +#else +CUDT::time_point CUDT::getPktTsbPdTime(void*, const CPacket& packet) { return m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); } +#endif 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) +int CUDT::handleSocketPacketReception(vector& incoming, bool& w_new_inserted, time_point& w_next_tsbpd, 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 @@ -10605,52 +10591,32 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& // 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) + for (vector::iterator unitIt = incoming.begin(); unitIt != incoming.end() && !m_bBroken; ++unitIt) { - CUnit * u = *unitIt; - CPacket &rpkt = u->m_Packet; + // We use reference because units will be MOVED to the receiver buffer + // (if applicable). + CRcvBuffer::UnitHandle& unit_handle = *unitIt; + CPacket &rpkt = unit_handle->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.seqno()); 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. - + // seqno <% m_iRcvLastAck : already in acknowledged area + // See "Discarding acknowledged packets" in the developer notes. if (bufidx < 0 || CSeqNo::seqcmp(rpkt.seqno(), m_iRcvLastAck) < 0) { time_point pts = getPktTsbPdTime(NULL, rpkt); - enterCS(m_StatsLock); + m_StatsLock.lock(); 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); + m_StatsLock.unlock(); HLOGC(qrlog.Debug, log << CONID() << "RECEIVED: %" << rpkt.seqno() << " bufidx=" << bufidx << " (BELATED/" << s_rexmitstat_str[pktrexmitflag] << ") with ACK %" << m_iRcvLastAck @@ -10694,92 +10660,45 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } } - 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 + bool adding_successful = true; + + // If this is false, behave as if nothing has been received. + bool incoming_valid = handlePacketDecryption((unit_handle->m_Packet)); + if (incoming_valid) { - w_new_inserted = true; + CRcvBuffer::InsertInfo info = m_pRcvBuffer->insert((unit_handle), m_pMuxer->id()); - IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); - excessive = false; - if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) + // Need to remember this value because this will influence conditions + // of triggering TSBPD if needed. + if (info.result == CRcvBuffer::InsertInfo::INSERTED) { - // 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)); - string why; - if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) - { - LOGC(qrlog.Warn, log << CONID() << "Decryption failed (seqno %" << u->m_Packet.getSeqNo() << "), dropped " - << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "." << why); - } -#if SRT_ENABLE_FREQUENT_LOG_TRACE - else - { + // This may happen multiple times in the loop, so update only if earlier. + if (w_next_tsbpd == time_point() || w_next_tsbpd > info.first_time) + w_next_tsbpd = info.first_time; + w_new_inserted = true; + } + const int buffer_add_result = int(info.result); - LOGC(qrlog.Warn, log << "SUPPRESSED: Decryption failed LOG: " << why); - } -#endif - } + if (buffer_add_result < 0) + { + // This catches both -1 and -2 values; the packet was not inserted, + // possibly duplicate. + IF_HEAVY_LOGGING(exc_type = "UNACKED"); + adding_successful = false; } - else if (m_pCryptoControl && m_pCryptoControl->m_RcvKmState == SRT_KM_S_SECURED) + else { - // 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)); - string why; - if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) - { - LOGC(qrlog.Warn, log << CONID() << "Packet not encrypted (seqno %" << u->m_Packet.getSeqNo() << "), dropped " - << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "."); - } + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; } } - - if (adding_successful) + else { - ScopedLock statslock(m_StatsLock); - m_stats.rcvr.recvdUnique.count(u->m_Packet.getLength()); + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + adding_successful = false; } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::ostringstream expectspec; if (excessive) expectspec << "EXCESSIVE(" << exc_type << ")"; @@ -10795,9 +10714,9 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& 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) + << " buffer=%(" << bufseq + << ":" << m_iRcvCurrSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) << ")"; } @@ -10812,13 +10731,14 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& << rpkt.MessageFlagStr()); #endif - // Decryption should have made the crypto flags EK_NOENC. - // Otherwise it's an error. if (adding_successful) { + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(rpkt.getLength()); + HLOGC(qrlog.Debug, - log << CONID() - << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); + log << CONID() + << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); if (CSeqNo::seqcmp(rpkt.seqno(), CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { @@ -10829,33 +10749,365 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } } - // 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.seqno(), m_iRcvCurrSeqNo) > 0) + // If not valid, don't even check the incoming sequence number. + // Take it as if nothing was received. + if (incoming_valid) + { + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. + // + // Group note: for the new group receiver the group hosts the receiver + // buffer, but the socket still maintains the losses. + if (CSeqNo::seqcmp(rpkt.seqno(), m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } + } + } + + return 0; +} + +// NOTE: packet is not yet inserted into the buffer. Will be tried, if this +// function returns true. +bool CUDT::handlePacketDecryption(CPacket& packet) +{ + IF_LOGGING(std::string failure); + + const bool packet_encrypted = packet.getMsgCryptoFlags() != EK_NOENC; + const bool encryption_enabled = m_CryptoControl.kmState().rcv == SRT_KM_S_SECURED; + + if (!packet_encrypted) + { + if (!encryption_enabled) + return true; + + IF_LOGGING(failure = "Packet not encrypted"); + } + else // Decrypt it (regardless of the kmstate) + { + // TODO: reset and restore the timestamp if TSBPD is disabled. + // Reset retransmission flag (must be excluded from GCM auth tag). + const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; + + packet.setRexmitFlag(false); + const EncryptionStatus rc = m_CryptoControl.decrypt((packet)); + packet.setRexmitFlag(pktrexmitflag == 1); // Recover the flag. + + // Decryption should have made the crypto flags EK_NOENC. + // Otherwise it's an error. + if (rc == ENCS_CLEAR && packet.getMsgCryptoFlags() == EK_NOENC) + return true; + + IF_LOGGING(failure = fmtcat("Decryption ", rc == ENCS_FAILED ? "failed" : "unsupported")); + } + + // DECRYPTION FAILED: Just display the error in the logs and update stats. + { + ScopedLock lg(m_StatsLock); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(packet.getLength(), 1)); + } +#if HVU_ENABLE_LOGGING + const steady_clock::time_point tnow = steady_clock::now(); + string why; + if (frequentLogAllowed(FREQLOGFA_ENCRYPTION_FAILURE, tnow, (why))) + { + LOGC(qrlog.Warn, log << CONID() << failure << " (seqno %" << packet.getSeqNo() + << ") -- dropped. pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "." << why); + } +#if SRT_ENABLE_FREQUENT_LOG_TRACE + else + { + + LOGC(qrlog.Warn, log << "SUPPRESSED: Decryption failed LOG: " << why); + } +#endif +#endif + return false; +} + +#if SRT_ENABLE_BONDING +bool CUDT::handleGroupPacketReception(CUDTGroup* grp, vector& incoming, 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 + + // 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 CUDT::processData(). + for (vector::iterator unitIt = incoming.begin(); unitIt != incoming.end() && !m_bBroken; ++unitIt) + { + CRcvBuffer::UnitHandle& unit_handle = *unitIt; + CPacket &rpkt = unit_handle->m_Packet; + const int pktrexmitflag = m_bPeerRexmitFlag ? (rpkt.getRexmitFlag() ? 1 : 0) : 2; + bool adding_successful = true; + bool have_loss = false; + IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); + + bool incoming_valid = handlePacketDecryption((unit_handle->m_Packet)); + if (incoming_valid) + { + // This is executed only when bonding is enabled and only + // with the new buffer (in which case the buffer is in the group). + // NOTE: this will lock ALSO the receiver buffer lock in the group + CRcvBuffer::InsertInfo info = grp->addDataUnit( + m_pMuxer->id(), + m_parent, // ->m_GroupMemberData, <- Accessing m_GroupMemberData here is racy + (unit_handle), + (w_srt_loss_seqs), + (have_loss)); + + if (info.result == CRcvBuffer::InsertInfo::DISCREPANCY) + { + // XXX PROBABLY the new receiver buffer can give the possibility + // of completely resetting itself at the moment when this happens, + // so closing may be not necessary in case of TLPKTDROP, but instead + // the whole buffer will be dropped and it will start over from the + // newly incoming sequence number. + if (m_bGroupTsbPd && info.avail_range == 0) + { + LOGC(qrlog.Error, log << CONID() << + "SEQUENCE DISCREPANCY. BREAKING CONNECTION."); + + // Here in this place there's nothing to unlock; locking is done + // exclusively in the call to addDataUnit(). + processClose(); + } + else + { + // Can't reach the buffer information because it's inside the group. + // The log should be likely fully presented in the CUDTGroup::addDataUnit(). + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.seqno()); + } + + // IGNORE remaining packets + return false; + } + + if (info.result == CRcvBuffer::InsertInfo::BELATED) + { + time_point pts = getPktTsbPdTime(grp, rpkt); + + IF_HEAVY_LOGGING(exc_type = "BELATED"); + m_StatsLock.lock(); + 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()); + m_StatsLock.unlock(); + HLOGC(qrlog.Debug, + log << CONID() << "RECEIVED: seq=" << rpkt.seqno() << " (BELATED/" + << s_rexmitstat_str[pktrexmitflag] << ") FLAGS: " << rpkt.MessageFlagStr()); + + // For BELATED packets you should just skip anything else. + // This means it's already beyond the first entry in the buffer, so this + // sequence means nothing also for the loss check. + continue; + } + + if (info.result == CRcvBuffer::InsertInfo::REDUNDANT) + { + // 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 // INSERTED + { + IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); + excessive = false; + } + } + else { - m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED"); + adding_successful = false; } + +#if HVU_ENABLE_HEAVY_LOGGING + hvu::ofmtbufstream expectspec; + if (excessive) + expectspec << "EXCESSIVE(" << exc_type << ")"; else + expectspec << "ACCEPTED"; + + // Empty buffer info in case of groupwise receiver. + // There's no way to obtain this information here. + + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.seqno() + << " RSL=" << expectspec + << " SN=" << s_rexmitstat_str[pktrexmitflag] + << " FLAGS: " + << rpkt.MessageFlagStr()); +#endif + + if (adding_successful) + { + HLOGC(qrlog.Debug, + log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); + + if (have_loss) + { + HLOGC(qrlog.Debug, log << "grp/LOSS DETECTED: " << FormatLossArray(w_srt_loss_seqs)); + } + + ScopedLock statslock(m_StatsLock); + m_stats.rcvr.recvdUnique.count(rpkt.getLength()); + } + + if (incoming_valid) { - unlose(rpkt); // was BELATED or RETRANSMITTED - w_was_sent_in_order &= 0 != pktrexmitflag; + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. + // + // Group note: for the new group receiver the group hosts the receiver + // buffer, but the socket still maintains the losses. + if (CSeqNo::seqcmp(rpkt.seqno(), m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + } + else + { + unlose(rpkt); // was BELATED or RETRANSMITTED + w_was_sent_in_order &= 0 != pktrexmitflag; + } } } - return 0; + return true; +} +#endif + +#if USE_RECEIVER_UNIT_POOL +struct PacketFilterCollector +{ + CRcvQueue* extractor; + vector units; + + bool retrieveUnit(CPacketUnitPool::UnitPtr& to) + { + return extractor->retrieveUnit((to)); + } + + void returnAcquiredUnits() + { + // Packets that were acquired are NULL unit pointers. Others would be + // deleted together with the collector, so let's then pass these units + // back to where they came from. This is just optimization to avoid later + // unnecessary allocations. + for (std::vector::iterator i = units.begin(); i != units.end(); ++i) + { + if (*i) + { + extractor->returnUnit(*i); + } + } + } +}; + +// This is a static method in CRcvQueue prepared as a callback for PacketFilter::provide() +static bool collectFilterPacket(void* vthat, const char* header, const char* data, size_t datasize) +{ + PacketFilterCollector* col = (PacketFilterCollector*)vthat; + + col->units.push_back(CPacketUnitPool::UnitPtr()); + // Transfer ownership to this vector + if (!col->retrieveUnit( (col->units.back()) )) + { + HLOGC(qrlog.Debug, log << "PF: unit pool provided no packet, can't store rebuilt!"); + col->units.pop_back(); // it's empty anyway + return false; // drop this and all remaining in the loop + } + SRT_ASSERT(col->units.back()); + CPacket& packet = col->units.back()->m_Packet; + + memcpy((packet.getHeader()), header, CPacket::HDR_SIZE); + memcpy((packet.m_pcData), data, datasize); + packet.setLength(datasize); + HLOGC(qrlog.Debug, log << "PF: ... got %" << packet.getSeqNo()); + + return true; +} +#else +struct PacketFilterCollector +{ + CRcvQueue* extractor; + vector units; +}; + +static bool collectFilterPacket(void* vthat, const char* header, const char* data, size_t datasize) +{ + PacketFilterCollector* col = (PacketFilterCollector*)vthat; + + CUnit* u = col->extractor->getBufferQueue()->getNextAvailUnit(); + if (!u) + return false; // drop this and all remaining + CPacket& packet = u->m_Packet; + + memcpy((packet.getHeader()), header, CPacket::HDR_SIZE); + memcpy((packet.m_pcData), data, datasize); + packet.setLength(datasize); + + u->m_bTaken = true; + col->units.push_back(u); + return true; +} +#endif + +#if HVU_ENABLE_HEAVY_LOGGING +inline static size_t countAcquiredUnits(const std::vector& v) +{ + size_t n = 0; + for (std::vector::const_iterator i = v.begin(); i != v.end(); ++i) + if (!*i) + ++n; + return n; } +#endif -int srt::CUDT::processData(CUnit* in_unit) +struct SortBySequence +{ + template + bool operator()(UnitType& u1, UnitType& u2) + { + // NOTE: UnitType must be a plain pointer or a smart pointer + int32_t s1 = u1->m_Packet.getSeqNo(); + int32_t s2 = u2->m_Packet.getSeqNo(); + + return CSeqNo::seqcmp(s1, s2) < 0; + } +}; + + +#if USE_RECEIVER_UNIT_POOL +int CUDT::acquireDataPacket(CPacketUnitPool::UnitPtr& in_unit, CRcvQueue* provider) +#else +int CUDT::processData(CUnit* in_unit, CRcvQueue* provider) +#endif { if (m_bClosing) return -1; - CPacket &packet = in_unit->m_Packet; + // Ok, logically we have to extract this unit, and if there's no reason + // for it to be passed anywhere else, put it back to source. + SRT_ASSERT(!! in_unit); + CPacket& packet = in_unit->m_Packet; // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; - m_tsLastRspTime.store(steady_clock::now()); + m_tsLastRspTime.store(steady_clock::now()); // XXX Requires lock m_RecvAckLock + // Keep the group alive until the end of this function. + +#if SRT_ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); +#endif // We are receiving data, start tsbpd thread if TsbPd is enabled if (-1 == checkLazySpawnTsbPdThread()) @@ -10865,18 +11117,16 @@ int srt::CUDT::processData(CUnit* in_unit) const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2; const bool retransmitted = pktrexmitflag == 1; -#if ENABLE_HEAVY_LOGGING - string rexmit_reason; -#endif + IF_HEAVY_LOGGING(string rexmit_reason); - if (retransmitted) + if (pktrexmitflag == 1) { // This packet was retransmitted - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.rcvr.recvdRetrans.count(packet.getLength()); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING // Check if packet was retransmitted on request or on ack timeout // Search the sequence in the loss record. rexmit_reason = " by "; @@ -10885,25 +11135,32 @@ int srt::CUDT::processData(CUnit* in_unit) rexmit_reason += "BLIND"; else rexmit_reason += "NAKREPORT"; + // XXX rexmit_reason is unused; check this!!! #endif } -#if ENABLE_HEAVY_LOGGING - { - steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode) +#if HVU_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; + // 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. + time_point pts; +#if SRT_ENABLE_BONDING + pts = getPktTsbPdTime(gkeeper.group, packet); +#else + pts = getPktTsbPdTime(NULL, packet); +#endif + 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)); - } + 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) + << " NOW=" << FormatTime(m_tsLastRspTime.load())); + } #endif updateCC(TEV_RECEIVE, EventVariant(&packet)); @@ -10932,14 +11189,15 @@ int srt::CUDT::processData(CUnit* in_unit) // otherwise measurement must be rejected. m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.rcvr.recvd.count(pktsz); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); - loss_seqs_t filter_loss_seqs; - loss_seqs_t srt_loss_seqs; - vector incoming; - bool was_sent_in_order = true; + loss_seqs_t filter_loss_seqs; + loss_seqs_t srt_loss_seqs; + PacketFilterCollector collector; + collector.extractor = provider; + bool was_sent_in_order = true; // If the peer doesn't understand REXMIT flag, send rexmit request // always immediately. @@ -10957,7 +11215,10 @@ int srt::CUDT::processData(CUnit* in_unit) // 1 - subsequent packet (alright) // <0 - belated or recovered packet // >1 - jump over a packet loss (loss = seqdiff-1) - if (diff > 1) + + // Hook on non-NULL receiver buffer for a case of the common group buffer. + // This is for stats only and for groups it can be done elsewhere. + if (m_pRcvBuffer && diff > 1) { const int loss = diff - 1; // loss is all that is above diff == 1 @@ -10979,7 +11240,7 @@ int srt::CUDT::processData(CUnit* in_unit) // [[using locked()]]; // (NOTHING locked) -#if ENABLE_BONDING +#if SRT_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 @@ -10987,9 +11248,10 @@ int srt::CUDT::processData(CUnit* in_unit) // 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) + if (gkeeper.group) { - ExclusiveLock protect_group_existence (uglobal().m_GlobControlLock); + ScopedLock glk (*gkeeper.group->exp_groupLock()); + // NO GLOBAL LOCKING: group is preserved by the Keeper groups::SocketData* gi = m_parent->m_GroupMemberData; // This check is needed as after getting the lock the socket @@ -11004,6 +11266,7 @@ int srt::CUDT::processData(CUnit* in_unit) log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] << " -> RUNNING."); gi->rcvstate = SRT_GST_RUNNING; + gkeeper.group->updateRcvRunningState(); } else { @@ -11015,48 +11278,124 @@ int srt::CUDT::processData(CUnit* in_unit) } #endif + // NULL time by default + time_point next_tsbpd_avail; bool new_inserted = false; + // Alright, we have the unit owned now by in_unit. If it contains a normal data packet, + // insert it into collector.units, otherwise return to sender. if (m_PacketFilter) { + // NOTE: this is a one-shot callback, not permanent; it's safe to use a local object here. // Stuff this data into the filter - m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs)); + bool passthru = m_PacketFilter.provide(packet, MakeCallback((void*)&collector, collectFilterPacket), (filter_loss_seqs)); + if (passthru) + { +#if USE_RECEIVER_UNIT_POOL + MoveBack((collector.units), (in_unit)); +#else + collector.units.push_back(in_unit); +#endif + } + // ELSE: leave this packet in `in_unit`; the caller will take it back. + + if (collector.units.size() > 1) + { + // Here we can have *POSSIBLY* incoming packet plus *POTENTIALLY* several rebuilt ones. + // We don't know in what order they have come, so reorder them by sequence to avoid false loss recognition. + sort(collector.units.begin(), collector.units.end(), SortBySequence()); + } + HLOGC(qrlog.Debug, - log << CONID() << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs) + log << CONID() << "(FILTER) fed data, received " << collector.units.size() << " pkts, " << Printable(filter_loss_seqs) << " loss to report, " << (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF" : "REPORT ONLY THOSE")); } else { +#if USE_RECEIVER_UNIT_POOL + MoveBack((collector.units), (in_unit)); +#else // Stuff in just one packet that has come in. - incoming.push_back(in_unit); + collector.units.push_back(in_unit); +#endif } + // NOTE: DO NOT use in_unit since this point on! + // This unit is still alive, but it may be in in_unit, or + // in collector.units, depending on what happened above. + // Refer to `packet` if you need information about the incoming packet. + +#if SRT_ENABLE_BONDING + if (gkeeper.group) + { + // Needed for possibly check for needsQuickACK. + const bool incoming_belated = (CSeqNo::seqcmp(packet.seqno(), gkeeper.group->getOldestRcvSeqNo()) < 0); + + bool handled = handleGroupPacketReception(gkeeper.group, + (collector.units), // potentially purged + (was_sent_in_order), + (srt_loss_seqs)); + +#if USE_RECEIVER_UNIT_POOL + HLOGC(qrlog.Debug, log << CONID() << "processData: handled " << collector.units.size() + << " units, acquired " << countAcquiredUnits(collector.units)); + + collector.returnAcquiredUnits(); +#endif + + // These variables are used to decide about pinging the CV that kicks + // the TSBPD thread prematurely. In case of group common receiver + // there's no per-socket TSBPD and the group TSBPD will be handled + // internally. + next_tsbpd_avail = time_point(); + new_inserted = false; + + if (!handled) + return -1; + + if (!incoming_belated && was_sent_in_order) + { + if (m_CongCtl->needsQuickACK(packet)) + { + m_tsNextACKTime.store(steady_clock::now()); + } + } + + // Here continue the processing because even if no new packets were + // added to the buffer, there might be needed losses handled. + } + else +#endif { // Start of offset protected section // Prevent TsbPd thread from modifying Ack position while adding data // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() UniqueLock recvbuf_acklock(m_RcvBufferLock); // Needed for possibly check for needsQuickACK. - const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.seqno(), m_pRcvBuffer->getStartSeqNo()) < 0); + const bool incoming_belated = (CSeqNo::seqcmp(packet.seqno(), m_pRcvBuffer->getStartSeqNo()) < 0); - const int res = handleSocketPacketReception(incoming, + const int res = handleSocketPacketReception(collector.units, (new_inserted), + (next_tsbpd_avail), (was_sent_in_order), (srt_loss_seqs)); 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. + // Must be unlocked before processClose; that would deadlock. recvbuf_acklock.unlock(); processClose(); return -1; } +#if USE_RECEIVER_UNIT_POOL + HLOGC(qrlog.Debug, log << CONID() << "processData: handled " << collector.units.size() + << " units, acquired " << countAcquiredUnits(collector.units)); + + collector.returnAcquiredUnits(); +#endif if (res == -1) { @@ -11082,18 +11421,8 @@ int srt::CUDT::processData(CUnit* in_unit) } } - // This is moved earlier after introducing filter because it shouldn't - // be executed in case when the packet was rejected by the receiver buffer. - // However now the 'excessive' condition may be true also in case when - // a truly non-excessive packet has been received, just it has been temporarily - // stored for better times by the filter module. This way 'excessive' is also true, - // although the old condition that a packet with a newer sequence number has arrived - // or arrived out of order may still be satisfied. if (!incoming_belated && was_sent_in_order) { - // Basing on some special case in the packet, it might be required - // to enforce sending ACK immediately (earlier than normally after - // a given period). if (m_CongCtl->needsQuickACK(packet)) { m_tsNextACKTime.store(steady_clock::now()); @@ -11108,16 +11437,28 @@ int srt::CUDT::processData(CUnit* in_unit) if (m_bClosing) { - // RcvQueue worker thread can call processData while closing (or close while processData) - // This race condition exists in the UDT design but the protection against TsbPd thread - // (with AckLock) and decryption enlarged the probability window. - // Application can crash deep in decrypt stack since crypto context is deleted in close. - // RcvQueue worker thread will not necessarily be deleted with this connection as it can be - // used by others (socket multiplexer). + // The code should be now safe from any mishits for handling incoming packets + // while closing the socket, but increase the chance to give up if closing was + // requested. return -1; } - if (incoming.empty()) + // Meaning of the variables: + // - new_inserted: if true, there's a new packet in the buffer (preceding or not) + // - next_tsbpd_avail: if nonzero, it's play time of the new, PRECEDING packet. + // - m_bTsbPdNeedsWakeup: if true, TSBPD sleeps forever + + // See "TSBPD synchronization on packet arrival" in developer notes for details. + + if (m_bTsbPd && ((m_bTsbPdNeedsWakeup && new_inserted) || next_tsbpd_avail != time_point())) + { + HLOGC(qrlog.Debug, log << "processData: will SIGNAL TSBPD for socket. WakeOnRecv=" << m_bTsbPdNeedsWakeup + << " new_inserted=" << new_inserted << " next_tsbpd_avail=" << FormatTime(next_tsbpd_avail)); + CUniqueSync tsbpd_cc(m_RecvLock, m_RcvTsbPdCond); + tsbpd_cc.notify_all(); + } + + if (collector.units.empty()) { // Treat as excessive. This is when a filter cumulates packets // until the loss is rebuilt, or eats up a filter control packet @@ -11132,60 +11473,26 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } - - if (m_bTsbPd) - { - 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"); - } } - // Separately report loss records of those reported by a filter. - // ALWAYS report whatever has been reported back by a filter. Note that - // the filter never reports anything when rexmit fallback level is ALWAYS or NEVER. - // With ALWAYS only those are reported that were recorded here by SRT. - // With NEVER, nothing is to be reported. + // Always report losses reported by the filter. + // NOTE: The filter reports them only if arq=onreq. if (!filter_loss_seqs.empty()) { HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); - - if (m_bTsbPd) - { - HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); - CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); - } } - // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. + // The "fresh loss" functionality. See the "Delaying loss reports" developer note. - // PERFORMANCE CONSIDERATIONS: - // This list is quite inefficient as a data type and finding the candidate to send UMSG_LOSSREPORT - // 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 dropFromLossLists() of one sequence) - // - first found record with TTL>0 means end of "ready to LOSSREPORT" records - // So: - // All you have to do is: - // - start with first element and continue with next elements, as long as they have TTL=0 - // If so, send the loss report and remove this element. - // - Since the first element that has TTL>0, iterate until the end of container and decrease TTL. - // - // This will be efficient because the loop to increment one field (without any condition check) - // can be quite well optimized. + // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. vector lossdata; + if (initial_loss_ttl) { 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. - if (initial_loss_ttl && !m_FreshLoss.empty()) + if (!m_FreshLoss.empty()) { deque::iterator i = m_FreshLoss.begin(); @@ -11241,9 +11548,9 @@ int srt::CUDT::processData(CUnit* in_unit) if (m_iReorderTolerance > 0) { m_iReorderTolerance--; - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.traceReorderDistance--; - leaveCS(m_StatsLock); + m_StatsLock.unlock(); HLOGC(qrlog.Debug, log << "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to " << m_iReorderTolerance); } @@ -11253,42 +11560,17 @@ int srt::CUDT::processData(CUnit* in_unit) return 0; } -#if ENABLE_BONDING +#if SRT_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) +// This is a part of BACKUP-type group handling; it should shift the +// initial sequence of an IDLE link (this one) after reception of a packet +// from the ACTIVE link. It is not set backwards and the value of the +// initial sequence is fixed to not be earlier than the first in the group's +// receiver buffer. +// XXX CONSIDER changing the name to `updateInitialRcvSeq` and make it return bool +// instead of logging, so that all required logging is moved to `CUDTGroup::updateLatestRcv`. +void CUDT::updateIdleLinkFrom(int32_t new_last_rcv, SRTSOCKET id SRT_ATR_UNUSED /* logging only */) { - 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) { @@ -11300,28 +11582,16 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) } HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID - << " from @" << source->m_SocketID << ": %" << new_last_rcv); + << " from @" << id << ": %" << 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. -/// -/// Additionally, check - if supported by the peer - whether the "latecoming" packet -/// has been sent due to retransmission or due to reordering, by checking the rexmit -/// support flag and rexmit flag itself. If this packet was surely ORIGINALLY SENT -/// it means that the current network connection suffers of packet reordering. This -/// way try to introduce a dynamic tolerance by calculating the difference between -/// the current packet reception sequence and this packet's sequence. This value -/// 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 iMaxReorderTolerance. -/// This value can be set in options - SRT_LOSSMAXTTL. -void srt::CUDT::unlose(const CPacket &packet) +// This is called for every "old" incoming packet, potentially a loss recovery. +// This should remove the packet's sequence from any loss records. +// See the "Delaying loss reports" developer note for additional details. +void CUDT::unlose(const CPacket &packet) { ScopedLock lg(m_RcvLossLock); int32_t sequence = packet.seqno(); @@ -11334,20 +11604,15 @@ void srt::CUDT::unlose(const CPacket &packet) if (m_bPeerRexmitFlag) { - // If the peer understands the REXMIT flag, it means that the REXMIT flag is contained - // in the PH_MSGNO field. - - // The packet is considered coming originally (just possibly out of order), if REXMIT - // flag is NOT set. was_reordered = !packet.getRexmitFlag(); if (was_reordered) { HLOGC(qrlog.Debug, log << "received out-of-band packet %" << sequence); const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.seqno())); - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance); - leaveCS(m_StatsLock); + m_StatsLock.unlock(); if (seqdiff > m_iReorderTolerance) { const int new_tolerance = min(seqdiff, m_config.iMaxReorderTolerance); @@ -11404,9 +11669,9 @@ void srt::CUDT::unlose(const CPacket &packet) if (m_iReorderTolerance > 0) { m_iReorderTolerance--; - enterCS(m_StatsLock); + m_StatsLock.lock(); m_stats.traceReorderDistance--; - leaveCS(m_StatsLock); + m_StatsLock.unlock(); HLOGC(qrlog.Debug, log << "... reached " << m_iConsecEarlyDelivery << " times - decreasing tolerance to " << m_iReorderTolerance); } @@ -11416,7 +11681,7 @@ void srt::CUDT::unlose(const CPacket &packet) } } -void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) +void CUDT::dropFromLossLists(int32_t from, int32_t to) { ScopedLock lg(m_RcvLossLock); @@ -11433,7 +11698,7 @@ void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) m_pRcvLossList->remove(from, to); } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING ostringstream range; if (begin == SRT_SEQNO_NONE) { @@ -11497,7 +11762,7 @@ void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) } // This function, as the name states, should bake a new cookie. -int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int correction) +int32_t CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int correction) { static unsigned int distractor = 0; unsigned int rollover = distractor + 10; @@ -11514,7 +11779,8 @@ int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int co clientport, sizeof(clientport), NI_NUMERICHOST | NI_NUMERICSERV); - int64_t timestamp = (count_microseconds(steady_clock::now() - m_stats.tsStartTime) / 60000000) + distractor + + time_point start_time = m_stats.tsStartTime; + int64_t timestamp = (count_microseconds(steady_clock::now() - start_time) / 60000000) + distractor + correction; // secret changes every one minute stringstream cookiestr; cookiestr << clienthost << ":" << clientport << ":" << timestamp; @@ -11555,14 +11821,19 @@ int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int co // XXX Make this function return EConnectStatus enum type (extend if needed), // and this will be directly passed to the caller. -// [[using locked(m_pRcvQueue->m_LSLock)]]; -int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) +// [[using locked(m_RcvQueue.m_LSLock)]]; +int CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) { // XXX ASSUMPTIONS: // [[using assert(packet.id() == 0)]] HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received a connection request"); + // NOTE (IMPORTANT!!!) + // + // The current CUDT object represents a LISTENER SOCKET to which + // the request was redirected from the receiver queue. + if (m_bClosing) { m_RejectReason = SRT_REJ_CLOSE; @@ -11624,14 +11895,14 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) int32_t cookie_val = bake(addr); - HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: new cookie: " << hex << cookie_val); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: new cookie: " << fmt(cookie_val, hex)); // 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(); + CNetworkInterface use_source_addr = packet.udpDestAddr(); // REQUEST:INDUCTION. // Set a cookie, a target ID, and send back the same as @@ -11678,7 +11949,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) // 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); + channel()->sendto(addr, packet, use_source_addr); return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code. } @@ -11704,7 +11975,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (hs.m_iCookie != cookie_val) { m_RejectReason = SRT_REJ_RDVCOOKIE; - HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."); + HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ...wrong cookie " << fmt(cookie_val, hex) << ". Ignoring."); return m_RejectReason; } @@ -11766,7 +12037,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) packet.set_id(id); setPacketTS((packet), steady_clock::now()); HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: SENDING HS (e): " << hs.show()); - m_pSndQueue->sendto(addr, packet, use_source_addr); + channel()->sendto(addr, packet, use_source_addr); } else { @@ -11818,7 +12089,9 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (hs.m_iVersion >= HS_VERSION_SRT1) { // Always attach extension. - hs.m_extension = true; + hs.m_extensionType = SRT_CMD_HSRSP; + // XXX REQUIRES LOCK ON acpu->m_ConnectionLock. + // Check clashes with m_LSLock! conn = acpu->craftKmResponse((kmdata), (kmdatasize)); } else @@ -11830,6 +12103,8 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) return conn; packet.setLength(m_iMaxSRTPayloadSize); + // XXX REQUIRES LOCK ON acpu->m_ConnectionLock. + // Check clashes with m_LSLock! if (!acpu->createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (packet), (hs))) @@ -11847,21 +12122,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) } } - 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 - // 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) + if (result == -1) { // The new connection failed // or the connection already existed, but manually sending the HS response above has failed. @@ -11876,7 +12137,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) setPacketTS((rsp), steady_clock::now()); rsp.pack(UMSG_SHUTDOWN); rsp.set_id(m_PeerID); - m_pSndQueue->sendto(addr, rsp, use_source_addr); + channel()->sendto(addr, rsp, use_source_addr); } else { @@ -11889,7 +12150,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) packet.set_id(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); + channel()->sendto(addr, packet, use_source_addr); } } } @@ -11898,7 +12159,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) return RejectReasonForURQ(hs.m_iReqType); } -void srt::CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) +void CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) { if (lo == hi) lr.push_back(lo); @@ -11909,16 +12170,13 @@ void srt::CUDT::addLossRecord(std::vector &lr, int32_t lo, int32_t hi) } } -int srt::CUDT::checkACKTimer(const steady_clock::time_point &currtime) +int CUDT::checkACKTimer(const steady_clock::time_point &currtime) { + // See "Sending ACK and conditions" in docs/dev/development-notes.md 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) + if (currtime > m_tsNextACKTime.load() || (m_CongCtl->ACKMaxPackets() > 0 && m_iPktCount >= m_CongCtl->ACKMaxPackets())) { - // ACK timer expired or ACK interval is reached sendCtrl(UMSG_ACK); const steady_clock::duration ack_interval = m_CongCtl->ACKTimeout_us() > 0 @@ -11930,13 +12188,6 @@ int srt::CUDT::checkACKTimer(const steady_clock::time_point &currtime) 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_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. else if (m_iPktCount >= SELF_CLOCK_INTERVAL * m_iLightACKCount) { // send a "light" ACK @@ -11948,31 +12199,15 @@ int srt::CUDT::checkACKTimer(const steady_clock::time_point &currtime) return because_decision; } -int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) +int 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 - // implemented. The reason for it is that the structure of the - // loss list container (m_pRcvLossList) is such that it is expected - // that the loss records are ordered by sequence numbers (so - // that two ranges sticking together are merged in place). - // Unfortunately in case of SRT_ARQ_ONREQ losses must be recorded - // as before, but they should not be reported, until confirmed - // 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. + // See docs/dev/retransmission.md for details, also why this condition blocks it. if (!m_config.bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) return BECAUSE_NO_REASON; - /* - * 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); + m_RcvLossLock.lock(); const int loss_len = m_pRcvLossList->getLossLength(); - leaveCS(m_RcvLossLock); + m_RcvLossLock.unlock(); SRT_ASSERT(loss_len >= 0); int debug_decision = BECAUSE_NO_REASON; @@ -11990,10 +12225,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) return debug_decision; } -bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int check_reason SRT_ATR_UNUSED) +bool CUDT::checkExpTimer(const steady_clock::time_point& currtime, int check_reason SRT_ATR_UNUSED) { // VERY HEAVY LOGGING -#if ENABLE_HEAVY_LOGGING & 1 +#if HVU_ENABLE_HEAVY_LOGGING & 1 static const char* const decisions [] = { "ACK", "LITE-ACK", @@ -12019,6 +12254,8 @@ bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int chec // In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class. // There's nothing in the original code that alters these values. + // XXX lock on m_RecvAckLock ? + steady_clock::time_point next_exp_time; if (m_CongCtl->RTO()) { @@ -12044,6 +12281,7 @@ bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int chec if (m_bBreakAsUnstable || ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) && (currtime - last_rsp_time > microseconds_from(PEER_IDLE_TMO_US)))) { + setAgentCloseReason(SRT_CLS_PEERIDLE); // // Connection is broken. // UDT does not signal any information about this instead of to stop quietly. @@ -12056,7 +12294,7 @@ bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int chec m_iBrokenCounter = 30; // update snd U list to remove this socket - m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE); + m_pMuxer->updateSendFast(m_parent); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! @@ -12069,44 +12307,11 @@ bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int chec << " elapsed=" << (count_microseconds(currtime - last_rsp_time)) << "/" << (+PEER_IDLE_TMO_US) << "us"); ++m_iEXPCount; - - /* - * (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 - * 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_tsLastRspTime = currtime_tk; - return false; } -void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) +void CUDT::checkBlindRexmitTimer(const steady_clock::time_point& currtime) { - // Check if HSv4 should be retransmitted, and if KM_REQ should be resent if the side is INITIATOR. - checkSndTimers(); - - // 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. - // - { ScopedLock ack_lock(m_RecvAckLock); const uint64_t rtt_syn = (m_iSRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US); @@ -12116,8 +12321,6 @@ void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) return; } - // If there is no unacknowledged data in the sending buffer, - // then there is nothing to retransmit. if (m_pSndBuffer->getCurrBufSize() <= 0) return; @@ -12129,87 +12332,80 @@ void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) if (is_fastrexmit && m_bPeerNakReport) return; - // 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. - 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) - { - enterCS(m_StatsLock); - m_stats.sndr.lost.count(num); - leaveCS(m_StatsLock); + if ((!is_laterexmit || m_pSndBuffer->getLossLength() == 0)) + { + ScopedLock acklock(m_RecvAckLock); + if (getFlightSpan() > 0) + { + const int32_t csn = m_iSndCurrSeqNo; + const int num = m_pSndBuffer->insertLoss(m_iSndLastAck, csn, steady_clock::now()); + if (num > 0) + { + m_StatsLock.lock(); + m_stats.sndr.lost.count(num); + m_StatsLock.unlock(); - 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)"); + 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)"); + } } } - ++m_iReXmitCount; + { + ScopedLock ack_lock(m_RecvAckLock); + ++m_iReXmitCount; + } const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT; updateCC(TEV_CHECKTIMER, EventVariant(stage)); - // schedule sending if not scheduled already - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + m_pMuxer->updateSendNormal(m_parent); } -void srt::CUDT::checkTimers() +void CUDT::checkTimers() { - // update CC parameters updateCC(TEV_CHECKTIMER, EventVariant(TEV_CHT_INIT)); const steady_clock::time_point currtime = steady_clock::now(); // This is a very heavy log, unblock only for temporary debugging! #if 0 - HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_tsNextACKTime) - << " AckInterval=" << m_iACKInterval + HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=+" + << FormatDuration(m_tsNextACKTime.load() - steady_clock::now()) + << " AckInterval=" << FormatDuration(m_tdACKInterval) << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount); #endif -#ifdef ENABLE_RATE_MEASUREMENT +#ifdef SRT_ENABLE_RATE_MEASUREMENT m_SndRegularMeasurement.pickup(currtime); m_SndRexmitMeasurement.pickup(currtime); #endif - // Check if it is time to send ACK - int debug_decision = checkACKTimer(currtime); - - // Check if it is time to send a loss report + int debug_decision = 0; + debug_decision |= checkACKTimer(currtime); debug_decision |= checkNAKTimer(currtime); - // Check if the connection is expired if (checkExpTimer(currtime, debug_decision)) return; - // Check if FAST or LATE packet retransmission is required - checkRexmitTimer(currtime); + checkSndTimers(); // Missed KM updates and HSv4 legacy HS updates + + checkBlindRexmitTimer(currtime); if (currtime > m_tsLastSndTime.load() + microseconds_from(COMM_KEEPALIVE_PERIOD_US)) { sendCtrl(UMSG_KEEPALIVE); -#if ENABLE_BONDING - if (m_parent->m_GroupOf) +#if SRT_ENABLE_BONDING { + // 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. SharedLock 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 @@ -12217,28 +12413,30 @@ void srt::CUDT::checkTimers() } } -void srt::CUDT::updateBrokenConnection() +void CUDT::updateBrokenConnection() { HLOGC(smlog.Debug, log << "updateBrokenConnection: setting closing=true and taking out epoll events"); 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) +void CUDT::completeBrokenConnectionDependencies(int errorcode) { int token = -1; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING bool pending_broken = false; { SharedLock guard_group_existence (uglobal().m_GlobControlLock); + groups::SocketData* member = m_parent->m_GroupMemberData; if (m_parent->m_GroupOf) { - token = m_parent->m_GroupMemberData->token; - if (m_parent->m_GroupMemberData->sndstate == SRT_GST_PENDING) + ScopedLock lock_group (*m_parent->m_GroupOf->exp_groupLock()); + + token = member->token; + if (member->sndstate == SRT_GST_PENDING) { HLOGC(gmlog.Debug, log << CONID() << "updateBrokenConnection: a pending link was broken - will be removed"); pending_broken = true; @@ -12247,12 +12445,12 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) { HLOGC(gmlog.Debug, log << CONID() << "updateBrokenConnection: state=" - << CUDTGroup::StateStr(m_parent->m_GroupMemberData->sndstate) + << CUDTGroup::StateStr(member->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; + member->sndstate = SRT_GST_BROKEN; + member->rcvstate = SRT_GST_BROKEN; } } #endif @@ -12262,7 +12460,7 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) CALLBACK_CALL(m_cbConnectHook, m_SocketID, errorcode, m_PeerAddr.get(), token); } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING { // Lock GlobControlLock in order to make sure that // the state if the socket having the group and the @@ -12293,21 +12491,45 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) #endif } -void srt::CUDT::addEPoll(const int eid) +// [[using locked_shared(m_GlobControlLock)]] +void CUDT::addEPoll(const int eid) { - enterCS(uglobal().m_EPoll.m_EPollLock); + uglobal().m_EPoll.m_EPollLock.lock(); m_sPollID.insert(eid); - leaveCS(uglobal().m_EPoll.m_EPollLock); + uglobal().m_EPoll.m_EPollLock.unlock(); + + if (m_bListening) + { + // A listener socket can only get readiness on SRT_EPOLL_ACCEPT + // (which has the same value as SRT_EPOLL_IN), or sometimes + // also SRT_EPOLL_UPDATE. All interesting fields for that purpose + // are contained in the CUDTSocket class, so redirect there. + + // NOTE: m_GlobControlLock is required here, but it's already applied + // on this function (see CUDTUnited::epoll_add_usock_INTERNAL) + SRT_EPOLL_T events = m_parent->getListenerEvents(); + + // Only light up the events that were returned, do nothing if none is ready, + // the "no event" state is the default. + if (events) + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, events, true); + + // You don't check anything else here - a listener socket can be only + // used for listening and nothing else. + return; + } if (!stillConnected()) return; - enterCS(m_RecvLock); - if (isRcvBufferReady()) + m_RecvLock.lock(); + // Never update sockets with no receiver buffer; they are member sockets + // and the group owns the buffer. + if (m_pRcvBuffer && isRcvBufferReady()) { uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true); } - leaveCS(m_RecvLock); + m_RecvLock.unlock(); if (m_config.iSndBufSize > m_pSndBuffer->getCurrBufSize()) { @@ -12315,7 +12537,7 @@ void srt::CUDT::addEPoll(const int eid) } } -void srt::CUDT::removeEPollEvents(const int eid) +void CUDT::removeEPollEvents(const int eid) { // clear IO events notifications; // since this happens after the epoll ID has been removed, they cannot be set again @@ -12324,14 +12546,14 @@ void srt::CUDT::removeEPollEvents(const int eid) uglobal().m_EPoll.update_events(m_SocketID, remove, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); } -void srt::CUDT::removeEPollID(const int eid) +void CUDT::removeEPollID(const int eid) { - enterCS(uglobal().m_EPoll.m_EPollLock); + uglobal().m_EPoll.m_EPollLock.lock(); m_sPollID.erase(eid); - leaveCS(uglobal().m_EPoll.m_EPollLock); + uglobal().m_EPoll.m_EPollLock.unlock(); } -void srt::CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) +void CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) { if (evt >= TEV_E_SIZE) return; // sanity check @@ -12339,7 +12561,7 @@ void srt::CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl) m_Slots[evt].push_back(sl); } -void srt::CUDT::DisconnectSignal(ETransmissionEvent evt) +void CUDT::DisconnectSignal(ETransmissionEvent evt) { if (evt >= TEV_E_SIZE) return; // sanity check @@ -12347,7 +12569,7 @@ void srt::CUDT::DisconnectSignal(ETransmissionEvent evt) m_Slots[evt].clear(); } -void srt::CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) +void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) { for (std::vector::iterator i = m_Slots[tev].begin(); i != m_Slots[tev].end(); ++i) { @@ -12355,7 +12577,7 @@ void srt::CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var) } } -int srt::CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) +int CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) { CUDTSocket *s = uglobal().locateSocket(u); if (!s) @@ -12378,7 +12600,7 @@ int srt::CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes) return std::abs(timespan); } -int srt::CUDT::rejectReason(SRTSOCKET u) +int CUDT::rejectReason(SRTSOCKET u) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) @@ -12387,7 +12609,7 @@ int srt::CUDT::rejectReason(SRTSOCKET u) return s->core().m_RejectReason; } -int srt::CUDT::rejectReason(SRTSOCKET u, int value) +SRTSTATUS CUDT::rejectReason(SRTSOCKET u, int value) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) @@ -12397,19 +12619,30 @@ int srt::CUDT::rejectReason(SRTSOCKET u, int value) return APIError(MJ_NOTSUP, MN_INVAL); s->core().m_RejectReason = value; - return 0; + return SRT_STATUS_OK; } -int64_t srt::CUDT::socketStartTime(SRTSOCKET u) +int64_t CUDT::socketStartTime(SRTSOCKET u) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) - return APIError(MJ_NOTSUP, MN_SIDINVAL); + return APIError(MJ_NOTSUP, MN_SIDINVAL).as(); + + const time_point& start_time = s->core().socketStartTime(); + return count_microseconds(start_time.time_since_epoch()); +} + +void CUDT::clearBuffers() +{ + ScopedLock lck(m_RcvBufferLock); + if (m_pRcvBuffer) + m_pRcvBuffer->clear(); - return count_microseconds(s->core().socketStartTime().time_since_epoch()); + if (m_pSndBuffer) + m_pSndBuffer->clear(); } -bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) +bool CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) { // Prepare the information for the hook. @@ -12424,7 +12657,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING bool have_group = false; SRT_GROUP_TYPE gt = SRT_GTYPE_UNDEFINED; #endif @@ -12459,7 +12692,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak // Un-swap on big endian machines ItoHLA(((uint32_t *)target), (uint32_t *)target, blocklen); } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING else if (cmd == SRT_CMD_GROUP) { uint32_t* groupdata = begin + 1; @@ -12483,7 +12716,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak } } -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING if (have_group && acore->m_config.iGroupConnect == 0) { HLOGC(cnlog.Debug, @@ -12514,19 +12747,18 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak return true; } -void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) +void CUDT::processKeepalive(const CPacket& ctrlpkt SRT_ATR_UNUSED, const time_point& tsArrival SRT_ATR_UNUSED) { // Here can be handled some protocol definition // for extra data sent through keepalive. -#if ENABLE_BONDING - if (m_parent->m_GroupOf) +#if SRT_ENABLE_BONDING { - // Lock GlobControlLock in order to make sure that - // the state of 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. + // m_parent->m_GroupOf could theoretically be checked without locking + // because it is either NULL all the lifetime, or it was once set and + // will be cleared during closing. But this handler runs once per a second + // so locking, still necessary for the operation anyway, won't be a burden + // even if this is a non-member socket. ExclusiveLock lock(uglobal().m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) @@ -12534,18 +12766,69 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr // 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); + pg->processKeepalive(m_parent->m_GroupMemberData, ctrlpkt, tsArrival); } } #endif + // XXX This is likely required, but the call in this place may cause + // a potential deadlock. Try maybe to schedule it somehow. +#if 0 ScopedLock lck(m_RcvBufferLock); m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); if (m_config.bDriftTracer) m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); +#endif +} + +// This function should be called when closing the socket internally. +void CUDT::setAgentCloseReason(int reason) +{ + m_AgentCloseReason.compare_exchange(SRT_CLS_UNKNOWN, reason); + + // Do not touch m_PeerCloseReason, it should remain SRT_CLS_UNKNOWN. + // If this reason is already set to some value, then m_AgentCloseReason + // should have been already set to SRT_CLS_PEER. + + m_CloseTimeStamp.compare_exchange(time_point(), steady_clock::now()); +} + +// This function should be called in a handler of UMSG_SHUTDOWN. +void CUDT::setPeerCloseReason(int reason) +{ + m_AgentCloseReason.compare_exchange(SRT_CLS_UNKNOWN, SRT_CLS_PEER); + if (m_AgentCloseReason == SRT_CLS_PEER) + { + m_PeerCloseReason.compare_exchange(SRT_CLS_UNKNOWN, reason); + + m_CloseTimeStamp.compare_exchange(time_point(), steady_clock::now()); + } +} + +void CUDT::copyCloseInfo(SRT_CLOSE_INFO& info) +{ + info.agent = SRT_CLOSE_REASON(m_AgentCloseReason.load()); + info.peer = SRT_CLOSE_REASON(m_PeerCloseReason.load()); + info.time = sync::count_microseconds(m_CloseTimeStamp.load().time_since_epoch()); +} + + +size_t CUDT::payloadSize() const +{ + HLOGC(cnlog.Debug, log << "payloadSize Q: config/exp=" << m_config.zExpPayloadSize + << " max=" << m_iMaxSRTPayloadSize << " " << (m_bConnected? "+":"-") << "connected"); + // If payloadsize is set, it should already be checked that + // it is less than the possible maximum payload size. So return it + // if it is set to nonzero value. In case when the connection isn't + // yet established, return also 0, if the value wasn't set. + if (!m_bConnected || m_config.zExpPayloadSize) + return m_config.zExpPayloadSize; + + // If SRTO_PAYLOADSIZE was remaining with 0 (default for FILE mode) + // then return the maximum payload size per packet. + return m_iMaxSRTPayloadSize; } -namespace srt { HandshakeSide getHandshakeSide(SRTSOCKET u) { return CUDT::handshakeSide(u); @@ -12556,6 +12839,4 @@ HandshakeSide CUDT::handshakeSide(SRTSOCKET u) CUDTSocket *s = uglobal().locateSocket(u); return s ? s->core().handshakeSide() : HSD_DRAW; } -} - - +} // END namespace srt diff --git a/srtcore/core.h b/srtcore/core.h index e5fea5d94..28ba0f4d4 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -71,7 +71,6 @@ modified by #include "packetfilter.h" #include "socketconfig.h" #include "utilities.h" -#include "logger_defs.h" #include "stats.h" @@ -81,28 +80,21 @@ modified by #define SRT_ENABLE_FREQUENT_LOG_TRACE 0 #endif - -// TODO: Utility function - to be moved to utilities.h? -template -inline T CountIIR(T base, T newval, double factor) -{ - if ( base == 0.0 ) - return newval; - - T diff = newval - base; - return base+T(diff*factor); -} +namespace srt { // 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 { + // NOTE: ACKD_TOTAL_SIZE_* fields define version-dependent values: + // - Original UDT, always present (total size: ACKD_TOTAL_SIZE_SMALL): 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. + // - Additional UDT fields, not always attached: // Extra fields for Full ACK. ACKD_RCVSPEED = 4, ACKD_BANDWIDTH = 5, @@ -120,13 +112,8 @@ enum AckDataItem }; const size_t ACKD_FIELD_SIZE = sizeof(int32_t); -#ifdef ENABLE_MAXREXMITBW static const size_t SRT_SOCKOPT_NPOST = 13; -#else -static const size_t SRT_SOCKOPT_NPOST = 12; -#endif - -extern const SRT_SOCKOPT srt_post_opt_list []; +extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST]; enum GroupDataItem { @@ -152,14 +139,13 @@ enum SeqPairItems // Extended SRT Congestion control class - only an incomplete definition required class CCryptoControl; -namespace srt { class CUDTUnited; class CUDTSocket; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING class CUDTGroup; #endif -#ifdef ENABLE_RATE_MEASUREMENT +#ifdef SRT_ENABLE_RATE_MEASUREMENT struct RateMeasurement { typedef sync::steady_clock clock_type; @@ -299,7 +285,7 @@ class CUDT friend class CCC; friend struct CUDTComp; friend class CCache; - friend class CRendezvousQueue; + friend struct CMultiplexer; friend class CSndQueue; friend class CRcvQueue; friend class CSndUList; @@ -322,31 +308,32 @@ class CUDT ~CUDT(); public: //API - static int startup(); - static int cleanup(); + static SRTRUNSTATUS startup(); + static SRTSTATUS cleanup(); static int cleanupAtFork(); static SRTSOCKET socket(); -#if ENABLE_BONDING +#if SRT_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; } + static SRTSTATUS getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize); + static bool isgroup(SRTSOCKET sock) { return (int32_t(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 SRTSTATUS bind(SRTSOCKET u, const sockaddr* name, int namelen); + static SRTSTATUS bind(SRTSOCKET u, UDPSOCKET udpsock); + static SRTSTATUS 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); + static SRTSOCKET connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + static SRTSOCKET connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen); +#if SRT_ENABLE_BONDING + static SRTSOCKET 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); - static int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); - static int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); + static SRTSTATUS close(SRTSOCKET u, int reason); + static SRTSTATUS getpeername(SRTSOCKET u, sockaddr* name, int* namelen); + static SRTSTATUS getsockname(SRTSOCKET u, sockaddr* name, int* namelen); + static SRTSTATUS getsockdevname(SRTSOCKET u, char* name, size_t* namelen); + static SRTSTATUS getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); + static SRTSTATUS 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 = SRT_MSGTTL_INF, bool inorder = false, int64_t srctime = 0); @@ -355,33 +342,37 @@ class CUDT 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, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout); + static int select(int nfds, std::set* readfds, std::set* writefds, std::set* 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 SRTSTATUS epoll_clear_usocks(int eid); + static SRTSTATUS epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + static SRTSTATUS epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + static SRTSTATUS epoll_remove_usock(const int eid, const SRTSOCKET u); + static SRTSTATUS epoll_remove_ssock(const int eid, const SYSSOCKET s); + static SRTSTATUS epoll_update_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + static SRTSTATUS 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_wait2(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, + int64_t msTimeOut, + SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum); 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 SRTSTATUS 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); + static SRTSTATUS bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true, bool instantaneous = false); +#if SRT_ENABLE_BONDING + static SRTSTATUS 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 int getsndbuffer(SRTSOCKET u, size_t* blocks, size_t* bytes); // returns buffer span in [ms] static int rejectReason(SRTSOCKET s); - static int rejectReason(SRTSOCKET s, int value); + static SRTSTATUS rejectReason(SRTSOCKET s, int value); static int64_t socketStartTime(SRTSOCKET s); + static int getMaxPayloadSize(SRTSOCKET u); public: // internal API // This is public so that it can be used directly in API implementation functions. @@ -389,15 +380,20 @@ class CUDT { APIError(const CUDTException&); APIError(CodeMajor, CodeMinor, int = 0); + APIError(int error_code); - operator int() const + // This represents both SRT_ERROR and SRT_INVALID_SOCK. + operator SRTSTATUS() const { return SRT_ERROR; } - }; - static const SRTSOCKET INVALID_SOCK = -1; // Invalid socket descriptor - static const int ERROR = -1; // Socket api error returned value + template + Retval as() const + { + return Retval((int)SRT_ERROR); + } + }; static const int HS_VERSION_UDT4 = 4; static const int HS_VERSION_SRT1 = 5; @@ -416,11 +412,13 @@ class CUDT static const int INITIAL_RTT = 10 * COMM_SYN_INTERVAL_US; static const int INITIAL_RTTVAR = INITIAL_RTT / 2; + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) int handshakeVersion() { return m_ConnRes.m_iVersion; } + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) int32_t handshakeCookie() { return m_ConnReq.m_iCookie; @@ -434,9 +432,9 @@ class CUDT std::string CONID() const { -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING std::ostringstream os; - os << "@" << m_SocketID << ": "; + os << "@" << int(m_SocketID) << ": "; return os.str(); #else return ""; @@ -444,29 +442,39 @@ class CUDT } SRTSOCKET socketID() const { return m_SocketID; } + SRTSOCKET peerID() const { return m_PeerID; } static CUDT* getUDTHandle(SRTSOCKET u); - static std::vector existingSockets(); void addressAndSend(CPacket& pkt); - SRT_ATTR_REQUIRES(m_ConnectionLock) - void sendSrtMsg(int cmd, uint32_t *srtdata_in = NULL, size_t srtlen_in = 0); + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + void sendSrtMsg(int cmd, const 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 avgRTT() const { return m_iSRTT; } int RTTVar() const { return m_iRTTVar; } + duration optimisticRTT() const + { + int avgrtt = m_iSRTT; + int slip = 4 * m_iRTTVar; + // This is mainly to prevent the value from being negative + return sync::microseconds_from(std::max(avgrtt/2, avgrtt - slip)); + } + + SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) int32_t sndSeqNo() const { return m_iSndCurrSeqNo; } int32_t schedSeqNo() const { return m_iSndNextSeqNo; } bool overrideSndSeqNo(int32_t seq); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING + SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) 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; } - SRT_ATTR_REQUIRES(m_RecvAckLock) + SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) int flowWindowSize() const { return m_iFlowWindowSize; } int32_t deliveryRate() const { return m_iDeliveryRate; } int bandwidth() const { return m_iBandwidth; } @@ -477,26 +485,21 @@ class CUDT 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; } - size_t payloadSize() const - { - // If payloadsize is set, it should already be checked that - // it is less than the possible maximum payload size. So return it - // if it is set to nonzero value. In case when the connection isn't - // yet established, return also 0, if the value wasn't set. - if (m_config.zExpPayloadSize || !m_bConnected) - return m_config.zExpPayloadSize; - - // If SRTO_PAYLOADSIZE was remaining with 0 (default for FILE mode) - // then return the maximum payload size per packet. - return m_iMaxSRTPayloadSize; - } + size_t payloadSize() const; - int sndLossLength() { return m_pSndLossList->getLossLength(); } + int sndLossLength() { return m_pSndBuffer->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; } + void updateRejectReason(SRT_REJECT_REASON rr) + { + SRT_REJECT_REASON reason = SRT_REJECT_REASON(m_RejectReason.load()); + if (reason == SRT_REJ_UNKNOWN) + m_RejectReason = rr; + } + /// 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 @@ -528,7 +531,7 @@ class CUDT /// Returns the number of packets in flight (sent, but not yet acknowledged). /// @returns The number of packets in flight belonging to the interval [0; ...) - SRT_ATTR_REQUIRES(m_RecvAckLock) + SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) int32_t getFlightSpan() const { return getFlightSpan(m_iSndLastAck, m_iSndCurrSeqNo); @@ -561,22 +564,23 @@ class CUDT /// @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) + SRT_TSA_NEEDS_NONLOCKED(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) + SRT_TSA_NEEDS_NONLOCKED(m_StatsLock) void setDataPacketTS(CPacket& p, const time_point& ts); // Utility used for closing a listening socket // immediately to free the socket - void notListening() + int notListening() { m_bListening = false; - m_pRcvQueue->removeListener(this); + m_pMuxer->removeListener(this); + return m_pMuxer->id(); } static int32_t generateISN() @@ -593,14 +597,18 @@ class CUDT 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(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 breakAsUnstable() + { + m_bBreakAsUnstable = true; + setAgentCloseReason(SRT_CLS_UNSTABLE); + } void ConnectSignal(ETransmissionEvent tev, EventSlot sl); void DisconnectSignal(ETransmissionEvent tev); @@ -612,6 +620,17 @@ class CUDT typedef loss_seqs_t packetArrival_cb(void*, CPacket&); CallbackHolder m_cbPacketArrival; + bool stillConnected() + { + // Still connected is when: + // - no "broken" condition appeared (security, protocol error, response timeout) + return !m_bBroken + // - still connected (no one called srt_close()) + && m_bConnected + // - isn't currently closing (srt_close() called, response timeout, shutdown) + && !m_bClosing; + } + private: /// initialize a UDT entity and bind to a local address. void open(); @@ -623,6 +642,8 @@ class CUDT /// @param peer [in] The address of the listening UDT entity. void startConnect(const sockaddr_any& peer, int32_t forced_isn); + void registerConnector(const sockaddr_any& addr, const time_point& ttl); + /// Process the response handshake packet. Failure reasons can be: /// * Socket is not in connecting state /// * Response @a pkt is not a handshake control message @@ -631,7 +652,8 @@ class CUDT /// @retval 0 Connection successful /// @retval 1 Connection in progress (m_ConnReq turned into RESPONSE) /// @retval -1 Connection failed - SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) EConnectStatus processConnectResponse(const CPacket& pkt, CUDTException* eout) ATR_NOEXCEPT; // This function works in case of HSv5 rendezvous. It changes the state @@ -641,7 +663,10 @@ 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(UDTRequestType& rsptype, bool& needs_extension, bool& needs_hsrsp); + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + void rendezvousSwitchState(UDTRequestType& rsptype, int& w_need_ext); + + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) void cookieContest(); /// Interpret the incoming handshake packet in order to perform appropriate @@ -651,23 +676,33 @@ class CUDT /// @param response incoming handshake response packet to be interpreted /// @param serv_addr incoming packet's address /// @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 SRT_ATTR_REQUIRES(m_ConnectionLock) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(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) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) bool prepareBuffers(CUDTException* eout); + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) int getAuthTagSize() const; - SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) EConnectStatus postConnect(const CPacket* response, bool rendezvous, CUDTException* eout) ATR_NOEXCEPT; - SRT_ATR_NODISCARD bool applyResponseSettings(const CPacket* hspkt /*[[nullable]]*/) ATR_NOEXCEPT; + // This should be called in case when a blocking mode connect + // should continue and return success or failure. + void notifyBlockingConnect(); + + + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + 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_any& serv_addr); SRT_ATR_NODISCARD EConnectStatus craftKmResponse(uint32_t* aw_kmdata, size_t& w_kmdatasize); @@ -678,33 +713,44 @@ 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 SRT_ATTR_REQUIRES(m_ConnectionLock) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(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); + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + size_t fillHsExtConfigString(uint32_t *pcmdspec, int cmd, const std::string &str); +#if SRT_ENABLE_BONDING + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + size_t fillHsExtGroup(uint32_t *pcmdspec); #endif - SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(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 + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + 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 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 interpretSrtHandshake(CUDTSocket* lsn, const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data, size_t* out_len); SRT_ATR_NODISCARD bool checkApplyFilterConfig(const std::string& cs); -#if ENABLE_BONDING - static CUDTGroup& newGroup(const int); // defined EXCEPTIONALLY in api.cpp for convenience reasons +#if SRT_ENABLE_BONDING // 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); + SRT_ATR_NODISCARD bool interpretGroup(CUDTSocket* listener, const int32_t grpdata[], size_t data_size, int hsreq_type_cmd); + SRT_ATR_NODISCARD + // XXX These below can't be set because the header file has no access to these field definitions + //SRT_TSA_NEEDS_LOCKED(CUDTUnited::m_GlobControlLock) + //SRT_TSA_NEEDS_NONLOCKED(CUDTGroup::m_GroupLock) + SRTSOCKET makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE tp, uint32_t link_flags, SRTSOCKET listener); void synchronizeWithGroup(CUDTGroup* grp); #endif @@ -713,11 +759,13 @@ class CUDT void updateSrtRcvSettings(); void updateSrtSndSettings(); - void updateIdleLinkFrom(CUDT* source); + void updateIdleLinkFrom(int32_t seq, SRTSOCKET id); /// @brief Drop packets too late to be delivered if any. /// @returns the number of packets actually dropped. - SRT_ATTR_REQUIRES2(m_RecvAckLock, m_StatsLock) + SRT_TSA_NEEDS_NONLOCKED(m_RecvAckLock) + SRT_TSA_NEEDS_NONLOCKED(m_StatsLock) + SRT_TSA_NEEDS_LOCKED(m_SendLock) int sndDropTooLate(); // Returns true if there is a regular packet waiting for sending @@ -736,7 +784,17 @@ class CUDT /// @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(CUDTSocket* lsn, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& hs); + + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + bool createSendHSResponse(uint32_t* kmdata, size_t kmdatasize, const CNetworkInterface& hsaddr, CHandShake& w_hs) ATR_NOTHROW; + + SRT_TSA_NEEDS_NONLOCKED(m_ConnectionLock) + bool createSendHSResponse_WITHLOCK(uint32_t* kmdata, size_t kmdatasize, const CNetworkInterface& hsaddr, CHandShake& w_hs) ATR_NOTHROW + { + sync::ScopedLock lk (m_ConnectionLock); + return createSendHSResponse(kmdata, kmdatasize, hsaddr, (w_hs)); + } /// Write back to the hs structure the data after they have been /// negotiated by acceptAndRespond. @@ -745,11 +803,14 @@ class CUDT /// Close the opened UDT entity. - bool closeInternal() ATR_NOEXCEPT; + bool closeEntity(int reason) ATR_NOEXCEPT; bool closeAtFork() ATR_NOEXCEPT; void updateBrokenConnection(); void completeBrokenConnectionDependencies(int errorcode); + void setAgentCloseReason(int reason); + void setPeerCloseReason(int reason); + /// 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. /// @param len [in] The size of the data block. @@ -783,6 +844,10 @@ class CUDT SRT_ATR_NODISCARD int sendmsg2(const char* data, int len, SRT_MSGCTRL& w_m); + SRT_ATR_NODISCARD int sendMessageInternal(const char* data, int len, void* selink, SRT_MSGCTRL& w_m); + + void updateCryptoOnSending(); + 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*/); @@ -822,7 +887,7 @@ class CUDT void getOpt(SRT_SOCKOPT optName, void* optval, int& w_optlen); -#if ENABLE_BONDING +#if SRT_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); @@ -840,16 +905,20 @@ class CUDT /// removes the loss record from both current receiver loss list and /// the receiver fresh loss list. void unlose(const CPacket& oldpacket); - void dropFromLossLists(int32_t from, int32_t to); - SRT_ATTR_REQUIRES(m_RecvAckLock) + SRT_TSA_NEEDS_NONLOCKED(m_RcvLossLock) // will scope-lock it inside + void dropFromLossLists(int32_t from, int32_t to); + SRT_TSA_NEEDS_NONLOCKED(m_RcvBufferLock) + // SRT_TSA_NEEDS_LOCKED(m_RecvAckLock) // <<-- XXX levaing now, but must be investigated bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); - SRT_ATTR_EXCLUDES(m_ConnectionLock) + /// Checks if Legacy HSv4 in-connection handshake should be updated + /// and KMX message resent (when key change period passed and the packet was lost). + SRT_TSA_NEEDS_NONLOCKED(m_ConnectionLock) void checkSndTimers(); /// @brief Check and perform KM refresh if needed. - void checkSndKMRefresh(); + bool checkSndKMRefresh(int* aw_keyindex); void handshakeDone() { @@ -866,17 +935,6 @@ class CUDT return double(basebw) * 8.0/1000000.0; } - bool stillConnected() - { - // Still connected is when: - // - no "broken" condition appeared (security, protocol error, response timeout) - return !m_bBroken - // - still connected (no one called srt_close()) - && m_bConnected - // - isn't currently closing (srt_close() called, response timeout, shutdown) - && !m_bClosing; - } - int sndSpaceLeft() { return static_cast(sndBuffersLeft() * maxPayloadSize()); @@ -893,13 +951,13 @@ class CUDT return m_stats.tsStartTime; } - SRT_ATTR_EXCLUDES(m_RcvBufferLock) + SRT_TSA_NEEDS_NONLOCKED(m_RcvBufferLock) bool isRcvBufferReady() const; - SRT_ATTR_REQUIRES(m_RcvBufferLock) + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) bool isRcvBufferReadyNoLock() const; - SRT_ATTR_EXCLUDES(m_RcvBufferLock) + SRT_TSA_NEEDS_NONLOCKED(m_RcvBufferLock) bool isRcvBufferFull() const; // TSBPD thread main function. @@ -916,32 +974,34 @@ class CUDT /// @param seqno [in] The sequence number of the first packets following those to be dropped. /// @param reason A reason for dropping (see @a DropReason). /// @return The number of packets dropped. - SRT_ATTR_EXCLUDES(m_RcvBufferLock, m_RcvLossLock) + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + SRT_TSA_NEEDS_NONLOCKED(m_RcvLossLock) int rcvDropTooLateUpTo(int seqno, DropReason reason = DROP_TOO_LATE); static loss_seqs_t defaultPacketArrival(void* vself, CPacket& pkt); static loss_seqs_t groupPacketArrival(void* vself, CPacket& pkt); - void setRateEstimator(const CRateEstimator& rate) + void restoreRateEstimator(const CRateEstimator& rate) { if (!m_pSndBuffer) return; - m_pSndBuffer->setRateEstimator(rate); + m_pSndBuffer->restoreEstimation(rate); updateCC(TEV_SYNC, EventVariant(0)); } private: // Identification - CUDTSocket* const m_parent; // Temporary, until the CUDTSocket class is merged with CUDT - SRTSOCKET m_SocketID; // UDT socket number - SRTSOCKET m_PeerID; // Peer ID, for multiplexer + CUDTSocket* const m_parent; // Temporary, until the CUDTSocket class is merged with CUDT + SocketHolder::sockiter_t m_MuxNode; + SRTSOCKET m_SocketID; // UDT socket number + SRTSOCKET m_PeerID; // Peer ID, for multiplexer // HSv4 (legacy handshake) support) time_point m_tsSndHsLastTime; // Last SRT handshake request time int m_iSndHsRetryCnt; // SRT handshake retries left -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING SRT_GROUP_TYPE m_HSGroupType; // Group type about-to-be-set in the handshake #endif @@ -950,8 +1010,12 @@ class CUDT 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 + + // XXX Controversial because it is being stated + // that it should be guarded during creation in the + // initial time, but it's not guarded during dispatching + // of the handshake. + CCryptoControl m_CryptoControl; CCache* m_pCache; // Network information cache // Congestion control @@ -976,8 +1040,18 @@ class CUDT 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_bManaged; // The socket should be closed automatically if broken sync::atomic m_RejectReason; - bool m_bOpened; // If the UDT entity has been opened + + // If the socket was closed by some reason locally, the reason is + // in m_AgentCloseReason and the m_PeerCloseReason is then SRT_CLS_UNKNOWN. + // If the socket was closed due to reception of UMSG_SHUTDOWN, the reason + // extracted from the message is written to m_PeerCloseReason and the + // m_AgentCloseReason == SRT_CLS_PEER. + sync::atomic m_AgentCloseReason; + sync::atomic m_PeerCloseReason; + atomic_time_point m_CloseTimeStamp; // Time when the close reason was first set + sync::atomic m_bOpened; // If the UDT entity has been opened // 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. @@ -994,23 +1068,24 @@ class CUDT sync::atomic m_iDeliveryRate; // Packet arrival rate at the receiver side sync::atomic m_iByteDeliveryRate; // Byte arrival rate at the receiver side + SRT_TSA_GUARDED_BY(m_ConnectionLock) CHandShake m_ConnReq; // Connection request + SRT_TSA_GUARDED_BY(m_ConnectionLock) 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) private: // Sending related data CSndBuffer* m_pSndBuffer; // Sender buffer - CSndLossList* m_pSndLossList; // Sender loss list CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW size_t m_zSndAveragePacketSize; size_t m_zSndMaxPacketSize; // XXX Old rate estimator for rexmit // CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation. CShaper m_SndRexmitShaper; -#ifdef ENABLE_RATE_MEASUREMENT +#ifdef SRT_ENABLE_RATE_MEASUREMENT RateMeasurement m_SndRegularMeasurement; // Regular rate measurement RateMeasurement m_SndRexmitMeasurement; // Retransmission rate measurement #endif @@ -1020,7 +1095,7 @@ class CUDT atomic_duration m_tdSendTimeDiff; // Aggregate difference in inter-packet sending time - SRT_ATTR_GUARDED_BY(m_RecvAckLock) + SRT_TSA_GUARDED_BY(m_RecvAckLock) sync::atomic m_iFlowWindowSize; // Flow control window size sync::atomic m_iCongestionWindow; // Congestion window size @@ -1031,7 +1106,9 @@ class CUDT duration m_tdACKInterval; // ACK interval duration m_tdNAKInterval; // NAK interval - SRT_ATTR_GUARDED_BY(m_RecvAckLock) + // XXX Controversial because it is often updated without locking, + // but then it holds the time when the response has come. + SRT_TSA_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) @@ -1049,20 +1126,13 @@ class CUDT time_point m_tsNextSendTime; // Scheduled time of next packet sending sync::atomic m_iSndLastFullAck; // Last full ACK received - SRT_ATTR_GUARDED_BY(m_RecvAckLock) + SRT_TSA_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 - SRT_ATTR_GUARDED_BY(m_RecvAckLock) + SRT_TSA_GUARDED_BY(m_RecvAckLock) sync::atomic m_iSndCurrSeqNo; // The largest sequence number that HAS BEEN SENT + atomic_time_point m_tsSndNextUnique; + sync::atomic m_iSndNextSeqNo; // The sequence number predicted to be placed at the currently scheduled packet // Note important differences between Curr and Next fields: @@ -1079,10 +1149,11 @@ class CUDT int32_t m_iSndLastAck2; // Last ACK2 sent back time_point m_SndLastAck2Time; // The time when last ACK2 was sent back + + SRT_TSA_DISABLED // because this should be run only on initialization void setInitialSndSeq(int32_t isn) { m_iSndLastAck = isn; - m_iSndLastDataAck = isn; m_iSndLastFullAck = isn; m_iSndCurrSeqNo = CSeqNo::decseq(isn); m_iSndNextSeqNo = isn; @@ -1091,13 +1162,15 @@ class CUDT void setInitialRcvSeq(int32_t isn); + sync::atomic m_iSndMinFlightSpan; // updated with every ACK, number of packets in flight at ACK + 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 + bool m_bPeerRexmitFlag; // Receiver supports rexmit flag in payload packets (XXX deprecated) - SRT_ATTR_GUARDED_BY(m_RecvAckLock) + SRT_TSA_GUARDED_BY(m_RecvAckLock) int32_t m_iReXmitCount; // Re-Transmit Count since last ACK static const size_t @@ -1118,11 +1191,11 @@ class CUDT bool frequentLogAllowed(size_t logid, const time_point& tnow, std::string& why); private: // Receiving related data - SRT_ATTR_GUARDED_BY(m_RcvBufferLock) + SRT_TSA_PT_GUARDED_BY(m_RcvBufferLock) CRcvBuffer* m_pRcvBuffer; //< Receiver buffer - SRT_ATTR_GUARDED_BY(m_RcvLossLock) + SRT_TSA_PT_GUARDED_BY(m_RcvLossLock) CRcvLossList* m_pRcvLossList; //< Receiver loss list - SRT_ATTR_GUARDED_BY(m_RcvLossLock) + SRT_TSA_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 @@ -1133,8 +1206,8 @@ class CUDT CPktTimeWindow<16, 64> m_RcvTimeWindow; // Packet arrival time window int32_t m_iRcvLastAck; // First unacknowledged packet seqno sent in the latest ACK. -#ifdef ENABLE_LOGGING - int32_t m_iDebugPrevLastAck; +#if HVU_ENABLE_LOGGING + sync::atomic m_iDebugPrevLastAck; #endif 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 @@ -1147,22 +1220,25 @@ class CUDT uint32_t m_uPeerSrtFlags; bool m_bTsbPd; // Peer sends TimeStamp-Based Packet Delivery Packets + + // XXX This field is likely unused and deprecated. Check the common + // receiver buffer feature if it has removed it. bool m_bGroupTsbPd; // TSBPD should be used for GROUP RECEIVER instead - SRT_ATTR_GUARDED_BY(m_RcvTsbPdStartupLock) + SRT_TSA_GUARDED_BY(m_RcvTsbPdStartupLock) 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_bTsbPdNeedsWakeup; // Signal TsbPd thread to wake up on RCV buffer state change. + sync::atomic m_bTsbPdNeedsWakeup; // Expected to wake up TSBPD when a read-ready data packet is received. sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creation and joining. CallbackHolder m_cbAcceptHook; CallbackHolder m_cbConnectHook; // FORWARDER public: - static int installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); - static int installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); - static enum HandshakeSide compareCookies(int32_t req, int32_t res); - static enum HandshakeSide backwardCompatibleCookieContest(int32_t req, int32_t res); + static SRTSTATUS installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); + static SRTSTATUS installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); + static HandshakeSide compareCookies(int32_t req, int32_t res); + static HandshakeSide backwardCompatibleCookieContest(int32_t req, int32_t res); private: void installAcceptHook(srt_listen_callback_fn* hook, void* opaq) { @@ -1216,8 +1292,9 @@ class CUDT // 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); + SRT_ATR_NODISCARD + SRT_TSA_NEEDS_LOCKED(m_ConnectionLock) + bool createCrypter(HandshakeSide side); private: // Generation and processing of packets void sendCtrl(UDTMessageType pkttype, const int32_t* lparam = NULL, void* rparam = NULL, int size = 0); @@ -1257,32 +1334,49 @@ class CUDT void processCtrlDropReq(const CPacket& ctrlpkt); /// @brief Process incoming shutdown control packet - void processCtrlShutdown(); + void processCtrlShutdown(const CPacket& ctrlpkt); /// @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. + /// @brief Update sender side socket data according to incoming ACK message. + /// + /// Incoming ACK message marks a point behind which everything is considered + /// received correctly, or at least there's no need to worry about it. This + /// requires to forget anything that refers to packets prior to this number. + /// In case of a group member, this number reflects this state also for the + /// whole group. + /// /// @param ackdata_seqno sequence number of a data packet being acknowledged - void updateSndLossListOnACK(int32_t ackdata_seqno); + bool revokeACKedSequences(int32_t ackdata_seqno, int32_t& w_last_sent_seqno); /// Pack a packet from a list of lost packets. /// @param packet [in, out] a packet structure to fill /// @return payload size on success, <=0 on failure - int packLostData(CPacket &packet); + int packLostData(CSndPacket &packet); + + duration minRexmitInterval() + { + if (m_bPeerNakReport && m_config.iRetransmitAlgo != 0) + { + // Minimum required time interval since the last retransmission request. + return optimisticRTT(); + } + return duration(); + } + bool retransmissionRateFit(size_t granted_size); + void retransmissionConsumeLength(size_t payload_size); - std::pair getCleanRexmitOffset(); - bool checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time); - int extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, - srt::sync::steady_clock::time_point& w_tsOrigin); /// 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); + bool packUniqueData(CSndPacket& packet); - /// Pack in CPacket the next data to be send. + /// Pack in CPacket the next data to be send. If the call succeeds, the sequence number + /// of the packet is reserved and guaranteed to stay in the buffer. This should be later + /// released by calling releaseSend(packet). /// /// @param packet [out] a CPacket structure to fill /// @param nexttime [out] Time when this socket should be next time picked up for processing. @@ -1290,11 +1384,18 @@ class CUDT /// /// @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); + bool packData(CSndPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); + bool releaseSend(); + void removeSndLossUpTo(int32_t seq); - /// Also excludes srt::CUDTUnited::m_GlobControlLock. - SRT_ATTR_EXCLUDES(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) - int processData(CUnit* unit); +#if USE_RECEIVER_UNIT_POOL + SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) + int acquireDataPacket(CPacketUnitPool::UnitPtr& in_unit, CRcvQueue* provider); +#else + /// Also needs unlocked srt::CUDTUnited::m_GlobControlLock. + SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) + int processData(CUnit* unit, CRcvQueue* provider); +#endif /// This function passes the incoming packet to the initial processing /// (like packet filter) and is about to store it effectively to the @@ -1303,22 +1404,43 @@ class CUDT /// /// @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_next_tsbpd [out] Get the TSBPD time of the earliest playable packet after insertion /// @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); + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + int handleSocketPacketReception(std::vector& incoming, bool& w_new_inserted, time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); + + /// Check if the packet SHOULD BE decrypted, and decrypt it if needed. + /// False is returned if: + /// * The packet is not encrypted, while KMSTATE is SECURED ("should be" encrypted) + /// * The decryption process failed + /// + /// @param w_packet Packet to be possibly decrypted + /// @return True if the packet need not be decrypted, or was successfully decrypted. + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + bool handlePacketDecryption(CPacket& w_packet); + +#if SRT_ENABLE_BONDING + bool handleGroupPacketReception(CUDTGroup* grp, std::vector& incoming, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); +#endif - /// Get the packet's TSBPD time - - /// the time when it is passed to the reading application. - /// The @a grp passed by void* is not used yet - /// and shall not be used when ENABLE_BONDING=0. + // This function is to return the packet's play time (time when + // it is submitted to the reading application) of the given packet. + // The version with `void*` is provided because the function body + // is mostly common for bonding and non-bonding, while it needs + // access to groups anyway, if present, and gets NULL in worst case. +#if SRT_ENABLE_BONDING + time_point getPktTsbPdTime(CUDTGroup* grp, const CPacket& packet); +#else time_point getPktTsbPdTime(void* grp, const CPacket& packet); +#endif - SRT_ATTR_EXCLUDES(m_RcvTsbPdStartupLock) /// Checks and spawns the TSBPD thread if required. + SRT_TSA_NEEDS_NONLOCKED(m_RcvTsbPdStartupLock) int checkLazySpawnTsbPdThread(); void processClose(); @@ -1337,15 +1459,17 @@ class CUDT void processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival); - SRT_ATTR_REQUIRES(m_RcvBufferLock) /// Retrieves the available size of the receiver buffer. /// Expects that m_RcvBufferLock is locked. + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) size_t getAvailRcvBufferSizeNoLock() const; + void clearBuffers(); + private: // Trace struct CoreStats { - time_point tsStartTime; // timestamp when the UDT entity is started + atomic_time_point tsStartTime; // timestamp when the UDT entity is started stats::Sender sndr; // sender statistics stats::Receiver rcvr; // receiver statistics @@ -1365,8 +1489,10 @@ class CUDT static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq static const int PACKETPAIR_MASK = 0xF; + void copyCloseInfo(SRT_CLOSE_INFO&); + private: // Timers functions -#if ENABLE_BONDING +#if SRT_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 @@ -1383,21 +1509,21 @@ class CUDT 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); + void checkBlindRexmitTimer(const time_point& currtime); private: // for UDP multiplexer - CSndQueue* m_pSndQueue; // packet sending queue - CRcvQueue* m_pRcvQueue; // packet receiving queue + CMultiplexer* m_pMuxer; + + // XXX THIS FIELD IS DUPLICATED IN CUDTSocket!!! sockaddr_any m_PeerAddr; // peer address - sockaddr_any m_SourceAddr; // override UDP source address with this one when sending + CNetworkInterface 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 + int m_TransferIPVersion; // AF_INET/6 that should be used to determine common payload size public: // For SrtCongestion - const CSndQueue* sndQueue() { return m_pSndQueue; } - const CRcvQueue* rcvQueue() { return m_pRcvQueue; } + const CMultiplexer* muxer() { return m_pMuxer; } + const CChannel* channel() { return m_pMuxer ? m_pMuxer->channel(): (CChannel*)NULL; } private: // for epoll std::set m_sPollID; // set of epoll ID to trigger diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index 68d551e58..385520a1d 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -13,6 +13,8 @@ written by Haivision Systems Inc. *****************************************************************************/ +#include "crypto.h" + #include "platform_sys.h" #include @@ -20,24 +22,22 @@ written by #include #include -#include "udt.h" #include "utilities.h" #include -#include "crypto.h" #include "logging.h" #include "core.h" #include "api.h" -using namespace srt_logging; +using namespace srt::logging; + +namespace srt +{ + +const size_t SRT_MAX_KMRETRY SRT_ATR_UNUSED = 10; + +const size_t SRT_CMD_KMREQ_SZ = HCRYPT_MSG_KM_MAX_SZ; +SRT_STATIC_ASSERT(SRT_CMD_KMREQ_SZ <= SRT_CMD_MAXSZ, "error: SRT_CMD_MAXSZ too small"); -#define SRT_MAX_KMRETRY 10 - -//#define SRT_CMD_KMREQ 3 /* HaiCryptTP SRT Keying Material */ -//#define SRT_CMD_KMRSP 4 /* HaiCryptTP SRT Keying Material ACK */ -#define SRT_CMD_KMREQ_SZ HCRYPT_MSG_KM_MAX_SZ /* */ -#if SRT_CMD_KMREQ_SZ > SRT_CMD_MAXSZ -#error SRT_CMD_MAXSZ too small -#endif /* Key Material Request (Network Order) See HaiCryptTP SRT (hcrypt_xpt_srt.c) */ @@ -45,8 +45,6 @@ using namespace srt_logging; // 10* HAICRYPT_DEF_KM_PRE_ANNOUNCE const int SRT_CRYPT_KM_PRE_ANNOUNCE SRT_ATR_UNUSED = 0x10000; -namespace srt_logging -{ std::string KmStateStr(SRT_KM_STATE state) { switch (state) @@ -57,27 +55,14 @@ 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]; -#if defined(_MSC_VER) && _MSC_VER < 1900 - _snprintf(buf, sizeof(buf) - 1, "??? (%d)", state); -#else - snprintf(buf, sizeof(buf), "??? (%d)", state); -#endif - return buf; - } + return hvu::fmtcat("??? (", int(state), ")"); } } -} // namespace -using srt_logging::KmStateStr; - -void srt::CCryptoControl::globalInit() +void CCryptoControl::globalInit() { #ifdef SRT_ENABLE_ENCRYPTION // We need to force the Cryspr to be initialized during startup to avoid the @@ -86,7 +71,7 @@ void srt::CCryptoControl::globalInit() #endif } -bool srt::CCryptoControl::isAESGCMSupported() +bool CCryptoControl::isAESGCMSupported() { #ifdef SRT_ENABLE_ENCRYPTION return HaiCrypt_IsAESGCM_Supported() != 0; @@ -95,8 +80,8 @@ bool srt::CCryptoControl::isAESGCMSupported() #endif } -#if ENABLE_LOGGING -std::string srt::CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen) +#if HVU_ENABLE_LOGGING +std::string CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen) { std::ostringstream os; os << hdr << ": cmd=" << cmd << "(" << (cmd == SRT_CMD_KMREQ ? "KMREQ":"KMRSP") <<") len=" @@ -107,7 +92,7 @@ std::string srt::CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_ } #endif -void srt::CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) +void CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) { if (cmd == SRT_CMD_KMREQ) { @@ -123,7 +108,7 @@ void srt::CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED) } } -void srt::CCryptoControl::createFakeSndContext() +void CCryptoControl::createFakeSndContext() { if (!m_iSndKmKeyLen) m_iSndKmKeyLen = 16; @@ -135,7 +120,7 @@ void srt::CCryptoControl::createFakeSndContext() } } -int srt::CCryptoControl::processSrtMsg_KMREQ( +int CCryptoControl::processSrtMsg_KMREQ( const uint32_t* srtdata SRT_ATR_UNUSED, size_t bytelen SRT_ATR_UNUSED, int hsv SRT_ATR_UNUSED, unsigned srtv SRT_ATR_UNUSED, @@ -155,8 +140,6 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( // what has called this function. The HSv5 handshake only enforces bidirectional // connection. - 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 @@ -168,6 +151,8 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( bool wasb4 SRT_ATR_UNUSED = false; size_t sek_len = 0; + sync::ScopedLock lck(m_mtxLock); + 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); @@ -201,7 +186,7 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( m_iRcvKmKeyLen = sek_len; // Overwrite the key length anyway - it doesn't make sense to somehow // keep the original setting because it will only make KMX impossible. -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING if (m_iSndKmKeyLen != m_iRcvKmKeyLen) { LOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen @@ -221,6 +206,9 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( } wasb4 = m_hRcvCrypto; + // XXX Note that this is called "just in case", although as this function + // should be called only on the KMREQ as message, it should take place only + // in case of a running connection, and m_hRcvCrypto should be already created. if (!createCryptoCtx((m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM)) { LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."); @@ -256,7 +244,7 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"); break; case HAICRYPT_ERROR_CIPHER: -#ifdef ENABLE_AEAD_API_PREVIEW +#ifdef SRT_ENABLE_AEAD m_RcvKmState = m_SndKmState = SRT_KM_S_BADCRYPTOMODE; #else m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback. @@ -281,70 +269,53 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( 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 (m_RcvKmState == SRT_KM_S_SECURED) { - // Note: 'bidirectional' means that we want a bidirectional key update, - // which happens only and exclusively with HSv5 handshake - not when the - // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used - // in HSv4 versions also to initialize the first key, unlike HSv5). - if (m_RcvKmState == SRT_KM_S_SECURED) + HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153); + + if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto) { - if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto) + m_iSndKmKeyLen = m_iRcvKmKeyLen; + if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, (&m_hSndCrypto)) != HAICRYPT_OK) { - m_iSndKmKeyLen = m_iRcvKmKeyLen; - if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK) - { - 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 - m_SndKmState = SRT_KM_S_NOSECRET; - } + 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 - { - m_SndKmState = SRT_KM_S_SECURED; - } - - 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); - m_SndKmMsg[0].MsgLen = bytelen; - m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :) + m_SndKmState = SRT_KM_S_NOSECRET; } else { - HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " - << KmStateStr(m_SndKmState) << " state"); + m_SndKmState = SRT_KM_S_SECURED; + HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153); } + + 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); + m_SndKmMsg[0].MsgLen = bytelen; + m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :) } else { - HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " + << KmStateStr(m_SndKmState) << " state"); } } else { - HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX."); + HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); } -#ifdef SRT_ENABLE_ENCRYPTION - if (m_hRcvCrypto != NULL) - HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153); - if (m_hSndCrypto != NULL) - HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153); -#endif - return SRT_CMD_KMRSP; HSv4_ErrorReport: - if (bidirectional && hasPassphrase()) + if (hasPassphrase()) { // If the Forward KMX process has failed, the reverse-KMX process was not done at all. // This will lead to incorrect object configuration and will fail to properly declare @@ -368,7 +339,7 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( return SRT_CMD_KMRSP; } -int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) +int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) { /* All 32-bit msg fields (if present) swapped on reception * But HaiCrypt expect network order message @@ -380,6 +351,8 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len int retstatus = -1; + sync::ScopedLock lck(m_mtxLock); + // 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; @@ -416,7 +389,7 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len m_SndKmState = SRT_KM_S_UNSECURED; retstatus = 0; break; -#ifdef ENABLE_AEAD_API_PREVIEW +#ifdef SRT_ENABLE_AEAD 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; @@ -480,7 +453,7 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len return retstatus; } -void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SRT_ATR_UNUSED) +void CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SRT_ATR_UNUSED) { sync::ScopedLock lck(m_mtxLock); if (!m_hSndCrypto || m_SndKmState == SRT_KM_S_UNSECURED) @@ -490,7 +463,7 @@ void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SR return; } #ifdef SRT_ENABLE_ENCRYPTION - srt::sync::steady_clock::time_point now = srt::sync::steady_clock::now(); + sync::steady_clock::time_point now = sync::steady_clock::now(); /* * Crypto Key Distribution to peer: * If... @@ -501,7 +474,7 @@ void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SR * then (re-)send handshake request. */ if (((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0)) - && ((m_SndKmLastTime + srt::sync::microseconds_from((iSRTT * 3)/2)) <= now)) + && ((m_SndKmLastTime + sync::microseconds_from((iSRTT * 3)/2)) <= now)) { for (int ki = 0; ki < 2; ki++) { @@ -518,20 +491,26 @@ void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SR #endif } -void srt::CCryptoControl::regenCryptoKm(CUDT* sock SRT_ATR_UNUSED, bool bidirectional SRT_ATR_UNUSED) +bool CCryptoControl::regenCryptoKm_INTERNAL(int* aw_keyindex SRT_ATR_UNUSED) { + int sent = 0; + #ifdef SRT_ENABLE_ENCRYPTION + + SRT_ASSERT(!aw_keyindex || aw_keyindex[0] == -1); + sync::ScopedLock lck(m_mtxLock); if (!m_hSndCrypto) - return; + return false; void *out_p[2]; size_t out_len_p[2]; int nbo = HaiCrypt_Tx_ManageKeys(m_hSndCrypto, out_p, out_len_p, 2); - int sent = 0; HLOGC(cnlog.Debug, log << "regenCryptoKm: regenerating crypto keys nbo=" << nbo << - " THEN=" << (sock ? "SEND" : "KEEP") << " DIR=" << (bidirectional ? "BOTH" : "SENDER")); + " THEN=" << (aw_keyindex ? "SEND" : "KEEP")); + + int kiw = 0; for (int i = 0; i < nbo && i < 2; i++) { @@ -557,9 +536,9 @@ void srt::CCryptoControl::regenCryptoKm(CUDT* sock SRT_ATR_UNUSED, bool bidirect /* New Keying material, send to peer */ 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; + m_SndKmMsg[ki].iPeerRetry = SRT_MAX_KMRETRY; - if (bidirectional && !sock) + if (!aw_keyindex) { // "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 @@ -571,35 +550,32 @@ void srt::CCryptoControl::regenCryptoKm(CUDT* sock SRT_ATR_UNUSED, bool bidirect // Not sure if anything has to be reported. } } - - if (sock) + else { - HLOGC(cnlog.Debug, log << "regenCryptoKm: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen - << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry); - sock->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t)); + aw_keyindex[kiw++] = ki; sent++; } } - else if (out_len_p[i] == 0) - { - HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": not generated"); - } else { - HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": key unchanged"); + HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": " + << (out_len_p[i] == 0 ? "not generated" : "key unchanged")); } } 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); + // We didn't send obviously, but we have reported the necessity to send + // and we believe that the caller will do as required. if (sent) - m_SndKmLastTime = srt::sync::steady_clock::now(); + m_SndKmLastTime = sync::steady_clock::now(); #endif + return sent; } -srt::CCryptoControl::CCryptoControl(SRTSOCKET id) - : m_SocketID(id) +CCryptoControl::CCryptoControl() + : m_SocketID(SRT_INVALID_SOCK) , m_iSndKmKeyLen(0) , m_iRcvKmKeyLen(0) , m_SndKmState(SRT_KM_S_UNSECURED) @@ -621,16 +597,18 @@ srt::CCryptoControl::CCryptoControl(SRTSOCKET id) m_hRcvCrypto = NULL; } -bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool bidirectional SRT_ATR_UNUSED, bool bUseGcm153 SRT_ATR_UNUSED) +// NOTE: +// MUTEX LOCKING NOT USED because this function is called +// during connection, where no updates or packet exchange is expected. +bool CCryptoControl::init(SRTSOCKET id, HandshakeSide side, const CSrtConfig& cfg, + bool bUseGcm153 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. + // NOTE: INITIATOR creates m_hSndCrypto and m_hRcvCrypto with the same key + // length. RESPONDER creates nothing - it will create appropriate contexts + // when receiving KMREQ from the INITIATOR. HLOGC(cnlog.Debug, log << "CCryptoControl::init: HS SIDE:" - << (side == HSD_INITIATOR ? "INITIATOR" : "RESPONDER") - << " DIRECTION:" << (bidirectional ? "BOTH" : (side == HSD_INITIATOR) ? "SENDER" : "RECEIVER")); + << (side == HSD_INITIATOR ? "INITIATOR" : "RESPONDER")); // Set UNSECURED state as default m_RcvKmState = SRT_KM_S_UNSECURED; @@ -669,7 +647,7 @@ bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool b 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) + if (ok) { m_iRcvKmKeyLen = m_iSndKmKeyLen; const int st = HaiCrypt_Clone(m_hSndCrypto, HAICRYPT_CRYPTO_DIR_RX, &m_hRcvCrypto); @@ -681,16 +659,13 @@ bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool b if (!ok) { m_SndKmState = SRT_KM_S_NOSECRET; // wanted to secure, but error occurred. - if (bidirectional) - m_RcvKmState = SRT_KM_S_NOSECRET; + m_RcvKmState = SRT_KM_S_NOSECRET; return false; } - regenCryptoKm( - 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 - ); + // Do not send the key (the KM msg will be attached to the HSv5 handshake) + regenCryptoKm(); m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR; #else @@ -711,19 +686,21 @@ bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool b HLOGC(cnlog.Debug, log << "CCryptoControl::init: NOT creating crypto contexts - will be created upon reception of KMREQ"); } + m_SocketID = id; return true; } -void srt::CCryptoControl::close() +void CCryptoControl::close() { /* Wipeout secrets */ sync::ScopedLock lck(m_mtxLock); memset(&m_KmSecret, 0, sizeof(m_KmSecret)); + m_SocketID = SRT_INVALID_SOCK; } -std::string srt::CCryptoControl::CONID() const +std::string CCryptoControl::CONID() const { - if (m_SocketID == 0) + if (int32_t(m_SocketID) <= 0) return ""; std::ostringstream os; @@ -734,8 +711,7 @@ std::string srt::CCryptoControl::CONID() const #ifdef SRT_ENABLE_ENCRYPTION -#if ENABLE_HEAVY_LOGGING -namespace srt { +#if HVU_ENABLE_HEAVY_LOGGING static std::string CryptoFlags(int flg) { using namespace std; @@ -752,10 +728,9 @@ static std::string CryptoFlags(int flg) copy(f.begin(), f.end(), ostream_iterator(os, "|")); return os.str(); } -} // namespace srt -#endif // ENABLE_HEAVY_LOGGING +#endif // HVU_ENABLE_HEAVY_LOGGING -bool srt::CCryptoControl::createCryptoCtx(HaiCrypt_Handle& w_hCrypto, size_t keylen, HaiCrypt_CryptoDir cdir, bool bAESGCM) +bool CCryptoControl::createCryptoCtx(HaiCrypt_Handle& w_hCrypto, size_t keylen, HaiCrypt_CryptoDir cdir, bool bAESGCM) { if (w_hCrypto) { @@ -802,23 +777,23 @@ bool srt::CCryptoControl::createCryptoCtx(HaiCrypt_Handle& w_hCrypto, size_t key return true; } #else -bool srt::CCryptoControl::createCryptoCtx(HaiCrypt_Handle&, size_t, HaiCrypt_CryptoDir, bool) +bool CCryptoControl::createCryptoCtx(HaiCrypt_Handle&, size_t, HaiCrypt_CryptoDir, bool) { return false; } #endif // SRT_ENABLE_ENCRYPTION -srt::EncryptionStatus srt::CCryptoControl::encrypt(CPacket& w_packet SRT_ATR_UNUSED) -{ #ifdef SRT_ENABLE_ENCRYPTION +EncryptionStatus CCryptoControl::encrypt(const void* header, const void* payload, int& w_size) +{ // Encryption not enabled - do nothing. - if ( getSndCryptoFlags() == EK_NOENC ) + if (getSndCryptoFlags() == EK_NOENC) return ENCS_CLEAR; // 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()); + int rc = HaiCrypt_Tx_Data(m_hSndCrypto, ((uint8_t*)header), ((uint8_t*)payload), w_size); if (rc < 0) { return ENCS_FAILED; @@ -827,18 +802,21 @@ srt::EncryptionStatus srt::CCryptoControl::encrypt(CPacket& w_packet SRT_ATR_UNU { // XXX what happens if the encryption is said to be "succeeded", // but the length is 0? Shouldn't this be treated as unwanted? - w_packet.setLength(rc); + w_size = rc; } return ENCS_CLEAR; +} #else +EncryptionStatus CCryptoControl::encrypt(const void*, const void*, int&) +{ return ENCS_NOTSUP; -#endif } +#endif -srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNUSED) -{ #ifdef SRT_ENABLE_ENCRYPTION +EncryptionStatus CCryptoControl::decrypt(CPacket& w_packet) +{ if (w_packet.getMsgCryptoFlags() == EK_NOENC) { HLOGC(cnlog.Debug, log << "CPacket::decrypt: packet not encrypted"); @@ -883,7 +861,7 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU if (!m_bErrorReported) { m_bErrorReported = true; - LOGC(cnlog.Error, log << "SECURITY STATUS: " << KmStateStr(m_RcvKmState) << " - can't decrypt w_packet."); + LOGC(cnlog.Error, log << "SECURITY STATUS: " << KmStateStr(m_RcvKmState) << " - can't decrypt a packet."); } HLOGC(cnlog.Debug, log << "Packet still not decrypted, status=" << KmStateStr(m_RcvKmState) << " - dropping size=" << w_packet.getLength()); @@ -906,13 +884,16 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU HLOGC(cnlog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc); return ENCS_CLEAR; +} #else +EncryptionStatus CCryptoControl::decrypt(CPacket&) +{ return ENCS_NOTSUP; -#endif } +#endif -srt::CCryptoControl::~CCryptoControl() +CCryptoControl::~CCryptoControl() { #ifdef SRT_ENABLE_ENCRYPTION close(); @@ -927,3 +908,5 @@ srt::CCryptoControl::~CCryptoControl() } #endif } + +} diff --git a/srtcore/crypto.h b/srtcore/crypto.h index 613ded8dd..3799b4dad 100644 --- a/srtcore/crypto.h +++ b/srtcore/crypto.h @@ -20,47 +20,71 @@ written by #include // UDT -#include "udt.h" #include "packet.h" #include "utilities.h" #include "logging.h" +#if HVU_ENABLE_LOGGING +#include "logger_fas.h" +#endif #include #include - - -namespace srt_logging -{ -std::string KmStateStr(SRT_KM_STATE state); -#if ENABLE_LOGGING -extern Logger cnlog; -#endif -} - namespace srt { class CUDT; struct CSrtConfig; +std::string KmStateStr(SRT_KM_STATE state); // 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 SRT_CMD_MAXSZ = HCRYPT_MSG_KM_MAX_SZ; // Maximum SRT custom messages payload size (bytes) const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ/sizeof(uint32_t); class CCryptoControl { + // This has only two purposes: + // - use in CONID() entry in the logs + // - mark initialized if not set to SRT_INVALID_SOCK. SRTSOCKET m_SocketID; size_t m_iSndKmKeyLen; //Key length size_t m_iRcvKmKeyLen; //Key length from rx KM + sync::atomic m_SndKmState; //Sender Km State (imposed by agent) + sync::atomic m_RcvKmState; //Receiver Km State (informed by peer) + // Temporarily allow these to be accessed. public: - SRT_KM_STATE m_SndKmState; //Sender Km State (imposed by agent) - SRT_KM_STATE m_RcvKmState; //Receiver Km State (informed by peer) + bool initialized() const { return m_SocketID != SRT_INVALID_SOCK; } + + struct State + { + SRT_KM_STATE snd, rcv; + }; + + State kmState() const + { + const State s = {m_SndKmState, m_RcvKmState}; + return s; + } + + void setKMState(SRT_KM_STATE snd, SRT_KM_STATE rcv) + { + m_SndKmState = snd; + m_RcvKmState = rcv; + } + + struct KmMessage + { + unsigned char Msg[HCRYPT_MSG_KM_MAX_SZ]; + size_t MsgLen; + int iPeerRetry; + const unsigned char* bytedata() const { return Msg; } + size_t bytesize() const { return MsgLen; } + }; private: // Partial haicrypt configuration, consider @@ -74,13 +98,8 @@ class CCryptoControl // Sender 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; - int iPeerRetry; - } m_SndKmMsg[2]; + KmMessage m_SndKmMsg[2]; HaiCrypt_Handle m_hSndCrypto; - // Receiver HaiCrypt_Handle m_hRcvCrypto; bool m_bErrorReported; @@ -90,7 +109,7 @@ class CCryptoControl static bool isAESGCMSupported(); - bool sendingAllowed() + bool sendingAllowed() const { // This function is called to state as to whether the // crypter allows the packet to be sent over the link. @@ -120,10 +139,18 @@ class CCryptoControl /// 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); + SRT_TSA_NEEDS_NONLOCKED(m_mtxLock) + bool regenCryptoKm(int (&aw_keyindex)[2]) { int* a = aw_keyindex; return regenCryptoKm_INTERNAL(a); } + + SRT_TSA_NEEDS_NONLOCKED(m_mtxLock) + bool regenCryptoKm() { return regenCryptoKm_INTERNAL(NULL); } + +private: + bool regenCryptoKm_INTERNAL(int aw_keyindex[2]); + +public: - size_t KeyLen() { return m_iSndKmKeyLen; } + size_t keylen() const { return m_iSndKmKeyLen; } // Needed for CUDT void updateKmState(int cmd, size_t srtlen); @@ -142,6 +169,11 @@ class CCryptoControl int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv); void createFakeSndContext(); + const KmMessage* getKmMsg(size_t ki) const + { + return &m_SndKmMsg[ki]; + } + const unsigned char* getKmMsg_data(size_t ki) const { return m_SndKmMsg[ki].Msg; } size_t getKmMsg_size(size_t ki) const { return m_SndKmMsg[ki].MsgLen; } @@ -171,9 +203,7 @@ class CCryptoControl /// during transmission (otherwise it's during the handshake) void getKmMsg_markSent(size_t ki, bool runtime) { -#if ENABLE_LOGGING - using srt_logging::cnlog; -#endif + IF_HEAVY_LOGGING(using srt::logging::cnlog); m_SndKmLastTime = sync::steady_clock::now(); if (runtime) @@ -205,14 +235,17 @@ class CCryptoControl return false; } - CCryptoControl(SRTSOCKET id); + CCryptoControl(); // DEBUG PURPOSES: std::string CONID() const; +#if HVU_ENABLE_LOGGING std::string FormatKmMessage(std::string hdr, int cmd, size_t srtlen); +#endif + + bool init(SRTSOCKET id, HandshakeSide, const CSrtConfig&, bool bUseGcm153); - bool init(HandshakeSide, const CSrtConfig&, bool bidir, bool bUseGcm153); - SRT_ATTR_EXCLUDES(m_mtxLock) + SRT_TSA_NEEDS_NONLOCKED(m_mtxLock) void close(); /// (Re)send KM request to a peer on timeout. @@ -221,7 +254,7 @@ class CCryptoControl /// - 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) + SRT_TSA_NEEDS_NONLOCKED(m_mtxLock) void sendKeysToPeer(CUDT* sock, int iSRTT); void setCryptoSecret(const HaiCrypt_Secret& secret) @@ -235,21 +268,23 @@ class CCryptoControl m_iRcvKmKeyLen = keylen; } - bool createCryptoCtx(HaiCrypt_Handle& rh, size_t keylen, HaiCrypt_CryptoDir tx, bool bAESGCM); - - int getSndCryptoFlags() const + EncryptionKeySpec getSndCryptoFlags() const { #ifdef SRT_ENABLE_ENCRYPTION - return(m_hSndCrypto ? - HaiCrypt_Tx_GetKeyFlags(m_hSndCrypto) : - // When encryption isn't on, check if it was required - // If it was, return -1 as flags, which means that - // encryption was requested and not possible. - hasPassphrase() ? -1 : - 0); -#else - return 0; + if (m_hSndCrypto) + { + // This will return 1 or 2 mapped to EK_EVEN and EK_ODD respectively + return EncryptionKeySpec(HaiCrypt_Tx_GetKeyFlags(m_hSndCrypto)); + } + else if (hasPassphrase()) + { + // When encryption isn't on, check if it was required + // If it was, return EK_ERROR, which means that + // encryption was requested and not possible. + return EK_ERROR; + } #endif + return EK_NOENC; } bool isSndEncryptionOK() const @@ -270,7 +305,7 @@ 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(CPacket& w_packet); + EncryptionStatus encrypt(const void* header, const void* payload, int& size); /// Decrypts the packet. If the packet has ENCKEYSPEC part /// in PH_MSGNO set to EK_NOENC, it does nothing. It decrypts @@ -280,6 +315,11 @@ class CCryptoControl EncryptionStatus decrypt(CPacket& w_packet); ~CCryptoControl(); + +private: + bool createCryptoCtx(HaiCrypt_Handle& rh, size_t keylen, HaiCrypt_CryptoDir tx, bool bAESGCM); + + }; } // namespace srt diff --git a/srtcore/epoll.cpp b/srtcore/epoll.cpp index e3545b738..cf6ee5525 100644 --- a/srtcore/epoll.cpp +++ b/srtcore/epoll.cpp @@ -65,101 +65,193 @@ modified by #include "common.h" #include "epoll.h" #include "logging.h" -#include "udt.h" +#include "logger_fas.h" #include "utilities.h" using namespace std; using namespace srt::sync; +using namespace srt::logging; -namespace srt_logging -{ - extern Logger eilog, ealog; -} - -using namespace srt_logging; - -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING #define IF_DIRNAME(tested, flag, name) (tested & flag ? name : "") #endif -srt::CEPoll::CEPoll(): +namespace srt +{ + +CEPoll::CEPoll(): m_iIDSeed(0) { // Exception -> CUDTUnited ctor. setupMutex(m_EPollLock, "EPoll"); } -srt::CEPoll::~CEPoll() +CEPoll::~CEPoll() { releaseMutex(m_EPollLock); } -int srt::CEPoll::create(CEPollDesc** pout) -{ - ScopedLock pg(m_EPollLock); - - if (++ m_iIDSeed >= 0x7FFFFFFF) - m_iIDSeed = 0; - - // Check if an item already exists. Should not ever happen. - if (m_mPolls.find(m_iIDSeed) != m_mPolls.end()) - throw CUDTException(MJ_SETUP, MN_NONE); - - int localid = 0; - - #ifdef LINUX +// Creating the system-wide resource for polling descriptors. +// MAIN INTERFACE: +// +// int createSysPoll_asneeded(); +// +// Returns the system-wide descriptor to be used for polling. +// It has set the CLOEXEC flag, if requested. +// The returned resource is always valid. On error it throws +// CUDTException. +// +// PORTABILITY: +// +// LINUX: uses epoll system. Sets CLOEXEC first valid found way. +// MACOS: uses kqueue. Sets CLOEXEC first valid found way. +// Others: just returns -1; polling system fd is not in use. +// +// At the end it should be closed, but ::close() has also +// a limited portability: +// +// void closeSysPoll(int id); +#ifdef LINUX +static inline int createEpoll_with_cloexec() +{ + int localid; // 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) - { +#if defined(EPOLL_CLOEXEC) + localid = epoll_create1(EPOLL_CLOEXEC); +#else + localid = epoll_create(1); + if (localid != -1) + { + int fdFlags = fcntl(localid, F_GETFD); + if (fdFlags != -1) + { fdFlags |= FD_CLOEXEC; fcntl(localid, F_SETFD, fdFlags); - } - } - #endif - #endif + } + } +#endif + return localid; +} +static inline int createSysPoll_asneeded() +{ + int res; +#if SRT_ENABLE_CLOEXEC + res = createEpoll_with_cloexec(); +#else + // No CLOEXEC + res = epoll_create(1); +#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. ENOMEM: There was insufficient memory to create the kernel object. - */ - if (localid < 0) - throw CUDTException(MJ_SETUP, MN_NONE, errno); - #elif defined(BSD) || TARGET_OS_MAC - localid = kqueue(); - if (localid < 0) - throw CUDTException(MJ_SETUP, MN_NONE, errno); - #else - // 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; + */ + if (res == -1) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + return res; +} +inline void closeSysPoll(int id) { ::close(id); } + +#elif defined(BSD) || TARGET_OS_MAC + +static inline int createKQueue_with_cloexec() +{ + int localid; +#if defined(KQUEUE_CLOEXEC) + localid = kqueuex(KQUEUE_CLOEXEC); +#else + localid = kqueue(); + if (localid != -1) + { + int fdFlags = fcntl(localid, F_GETFD); + if (fdFlags != -1) + { + fdFlags |= FD_CLOEXEC; + fcntl(localid, F_SETFD, fdFlags); + } + } +#endif + return localid; +} + +static inline int createSysPoll_asneeded() +{ +#if SRT_ENABLE_CLOEXEC + int localid = createKQueue_with_cloexec(); +#else + int localid = kqueue(); +#endif + if (localid < 0) + throw CUDTException(MJ_SETUP, MN_NORES, errno); + + return localid; +} + +inline void closeSysPoll(int id) { ::close(id); } + +#else + +// Other systems: use a stub, do not allow for subscription +// of system sockets to the SRT Epoll. + +static inline int createSysPoll_asneeded() +{ + // TODO: Solaris, use port_getn() + // https://docs.oracle.com/cd/E86824_01/html/E54766/port-get-3c.html + // on Windows, select + + // Simply return -1; this will not be taken into account by + // rest of the code anyway. + return -1; +} +inline void closeSysPoll(int) {} +#endif + + +int CEPoll::create(CEPollDesc** pout) +{ + ScopedLock pg(m_EPollLock); + + int seed = m_iIDSeed, backroll = m_iIDSeed; + + if (++seed >= 0x7FFFFFFF) + seed = 0; + + // Check if an item already exists. Should not ever happen. + while (m_mPolls.find(seed) != m_mPolls.end()) + { + // rollover-hitting is possible, but we need to also + // limit re-reviewing to a single second rollout + if (++seed >= 0x7FFFFFFF) + seed = 0; + if (seed == backroll) + throw CUDTException(MJ_SETUP, MN_NORES); + } + + int localid = createSysPoll_asneeded(); + + try + { + pair::iterator, bool> res = m_mPolls.insert(make_pair(seed, CEPollDesc(seed, localid))); + if (pout) + *pout = &res.first->second; + + m_iIDSeed = seed; + } + catch (...) + { + closeSysPoll(localid); + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY); + } return m_iIDSeed; } -int srt::CEPoll::clear_usocks(int eid) +void CEPoll::clear_usocks(int eid) { // This should remove all SRT sockets from given eid. ScopedLock pg (m_EPollLock); @@ -171,12 +263,10 @@ int srt::CEPoll::clear_usocks(int eid) CEPollDesc& d = p->second; d.clearAll(); - - return 0; } -void srt::CEPoll::clear_ready_usocks(CEPollDesc& d, int direction) +void CEPoll::clear_ready_usocks(CEPollDesc& d, int direction) { if ((direction & ~SRT_EPOLL_EVENTTYPES) != 0) { @@ -213,7 +303,7 @@ void srt::CEPoll::clear_ready_usocks(CEPollDesc& d, int direction) d.removeSubscription(cleared[j]); } -int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) +void CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) { ScopedLock pg(m_EPollLock); @@ -240,7 +330,7 @@ int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) ev.data.fd = s; if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_ADD, s, &ev) < 0) - throw CUDTException(); + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #elif defined(BSD) || TARGET_OS_MAC struct kevent ke[2]; int num = 0; @@ -262,12 +352,13 @@ int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) } } if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0) - throw CUDTException(); + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #else // fake use 'events' to prevent warning. Remove when implemented. (void)events; (void)s; + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #ifdef _MSC_VER // Microsoft Visual Studio doesn't support the #warning directive - nonstandard anyway. @@ -281,11 +372,9 @@ int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) #endif p->second.m_sLocals.insert(s); - - return 0; } -int srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) +void CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) { ScopedLock pg(m_EPollLock); @@ -311,12 +400,10 @@ int srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) #endif p->second.m_sLocals.erase(s); - - return 0; } // Need this to atomically modify polled events (ex: remove write/keep read) -int srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) +void CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) { ScopedLock pg(m_EPollLock); IF_HEAVY_LOGGING(ostringstream evd); @@ -385,10 +472,9 @@ int srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* even HLOGC(ealog.Debug, log << "srt_epoll_update_usock: REMOVED E" << eid << " socket @" << u); d.removeSubscription(u); } - return 0; } -int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) +void CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) { ScopedLock pg(m_EPollLock); @@ -415,7 +501,7 @@ int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* even ev.data.fd = s; if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_MOD, s, &ev) < 0) - throw CUDTException(); + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #elif defined(BSD) || TARGET_OS_MAC struct kevent ke[2]; int num = 0; @@ -445,21 +531,21 @@ int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* even } } if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0) - throw CUDTException(); + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #else // fake use 'events' to prevent warning. Remove when implemented. (void)events; (void)s; + throw CUDTException(MJ_SETUP, MN_NONE); #endif // Assuming add is used if not inserted // p->second.m_sLocals.insert(s); - return 0; } -int srt::CEPoll::setflags(const int eid, int32_t flags) +int32_t CEPoll::setflags(const int eid, int32_t flags) { ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); @@ -484,7 +570,7 @@ int srt::CEPoll::setflags(const int eid, int32_t flags) return oflags; } -int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) +int CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { // It is allowed to call this function with fdsSize == 0 // and therefore also NULL fdsSet. This will then only report @@ -500,18 +586,23 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) + { + LOGC(ealog.Error, log << "epoll_uwait: E" << eid << " doesn't exist"); throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + } CEPollDesc& ed = p->second; if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty()) { // Empty EID is not allowed, report error. + LOGC(ealog.Error, log << "epoll_uwait: E" << eid << " is empty (use SRT_EPOLL_ENABLE_EMPTY to allow)"); throw CUDTException(MJ_NOTSUP, MN_EEMPTY); } if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK) && (fdsSet == NULL || fdsSize == 0)) { - // Empty EID is not allowed, report error. + // Empty container is not allowed, report error. + LOGC(ealog.Error, log << "epoll_uwait: empty output container with E" << eid << " (use SRT_EPOLL_ENABLE_OUTPUTCHECK to allow)"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } @@ -519,23 +610,32 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 { // XXX Add error log // uwait should not be used with EIDs subscribed to system sockets + LOGC(ealog.Error, log << "epoll_uwait: E" << eid << " is subscribed to system sckets (not allowed for uwait)"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } CEPollDesc::enotice_t::iterator i = ed.enotice_begin(), inext; int pos = 0; // This is a list, so count it during iteration - for (inext = i ; i != ed.enotice_end() && pos < fdsSize ; ++pos, i = inext) + bool sufficient SRT_ATR_UNUSED = true; + for (inext = i ; i != ed.enotice_end() && (sufficient = pos < fdsSize) ; ++pos, i = inext) { ++inext; // deletion-safe list loop fdsSet[pos] = *i; - ed.checkEdge(i); // NOTE: potentially deletes `i` + IF_HEAVY_LOGGING(std::ostringstream out); + IF_HEAVY_LOGGING(out << "epoll_uwait: Notice: fd=" << i->fd << " events="); + IF_HEAVY_LOGGING(PrintEpollEvent(out, i->events, 0)); + + SRT_ATR_UNUSED const bool was_edge = ed.checkEdge(i); // NOTE: potentially deletes `i` + IF_HEAVY_LOGGING(out << (was_edge ? "(^)" : "")); + HLOGP(ealog.Debug, out.str()); } + IF_HEAVY_LOGGING(if (sufficient) LOGC(ealog.Debug, log << "epoll_uwait: output container size=" << fdsSize << " insufficient to report all sockets")); if (pos) // pos is increased by 1 towards the last used position return pos; } - if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) + if ((msTimeOut >= 0) && (count_microseconds(sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) break; // official wait does: throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); CGlobEvent::waitForEvent(); @@ -544,7 +644,7 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 return 0; } -int srt::CEPoll::wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) +int 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)) @@ -558,7 +658,7 @@ int srt::CEPoll::wait(const int eid, set* readfds, set* wr int total = 0; - srt::sync::steady_clock::time_point entertime = srt::sync::steady_clock::now(); + sync::steady_clock::time_point entertime = sync::steady_clock::now(); while (true) { { @@ -629,7 +729,7 @@ int srt::CEPoll::wait(const int eid, set* readfds, set* wr #ifdef LINUX const int max_events = ed.m_sLocals.size(); SRT_ASSERT(max_events > 0); - srt::FixedArray ev(max_events); + FixedArray ev(max_events); int nfds = ::epoll_wait(ed.m_iLocalID, ev.data(), ev.size(), 0); IF_HEAVY_LOGGING(const int prev_total = total); @@ -652,7 +752,7 @@ int srt::CEPoll::wait(const int eid, set* readfds, set* wr struct timespec tmout = {0, 0}; const int max_events = (int)ed.m_sLocals.size(); SRT_ASSERT(max_events > 0); - srt::FixedArray ke(max_events); + FixedArray ke(max_events); int nfds = kevent(ed.m_iLocalID, NULL, 0, ke.data(), (int)ke.size(), &tmout); IF_HEAVY_LOGGING(const int prev_total = total); @@ -727,7 +827,7 @@ int srt::CEPoll::wait(const int eid, set* readfds, set* wr if (total > 0) return total; - if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) + if ((msTimeOut >= 0) && (count_microseconds(sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) { HLOGC(ealog.Debug, log << "EID:" << eid << ": TIMEOUT."); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); @@ -741,7 +841,7 @@ int srt::CEPoll::wait(const int eid, set* readfds, set* wr return 0; } -int srt::CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut, bool report_by_exception) +int CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut, bool report_by_exception) { { ScopedLock lg (m_EPollLock); @@ -805,7 +905,7 @@ int srt::CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut // 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 << ": " + HLOGC(ealog.Debug, log << "swait: E" << d.m_iID << " rdy=" << total << ": " << singles.str() << " TRACKED: " << d.DisplayEpollWatch()); return total; @@ -828,13 +928,13 @@ int srt::CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut return 0; } -bool srt::CEPoll::empty(const CEPollDesc& d) const +bool CEPoll::empty(const CEPollDesc& d) const { ScopedLock lg (m_EPollLock); return d.watch_empty(); } -int srt::CEPoll::release(const int eid) +void CEPoll::release(const int eid) { ScopedLock pg(m_EPollLock); @@ -842,26 +942,24 @@ int srt::CEPoll::release(const int eid) if (i == m_mPolls.end()) throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); - #ifdef LINUX - // release local/system epoll descriptor - ::close(i->second.m_iLocalID); - #elif defined(BSD) || TARGET_OS_MAC - ::close(i->second.m_iLocalID); - #endif - + closeSysPoll(i->second.m_iLocalID); m_mPolls.erase(i); - - return 0; } -int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const int events, const bool enable) +int CEPoll::update_events(const SRTSOCKET& uid, 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. + return int(SRT_ERROR); // still, ignored. + } + + if (uid == SRT_INVALID_SOCK || uid == SRT_SOCKID_CONNREQ) + { + LOGC(eilog.Fatal, log << "epoll/update: IPE: invalid 'uid' submitted for update!"); + return int(SRT_ERROR); } int nupdated = 0; @@ -877,7 +975,7 @@ int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const 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"); + HLOGC(eilog.Note, log << "epoll/update: E" << *i << " by @" << uid << " 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); @@ -940,12 +1038,37 @@ int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const return nupdated; } -// Debug use only. -#if ENABLE_HEAVY_LOGGING -namespace srt +/// This is a simple function which removes the socket from epoll system. +/// The subscription list should be provided in the @a eids container and +/// the socket is removed from each of them, then this is cleared. This +/// should be the socket's private EID container that keeps EIDs that it +/// should update when an appropriate event comes. +/// +/// @param uid Socket ID that has to be removed from the epoll system +/// @param eids EIDs that the given socket believes being subscribed in +void CEPoll::wipe_usock(const SRTSOCKET uid, set& eids) { + 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/wipe: E" << *i << " in @" << uid << " was deleted in the meantime"); + continue; + } -string DisplayEpollResults(const std::map& sockset) + CEPollDesc& ed = p->second; + ed.removeSubscription(uid); + } + + eids.clear(); +} + +// Debug use only. +#if HVU_ENABLE_HEAVY_LOGGING + +string DisplayEpollResults(const map& sockset) { typedef map fmap_t; ostringstream os; @@ -972,6 +1095,7 @@ string CEPollDesc::DisplayEpollWatch() return os.str(); } +#endif + } // namespace srt -#endif diff --git a/srtcore/epoll.h b/srtcore/epoll.h index 09e0f0ec7..5b1bfed84 100644 --- a/srtcore/epoll.h +++ b/srtcore/epoll.h @@ -57,7 +57,6 @@ modified by #include #include #include -#include "udt.h" namespace srt { @@ -149,7 +148,7 @@ class CEPollDesc typedef std::map ewatch_t; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::string DisplayEpollWatch(); #endif @@ -360,9 +359,9 @@ std::string DisplayEpollWatch(); class CEPoll { -friend class srt::CUDT; -friend class srt::CUDTGroup; -friend class srt::CRendezvousQueue; +friend class CUDT; +friend class CUDTGroup; +friend class CRendezvousQueue; public: CEPoll(); @@ -377,38 +376,29 @@ friend class srt::CRendezvousQueue; /// delete all user sockets (SRT sockets) from an EPoll /// @param [in] eid EPoll ID. - /// @return 0 - int clear_usocks(int eid); + void 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. - - int add_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); + void add_ssock(const int eid, const SYSSOCKET& s, const int* events = 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. - - int remove_ssock(const int eid, const SYSSOCKET& s); + void 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. - - int update_usock(const int eid, const SRTSOCKET& u, const int* events); + void 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. - - int update_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); + void update_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); /// wait for EPoll events or timeout. /// @param [in] eid EPoll ID. @@ -475,7 +465,7 @@ friend class srt::CRendezvousQueue; /// @param [in] eid EPoll ID. /// @return 0 if success, otherwise an error number. - int release(const int eid); + void release(const int eid); public: // for CUDT to acknowledge IO status @@ -489,17 +479,19 @@ friend class srt::CRendezvousQueue; int update_events(const SRTSOCKET& uid, std::set& eids, int events, bool enable); - int setflags(const int eid, int32_t flags); + void wipe_usock(const SRTSOCKET uid, std::set& eids); + + int32_t setflags(const int eid, int32_t flags); private: int m_iIDSeed; // seed to generate a new ID - srt::sync::Mutex m_SeedLock; + sync::Mutex m_SeedLock; std::map m_mPolls; // all epolls - mutable srt::sync::Mutex m_EPollLock; + mutable sync::Mutex m_EPollLock; }; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::string DisplayEpollResults(const std::map& sockset); #endif diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index 4ee5e4428..4d15f6edf 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -33,7 +33,8 @@ #define SRT_FEC_MAX_RCV_HISTORY 10 using namespace std; -using namespace srt_logging; +using namespace srt::logging; +using namespace hvu; // ofmt namespace srt { @@ -533,13 +534,14 @@ void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt) ClipData(g, length_net, kflg, timestamp_hw, pkt.data(), pkt.size()); - 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) - << " LENGTH[ne]=" << g.length_clip - << " TS[he]=" << g.timestamp_clip - << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); + HLOGC(pflog.Debug, log << "FEC DATA PKT CLIP: " + << "FLAGS=" << fmt(kflg, hex) + << " LENGTH[ne]=" << fmt(length_net, hex) + << " TS[he]=" << fmt(timestamp_hw, hex) + << " CLIP STATE: FLAGS=" << fmt(g.flag_clip, hex) + << " LENGTH[ne]=" << fmt(g.length_clip, hex) + << " TS[he]=" << fmt(g.timestamp_clip, hex) + << " PL4=" << fmt(*(uint32_t*)&g.payload_clip[0], hex)); } // Clipping a control packet does merely the same, just the packet has @@ -560,13 +562,14 @@ void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) ClipData(g, *length_clip, *flag_clip, timestamp_hw, payload, payload_clip_len); - 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) - << " LENGTH[ne]=" << g.length_clip - << " TS[he]=" << g.timestamp_clip - << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); + HLOGC(pflog.Debug, log << "FEC/CTL CLIP: " + << "FLAGS=" << fmt(*flag_clip, hex) + << " LENGTH[ne]=" << fmt(*length_clip, hex) + << " TS[he]=" << fmt(timestamp_hw, hex) + << " CLIP STATE: FLAGS=" << fmt(g.flag_clip, hex) + << " LENGTH[ne]=" << fmt(g.length_clip, hex) + << " TS[he]=" << fmt(g.timestamp_clip, hex) + << " PL4=" << fmt(*(uint32_t*)&g.payload_clip[0], hex)); } void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) @@ -582,13 +585,14 @@ void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) ClipData(g, length_net, kflg, timestamp_hw, pkt.buffer, pkt.length); - 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) - << " LENGTH[ne]=" << g.length_clip - << " TS[he]=" << g.timestamp_clip - << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); + HLOGC(pflog.Debug, log << "FEC REBUILT DATA CLIP: " + << "FLAGS=" << fmt(kflg, hex) + << " LENGTH[ne]=" << fmt(length_net, hex) + << " TS[he]=" << fmt(timestamp_hw, hex) + << " CLIP STATE: FLAGS=" << fmt(g.flag_clip, hex) + << " LENGTH[ne]=" << fmt(g.length_clip, hex) + << " TS[he]=" << fmt(g.timestamp_clip, hex) + << " PL4=" << fmt(*(uint32_t*)&g.payload_clip[0], hex)); } void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg, @@ -734,7 +738,7 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& + g.payload_clip.size(); // Sanity -#if ENABLE_DEBUG +#if SRT_ENABLE_DEBUG if (g.output_buffer.size() < total_size) { LOGC(pflog.Fatal, log << "OUTPUT BUFFER TOO SMALL!"); @@ -765,10 +769,10 @@ void FECFilterBuiltin::PackControl(const Group& g, signed char index, SrtPacket& 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 - << " PL(" << dec << g.payload_clip.size() << ")[0-4]=" << hex - << (*(uint32_t*)&g.payload_clip[0])); + << int(index) << " LENGTH[ne]=" << fmt(g.length_clip, hex) + << " FLAGS=" << fmt(g.flag_clip, hex) << " TS=" << fmt(g.timestamp_clip, hex) + << " PL(" << g.payload_clip.size() << ")[0-4]=" + << fmt(*(uint32_t*)&g.payload_clip[0], hex)); } @@ -854,7 +858,7 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) loss_seqs_t irrecover_row, irrecover_col; -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING static string hangname [] = {"NOT-DONE", "SUCCESS", "PAST", "CRAZY"}; #endif @@ -1099,7 +1103,7 @@ void FECFilterBuiltin::CollectIrrecoverRow(RcvGroup& g, loss_seqs_t& irrecover) g.dismissed = true; } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING static inline char CellMark(const std::deque& cells, int index) { if (index >= int(cells.size())) @@ -1190,7 +1194,7 @@ FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangHorizontal(const CPacket& rp RcvRebuild(rowg, RcvGetLossSeqHoriz(rowg), m_number_rows == 1 ? Group::SINGLE : Group::HORIZ); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::ostringstream os; for (size_t i = 0; i < rcv.rebuilt.size(); ++i) { @@ -1326,7 +1330,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) if (!rcv.CellAt(cix)) { offset = int(cix); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING // For heavy logging case, show all cells in the range LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): MISSING"); @@ -1339,7 +1343,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqHoriz(Group& g) break; #endif } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING else { LOGC(pflog.Debug, log << "FEC/H: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) @@ -1379,7 +1383,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) if (!rcv.CellAt(cix)) { offset = int(cix); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING // For heavy logging case, show all cells in the range LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) << " (+" << cix << "): MISSING"); @@ -1392,7 +1396,7 @@ int32_t FECFilterBuiltin::RcvGetLossSeqVert(Group& g) break; #endif } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING else { LOGC(pflog.Debug, log << "FEC/V: cell %" << CSeqNo::incseq(rcv.cell_base, int(cix)) @@ -1447,7 +1451,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) ; p.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip; - p.hdr[SRT_PH_ID] = rcv.id; + p.hdr[SRT_PH_ID] = int32_t(rcv.id); // Header ready, now we rebuild the contents // First, rebuild the length. @@ -1464,7 +1468,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) 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] + << " TS=" << p.hdr[SRT_PH_TIMESTAMP] << " ID=" << p.hdr[SRT_PH_ID] << " size=" << length_hw << " !" << BufferStamp(p.buffer, p.length)); @@ -1562,7 +1566,7 @@ size_t FECFilterBuiltin::ExtendRows(size_t rowx) // index is > 2*m_number_cols. If so, shrink // the container first. -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: ROW STATS BEFORE: n=" << rcv.rowq.size()); for (size_t i = 0; i < rcv.rowq.size(); ++i) @@ -1592,7 +1596,7 @@ size_t FECFilterBuiltin::ExtendRows(size_t rowx) ConfigureGroup(rcv.rowq[i], ibase, 1, m_number_cols); } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: ROW STATS AFTER: n=" << rcv.rowq.size()); for (size_t i = 0; i < rcv.rowq.size(); ++i) @@ -1671,7 +1675,7 @@ void FECFilterBuiltin::MarkCellReceived(int32_t seq, ECellReceived is_received) rcv.cells[cell_offset] = (is_received == CELL_RECEIVED); } -#if ENABLE_HEAVY_LOGGING +#if HVU_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") << ")" @@ -1894,7 +1898,7 @@ FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangVertical(const CPacket& rpkt // at any time of when a packet has been received. RcvCheckDismissColumn(rpkt.getSeqNo(), colgx, irrecover); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS ATM: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) @@ -2037,7 +2041,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t } else if (rcv.colq.size() - 1 < numberCols()) // COND 2: full matrix in columns { -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC/V: IPE: about to dismiss past %" << seq << " with required %" << CSeqNo::incseq(base0, mindist) << " but col container size still " << rcv.colq.size() << "; COL STATS:"); @@ -2085,7 +2089,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t // ensured existence of the removed range: see COND 2 above. rcv.colq.erase(rcv.colq.begin(), rcv.colq.begin() + numberCols()); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) @@ -2103,7 +2107,7 @@ void FECFilterBuiltin::RcvCheckDismissColumn(int32_t seq, int colgx, loss_seqs_t else { -#if ENABLE_HEAVY_LOGGING +#if HVU_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:"); @@ -2526,7 +2530,7 @@ size_t FECFilterBuiltin::ExtendColumns(size_t colgx) } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) @@ -2572,7 +2576,7 @@ size_t FECFilterBuiltin::ExtendColumns(size_t colgx) ConfigureColumns(rcv.colq, sbase); } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING LOGC(pflog.Debug, log << "FEC: COL STATS BEFORE: n=" << rcv.colq.size()); for (size_t i = 0; i < rcv.colq.size(); ++i) diff --git a/srtcore/fec.h b/srtcore/fec.h index f4ed0e4cc..40ef56b4a 100644 --- a/srtcore/fec.h +++ b/srtcore/fec.h @@ -84,7 +84,7 @@ class FECFilterBuiltin: public SrtPacketFilterBase bool dismissed; RcvGroup(): fec(false), dismissed(false) {} -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::string DisplayStats() { if (base == SRT_SEQNO_NONE) diff --git a/srtcore/filelist.maf b/srtcore/filelist.maf index e801c8b7f..a5421bf38 100644 --- a/srtcore/filelist.maf +++ b/srtcore/filelist.maf @@ -1,4 +1,4 @@ - +# File list for SRT core SOURCES api.cpp @@ -14,9 +14,8 @@ epoll.cpp fec.cpp handshake.cpp list.cpp -logger_default.cpp -logger_defs.cpp -logging.cpp +logger_fas.cpp +../logging/logging.cpp md5.cpp packet.cpp packetfilter.cpp @@ -24,12 +23,15 @@ queue.cpp congctl.cpp socketconfig.cpp srt_c_api.cpp -srt_compat.c +../logging/hvu_compat.c strerror_defs.cpp sync.cpp tsbpd_time.cpp window.cpp +# IMPORTANT! Conditionals in the headers are defined IN THE BUILD; +# conditional symbols are build-system variables, not C++ macros. + SOURCES - ENABLE_BONDING group.cpp group_backup.cpp @@ -46,12 +48,12 @@ srt_shared.rc PUBLIC HEADERS srt.h -logging_api.h +../logging/logging_api.h access_control.h +# version.h - NOTE: generated in the build and added to installation PROTECTED HEADERS platform_sys.h -udt.h PRIVATE HEADERS api.h @@ -66,17 +68,20 @@ crypto.h epoll.h handshake.h list.h -logging.h +logger_fas.h +../logging/logging.h md5.h netinet_any.h +../logging/ofmt.h +../logging/ofmt_iostream.h packet.h sync.h queue.h congctl.h socketconfig.h -srt_compat.h +../logging/hvu_compat.h stats.h -threadname.h +../logging/hvu_threadname.h tsbpd_time.h utilities.h window.h diff --git a/srtcore/group.cpp b/srtcore/group.cpp index b873bb710..fd8483bd8 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -4,63 +4,24 @@ #include "api.h" #include "group.h" +#include "socketconfig.h" +#include "hvu_threadname.h" using namespace std; using namespace srt::sync; using namespace srt::groups; -using namespace srt_logging; +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; +sync::atomic 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; -} +#if 0 // used in a blocked code part - restore if needed +static inline char fmt_onoff(bool val) { return val ? '+' : '-'; } +#endif // [[using locked(this->m_GroupLock)]]; bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_t& w_rcv_isn) @@ -162,7 +123,7 @@ void CUDTGroup::debugMasterData(SRTSOCKET slave) // 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(mpeer = gi->ps->core().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 @@ -233,10 +194,10 @@ CUDTGroup::SocketData* CUDTGroup::add(SocketData data) { 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 + log << "CUDTGroup::add: taking MAX payload size from socket @" << data.ps->core().m_SocketID << ": " << plsize << " " << (plsize ? "(explicit)" : "(unspecified = fallback to 1456)")); if (plsize == 0) - plsize = SRT_LIVE_MAX_PLSIZE; + plsize = CPacket::srtPayloadSize(data.agent.family()); // It is stated that the payload size // is taken from first, and every next one // will get the same. @@ -249,11 +210,17 @@ CUDTGroup::SocketData* CUDTGroup::add(SocketData data) CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) : m_Global(CUDT::uglobal()) - , m_GroupID(-1) - , m_PeerGroupID(-1) + , m_GroupID(SRT_INVALID_SOCK) + , m_PeerGroupID(SRT_INVALID_SOCK) + , m_zLongestDistance(0) + , m_tdLongestDistance() +#if USE_RECEIVER_UNIT_POOL + , m_WaterTotalSizeCache(0) + , m_bWaterOverflow() +#endif , m_type(gtype) - , m_listener() , m_iBusy() + , m_iRcvPossibleLossSeq(SRT_SEQNO_NONE) , m_iSndOldestMsgNo(SRT_MSGNO_NONE) , m_iSndAckedMsgNo(SRT_MSGNO_NONE) , m_uOPT_MinStabilityTimeout_us(1000 * CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS) @@ -269,9 +236,11 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) // in the constructor body. , m_iSndTimeOut(-1) , m_iRcvTimeOut(-1) + , m_bOPT_MessageAPI(true) // XXX currently not settable + , m_iOPT_RcvBufSize(CSrtConfig::DEF_BUFFER_SIZE) + , m_bOPT_DriftTracer(true) , m_tsStartTime() , m_tsRcvPeerStartTime() - , m_RcvBaseSeqNo(SRT_SEQNO_NONE) , m_bOpened(false) , m_bConnected(false) , m_bClosing(false) @@ -281,19 +250,75 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) setupMutex(m_GroupLock, "Group"); setupMutex(m_RcvDataLock, "G/RcvData"); setupCond(m_RcvDataCond, "G/RcvData"); - m_RcvEID = m_Global.m_EPoll.create(&m_RcvEpolld); + setupCond(m_RcvTsbPdCond, "G/TSBPD"); + setupMutex(m_RcvBufferLock, "G/Buffer"); + m_SndEID = m_Global.m_EPoll.create(&m_SndEpolld); + HLOGC(gmlog.Debug, log << "Group internal EID: W:E" << m_SndEID); + m_stats.init(); // Set this data immediately during creation before // two or more sockets start arguing about it. m_iLastSchedSeqNo = CUDT::generateISN(); + m_RcvFurthestPacketTime = steady_clock::now(); +} + +void CUDTGroup::createBuffers(const CUDT& core, const time_point& tsbpd_start_time) +{ + // XXX NOT YET, but will be in use. + m_pSndBuffer.reset(); + + // This buffer is created without the source queue - so the units will be decommissioned + // by checking the muxid recorded in the unit. + m_pRcvBuffer.reset(new srt::CRcvBuffer(core.ISN(), m_iOPT_RcvBufSize, this, m_bOPT_MessageAPI)); + if (tsbpd_start_time != time_point()) + { + HLOGC(gmlog.Debug, log << "grp/createBuffers: setting rcv buf start time=" << FormatTime(tsbpd_start_time) << " lat=" << latency_us() << "us"); + // [TSA] Not locking because this is initialization. + m_pRcvBuffer->setTsbPdMode(tsbpd_start_time, false, microseconds_from(latency_us())); + } +} + +/// Update the internal state after a single link has been switched to RUNNING state. +// [[using locked(m_GroupLock)]] +void CUDTGroup::updateRcvRunningState() +{ + size_t nrunning = 0; + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + if (gi->rcvstate == SRT_GST_RUNNING) + ++nrunning; + } + + m_Group.set_number_running(nrunning); +} + +// [[using locked(m_GroupLock)]] +void CUDTGroup::updateErasedLink() +{ + // When a link has been erased, reset the tracing data + // to enforce a situation that some new links have been + // added + if (m_Group.size() > 1) + { + updateRcvRunningState(); + } + + m_zLongestDistance = 0; + m_tdLongestDistance = duration::zero(); } CUDTGroup::~CUDTGroup() { - srt_epoll_release(m_RcvEID); + if (m_RcvTsbPdThread.joinable()) + { + // This SHOULD NOT happen, but let's just stay safe. + LOGC(gmlog.Fatal, log << "IPE: GLat thread should be already stopped when deleting a group!!!"); + m_RcvTsbPdThread.join(); + } + srt_epoll_release(m_SndEID); releaseMutex(m_GroupLock); releaseMutex(m_RcvDataLock); @@ -387,11 +412,50 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) case SRTO_SNDTIMEO: m_iSndTimeOut = cast_optval(optval, optlen); - break; + break; // passthrough to socket option case SRTO_RCVTIMEO: m_iRcvTimeOut = cast_optval(optval, optlen); - break; + break; // passthrough to socket option + + case SRTO_RCVBUF: + { + // This requires to obtain the possibly set MSS and FC options. + // XXX Find some more sensible way to do it. Would be nice to + // systematize the search method and default values. + int val = cast_optval(optval, optlen); + if (val <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + // Search if you already have SRTO_MSS set + int mss = CSrtConfig::DEF_MSS; + vector::iterator f = + find_if(m_config.begin(), m_config.end(), ConfigItem::OfType(SRTO_MSS)); + if (f != m_config.end()) + { + f->get(mss); // worst case, it will leave it unchanged. + } + + // Search if you already have SRTO_FC set + int fc = CSrtConfig::DEF_FLIGHT_SIZE; + f = find_if(m_config.begin(), m_config.end(), ConfigItem::OfType(SRTO_FC)); + if (f != m_config.end()) + { + f->get(fc); // worst case, it will leave it unchanged. + } + + if (mss <= 0 || fc <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + m_iOPT_RcvBufSize = srt::RcvBufferSizeOptionToValue(val, fc, mss); + } + break; // Keep passthru. This is also required for Unit queue initial size. + + case SRTO_DRIFTTRACER: + { + m_bOPT_DriftTracer = cast_optval(optval, optlen); + return; // no passthru. + } case SRTO_GROUPMINSTABLETIMEO: { @@ -465,9 +529,13 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) CSrtConfig testconfig; // Note: this call throws CUDTException by itself. + // -1 is returned only if the option isn't found in this set. int result = testconfig.set(optName, optval, optlen); if (result == -1) // returned in case of unknown option + { + LOGC(gmlog.Error, log << "setOpt: not an option that can be stored: #" << optName); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } // Store the option regardless if pre or post. This will apply m_config.push_back(ConfigItem(optName, optval, optlen)); @@ -488,7 +556,10 @@ static bool operator!=(const struct linger& l1, const struct linger& l2) template static void importTrivialOption(vector& storage, SRT_SOCKOPT optname, const ValueType& optval, const int optsize = sizeof(ValueType)) { - SRT_STATIC_ASSERT(std::is_trivial::value, "ValueType must be a trivial type."); + // Using the check only in C++11 mode because std::is_trivially_copyable is only there available. +#if HAVE_FULL_CXX11 + static_assert(std::is_trivially_copyable::value, "ValueType must be a trivial type."); +#endif ValueType optval_dflt = ValueType(); int optsize_dflt = sizeof(ValueType); if (!getOptDefault(optname, (&optval_dflt), (optsize_dflt)) || optval_dflt != optval) @@ -569,8 +640,8 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_FC, iFlightFlagSize); // Nonstandard - importTrivialOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); - importTrivialOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); + importTrivialOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::udpHeaderSize(AF_INET))); + importTrivialOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::udpHeaderSize(AF_INET))); IM(SRTO_LINGER, Linger); @@ -591,6 +662,10 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_OHEADBW, iOverheadBW); IM(SRTO_IPTOS, iIpToS); IM(SRTO_IPTTL, iIpTTL); + + // XXX CONTROVERSIAL: there must be either the whole group TSBPD or not. + // Single sockets should not have a say there. And also currently the + // groups are not prepared to handle non-tsbpd mode. IM(SRTO_TSBPDMODE, bTSBPD); IM(SRTO_RCVLATENCY, iRcvLatency); IM(SRTO_PEERLATENCY, iPeerLatency); @@ -611,7 +686,8 @@ void CUDTGroup::deriveSettings(CUDT* u) importStringOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig); - importTrivialOption(m_config, SRTO_PBKEYLEN, (int) u->m_pCryptoControl->KeyLen()); + // XXX reaching out to m_pCryptoControl here can be racy. + importTrivialOption(m_config, SRTO_PBKEYLEN, (int) u->m_CryptoControl.keylen()); // Passphrase is empty by default. Decipher the passphrase and // store as passphrase option @@ -712,7 +788,7 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) case SRTO_SNDBUF: case SRTO_RCVBUF: - w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::UDP_HDR_SIZE)); + w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::udpHeaderSize(AF_INET))); break; case SRTO_LINGER: @@ -730,7 +806,7 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(true); case SRTO_MAXBW: RD(int64_t(-1)); -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW case SRTO_MAXREXMITBW: RD(int64_t(-1)); #endif @@ -867,11 +943,11 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) // Can't have m_GroupLock locked while calling getOpt on a member socket // because the call will acquire m_ControlLock leading to a lock-order-inversion. SRTSOCKET firstsocket = SRT_INVALID_SOCK; - enterCS(m_GroupLock); + m_GroupLock.lock(); gli_t gi = m_Group.begin(); if (gi != m_Group.end()) firstsocket = gi->ps->core().id(); - leaveCS(m_GroupLock); + m_GroupLock.unlock(); // CUDTUnited::m_GlobControlLock can't be acquired with m_GroupLock either. // We have also no guarantee that after leaving m_GroupLock the socket isn't // going to be deleted. Hence use the safest method by extracting through the id. @@ -927,17 +1003,15 @@ SRT_KM_STATE CUDTGroup::getGroupEncryptionState() for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) { - CCryptoControl* cc = gi->ps->core().m_pCryptoControl.get(); - if (!cc) - continue; - SRT_KM_STATE gst = cc->m_RcvKmState; + CCryptoControl::State cst = gi->ps->core().m_CryptoControl.kmState(); // A fix to NOSECRET is because this is the state when agent has set // no password, but peer did, and ENFORCEDENCRYPTION=false allowed // this connection to be established. UNSECURED can't be taken in this // case because this would suggest that BOTH are unsecured, that is, // we have established an unsecured connection (which is not true). - if (gst == SRT_KM_S_UNSECURED && cc->m_SndKmState == SRT_KM_S_NOSECRET) - gst = SRT_KM_S_NOSECRET; + SRT_KM_STATE gst = (cst.rcv == SRT_KM_S_UNSECURED && cst.snd == SRT_KM_S_NOSECRET) + ? SRT_KM_S_NOSECRET + : cst.rcv; kmstates.insert(gst); } } @@ -1021,7 +1095,7 @@ SRT_SOCKSTATUS CUDTGroup::getStatus() } // [[using locked(m_GroupLock)]]; -void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) +void CUDTGroup::syncWithFirstSocket(const CUDT& core, const HandshakeSide side) { if (side == HSD_RESPONDER) { @@ -1032,15 +1106,487 @@ void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) set_currentSchedSequence(core.ISN()); } - // Only set if was not initialized to avoid problems on a running connection. - if (m_RcvBaseSeqNo == SRT_SEQNO_NONE) - m_RcvBaseSeqNo = CSeqNo::decseq(core.m_iPeerISN); - + // Must be done here before createBuffers because the latency value + // will be used to set it to the buffer after creation. + HLOGC(gmlog.Debug, log << "grp/syncWithFirstSocket: setting group latency: " << core.m_iTsbPdDelay_ms << "ms"); // Get the latency (possibly fixed against the opposite side) // from the first socket (core.m_iTsbPdDelay_ms), - // and set it on the current socket. + // and set it on the group. set_latency_us(core.m_iTsbPdDelay_ms * int64_t(1000)); + + /* + FIX: In this implementation we need to initialize the receiver buffer. + This function is called when the first socket is added to the group, + both as the first connection on the caller side and the socket connection + that spawned this group as a mirror group on the listener side. + The receiver buffer, which will be common for the group, needs ISN, + in order to be able to recover any initially lost packets. Also, + with the newly created fresh socket and very first socket in the group, + it should be completely safe to set the ISN from the first socket, + which is the same for sending and receiving. Next sockets added to + the group may have these values derived from the group, and they can + differ in sender and receiver. + */ + + // Only set if was not initialized to avoid problems on a running connection. + int32_t butlast_seqno = CSeqNo::decseq(core.ISN()); + // XXX NEEDED? if (m_RcvBaseSeqNo == SRT_SEQNO_NONE) + m_RcvLastSeqNo = butlast_seqno; + + // This should be the sequence of the latest packet in flight, + // after being send over whichever member connection. + m_SndLastSeqNo = butlast_seqno; // [TSA] initial, not locking + m_SndLastDataAck = core.ISN(); + + if (core.m_bGroupTsbPd) + { + m_tsRcvPeerStartTime = core.m_tsRcvPeerStartTime; + } + + HLOGC(gmlog.Debug, log << "grp/syncWithFirstSocket: creating receiver buffer for ISN=%" << core.ISN() + << " TSBPD start: " << (core.m_bGroupTsbPd ? FormatTime(m_tsRcvPeerStartTime) : "not enabled")); + + createBuffers(core, m_tsRcvPeerStartTime); +} + +CRcvBuffer::InsertInfo CUDTGroup::addDataUnit(int32_t muxid, CUDTSocket* msock, CRcvBuffer::UnitHandle& u, CUDT::loss_seqs_t& w_losses, bool& w_have_loss) +{ + // If this returns false, the adding has failed and + + CRcvBuffer::InsertInfo info; + const CPacket& rpkt = u->m_Packet; + w_have_loss = false; + + { + // NOTE: locking m_GroupLock is to prevent the member data + // from being taken out in the meantime. After setting m_GroupMemberData + // the given socket is going to delete itself from the group, but this + // thing will be only allowed after acquisition of m_GroupLock. + + ScopedLock glk (m_GroupLock); + groups::SocketData* member = msock->m_GroupMemberData; + + ScopedLock lk (m_RcvBufferLock); + info = m_pRcvBuffer->insert((u), muxid); + + if (info.result == CRcvBuffer::InsertInfo::INSERTED) + { + w_have_loss = checkPacketArrivalLoss(member, rpkt, (w_losses)); + } + } + + if (info.result == CRcvBuffer::InsertInfo::INSERTED) + { + // If m_bTsbpdWaitForNewPacket, then notify anyway. + // Otherwise notify only if a "fresher" packet was added, + // so TSBPD should interrupt its sleep earlier and re-check. + if (m_bTsbPd && (m_bTsbpdWaitForNewPacket || info.first_time != time_point())) + { + HLOGC(gmlog.Debug, log << CONID() << "grp/addDataUnit: got a packet [live], reason:" + << (m_bTsbpdWaitForNewPacket ? "expected" : "sealing") << " - SIGNAL TSBPD"); + // Make a lock on data reception first, to protect the buffer. + // Then notify TSBPD if required. + CUniqueSync tsbpd_cc(m_RcvDataLock, m_RcvTsbPdCond); + tsbpd_cc.notify_all(); + } + } + else if (info.result == CRcvBuffer::InsertInfo::DISCREPANCY) + { + ScopedLock lk (m_RcvBufferLock); + LOGC(qrlog.Error, log << CONID() << "grp/addDataUnit: " + << "SEQUENCE DISCREPANCY. DISCARDING." + << " seq=" << rpkt.seqno() + << " buffer=(" << m_pRcvBuffer->getStartSeqNo() + << ":" << m_RcvLastSeqNo // -1 = size to last index + << "+" << CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), int(m_pRcvBuffer->capacity()) - 1) + << ")"); + } + else + { +#if HVU_ENABLE_HEAVY_LOGGING + // XXX Consider generalizing this display value. + static const char* const ival [] = { "inserted", "redundant", "belated", "discrepancy" }; + if (int(info.result) > -4 && int(info.result) <= 0) + { + LOGC(qrlog.Debug, log << CONID() << "grp/addDataUnit: insert status: " << ival[-info.result]); + } + else + { + LOGC(qrlog.Debug, log << CONID() << "grp/addDataUnit: IPE: invalid insert status"); + } +#endif + } + + return info; +} + +// [[using locked(m_RcvBufferLock)]] +int CUDTGroup::rcvDropTooLateUpTo(int seqno) +{ + int iDropCnt = 0; + + // Nothing to drop from an empty buffer. + // Required to check first to secure size()-1 expression. + if (!m_pRcvBuffer->empty()) + { + // Make sure that it would not drop over m_iRcvCurrSeqNo, which may break senders. + int32_t last_seq = CSeqNo::incseq(m_pRcvBuffer->getStartSeqNo(), m_pRcvBuffer->size() - 1); + if (CSeqNo::seqcmp(seqno, last_seq) > 0) + seqno = last_seq; + + // Skipping the sequence number of the new contiguous region + std::pair drop = m_pRcvBuffer->dropUpTo(seqno); + + // XXX Temporary solution, but these numbers were split for a reason. + iDropCnt = drop.first + drop.second; + + /* XXX not sure how to stats. + if (iDropCnt > 0) + { + m_StatsLock.lock(); + // 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)); + m_StatsLock.unlock(); + } + */ + } + + return iDropCnt; +} + +void CUDTGroup::synchronizeLoss(int32_t seqno) +{ + ScopedLock lk (m_GroupLock); + + for (gli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + CUDT& u = gi->ps->core(); + u.dropFromLossLists(SRT_SEQNO_NONE, seqno); + } +} + +// [[using locked(m_RcvBufferLock)]] +bool CUDTGroup::checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, CUDT::loss_seqs_t& w_losses) +{ + // This is called upon successful adding a packet to the receiver buffer, so: + // - check the gap to the previous packet + // - update the m_RcvLastSeqNo if it is newest + + // Also, if this packet is going to be sealed from another + // socket in the group, then this check should be done again + // from the beginning, regarding the already recorded loss candidate. + + int32_t expected_seqno = m_RcvLastSeqNo; + expected_seqno = CSeqNo::incseq(expected_seqno); + bool have = false; + + // For group types using multiple links, use some more complicated mechanism. + if (type() == SRT_GTYPE_BROADCAST) + { + have = checkMultilinkLoss(rpkt, (w_losses)); + } + else if (CSeqNo::seqcmp(rpkt.seqno(), expected_seqno) > 0) + { + int32_t seqlo = expected_seqno; + int32_t seqhi = CSeqNo::decseq(rpkt.seqno()); + + w_losses.push_back(make_pair(seqlo, seqhi)); + have = true; + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: loss detected: %(" + << seqlo << " - " << seqhi << ")"); + } + + // NOTE: 'member' is allowed to be NULL; this may handle the case of checking + // losses on a link that got dead just after delivering a packet. The rest of + // this function is updating some per-member data; a socket being closed can + // simply get this + + if (!member) + return have; + + if (CSeqNo::seqcmp(rpkt.seqno(), m_RcvLastSeqNo) > 0) + { + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest updated: %" << m_RcvLastSeqNo << " -> %" << rpkt.seqno()); + m_RcvLastSeqNo = rpkt.seqno(); + + // This should theoretically set it up with the very first packet received over whichever link + // but this time is initialized upon creation of the group, just in case. + m_RcvFurthestPacketTime = steady_clock::now(); + m_zLongestDistance = 0; // this member is at top + member->updateCounter = 0; + } + else + { + // XXX Check if this action should be avoided in case when the + // incoming packet has the R (retransmission) flag set. + bool updated SRT_ATR_UNUSED = false; + if (++member->updateCounter == 10 && m_zLongestDistance > 1) + { + // Decrease by 1 once per 10 events so that if a link + // happens to deliver packets faster, it is at some point detected + // and taken into account. + --m_zLongestDistance; + m_tdLongestDistance = duration::zero(); + member->updateCounter = 0; + updated = true; + } + + int dist = CSeqNo::seqoff(rpkt.seqno(), m_RcvLastSeqNo); + dist = max(m_zLongestDistance, dist); + m_zLongestDistance = dist; + + duration td = steady_clock::now() - m_RcvFurthestPacketTime.load(); + td = max(m_tdLongestDistance.load(), td); + m_tdLongestDistance = td; + + HLOGC(grlog.Debug, log << "grp:checkPacketArrivalLoss: latest = %" << m_RcvLastSeqNo << ": pkt %" << rpkt.seqno() + << " dist={" << dist << "pkt " << FormatDuration(m_tdLongestDistance) << (updated ? "} (reflected)" : "} (continued)")); + } + + return have; +} + +// [[using locked(m_RcvBufferLock)]] +bool CUDTGroup::checkMultilinkLoss(const CPacket& pkt, CUDT::loss_seqs_t& w_losses) +{ + // This is done in case of every incoming packet. + + if (pkt.getSeqNo() == m_iRcvPossibleLossSeq) + { + // XXX WARNING: it's unknown so far as to whether this "first loss" + // hasn't been reported already. + + // This seals the exact loss position. + // The returned value can be also NONE, which clears out the loss information. + m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(m_iRcvPossibleLossSeq); + + HLOGC(gmlog.Debug, log << "grp:checkMultilinkLoss: %" << pkt.getSeqNo() << " SEALS A LOSS, shift to %" << m_iRcvPossibleLossSeq); + return false; + } + + // We state that this is the oldest possible loss sequence; just formally check + int cmp = CSeqNo::seqcmp(pkt.seqno(), m_RcvLastSeqNo); + if (cmp < 0) + { + HLOGC(gmlog.Debug, log << "grp:checkMultilinkLoss: %" << pkt.getSeqNo() << " IN THE PAST"); + return false; + } + + // We need to check first, if we ALREADY have some older loss candidate, + // and if so, if the condition for having it "eclipsed" is satisfied. + + bool found_reportable_losses = false, more_losses = false; + + while (m_iRcvPossibleLossSeq != SRT_SEQNO_NONE) + { + // We do have a recorded loss before. Get unit information. + vector followers; + + // NOTE: calling m_Group.size() doesn't need locking of m_GroupLock. + // Getting elements or modifying a container does. + m_pRcvBuffer->getUnitSeriesInfo(m_iRcvPossibleLossSeq, m_Group.size(), (followers)); + + // The "eclipse" condition is one of two: + // + // When the loss (even if divided by other losses) is followed by some + // number of packets, among which: + // + // 1. There is at least one packet from every link. + // 2. There are at least two packets coming from one of the links. + + HLOGC(gmlog.Debug, log << "grp:checkMultilinkLoss: existing %" << m_iRcvPossibleLossSeq << " followed by: " + << Printable(followers)); + + map nums; + FringeValues(followers, (nums)); + +#if HVU_ENABLE_HEAVY_LOGGING + const char* which_condition[3] = {"fullcover", "longtail", "both???"}; +#endif + + bool longtail = false; + bool fullcover = nums.size() >= m_Group.number_running(); + if (!fullcover) + { + int actual_distance = CSeqNo::seqoff(m_iRcvPossibleLossSeq, m_RcvLastSeqNo); + + // The minimum distance is the number of links. + // This is used always, regardless of other conditions + longtail = (actual_distance > int(m_Group.size() + 1)); + + if (longtail && m_zLongestDistance > m_Group.size()) + { + // This is a complicated condition. We need to state that + // the long tail has been exceeded if: + // 1. We have a long distance measured. + // a. If not, fall back to the number of member links. + // + // 2. To this value we add 0.2 of the value (minimum 1) to make it + // a base value for test if this is exceeded. + // + // 3. We check the distance between the packet tested for + // being a loss (m_iRcvPossibleLossSeq) and the latest received + // (m_RcvLastSeqNo). + + int32_t basefax = m_zLongestDistance; + double extrafax = max(basefax * 0.2, 1.0); + basefax += int(extrafax); + + longtail = (actual_distance > basefax); + + HLOGC(grlog.Debug, log << "grp:checkMultilinkLoss: loss-distance=" << actual_distance + << (longtail ? " EXCEEDS" : " UNDER") << " the longest tail " << m_zLongestDistance + << " stretched to " << basefax); + } + else + { + HLOGC(grlog.Debug, log << "grp:checkMultilinkLoss: loss-distance=" << actual_distance + << (longtail ? " EXCEEDS" : " BELOW") << " the group size=" << m_Group.size() + << (longtail ? " but not" : " and") << " the tail=" << m_zLongestDistance); + } + } + else + { + HLOGC(grlog.Debug, log << "grp:checkMultilinkLoss: loss confirmed by " << nums.size() << " sources out of " << m_Group.number_running() << " running"); + } + + if (longtail || fullcover) + { + // Extract the whole first loss + CUDT::loss_seqs_t::value_type loss; + loss.first = m_pRcvBuffer->getFirstLossSeq(m_iRcvPossibleLossSeq, (&loss.second)); + if (loss.first == SRT_SEQNO_NONE) + { + HLOGC(gmlog.Debug, log << "... LOSS SEALED (IPE) ???"); + m_iRcvPossibleLossSeq = SRT_SEQNO_NONE; + break; + } + w_losses.push_back(loss); + + found_reportable_losses = true; + + // Save the next found loss + m_iRcvPossibleLossSeq = m_pRcvBuffer->getFirstLossSeq(CSeqNo::incseq(loss.second)); + + HLOGC(gmlog.Debug, log << "... qualified as loss (" << which_condition[(int(fullcover) + 2*int(longtail))-1] << "): %(" << loss.first << " - " << loss.second + << "), next loss: %" << m_iRcvPossibleLossSeq); + + if (m_iRcvPossibleLossSeq == SRT_SEQNO_NONE) + { + // We extracted all losses + more_losses = false; + break; + } + + // Found at least one reportable loss + more_losses = true; + continue; + } + else + { + HLOGC(gmlog.Debug, log << "... not yet a loss - waiting for possible sealing"); + } + + break; + } + + // found_reportable_losses = at least one of the so far POTENTIAL loss was confirmed as ACTUAL loss and we report it. + // more_losses = not all seen losses have been extracted (so don't try to register a new POTENTIAL loss) + + // In case when the above procedure didn't set m_iRcvPossibleLossSeq, + // check now the CURRENT arrival if it doesn't create a new loss. + + // HERE: if !more_losses, then m_iRcvPossibleLossSeq == SRT_SEQNO_NONE. + // This condition may change it or leave as is. + + int32_t next_seqno = CSeqNo::incseq(m_RcvLastSeqNo); + if (!more_losses && CSeqNo::seqcmp(pkt.seqno(), next_seqno) > 0) + { + // NOTE: in case when you have (at least temporarily) only one link, + // then you have to do the same as with a general case. The above loop + // had to be performed anyway, but this only touches upon any earlier losses. + // In this case if we have one link only, do not notify it for the next time, + // but report it directly instead. + if (m_Group.size() == 1) + { + CUDT::loss_seqs_t::value_type loss = make_pair(next_seqno, CSeqNo::decseq(pkt.seqno())); + w_losses.push_back(loss); + HLOGC(gmlog.Debug, log << "grp:checkMultilinkLoss: incom %" << pkt.seqno() << " jumps over expected %" << next_seqno + << " - with 1 link only, just reporting"); + return true; + } + + HLOGC(gmlog.Debug, log << "grp:checkMultilinkLoss: incom %" << pkt.seqno() << " jumps over expected %" << next_seqno + << " - setting up as loss candidate"); + m_iRcvPossibleLossSeq = next_seqno; + } + + return found_reportable_losses; +} + +bool CUDTGroup::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) +{ + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "last received"; + + HLOGC(xtlog.Debug, log << CONID() << "NONCONT-SEQUENCE: " << w_log_reason << " %" << w_seq); + + return true; +} + +#if USE_RECEIVER_UNIT_POOL +// [[using locked(m_RcvBufferLock)]] +// (NOTE: This is called as a reverse-object call from the buffer) +void CUDTGroup::returnUnit(CRcvBuffer::UnitHandle& w_u, int32_t muxid) +{ + SRT_ASSERT(!! w_u); + vector& lwater = m_Water[muxid]; + if (lwater.empty()) + lwater.reserve(SRT_RCV_BUFFER_POOL_SERIES_SIZE); + lwater.push_back(CRcvBuffer::UnitHandle()); + w_u.swap(lwater.back()); + ++m_WaterTotalSizeCache; + + if (m_WaterTotalSizeCache >= SRT_RCV_BUFFER_POOL_SERIES_SIZE) + m_bWaterOverflow = true; +} + +void CUDTGroup::flushWater() +{ + std::list trash; + + SharedLock globlock(CUDT::uglobal().m_GlobControlLock); + ScopedLock g(m_RcvBufferLock); + + HLOGC(qrlog.Debug, log << "flushWater: collected for " << m_Water.size() << " multiplexers"); + + IF_HEAVY_LOGGING(int ncleaned = 0); + for (water_t::iterator i = m_Water.begin(); i != m_Water.end(); ++i) + { + int32_t muxid = i->first; + CMultiplexer* muxer = CUDT::uglobal().locateMultiplexer_LOCKED(muxid); + if (muxer) + { + CPacketUnitPool* q = muxer->getBufferQueue(); + q->returnUnitSeries((i->second)); + IF_HEAVY_LOGGING(++ncleaned); + } + // Otherwise leave it in the container. + } + + HLOGC(qrlog.Debug, log << "flushWater: condensed units for " << ncleaned << "/" << m_Water.size() << " muxers"); + + // Remove all items; those that were returned to their + // multiplexers, are NULL already, others will be deleted here. + m_Water.clear(); + m_WaterTotalSizeCache = 0; } +#endif void CUDTGroup::close() { @@ -1062,7 +1608,7 @@ void CUDTGroup::close() CUDTSocket* s = CUDT::uglobal().locateSocket_LOCKED(ig->id); if (!s) { - HLOGC(smlog.Debug, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); + LOGC(smlog.Error, log << "group/close: IPE(NF): group member @" << ig->id << " already deleted"); continue; } @@ -1077,7 +1623,7 @@ void CUDTGroup::close() 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"); + HLOGC(smlog.Debug, log << "group/close: CUTTING OFF @" << ig->id << " (found as @" << s->core().m_SocketID << ") from the group"); } // After all sockets that were group members have their ties cut, @@ -1085,7 +1631,7 @@ void CUDTGroup::close() // removing themselves from the group when closing because they // are unaware of being group members. m_Group.clear(); - m_PeerGroupID = -1; + m_PeerGroupID = SRT_INVALID_SOCK; set epollid; { @@ -1122,7 +1668,7 @@ void CUDTGroup::close() { try { - CUDT::uglobal().close(*i); + CUDT::uglobal().close(*i, SRT_CLS_INTERNAL); } catch (CUDTException&) { @@ -1151,11 +1697,23 @@ void CUDTGroup::close() // 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 implementation. - // CSync::lock_notify_one(m_RcvDataCond, m_RcvDataLock); + // XXX Not exactly; looks like CUDTGroup::recv uses it. + { + ScopedLock lk(m_RcvDataLock); + m_RcvTsbPdCond.notify_all(); + m_RcvDataCond.notify_all(); + } + + // The loop of m_RcvTsbPdThread should exit after setting m_bClosing. + if (m_RcvTsbPdThread.joinable()) + m_RcvTsbPdThread.join(); } -// [[using locked(m_Global->m_GlobControlLock)]] +// [[using locked(m_Global.m_GlobControlLock)]] // [[using locked(m_GroupLock)]] +// XXX TSA blocked because it causes errors on some versions of clang +SRT_TSA_NEEDS_LOCKED_SHARED(CUDTGroup::m_Global.m_GlobControlLock) +SRT_TSA_NEEDS_LOCKED(CUDTGroup::m_GroupLock) void CUDTGroup::send_CheckValidSockets() { vector toremove; @@ -1206,6 +1764,11 @@ int CUDTGroup::send(const char* buf, int len, SRT_MSGCTRL& w_mc) } int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) +{ + return sendMultilink(buf, len, (w_mc), false); +} + +int CUDTGroup::sendMultilink(const char* buf, int len, SRT_MSGCTRL& w_mc, bool use_select SRT_ATR_UNUSED) { // Avoid stupid errors in the beginning. if (len <= 0) @@ -1233,12 +1796,12 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) vector activeLinks; // First, acquire GlobControlLock to make sure all member sockets still exist - enterCS(m_Global.m_GlobControlLock); + m_Global.m_GlobControlLock.lock(); ScopedLock guard(m_GroupLock); if (m_bClosing) { - leaveCS(m_Global.m_GlobControlLock); + m_Global.m_GlobControlLock.unlock(); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } @@ -1246,7 +1809,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // LOCKED: GlobControlLock, GroupLock (RIGHT ORDER!) send_CheckValidSockets(); - leaveCS(m_Global.m_GlobControlLock); + m_Global.m_GlobControlLock.unlock(); // LOCKED: GroupLock (only) // Since this moment GlobControlLock may only be locked if GroupLock is unlocked first. @@ -1270,7 +1833,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (!pu || pu->m_bBroken) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << " detected +Broken - transit to BROKEN"); + log << "grp/sendMultilink: socket @" << d->id << " detected +Broken - transit to BROKEN"); d->sndstate = SRT_GST_BROKEN; d->rcvstate = SRT_GST_BROKEN; } @@ -1280,7 +1843,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (d->sndstate == SRT_GST_BROKEN) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket in BROKEN state: @" << d->id + log << "grp/sendMultilink: socket in BROKEN state: @" << d->id << ", sockstatus=" << SockStatusStr(d->ps ? d->ps->getStatus() : SRTS_NONEXIST)); wipeme.push_back(d->id); continue; @@ -1309,7 +1872,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) continue; } - HLOGC(gslog.Debug, log << "grp/sendBroadcast: socket in IDLE state: @" << d->id << " - will activate it"); + HLOGC(gslog.Debug, log << "grp/sendMultilink: 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 @@ -1323,13 +1886,13 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (d->sndstate == SRT_GST_RUNNING) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket in RUNNING state: @" << d->id << " - will send a payload"); + log << "grp/sendMultilink: 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) << "(" + log << "grp/sendMultilink: socket @" << d->id << " not ready, state: " << StateStr(d->sndstate) << "(" << int(d->sndstate) << ") - NOT sending, SET AS PENDING"); pendingSockets.push_back(d->id); @@ -1409,7 +1972,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // Now we can go to the idle links and attempt to send the payload // also over them. - // TODO: { sendBroadcast_ActivateIdleLinks + // TODO: { sendMultilink_ActivateIdleLinks for (vector::iterator i = idleLinks.begin(); i != idleLinks.end(); ++i) { gli_t d = *i; @@ -1421,7 +1984,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (curseq != SRT_SEQNO_NONE && curseq != lastseq) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << ": override snd sequence %" << lastseq << " with %" + log << "grp/sendMultilink: 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); @@ -1429,7 +1992,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) else { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: socket @" << d->id << ": sequence remains with original value: %" + log << "grp/sendMultilink: socket @" << d->id << ": sequence remains with original value: %" << lastseq << "; SENDING PAYLOAD " << BufferStamp(buf, len)); } @@ -1469,7 +2032,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (nextseq != SRT_SEQNO_NONE) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: $" << id() << ": updating current scheduling sequence %" << nextseq); + log << "grp/sendMultilink: $" << id() << ": updating current scheduling sequence %" << nextseq); m_iLastSchedSeqNo = nextseq; } @@ -1498,7 +2061,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (!pendingSockets.empty() || nblocked) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets (blocked: " << nblocked << "), polling them."); + HLOGC(gslog.Debug, log << "grp/sendMultilink: found pending sockets (blocked: " << nblocked << "), polling them."); // These sockets if they are in pending state, they should be added to m_SndEID // at the connecting stage. @@ -1508,7 +2071,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { // Sanity check - weird pending reported. LOGC(gslog.Error, - log << "grp/sendBroadcast: IPE: reported pending sockets, but EID is empty - wiping pending!"); + log << "grp/sendMultilink: IPE: reported pending sockets, but EID is empty - wiping pending!"); copy(pendingSockets.begin(), pendingSockets.end(), back_inserter(wipeme)); } else @@ -1521,7 +2084,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // If this is the case when if (m_bSynSending && is_pending_blocked) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: will block for " << m_iSndTimeOut << " - waiting for any writable in blocking mode"); + HLOGC(gslog.Debug, log << "grp/sendMultilink: will block for " << m_iSndTimeOut << " - waiting for any writable in blocking mode"); swait_timeout = m_iSndTimeOut; } @@ -1540,7 +2103,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } - HLOGC(gslog.Debug, log << "grp/sendBroadcast: RDY: " << DisplayEpollResults(sready)); + HLOGC(gslog.Debug, log << "grp/sendMultilink: RDY: " << DisplayEpollResults(sready)); // sockets in EX: should be moved to wipeme. // IMPORTANT: we check only PENDING sockets (not blocked) because only @@ -1552,7 +2115,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (CEPoll::isready(sready, *i, SRT_EPOLL_ERR)) { HLOGC(gslog.Debug, - log << "grp/sendBroadcast: Socket @" << (*i) << " reported FAILURE - moved to wiped."); + log << "grp/sendMultilink: Socket @" << (*i) << " reported FAILURE - moved to wiped."); // Failed socket. Move d to wipeme. Remove from eid. wipeme.push_back(*i); int no_events = 0; @@ -1588,7 +2151,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // } - // { sendBroadcast_CheckBlockedLinks() + // { sendMultilink_CheckBlockedLinks() // Alright, we've made an attempt to send a packet over every link. // Every operation was done through a non-blocking attempt, so @@ -1617,8 +2180,8 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { { InvertedLock ung (m_GroupLock); - enterCS(CUDT::uglobal().m_GlobControlLock); - HLOGC(gslog.Debug, log << "grp/sendBroadcast: Locked GlobControlLock, locking back GroupLock"); + CUDT::uglobal().m_GlobControlLock.lock(); + HLOGC(gslog.Debug, log << "grp/sendMultilink: Locked GlobControlLock, locking back GroupLock"); } // Under this condition, as an unlock-lock cycle was done on m_GroupLock, @@ -1658,7 +2221,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) continue; } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING string errmsg = cx.getErrorString(); LOGC(gslog.Debug, log << "SEND STATE link [" << (is - sendstates.begin()) << "]: FAILURE (result:" << is->stat @@ -1669,13 +2232,13 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) } // Now you can leave GlobControlLock, while GroupLock is still locked. - leaveCS(CUDT::uglobal().m_GlobControlLock); + CUDT::uglobal().m_GlobControlLock.unlock(); } // Re-check after the waiting lock has been reacquired if (m_bClosing) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: GROUP CLOSED, ABANDONING"); + HLOGC(gslog.Debug, log << "grp/sendMultilink: GROUP CLOSED, ABANDONING"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } @@ -1715,7 +2278,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); } - HLOGC(gslog.Debug, log << "grp/sendBroadcast: all blocked, trying to common-block on epoll..."); + HLOGC(gslog.Debug, log << "grp/sendMultilink: 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). @@ -1737,7 +2300,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { // 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"); + HLOGC(gslog.Debug, log << "grp/sendMultilink: blocking on any of blocked sockets to allow sending"); // m_iSndTimeOut is -1 by default, which matches the meaning of waiting forever THREAD_PAUSED(); @@ -1826,7 +2389,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) none_succeeded = false; continue; } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING string errmsg = cx.getErrorString(); HLOGC(gslog.Debug, log << "... (repeat-waited) sending FAILED (" << errmsg @@ -1845,12 +2408,12 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); if (!m_bSynSending && (is_pending_blocked || was_blocked)) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: no links are ready for sending"); + HLOGC(gslog.Debug, log << "grp/sendMultilink: no links are ready for sending"); ercode = SRT_EASYNCSND; } else { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)"); + HLOGC(gslog.Debug, log << "grp/sendMultilink: all links broken (none succeeded to send a payload)"); m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); } @@ -1871,7 +2434,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) { // This only sets the state to the socket; the GC process should // pick it up at the next time. - HLOGC(gslog.Debug, log << "grp/sendBroadcast: per PARTIAL SUCCESS, closing failed @" << is->id); + HLOGC(gslog.Debug, log << "grp/sendMultilink: per PARTIAL SUCCESS, closing failed @" << is->id); is->mb->ps->setBrokenClosed(); } } @@ -1925,7 +2488,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) return rstat; } -int CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) { if (!psize) return CUDT::APIError(MJ_NOTSUP, MN_INVAL); @@ -1936,7 +2499,7 @@ int CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) } // [[using locked(this->m_GroupLock)]] -int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) { SRT_ASSERT(psize != NULL); const size_t size = *psize; @@ -1945,7 +2508,9 @@ int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) if (!pdata) { - return 0; + // The request was only to get the number of group members, + // already filled. + return SRT_STATUS_OK; } if (m_Group.size() > size) @@ -1960,7 +2525,7 @@ int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) copyGroupData(*d, (pdata[i])); } - return (int)m_Group.size(); + return SRT_STATUS_OK; } // [[using locked(this->m_GroupLock)]] @@ -1981,20 +2546,20 @@ void CUDTGroup::copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUP if (source.sndstate == SRT_GST_RUNNING || source.rcvstate == SRT_GST_RUNNING) { - w_target.result = 0; + w_target.result = SRT_STATUS_OK; 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.result = SRT_ERROR; w_target.memberstate = SRT_GST_BROKEN; } else { // IDLE or PENDING - w_target.result = 0; + w_target.result = SRT_STATUS_OK; w_target.memberstate = source.sndstate; } @@ -2050,7 +2615,7 @@ void CUDTGroup::fillGroupData(SRT_MSGCTRL& w_out, // MSGCTRL to be written return; } - int st = getGroupData_LOCKED((grpdata), (&grpdata_size)); + SRTSTATUS st = getGroupData_LOCKED((grpdata), (&grpdata_size)); // Always write back the size, no matter if the data were filled. w_out.grpdata_size = grpdata_size; @@ -2065,7 +2630,7 @@ void CUDTGroup::fillGroupData(SRT_MSGCTRL& w_out, // MSGCTRL to be written w_out.grpdata = grpdata; } -// [[using locked(CUDT::uglobal()->m_GlobControLock)]] +// [[using locked(CUDT::uglobal()->m_GlobControlLock)]] // [[using locked(m_GroupLock)]] struct FLookupSocketWithEvent_LOCKED { @@ -2079,6 +2644,7 @@ struct FLookupSocketWithEvent_LOCKED typedef CUDTSocket* result_type; + SRT_TSA_NEEDS_LOCKED(glob->m_GlobControlLock) pair operator()(const pair& es) { CUDTSocket* so = NULL; @@ -2090,9 +2656,13 @@ struct FLookupSocketWithEvent_LOCKED } }; + +// Old unused procedure. +// Leaving here for historical reasons. +#if 0 void CUDTGroup::recv_CollectAliveAndBroken(vector& alive, set& broken) { -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING std::ostringstream ds; ds << "E(" << m_RcvEID << ") "; #define HCLOG(expr) expr @@ -2208,11 +2778,12 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& // This call may wait indefinite time, so GroupLock must be unlocked. InvertedLock ung (m_GroupLock); THREAD_PAUSED(); + HLOGC(grlog.Debug, log << "group/recv: e-polling E" << m_RcvEID << " timeout=" << timeout << "ms"); 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); + CUDT::uglobal(.lock().m_GlobControlLock); } // BOTH m_GlobControlLock AND m_GroupLock are locked here. @@ -2222,7 +2793,7 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& { // GlobControlLock is applied manually, so unlock manually. // GroupLock will be unlocked as per scope. - leaveCS(CUDT::uglobal().m_GlobControlLock); + CUDT::uglobal(.unlock().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. @@ -2255,7 +2826,7 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& 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); + const CEPoll::fmap_t::const_iterator ready_iter = sready.find(sock->core().m_SocketID); if (ready_iter != sready.end()) { if (ready_iter->second & SRT_EPOLL_ERR) @@ -2280,7 +2851,7 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& } } - leaveCS(CUDT::uglobal().m_GlobControlLock); + CUDT::uglobal(.unlock().m_GlobControlLock); return readReady; } @@ -2340,6 +2911,7 @@ int32_t CUDTGroup::getRcvBaseSeqNo() ScopedLock lg(m_GroupLock); return m_RcvBaseSeqNo; } +#endif void CUDTGroup::updateWriteState() { @@ -2347,6 +2919,7 @@ void CUDTGroup::updateWriteState() m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, true); } +#if 0 /// Validate iPktSeqno is in range /// (iBaseSeqno - m_iSeqNoTH/2; iBaseSeqno + m_iSeqNoTH). /// @@ -2384,10 +2957,10 @@ static bool isValidSeqno(int32_t iBaseSeqno, int32_t iPktSeqno) return false; } -int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) +int CUDTGroup::recv_old(char* buf, int len, SRT_MSGCTRL& w_mc) { // First, acquire GlobControlLock to make sure all member sockets still exist - enterCS(m_Global.m_GlobControlLock); + m_Global.m_GlobControlLock.lock(); ScopedLock guard(m_GroupLock); if (m_bClosing) @@ -2397,13 +2970,13 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) // 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); + m_Global.m_GlobControlLock.unlock(); 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); + m_Global.m_GlobControlLock.unlock(); if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -2418,8 +2991,8 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) if (!m_bOpened || !m_bConnected) { LOGC(grlog.Error, - log << boolalpha << "grp/recv: $" << id() << ": ABANDONING: opened=" << m_bOpened - << " connected=" << m_bConnected); + log << "grp/recv: $" << id() << ": ABANDONING: opened" << fmt_onoff(m_bOpened) + << " connected" << fmt_onoff(m_bConnected)); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } @@ -2460,7 +3033,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) if (cnt > 0) { HLOGC(grlog.Debug, - log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": dropped " << cnt + log << "grp/recv: $" << id() << ": @" << ps->core().m_SocketID << ": dropped " << cnt << " packets before reading: m_RcvBaseSeqNo=" << m_RcvBaseSeqNo); } } @@ -2469,15 +3042,16 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) ps->core().m_pRcvBuffer->getFirstReadablePacketInfo(tnow); if (info.seqno == SRT_SEQNO_NONE) { - HLOGC(grlog.Debug, log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": Nothing to read."); + HLOGC(grlog.Debug, log << "grp/recv: $" << id() << ": @" << ps->core().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=%" + log << "grp/recv: $" << id() << ": @" << ps->core().m_SocketID << ": SEQUENCE DISCREPANCY: base=%" << m_RcvBaseSeqNo << " vs pkt=%" << info.seqno << ", setting ESECFAIL"); + ps->core().setAgentCloseReason(SRT_CLS_ROGUE); ps->core().m_bBroken = true; broken.insert(ps); continue; @@ -2487,7 +3061,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) socketToRead = ps; infoToRead = info; - if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && ((CSeqNo(w_mc.pktseq) - CSeqNo(m_RcvBaseSeqNo)) == 1)) + if (m_RcvBaseSeqNo != SRT_SEQNO_NONE && ((SeqNo(w_mc.pktseq) - SeqNo(m_RcvBaseSeqNo)) == 1)) { // We have the next packet. No need to check other read-ready sockets. break; @@ -2514,26 +3088,26 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) else { HLOGC(grlog.Debug, - log << "grp/recv: $" << id() << ": Found first readable packet from @" << socketToRead->m_SocketID + log << "grp/recv: $" << id() << ": Found first readable packet from @" << socketToRead->core().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 %" + log << "grp/recv: $" << id() << ": @" << socketToRead->core().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..."); + log << "grp/recv: $" << id() << ": @" << socketToRead->core().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) + if (res == int(SRT_ERROR)) { LOGC(grlog.Warn, - log << "grp/recv: $" << id() << ": @" << socketToRead->m_SocketID << ": " << srt_getlasterror_str() + log << "grp/recv: $" << id() << ": @" << socketToRead->core().m_SocketID << ": " << srt_getlasterror_str() << ". Retrying next socket..."); broken.insert(socketToRead); continue; @@ -2544,7 +3118,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) // so a packet drop at the start should also be detected by this condition. if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) { - const int32_t iNumDropped = (CSeqNo(w_mc.pktseq) - CSeqNo(m_RcvBaseSeqNo)) - 1; + const int32_t iNumDropped = (SeqNo(w_mc.pktseq) - SeqNo(m_RcvBaseSeqNo)) - 1; if (iNumDropped > 0) { m_stats.recvDrop.count(stats::BytesPackets(iNumDropped * static_cast(avgRcvPacketSize()), iNumDropped)); @@ -2573,13 +3147,13 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) if (cnt > 0) { HLOGC(grlog.Debug, - log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": dropped " << cnt + log << "grp/recv: $" << id() << ": @" << ps->core().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); + m_Global.m_EPoll.update_events(ps->core().m_SocketID, ps->core().m_sPollID, SRT_EPOLL_IN, false); else canReadFurther = true; } @@ -2594,6 +3168,8 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } +#endif // block by if 0 + const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) { static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; @@ -2604,39 +3180,13 @@ const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) return unknown; } -void CUDTGroup::synchronizeDrift(const srt::CUDT* srcMember) +void CUDTGroup::addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt) { - 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"); + if (!m_bOPT_DriftTracer) 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); - } + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->addRcvTsbPdDriftSample(timestamp, tsArrival, rtt); } void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) @@ -2652,7 +3202,7 @@ void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) // links and sending a single packet over these two links could be different. // These stats then don't make much sense in this form, this has to be // redesigned. We use the header size as per IPv4, as it was everywhere. - const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + const int pktHdrSize = CPacket::HDR_SIZE + CPacket::udpHeaderSize(AF_INET); memset(perf, 0, sizeof *perf); @@ -2686,6 +3236,271 @@ void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) } } +// The REAL version for the new group receiver. +// +int CUDTGroup::do_recv(char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + CUniqueSync tscond (m_RcvDataLock, m_RcvTsbPdCond); + + /* XXX DEBUG STUFF - enable when required + char charbool[2] = {'0', '1'}; + char ptrn [] = "RECVMSG/BEGIN BROKEN 1 CONN 1 CLOSING 1 SYNCR 1 NMSG "; + int pos [] = {21, 28, 38, 46, 53}; + ptrn[pos[0]] = charbool[m_bBroken]; + ptrn[pos[1]] = charbool[m_bConnected]; + ptrn[pos[2]] = charbool[m_bClosing]; + 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); + // */ + + if (m_bClosing) + { + HLOGC(arlog.Debug, log << CONID() << "grp:recv: CONNECTION BROKEN - reading from recv buffer just for formality"); + + int as_result = 0; + { + ScopedLock lk (m_RcvBufferLock); + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + as_result = m_pRcvBuffer->readMessage(data, len, (w_mctrl)); + } + } + + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + const int res = as_result; + + w_mctrl.srctime = 0; + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(tslog.Debug, "SIGNAL TSBPD thread to schedule wakeup FOR EXIT"); + tscond.notify_all(); + } + else + { + HLOGP(tslog.Debug, "NOT pinging TSBPD - not set"); + } + + if (!isRcvBufferReady()) + { + // read is not available any more + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + } + + if (res == 0) + { + if (!m_bOPT_MessageAPI && !m_bOpened) + return 0; + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + else + return res; + } + + pair seqrange; + + if (!m_bSynRecving) + { + HLOGC(arlog.Debug, log << CONID() << "grp:recv: BEGIN ASYNC MODE. Going to extract payload size=" << len); + + int as_result = 0; + { + ScopedLock lk (m_RcvBufferLock); + bool ready = m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + + if (ready) + { + as_result = m_pRcvBuffer->readMessage(data, len, (w_mctrl), (&seqrange)); + } + } + + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + const int res = as_result; + + HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res); + + if (res == 0) + { + // read is not available any more + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGC(arlog.Debug, log << "grp:recv: nothing to read, SIGNAL TSBPD (" << (m_bTsbpdWaitForExtraction ? "" : "un") << "expected), return AGAIN"); + tscond.notify_all(); + } + else + { + HLOGP(arlog.Debug, "grp:recv: nothing to read, return AGAIN"); + } + + // Shut up EPoll if no more messages in non-blocking mode + CUDT::uglobal().m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + // Forced to return 0 instead of throwing exception, in case of AGAIN/READ + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + + if (!m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + { + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGC(arlog.Debug, log << "grp:recv: ONE PACKET READ, but no more avail, SUGNAL TSBPD (" << (m_bTsbpdWaitForExtraction ? "" : "un") << "expected), return AGAIN"); + tscond.notify_all(); + } + else + { + HLOGP(arlog.Debug, "grp:recv: DATA READ, but nothing more"); + } + + // Shut up EPoll if no more messages in non-blocking mode + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + + } + return res; + } + + HLOGC(arlog.Debug, log << CONID() << "grp:recv: 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. + const steady_clock::duration recv_timeout = m_iRcvTimeOut < 0 ? seconds_from(1) : milliseconds_from(m_iRcvTimeOut); + + CSync recv_cond (m_RcvDataCond, tscond.locker()); + + do + { + if (stillConnected() && !timeout && !isRcvBufferReady()) + { + /* Kick TsbPd thread to schedule next wakeup (if running) */ + if (m_bTsbPd) + { + // 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. + HLOGC(tslog.Debug, log << CONID() << "grp:recv: SIGNAL TSBPD" << (m_bTsbpdWaitForNewPacket ? " (spurious)" : "")); + tscond.notify_one(); + } + + THREAD_PAUSED(); + do + { + // `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 timeout is + // higher than this. + const steady_clock::time_point exptime = steady_clock::now() + recv_timeout; + + HLOGC(tslog.Debug, + log << CONID() << "grp:recv: fall asleep up to TS=" << FormatTime(exptime) + << " lock=" << (&m_RcvDataLock) << " cond=" << (&m_RcvDataCond)); + + if (!recv_cond.wait_until(exptime)) + { + if (m_iRcvTimeOut >= 0) // otherwise it's "no timeout set" + timeout = true; + HLOGP(tslog.Debug, + "grp:recv: DATA COND: EXPIRED -- checking connection conditions and rolling again"); + } + else + { + HLOGP(tslog.Debug, "grp:recv: DATA COND: KICKED."); + } + } while (stillConnected() && !timeout && (!isRcvBufferReady())); + THREAD_RESUMED(); + + HLOGC(tslog.Debug, + log << CONID() << "grp:recv: lock-waiting loop exited: stillConntected=" << stillConnected() + << " timeout=" << timeout << " data-ready=" << isRcvBufferReady()); + } + + /* XXX DEBUG STUFF - enable when required + LOGC(arlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected + << " CLOSING " << m_bClosing << " TMOUT " << timeout + << " NMSG " << m_pRcvBuffer->getRcvMsgNum()); + */ + + m_RcvBufferLock.lock(); + res = m_pRcvBuffer->readMessage((data), len, (w_mctrl)); + m_RcvBufferLock.unlock(); + HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res); + + { + ScopedLock lk (m_GroupLock); + fillGroupData((w_mctrl), w_mctrl); + } + + + if (m_bClosing) + { + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + } + else if (!m_bConnected) + { + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } + } while ((res == 0) && !timeout); + + if (!isRcvBufferReady()) + { + // Falling here means usually that res == 0 && timeout == true. + // res == 0 would repeat the above loop, unless there was also a timeout. + // timeout has interrupted the above loop, but with res > 0 this condition + // wouldn't be satisfied. + + // read is not available any more + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + HLOGP(tslog.Debug, "recvmsg: SIGNAL TSBPD (buffer empty)"); + tscond.notify_all(); + } + + // Shut up EPoll if no more messages in non-blocking mode + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + } + + // Unblock when required + // LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT"); + + if ((res <= 0) && (m_iRcvTimeOut >= 0)) + { + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + } + + return res; +} + +int CUDTGroup::recv(char* data, int len, SRT_MSGCTRL& w_mctrl) +{ + int res = do_recv(data, len, (w_mctrl)); +#if USE_RECEIVER_UNIT_POOL + if (m_bWaterOverflow) + { + m_bWaterOverflow = false; + flushWater(); + } +#endif + return res; +} + /// @brief Compares group members by their weight (higher weight comes first). struct FCompareByWeight { @@ -2760,26 +3575,26 @@ class StabilityTracer ~StabilityTracer() { - srt::sync::ScopedLock lck(m_mtx); + 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, + void trace(const CUDT& u, const 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); + ScopedLock lck(m_mtx); create_file(); - m_fout << srt::sync::FormatTime(currtime) << ","; + m_fout << FormatTime(currtime) << ","; m_fout << u.id() << ","; m_fout << weight << ","; m_fout << u.peerLatency_us() << ","; - m_fout << u.SRTT() << ","; + m_fout << u.avgRTT() << ","; 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 << (is_zero(u.freshActivationStart()) ? -1 : (count_microseconds(currtime - u.freshActivationStart()))) << ","; m_fout << activation_period_us << "\n"; m_fout.flush(); } @@ -2787,7 +3602,6 @@ class StabilityTracer private: void print_header() { - //srt::sync::ScopedLock lck(m_mtx); m_fout << "Timepoint,SocketID,weight,usLatency,usRTT,usRTTVar,usStabilityTimeout,usSinceLastResp,State,usSinceActivation,usActivationPeriod\n"; } @@ -2796,9 +3610,10 @@ class StabilityTracer if (m_fout.is_open()) return; - std::string str_tnow = srt::sync::FormatTimeSys(srt::sync::steady_clock::now()); + std::string str_tnow = FormatTimeSys(steady_clock::now()); str_tnow.resize(str_tnow.size() - 7); // remove trailing ' [SYST]' part - while (str_tnow.find(':') != std::string::npos) { + while (str_tnow.find(':') != std::string::npos) + { str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "stability_trace_" + str_tnow + ".csv"; @@ -2810,7 +3625,7 @@ class StabilityTracer } private: - srt::sync::Mutex m_mtx; + Mutex m_mtx; std::ofstream m_fout; }; @@ -2984,7 +3799,7 @@ CUDTGroup::BackupMemberState CUDTGroup::sendBackup_QualifyActiveState(const gli_ // 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); + : min(max(min_stability_us, 2 * u.avgRTT() + 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; @@ -2995,9 +3810,9 @@ CUDTGroup::BackupMemberState CUDTGroup::sendBackup_QualifyActiveState(const gli_ return BKUPST_ACTIVE_UNSTABLE; } - enterCS(u.m_StatsLock); + u.m_StatsLock.lock(); const int64_t drop_total = u.m_stats.sndr.dropped.total.count(); - leaveCS(u.m_StatsLock); + u.m_StatsLock.unlock(); const bool have_new_drops = d->pktSndDropTotal != drop_total; if (have_new_drops) @@ -3210,7 +4025,7 @@ size_t CUDTGroup::sendBackup_TryActivateStandbyIfNeeded( { 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()); + cudt.restoreRateEstimator(w_sendBackupCtx.m_rateEstimate); // TODO: At this point all packets that could be sent // are located in m_SenderBuffer. So maybe just use sendBackupRexmit()? @@ -3590,7 +4405,7 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx 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! + CUDT::uglobal().close(s, SRT_CLS_INTERNAL); // << LOCKS m_GlobControlLock, then GroupLock! } else { @@ -3774,7 +4589,9 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) } // Only live streaming is supported - if (len > SRT_LIVE_MAX_PLSIZE) + // Also - as the group may use potentially IPv4 and IPv6 connections + // in the same group, use the size that fits both + if (len > SRT_MAX_PLSIZE_AF_INET6) { LOGC(gslog.Error, log << "grp/send(backup): buffer size=" << len << " exceeds maximum allowed in live mode"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -3783,19 +4600,19 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) // [[using assert(this->m_pSndBuffer != nullptr)]]; // First, acquire GlobControlLock to make sure all member sockets still exist - enterCS(m_Global.m_GlobControlLock); + m_Global.m_GlobControlLock.lock(); ScopedLock guard(m_GroupLock); if (m_bClosing) { - leaveCS(m_Global.m_GlobControlLock); + m_Global.m_GlobControlLock.unlock(); LOGC(gslog.Error, log << "grp/send(backup): Cannot send, connection lost!"); 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); + m_Global.m_GlobControlLock.unlock(); steady_clock::time_point currtime = steady_clock::now(); @@ -3974,7 +4791,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& SRT_ASSERT(w_nsuccessful == 0); SRT_ASSERT(w_maxActiveWeight == 0); - int group_send_result = SRT_ERROR; + int group_send_result = int(SRT_ERROR); // TODO: implement iterator over active links typedef vector::const_iterator const_iter_t; @@ -3988,7 +4805,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& // 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; + int sndresult = int(SRT_ERROR); try { // This must be wrapped in try-catch because on error it throws an exception. @@ -4001,7 +4818,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& { w_cx = e; erc = e.getErrorCode(); - sndresult = SRT_ERROR; + sndresult = int(SRT_ERROR); } const bool send_succeeded = sendBackup_CheckSendStatus( @@ -4019,7 +4836,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_maxActiveWeight = max(w_maxActiveWeight, d->weight); if (u.m_pSndBuffer) - w_sendBackupCtx.setRateEstimate(u.m_pSndBuffer->getRateEstimator()); + u.m_pSndBuffer->saveEstimation((w_sendBackupCtx.m_rateEstimate)); } else if (erc == SRT_EASYNCSND) { @@ -4040,7 +4857,7 @@ 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"); + LOGC(gslog.Fatal, log << core.CONID() << "IPE: sendBackupRexmit: sender buffer empty"); // Although act as if it was successful, otherwise you'll get connection break return 0; @@ -4072,8 +4889,9 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) // 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"); + log << core.CONID() << "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 { @@ -4082,11 +4900,11 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) // 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!")); + const int32_t old SRT_ATR_UNUSED = core.schedSeqNo(); + const bool success SRT_ATR_UNUSED = core.overrideSndSeqNo(curseq); + LOGC(gslog.Debug, + log << core.CONID() << "sendBackupRexmit: OVERRIDING seq %" << old << " with %" << curseq + << (success ? " - succeeded" : " - FAILED!")); } } @@ -4094,8 +4912,8 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) 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"); + log << core.CONID() << "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 } @@ -4111,17 +4929,20 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) { // Stop sending if one sending ended up with error LOGC(gslog.Warn, - log << "sendBackupRexmit: sending from buffer stopped at %" << core.schedSeqNo() << " and FAILED"); + log << core.CONID() << "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); + HLOGC(gslog.Debug, + log << core.CONID() << "sendBackupRexmit: pre-sent collected %" << curseq << " - %" << w_mc.pktseq); return stat; } +// XXX DEAD CODE. // [[using locked(CUDTGroup::m_GroupLock)]]; void CUDTGroup::ackMessage(int32_t msgno) { @@ -4157,7 +4978,7 @@ void CUDTGroup::ackMessage(int32_t msgno) m_iSndAckedMsgNo = msgno; } -void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli) +void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli, const CPacket& ctrlpkt SRT_ATR_UNUSED, const time_point& tsArrival SRT_ATR_UNUSED) { // received keepalive for that group member // In backup group it means that the link went IDLE. @@ -4192,10 +5013,17 @@ void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli) log << "GROUP: received KEEPALIVE in @" << gli->id << " active=PAST - link turning snd=IDLE"); } } + + ScopedLock lck(m_RcvBufferLock); + m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); + if (m_bOPT_DriftTracer) + m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); + } void CUDTGroup::internalKeepalive(SocketData* gli) { + ScopedLock gl(m_GroupLock); // 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 @@ -4210,7 +5038,8 @@ void CUDTGroup::internalKeepalive(SocketData* gli) } } -CUDTGroup::BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_LIVE_MAX_PLSIZE /*, 1000*/); +// Use the bigger size of SRT_MAX_PLSIZE to potentially fit both IPv4/6 +BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_MAX_PLSIZE_AF_INET /*, 1000*/); // Forwarder needed due to class definition order int32_t CUDTGroup::generateISN() @@ -4222,6 +5051,7 @@ void CUDTGroup::setGroupConnected() { if (!m_bConnected) { + HLOGC(cnlog.Debug, log << "GROUP: First socket connected, SETTING GROUP CONNECTED (" << m_Group.size() << " members now)"); // Switch to connected state and give appropriate signal m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_CONNECT, true); m_bConnected = true; @@ -4235,7 +5065,7 @@ void CUDTGroup::updateLatestRcv(CUDTSocket* s) return; HLOGC(grlog.Debug, - log << "updateLatestRcv: BACKUP group, updating from active link @" << s->m_SocketID << " with %" + log << "updateLatestRcv: BACKUP group, updating from active link @" << s->id() << " with %" << s->core().m_iRcvLastAck); CUDT* source = &s->core(); @@ -4288,9 +5118,26 @@ void CUDTGroup::updateLatestRcv(CUDTSocket* s) // operation will need receiver lock, so it might // risk a deadlock. + int bufseq; + { + ScopedLock bg (m_RcvBufferLock); + bufseq = m_pRcvBuffer->getStartSeqNo(); + } + int32_t latest_seq = CSeqNo::maxseq(bufseq, source->m_iRcvLastAck); + for (size_t i = 0; i < targets.size(); ++i) { - targets[i]->updateIdleLinkFrom(source); + targets[i]->updateIdleLinkFrom(latest_seq, source->id()); + } +} + +void CUDTGroup::getMemberSockets(std::set& w_ids) const +{ + ScopedLock gl (m_GroupLock); + + for (cgli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + w_ids.insert(gi->id); } } @@ -4310,9 +5157,9 @@ void CUDTGroup::activateUpdateEvent(bool still_have_items) void CUDTGroup::addEPoll(int eid) { - enterCS(m_Global.m_EPoll.m_EPollLock); + m_Global.m_EPoll.m_EPollLock.lock(); m_sPollID.insert(eid); - leaveCS(m_Global.m_EPoll.m_EPollLock); + m_Global.m_EPoll.m_EPollLock.unlock(); bool any_read = false; bool any_write = false; @@ -4371,9 +5218,9 @@ void CUDTGroup::removeEPollEvents(const int eid) void CUDTGroup::removeEPollID(const int eid) { - enterCS(m_Global.m_EPoll.m_EPollLock); + m_Global.m_EPoll.m_EPollLock.lock(); m_sPollID.erase(eid); - leaveCS(m_Global.m_EPoll.m_EPollLock); + m_Global.m_EPoll.m_EPollLock.unlock(); } void CUDTGroup::updateFailedLink() @@ -4403,7 +5250,411 @@ void CUDTGroup::updateFailedLink() } } -#if ENABLE_HEAVY_LOGGING +/* + +XXX This part is blocked because the code used here will be useful for the +common sender buffer initiative, but in the current form it's useless. Will +have to be reworked, too. In the current implementation the loss list is +built in into the sender buffer, so with a common sender buffer for the group +this list will be common, too, although might be that this code will be +integrated with the appropriate fragment of CUDT functionality responsible +for sender buffer and managing losses. + +// Update on received loss report or request to retransmit on NAKREPORT. +bool CUDTGroup::updateSendPacketLoss(bool use_send_sched, const std::vector< std::pair >& seqlist) +{ + ScopedLock lg (m_LossAckLock); + + typedef std::vector< std::pair > seqlist_t; + + // XXX int num = 0; // for stats + + HLOGC(gslog.Debug, log << "INITIAL:"); + HLOGC(gslog.Debug, m_pSndLossList->traceState(log)); + + // Add the loss list to the groups loss list + for (seqlist_t::const_iterator seqpair = seqlist.begin(); seqpair != seqlist.end(); ++seqpair) + { + int len = m_pSndLossList->insert(seqpair->first, seqpair->second); + (void)len; // sim usage + // num += len; + HLOGC(gslog.Debug, log << "LOSS Added: " << Printable(seqlist) << " length: " << len); + HLOGC(gslog.Debug, m_pSndLossList->traceState(log)); + } + + if (use_send_sched) + { + (void)0; + } + return true; +} + +void CUDTGroup::updateOnACK(int32_t ackdata_seqno) +{ + ScopedLock guard(m_LossAckLock); + if (CSeqNo::seqcmp(m_SndLastDataAck, ackdata_seqno) < 0) + { + // remove any loss that predates 'ack' (not to be considered loss anymore) + m_pSndLossList->removeUpTo(CSeqNo::decseq(ackdata_seqno)); + m_SndLastDataAck = ackdata_seqno; + } +} + +// This is almost a copy of the CUDT::packLostData except that: +// - it uses a separate mechanism to extract the selected sequence number +// (which is known from the schedule, while the schedule is filled upon incoming loss request) +// - it doesn't check if the loss was received too early (it's more complicated this time) +int CUDTGroup::packLostData(CUDT* core, int32_t& w_last_send_upd, CSndPacket& w_packet, int32_t exp_seq) +{ +#ifdef SRT_ENABLE_MAXREXMITBW + // XXX Here we use 0 so that it picks up the average sent packet size. + // Consider adding a functionality to m_pSndBuffer to get the real size + // of the next retransmission candidate. + if (!core->retransmissionRateFit(0)) + return 0; +#endif + steady_clock::time_point tsOrigin; + + typedef CSndBuffer::DropRange DropRange; + std::vector drops; + + // protect m_iSndLastDataAck from updating by ACK processing + UniqueLock ackguard(m_LossAckLock); + //const steady_clock::time_point time_now = steady_clock::now(); + //const steady_clock::time_point time_nak = time_now - microseconds_from(core->m_iSRTT - 4 * core->m_iRTTVar); + int32_t last_send_seq = m_SndLastSeqNo; + + int payload = 0; // will stay 0 if nothing was extracted + + // REPEATABLE BLOCK + // Reason: if by some reason the packet could not be extracted as loss, + // try again with popLostSeq(). Repeating is not done for explicit exp_seq. + for (;;) + { + // XXX This is temporarily used for broadcast with common loss list. + bool have_extracted = false; + payload = 0; // set before every repetition + + IF_HEAVY_LOGGING(const char* as = "FIRST FOUND"); + int32_t seq; + if (exp_seq == SRT_SEQNO_NONE) + { + seq = m_pSndLossList->popLostSeq(); + have_extracted = (seq != SRT_SEQNO_NONE); + } + else + { + IF_HEAVY_LOGGING(as = "EXPECTED"); + seq = exp_seq; + have_extracted = m_pSndLossList->popLostSeq(exp_seq); + } + + HLOGC(gslog.Debug, log << "CUDTGroup::packLostData: " << (have_extracted ? "" : "NOT") << " extracted " + << as << " %" << exp_seq); + + if (!have_extracted) + { + // This is not the sequence we are looking for. + HLOGC(gslog.Debug, log << "packLostData: expected %" << exp_seq << " not found in the group's loss list"); + break; + } + + DropRange single_drop; + payload = core->m_pSndBuffer->readOldPacket(seq, (w_packet), (tsOrigin), (single_drop)); + if (payload == CSndBuffer::READ_DROP) + { + SRT_ASSERT(CSeqNo::seqoff(single_drop.seqno[DropRange::BEGIN], single_drop.seqno[DropRange::END]) >= 0); + + HLOGC(qslog.Debug, + log << "... loss-reported packets expired in SndBuf - requesting DROP: #" + << single_drop.msgno << " %(" << single_drop.seqno[DropRange::BEGIN] << " - " + << single_drop.seqno[DropRange::END] << ")"); + drops.push_back(single_drop); + + continue; + } + + break; + } + + if (!drops.empty()) + { + for (size_t i = 0; i < drops.size(); ++i) + { + CSndBuffer::DropRange& buffer_drop = drops[i]; + int32_t seqpair[2] = { + buffer_drop.seqno[DropRange::BEGIN], + buffer_drop.seqno[DropRange::END] + }; + + HLOGC(qslog.Debug, + log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" + << seqpair[0] << " - " << seqpair[1] << ")"); + + // skip all dropped packets + const int32_t latest = buffer_drop.seqno[DropRange::END]; + last_send_seq = CSeqNo::maxseq(last_send_seq, latest); + m_pSndLossList->removeUpTo(latest); + + // See interpretation in processCtrlDropReq(). We don't know the message number, + // so we request that the drop be exclusively sequence number based. + int32_t msgno = SRT_MSGNO_CONTROL; + core->sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); + } + } + + w_last_send_upd = last_send_seq; + + // 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.unlock(); + + updateSentSeq(last_send_seq); + + if (payload == 0) //nothing to send + return 0; + + core->m_StatsLock.lock(); + core->m_stats.sndr.sentRetrans.count(payload); + core->m_StatsLock.unlock(); + + // Despite the contextual interpretation of packet.m_iMsgNo around + // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular + // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. + // So, set here the rexmit flag if the peer understands it. + if (core->m_bPeerRexmitFlag) + { + w_packet.pkt.set_msgflags(w_packet.pkt.msgflags() | PACKET_SND_REXMIT); + } + + // XXX we don't predict any other use of groups than live, + // so tsbpdmode is always on. Unblock this code otherwise: + // if (!m_bTsbpdMode) + // { + // tsOrigin = steady_clock::now(); + // } + + // Only assert here. Any user-supplied origin time that is earlier + // than start time should be rejected with API error. + SRT_ASSERT(tsOrigin > m_tsStartTime); + + CUDT::setPacketTS(w_packet.pkt, m_tsStartTime, tsOrigin); + + return payload; +} +*/ + +// Receiver part + +int CUDTGroup::checkLazySpawnTsbPdThread() +{ + // It is confirmed that the TSBPD thread is required, + // so just check if it's running already. + + ScopedLock lock(m_GroupLock); + + // Block also before checking joinable because the first socket finding it + // starts the thread. Meaning, the first creating the group, per handshake, + // dispatched in the multiplexer's thread. + if (!m_RcvTsbPdThread.joinable()) + { + if (m_bClosing) // Check again to protect join() in CUDT::releaseSync() + return -1; + + using namespace hvu; + + HLOGP(qrlog.Debug, "Spawning Group TSBPD thread"); +#if HVU_ENABLE_HEAVY_LOGGING + // Take the last 2 ciphers from the socket ID. + string s = fmts(id(), fmtc().fillzero().width(2)); + + const string& tn = fmtcat("SRT:GLat:$", s.substr(s.size()-2, 2)); + + ThreadName tnkeep(tn); + const string& thname = tn; +#else + const string thname = "SRT:GLat"; +#endif + if (!StartThread(m_RcvTsbPdThread, CUDTGroup::tsbpd, this, thname)) + return -1; + } + + return 0; +} + +void* CUDTGroup::tsbpd(void* param) +{ + CUDTGroup* self = (CUDTGroup*)param; + + THREAD_STATE_INIT("SRT:GLat"); + + // 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!!! + ScopedGroupKeeper gkeeper(self); + + CUniqueSync recvdata_lcc(self->m_RcvDataLock, self->m_RcvDataCond); + CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); + + self->m_bTsbpdWaitForNewPacket = true; + HLOGC(gmlog.Debug, log << "grp/TSBPD: START"); + while (!self->m_bClosing) + { + self->m_RcvBufferLock.lock(); + const steady_clock::time_point tnow = steady_clock::now(); + + self->m_pRcvBuffer->updRcvAvgDataSize(tnow); + const srt::CRcvBuffer::PacketInfo info = self->m_pRcvBuffer->getFirstValidPacketInfo(); + + const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); + steady_clock::time_point tsNextDelivery = info.tsbpd_time; + bool rxready = false; + + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); + + bool synch_loss_after_drop = false; + + if (!self->m_bTLPktDrop) + { + rxready = !info.seq_gap && is_time_to_deliver; + } + else if (is_time_to_deliver) + { + rxready = true; + if (info.seq_gap) + { + const int iDropCnt SRT_ATR_UNUSED = self->rcvDropTooLateUpTo(info.seqno); + + // Part required for synchronizing loss state in all group members should + // follow the drop, but this must be done outside the lock on the buffer. + synch_loss_after_drop = iDropCnt; + +#if HVU_ENABLE_LOGGING + const int64_t timediff_us = count_microseconds(tnow - info.tsbpd_time); +#endif +#if HVU_ENABLE_HEAVY_LOGGING + HLOGC(tslog.Debug, + log << self->CONID() << "grp/tsbpd: DROPSEQ: up to seqno %" << CSeqNo::decseq(info.seqno) << " (" + << iDropCnt << " packets) playable at " << FormatTime(info.tsbpd_time) << " delayed " + << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') << (timediff_us % 1000) + << " ms"); +#endif + LOGC(brlog.Warn, + log << self->CONID() << "RCV-DROPPED " << iDropCnt << " packet(s). Packet seqno %" << info.seqno + << " delayed for " << (timediff_us / 1000) << "." << std::setw(3) << std::setfill('0') + << (timediff_us % 1000) << " ms"); + + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } + } + self->m_RcvBufferLock.unlock(); + + if (synch_loss_after_drop) + self->synchronizeLoss(info.seqno); + + if (rxready) + { + HLOGC(tslog.Debug, + log << self->CONID() << "grp/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) + { + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: SIGNAL blocking recv()"); + recvdata_lcc.notify_one(); + } + /* + * Set EPOLL_IN to wakeup any thread waiting on epoll + */ + CUDT::uglobal().m_EPoll.update_events(self->id(), self->m_sPollID, SRT_EPOLL_IN, true); + CGlobEvent::triggerEvent(); + tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. + } + else + { + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: NEXT PACKET: " + << (info.tsbpd_time == time_point() ? "NOT AVAILABLE" : FormatTime(info.tsbpd_time)) + << " vs. now=" << FormatTime(tnow)); + } + + SRT_ATR_UNUSED bool got_signal = true; + + // None should be true in case when waiting for the next time. + // If there is a ready packet, but only to be extracted in some time, + // then sleep until this time and then retry triggering. + self->m_bTsbpdWaitForNewPacket = false; + self->m_bTsbpdWaitForExtraction = false; + + // NOTE: if (rxready) then tsNextDelivery == 0. So this branch is for a situation + // when: + // - no packet is currently READY for delivery + // - but there is a packet candidate ready soon. + // So you have to sleep until it's ready and then trigger read-readiness. + if (!is_zero(tsNextDelivery)) + { + IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); + /* + * Buffer at head of queue is not ready to play. + * Schedule wakeup when it will be. + */ + HLOGC(tslog.Debug, + log << self->CONID() << "grp/tsbpd: FUTURE PACKET seq=" << info.seqno + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms up to " << FormatTime(tsNextDelivery)); + THREAD_PAUSED(); + got_signal = tsbpd_cc.wait_until(tsNextDelivery); + THREAD_RESUMED(); + } + else + { + /* + * We have just signaled epoll; or + * receive queue is empty; or + * next buffer to deliver is not in receive queue (missing packet in sequence). + * + * Block until woken up by one of the following event: + * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time + * if any) + * - New packet arrived + * - Closing the connection + */ + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: " << (rxready ? "expecting user's packet retrieval" : "no data to deliver") << ", scheduling wakeup on reception"); + + // If there was rxready, then epoll readiness was set, and recvdata_lcc was triggered + // - so it should remain sleeping until the user's thread has extracted EVERY ready packet and turned epoll back to not-ready. + // Otherwise the situation was that there's no ready packet at all. + // - so it should remain sleeping until a new packet arrives and it is potentially extractable. + if (rxready) + { + self->m_bTsbpdWaitForExtraction = true; + } + else + { + self->m_bTsbpdWaitForNewPacket = true; + } + THREAD_PAUSED(); + tsbpd_cc.wait(); + THREAD_RESUMED(); + } + + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: WAKE UP on " << (got_signal ? "signal" : "timeout") + << "; now=" << FormatTime(steady_clock::now())); + } + THREAD_EXIT(); + HLOGC(tslog.Debug, log << self->CONID() << "grp/tsbpd: EXITING"); + return NULL; +} + + +#if HVU_ENABLE_HEAVY_LOGGING // [[using maybe_locked(CUDT::uglobal()->m_GlobControlLock)]] void CUDTGroup::debugGroup() { @@ -4414,7 +5665,7 @@ void CUDTGroup::debugGroup() 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 + log << " ... id { agent=@" << gi->id << " peer=@" << gi->ps->core().m_PeerID << " } address { agent=" << gi->agent.str() << " peer=" << gi->peer.str() << "} " << " state {snd=" << StateStr(gi->sndstate) << " rcv=" << StateStr(gi->rcvstate) << "}"); } diff --git a/srtcore/group.h b/srtcore/group.h index e47f1243a..e10668a1c 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -25,10 +25,12 @@ Written by namespace srt { -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING const char* const srt_log_grp_state[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; #endif +// [[fwd]] +class CUDTSocket; class CUDTGroup { @@ -36,6 +38,7 @@ class CUDTGroup typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; + typedef sync::AtomicDuration atomic_duration; typedef sync::steady_clock steady_clock; typedef groups::SocketData SocketData; typedef groups::SendBackupCtx SendBackupCtx; @@ -63,8 +66,15 @@ class CUDTGroup 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;} + static sync::atomic s_tokenGen; + static int32_t genToken() + { + // It's not a problem when occasionally it has been increased multiple + // times. Important is that the returned value is never negative. + if (!s_tokenGen.compare_exchange(int32_t(0x7FFFFFFF), 0)) + ++s_tokenGen; + return s_tokenGen.load(); + } struct ConfigItem { @@ -101,7 +111,8 @@ class CUDTGroup typedef std::list group_t; typedef group_t::iterator gli_t; - typedef std::vector< std::pair > sendable_t; + typedef group_t::const_iterator cgli_t; + typedef std::vector< std::pair > sendable_t; struct Sendstate { @@ -114,6 +125,8 @@ class CUDTGroup CUDTGroup(SRT_GROUP_TYPE); ~CUDTGroup(); + void createBuffers(const CUDT& core, const time_point& tsbpd_start_time); + SocketData* add(SocketData data); struct HaveID @@ -128,7 +141,7 @@ class CUDTGroup bool contains(SRTSOCKET id, SocketData*& w_f) { - srt::sync::ScopedLock g(m_GroupLock); + sync::ScopedLock g(m_GroupLock); gli_t f = std::find_if(m_Group.begin(), m_Group.end(), HaveID(id)); if (f == m_Group.end()) { @@ -140,7 +153,9 @@ class CUDTGroup } // NEED LOCKING + SRT_TSA_NEEDS_LOCKED(m_GroupLock) gli_t begin() { return m_Group.begin(); } + SRT_TSA_NEEDS_LOCKED(m_GroupLock) gli_t end() { return m_Group.end(); } /// Remove the socket from the group container. @@ -151,8 +166,8 @@ class CUDTGroup /// @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); + using srt::logging::gmlog; + sync::ScopedLock g(m_GroupLock); bool empty = false; LOGC(gmlog.Note, log << "group/remove: removing member @" << id << " from group $" << m_GroupID); @@ -161,6 +176,7 @@ class CUDTGroup if (f != m_Group.end()) { m_Group.erase(f); + updateErasedLink(); // Reset sequence numbers on a dead group so that they are // initialized anew with the new alive connection within @@ -178,7 +194,9 @@ class CUDTGroup // number will collide with any ISN provided by a socket. // Also since now every socket will derive this ISN. m_iLastSchedSeqNo = generateISN(); - resetInitialRxSequence(); + // XXX resetInitialRxSequence() was here, but + // this uses a field that is removed, and replaced by + // m_RcvLastSeqNo empty = true; } } @@ -199,20 +217,46 @@ class CUDTGroup bool groupEmpty() { - srt::sync::ScopedLock g(m_GroupLock); + sync::ScopedLock g(m_GroupLock); + return groupEmpty_LOCKED(); + } + + SRT_TSA_NEEDS_LOCKED(m_GroupLock) + bool groupEmpty_LOCKED() const + { return m_Group.empty(); } + bool groupPending() + { + sync::ScopedLock g(m_GroupLock); + return groupPending_LOCKED(); + } + + bool groupPending_LOCKED() + { + return !m_PendingListeners.empty(); + } + + std::vector clearPendingListeners() + { + sync::ScopedLock g(m_GroupLock); + std::vector pending; + std::swap(m_PendingListeners, pending); + return pending; + } + 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); + 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); + int sendMultilink(const char* buf, int len, SRT_MSGCTRL& w_mc, bool use_select); static int32_t generateISN(); private: // For Backup, sending all previous packet - int sendBackupRexmit(srt::CUDT& core, SRT_MSGCTRL& w_mc); + int sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc); // Support functions for sendBackup and sendBroadcast /// Check if group member is idle. @@ -233,7 +277,7 @@ class CUDTGroup /// @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); + void sendBackup_AssignBackupState(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. @@ -316,13 +360,28 @@ class CUDTGroup SRT_KM_STATE getGroupEncryptionState(); public: + // NOTE: recv will also do periodic cleanups. int recv(char* buf, int len, SRT_MSGCTRL& w_mc); + int do_recv(char* buf, int len, SRT_MSGCTRL& w_mc); + + // XXX not sure if taking time here is right + bool isRcvBufferReady() const + { + srt::sync::ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); + } + + size_t getAvailBufSize(int32_t last_ack) const + { + srt::sync::ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->getAvailSize(last_ack); + } 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); + void deriveSettings(CUDT* source); bool applyFlags(uint32_t flags, HandshakeSide); SRT_SOCKSTATUS getStatus(); @@ -362,18 +421,18 @@ class CUDTGroup /// @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); + std::vector providePacket(int32_t exp_sequence, int32_t sequence, 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 readyPackets(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); + void syncWithFirstSocket(const CUDT& core, const HandshakeSide side); + SRTSTATUS getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); + SRTSTATUS getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); /// Predicted to be called from the reading function to fill /// the group data array as requested. @@ -383,14 +442,14 @@ class CUDTGroup void copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUPDATA& w_target); -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING void debugGroup(); #else void debugGroup() {} #endif void ackMessage(int32_t msgno); - void processKeepalive(SocketData*); + void processKeepalive(SocketData*, const CPacket&, const time_point& tsArrival); void internalKeepalive(SocketData*); private: @@ -398,16 +457,17 @@ class CUDTGroup // 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; + CUDTUnited& m_Global; + mutable sync::Mutex m_GroupLock; SRTSOCKET m_GroupID; SRTSOCKET m_PeerGroupID; struct GroupContainer { private: - std::list m_List; - sync::atomic m_SizeCache; + std::list m_List; + sync::atomic m_SizeCache; + sync::atomic m_zNumberRunning; /// This field is used only by some types of groups that need /// to keep track as to which link was lately used. Note that @@ -419,6 +479,7 @@ class CUDTGroup GroupContainer() : m_SizeCache(0) + , m_zNumberRunning(0) , m_LastActiveLink(m_List.end()) { } @@ -428,7 +489,9 @@ class CUDTGroup gli_t begin() { return m_List.begin(); } gli_t end() { return m_List.end(); } - bool empty() { return m_List.empty(); } + cgli_t begin() const { return m_List.begin(); } + cgli_t end() const { return m_List.end(); } + bool empty() const { return m_List.empty(); } void push_back(const SocketData& data) { m_List.push_back(data); ++m_SizeCache; } void clear() { @@ -436,14 +499,53 @@ class CUDTGroup m_List.clear(); m_SizeCache = 0; } - size_t size() { return m_SizeCache; } void erase(gli_t it); + + // NOTE: These methods below don't need locking in RO version. + SRTU_PROPERTY_RO(size_t, size, m_SizeCache); + + // UPDATED BY: updateRcvRunningState() (which needs locking). + SRTU_PROPERTY_RW(size_t, number_running, m_zNumberRunning); }; + + // The maximum seen distance between two sequence numbers, + // one from the currently incoming packet, the other being + // m_RcvLastSeqNo. + sync::atomic m_zLongestDistance; + atomic_duration m_tdLongestDistance; + +#if USE_RECEIVER_UNIT_POOL + // The WATER facility is the cache for decommissioned packet units + // from the receiver buffer. They are tried to be returned to the + // multiplexer from which they have come (hence std::map), but this happens + // only once per some time in order to void too often locking of + // m_GlobControlLock. +public: + void returnUnit(CRcvBuffer::UnitHandle& w_u, int32_t muxid); +private: + + void flushWater(); + typedef std::map< int32_t, std::vector > water_t; + water_t m_Water; + size_t m_WaterTotalSizeCache; + sync::atomic m_bWaterOverflow; +#endif + +public: + SRT_TSA_NEEDS_LOCKED(m_GroupLock) + void updateRcvRunningState(); + + SRT_TSA_NEEDS_LOCKED(m_GroupLock) + void updateErasedLink(); + + void updateInterlinkDistance(); +private: + + SRT_TSA_GUARDED_BY(m_GroupLock) GroupContainer m_Group; SRT_GROUP_TYPE m_type; - CUDTSocket* m_listener; // A "group" can only have one listener. - srt::sync::atomic m_iBusy; + sync::atomic m_iBusy; CallbackHolder m_cbConnectHook; void installConnectHook(srt_connect_callback_fn* hook, void* opaq) { @@ -509,50 +611,6 @@ class CUDTGroup 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; @@ -612,6 +670,36 @@ class CUDTGroup // typedef StaticBuffer senderBuffer_t; private: + + UniquePtr m_pSndBuffer; // XXX for future. + + SRT_TSA_PT_GUARDED_BY(m_RcvBufferLock) + UniquePtr m_pRcvBuffer; + + sync::CThread m_RcvTsbPdThread; // Rcv TsbPD Thread handle + + static void* tsbpd(void* param); + + struct ScopedGroupKeeper + { + CUDTGroup* me; + + ScopedGroupKeeper(CUDTGroup* meme): me(meme) + { + srt::sync::ScopedLock lk(me->m_GroupLock); + me->apiAcquire(); + } + + ~ScopedGroupKeeper() + { + srt::sync::ScopedLock lk(me->m_GroupLock); + me->apiRelease(); + } + }; + friend struct ScopedGroupKeeper; + + sync::atomic m_iRcvPossibleLossSeq; + // Fields required for SRT_GTYPE_BACKUP groups. senderBuffer_t m_SenderBuffer; // This mechanism is to be removed on group-common sndbuf int32_t m_iSndOldestMsgNo; // oldest position in the sender buffer @@ -630,14 +718,16 @@ class CUDTGroup 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 + bool m_bOPT_MessageAPI; // XXX false not supported + int m_iOPT_RcvBufSize; + bool m_bOPT_DriftTracer; + // 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 @@ -645,25 +735,76 @@ class CUDTGroup time_point m_tsStartTime; time_point m_tsRcvPeerStartTime; - void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); + sync::atomic m_RcvLastSeqNo; + sync::AtomicClock m_RcvFurthestPacketTime; + sync::atomic m_SndLastDataAck; - /// 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 required as a value with its own lock. + // There's no point in locking the whole group for this, + // but for a Sequence type data atomic is not enough + // because the update uses a roll-number comparison. + // It is however enough for reading the current value. - // 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; + sync::Mutex m_SndLastSeqLock; - 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; + SRT_TSA_GUARDED_BY(m_SndLastSeqLock) + sync::atomic m_SndLastSeqNo; + +public: + int32_t updateSentSeq(int32_t seqno) + { + sync::ScopedLock lk(m_SndLastSeqLock); + + if (CSeqNo::seqcmp(seqno, m_SndLastSeqNo) > 0) + { + m_SndLastSeqNo = seqno; + } + return m_SndLastSeqNo; + } + + SRT_TSA_NEEDS_LOCKED(m_SndLastSeqLock) + int32_t getSentSeq() const + { + return m_SndLastSeqNo; + } + +private: + + /// True: at least one socket has joined the group in at least pending state + sync::atomic m_bOpened; + + /// True: at least one socket is connected, even if pending from the listener + sync::atomic m_bConnected; + + // Added with every listener at the moment when a new + // connection reports in and the first socket comes in from + // the listener. This listener must stay in this list as long + // as the group is in the "pending mode", that is, waiting for + // being accepted by srt_accept() call. After getting accepted + // this list is clear. The purpose of this list is that after + // doing srt_accept(), every listener in this list must be checked + // if it still has queued sockets that are members of this group. + // NOTE: + // - BEFORE srt_accept() is called, EVERY member socket of this + // group must remain among queued sockets. + // - AFTER srt_accept() is called (the group may be accepted off + // any listener that gets a pending socket connection), all accepted + // sockets are withdrawn from the accept queue of all listeners + // that provided this group's members. All these listeners are + // withdrawn IN readiness, if there are no other sockets waiting, + // and all these listeners (except the one that delivered the group) + // get the UPDATE readiness. + std::vector m_PendingListeners; + + /// True: the group was requested to close and it should not allow any operations. + sync::atomic m_bClosing; + + bool stillConnected() + { + return m_bOpened + && m_bConnected + && !m_bClosing; + } // There's no simple way of transforming config // items that are predicted to be used on socket. @@ -675,10 +816,14 @@ class CUDTGroup // is ready to deliver. sync::Condition m_RcvDataCond; sync::Mutex m_RcvDataLock; + sync::Condition m_RcvTsbPdCond; + sync::atomic m_bTsbpdWaitForNewPacket; // TSBPD forever-wait should be signaled by new packet reception + sync::atomic m_bTsbpdWaitForExtraction;// TSBPD forever-wait should be signaled by extracting the last ready packet + mutable sync::Mutex m_RcvBufferLock; // Protects the state of the m_pRcvBuffer + sync::atomic m_iLastSchedSeqNo; // represetnts the value of CUDT::m_iSndNextSeqNo for each running socket sync::atomic m_iLastSchedMsgNo; // Statistics - struct Stats { // Stats state @@ -692,8 +837,8 @@ class CUDTGroup void init() { - tsActivateTime = srt::sync::steady_clock::time_point(); - tsLastSampleTime = srt::sync::steady_clock::now(); + tsActivateTime = sync::steady_clock::time_point(); + tsLastSampleTime = sync::steady_clock::now(); sent.reset(); recv.reset(); recvDrop.reset(); @@ -702,7 +847,7 @@ class CUDTGroup void reset() { - tsLastSampleTime = srt::sync::steady_clock::now(); + tsLastSampleTime = sync::steady_clock::now(); sent.resetTrace(); recv.resetTrace(); @@ -736,32 +881,72 @@ class CUDTGroup // 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; } + // However, after creation it will be still waiting for being + // extracted by the application in `srt_accept`, and until then + // it stays as pending. + void setOpenPending(SRTSOCKET lsn) + { + sync::ScopedLock lk (m_GroupLock); + m_bOpened = true; + m_PendingListeners.push_back(lsn); + } + + bool isClosing() const + { + return m_bClosing; + } + + void updatePending(SRTSOCKET lsn) + { + sync::ScopedLock lk (m_GroupLock); + + // This is called when the group has been already created at + // the listener side as mirror, and now whether it's still in + // the pending mode or it's already accepted, is recognized + // by that there is anything in the pending list. + // + // When this group is first created, it gets called setOpenPending, + // which always adds at least one listener to the list. This here + // is called either after the first setOpenPending for any next + // pending socket, while the group wasn't yet accepted, or after + // the group was accepted, in which case this list is empty. + + if (!m_PendingListeners.empty()) + { + m_PendingListeners.push_back(lsn); + } + } std::string CONID() const { -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING std::ostringstream os; - os << "@" << m_GroupID << ":"; + os << "$" << int(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; - } + int checkLazySpawnTsbPdThread(); + CRcvBuffer::InsertInfo addDataUnit(int32_t muxid, CUDTSocket* msock, CRcvBuffer::UnitHandle& u, CUDT::loss_seqs_t&, bool&); + + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + bool checkPacketArrivalLoss(SocketData* member, const CPacket& rpkt, CUDT::loss_seqs_t&); + + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + bool checkMultilinkLoss(const CPacket& rpkt, CUDT::loss_seqs_t&); + + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + int rcvDropTooLateUpTo(int32_t seqno); + void synchronizeLoss(int32_t seqno); + void addGroupDriftSample(uint32_t timestamp, const time_point& tsArrival, int rtt); + bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); bool applyGroupTime(time_point& w_start_time, time_point& w_peer_start_time) { - using srt::sync::is_zero; - using srt_logging::gmlog; + using sync::is_zero; + using srt::logging::gmlog; if (is_zero(m_tsStartTime)) { @@ -786,15 +971,20 @@ class CUDTGroup return false; } - // Live state synchronization - bool getBufferTimeBase(srt::CUDT* forthesakeof, time_point& w_tb, bool& w_wp, duration& w_dr); + SRT_TSA_NEEDS_LOCKED(m_GroupLock) 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(CUDTSocket*); + + void getMemberSockets(std::set&) const; + + SRT_ATR_NODISCARD bool updateSendPacketUnique_LOCKED(int32_t single_seq); - void updateLatestRcv(srt::CUDTSocket*); + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) + time_point getPktTsbPdTime(uint32_t usPktTimestamp) const + { + return m_pRcvBuffer->getPktTsbPdTime(usPktTimestamp); + } // Property accessors SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, id, m_GroupID); @@ -804,6 +994,9 @@ class CUDTGroup SRTU_PROPERTY_RRW(std::set&, epollset, m_sPollID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, int64_t, latency_us, m_iTsbPdDelay_us); SRTU_PROPERTY_RO(bool, closing, m_bClosing); + + SRT_TSA_NEEDS_LOCKED(m_RcvBufferLock) // RO provides only one getter method + SRTU_PROPERTY_RO(int32_t, getOldestRcvSeqNo, m_pRcvBuffer->getStartSeqNo()); }; } // namespace srt diff --git a/srtcore/group_backup.cpp b/srtcore/group_backup.cpp index 9adb19607..0b927d966 100644 --- a/srtcore/group_backup.cpp +++ b/srtcore/group_backup.cpp @@ -18,6 +18,7 @@ #include #include "group_backup.h" +#include "packet.h" namespace srt @@ -26,7 +27,7 @@ namespace groups { using namespace std; -using namespace srt_logging; +using namespace srt::logging; const char* stateToStr(BackupMemberState state) { @@ -77,6 +78,22 @@ struct FCompareByWeight } }; +SendBackupCtx::SendBackupCtx() + : m_stateCounter() // default init with zeros + , m_activeMaxWeight() + , m_standbyMaxWeight() +{ + // XXX Setting AF_INET6 is a temporary solution for using rate estimator + // that counts a rate based on the current link's IP version. The results + // for links using IPv4 could be slightly falsified due to that (16 bytes + // more per a packet), but this makes the estimation results the same for + // the same data sent over the group, regardless of the IP version used + // for the currently active link (which in reality results in different + // load for the same stream, if links use different IP version). + m_rateEstimate.setHeaderSize(CPacket::HDR_SIZE + CPacket::udpHeaderSize(AF_INET6)); +} + + void SendBackupCtx::recordMemberState(SocketData* pSockData, BackupMemberState st) { m_memberStates.push_back(BackupMemberStateEntry(pSockData, st)); diff --git a/srtcore/group_backup.h b/srtcore/group_backup.h index 71a1f7abb..a224743a2 100644 --- a/srtcore/group_backup.h +++ b/srtcore/group_backup.h @@ -75,20 +75,7 @@ namespace groups class SendBackupCtx { public: - SendBackupCtx() - : m_stateCounter() // default init with zeros - , m_activeMaxWeight() - , m_standbyMaxWeight() - // XXX Setting AF_INET6 is a temporary solution for using rate estimator - // that counts a rate based on the current link's IP version. The results - // for links using IPv4 could be slightly falsified due to that (16 bytes - // more per a packet), but this makes the estimation results the same for - // the same data sent over the group, regardless of the IP version used - // for the currently active link (which in reality results in different - // load for the same stream, if links use different IP version). - , m_rateEstimate(AF_INET6) - { - } + SendBackupCtx(); /// @brief Adds or updates a record of the member socket state. /// @param pSocketDataIt Iterator to a socket @@ -118,15 +105,12 @@ namespace groups 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; + public: CRateEstimator m_rateEstimate; // The rate estimator state of the active link to copy to a backup on activation. }; diff --git a/srtcore/group_common.cpp b/srtcore/group_common.cpp index 536bdf52c..02610227e 100644 --- a/srtcore/group_common.cpp +++ b/srtcore/group_common.cpp @@ -14,6 +14,7 @@ Written by *****************************************************************************/ #include "platform_sys.h" +#include "srt.h" #include "group_common.h" #include "api.h" @@ -23,7 +24,7 @@ namespace srt namespace groups { -SocketData prepareSocketData(CUDTSocket* s) +SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type SRT_ATR_UNUSED) { // 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 @@ -40,7 +41,7 @@ SocketData prepareSocketData(CUDTSocket* s) // - 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->id(), s, -1, SRTS_INIT, @@ -53,8 +54,13 @@ SocketData prepareSocketData(CUDTSocket* s) false, false, false, + false, // use_send_schedule + 0, // load_factor + 0, // unit_load 0, // weight - 0 // pktSndDropTotal + 0, // pktSndDropTotal + 0, // rcvSeqDistance + 0, // updateCounter }; return sd; } diff --git a/srtcore/group_common.h b/srtcore/group_common.h index d780d0b9a..46f7f6a0d 100644 --- a/srtcore/group_common.h +++ b/srtcore/group_common.h @@ -16,12 +16,13 @@ Written by #ifndef INC_SRT_GROUP_COMMON_H #define INC_SRT_GROUP_COMMON_H +#include +#include + #include "srt.h" #include "common.h" #include "core.h" -#include - namespace srt { namespace groups @@ -44,14 +45,22 @@ namespace groups bool ready_write; bool ready_error; + // Balancing data + bool use_send_schedule; + double load_factor; + double unit_load; + // Configuration uint16_t weight; - // Stats - int64_t pktSndDropTotal; + // Measurement + int64_t pktSndDropTotal; //< copy of socket's max drop stat value + int rcvSeqDistance; //< distance to the latest received sequence in the group + + size_t updateCounter; //< counter used to damper measurement pickup for longest sequence span }; - SocketData prepareSocketData(CUDTSocket* s); + SocketData prepareSocketData(CUDTSocket* s, SRT_GROUP_TYPE type); typedef std::list group_t; typedef group_t::iterator gli_t; diff --git a/srtcore/handshake.cpp b/srtcore/handshake.cpp index c97b4e2a3..4e7578034 100644 --- a/srtcore/handshake.cpp +++ b/srtcore/handshake.cpp @@ -51,17 +51,17 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include "udt.h" #include "api.h" #include "core.h" #include "handshake.h" #include "utilities.h" using namespace std; -using namespace srt; +namespace srt +{ -srt::CHandShake::CHandShake() +CHandShake::CHandShake() : m_iVersion(0) , m_iType(0) // Universal: UDT_UNDEFINED or no flags , m_iISN(0) @@ -70,13 +70,13 @@ srt::CHandShake::CHandShake() , m_iReqType(URQ_WAVEAHAND) , m_iID(0) , m_iCookie(0) - , m_extension(false) + , m_extensionType(0) { for (int i = 0; i < 4; ++ i) m_piPeerIP[i] = 0; } -int srt::CHandShake::store_to(char* buf, size_t& w_size) +int CHandShake::store_to(char* buf, size_t& w_size) { if (w_size < m_iContentSize) return -1; @@ -88,7 +88,7 @@ int srt::CHandShake::store_to(char* buf, size_t& w_size) *p++ = m_iMSS; *p++ = m_iFlightFlagSize; *p++ = int32_t(m_iReqType); - *p++ = m_iID; + *p++ = int32_t(m_iID); *p++ = m_iCookie; for (int i = 0; i < 4; ++ i) *p++ = m_piPeerIP[i]; @@ -98,7 +98,7 @@ int srt::CHandShake::store_to(char* buf, size_t& w_size) return 0; } -int srt::CHandShake::load_from(const char* buf, size_t size) +int CHandShake::load_from(const char* buf, size_t size) { if (size < m_iContentSize) return -1; @@ -111,18 +111,25 @@ int srt::CHandShake::load_from(const char* buf, size_t size) m_iMSS = *p++; m_iFlightFlagSize = *p++; m_iReqType = UDTRequestType(*p++); - m_iID = *p++; + m_iID = SRTSOCKET(*p++); m_iCookie = *p++; for (int i = 0; i < 4; ++ i) m_piPeerIP[i] = *p++; + m_extensionType = 0; + if (size > m_iContentSize + sizeof(int32_t) && m_iReqType == URQ_CONCLUSION) + { + // Extensions provided - check the first word for HSREQ/HSRSP + int cmd = HS_CMDSPEC_CMD::unwrap(*p); + if (cmd == SRT_CMD_HSREQ || cmd == SRT_CMD_HSRSP) + m_extensionType = cmd; + } + return 0; } -#ifdef ENABLE_LOGGING +#if HVU_ENABLE_LOGGING -namespace srt -{ const char* srt_rejectreason_name [] = { "UNKNOWN", "SYSTEM", @@ -143,9 +150,8 @@ const char* srt_rejectreason_name [] = { "TIMEOUT", "CRYPTO" }; -} -std::string srt::RequestTypeStr(UDTRequestType rq) +std::string RequestTypeStr(UDTRequestType rq) { if (rq >= URQ_FAILURE_TYPES) { @@ -178,7 +184,7 @@ std::string srt::RequestTypeStr(UDTRequestType rq) } } -string srt::CHandShake::RdvStateStr(CHandShake::RendezvousState s) +string CHandShake::RdvStateStr(CHandShake::RendezvousState s) { switch (s) { @@ -194,7 +200,7 @@ string srt::CHandShake::RdvStateStr(CHandShake::RendezvousState s) } #endif -bool srt::CHandShake::valid() +bool CHandShake::valid() { if (m_iVersion < CUDT::HS_VERSION_UDT4 || m_iISN < 0 || m_iISN >= CSeqNo::m_iMaxSeqNo @@ -205,56 +211,57 @@ bool srt::CHandShake::valid() return true; } -string srt::CHandShake::show() +string CHandShake::show() { - ostringstream so; - - 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 - << " srcIP="; - - const unsigned char* p = (const unsigned char*)m_piPeerIP; - const unsigned char* pe = p + 4 * (sizeof(uint32_t)); + using namespace hvu; + ofmtbufstream so; - copy(p, pe, ostream_iterator(so, ".")); + so << "version=" << m_iVersion + << " type=0x" << fmt(m_iType, hex) + << " ISN=" << m_iISN << " MSS=" << m_iMSS << " FLW=" << m_iFlightFlagSize + << " reqtype=" << RequestTypeStr(m_iReqType) << " srcID=" << m_iID + << " cookie=" << fmt(m_iCookie, hex) + << " srcIP=" << CIPAddress::show(m_piPeerIP); // XXX HS version symbols should be probably declared inside // CHandShake, not CUDT. if ( m_iVersion > CUDT::HS_VERSION_UDT4 ) { - 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); + so << " FLAGS: "; + so << ExtensionFlagStr(m_iType); } return so.str(); } -string srt::CHandShake::ExtensionFlagStr(int32_t fl) +string CHandShake::ExtensionFlagStr(int32_t fl) { std::ostringstream out; - if ( fl & HS_EXT_HSREQ ) - out << " hsx"; - if ( fl & HS_EXT_KMREQ ) - out << " kmx"; - if ( fl & HS_EXT_CONFIG ) - out << " config"; + const int flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(fl); const int kl = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(fl) << 6; + + if (flags == SrtHSRequest::SRT_MAGIC_CODE) + out << "MAGIC "; + else if (fl == 0) + out << "NONE"; // no flags and no advertised pbkeylen + else + { + if ( fl & HS_EXT_HSREQ ) + out << "hsx "; + if ( fl & HS_EXT_KMREQ ) + out << "kmx "; + if ( fl & HS_EXT_CONFIG ) + out << "cfg "; + } + if (kl != 0) { - out << " AES-" << kl; + out << "[AES-" << kl << "]"; } else { - out << " no-pbklen"; + out << "[nokeyad]"; } return out.str(); @@ -264,7 +271,7 @@ string srt::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 srt::SrtHSRequest::serialize(char* buf, size_t size) const +bool SrtHSRequest::serialize(char* buf, size_t size) const { if (size < SRT_HS_SIZE) return false; @@ -279,7 +286,7 @@ bool srt::SrtHSRequest::serialize(char* buf, size_t size) const } -bool srt::SrtHSRequest::deserialize(const char* buf, size_t size) +bool SrtHSRequest::deserialize(const char* buf, size_t size) { m_iSrtVersion = 0; // just to let users recognize if it succeeded or not. @@ -295,7 +302,7 @@ bool srt::SrtHSRequest::deserialize(const char* buf, size_t size) return true; } -std::string srt::SrtFlagString(int32_t flags) +string SrtFlagString(int32_t flags) { #define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) @@ -326,3 +333,5 @@ std::string srt::SrtFlagString(int32_t flags) return output; } + +} diff --git a/srtcore/handshake.h b/srtcore/handshake.h index c6a5731e4..a9bbeb90e 100644 --- a/srtcore/handshake.h +++ b/srtcore/handshake.h @@ -99,7 +99,8 @@ const int SRT_CMD_REJECT = 0, // REJECT is only a symbol for return type 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} + SRT_CMD_NONE = -1, // for cases when {no pong for ping is required} | {no extension block found} + SRT_CMD_E_SIZE = 9; // for range check enum SrtDataStruct { @@ -277,7 +278,7 @@ const UDTRequestType URQ_ERROR_REJECT SRT_ATR_DEPRECATED = (UDTRequestType)1002; const UDTRequestType URQ_ERROR_INVALID SRT_ATR_DEPRECATED = (UDTRequestType)1004; // == 1000 + SRT_REJ_ROGUE // XXX Change all uses of that field to UDTRequestType when possible -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING std::string RequestTypeStr(UDTRequestType); #else inline std::string RequestTypeStr(UDTRequestType) { return ""; } @@ -314,17 +315,25 @@ class CHandShake bool v5orHigher() { return m_iVersion > 4; } public: + // Serializable fields (direct) 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 + SRTSOCKET 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; + // State fields (updated after parsing) + + // Possible values: + // - SRT_CMD_REJECT (default for most cases) + // Otherwise, if m_iReqType == URQ_CONCLUSION and m_iType contains nonzero flags: + // - SRT_CMD_HSREQ - the handshake contains HSREQ extension + // - SRT_CMD_HSRSP - the handshake contains HSRSP extension + int m_extensionType; bool valid(); std::string show(); @@ -355,7 +364,7 @@ class CHandShake RDV_CONNECTED //< Final connected state. [ATTENTION]:URQ_CONCLUSION --> [CONNECTED] <-- [FINE]:URQ_AGREEMENT. }; -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING static std::string RdvStateStr(RendezvousState s); #else static std::string RdvStateStr(RendezvousState) { return ""; } diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 94eaa5da0..c90badf44 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -55,426 +55,17 @@ modified by #include "list.h" #include "packet.h" #include "logging.h" +#include "logger_fas.h" -// Use "inline namespace" in C++11 -namespace srt_logging -{ -extern Logger qrlog; -extern Logger qslog; -extern Logger tslog; -} - -using srt_logging::qrlog; -using srt_logging::qslog; -using srt_logging::tslog; - +using namespace srt::logging; 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].seqstart = SRT_SEQNO_NONE; - m_caSeq[i].seqend = SRT_SEQNO_NONE; - } - - // sender list needs mutex protection - setupMutex(m_ListLock, "LossList"); -} - -srt::CSndLossList::~CSndLossList() -{ - delete[] m_caSeq; - releaseMutex(m_ListLock); -} - -void srt::CSndLossList::traceState() const -{ - traceState(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) - { - // No overlap - // TODO: Here we should actually insert right after i, not at loc. - insertAfter(loc, i, seqno1, seqno2); - } - else - { - // TODO: Replace with updateElement(i, seqno1, seqno2). - // Some changes to updateElement(..) are required. - m_iLastInsertPos = i; - if (CSeqNo::seqcmp(seqend, seqno2) >= 0) - return 0; - - // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7] - m_iLength += CSeqNo::seqlen(seqend, seqno2) - 1; - m_caSeq[i].seqend = seqno2; - - loc = i; - } - } - } - else // offset == 0, loc == m_iHead - { - const bool updated = updateElement(m_iHead, seqno1, seqno2); - if (!updated) - return 0; - } - - coalesce(loc); - return m_iLength - origlen; -} - -void srt::CSndLossList::removeUpTo(int32_t seqno) -{ - 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; - - m_iHead = loc; - - m_iLength--; - } - else if (offset > 0) - { - int h = m_iHead; - - if (seqno == m_caSeq[loc].seqstart) - { - // target node is not empty, remove part/all of the seqno in the node. - int temp = loc; - loc = (loc + 1) % m_iSize; - - if (SRT_SEQNO_NONE == m_caSeq[temp].seqend) - m_iHead = m_caSeq[temp].inext; - else - { - // remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3) - m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[temp].seqend, m_caSeq[loc].seqstart) > 0) - m_caSeq[loc].seqend = m_caSeq[temp].seqend; - m_iHead = loc; - m_caSeq[loc].inext = m_caSeq[temp].inext; - m_caSeq[temp].inext = loc; - m_caSeq[temp].seqend = SRT_SEQNO_NONE; - } - } - else - { - // target node is empty, check prior node - int i = m_iHead; - while ((-1 != m_caSeq[i].inext) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].inext].seqstart, seqno) < 0)) - i = m_caSeq[i].inext; - - loc = (loc + 1) % m_iSize; - - if (SRT_SEQNO_NONE == m_caSeq[i].seqend) - m_iHead = m_caSeq[i].inext; - else if (CSeqNo::seqcmp(m_caSeq[i].seqend, seqno) > 0) - { - // remove part/all seqno in the prior node - m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[i].seqend, m_caSeq[loc].seqstart) > 0) - m_caSeq[loc].seqend = m_caSeq[i].seqend; - - m_caSeq[i].seqend = seqno; - - m_caSeq[loc].inext = m_caSeq[i].inext; - m_caSeq[i].inext = loc; - - m_iHead = loc; - } - else - m_iHead = m_caSeq[i].inext; - } - - // Remove all nodes prior to the new head - while (h != m_iHead) - { - if (m_caSeq[h].seqend != SRT_SEQNO_NONE) - { - m_iLength -= CSeqNo::seqlen(m_caSeq[h].seqstart, m_caSeq[h].seqend); - m_caSeq[h].seqend = SRT_SEQNO_NONE; - } - else - m_iLength--; - - m_caSeq[h].seqstart = SRT_SEQNO_NONE; - - if (m_iLastInsertPos == h) - m_iLastInsertPos = -1; - - h = m_caSeq[h].inext; - } - } -} - -int srt::CSndLossList::getLossLength() const -{ - ScopedLock listguard(m_ListLock); - - return m_iLength; -} - -int32_t srt::CSndLossList::popLostSeq() +namespace srt { - ScopedLock listguard(m_ListLock); - - if (0 == m_iLength) - { - SRT_ASSERT(m_iHead == -1); - return SRT_SEQNO_NONE; - } - - if (m_iLastInsertPos == m_iHead) - m_iLastInsertPos = -1; - - // return the first loss seq. no. - const int32_t seqno = m_caSeq[m_iHead].seqstart; - - // head moves to the next node - if (SRT_SEQNO_NONE == m_caSeq[m_iHead].seqend) - { - //[3, SRT_SEQNO_NONE] becomes [], and head moves to next node in the list - m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; - m_iHead = m_caSeq[m_iHead].inext; - } - else - { - // shift to next node, e.g., [3, 7] becomes [], [4, 7] - int loc = (m_iHead + 1) % m_iSize; - - m_caSeq[loc].seqstart = CSeqNo::incseq(seqno); - if (CSeqNo::seqcmp(m_caSeq[m_iHead].seqend, m_caSeq[loc].seqstart) > 0) - m_caSeq[loc].seqend = m_caSeq[m_iHead].seqend; - - m_caSeq[m_iHead].seqstart = SRT_SEQNO_NONE; - m_caSeq[m_iHead].seqend = SRT_SEQNO_NONE; - - m_caSeq[loc].inext = m_caSeq[m_iHead].inext; - m_iHead = loc; - } - - 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; - - // 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); - - 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--; - } - - m_caSeq[i].seqstart = SRT_SEQNO_NONE; - m_caSeq[i].seqend = SRT_SEQNO_NONE; - m_caSeq[loc].inext = m_caSeq[i].inext; - } -} - -bool srt::CSndLossList::updateElement(int pos, int32_t seqno1, int32_t seqno2) -{ - m_iLastInsertPos = pos; - - if (seqno2 == SRT_SEQNO_NONE || seqno2 == seqno1) - return false; - - if (m_caSeq[pos].seqend == SRT_SEQNO_NONE) - { - m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; - m_caSeq[pos].seqend = seqno2; - return true; - } - - // seqno2 <= m_caSeq[pos].seqend - if (CSeqNo::seqcmp(seqno2, m_caSeq[pos].seqend) <= 0) - return false; - - // seqno2 > m_caSeq[pos].seqend - m_iLength += CSeqNo::seqlen(m_caSeq[pos].seqend, seqno2) - 1; - m_caSeq[pos].seqend = seqno2; - return true; -} //////////////////////////////////////////////////////////////////////////////// -srt::CRcvLossList::CRcvLossList(int size) +CRcvLossList::CRcvLossList(int size) : m_caSeq() , m_iHead(-1) , m_iTail(-1) @@ -492,12 +83,12 @@ srt::CRcvLossList::CRcvLossList(int size) } } -srt::CRcvLossList::~CRcvLossList() +CRcvLossList::~CRcvLossList() { delete[] m_caSeq; } -int srt::CRcvLossList::insert(int32_t seqno1, int32_t seqno2) +int CRcvLossList::insert(int32_t seqno1, int32_t seqno2) { SRT_ASSERT(seqno1 != SRT_SEQNO_NONE && seqno2 != SRT_SEQNO_NONE); // Make sure that seqno2 isn't earlier than seqno1. @@ -577,7 +168,7 @@ int srt::CRcvLossList::insert(int32_t seqno1, int32_t seqno2) return n; } -bool srt::CRcvLossList::remove(int32_t seqno) +bool CRcvLossList::remove(int32_t seqno) { if (m_iLargestSeq == SRT_SEQNO_NONE || CSeqNo::seqcmp(seqno, m_iLargestSeq) > 0) m_iLargestSeq = seqno; @@ -716,7 +307,7 @@ bool srt::CRcvLossList::remove(int32_t seqno) return true; } -bool srt::CRcvLossList::remove(int32_t seqno1, int32_t seqno2) +bool CRcvLossList::remove(int32_t seqno1, int32_t seqno2) { if (CSeqNo::seqcmp(seqno1, seqno2) > 0) { @@ -729,7 +320,7 @@ bool srt::CRcvLossList::remove(int32_t seqno1, int32_t seqno2) return true; } -int32_t srt::CRcvLossList::removeUpTo(int32_t seqno_last) +int32_t CRcvLossList::removeUpTo(int32_t seqno_last) { int32_t first = getFirstLostSeq(); if (first == SRT_SEQNO_NONE) @@ -757,7 +348,7 @@ int32_t srt::CRcvLossList::removeUpTo(int32_t seqno_last) return first; } -bool srt::CRcvLossList::find(int32_t seqno1, int32_t seqno2) const +bool CRcvLossList::find(int32_t seqno1, int32_t seqno2) const { if (0 == m_iLength) return false; @@ -778,12 +369,12 @@ bool srt::CRcvLossList::find(int32_t seqno1, int32_t seqno2) const return false; } -int srt::CRcvLossList::getLossLength() const +int CRcvLossList::getLossLength() const { return m_iLength; } -int32_t srt::CRcvLossList::getFirstLostSeq() const +int32_t CRcvLossList::getFirstLostSeq() const { if (0 == m_iLength) return SRT_SEQNO_NONE; @@ -791,7 +382,7 @@ int32_t srt::CRcvLossList::getFirstLostSeq() const return m_caSeq[m_iHead].seqstart; } -void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit) +void CRcvLossList::getLossArray(int32_t* array, int& len, int limit) { len = 0; @@ -814,7 +405,7 @@ void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit) } } -srt::CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age) +CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age) : ttl(initial_age) , timestamp(steady_clock::now()) { @@ -822,7 +413,7 @@ srt::CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age) seq[1] = seqhi; } -srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t sequence) +CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t sequence) { int32_t diffbegin = CSeqNo::seqcmp(sequence, seq[0]); int32_t diffend = CSeqNo::seqcmp(sequence, seq[1]); @@ -853,7 +444,7 @@ srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t sequence) return SPLIT; } -srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t lo, int32_t hi) +CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t lo, int32_t hi) { // This should only if the range lo-hi is anyhow covered by seq[0]-seq[1]. @@ -897,7 +488,7 @@ srt::CRcvFreshLoss::Emod srt::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) +bool CRcvFreshLoss::removeOne(std::deque& w_container, int32_t sequence, int* pw_had_ttl) { for (size_t i = 0; i < w_container.size(); ++i) { @@ -946,3 +537,4 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32 } +} diff --git a/srtcore/list.h b/srtcore/list.h index 802ede9a2..f4afbaae4 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -55,103 +55,10 @@ modified by #include -#include "udt.h" #include "common.h" namespace srt { -class CSndLossList -{ -public: - 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. - 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); - - /// Read the loss length.∏ - /// @return The length of the list. - int getLossLength() const; - - /// 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(); - - template - Stream& traceState(Stream& sout) const - { - int pos = m_iHead; - while (pos != SRT_SEQNO_NONE) - { - sout << "[" << pos << "]:" << m_caSeq[pos].seqstart; - if (m_caSeq[pos].seqend != SRT_SEQNO_NONE) - sout << ":" << m_caSeq[pos].seqend; - if (m_caSeq[pos].inext == -1) - sout << "=|"; - else - sout << "->[" << m_caSeq[pos].inext << "]"; - sout << ", "; - pos = m_caSeq[pos].inext; - } - sout << " {len:" << m_iLength << " head:" << m_iHead << " last:" << m_iLastInsertPos << "}"; - return sout; - } - void traceState() const; - - // Debug/unittest support. - - int head() const { return m_iHead; } - int next(int loc) const { return m_caSeq[loc].inext; } - int last() const { return m_iLastInsertPos; } - -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: - /// 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); - - /// 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); - - static const int LOC_NONE = -1; - -private: - CSndLossList(const CSndLossList&); - CSndLossList& operator=(const CSndLossList&); -}; - //////////////////////////////////////////////////////////////////////////////// class CRcvLossList @@ -280,9 +187,9 @@ struct CRcvFreshLoss { int32_t seq[2]; int ttl; - srt::sync::steady_clock::time_point timestamp; + sync::steady_clock::time_point timestamp; - CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl); + CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl = 1); // Don't WTF when looking at this. The Windows system headers define // a publicly visible preprocessor macro with that name. REALLY! diff --git a/srtcore/logger_default.cpp b/srtcore/logger_default.cpp deleted file mode 100644 index fa7e73b31..000000000 --- a/srtcore/logger_default.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - 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/srtcore/logger_defs.cpp b/srtcore/logger_defs.cpp deleted file mode 100644 index 041f6c8a7..000000000 --- a/srtcore/logger_defs.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - 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/srtcore/logger_defs.h b/srtcore/logger_defs.h deleted file mode 100644 index 63a4caf3e..000000000 --- a/srtcore/logger_defs.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - 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/srtcore/logger_fas.cpp b/srtcore/logger_fas.cpp new file mode 100644 index 000000000..6acd66859 --- /dev/null +++ b/srtcore/logger_fas.cpp @@ -0,0 +1,120 @@ +/* + WARNING: Generated from ../logging/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + + +#include "logging.h" +#include "logger_fas.h" + +namespace srt { namespace logging { + hvu::logging::LogConfigSingleton logger_config_si; + hvu::logging::LogConfig& logger_config() { return logger_config_si.instance();} + hvu::logging::Logger& gglog = logger_config().general; + + // Socket create/open/close/configure activities + hvu::logging::Logger smlog("sockmgmt", logger_config(), true, "SRT.sm"); + + + // Connection establishment and handshake + hvu::logging::Logger cnlog("conn", logger_config(), true, "SRT.cn"); + + + // The checkTimer and around activities + hvu::logging::Logger xtlog("xtimer", logger_config(), true, "SRT.xt"); + + + // The TsBPD thread + hvu::logging::Logger tslog("tsbpd", logger_config(), true, "SRT.ts"); + + + // System resource allocation and management + hvu::logging::Logger rslog("rsrc", logger_config(), true, "SRT.rs"); + + + // Congestion control module + hvu::logging::Logger cclog("congest", logger_config(), true, "SRT.cc"); + + + // Packet filter module + hvu::logging::Logger pflog("pfilter", logger_config(), true, "SRT.pf"); + + + // API part for socket and library management + hvu::logging::Logger aclog("api_ctrl", logger_config(), true, "SRT.ac"); + + + // Queue control activities + hvu::logging::Logger qclog("que_ctrl", logger_config(), true, "SRT.qc"); + + + // EPoll, internal update activities + hvu::logging::Logger eilog("epoll_upd", logger_config(), true, "SRT.ei"); + + + // API part for receiving + hvu::logging::Logger arlog("api_recv", logger_config(), true, "SRT.ar"); + + + // Buffer, receiving side + hvu::logging::Logger brlog("buf_recv", logger_config(), true, "SRT.br"); + + + // Queue, receiving side + hvu::logging::Logger qrlog("que_recv", logger_config(), true, "SRT.qr"); + + + // CChannel, receiving side + hvu::logging::Logger krlog("chn_recv", logger_config(), true, "SRT.kr"); + + + // Group, receiving side + hvu::logging::Logger grlog("grp_recv", logger_config(), true, "SRT.gr"); + + + // API part for sending + hvu::logging::Logger aslog("api_send", logger_config(), true, "SRT.as"); + + + // Buffer, sending side + hvu::logging::Logger bslog("buf_send", logger_config(), true, "SRT.bs"); + + + // Queue, sending side + hvu::logging::Logger qslog("que_send", logger_config(), true, "SRT.qs"); + + + // CChannel, sending side + hvu::logging::Logger kslog("chn_send", logger_config(), true, "SRT.ks"); + + + // Group, sending side + hvu::logging::Logger gslog("grp_send", logger_config(), true, "SRT.gs"); + + + // Internal activities not connected directly to a socket + hvu::logging::Logger inlog("internal", logger_config(), true, "SRT.in"); + + + // Queue, management part + hvu::logging::Logger qmlog("que_mgmt", logger_config(), true, "SRT.qm"); + + + // CChannel, management part + hvu::logging::Logger kmlog("chn_mgmt", logger_config(), true, "SRT.km"); + + + // Group, management part + hvu::logging::Logger gmlog("grp_mgmt", logger_config(), true, "SRT.gm"); + + + // EPoll, API part + hvu::logging::Logger ealog("epoll_api", logger_config(), true, "SRT.ea"); + + + + +} } diff --git a/srtcore/logger_fas.h b/srtcore/logger_fas.h new file mode 100644 index 000000000..a293843a5 --- /dev/null +++ b/srtcore/logger_fas.h @@ -0,0 +1,98 @@ +/* + WARNING: Generated from ../logging/generate-logging-defs.tcl + + DO NOT MODIFY. + + Copyright applies as per the generator script. + */ + + +#ifndef SRT_LOGGING_LOGGER_FAS_H +#define SRT_LOGGING_LOGGER_FAS_H + +#include "logging.h" + +namespace srt { namespace logging { + extern hvu::logging::LogConfig& logger_config(); + extern hvu::logging::Logger& gglog; + + extern hvu::logging::Logger smlog; + + + extern hvu::logging::Logger cnlog; + + + extern hvu::logging::Logger xtlog; + + + extern hvu::logging::Logger tslog; + + + extern hvu::logging::Logger rslog; + + + extern hvu::logging::Logger cclog; + + + extern hvu::logging::Logger pflog; + + + extern hvu::logging::Logger aclog; + + + extern hvu::logging::Logger qclog; + + + extern hvu::logging::Logger eilog; + + + extern hvu::logging::Logger arlog; + + + extern hvu::logging::Logger brlog; + + + extern hvu::logging::Logger qrlog; + + + extern hvu::logging::Logger krlog; + + + extern hvu::logging::Logger grlog; + + + extern hvu::logging::Logger aslog; + + + extern hvu::logging::Logger bslog; + + + extern hvu::logging::Logger qslog; + + + extern hvu::logging::Logger kslog; + + + extern hvu::logging::Logger gslog; + + + extern hvu::logging::Logger inlog; + + + extern hvu::logging::Logger qmlog; + + + extern hvu::logging::Logger kmlog; + + + extern hvu::logging::Logger gmlog; + + + extern hvu::logging::Logger ealog; + + + + +} } + +#endif diff --git a/srtcore/logging.cpp b/srtcore/logging.cpp deleted file mode 100644 index d0ba3fd4a..000000000 --- a/srtcore/logging.cpp +++ /dev/null @@ -1,149 +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. - *****************************************************************************/ - - -#include "srt_compat.h" -#include "logging.h" - -using namespace std; - - -namespace srt_logging -{ - - -#if ENABLE_LOGGING - -LogDispatcher::Proxy::Proxy(LogDispatcher& guy) : that(guy), that_enabled(that.CheckEnabled()) -{ - if (that_enabled) - { - i_file = ""; - i_line = 0; - flags = that.src_config->flags; - // Create logger prefix - that.CreateLogLinePrefix(os); - } -} - -LogDispatcher::Proxy LogDispatcher::operator()() -{ - return Proxy(*this); -} - -void LogDispatcher::CreateLogLinePrefix(std::ostringstream& serr) -{ - using namespace std; - using namespace srt; - - 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, NULL); - struct tm tm = SysLocalTime((time_t) tv.tv_sec); - - if (strftime(tmp_buf, sizeof(tmp_buf), "%X.", &tm)) - { - serr << tmp_buf << setw(6) << setfill('0') << tv.tv_usec; - } - } - - string out_prefix; - if ( !isset(SRT_LOGF_DISABLE_SEVERITY) ) - { - out_prefix = prefix; - } - - // Note: ThreadName::get needs a buffer of size min. ThreadName::BUFSIZE - if ( !isset(SRT_LOGF_DISABLE_THREADNAME) && ThreadName::get(tmp_buf) ) - { - serr << "/" << tmp_buf << out_prefix << ": "; - } - else - { - serr << out_prefix << ": "; - } -} - -std::string LogDispatcher::Proxy::ExtractName(std::string pretty_function) -{ - if ( pretty_function == "" ) - return ""; - size_t pos = pretty_function.find('('); - if ( pos == std::string::npos ) - return pretty_function; // return unchanged. - - pretty_function = pretty_function.substr(0, pos); - - // There are also template instantiations where the instantiating - // parameters are encrypted inside. Therefore, search for the first - // open < and if found, search for symmetric >. - - int depth = 1; - pos = pretty_function.find('<'); - if ( pos != std::string::npos ) - { - size_t end = pos+1; - for(;;) - { - ++pos; - if ( pos == pretty_function.size() ) - { - --pos; - break; - } - if ( pretty_function[pos] == '<' ) - { - ++depth; - continue; - } - - if ( pretty_function[pos] == '>' ) - { - --depth; - if ( depth <= 0 ) - break; - continue; - } - } - - std::string afterpart = pretty_function.substr(pos+1); - pretty_function = pretty_function.substr(0, end) + ">" + afterpart; - } - - // Now see how many :: can be found in the name. - // If this occurs more than once, take the last two. - pos = pretty_function.rfind("::"); - - if ( pos == std::string::npos || pos < 2 ) - return pretty_function; // return whatever this is. No scope name. - - // Find the next occurrence of :: - if found, copy up to it. If not, - // return whatever is found. - pos -= 2; - pos = pretty_function.rfind("::", pos); - if ( pos == std::string::npos ) - return pretty_function; // nothing to cut - - return pretty_function.substr(pos+2); -} -#endif - -} // (end namespace srt_logging) - diff --git a/srtcore/logging.h b/srtcore/logging.h deleted file mode 100644 index 7782245a2..000000000 --- a/srtcore/logging.h +++ /dev/null @@ -1,506 +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 INC_SRT_LOGGING_H -#define INC_SRT_LOGGING_H - - -#include -#include -#include -#include -#include -#ifdef _WIN32 -#include "win/wintime.h" -#include -#else -#include -#endif - -#include "srt.h" -#include "utilities.h" -#include "threadname.h" -#include "logging_api.h" -#include "sync.h" - -#ifdef __GNUC__ -#define PRINTF_LIKE __attribute__((format(printf,2,3))) -#else -#define PRINTF_LIKE -#endif - -#if ENABLE_LOGGING - -// GENERAL NOTE: All logger functions ADD THEIR OWN \n (EOL). Don't add any your own EOL character. -// The logging system may not add the EOL character, if appropriate flag was set in log settings. -// Anyway, treat the whole contents of eventually formatted message as exactly one line. - -// LOGC uses an iostream-like syntax, using the special 'log' symbol. -// This symbol isn't visible outside the log macro parameters. -// 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__); \ - { (void)(const srt_logging::LogDispatcher::Proxy&)(args); } \ -} - -// LOGF uses printf-like style formatting. -// 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(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 -#define HLOGP LOGP -#define HLOGF LOGF - -#define IF_HEAVY_LOGGING(instr,...) instr,##__VA_ARGS__ - -#else - -#define HLOGC(...) -#define HLOGF(...) -#define HLOGP(...) - -#define IF_HEAVY_LOGGING(instr) (void)0 - -#endif - -#else - -#define LOGC(...) -#define LOGF(...) -#define LOGP(...) - -#define HLOGC(...) -#define HLOGF(...) -#define HLOGP(...) - -#define IF_HEAVY_LOGGING(instr) (void)0 -#define IF_LOGGING(instr) (void)0 - -#endif - -namespace srt_logging -{ - -struct LogConfig -{ - typedef std::bitset fa_bitset_t; - fa_bitset_t enabled_fa; // NOTE: assumed atomic reading - LogLevel::type max_level; // NOTE: assumed atomic reading - std::ostream* log_stream; - SRT_LOG_HANDLER_FN* loghandler_fn; - void* loghandler_opaque; - mutable srt::sync::Mutex mutex; - int flags; - - 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() - { - } - - ~LogConfig() - { - } - - SRT_ATTR_ACQUIRE(mutex) - void lock() const { mutex.lock(); } - - SRT_ATTR_RELEASE(mutex) - void unlock() const { mutex.unlock(); } -}; - -// The LogDispatcher class represents the object that is responsible for -// a decision whether to log something or not, and if so, print the log. -struct SRT_API LogDispatcher -{ -private: - int fa; - LogLevel::type level; - static const size_t MAX_PREFIX_SIZE = 32; - char prefix[MAX_PREFIX_SIZE+1]; - size_t prefix_len; - LogConfig* src_config; - - bool isset(int flg) { return (src_config->flags & flg) != 0; } - -public: - - LogDispatcher(int functional_area, LogLevel::type log_level, const char* your_pfx, - const char* logger_pfx /*[[nullable]]*/, LogConfig& config): - fa(functional_area), - level(log_level), - src_config(&config) - { - const size_t your_pfx_len = your_pfx ? strlen(your_pfx) : 0; - const size_t logger_pfx_len = logger_pfx ? strlen(logger_pfx) : 0; - - if (logger_pfx && your_pfx_len + logger_pfx_len + 1 < MAX_PREFIX_SIZE) - { - memcpy(prefix, your_pfx, your_pfx_len); - prefix[your_pfx_len] = ':'; - memcpy(prefix + your_pfx_len + 1, logger_pfx, logger_pfx_len); - prefix[your_pfx_len + logger_pfx_len + 1] = '\0'; - prefix_len = your_pfx_len + logger_pfx_len + 1; - } - else if (your_pfx) - { - // Prefix too long, so copy only your_pfx and only - // as much as it fits - size_t copylen = std::min(+MAX_PREFIX_SIZE, your_pfx_len); - memcpy(prefix, your_pfx, copylen); - prefix[copylen] = '\0'; - prefix_len = copylen; - } - else - { - prefix[0] = '\0'; - prefix_len = 0; - } - } - - ~LogDispatcher() - { - } - - bool CheckEnabled(); - - void CreateLogLinePrefix(std::ostringstream&); - void SendLogLine(const char* file, int line, const std::string& area, const std::string& sl); - - // log.Debug("This is the ", nth, " time"); <--- C++11 only. - // log.Debug() << "This is the " << nth << " time"; <--- C++03 available. - -#if HAVE_CXX11 - - template - void PrintLogLine(const char* file, int line, const std::string& area, Args&&... args); - - template - void operator()(Args&&... args) - { - PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", args...); - } - - template - void printloc(const char* file, int line, const std::string& area, Args&&... args) - { - PrintLogLine(file, line, area, args...); - } -#else - template - void PrintLogLine(const char* file, int line, const std::string& area, const Arg& arg); - - // For C++03 (older) standard provide only with one argument. - template - void operator()(const Arg& arg) - { - PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg); - } - - void printloc(const char* file, int line, const std::string& area, const std::string& arg1) - { - PrintLogLine(file, line, area, arg1); - } -#endif - -#if ENABLE_LOGGING - - struct Proxy; - friend struct Proxy; - - Proxy operator()(); -#else - - // Dummy proxy that does nothing - struct DummyProxy - { - DummyProxy(LogDispatcher&) - { - } - - template - DummyProxy& operator<<(const T& ) // predicted for temporary objects - { - return *this; - } - - // 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; - } - - DummyProxy& setloc(const char* , int , std::string) - { - return *this; - } - }; - - DummyProxy operator()() - { - return DummyProxy(*this); - } - -#endif - -}; - -#if ENABLE_LOGGING - -struct LogDispatcher::Proxy -{ - LogDispatcher& that; - - std::ostringstream os; - - // Cache the 'enabled' state in the beginning. If the logging - // becomes enabled or disabled in the middle of the log, we don't - // want it to be partially printed anyway. - bool that_enabled; - int flags; - - // CACHE!!! - const char* i_file; - int i_line; - std::string area; - - Proxy& setloc(const char* f, int l, std::string a) - { - i_file = f; - i_line = l; - area = a; - return *this; - } - - // Left for future. Not sure if it's more convenient - // to use this to translate __PRETTY_FUNCTION__ to - // something short, or just let's leave __FUNCTION__ - // or better __func__. - std::string ExtractName(std::string pretty_function); - - Proxy(LogDispatcher& guy); - - // Copy constructor is needed due to noncopyable ostringstream. - // This is used only in creation of the default object, so just - // use the default values, just copy the location cache. - Proxy(const Proxy& p): that(p.that), area(p.area) - { - i_file = p.i_file; - i_line = p.i_line; - that_enabled = false; - flags = p.flags; - } - - - template - Proxy& operator<<(const T& arg) // predicted for temporary objects - { - if ( that_enabled ) - { - os << arg; - } - return *this; - } - - ~Proxy() - { - if (that_enabled) - { - if ((flags & SRT_LOGF_DISABLE_EOL) == 0) - os << std::endl; - that.SendLogLine(i_file, i_line, area, os.str()); - } - // Needed in destructor? - //os.clear(); - //os.str(""); - } - - Proxy& form(const char* fmts, ...) PRINTF_LIKE - { - if ( !that_enabled ) - return *this; - - if ( !fmts || fmts[0] == '\0' ) - return *this; - - va_list ap; - va_start(ap, fmts); - vform(fmts, ap); - va_end(ap); - return *this; - } - - Proxy& vform(const char* fmts, va_list ap) - { - char buf[512]; - -#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' ) - { - // Remove EOL character, should it happen to be at the end. - // The EOL will be added at the end anyway. - buf[len-1] = '\0'; - } - - os.write(buf, len); - return *this; - } -}; - - -#endif - -class Logger -{ - int m_fa; - LogConfig& m_config; - -public: - - LogDispatcher Debug; - LogDispatcher Note; - LogDispatcher Warn; - LogDispatcher Error; - LogDispatcher Fatal; - - Logger(int functional_area, LogConfig& config, const char* logger_pfx = NULL): - m_fa(functional_area), - m_config(config), - Debug ( m_fa, LogLevel::debug, " D", logger_pfx, m_config ), - Note ( m_fa, LogLevel::note, ".N", logger_pfx, m_config ), - Warn ( m_fa, LogLevel::warning, "!W", logger_pfx, m_config ), - Error ( m_fa, LogLevel::error, "*E", logger_pfx, m_config ), - Fatal ( m_fa, LogLevel::fatal, "!!FATAL!!", logger_pfx, m_config ) - { - } -}; - -inline bool LogDispatcher::CheckEnabled() -{ - // Don't use enabler caching. Check enabled state every time. - - // These assume to be atomically read, so the lock is not needed - // (note that writing to this field is still mutex-protected). - // It's also no problem if the level was changed at the moment - // when the enabler check is tested here. Worst case, the log - // will be printed just a moment after it was turned off. - const LogConfig* config = src_config; // to enforce using const operator[] - config->lock(); - int configured_enabled_fa = config->enabled_fa[fa]; - int configured_maxlevel = config->max_level; - config->unlock(); - - return configured_enabled_fa && level <= configured_maxlevel; -} - - -#if HAVE_CXX11 - -//extern std::mutex Debug_mutex; - -inline void PrintArgs(std::ostream&) {} - -template -inline void PrintArgs(std::ostream& serr, Arg1&& arg1, Args&&... args) -{ - serr << std::forward(arg1); - PrintArgs(serr, args...); -} - -template -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; - CreateLogLinePrefix(serr); - PrintArgs(serr, args...); - - if ( !isset(SRT_LOGF_DISABLE_EOL) ) - serr << std::endl; - - // Not sure, but it wasn't ever used. - SendLogLine(file, line, area, serr.str()); -#endif -} - -#else // !HAVE_CXX11 - -template -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; - CreateLogLinePrefix(serr); - serr << arg; - - if ( !isset(SRT_LOGF_DISABLE_EOL) ) - serr << std::endl; - - // Not sure, but it wasn't ever used. - SendLogLine(file, line, area, serr.str()); -#endif -} - -#endif // HAVE_CXX11 - -// SendLogLine can be compiled normally. It's intermediately used by: -// - Proxy object, which is replaced by DummyProxy when !ENABLE_LOGGING -// - PrintLogLine, which has empty body when !ENABLE_LOGGING -inline void LogDispatcher::SendLogLine(const char* file, int line, const std::string& area, const std::string& msg) -{ - src_config->lock(); - if ( src_config->loghandler_fn ) - { - (*src_config->loghandler_fn)(src_config->loghandler_opaque, int(level), file, line, area.c_str(), msg.c_str()); - } - else if ( src_config->log_stream ) - { - src_config->log_stream->write(msg.data(), msg.size()); - (*src_config->log_stream).flush(); - } - src_config->unlock(); -} - -} - -#endif // INC_SRT_LOGGING_H diff --git a/srtcore/netinet_any.h b/srtcore/netinet_any.h index a38765c08..85cb33141 100644 --- a/srtcore/netinet_any.h +++ b/srtcore/netinet_any.h @@ -239,6 +239,12 @@ struct sockaddr_any bool empty() const { + // Shortcut for empty-initialized. If sa_family is set to + // any of AF_INET or AF_INET6, it must also have len set accordingly. + // If it's still 0, it means that sa_family == AF_UNSPEC. + if (len == 0) + return true; + bool isempty = true; // unspec-family address is always empty if (sa.sa_family == AF_INET) @@ -267,20 +273,15 @@ struct sockaddr_any 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 - // field, just not mentioned in the sockaddr structure. + // NOTE: We rely on that sockaddr_in::sin_port and sockaddr_in6::sin6_port + // have the same offset and size. This is the current practice in every + // today OS, unlikely to change due to ABI compat requirements, + // but POSIX gives no such explicit guarantee, even though it does define + // these types themselves. uint16_t& r_port() { return sin.sin_port; } uint16_t r_port() const { return sin.sin_port; } int hport() const { return ntohs(sin.sin_port); } - - void hport(int value) - { - // Port is fortunately located at the same position - // in both sockaddr_in and sockaddr_in6 and has the - // same size. - sin.sin_port = htons(value); - } + void hport(int value) { sin.sin_port = htons(value); } sockaddr* get() { return &sa; } const sockaddr* get() const { return &sa; } @@ -387,22 +388,53 @@ struct sockaddr_any return "unknown:0"; std::ostringstream output; + write_addr(output, true); + return output.str(); + } + + std::string str_addr() const + { + if (family() != AF_INET && family() != AF_INET6) + return ""; + + std::ostringstream output; + write_addr(output, false); + return output.str(); + } + + void write_addr(std::ostringstream& output, bool withport) const + { + char hostbuf[1024]; int flags; - #if ENABLE_GETNAMEINFO + #if SRT_ENABLE_GETNAMEINFO flags = NI_NAMEREQD; #else flags = NI_NUMERICHOST | NI_NUMERICSERV; #endif - if (!getnameinfo(get(), size(), hostbuf, 1024, NULL, 0, flags)) + if (getnameinfo(get(), size(), hostbuf, 1024, NULL, 0, flags) == 0 /*success*/) + { + if (withport && family() == AF_INET6) + { + // For IPv6 the specification of the port is still with ":", + // and the standard mandates that in this situation the IP address + // must be in square brackets. + output << "[" << hostbuf << "]"; + } + else + { + output << hostbuf; + } + } + else { - output << hostbuf; + output << ""; } - output << ":" << hport(); - return output.str(); + if (withport) + output << ":" << hport(); } bool operator==(const sockaddr_any& other) const diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index e0b1edfb3..9227591b6 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -164,13 +164,10 @@ modified by #include "packet.h" #include "handshake.h" #include "logging.h" +#include "logger_fas.h" #include "handshake.h" -namespace srt_logging -{ -extern Logger inlog; -} -using namespace srt_logging; +using namespace srt::logging; namespace srt { @@ -258,7 +255,7 @@ void CPacket::setLength(size_t len, size_t cap) m_zCapacity = cap; } -#if ENABLE_HEAVY_LOGGING +#if HVU_ENABLE_HEAVY_LOGGING // Debug only static std::string FormatNumbers(UDTMessageType pkttype, const int32_t* lparam, void* rparam, const size_t size) { @@ -387,7 +384,7 @@ void CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, 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); + m_PacketVector[PV_DATA].set(rparam, size); break; @@ -584,7 +581,7 @@ inline void SprintSpecialWord(std::ostream& os, int32_t val) os << val; } -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING std::string CPacket::Info() { std::ostringstream os; diff --git a/srtcore/packet.h b/srtcore/packet.h index 6747e244c..b6c81d0a0 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -53,7 +53,6 @@ modified by #ifndef INC_SRT_PACKET_H #define INC_SRT_PACKET_H -#include "udt.h" #include "common.h" #include "utilities.h" #include "netinet_any.h" @@ -189,11 +188,23 @@ inline int32_t PacketBoundaryBits(PacketBoundary o) return MSGNO_PACKET_BOUNDARY::wrap(int32_t(o)); } +inline int32_t PacketBoundaryBitsIfRange(int begin, int val, int end) +{ + int32_t bits = PB_SUBSEQUENT; + if (val == begin) + bits |= PacketBoundaryBits(PB_FIRST); + if (val == end - 1) + bits |= PacketBoundaryBits(PB_LAST); + // NOTE: PB_FIRST | PB_LAST == PB_SOLO, while PB_SUBSEQUENT == 0. + return bits; +} + enum EncryptionKeySpec { EK_NOENC = 0, EK_EVEN = 1, - EK_ODD = 2 + EK_ODD = 2, + EK_ERROR = -1 }; enum EncryptionStatus @@ -309,7 +320,7 @@ class CPacket /// @return packet header field [2] (bit 0~31, bit 0-26 if SRT_DEBUG_TSBPD_WRAP). uint32_t getMsgTimeStamp() const; - sockaddr_any udpDestAddr() const { return m_DestAddr; } + CNetworkInterface udpDestAddr() const { return m_DestAddr; } #ifdef SRT_DEBUG_TSBPD_WRAP // Receiver static const uint32_t MAX_TIMESTAMP = 0x07FFFFFF; // 27 bit fast wraparound for tests (~2m15s) @@ -348,7 +359,7 @@ class CPacket int32_t m_extra_pad; bool m_data_owned; - sockaddr_any m_DestAddr; + CNetworkInterface m_DestAddr; size_t m_zCapacity; protected: @@ -371,16 +382,24 @@ class CPacket static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size = SRT_PH_E_SIZE * sizeof(uint32_t) - // 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; +private: // Do not disclose ingredients to the public + static const size_t UDP_HDR_SIZE = 8; // 8 bytes of UDP { u16 sport, dport, len, csum }. + static const size_t IPv4_HDR_SIZE = 20; // 20 bytes IPv4 + static const size_t IPv6_HDR_SIZE = 32; // 32 bytes IPv6 +public: + static inline size_t udpHeaderSize(int family) + { + return UDP_HDR_SIZE + (family == AF_INET ? IPv4_HDR_SIZE : IPv6_HDR_SIZE); + } + static inline size_t srtPayloadSize(int family) + { + return ETH_MAX_MTU_SIZE - (family == AF_INET ? IPv4_HDR_SIZE : IPv6_HDR_SIZE) - UDP_HDR_SIZE - HDR_SIZE; + } // Maximum transmission unit size. 1500 in case of Ethernet II (RFC 1191). static const size_t ETH_MAX_MTU_SIZE = 1500; // 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; } @@ -390,7 +409,7 @@ class CPacket void setCapacity(size_t cap) { m_zCapacity = cap; } uint32_t header(SrtPktHeaderFields field) const { return m_nHeader[field]; } -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING std::string MessageFlagStr() { return PacketMessageFlagStr(m_nHeader[SRT_PH_MSGNO]); } std::string Info(); #else diff --git a/srtcore/packetfilter.cpp b/srtcore/packetfilter.cpp index e8b2a6102..c9635cbbc 100644 --- a/srtcore/packetfilter.cpp +++ b/srtcore/packetfilter.cpp @@ -23,7 +23,7 @@ #include "logging.h" using namespace std; -using namespace srt_logging; +using namespace srt::logging; using namespace srt::sync; namespace srt { @@ -112,28 +112,17 @@ bool PacketFilter::Internal::CheckFilterCompat(SrtFilterConfig& w_agent, const S return true; } -struct SortBySequence +bool PacketFilter::provide(const CPacket& rpkt, CallbackHolder handler, loss_seqs_t& w_loss_seqs) { - 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; - } -}; - -void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs) -{ - const CPacket& rpkt = unit->m_Packet; + bool passthrough = false; + // w_loss_seqs enters empty into this function and can be only filled here. XXX ASSERT? 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_bTaken = true; - HLOGC(pflog.Debug, log << "FILTER: PASSTHRU current packet %" << unit->m_Packet.getSeqNo()); - w_incoming.push_back(unit); + HLOGC(pflog.Debug, log << "FILTER: PASSTHRU current packet %" << rpkt.getSeqNo()); + passthrough = true; } else { @@ -142,7 +131,6 @@ void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_se m_parent->m_stats.rcvr.recvdFilterExtra.count(1); } - // 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) { @@ -167,29 +155,12 @@ void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_se HLOGC(pflog.Debug, log << "FILTER: inserting REBUILT packets (" << m_provided.size() << "):"); size_t nsupply = m_provided.size(); - InsertRebuilt(w_incoming, m_unitq); + CopyRebuilt(handler); 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, - // SET THEM ALL FREE. This is because now it's up to the - // buffer to decide as to whether it wants them or not. - // 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_bTaken = false; - for (vector::iterator i = w_incoming.begin(); i != w_incoming.end(); ++i) - { - CUnit* u = *i; - u->m_bTaken = false; - } - - // Packets must be sorted by sequence number, ascending, in order - // not to challenge the SRT's contiguity checker. - sort(w_incoming.begin(), w_incoming.end(), SortBySequence()); - // For now, report immediately the irrecoverable packets // from the row. @@ -202,8 +173,7 @@ void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_se // With "always", do not report any losses, SRT will simply check // them itself. - return; - + return passthrough; } bool PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) @@ -237,37 +207,16 @@ bool PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) return true; } - -void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) +void PacketFilter::CopyRebuilt(CallbackHolder handler) { if (m_provided.empty()) return; for (vector::iterator i = m_provided.begin(); i != m_provided.end(); ++i) { - CUnit* u = uq->getNextAvailUnit(); - if (!u) - { - LOGC(pflog.Error, log << "FILTER: LOCAL STORAGE DEPLETED. Can't return rebuilt packets."); + bool shall_continue = CALLBACK_CALL(handler, (const char*)i->hdr, i->buffer, i->length); + if (!shall_continue) break; - } - - // LOCK the unit as taken because otherwise the next - // call to getNextAvailUnit will return THE SAME UNIT. - 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); - packet.setLength(i->length); - - HLOGC(pflog.Debug, log << "FILTER: PROVIDING rebuilt packet %" << packet.getSeqNo()); - - incoming.push_back(u); } m_provided.clear(); @@ -279,33 +228,12 @@ PacketFilter::Factory::~Factory() { } -#if HAVE_CXX11 - PacketFilter::Internal& PacketFilter::internal() { static PacketFilter::Internal instance; return instance; } -#else // !HAVE_CXX11 - -static pthread_once_t s_PacketFactoryOnce = PTHREAD_ONCE_INIT; - -static PacketFilter::Internal *getInstance() -{ - static PacketFilter::Internal instance; - return &instance; -} - -PacketFilter::Internal& PacketFilter::internal() -{ - // We don't want lock each time, pthread_once can be faster than mutex. - pthread_once(&s_PacketFactoryOnce, reinterpret_cast(getInstance)); - return *getInstance(); -} - -#endif - PacketFilter::Internal::Internal() { // Add here builtin packet filters and mark them @@ -316,7 +244,7 @@ PacketFilter::Internal::Internal() m_builtin_filters.insert("fec"); } -bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr) +bool PacketFilter::configure(CUDT* parent, const std::string& confstr) { m_parent = parent; @@ -332,12 +260,12 @@ bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& co SrtFilterInitializer init; init.socket_id = parent->socketID(); - init.snd_isn = parent->sndSeqNo(); + init.snd_isn = parent->sndSeqNo(); // [TSA] NOTE: ignore locking during init init.rcv_isn = parent->rcvSeqNo(); // XXX This is a formula for a full "SRT payload" part that undergoes transmission, // might be nice to have this formula as something more general. - init.payload_size = parent->OPT_PayloadSize() + parent->getAuthTagSize(); + init.payload_size = parent->OPT_PayloadSize() + parent->getAuthTagSize(); // [TSA] idem init.rcvbuf_size = parent->m_config.iRcvBufSize; HLOGC(pflog.Debug, log << "PFILTER: @" << init.socket_id << " payload size=" @@ -348,7 +276,6 @@ bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& co if (!m_filter) return false; - m_unitq = uq; // The filter should have pinned in all events // that are of its interest. It's stated that diff --git a/srtcore/packetfilter.h b/srtcore/packetfilter.h index 8d4dc6369..87fd58ba1 100644 --- a/srtcore/packetfilter.h +++ b/srtcore/packetfilter.h @@ -116,7 +116,7 @@ class PacketFilter SrtPacketFilterBase* m_filter; void Check() { -#if ENABLE_DEBUG +#if SRT_ENABLE_DEBUG if (!m_filter) abort(); #endif @@ -164,7 +164,7 @@ class PacketFilter return i->second.get(); } bool ParseConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf = NULL); - bool CheckFilterCompat(srt::SrtFilterConfig& w_agent, const srt::SrtFilterConfig& peer); + bool CheckFilterCompat(SrtFilterConfig& w_agent, const SrtFilterConfig& peer); public: Internal(); }; @@ -179,19 +179,19 @@ class PacketFilter // In the beginning it's initialized as first, builtin default. // Still, it will be created only when requested. - PacketFilter(): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {} + PacketFilter(): m_filter(), m_parent(), m_sndctlpkt(0) /*, m_unitq()*/ {} // Copy constructor - important when listener-spawning // Things being done: // 1. The filter is individual, so don't copy it. Set NULL. // 2. This will be configured anyway basing on possibly a new rule set. - PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_parent(), 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 // filter basing on the value in selector, then // pin oneself in into CUDT for receiving event signals. - bool configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr); + bool configure(CUDT* parent, const std::string& confstr); static bool correctConfig(const SrtFilterConfig& c); @@ -204,11 +204,18 @@ class PacketFilter void feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); } SRT_ARQLevel arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); } bool packControlPacket(int32_t seq, int kflg, CPacket& w_packet); - void receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs); + + // This handler will be called for every packet rebuilt (including 0 times). + // retval: + // - true: continue after that packet + // - false: stop after that call (rebuilt packets will be still removed) + // NOTE: it's up to the caller to sort all provided packets by sequence number! + typedef bool copy_rebuilt_fn(void* opaq, const char* header, const char* data, size_t datasize); + bool provide(const CPacket& packet, CallbackHolder handler, loss_seqs_t& w_loss_seqs); protected: PacketFilter& operator=(const PacketFilter& p); - void InsertRebuilt(std::vector& incoming, CUnitQueue* uq); + void CopyRebuilt(CallbackHolder handler); CUDT* m_parent; @@ -216,7 +223,6 @@ class PacketFilter SrtPacket m_sndctlpkt; // Receiver part - CUnitQueue* m_unitq; std::vector m_provided; }; diff --git a/srtcore/packetfilter_api.h b/srtcore/packetfilter_api.h index 3bfba7c76..ef0d8867f 100644 --- a/srtcore/packetfilter_api.h +++ b/srtcore/packetfilter_api.h @@ -66,7 +66,7 @@ struct SrtFilterInitializer struct SrtPacket { uint32_t hdr[SRT_PH_E_SIZE]; - char buffer[SRT_LIVE_MAX_PLSIZE]; + char buffer[SRT_MAX_PLSIZE_AF_INET]; // Using this as the bigger one (this for AF_INET6 is smaller) size_t length; SrtPacket(size_t size): length(size) diff --git a/srtcore/platform_sys.h b/srtcore/platform_sys.h index 5dd2b928d..5f292a520 100644 --- a/srtcore/platform_sys.h +++ b/srtcore/platform_sys.h @@ -34,25 +34,23 @@ typedef __int64 uint64_t; #endif #endif -#endif + #include + #include -#ifdef _WIN32 #include #include #include #include -#ifndef __MINGW32__ - #include -#endif + #ifndef __MINGW32__ + #include + #endif #ifdef SRT_IMPORT_TIME #include #endif - #include - #include #else #if defined(__APPLE__) && __APPLE__ diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 4ced92d6f..947bdc49c 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -51,21 +51,28 @@ modified by *****************************************************************************/ #include "platform_sys.h" +#include "queue.h" #include #include "common.h" #include "api.h" #include "netinet_any.h" -#include "threadname.h" +#include "hvu_threadname.h" +#include "sync.h" #include "logging.h" -#include "queue.h" using namespace std; using namespace srt::sync; -using namespace srt_logging; +using namespace srt::logging; +using namespace hvu; // ThreadName + +namespace srt +{ -srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss) +#if ! USE_RECEIVER_UNIT_POOL + +CUnitQueue::CUnitQueue(int initNumUnits, int mss) : m_iNumTaken(0) , m_iMSS(mss) , m_iBlockSize(initNumUnits) @@ -83,7 +90,7 @@ srt::CUnitQueue::CUnitQueue(int initNumUnits, int mss) m_iSize = m_iBlockSize; } -srt::CUnitQueue::~CUnitQueue() +CUnitQueue::~CUnitQueue() { CQEntry* p = m_pQEntry; @@ -101,7 +108,7 @@ srt::CUnitQueue::~CUnitQueue() } } -srt::CUnitQueue::CQEntry* srt::CUnitQueue::allocateEntry(const int iNumUnits, const int mss) +CUnitQueue::CQEntry* CUnitQueue::allocateEntry(const int iNumUnits, const int mss) { CQEntry* tempq = NULL; CUnit* tempu = NULL; @@ -126,6 +133,7 @@ srt::CUnitQueue::CQEntry* srt::CUnitQueue::allocateEntry(const int iNumUnits, co for (int i = 0; i < iNumUnits; ++i) { tempu[i].m_bTaken = false; + tempu[i].m_pParentQueue = this; tempu[i].m_Packet.m_pcData = tempb + i * mss; } @@ -136,7 +144,7 @@ srt::CUnitQueue::CQEntry* srt::CUnitQueue::allocateEntry(const int iNumUnits, co return tempq; } -int srt::CUnitQueue::increase_() +int CUnitQueue::increase_() { const int numUnits = m_iBlockSize; HLOGC(qrlog.Debug, log << "CUnitQueue::increase: Capacity" << capacity() << " + " << numUnits << " new units, " << m_iNumTaken << " in use."); @@ -154,7 +162,7 @@ int srt::CUnitQueue::increase_() return 0; } -srt::CUnit* srt::CUnitQueue::getNextAvailUnit() +CUnit* CUnitQueue::getNextAvailUnit() { const int iNumUnitsTotal = capacity(); if (m_iNumTaken * 10 > iNumUnitsTotal * 9) // 90% or more are in use. @@ -185,7 +193,7 @@ srt::CUnit* srt::CUnitQueue::getNextAvailUnit() return NULL; } -void srt::CUnitQueue::makeUnitFree(CUnit* unit) +void CUnitQueue::makeUnitFree(CUnit* unit) { SRT_ASSERT(unit != NULL); SRT_ASSERT(unit->m_bTaken); @@ -194,7 +202,7 @@ void srt::CUnitQueue::makeUnitFree(CUnit* unit) --m_iNumTaken; } -void srt::CUnitQueue::makeUnitTaken(CUnit* unit) +void CUnitQueue::makeUnitTaken(CUnit* unit) { ++m_iNumTaken; @@ -203,300 +211,460 @@ void srt::CUnitQueue::makeUnitTaken(CUnit* unit) unit->m_bTaken.store(true); } -srt::CSndUList::CSndUList(sync::CTimer* pTimer) - : m_pHeap(NULL) - , m_iArrayLength(512) - , m_iLastEntry(-1) - , m_ListLock() - , m_pTimer(pTimer) +#else + +void CPacketUnitPool::allocateOneSeries(UnitContainer& w_series, size_t series_size, size_t unit_size) { - setupCond(m_ListCond, "CSndUListCond"); - m_pHeap = new CSNode*[m_iArrayLength]; + // Just in case when w_series contained anything by mistake, delete it first. + w_series.clear(); + + w_series.resize(series_size); + + for (size_t i = 0; i < series_size; ++i) + { + w_series[i].allocate(unit_size); + } } -srt::CSndUList::~CSndUList() +bool CPacketUnitPool::retrieveSeries(UnitContainer& series) { - releaseCond(m_ListCond); - delete[] m_pHeap; + UniqueLock lk (m_UpperLock); + // EXPECTED: series.empty() + // Will be replaced by the existing series, if found + + if (PullBack( (m_Series), (series) )) + { + SRT_ASSERT(verifySeries(series)); + + HLOGC(qrlog.Debug, log << "CPacketUnitPool: getting spare storage with " << series.size() << "pkt - remaining " + << m_Series.size() << " series"); + return true; + } + // Otherwise m_Series is empty; allocate from system. + + size_t series_size = m_zSeriesSize; + size_t unit_size = m_zUnitSize; + + // We don't need access to internal data since here. + lk.unlock(); + + HLOGC(qrlog.Debug, log << "CPacketUnitPool: no spare storage - ALLOCATE one series (" << m_zSeriesSize << "pkt) from system"); + + // Allocate directly to the target vector. + // You'll get them back here when they are recycled. + allocateOneSeries((series), series_size, unit_size); + return true; } -void srt::CSndUList::resetAtFork() +void CPacketUnitPool::returnUnit(UnitPtr& returned_entry) { - resetCond(m_ListCond); + SRT_ASSERT(!! returned_entry); + ScopedLock lk (m_LowerLock); + m_RecycledUnits.push_back(UnitPtr()); + m_RecycledUnits.back().swap(returned_entry); + + + // Check if you have enough recycled units, and if so, + // fold them into the series container + if (m_RecycledUnits.size() >= m_zSeriesSize) + { + // NOTE ORDER: LowerLock, UpperLock + ScopedLock lkup (m_UpperLock); + if (m_Series.size() >= MAX_SERIES_ALLOWED) + { + HLOGC(qrlog.Debug, log << "CPacketUnitPool: retrieval denied: maximum of " << (+MAX_SERIES_ALLOWED) << " allowed"); + m_RecycledUnits.clear(); + } + else + { + MoveBack((m_Series), (m_RecycledUnits)); + HLOGC(qrlog.Debug, log << "CPacketUnitPool: CONDENSED UNIT, lifted up a new series of " << m_zSeriesSize + << " packets, total " << m_Series.size() << " available"); + } + m_RecycledUnits.reserve(m_zSeriesSize); + } + else + { + HLOGC(qrlog.Debug, log << "CPacketUnitPool: CONDENSED UNIT, still " << (m_zSeriesSize - m_RecycledUnits.size()) << "/" + << m_zSeriesSize << " to consolidate"); + } } -void srt::CSndUList::update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts) +void CPacketUnitPool::returnUnitSeries(std::vector& units) { - ScopedLock listguard(m_ListLock); + SRT_ASSERT(verifySeries(units)); + ScopedLock lk (m_LowerLock); - CSNode* n = u->m_pSNode; + // Move only a part of the units that fit in the condenser. This is + // because we are keeing the reservation in order to avoid copies in the + // vector. Split into several pieces if need be. - if (n->m_iHeapLoc >= 0) - { - if (reschedule == DONT_RESCHEDULE) - return; + size_t usize = units.size(); + IF_HEAVY_LOGGING(size_t initial_usize = usize); - if (n->m_tsTimeStamp <= ts) - return; + // Ok, this should be unlikely, but check, just in case. + IF_HEAVY_LOGGING(int nblocks = 0); + IF_HEAVY_LOGGING(int recycled_initial = m_RecycledUnits.size()); + while (usize > m_zSeriesSize) + { + UnitContainer part; + part.resize(m_zSeriesSize, UnitPtr()); + // SIZE-1 = last item. This is reverse-indexing by i + // NOTE: usize keeps the shrunk size of units (skipping removed) + size_t ui = usize - 1; + for (size_t i = 0; i < part.size(); ++i, --ui) + { + units[ui].swap(part[i]); + } + usize -= part.size(); + IF_HEAVY_LOGGING(++nblocks); - if (n->m_iHeapLoc == 0) + // Uplift this one { - n->m_tsTimeStamp = ts; - m_pTimer->interrupt(); - return; + SRT_ASSERT(verifySeries(part)); + ScopedLock lkup (m_UpperLock); + MoveBack( (m_Series), (part) ); } + } + // If any full blocks were removed, delete empty cells. + if (usize < units.size()) + units.resize(usize); - remove_(u); - insert_norealloc_(ts, u); - return; + SRT_ASSERT(verifySeries(units)); + + // Just in case, make sure that m_RecycledUnits isn't full + if (m_RecycledUnits.size() == m_zSeriesSize) + { + SRT_ASSERT(verifySeries(m_RecycledUnits)); + ScopedLock lkup (m_UpperLock); + MoveBack( (m_Series), (m_RecycledUnits) ); + + m_RecycledUnits.reserve(m_zSeriesSize); + IF_HEAVY_LOGGING(++nblocks); } - insert_(ts, u); -} + HLOGC(qrlog.Debug, log << "returnUnitSeries: turned in " << nblocks << " blocks from " << initial_usize + << " units, recycled: " << recycled_initial << " -> " + << m_RecycledUnits.size() << " remain " << usize << " units to condense"); -srt::CUDT* srt::CSndUList::pop() -{ - ScopedLock listguard(m_ListLock); + // Ok, nothing to do more in that case. + if (usize == 0) + return; - if (-1 == m_iLastEntry) - return NULL; + // Ok, now we are certain that units is at most m_zSeriesSize. + // Catch a special case when it's equal size. In this case only + // uplift the provided container and do not touch the recycler. + if (usize == m_zSeriesSize) + { + SRT_ASSERT(verifySeries(units)); + ScopedLock lkup (m_UpperLock); + MoveBack( (m_Series), (units) ); + HLOGC(qrlog.Debug, log << "returnUnitSeries: whole units container lifted; recycler contains " << m_RecycledUnits.size() << " units"); + return; + } - // no pop until the next scheduled time - if (m_pHeap[0]->m_tsTimeStamp > steady_clock::now()) - return NULL; + SRT_ASSERT(m_RecycledUnits.size() < m_zSeriesSize); + size_t recycled_free = m_zSeriesSize - m_RecycledUnits.size(); - CUDT* u = m_pHeap[0]->m_pUDT; - remove_(u); - return u; -} + // Ok, so we have possible partial overflow to rule out + size_t skipunits = 0; + if (units.size() > recycled_free) + skipunits = units.size() - recycled_free; -void srt::CSndUList::remove(const CUDT* u) -{ - ScopedLock listguard(m_ListLock); - remove_(u); -} + HLOGC(qrlog.Debug, log << "returnUnitSeries: merging " << units.size() << " skipping initial " << skipunits + << " to recycled containing already " << m_RecycledUnits.size()); -steady_clock::time_point srt::CSndUList::getNextProcTime() -{ - ScopedLock listguard(m_ListLock); + size_t rx = m_RecycledUnits.size(); + size_t total_size = rx + units.size() - skipunits; + m_RecycledUnits.resize(total_size); + + for (size_t ux = skipunits; ux < units.size(); ++ux, ++rx) + { + m_RecycledUnits[rx].swap(units[ux]); + } + // Remove cleared units + units.resize(skipunits); + SRT_ASSERT(verifySeries(units)); + SRT_ASSERT(verifySeries(m_RecycledUnits)); - if (-1 == m_iLastEntry) - return steady_clock::time_point(); + // If hit the exact size, lift up. + if (m_RecycledUnits.size() == m_zSeriesSize) + { + ScopedLock lkup (m_UpperLock); + MoveBack( (m_Series), (m_RecycledUnits) ); - return m_pHeap[0]->m_tsTimeStamp; + if (!units.empty()) + { + SRT_ASSERT(verifySeries(units)); + m_RecycledUnits.swap(units); + } + m_RecycledUnits.reserve(m_zSeriesSize); + } } -void srt::CSndUList::waitNonEmpty() const -{ - UniqueLock listguard(m_ListLock); - if (m_iLastEntry >= 0) - return; - m_ListCond.wait(listguard); -} -void srt::CSndUList::signalInterrupt() const +// This should check if there are any excessive +// recycled blocks, and deletes them. +void CPacketUnitPool::updateLimits() { - ScopedLock listguard(m_ListLock); - m_ListCond.notify_one(); -} + // Roughly calculate the memory occupied by + // existing series. -void srt::CSndUList::realloc_() -{ - CSNode** temp = NULL; + // Calculate how many series we are allowed to have + // NOTE: it is not allowed to have the size less than 3 series. + // This is because we need to have at least one series for the + // sole disposal of the multiplexer, one ready to pickup without hiccup + // (a hiccup means that the unit series will be denied and multiplexer + // will have to read and discard the packet), and one being reclaimed + // from the receiver buffer. + size_t max_remain_series = std::max(m_zMaxSeries.load(), +MIN_SERIES_REQUIRED); - try + ScopedLock lk (m_UpperLock); + if (max_remain_series < m_Series.size()) { - temp = new CSNode*[2 * m_iArrayLength]; + std::vector::iterator new_end = m_Series.begin() + m_zMaxSeries; + m_Series.erase(new_end, m_Series.end()); } - catch (...) +} + + +CPacketUnitPool::UnitPtr:: ~UnitPtr() +{ + if (owns) { - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + //HLOGC(qrlog.Debug, log << "CPacketUnitPool: DELETING a unit (returning to the system)"); + delete ptr; } - - memcpy((temp), m_pHeap, sizeof(CSNode*) * m_iArrayLength); - m_iArrayLength *= 2; - delete[] m_pHeap; - m_pHeap = temp; } -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) - realloc_(); +#endif - insert_norealloc_(ts, u); +// CSendOrderList -- replacement for CSndUList +CSendOrderList::CSendOrderList(sync::Mutex& emx): m_ExternLock(emx) +{ + setupCond(m_ListCond, "CSndUListCond"); } -void srt::CSndUList::insert_norealloc_(const steady_clock::time_point& ts, const CUDT* u) +void CSendOrderList::resetAtFork() { - CSNode* n = u->m_pSNode; + resetCond(m_ListCond); +} - // do not insert repeated node - if (n->m_iHeapLoc >= 0) - return; +bool CSendOrderList::update(SocketHolder::sockiter_t point, SocketHolder::EReschedule reschedule, sync::steady_clock::time_point ts) +{ + if (point == SocketHolder::none()) + { + HLOGC(qslog.Error, log << "CSendOrderList: IPE: trying to schedule a socket outside of Multiplexer!"); + return false; + } - SRT_ASSERT(m_iLastEntry < m_iArrayLength); + SocketHolder::SendNode& n = point->m_SendOrder; - m_iLastEntry++; - m_pHeap[m_iLastEntry] = n; - n->m_tsTimeStamp = ts; +#if HVU_ENABLE_HEAVY_LOGGING + sync::steady_clock::time_point now = sync::steady_clock::now(); + std::ostringstream nowrel, oldrel; + nowrel << " = now" << showpos << (ts - now).count() << "us"; + { + oldrel << " = now" << showpos << (n.time - now).count() << "us"; + } +#endif - int q = m_iLastEntry; - int p = q; - while (p != 0) + if (!n.pinned()) { - p = (q - 1) >> 1; - if (m_pHeap[p]->m_tsTimeStamp <= m_pHeap[q]->m_tsTimeStamp) - break; + // New insert, not considering reschedule. + HLOGC(qslog.Debug, log << "CSndUList: UPDATE: inserting @" << point->id() << " anew T=" << FormatTime(ts) << nowrel.str()); + + m_Schedule.insert(ts, point); + if (n.is_top()) + { + n.time = ts; + m_ListCond.notify_all(); + } + return true; + } - swap(m_pHeap[p], m_pHeap[q]); - m_pHeap[q]->m_iHeapLoc = q; - q = p; + // EXISTING NODE - reschedule if requested + if (reschedule == SocketHolder::DONT_RESCHEDULE) + { + HLOGC(qslog.Debug, log << "CSndUList: UPDATE: NOT rescheduling @" << point->id() + << " - remains T=" << FormatTime(n.time) << oldrel.str()); + return false; } - n->m_iHeapLoc = q; + // NOTE: Rescheduling means to speed up release time. So apply only if new time is earlier. + if (n.time <= ts) + { + HLOGC(qslog.Debug, log << "CSndUList: UPDATE: NOT rescheduling @" << point->id() + << " to +" << FormatDurationAuto(ts - n.time) + << " - remains T=" << FormatTime(n.time) << oldrel.str()); + return false; + } - // an earlier event has been inserted, wake up sending worker - if (n->m_iHeapLoc == 0) - m_pTimer->interrupt(); + HLOGC(qslog.Debug, log << "CSndUList: UPDATE: rescheduling @" << point->id() << " T=" << FormatTime(n.time) + << nowrel.str() << " - speedup by " << FormatDurationAuto(n.time - ts)); - // first entry, activate the sending queue - if (0 == m_iLastEntry) + // Special case for the first element - no replacement needed, just update. + if (n.is_top()) { - // m_ListLock is assumed to be locked. - m_ListCond.notify_one(); + n.time = ts; + m_ListCond.notify_all(); + return true; } + + m_Schedule.update(n.pos, ts); + + return true; } -void srt::CSndUList::remove_(const CUDT* u) +SocketHolder::sockiter_t CSendOrderList::wait(UniqueLock& w_lk) { - CSNode* n = u->m_pSNode; + CSync lg (m_ListCond, (w_lk)); - if (n->m_iHeapLoc >= 0) + bool signaled = false; + for (;;) { - // 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.load(); + sync::steady_clock::time_point uptime; - int q = n->m_iHeapLoc; - int p = q * 2 + 1; - while (p <= m_iLastEntry) + // Always pick up something if available, even if stopped. + if (!m_Schedule.empty()) { - if ((p + 1 <= m_iLastEntry) && (m_pHeap[p]->m_tsTimeStamp > m_pHeap[p + 1]->m_tsTimeStamp)) - p++; - - if (m_pHeap[q]->m_tsTimeStamp > m_pHeap[p]->m_tsTimeStamp) - { - swap(m_pHeap[p], m_pHeap[q]); - m_pHeap[p]->m_iHeapLoc = p; - m_pHeap[q]->m_iHeapLoc = q; + // Have at least one element in the list. + // Check if the ship time is in the past + SocketHolder::sockiter_t point = m_Schedule.top_raw(); + if (point->m_SendOrder.time < sync::steady_clock::now()) + return point; + uptime = point->m_SendOrder.time; + signaled = false; + } + // Always exit immediately if signalInterrupt was called + else if (signaled || !m_bRunning) + { + // This happens if the waiting on a CV has exit on alleged notification: + // - spurious - just return to waiting + // - added a list element - pickup if ready, otherwise wait + // - signalInterrupt was called - always exit immediately + return SocketHolder::none(); + } - q = p; - p = q * 2 + 1; - } - else - break; + // If not, continue waiting. Wait indefinitely if no time. + // Hangup prevention should be provided by having a certain + // interrupt request when closing a socket. + if (is_zero(uptime)) + { + signaled = true; + lg.wait(); + } + else + { + signaled = lg.wait_until(uptime); } + } +} + +bool CSendOrderList::requeue(SocketHolder::sockiter_t point, const sync::steady_clock::time_point& uptime) +{ + if (point == SocketHolder::none()) + { + HLOGC(qslog.Error, log << "CSendOrderList: IPE: trying to enqueue a socket outside of Multiplexer!"); + return false; + } + + SocketHolder::SendNode& node = point->m_SendOrder; + + // Should be. + if (!node.pinned()) + { + m_Schedule.insert(uptime, point); // 'node' is updated here! + return node.is_top(); + } + + if (m_Schedule.size() == 1) + { + node.time = uptime; - n->m_iHeapLoc = -1; + // Return true to declare that the top element was updated, + // but don't do anything additionally, as this function is + // to be used in the same thread that calls wait(). + return true; } - // the only event has been deleted, wake up immediately - if (0 == m_iLastEntry) - m_pTimer->interrupt(); + m_Schedule.update(node.pos, uptime); + return node.is_top(); } -// -srt::CSndQueue::CSndQueue() - : m_pSndUList(NULL) - , m_pChannel(NULL) - , m_pTimer(NULL) - , m_bClosing(false) +void CSendOrderList::signalInterrupt() { + ScopedLock listguard(m_ExternLock); + m_bRunning = false; + m_ListCond.notify_one(); } -srt::CSndQueue::~CSndQueue() +/////////////////////////////////////////// + +// +CSndQueue::CSndQueue(CMultiplexer* parent): + m_parent(parent), + m_SendOrderList(parent->m_SocketsLock), + m_pChannel(NULL), + m_bClosing(false) { - delete m_pSndUList; } -void srt::CSndQueue::resetAtFork() +void CSndQueue::resetAtFork() { resetThread(&m_WorkerThread); - m_pSndUList->resetAtFork(); + m_SendOrderList.resetAtFork(); } -void srt::CSndQueue::stop() +void CSndQueue::stop() { + // We use the decent way, so we say to the thread "please exit". m_bClosing = true; - if (m_pTimer != NULL) + m_SendOrderList.signalInterrupt(); + + // Sanity check of the function's affinity. + if (sync::this_thread_is(m_WorkerThread)) { - m_pTimer->interrupt(); + LOGC(rslog.Error, log << "IPE: SndQ:WORKER TRIES TO CLOSE ITSELF!"); + return; // do nothing else, this would cause a hangup or crash. } - // Unblock CSndQueue worker thread if it is waiting. - m_pSndUList->signalInterrupt(); - + HLOGC(rslog.Debug, log << "SndQueue: EXIT (forced)"); + // And we trust the thread that it does. if (m_WorkerThread.joinable()) - { - HLOGC(rslog.Debug, log << "SndQueue: EXIT"); m_WorkerThread.join(); - } } -int srt::CSndQueue::ioctlQuery(int type) const +CSndQueue::~CSndQueue() { - return m_pChannel->ioctlQuery(type); -} -int srt::CSndQueue::sockoptQuery(int level, int type) const -{ - return m_pChannel->sockoptQuery(level, type); } -#if ENABLE_LOGGING -srt::sync::atomic srt::CSndQueue::m_counter(0); +#if HVU_ENABLE_LOGGING +sync::atomic CSndQueue::m_counter(0); #endif -void srt::CSndQueue::init(CChannel* c, CTimer* t) +void CSndQueue::init(CChannel* c) { m_pChannel = c; - m_pTimer = t; - m_pSndUList = new CSndUList(t); -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING ++m_counter; - const std::string thrname = "SRT:SndQ:w" + Sprint(m_counter); + const string thrname = fmtcat("SRT:SndQ:w", m_counter.load()); const char* thname = thrname.c_str(); #else const char* thname = "SRT:SndQ"; #endif - if (!StartThread(m_WorkerThread, CSndQueue::worker, this, thname)) + if (!StartThread((m_WorkerThread), CSndQueue::worker_fwd, this, thname)) + { throw CUDTException(MJ_SYSTEMRES, MN_THREAD); + } } -int srt::CSndQueue::getIpTTL() const -{ - return m_pChannel ? m_pChannel->getIpTTL() : -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) +static void CSndQueueDebugHighratePrint(const CSndQueue* self, const steady_clock::time_point currtime) { if (self->m_DbgTime <= currtime) { @@ -514,360 +682,223 @@ static void CSndQueueDebugHighratePrint(const srt::CSndQueue* self, const steady } #endif -void* srt::CSndQueue::worker(void* param) +void CSndQueue::workerSendOrder() { - CSndQueue* self = (CSndQueue*)param; - - std::string thname; + string thname; ThreadName::get(thname); THREAD_STATE_INIT(thname.c_str()); -#if defined(SRT_DEBUG_SNDQ_HIGHRATE) -#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) - { - const steady_clock::time_point next_time = self->m_pSndUList->getNextProcTime(); - - INCREMENT_THREAD_ITERATIONS(); + m_SendOrderList.setRunning(); - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lIteration++); +#if SRT_ENABLE_THREAD_DEBUG + Condition::ScopedNotifier nt(m_SendOrderList.m_ListCond); +#endif - if (is_zero(next_time)) + for (;;) + { + if (m_bClosing) { - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyTs++); - - // wait here if there is no sockets with data to be sent - THREAD_PAUSED(); - if (!self->m_bClosing) - { - self->m_pSndUList->waitNonEmpty(); - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lCondWait++); - } - THREAD_RESUMED(); - - continue; + HLOGC(qslog.Debug, log << "SndQ: closed, exiting"); + break; } - // wait until next processing time of the first socket on the list - const steady_clock::time_point currtime = steady_clock::now(); - - IF_DEBUG_HIGHRATE(CSndQueueDebugHighratePrint(self, currtime)); - if (currtime < next_time) + CSndPacket sndpkt; + CNetworkInterface source_addr; + sockaddr_any target_addr; { + // Not scoped because it will get temporary unlock in wait(). + UniqueLock lk (m_parent->m_SocketsLock); + + HLOGC(qslog.Debug, log << "SndQ: waiting to get next send candidate..."); THREAD_PAUSED(); - self->m_pTimer->sleep_until(next_time); + + // NOTE: wait() unlocks lk for the stall time, then locks back on exit + // [TSA] NOTE: m_SendOrderList.m_ExternLock = m_parent->m_SocketsLock (CSndQueue ctor) + SocketHolder::sockiter_t runner = m_SendOrderList.wait((lk)); THREAD_RESUMED(); - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lSleepTo++); - } - // 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; - } + INCREMENT_THREAD_ITERATIONS(); -#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 (runner == SocketHolder::none()) + { + HLOGC(qslog.Debug, log << "SndQ: wait interrupted..."); + if (m_bClosing) + { + HLOGC(qslog.Debug, log << "SndQ: interrupted, closed, exiting"); + break; + } - if (!u->m_bConnected || u->m_bBroken) - { - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); - continue; - } + // REPORT IPE??? + // wait() should not exit if it wasn't forcefully interrupted + HLOGC(qslog.Debug, log << "SndQ: interrupted, SPURIOUS??? IPE??? Repeating..."); + continue; + } - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), u->id()); - if (!sk.socket) - { - HLOGC(qslog.Debug, log << "Socket to be processed was deleted in the meantime, not packing"); - continue; - } + // Acquire the socket to prevent it from deletion during processing. + // We can use acquire_LOCKED because no one will delete a bound socket + // until it's removed from the multiplexer, and against that we are protected + // by m_SocketsLock mutex. + CUDTUnited::SocketKeeper keep; + keep.acquire_LOCKED(runner->m_pSocket); - // 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)); + // Get a socket with a send request if any. + CUDT& u = runner->m_pSocket->core(); - // Check if extracted anything to send - if (res == false) - { - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lNotReadyPop++); - continue; - } + IF_HEAVY_LOGGING(const int id = u.socketID()); - const sockaddr_any addr = u->m_PeerAddr; - if (!is_zero(next_send_time)) - self->m_pSndUList->update(u, CSndUList::DO_RESCHEDULE, next_send_time); +#define UST(field) ((u.m_b##field) ? "+" : "-") << #field << " " + HLOGC(qslog.Debug, + log << "CSndQueue: requesting packet from @" << id << " 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 || u.m_bClosing) + { + HLOGC(qslog.Debug, log << "Socket to be processed is already broken, not packing"); + m_SendOrderList.remove(runner); // [TSA] IDEM + continue; + } - HLOGC(qslog.Debug, log << self->CONID() << "chn:SENDING: " << pkt.Info()); - self->m_pChannel->sendto(addr, pkt, source_addr); + /// { + // m_SocketsLock must be lifted here. + // The node data will have to be updated after that. + lk.unlock(); - IF_DEBUG_HIGHRATE(self->m_WorkerStats.lSendTo++); - } + // We unlock m_SocketsLock, but the socket is now API locked. - THREAD_EXIT(); - return NULL; -} + // pack a packet from the socket + steady_clock::time_point next_send_time; + const bool res = u.packData((sndpkt), (next_send_time), (source_addr)); -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 - // 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(); -} + lk.lock(); + /// } -// -srt::CRcvUList::CRcvUList() - : m_pUList(NULL) - , m_pLast(NULL) -{ -} + // THIS MUST BE UPDATED HERE because while the m_SocketsLock was unlocked, + // the socket could have been removed from the multiplexer; if that happened + // the node will be "NULL node". + runner = u.m_MuxNode; + if (runner == SocketHolder::none()) + { + HLOGC(gslog.Debug, log << "workerSendOrder: socket @" << id << " was unbound while in packData - not rescheduling"); -srt::CRcvUList::~CRcvUList() {} + // NOTE: we don't even remove it from m_SendOrderList because if it had NULL node, + // it was removed from the multiplexer, so from the send order list, too. + continue; + } -void srt::CRcvUList::insert(const CUDT* u) -{ - CRNode* n = u->m_pRNode; - n->m_tsTimeStamp = steady_clock::now(); + // Check if extracted anything to send + if (res == false) + { + HLOGC(qslog.Debug, log << "packData: nothing to send, WITHDRAWING sender"); + m_SendOrderList.remove(runner); // [TSA] IDEM + continue; + } - if (NULL == m_pUList) - { - // empty list, insert as the single node - n->m_pPrev = n->m_pNext = NULL; - m_pLast = m_pUList = n; + target_addr = u.m_PeerAddr; + if (!is_zero(next_send_time)) + { + m_SendOrderList.requeue(runner, next_send_time); // [TSA] IDEM + IF_HEAVY_LOGGING(sync::steady_clock::time_point now = sync::steady_clock::now()); + HLOGC(qslog.Debug, log << "SND updated to " << FormatTime(next_send_time) + << " (now" << fmt(count_microseconds(next_send_time - now), showpos) << "us)"); + } + else + { + m_SendOrderList.remove(runner); // [TSA] IDEM + } + } - return; + HLOGC(qslog.Debug, log << CONID() << "chn:SENDING: " << sndpkt.pkt.Info()); + m_pChannel->sendto(target_addr, sndpkt.pkt, source_addr); + // NOTE: Destructor of CSndPacket will release this packet's seqno + // from CSndBuffer and will try to remove packets from this one up to ACK + // if any are still present. } - // always insert at the end for RcvUList - n->m_pPrev = m_pLast; - n->m_pNext = NULL; - m_pLast->m_pNext = n; - m_pLast = n; + THREAD_EXIT(); } -void srt::CRcvUList::remove(const CUDT* u) -{ - CRNode* n = u->m_pRNode; +// This is to satisfy the requirement of HeapSet class. +// The values kept in HeapSet must be capable of a trap representation +// to be returned from none(). Here it's returned as empty_list.end(). +SocketHolder::socklist_t SocketHolder::empty_list; - if (!n->m_bOnList) - return; +void CMultiplexer::removeRID(const SRTSOCKET& id) +{ + ScopedLock lkv(m_SocketsLock); - if (NULL == n->m_pPrev) - { - // n is the first node - m_pUList = n->m_pNext; - if (NULL == m_pUList) - m_pLast = NULL; - else - m_pUList->m_pPrev = NULL; - } - else + for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { - n->m_pPrev->m_pNext = n->m_pNext; - if (NULL == n->m_pNext) + if (i->m_iID == id) { - // n is the last node - m_pLast = n->m_pPrev; + expirePending(*i->m_it); + m_lRendezvousID.erase(i); + return; } - else - n->m_pNext->m_pPrev = n->m_pPrev; } - n->m_pNext = n->m_pPrev = NULL; + // In case it's not found in RID, simply mark it BROKEN in the muxer + sockmap_t::iterator ish = m_SocketMap.find(id); + if (ish == m_SocketMap.end()) + { + LOGC(qmlog.Error, log << "removeRID: IPE: @" << id << " not found (also among subscribed)"); + return; + } + HLOGC(qmlog.Debug, log << "removeRID: @" << id << " not found in RID, but found in muxer"); + expirePending(*ish->second); } -void srt::CRcvUList::update(const CUDT* u) +void CMultiplexer::expirePending(SocketHolder& sh) { - CRNode* n = u->m_pRNode; - - if (!n->m_bOnList) - return; + // Removal from RID means that the socket is now connected. + sh.setConnectedState(); + HLOGC(qmlog.Debug, log << "expirePending: expiring SH: " << sh.report()); - n->m_tsTimeStamp = steady_clock::now(); + if (sh.peerID() != SRT_INVALID_SOCK) + m_RevPeerMap.erase(sh.peerID()); +} - // if n is the last node, do not need to change - if (NULL == n->m_pNext) - return; +void SocketHolder::setConnectedState() +{ + // Withdraws the state after connecting, but whether it's + // connected or broken, it must be checked in the flags + m_tsRequestTTL = sync::steady_clock::time_point(); - if (NULL == n->m_pPrev) + if (!m_pSocket) { - m_pUList = n->m_pNext; - m_pUList->m_pPrev = NULL; + m_State = BROKEN; } else { - n->m_pPrev->m_pNext = n->m_pNext; - n->m_pNext->m_pPrev = n->m_pPrev; - } - - n->m_pPrev = m_pLast; - n->m_pNext = NULL; - m_pLast->m_pNext = n; - m_pLast = n; -} - -// -srt::CHash::CHash() - : m_pBucket(NULL) - , m_iHashSize(0) -{ -} + CUDT& u = m_pSocket->core(); -srt::CHash::~CHash() -{ - for (int i = 0; i < m_iHashSize; ++i) - { - CBucket* b = m_pBucket[i]; - while (NULL != b) + if (u.stillConnected()) + { + m_State = ACTIVE; + } + else { - CBucket* n = b->m_pNext; - delete b; - b = n; + m_State = BROKEN; } } - - delete[] m_pBucket; } -void srt::CHash::init(int size) +CUDT* CMultiplexer::retrieveRID(const sockaddr_any& addr, SRTSOCKET id) const { - m_pBucket = new CBucket*[size]; - - for (int i = 0; i < size; ++i) - m_pBucket[i] = NULL; + ScopedLock vg(m_SocketsLock); - m_iHashSize = size; -} - -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]; - - while (NULL != b) - { - if (id == b->m_iID) - return b->m_pUDT; - b = b->m_pNext; - } - - return NULL; -} - -void srt::CHash::insert(int32_t id, CUDT* u) -{ - CBucket* b = m_pBucket[id % m_iHashSize]; - - CBucket* n = new CBucket; - n->m_iID = id; - n->m_pUDT = u; - n->m_pNext = b; - - m_pBucket[id % m_iHashSize] = n; -} - -void srt::CHash::remove(int32_t id) -{ - CBucket* b = m_pBucket[id % m_iHashSize]; - CBucket* p = NULL; - - while (NULL != b) - { - if (id == b->m_iID) - { - if (NULL == p) - m_pBucket[id % m_iHashSize] = b->m_pNext; - else - p->m_pNext = b->m_pNext; - - delete b; - - return; - } - - p = b; - b = b->m_pNext; - } -} - -// -srt::CRendezvousQueue::CRendezvousQueue() - : m_lRendezvousID() - , m_RIDListLock() -{ -} - -srt::CRendezvousQueue::~CRendezvousQueue() -{ - m_lRendezvousID.clear(); -} - -void srt::CRendezvousQueue::insert(const SRTSOCKET& id, - CUDT* u, - const sockaddr_any& addr, - const steady_clock::time_point& ttl) -{ - ScopedLock vg(m_RIDListLock); - - CRL r; - 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 srt::CRendezvousQueue::remove(const SRTSOCKET& id) -{ - ScopedLock lkv(m_RIDListLock); - - for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) - { - if (i->m_iID == id) - { - m_lRendezvousID.erase(i); - break; - } - } -} - -srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& w_id) const -{ - ScopedLock vg(m_RIDListLock); - - IF_HEAVY_LOGGING(const char* const id_type = w_id ? "THIS ID" : "A NEW CONNECTION"); + IF_HEAVY_LOGGING(const char* const id_type = id == SRT_SOCKID_CONNREQ ? "A NEW CONNECTION" : "THIS ID" ); // TODO: optimize search for (list::const_iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { - if (i->m_PeerAddr == addr && ((w_id == 0) || (w_id == i->m_iID))) + if (i->m_PeerAddr == addr && ((id == SRT_SOCKID_CONNREQ) || (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: + // As the "rendezvous queue" is used for handling rendezvous and + // the caller sockets, 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. @@ -880,7 +911,7 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& // 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) + if (id == SRT_SOCKID_CONNREQ && !i->m_pUDT->m_config.bRendezvous) { HLOGC(cnlog.Debug, log << "RID: found id @" << i->m_iID << " while looking for " @@ -892,17 +923,16 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& 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 +#if HVU_ENABLE_HEAVY_LOGGING std::ostringstream spec; - if (w_id == 0) + if (id == SRT_SOCKID_CONNREQ) spec << "A NEW CONNECTION REQUEST"; else - spec << " AGENT @" << w_id; + spec << " AGENT @" << id; HLOGC(cnlog.Debug, log << "RID: NO CONNECTOR FOR ADR:" << addr.str() << " while looking for " << spec.str() << " (" << m_lRendezvousID.size() << " connectors total)"); @@ -911,19 +941,16 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& return NULL; } -void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit) +void CRcvQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst, const CPacket* pkt) { vector toRemove, toProcess; - - const CPacket* pkt = unit ? &unit->m_Packet : NULL; - // 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->id() : 0; + const SRTSOCKET dest_id = pkt ? pkt->id() : SRT_SOCKID_CONNREQ; // 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))) + if (!m_parent->qualifyToHandleRID(rst, cst, dest_id, (toRemove), (toProcess))) return; HLOGC(cnlog.Debug, @@ -950,17 +977,10 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst EReadStatus read_st = rst; EConnectStatus conn_st = cst; - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id); - if (!sk.socket) - { - // Socket deleted already, so stop this and proceed to the next loop. - LOGC(cnlog.Error, log << "updateConnStatus: IPE: socket @" << i->id << " already closed, proceed to only removal from lists"); - toRemove.push_back(*i); - continue; - } - + // NOTE: A socket that is broken and on the way for deletion shall + // be at first removed from the queue dependencies and not present here. - if (cst != CONN_RENDEZVOUS && dest_id != 0) + if (cst != CONN_RENDEZVOUS && dest_id != SRT_SOCKID_CONNREQ) { if (i->id != dest_id) { @@ -992,7 +1012,8 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst LinkStatusInfo fi = *i; fi.errorcode = SRT_ECONNREJ; toRemove.push_back(fi); - i->u->sendCtrl(UMSG_SHUTDOWN); + uint32_t res[1] = {SRT_CLS_DEADLSN}; + i->u->sendCtrl(UMSG_SHUTDOWN, NULL, res, sizeof res); } } @@ -1005,16 +1026,6 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst for (vector::iterator i = toRemove.begin(); i != toRemove.end(); ++i) { HLOGC(cnlog.Debug, log << "updateConnStatus: COMPLETING dep objects update on failed @" << i->id); - remove(i->id); - - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id); - if (!sk.socket) - { - // This actually shall never happen, so it's a kind of paranoid check. - LOGC(cnlog.Error, log << "updateConnStatus: IPE: socket @" << i->id << " already closed, NOT ACCESSING its contents"); - continue; - } - // 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. @@ -1039,31 +1050,37 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst i->u->completeBrokenConnectionDependencies(i->errorcode); } + m_parent->resetExpiredRID(toRemove); +} + +void CMultiplexer::resetExpiredRID(const std::vector& toRemove) +{ + // Now, additionally for every failed link reset the TTL so that + // they are set expired right now. + ScopedLock vg(m_SocketsLock); + for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { - // 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) + if (find_if(toRemove.begin(), toRemove.end(), LinkStatusInfo::HasID(i->m_iID)) != toRemove.end()) { - 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 - } + 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, +// Must be defined here due to implementation dependency +SRTSOCKET SocketHolder::id() const { return m_pSocket->core().id(); } +SRTSOCKET SocketHolder::peerID() const { return m_pSocket->core().peerID(); } +sockaddr_any SocketHolder::peerAddr() const { return m_pSocket->core().peerAddr(); } + +bool CMultiplexer::qualifyToHandleRID(EReadStatus rst, EConnectStatus cst SRT_ATR_UNUSED, - int iDstSockID, + SRTSOCKET iDstSockID, vector& toRemove, vector& toProcess) { - ScopedLock vg(m_RIDListLock); + ScopedLock vg(m_SocketsLock); if (m_lRendezvousID.empty()) return false; // nothing to process. @@ -1111,6 +1128,7 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, // Collect in 'toRemove' to update later. LinkStatusInfo fi = {i->m_pUDT, i->m_iID, ccerror, i->m_PeerAddr, -1}; toRemove.push_back(fi); + expirePending(*i->m_it); // i_next was preincremented, but this is guaranteed to point to // the element next to erased one. @@ -1120,8 +1138,8 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, else { 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)..."); + log << "RID: socket @" << i->m_iID << " still active (remaining " + << fmt(count_microseconds(i->m_tsTTL - tsNow) / 1000000.0, fixed) << "s of TTL)..."); } const steady_clock::time_point tsLastReq = i->m_pUDT->m_tsLastReqTime; @@ -1134,7 +1152,7 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, if ((rst == RST_AGAIN || i->m_iID != iDstSockID) && tsNow <= tsRepeat) { HLOGC(cnlog.Debug, - log << "RID:@" << i->m_iID << " " << FormatDuration(tsNow - tsLastReq) + log << "RID:@" << i->m_iID << " " << FormatDurationAuto(tsNow - tsLastReq) << " passed since last connection request."); continue; @@ -1143,52 +1161,141 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, 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"); - } + // 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); } return !toRemove.empty() || !toProcess.empty(); } +void CMultiplexer::configure(int32_t id, const CSrtConfig& config, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock) +{ + m_mcfg = config; + m_iID = id; + + // XXX Leaving as dynamic due to a potential for abstracting out the channel class. + m_pChannel = new CChannel(); + m_pChannel->setConfig(m_mcfg); + + if (udpsock) + { + // In this case, reqaddr contains the address + // that has been extracted already from the + // given socket + m_pChannel->attach(*udpsock, reqaddr); + } + else if (reqaddr.empty()) + { + // If reqaddr was set as empty, only with set family, + // just automatically bind to the "0" address to autoselect + // everything. + 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_pChannel->open(reqaddr); + } + + // After the system binding the 0 port could be reassigned by the + // system-selected port; extract it. + m_SelfAddr = m_pChannel->getSockAddr(); + + // 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_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_mcfg.iIpV6Only = m_pChannel->sockopt(IPPROTO_IPV6, IPV6_V6ONLY, -1); + } + + m_SndQueue.init(m_pChannel); + + // We can't use maxPayloadSize() because this value isn't valid until the connection is established. + // We need to "think big", that is, allocate a size that would fit both IPv4 and IPv6. + const size_t payload_size = config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(AF_INET); + + // XXX m_pHash hash size passed HERE! + // (Likely here configure the hash table for m_Sockets). + HLOGC(smlog.Debug, log << "@" << id << ": configureMuxer: config rcv queue qsize=" << 128 + << " plsize=" << payload_size << " hsize=" << 1024); + m_RcvQueue.init(SRT_RCV_BUFFER_POOL_SERIES_SIZE, payload_size, m_pChannel); +} + +void CMultiplexer::removeSender(CUDT* u) +{ + ScopedLock slk (m_SocketsLock); + + SocketHolder::sockiter_t pos = u->m_MuxNode; + if (pos == SocketHolder::none()) + return; + + // will be re-added, if there's an API sending function called. + m_SndQueue.m_SendOrderList.remove(pos); // [TSA] IDEM +} + // -srt::CRcvQueue::CRcvQueue() - : m_WorkerThread() - , m_pUnitQueue(NULL) - , m_pRcvUList(NULL) - , m_pHash(NULL) - , m_pChannel(NULL) - , m_pTimer(NULL) - , m_iIPversion() - , m_szPayloadSize() - , m_bClosing(false) - , m_pRendezvousQueue(NULL) - , m_vNewEntry() - , m_IDLock() - , m_mBuffer() - , m_BufferCond() +CRcvQueue::CRcvQueue(CMultiplexer* parent): + m_parent(parent), + m_WorkerThread(), +#if USE_RECEIVER_UNIT_POOL + m_pUnitPool(), +#else + m_pUnitQueue(NULL), +#endif + m_pChannel(NULL), + m_zPayloadSize(), + m_bClosing(false), + m_mBuffer(), + m_BufferCond() { setupCond(m_BufferCond, "QueueBuffer"); } -srt::CRcvQueue::~CRcvQueue() +void CRcvQueue::stop() +{ + m_bClosing = true; + + // It is allowed that the queue stops itself. It should just not try + // to join itself. + if (sync::this_thread_is(m_WorkerThread)) + { + LOGC(rslog.Error, log << "RcvQueue: IPE: STOP REQUEST called from within worker thread - NOT EXITING."); + return; + } + + if (m_WorkerThread.joinable()) + { + HLOGC(rslog.Debug, log << "RcvQueue: EXITing thread..."); + m_WorkerThread.join(); + } + releaseCond(m_BufferCond); + + HLOGC(rslog.Debug, log << "RcvQueue: STOPPED."); +} + +CRcvQueue::~CRcvQueue() { + stop(); +#if USE_RECEIVER_UNIT_POOL + // Nothing to delete here. UniquePtr should do. +#else + + HLOGC(qrlog.Debug, log << "CPacketUnitPool: DELETING series of " << m_pUnitQueue->m_HandSeries.size() << " packets"); delete m_pUnitQueue; - delete m_pRcvUList; - delete m_pHash; - delete m_pRendezvousQueue; +#endif // remove all queued messages - for (map >::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++i) + for (qmap_t::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++i) { while (!i->second.empty()) { @@ -1204,110 +1311,109 @@ void srt::CRcvQueue::resetAtFork() resetThread(&m_WorkerThread); } -void srt::CRcvQueue::stop() +#if HVU_ENABLE_LOGGING +sync::atomic CRcvQueue::m_counter(0); +#endif + + +#if USE_RECEIVER_UNIT_POOL + +void CRcvQueue::retrieveUnit_raw(CPacketUnitPool::UnitPtr& to) { - m_bClosing = true; + m_pUnitPool->hand_pull_raw((to)); + HLOGC(qrlog.Debug, log << "CRcvQueue: unit retrieved; in-hand remaining: " << m_pUnitPool->hand_size()); +} - if (m_WorkerThread.joinable()) - { - HLOGC(rslog.Debug, log << "RcvQueue: EXIT"); - m_WorkerThread.join(); - } - releaseCond(m_BufferCond); +bool CRcvQueue::retrieveUnit(CPacketUnitPool::UnitPtr& to) +{ + bool ret = m_pUnitPool->hand_pull((to)); + HLOGC(qrlog.Debug, log << "CRcvQueue: unit " + << (ret ? "" : "NOT ") + << "retrieved; in-hand remaining: " << m_pUnitPool->hand_size()); + return ret; } +CPacketUnitPool::Unit* CRcvQueue::viewUnit() +{ + HLOGC(qrlog.Debug, log << "CRcvQueue: unit view requested"); + if (!m_pUnitPool->hand_refill()) + { + HLOGC(qrlog.Debug, log << "CRcvQueue: UNIT NOT OBTAINED. Hand: " << m_pUnitPool->hand_size() + << " Solid: " << m_pUnitPool->solid_size() << " Condenser: " << m_pUnitPool->condenser_size()); + return NULL; + } -#if ENABLE_LOGGING -srt::sync::atomic srt::CRcvQueue::m_counter(0); + // If refilling was working, this should return true. + CPacketUnitPool::Unit* ret = m_pUnitPool->hand_peek(); + SRT_ASSERT(ret); + return ret; +} #endif -void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CChannel* cc, CTimer* t) +void CRcvQueue::init(int series_size, size_t payload, CChannel* cc) { - m_iIPversion = version; - m_szPayloadSize = payload; + m_zPayloadSize = payload; - SRT_ASSERT(m_pUnitQueue == NULL); - m_pUnitQueue = new CUnitQueue(qsize, (int)payload); +#if USE_RECEIVER_UNIT_POOL - m_pHash = new CHash; - m_pHash->init(hsize); + // Normally this 128 is used here, but 32 would be a bit better + // as a single-shot resolution. + m_pUnitPool.reset(new CPacketUnitPool(series_size, payload)); - m_pChannel = cc; - m_pTimer = t; + // XXX This is initial, so should work, but formally the exit + // code should be checked. + m_pUnitPool->hand_refill(); + +#else + SRT_ASSERT(m_pUnitQueue == NULL); + m_pUnitQueue = new CUnitQueue(series_size, (int)payload); +#endif - m_pRcvUList = new CRcvUList; - m_pRendezvousQueue = new CRendezvousQueue; + m_pChannel = cc; -#if ENABLE_LOGGING +#if HVU_ENABLE_LOGGING const int cnt = ++m_counter; - const std::string thrname = "SRT:RcvQ:w" + Sprint(cnt); + const string thrname = fmtcat("SRT:RcvQ:w", cnt); #else - const std::string thrname = "SRT:RcvQ:w"; + const string thrname = "SRT:RcvQ:w"; #endif - if (!StartThread(m_WorkerThread, CRcvQueue::worker, this, thrname.c_str())) + if (!StartThread((m_WorkerThread), CRcvQueue::worker_fwd, this, thrname.c_str())) { throw CUDTException(MJ_SYSTEMRES, MN_THREAD); } } -void* srt::CRcvQueue::worker(void* param) +void* CRcvQueue::worker_fwd(void* param) { CRcvQueue* self = (CRcvQueue*)param; - sockaddr_any sa(self->getIPversion()); - int32_t id = 0; + self->worker(); + return NULL; +} - std::string thname; +void CRcvQueue::worker() +{ + string thname; ThreadName::get(thname); THREAD_STATE_INIT(thname.c_str()); - CUnit* unit = 0; - EConnectStatus cst = CONN_AGAIN; - while (!self->m_bClosing) + while (!m_bClosing) { - bool have_received = false; - EReadStatus rst = self->worker_RetrieveUnit((id), (unit), (sa)); - + // NOTE: `pkt` points to a packet inside a unit that was used to read the packet. + // It's provided (not NULL) only if it was read and it was a control packet. + const CPacket* pkt = NULL; + EConnectStatus cst = CONN_AGAIN; + SRTSOCKET id = SRT_SOCKID_CONNREQ; + EReadStatus rst = worker_RetrieveAndProcessUnit((cst), (pkt), (id)); 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(qrlog.Debug, - log << self->CONID() << "RECEIVED negative socket id '" << id - << "', rejecting (POSSIBLE ATTACK)"); - continue; - } - - // NOTE: cst state is being changed here. - // This state should be maintained through any next failed calls to worker_RetrieveUnit. - // Any error switches this to rejection, just for a case. - - // Note to rendezvous connection. This can accept: - // - ID == 0 - take the first waiting rendezvous socket - // - ID > 0 - find the rendezvous socket that has this ID. - 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); - } - 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); - // CAN RETURN CONN_REJECT, but m_RejectReason is already set - } - HLOGC(qrlog.Debug, log << self->CONID() << "worker: result for the unit: " << ConnectStatusStr(cst)); + // CAN RETURN CONN_REJECT, but m_RejectReason is already set + HLOGC(qrlog.Debug, log << CONID() << "worker: result for the unit: " << ConnectStatusStr(cst)); if (cst == CONN_AGAIN) { - HLOGC(qrlog.Debug, log << self->CONID() << "worker: packet not dispatched, continuing reading."); continue; } - have_received = true; } else if (rst == RST_ERROR) { @@ -1315,56 +1421,36 @@ void* srt::CRcvQueue::worker(void* param) // - IPE: all errors except EBADF // - socket was closed in the meantime by another thread: EBADF // If EBADF, then it's expected that the "closing" state is also set. - // Check that just to report possible errors, but interrupt the loop anyway. - if (self->m_bClosing) + if (m_bClosing) { HLOGC(qrlog.Debug, - log << self->CONID() << "CChannel reported error, but Queue is closing - INTERRUPTING worker."); + log << CONID() << "CChannel reported error, but Queue is closing - INTERRUPTING worker."); + break; } else { LOGC(qrlog.Fatal, - log << self->CONID() - << "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway."); + log << CONID() + << "CChannel reported ERROR DURING TRANSMISSION - IPE. NOT INTERRUPTING the worker until it's explicitly closed."); + + // Issue #3185, blocking: -- break; + // "break" should never be used because it causes worker thread to exit, + // while this shall never be done, unless the multiplexer is broken and requested to exit. } cst = CONN_REJECT; - break; } - // OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue. + // OTHERWISE: RST_AGAIN. No data was read, but the process should continue. // take care of the timing event for all UDT sockets 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; - while ((NULL != ul) && (ul->m_tsTimeStamp < curtime_minus_syn)) - { - CUDT* u = ul->m_pUDT; - - if (u->m_bConnected && !u->m_bBroken && !u->m_bClosing) - { - u->checkTimers(); - self->m_pRcvUList->update(u); - } - else - { - 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); - self->m_pRcvUList->remove(u); - u->m_pRNode->m_bOnList = false; - } + m_parent->rollUpdateSockets(curtime_minus_syn); - ul = self->m_pRcvUList->m_pUList; - } + IF_HEAVY_LOGGING(const char* rstname[3] = { "ERROR", "OK", "AGAIN" }); - if (have_received) - { - HLOGC(qrlog.Debug, - log << "worker: RECEIVED PACKET --> updateConnStatus. cst=" << ConnectStatusStr(cst) << " id=" << id - << " pkt-payload-size=" << unit->m_Packet.getLength()); - } + HLOGC(qrlog.Debug, log << "worker: READ STATUS: " << rstname[rst+1] + << " --> updateConnStatus. cst=" << ConnectStatusStr(cst) << " id=" << id); // Check connection requests status for all sockets in the RendezvousQueue. // Pass the connection status from the last call of: @@ -1372,7 +1458,10 @@ void* srt::CRcvQueue::worker(void* param) // worker_TryAsyncRend_OrStore ---> // CUDT::processAsyncConnectResponse ---> // CUDT::processConnectResponse - self->m_pRendezvousQueue->updateConnStatus(rst, cst, unit); + // + // NOTE: CONN_REJECT may be entering here, but it will be treated like CONN_AGAIN. + + updateConnStatus(rst, cst, pkt); // XXX updateConnStatus may have removed the connector from the list, // however there's still m_mBuffer in CRcvQueue for that socket to care about. @@ -1381,66 +1470,207 @@ void* srt::CRcvQueue::worker(void* param) HLOGC(qrlog.Debug, log << "worker: EXIT"); THREAD_EXIT(); - return NULL; } -srt::EReadStatus srt::CRcvQueue::worker_RetrieveUnit(int32_t& w_id, CUnit*& w_unit, sockaddr_any& w_addr) +EReadStatus CRcvQueue::worker_DropIncomingPacket(sockaddr_any& w_addr) { -#if !USE_BUSY_WAITING - // This might be not really necessary, and probably - // not good for extensive bidirectional communication. - m_pTimer->tick(); -#endif + CPacket temp; + temp.allocate(m_zPayloadSize); + THREAD_PAUSED(); + EReadStatus rst = m_pChannel->recvfrom((w_addr), (temp)); + THREAD_RESUMED(); + // 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; +} - // check waiting list, if new socket, insert it to the list - while (ifNewEntry()) +// Possible return values: +// - CONN_CONTINUE: the socket is acquired, you can continue passing the packet to it. +// - any other: this is an error, socket NOT acquired +// must be static due to dependencies that can't be exposed to the interface +static EConnectStatus rcv_AcquireTargetSocket(CMultiplexer* parent, SRTSOCKET id, const sockaddr_any& addr, CUDTUnited::SocketKeeper& w_sk, SocketHolder::State& w_hstate) +{ + w_hstate = SocketHolder::INIT; + CUDTSocket* s = parent->findAgent(id, addr, (w_hstate), parent->ACQ_ACQUIRE); + if (!s) { - CUDT* ne = getNewEntry(); - if (ne) - { - HLOGC(qrlog.Debug, - log << CUDTUnited::CONID(ne->m_SocketID) - << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP"); - m_pRcvUList->insert(ne); - m_pHash->insert(ne->m_SocketID, ne); - } + HLOGC(cnlog.Debug, + log << CUDTUnited::CONID(id) << "worker_ProcessAddressedPacket: socket @" + << id << " not found as expecting packet from " << addr.str() + << " - POSSIBLE ATTACK, ignore packet"); + return CONN_AGAIN; // This means that the packet should be ignored. } - // find next available slot for incoming packet - w_unit = m_pUnitQueue->getNextAvailUnit(); - if (!w_unit) + + // Test pending before checking for connected. + if (w_hstate == SocketHolder::PENDING) { - // no space, skip this packet - CPacket temp; - temp.allocate(m_szPayloadSize); - THREAD_PAUSED(); - EReadStatus rst = m_pChannel->recvfrom((w_addr), (temp)); - THREAD_RESUMED(); - // 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()); + w_sk.socket = s; + return CONN_CONTINUE; + } + + // Although we don´t have an exclusive passing here, + // we can count on that when the socket was once present in the hash, + // it will not be deleted for at least one GC cycle. But we still need + // to maintain the object existence as long as it's in use. + // Note that here we are out of any locks, so m_GlobControlLock can be locked. + + CUDT& u = s->core(); + if (!u.stillConnected()) + { + // Socket will be released in this block. + CUDTUnited::SocketKeeper sk; + sk.socket = s; // Acquired by findAgent() call + u.updateRejectReason(SRT_REJ_CLOSE); + + HLOGC(cnlog.Debug, log << "worker_ProcessAddressedPacket: target @" + << id << " is being closed, rejecting"); + // The socket is currently in the process of being disconnected + // or destroyed. Ignore. + // XXX send UMSG_SHUTDOWN in this case? + // XXX May it require mutex protection? + return CONN_REJECT; + } + + w_sk.socket = s; + return CONN_CONTINUE; +} - // 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; +EReadStatus CRcvQueue::worker_RetrieveAndProcessUnit(EConnectStatus& w_cst, const CPacket*& w_pkt, SRTSOCKET& w_id) +{ + w_pkt = NULL; + + sockaddr_any sa(m_parent->selfAddr().family()); +#if USE_RECEIVER_UNIT_POOL + CPacketUnitPool::Unit* unit = viewUnit(); +#else + CUnit* unit = m_pUnitQueue->getNextAvailUnit(); +#endif + if (!unit) + { + // no space, skip this packet + return worker_DropIncomingPacket((sa)); } - w_unit->m_Packet.setLength(m_szPayloadSize); + unit->m_Packet.setLength(m_zPayloadSize); // reading next incoming packet, recvfrom returns -1 is nothing has been received THREAD_PAUSED(); - EReadStatus rst = m_pChannel->recvfrom((w_addr), (w_unit->m_Packet)); + EReadStatus rst = m_pChannel->recvfrom((sa), (unit->m_Packet)); THREAD_RESUMED(); - if (rst == RST_OK) + if (rst != RST_OK) + return rst; + + w_id = unit->m_Packet.id(); + HLOGC(qrlog.Debug, + log << "INCOMING PACKET: FROM=" << sa.str() << " BOUND=" << m_pChannel->bindAddressAny().str() << " " + << unit->m_Packet.Info()); + + // Here we don't have to pass the unit to the function because + // the Unit Pool is exclusive for this thread and it has been ensured + // that the local unit series contains at least one packet and the last + // unit in the series is the one that was rewritten. + + if (int(w_id) < 0) // Any negative (illegal range) and SRT_INVALID_SOCK { - w_id = w_unit->m_Packet.id(); + // User error on peer. May log something, but generally can only ignore it. + // XXX Think maybe about sending some "connection rejection response". HLOGC(qrlog.Debug, - log << "INCOMING PACKET: FROM=" << w_addr.str() << " BOUND=" << m_pChannel->bindAddressAny().str() << " " - << w_unit->m_Packet.Info()); + log << CONID() << "RECEIVED negative socket w_id '" << w_id + << "', rejecting (POSSIBLE ATTACK)"); + w_cst = CONN_AGAIN; + return rst; + } + + // Can be later reset to NULL in case of a data packet. + w_pkt = &unit->m_Packet; + + // Note to rendezvous connection. This can accept: + // - ID == 0 - take the first waiting rendezvous socket that matches the address + // - ID > 0 - find the rendezvous socket that has this ID. + if (w_id == SRT_SOCKID_CONNREQ) + { + // ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets + // NOTE: packet can be rewritten so that it is reused for sending the response. + w_cst = worker_ProcessConnectionRequest( (unit->m_Packet), sa); + return rst; + } + + // Otherwise ID is expected to be associated with: + // - an enqueued rendezvous socket + // - a socket connected to a peer + CUDTUnited::SocketKeeper sk; + SocketHolder::State hstate; + w_cst = rcv_AcquireTargetSocket(m_parent, w_id, sa, (sk), (hstate)); + if (w_cst == CONN_CONTINUE) + { + CUDT* u = &sk.socket->core(); + if (hstate == SocketHolder::PENDING) + { + HLOGC(cnlog.Debug, log << "worker: resending to PENDING socket @" << w_id); + w_cst = worker_RetryOrRendezvous(u, unit->m_Packet); + return rst; + } + + HLOGC(cnlog.Debug, log << "Dispatching a " << (unit->m_Packet.isControl() ? "CONTROL MESSAGE" : "DATA PACKET") + << " to @" << w_id); + + if (unit->m_Packet.isControl()) + { + // The unit is processed in place and the packet buffer is still + // in the local series pool. + u->processCtrl(unit->m_Packet); + } + else + { + // NOTE: Data packet is swallowed by the receiver buffer and shall + // not be referred to from here anymore. + w_pkt = NULL; + +#if USE_RECEIVER_UNIT_POOL + // We need to pop off the packet first, as *this will be + // potentially used to extract additional units from. Might be, + // this unit will not be acquired, and in this case this function + // should return it to *this. + CPacketUnitPool::UnitPtr passunit; + + // `passunit` will be same as `unit`, just owned this time. + // Existence ensured through viewUnit() so no need to check refill + retrieveUnit_raw((passunit)); + u->acquireDataPacket((passunit), this); // passunit might be acquired + if (passunit) // did not acquire + returnUnit((passunit)); +#else + u->processData(unit, this); +#endif + } + + if (u->m_bBroken || u->m_bClosing) + { + // If these flags are set, the socket is no longer eligible for any + // updates, and they no longer are consistent as "former" group members. + + return RST_OK; // because we did handle the packet. + } + + HLOGC(cnlog.Debug, log << "POST-DISPATCH update for @" << w_id); + u->checkTimers(); + + // XXX Optimize it better + // We dispatch here `w_id` again because we can't keep the SocketEntry locked. + // Check, however, if keeping the socket through SocketKeeper can ensure that, + // so that this dispatching can be avoided. + m_parent->updateUpdateOrder(w_id, sync::steady_clock::now()); } + + // w_cst CAN BE CONN_REJECT, but m_RejectReason is already set return rst; } -srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr_any& addr) +EConnectStatus CRcvQueue::worker_ProcessConnectionRequest(CPacket& packet, const sockaddr_any& addr) { HLOGC(cnlog.Debug, log << "Got sockID=0 from " << addr.str() << " - trying to resolve it as a connection request..."); @@ -1457,7 +1687,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, if (pListener) { LOGC(cnlog.Debug, log << "PASSING request from: " << addr.str() << " to listener:" << pListener->socketID()); - listener_ret = pListener->processConnectRequest(addr, unit->m_Packet); + listener_ret = pListener->processConnectRequest(addr, packet); // This function does return a code, but it's hard to say as to whether // anything can be done about it. In case when it's stated possible, the @@ -1481,376 +1711,692 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, return listener_ret == SRT_REJ_UNKNOWN ? CONN_CONTINUE : CONN_REJECT; } - // If there's no listener waiting for the packet, just store it into the queue. - return worker_TryAsyncRend_OrStore(0, unit, addr); // 0 id because the packet came in with that very ID. -} - -srt::EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& addr) -{ - CUDT* u = m_pHash->lookup(id); - if (!u) + if (worker_TryAcceptedSocket(packet, addr)) { - // Pass this to either async rendezvous connection, - // or store the packet in the queue. - HLOGC(cnlog.Debug, log << "worker_ProcessAddressedPacket: resending to QUEUED socket @" << id); - return worker_TryAsyncRend_OrStore(id, unit, addr); + HLOGC(cnlog.Debug, log << "connection request to an accepted socket succeeded"); + return CONN_CONTINUE; + } + else + { + HLOGC(cnlog.Debug, log << "connection request to an accepted socket failed. Will retry RDV or store"); } - // Although we don´t have an exclusive passing here, - // we can count on that when the socket was once present in the hash, - // it will not be deleted for at least one GC cycle. But we still need - // to maintain the object existence until it's in use. - // Note that here we are out of any locks, so m_GlobControlLock can be locked. - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), u->m_parent); - // Found associated CUDT - process this as control or data packet - // addressed to an associated socket. - if (addr != u->m_PeerAddr) + // If there is no listener waiting for that packet, try a rendezvous socket + // for the incoming address. This is then regardless if the peer knows the + // proper ID or not. Anyway, if the proper ID was supplied, it would be handled + // earlier by retrievePending called from worker_ProcessAddressedPacket. + CUDT* u = m_parent->retrieveRID(addr, SRT_SOCKID_CONNREQ); + if (!u) { - 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. + HLOGC(cnlog.Debug, log << CONID() + << "worker_ProcessConnectionRequest: no sockets expect connection from " << addr.str() + << " - POSSIBLE ATTACK, ignore packet"); return CONN_AGAIN; } - if (!u->m_bConnected || u->m_bBroken || u->m_bClosing) - { - u->m_RejectReason = SRT_REJ_CLOSE; - // The socket is currently in the process of being disconnected - // or destroyed. Ignore. - // XXX send UMSG_SHUTDOWN in this case? - // XXX May it require mutex protection? + return worker_RetryOrRendezvous(u, packet); +} + +bool CRcvQueue::worker_TryAcceptedSocket(const CPacket& pkt, const sockaddr_any& addr) +{ + // We are working with a possibly HS packet... check that. + if (pkt.getLength() < CHandShake::m_iContentSize || !pkt.isControl(UMSG_HANDSHAKE)) + return false; + + CHandShake hs; + if (0 != hs.load_from(pkt.data(), pkt.size())) + return false; + + if (hs.m_iReqType != URQ_CONCLUSION) + return false; + + if (hs.m_iVersion >= CUDT::HS_VERSION_SRT1) + hs.m_extensionType = SRT_CMD_HSRSP; + + // Ok, at last we have a peer ID info + SRTSOCKET peerid = hs.m_iID; + + // Now search for a socket that has this peer ID + CUDTSocket* s = m_parent->findPeer(peerid, addr, m_parent->ACQ_ACQUIRE); + if (!s) + { + HLOGC(cnlog.Debug, log << "worker_TryAcceptedSocket: can't find accepted socket for peer -@" << peerid + << " and address: " << addr.str() << " - POSSIBLE ATTACK, rejecting"); + return false; + } + + // Acquired in findPeer, so this can be now kept without acquiring m_GlobControlLock. + CUDTUnited::SocketKeeper keep; + keep.socket = s; + + CUDT* u = &s->core(); + if (u->m_bBroken || u->m_bClosing) + { + return false; + } + + HLOGC(cnlog.Debug, log << "FOUND accepted socket @" << u->m_SocketID << " that is a peer for -@" + << peerid << " - DISPATCHING to it to resend HS response"); + + uint32_t kmdata[SRTDATA_MAXSIZE]; + size_t kmdatasize = SRTDATA_MAXSIZE; + if (u->craftKmResponse((kmdata), (kmdatasize)) != CONN_ACCEPT) + { + HLOGC(cnlog.Debug, log << "craftKmResponse: failed"); + return false; + } + + return u->createSendHSResponse_WITHLOCK(kmdata, kmdatasize, pkt.udpDestAddr(), (hs)); +} + +EConnectStatus CRcvQueue::worker_RetryOrRendezvous(CUDT* u, const CPacket& packet) +{ + HLOGC(cnlog.Debug, log << "worker_RetryOrRendezvous: packet RESOLVED TO @" << u->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 + // call to this method applies the lock by itself, and same-thread-double-locking is nonportable (crashable). + EConnectStatus cst = u->processAsyncConnectResponse(packet); + if (cst != CONN_CONFUSED) + return cst; + + LOGC(cnlog.Warn, log << "worker_RetryOrRendezvous: PACKET NOT HANDSHAKE - re-requesting handshake from peer"); + storePktClone(u->id(), packet); + if (!u->processAsyncConnectRequest(RST_AGAIN, CONN_CONTINUE, &packet, u->m_PeerAddr)) + { + // Reuse previous behavior to reject a packet return CONN_REJECT; } + return CONN_CONTINUE; +} + +bool CRcvQueue::setListener(CUDT* u) +{ + return m_pListener.compare_exchange(NULL, u); +} + +CUDT* CRcvQueue::getListener() +{ + SharedLock lkl (m_pListener); + return m_pListener.get_locked(lkl); +} + +// XXX NOTE: TSan reports here false positive against the call +// to locateSocket in CUDTUnited::newConnection. This here will apply +// exclusive lock on m_pListener, while keeping shared lock on +// CUDTUnited::m_GlobControlLock in CUDTUnited::closeAllSockets. +// As the other thread locks both as shared, this is no deadlock risk. +bool CRcvQueue::removeListener(CUDT* u) +{ + bool rem = m_pListener.compare_exchange(u, NULL); + // DO NOT delete socket here. Just listener. + return rem; +} + +void CMultiplexer::registerCRL(const CRL& setup) +{ + ScopedLock vg(m_SocketsLock); + + // Check first if the alleged socket is already in the map, + // otherwise it wasn't bound. This should never happen, so + // it's more a sanity check. The check is necessary because + // the RID queue is not allowed to keep sockets that were not + // previously assigned to this multiplexer. + sockmap_t::iterator p = m_SocketMap.find(setup.m_iID); + if (p == m_SocketMap.end()) + { + LOGC(qmlog.Error, log << "registerCRL: IPE: socket @" << setup.m_iID << " not found in muxer id=" << m_iID); + return; + } + + m_lRendezvousID.push_back(setup); + std::list::iterator last = m_lRendezvousID.end(); + --last; + last->m_it = p->second; + + // Ok, the RID is only a helping map to extract incoming connection + // request, but the caller or rendezvous socket, for which this function + // is being called, needs to qualify this socket as a pending for connection. + + p->second->setConnector(setup.m_PeerAddr, setup.m_tsTTL); +} + +// DEBUG SUPPORT +string SocketHolder::report() const +{ + // XXX make it better performant in the new logging format + std::ostringstream out; + + out << "@"; + if (m_pSocket) + out << m_pSocket->core().id(); + else + out << "!!!"; - if (unit->m_Packet.isControl()) - u->processCtrl(unit->m_Packet); + out << " s=" << StateStr(m_State); + + out << " PEER: @"; + if (peerID() <= 0) + out << "NONE"; else - u->processData(unit); + out << peerID(); + + if (!m_PeerAddr.empty()) + out << " (" << m_PeerAddr.str() << ")"; + + out << " TS:"; - u->checkTimers(); - m_pRcvUList->update(u); + if (!is_zero(m_tsRequestTTL)) + out << " RQ:" << FormatTime(m_tsRequestTTL); + if (!is_zero(m_UpdateOrder.time)) + out << " UP:" << FormatTime(m_UpdateOrder.time); + if (!is_zero(m_SendOrder.time)) + out << " SN:" << FormatTime(m_SendOrder.time); - return CONN_RUNNING; + return out.str(); } -// This function responds to the fact that a packet has come -// for a socket that does not expect to receive a normal connection -// request. This can be then: -// - a normal packet of whatever kind, just to be processed by the message loop -// - a rendezvous connection -// 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. -srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& addr) +void CRcvQueue::removeConnector(const SRTSOCKET& id) { - // 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, (id)); - if (!u) + HLOGC(cnlog.Debug, log << "removeConnector: removing @" << id); + m_parent->removeRID(id); + + ScopedLock bufferlock(m_BufferLock); + + qmap_t::iterator i = m_mBuffer.find(id); + if (i != m_mBuffer.end()) { - // this socket is then completely unknown to the system. - // Note that this situation may also happen at a very unfortunate - // coincidence that the socket is already bound, but the registerConnector() - // has not yet started. In case of rendezvous this may mean that the other - // side just started sending its handshake packets, the local side has already - // run the CRcvQueue::worker thread, and this worker thread is trying to dispatch - // the handshake packet too early, before the dispatcher has a chance to see - // this socket registered 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 - // attack" and ignore it. This also should better protect the rendezvous socket - // against a rogue connector. - if (id == 0) - { - HLOGC(cnlog.Debug, - log << CONID() << "AsyncOrRND: no sockets expect connection from " << addr.str() - << " - POSSIBLE ATTACK, ignore packet"); - } - else + HLOGC(cnlog.Debug, + log << "removeConnector: ... and its packet queue with " << i->second.size() << " packets collected"); + while (!i->second.empty()) { - HLOGC(cnlog.Debug, - log << CONID() << "AsyncOrRND: no sockets expect socket " << id << " from " << addr.str() - << " - POSSIBLE ATTACK, ignore packet"); + delete i->second.front(); + i->second.pop(); } - return CONN_AGAIN; // This means that the packet should be ignored. + m_mBuffer.erase(i); } +} - // asynchronous connect: call connect here - // otherwise wait for the UDT socket to retrieve this packet - if (!u->m_config.bSynRecving) - { - 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 - // call to this method applies the lock by itself, and same-thread-double-locking is nonportable (crashable). - EConnectStatus cst = u->processAsyncConnectResponse(unit->m_Packet); +void CRcvQueue::kick() +{ + CSync::lock_notify_all(m_BufferCond, m_BufferLock); +} - if (cst == CONN_CONFUSED) - { - 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; - } - else - { - cst = CONN_CONTINUE; - } - } +void CRcvQueue::storePktClone(SRTSOCKET id, const CPacket& pkt) +{ + CUniqueSync passcond(m_BufferLock, m_BufferCond); - // It might be that this is a data packet, which has turned the connection - // into "connected" state, removed the connector (so since now every next packet - // will land directly in the queue), but this data packet shall still be delivered. - if (cst == CONN_ACCEPT && !unit->m_Packet.isControl()) - { - // The process as called through processAsyncConnectResponse() should have put the - // socket into the pending queue for pending connection (don't ask me, this is so). - // This pending queue is being purged every time in the beginning of this loop, so - // currently the socket is in the pending queue, but not yet in the connection queue. - // It will be done at the next iteration of the reading loop, but it will be too late, - // we have a pending data packet now and we must either dispatch it to an already connected - // socket or disregard it, and rather prefer the former. So do this transformation now - // that we KNOW (by the cst == CONN_ACCEPT result) that the socket should be inserted - // into the pending anteroom. - - CUDT* ne = getNewEntry(); // This function actually 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 - // this "accident" simply disregard the packet from any further processing, it will be later - // loss-recovered. - // XXX (Probably the old contents of UDT's CRcvQueue::worker should be shaped a little bit - // differently throughout the functions). - if (ne) - { - HLOGC(cnlog.Debug, - log << CUDTUnited::CONID(ne->m_SocketID) - << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP"); - m_pRcvUList->insert(ne); - m_pHash->insert(ne->m_SocketID, ne); - - // The current situation is that this has passed processAsyncConnectResponse, but actually - // this packet *SHOULD HAVE BEEN* handled by worker_ProcessAddressedPacket, however the - // connection state wasn't completed at the moment when dispatching this packet. This has - // been now completed inside the call to processAsyncConnectResponse, but this is still a - // data packet that should have expected the connection to be already established. Therefore - // redirect it once again into worker_ProcessAddressedPacket here. + qmap_t::iterator i = m_mBuffer.find(id); - HLOGC(cnlog.Debug, - log << "AsyncOrRND: packet SWITCHED TO CONNECTED with ID=" << id - << " -- passing to worker_ProcessAddressedPacket"); - - // Theoretically we should check if m_pHash->lookup(ne->m_SocketID) returns 'ne', but this - // has been just added to m_pHash, so the check would be extremely paranoid here. - cst = worker_ProcessAddressedPacket(id, unit, addr); - if (cst == CONN_REJECT) - return cst; - return CONN_ACCEPT; // this function usually will return CONN_CONTINUE, which doesn't represent current - // situation. - } - else - { - 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; + if (i == m_mBuffer.end()) + { + m_mBuffer[id].push(pkt.clone()); + passcond.notify_one(); } - 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. - storePktClone(id, unit->m_Packet); + else + { + // Avoid storing too many packets, in case of malfunction or attack. + if (i->second.size() > 16) + return; - return CONN_CONTINUE; + i->second.push(pkt.clone()); + } } -void srt::CRcvQueue::stopWorker() +bool CMultiplexer::addSocket(CUDTSocket* s) { - // We use the decent way, so we say to the thread "please exit". - m_bClosing = true; + sync::ScopedLock lk (m_SocketsLock); - // Sanity check of the function's affinity. - if (srt::sync::this_thread::get_id() == m_WorkerThread.get_id()) + // Check if socket is not added twice, just in case + sockmap_t::iterator fo = m_SocketMap.find(s->core().id()); + if (fo != m_SocketMap.end()) { - LOGC(rslog.Error, log << "IPE: RcvQ:WORKER TRIES TO CLOSE ITSELF!"); - return; // do nothing else, this would cause a hangup or crash. + LOGC(qmlog.Error, log << "IPE: attempting to add @" << s->core().m_SocketID << " TWICE (already found)"); + return false; } - HLOGC(rslog.Debug, log << "RcvQueue: EXIT (forced)"); - // And we trust the thread that it does. - m_WorkerThread.join(); + m_Sockets.push_back(SocketHolder::initial(s)); + std::list::iterator last = m_Sockets.end(); + --last; // guaranteed to be valid after push_back + m_SocketMap[s->core().m_SocketID] = last; + s->core().m_MuxNode = last; + ++m_zSockets; + HLOGC(qmlog.Debug, log << "MUXER: id=" << m_iID << " added @" << s->core().m_SocketID << " (total of " << m_zSockets.load() << " sockets)"); + +#if SRT_ENABLE_THREAD_DEBUG + last->addCondSanitizer(s->core().m_RcvTsbPdCond); +#endif + + return true; } -int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet) +bool CMultiplexer::setConnected(SRTSOCKET id) { - CUniqueSync buffercond(m_BufferLock, m_BufferCond); + if (!m_zSockets) + { + LOGC(qmlog.Error, log << "setConnected: MUXER id=" << m_iID << " no sockets while looking for @" << id); + return false; + } - map >::iterator i = m_mBuffer.find(id); + sync::ScopedLock lk (m_SocketsLock); - if (i == m_mBuffer.end()) + sockmap_t::iterator fo = m_SocketMap.find(id); + if (fo == m_SocketMap.end()) { - THREAD_PAUSED(); - buffercond.wait_for(seconds_from(1)); - THREAD_RESUMED(); + LOGC(qmlog.Error, log << "setConnected: MUXER id=" << m_iID << " NOT FOUND: @" << id); + return false; + } - i = m_mBuffer.find(id); - if (i == m_mBuffer.end()) - { - w_packet.setLength(-1); - return -1; - } + std::list::iterator point = fo->second; + SocketHolder& sh = *point; + + // XXX assert? + if (!sh.m_pSocket) + { + LOGC(qmlog.Error, log << "MUXER id=" << m_iID << " IPE: @" << id << " found, but NULL socket"); + return false; } - // retrieve the earliest packet - CPacket* newpkt = i->second.front(); + // Unknown why it might happen, so leaving just in case. + if (sh.m_pSocket->core().m_PeerID < 1) + { + LOGC(qmlog.Warn, log << "MUXER: @" << id << " has no peer set"); + return false; + } - if (w_packet.getLength() < newpkt->getLength()) + // It's hard to distinguish the origin of the call, + // and in case of caller you already have set the peer address + // in advance, while for the accepted socket this is only + // known when creating the socket, but passing it there is + // complicated. So, instead we simply rewrite the peer address + // from the settings in the CUDT entity, and only in case when + // it wasn't yet set. We recognize it by having port == 0 because + // this isn't a valid port number, at least not of the peer. + if (sh.m_PeerAddr.hport() == 0) { - w_packet.setLength(-1); - return -1; + sh.m_PeerAddr = sh.m_pSocket->core().m_PeerAddr; } - // copy packet content - // XXX Check if this wouldn't be better done by providing - // copy constructor for DynamicStruct. - // XXX Another thing: this looks wasteful. This expects an already - // allocated memory on the packet, this thing gets the 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((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; - - // remove this message from queue, - // if no more messages left for this socket, release its data structure - i->second.pop(); - if (i->second.empty()) - m_mBuffer.erase(i); + SRTSOCKET prid = sh.m_pSocket->core().m_PeerID; + m_RevPeerMap[prid] = id; + sh.m_State = SocketHolder::ACTIVE; + + m_UpdateOrderList.insert(steady_clock::now(), point); - return (int)w_packet.getLength(); + HLOGC(qmlog.Debug, log << "MUXER id=" << m_iID << ": connected: " << sh.report() + << "UPDATE-LIST: pos=" << point->m_UpdateOrder.pos + << " TIME:" << FormatTime(point->m_UpdateOrder.time) << " total " + << m_UpdateOrderList.size() << " sockets"); + + return true; } -bool srt::CRcvQueue::setListener(CUDT* u) +bool CMultiplexer::setBroken(SRTSOCKET id) { - return m_pListener.compare_exchange(NULL, u); + if (!m_zSockets) + { + LOGC(qmlog.Error, log << "setBroken: MUXER id=" << m_iID << " no sockets while looking for @" << id); + return false; + } + + sync::ScopedLock lk (m_SocketsLock); + return setBrokenInternal(id); } -srt::CUDT* srt::CRcvQueue::getListener() +bool CMultiplexer::setBrokenInternal(SRTSOCKET id) { - SharedLock lkl (m_pListener); - return m_pListener.get_locked(lkl); + sockmap_t::iterator fo = m_SocketMap.find(id); + if (fo == m_SocketMap.end()) + { + LOGC(qmlog.Error, log << "setBroken: MUXER id=" << m_iID << " NOT FOUND: @" << id); + return false; + } + + std::list::iterator point = fo->second; + setBrokenDirect(point); + return true; } -// XXX NOTE: TSan reports here false positive against the call -// to locateSocket in CUDTUnited::newConnection. This here will apply -// exclusive lock on m_pListener, while keeping shared lock on -// CUDTUnited::m_GlobControlLock in CUDTUnited::closeAllSockets. -// As the other thread locks both as shared, this is no deadlock risk. -bool srt::CRcvQueue::removeListener(CUDT* u) +void CMultiplexer::setBrokenDirect(sockiter_t point) { - return m_pListener.compare_exchange(u, NULL); + m_RevPeerMap.erase(point->setBrokenPeer()); + + HLOGC(qmlog.Debug, log << "setBroken: MUXER id=" << m_iID << " set to @" << point->id()); } -void srt::CRcvQueue::registerConnector(const SRTSOCKET& id, - CUDT* u, - const sockaddr_any& addr, - const steady_clock::time_point& ttl) +bool CMultiplexer::deleteSocket(SRTSOCKET id) { - HLOGC(cnlog.Debug, - log << "registerConnector: adding @" << id << " addr=" << addr.str() << " TTL=" << FormatTime(ttl)); - m_pRendezvousQueue->insert(id, u, addr, ttl); + if (!m_zSockets) + { + LOGC(qmlog.Error, log << "deleteSocket: MUXER id=" << m_iID << " no sockets while looking for @" << id); + return false; + } + + sync::ScopedLock lk (m_SocketsLock); + + sockmap_t::iterator fo = m_SocketMap.find(id); + if (fo == m_SocketMap.end()) + { + LOGC(qmlog.Error, log << "deleteSocket: MUXER id=" << m_iID << " no socket @" << id); + return false; + } + + std::list::iterator point = fo->second; + HLOGC(qmlog.Debug, log << "deleteSocket: removing: " << point->report()); + + // Remove from m_lRendezvousID (no longer valid after removal from here) + 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; + + if (i->m_it == point) + m_lRendezvousID.erase(i); + } + + // Remove from the Update Lists, if present + CUDTSocket* s = point->m_pSocket; + + // Remove from maps and list + m_UpdateOrderList.erase(point); + m_SndQueue.m_SendOrderList.remove(point); + + // As this is being waited for in another thread, you need to request sync. + // It will be anyway effective only after this function exits and unlocks m_SocketsLock. + m_SndQueue.m_SendOrderList.notify_schedule(); + + HLOGC(qmlog.Debug, log << "UPDATE-LIST: removed @" << id << " per removal from muxer"); + + s->core().m_MuxNode = SocketHolder::none(); // rewrite before it becomes invalid + m_RevPeerMap.erase(point->peerID()); + m_SocketMap.erase(id); // fo is no longer valid! + m_Sockets.erase(point); + --m_zSockets; + HLOGC(qmlog.Debug, log << "deleteSocket: MUXER id=" << m_iID << " removed @" << id << " (remaining " << m_zSockets << ")"); + return true; } -void srt::CRcvQueue::removeConnector(const SRTSOCKET& id) +/// Find a mapped CUDTSocket whose id is @a id. +CUDTSocket* CMultiplexer::findAgent(SRTSOCKET id, const sockaddr_any& remote_addr, + SocketHolder::State& w_state, AcquisitionControl acq) { - HLOGC(cnlog.Debug, log << "removeConnector: removing @" << id); - m_pRendezvousQueue->remove(id); + sync::ScopedLock lk (m_SocketsLock); + if (!m_zSockets) + { + LOGC(qmlog.Error, log << "findAgent: MUXER id=" << m_iID << " no sockets while looking for @" << id); + return NULL; + } - ScopedLock bufferlock(m_BufferLock); + sockmap_t::iterator fo = m_SocketMap.find(id); + if (fo == m_SocketMap.end()) + { + LOGC(qmlog.Error, log << "findAgent: MUXER id=" << m_iID << " no socket @" << id); + return NULL; + } - map >::iterator i = m_mBuffer.find(id); - if (i != m_mBuffer.end()) + std::list::iterator point = fo->second; + + // Note that this finding function needs a socket + // that is currently connected, so if it's not, behave + // as if nothing was found. + sync::steady_clock::time_point ttl; + SocketHolder::MatchState ms = point->checkIncoming(remote_addr, (ttl), (w_state)); + + if (ms != SocketHolder::MS_OK) { - HLOGC(cnlog.Debug, - log << "removeConnector: ... and its packet queue with " << i->second.size() << " packets collected"); - while (!i->second.empty()) + if (ms != SocketHolder::MS_INVALID_STATE) { - delete i->second.front(); - i->second.pop(); + LOGC(qmlog.Error, log << "findAgent: MUXER id=" << m_iID << ": " << point->report() + << " request from " << remote_addr.str() << " invalid " + << SocketHolder::MatchStr(ms)); + return NULL; } - m_mBuffer.erase(i); + HLOGC(qmlog.Debug, log << "findAgent: MUXER id=" << m_iID << " INVALID STATE: " << point->report()); + return NULL; } + + HLOGC(qmlog.Debug, log << "findAgent: MUXER id=" << m_iID << " found " << point->report()); + if (acq == ACQ_ACQUIRE) + point->m_pSocket->apiAcquire(); + return point->m_pSocket; } -void srt::CRcvQueue::setNewEntry(CUDT* u) +string SocketHolder::MatchStr(SocketHolder::MatchState ms) { - HLOGC(cnlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << "setting socket PENDING FOR CONNECTION"); - ScopedLock listguard(m_IDLock); - m_vNewEntry.push_back(u); + static const string table [] = { + "OK", + "STATE", + "ADDRESS", + "DATA" + }; + return table[int(ms)]; } -bool srt::CRcvQueue::ifNewEntry() +/// Find a mapped CUDTSocket for whom the peer ID has +/// been assigned as @a rid. +CUDTSocket* CMultiplexer::findPeer(SRTSOCKET rid, const sockaddr_any& remote_addr, AcquisitionControl acq) { - ScopedLock listguard(m_IDLock); - return !(m_vNewEntry.empty()); + if (!m_zSockets) + { + HLOGC(qmlog.Debug, log << "findPeer: MUXER id=" << m_iID << " no sockets while looking for -@" << rid); + return NULL; + } + + sync::ScopedLock lk (m_SocketsLock); + + std::map::iterator rfo = m_RevPeerMap.find(rid); + if (rfo == m_RevPeerMap.end()) + { + HLOGC(qmlog.Debug, log << "findPeer: MUXER id=" << m_iID << " -@" << rid << " not found in rev map"); + return NULL; + } + const int id = rfo->second; + + sockmap_t::iterator fo = m_SocketMap.find(id); + if (fo == m_SocketMap.end()) + { + LOGC(qmlog.Error, log << "findPeer: IPE: MUXER id=" <::iterator point = fo->second; + if (point->m_PeerAddr != remote_addr) + { + LOGC(qmlog.Error, log << "findPeer: MUXER id=" <m_pSocket->apiAcquire(); + + return point->m_pSocket; } -srt::CUDT* srt::CRcvQueue::getNewEntry() +steady_clock::time_point CMultiplexer::updateSendNormal(CUDTSocket* s) { - ScopedLock listguard(m_IDLock); + const steady_clock::time_point currtime = steady_clock::now(); - if (m_vNewEntry.empty()) - return NULL; + ScopedLock lks (m_SocketsLock); + bool updated SRT_ATR_UNUSED = + m_SndQueue.m_SendOrderList.update(s->core().m_MuxNode, SocketHolder::DONT_RESCHEDULE, currtime); + HLOGC(qslog.Debug, log << s->core().CONID() << "NORMAL update: " << (updated ? "" : "NOT ") + << "updated to " << FormatTime(currtime)); + return currtime; +} - CUDT* u = (CUDT*)*(m_vNewEntry.begin()); - m_vNewEntry.erase(m_vNewEntry.begin()); +void CMultiplexer::updateSendFast(CUDTSocket* s) +{ + steady_clock::duration immediate = milliseconds_from(1); + steady_clock::time_point yesterday = steady_clock::time_point(immediate); - return u; + ScopedLock lks (m_SocketsLock); + bool updated SRT_ATR_UNUSED = + m_SndQueue.m_SendOrderList.update(s->core().m_MuxNode, SocketHolder::DO_RESCHEDULE, yesterday); + HLOGC(qslog.Debug, log << s->core().CONID() << "FAST update: " << (updated ? "" : "NOT ") + << "updated"); } -void srt::CRcvQueue::storePktClone(int32_t id, const CPacket& pkt) + +void CMultiplexer::setReceiver(CUDT* u) { - CUniqueSync passcond(m_BufferLock, m_BufferCond); + SRT_ASSERT_AFFINITY(m_RcvQueue.m_WorkerThread.get_id()); + SRT_ASSERT(u->m_bOpened); - map >::iterator i = m_mBuffer.find(id); + HLOGC(qrlog.Debug, log << u->CONID() << " SOCKET pending for connection - ADDING TO RCV QUEUE/MAP (directly)"); + setConnected(u->m_SocketID); + // Register in updates -- done in setConnected! +} - if (i == m_mBuffer.end()) +void CMultiplexer::updateUpdateOrder(SRTSOCKET id, const sync::steady_clock::time_point& tnow) +{ + if (!m_zSockets) { - m_mBuffer[id].push(pkt.clone()); - passcond.notify_one(); + LOGC(qmlog.Error, log << "updateUpdateOrder: MUXER id=" << m_iID << " no sockets while looking for @" << id); + return; } - else + + sync::ScopedLock lk (m_SocketsLock); + + sockmap_t::iterator fo = m_SocketMap.find(id); + if (fo == m_SocketMap.end()) { - // Avoid storing too many packets, in case of malfunction or attack. - if (i->second.size() > 16) + LOGC(qmlog.Error, log << "updateUpdateOrder: MUXER id=" << m_iID << " no socket @" << id); + return; + } + + std::list::iterator point = fo->second; + if (point->m_UpdateOrder.pos == m_UpdateOrderList.npos) + { + // Weird, but don't add it either. + HLOGC(qmlog.Error, log << "UPDATE-LIST: updateUpdateOrder: @" << id << " is NOT in the update list - NOT ADDING"); + return; + } + + m_UpdateOrderList.update(point->m_UpdateOrder.pos, tnow); + HLOGC(qmlog.Debug, log << "UPDATE-LIST: @" << id << " pos=" << point->m_UpdateOrder.pos + << " updated to time " << FormatTime(point->m_UpdateOrder.time)); +} + +void CMultiplexer::rollUpdateSockets(const sync::steady_clock::time_point& curtime_minus_syn) +{ + sync::steady_clock::time_point tnow = sync::steady_clock::now(); + + vector sockets_to_update; + { + sync::ScopedLock lk (m_SocketsLock); + if (m_UpdateOrderList.empty()) + { return; + } - i->second.push(pkt.clone()); + for (;;) + { + // Guaranteed at least one element, so top() is valid. + sockiter_t point = m_UpdateOrderList.top(); + if (point != m_UpdateOrderList.none() && point->m_UpdateOrder.time < curtime_minus_syn) + { + HLOGC(qmlog.Debug, log << "UPDATE-LIST: roll: got @" << point->id() << " due in " + << FormatDuration(curtime_minus_syn - point->m_UpdateOrder.time)); + // PASS + } + else + { + HLOGC(qmlog.Debug, log << "UPDATE-LIST: roll: no more past-time sockets (remain " << m_UpdateOrderList.size() << " future sockets)"); + break; + } + + CUDT* u = &point->m_pSocket->core(); + + if (u->m_bConnected && !u->m_bBroken && !u->m_bClosing) + { + // Lock the sockets being collected here to prevent unexpected deletion + // SYMMETRY is ensured by adding them to this container. + point->m_pSocket->apiAcquire(); + sockets_to_update.push_back(point->m_pSocket); + + // Now reinsert the item with the new time. + m_UpdateOrderList.update(point->m_UpdateOrder.pos, tnow); + + HLOGC(qmlog.Debug, log << "UPDATE-LIST: reinserted @" << u->id() << " pos=" << point->m_UpdateOrder.pos + << " TIME:" << FormatTime(point->m_UpdateOrder.time) << " total " + << m_UpdateOrderList.size() << " update ordered sockets"); + } + else + { + HLOGC(qrlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) + << " UPDATE-LIST: SOCKET broken, removing from the list."); + m_UpdateOrderList.pop(); + // the socket must be removed from Hash table first, then RcvUList + + // We should NOT let the m_SocketsLock be locked, and simultaneously + // we know that the socket is there, so we don't need to pre-check the size. + setBrokenInternal(u->m_SocketID); + // Do nothing more. The socket is removed from update list, + // so just do not reinsert it. + } + } + } + + // Run the update outside the lock of m_SocketsLock. Some underlying + // activities may need to lock m_GlobControlLock, so we need this fragment + // to be lock-free. Instead, we have applied busy-lock, which will be also + // freed here once we are done with the handling. + for (size_t i = 0; i < sockets_to_update.size(); ++i) + { + CUDTSocket* s = sockets_to_update[i]; + s->core().checkTimers(); + s->apiRelease(); } } +bool CMultiplexer::tryCloseIfEmpty() +{ + if (!empty()) + return false; + + // Only set the closing flags because without this the worker loops + // will report errors, but continue their work. Setting this flag will + // make the threads exit in perspective, but at least they won't treat + // the reading failure as IPE. The thread exiting will be still ensured + // after this call. + setClosing(); + + if (m_pChannel) + m_pChannel->close(); + + // CONSIDER - but this field is inter-thread with no mutex + // m_SelfAddr.reset(); + return true; +} + void srt::CMultiplexer::resetAtFork() { - if (m_pRcvQueue != NULL) - m_pRcvQueue->resetAtFork(); - if (m_pSndQueue != NULL) - m_pSndQueue->resetAtFork(); + m_RcvQueue.resetAtFork(); + m_SndQueue.resetAtFork(); } -void srt::CMultiplexer::close() +bool CMultiplexer::reserveDisposal() +{ + if (m_ReservedDisposal != CThread::id()) + { + // Already reserved + return false; + } + + m_ReservedDisposal = sync::this_thread::get_id(); + return true; +} + +CMultiplexer::~CMultiplexer() +{ + // Reverse order of the assigned. + stop(); + close(); +} + +void CMultiplexer::close() { if (m_pChannel) { @@ -1862,15 +2408,43 @@ void srt::CMultiplexer::close() void srt::CMultiplexer::stop() { - if (m_pRcvQueue != NULL) - m_pRcvQueue->stop(); - if (m_pSndQueue != NULL) - m_pSndQueue->stop(); + m_RcvQueue.stop(); + m_SndQueue.stop(); } -void srt::CMultiplexer::destroy() +string CMultiplexer::testAllSocketsClear() { - // Reverse order of the assigned. - stop(); - close(); + std::ostringstream out; + ScopedLock lk (m_SocketsLock); + + for (sockmap_t::iterator i = m_SocketMap.begin(); i != m_SocketMap.end(); ++i) + { + // Do not notify those that are broken or nonexistent + if (int(i->second->m_State) >= SocketHolder::INIT) + out << " +" << i->first << "=" << SocketHolder::StateStr(i->second->m_State); + } + + for (std::map::iterator i = m_RevPeerMap.begin(); i != m_RevPeerMap.end(); ++i) + out << " R[" << i->first << "]=" << i->second; + + return out.str(); } + +string SocketHolder::StateStr(SocketHolder::State st) +{ + static const char* const state_names [] = { + "INVALID", + "BROKEN", + "INIT", + "PENDING", + "ACTIVE" + }; + int statex = int(st) + 2; + if (statex < 0 || statex > 5) + statex = 0; + + return state_names[statex]; +} + +} // end namespace + diff --git a/srtcore/queue.h b/srtcore/queue.h index 930ddd92a..6352e219c 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -53,25 +53,48 @@ modified by #ifndef INC_SRT_QUEUE_H #define INC_SRT_QUEUE_H +#include +#include +#include +#include #include "common.h" #include "packet.h" #include "socketconfig.h" #include "netinet_any.h" #include "utilities.h" -#include -#include -#include -#include + +// You can change it to 0 to use the old solution, +// just note that it won't work with bonding. +#define USE_RECEIVER_UNIT_POOL 1 + +#if SRT_ENABLE_BONDING + #ifndef USE_RECEIVER_UNIT_POOL + #define USE_RECEIVER_UNIT_POOL 1 + #endif +#endif + +// May change this setting on demand +#ifndef SRT_RCV_BUFFER_POOL_MAX_SERIES +#define SRT_RCV_BUFFER_POOL_MAX_SERIES 3 +#endif + +#ifndef SRT_RCV_BUFFER_POOL_SERIES_SIZE +#define SRT_RCV_BUFFER_POOL_SERIES_SIZE 128 +#endif namespace srt { class CChannel; class CUDT; +#if !USE_RECEIVER_UNIT_POOL +class CUnitQueue; + struct CUnit { CPacket m_Packet; // packet sync::atomic m_bTaken; // true if the unit is is use (can be stored in the RCV buffer). + CUnitQueue* m_pParentQueue; }; class CUnitQueue @@ -118,7 +141,7 @@ class CUnitQueue /// @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); + /* static - restore after removing m_pParentQueue */ CQEntry* allocateEntry(const int iNumUnits, const int mss); private: CQEntry* m_pQEntry; // pointer to the first unit queue @@ -135,270 +158,517 @@ class CUnitQueue CUnitQueue& operator=(const CUnitQueue&); }; -struct CSNode -{ - CUDT* m_pUDT; // Pointer to the instance of CUDT socket - sync::steady_clock::time_point m_tsTimeStamp; +#else // if USE_RECEIVER_UNIT_POOL - sync::atomic m_iHeapLoc; // location on the heap, -1 means not on the heap -}; - -class CSndUList +// REPLACEMENT FOR CUnitQueue +class CPacketUnitPool { public: - CSndUList(sync::CTimer* pTimer); - ~CSndUList(); -public: - enum EReschedule + static const size_t MIN_SERIES_REQUIRED = 1, + MAX_SERIES_ALLOWED = SRT_RCV_BUFFER_POOL_MAX_SERIES; + + struct Unit { - DONT_RESCHEDULE = 0, - DO_RESCHEDULE = 1 + CPacket m_Packet; + + // The m_Packet will not own the buffer, instead + // the buffer will be managed by m_Payload and only + // assigned to m_Packet's data. + FixedArray m_Payload; + + Unit(size_t size): m_Payload(size) + { + m_Packet.m_pcData = m_Payload.data(); + m_Packet.setLength(0, size); + } }; - static EReschedule rescheduleIf(bool cond) { return cond ? DO_RESCHEDULE : DONT_RESCHEDULE; } - void resetAtFork(); + // This pointer wrapper is modelled after the FIRST VERSION of std::auto_ptr. + // Copying transferred ownership and turned the source into a weak pointer. + // Note that the latest version of auto_ptr was using NULL as no-owner marker, + // this however made it also noncopyable. In C++11 version this could be just + // as well replaced by std::unique_ptr, or at least move semantics could make + // it way better. + struct UnitPtr + { + Unit* ptr; + mutable bool owns; // use the trick from the old auto_ptr + UnitPtr(): ptr(NULL), owns(false) {} - /// 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()); + static UnitPtr from(Unit* p) { UnitPtr u; u.ptr = p; u.owns = true; return u; } - /// 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(); + Unit* operator->() { return ptr; } + const Unit* operator->() const { return ptr; } + Unit& operator*() { return *ptr; } + const Unit& operator*() const { return *ptr; } - /// Remove UDT instance from the list. - /// @param [in] u pointer to the UDT instance - void remove(const CUDT* u);// EXCLUDES(m_ListLock); + void allocate(size_t bufsize) + { + if (ptr && owns) + delete ptr; + ptr = new Unit(bufsize); + owns = true; + } - /// Retrieve the next scheduled processing time. - /// @return Scheduled processing time of the first UDT socket in the list. - sync::steady_clock::time_point getNextProcTime(); + ~UnitPtr(); - /// Wait for the list to become non empty. - void waitNonEmpty() const; + void swap(UnitPtr& theOther) + { + std::swap(ptr, theOther.ptr); + std::swap(owns, theOther.owns); + } - /// Signal to stop waiting in waitNonEmpty(). - void signalInterrupt() const; + UnitPtr(const UnitPtr& victim) + { + ptr = victim.ptr; + owns = victim.owns; + victim.owns = false; + } + + // Unfortunately, the assignment operator is required because + // it's used by standard algorithms; C++03 must do this without + // explicit move semantics. We can only hope that tautology resulting + // from creating a default object and checking the condition for + // emptiness can be elided. There's also no self-assignment prevention. + UnitPtr& operator=(const UnitPtr& victim) + { + if (owns && ptr) + delete ptr; + ptr = victim.ptr; + owns = victim.owns; // Could also copy unowned one! + victim.owns = false; + return *this; + } + + // Needed for assertion + bool operator==(const UnitPtr& other) const { return ptr == other.ptr; } + bool operator!=(const UnitPtr& other) const { return ptr != other.ptr; } + + bool operator==(const Unit* other) const { return ptr == other; } + bool operator!=(const Unit* other) const { return ptr != other; } + + // Use in condition + operator bool() const { return ptr; } + bool operator !() const { return !ptr; } + + Unit* get() { return ptr; } + const Unit* get() const { return ptr; } + }; -private: - /// Doubles the size of the list. - /// - void realloc_();// REQUIRES(m_ListLock); + typedef std::vector UnitContainer; - /// 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); + // This is the container that should be at hand for the + // user object. No mutex protection, it's at the responsibility + // of the user object. Utility functions are provided to pickup + // or peeking. - /// 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); + bool hand_retrieve() + { + bool ret = retrieveSeries((m_HandSeries)); + SRT_ASSERT(ret); + return ret; + } - /// Removes CUDT entry from the list. - /// If the last entry is removed, calls sync::CTimer::interrupt(). - void remove_(const CUDT* u); + bool hand_refill() + { + if (!m_HandSeries.empty()) + return true; -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 or -1 if empty. + return hand_retrieve(); + } - mutable sync::Mutex m_ListLock; // Protects the list (m_pHeap, m_iArrayLength, m_iLastEntry). - mutable sync::Condition m_ListCond; + void hand_return(UnitPtr& from) + { + SRT_ASSERT(!! from); + m_HandSeries.push_back(UnitPtr()); + from.swap(m_HandSeries.back()); + } + + Unit* hand_peek() const + { + if (m_HandSeries.empty()) + return NULL; + SRT_ASSERT(!! m_HandSeries.back().ptr); + return m_HandSeries.back().ptr; + } + + void hand_pull_raw(UnitPtr& target) + { + SRT_ASSERT(!m_HandSeries.empty()); + PullBack_raw((m_HandSeries), (target)); + } - sync::CTimer* const m_pTimer; + bool hand_pull(UnitPtr& target) + { + if (!hand_refill()) + return false; + + hand_pull_raw((target)); + return true; + } + + // For convenience + size_t hand_size() const { return m_HandSeries.size(); } + + UnitContainer m_HandSeries; + + static void allocateOneSeries(UnitContainer& series, size_t series_size, size_t unit_size); private: - CSndUList(const CSndUList&); - CSndUList& operator=(const CSndUList&); -}; -struct CRNode -{ - CUDT* m_pUDT; // Pointer to the instance of CUDT socket - sync::steady_clock::time_point m_tsTimeStamp; // Time Stamp + // UpperBuffer: Contains entry series. + std::vector m_Series; + mutable sync::Mutex m_UpperLock; - CRNode* m_pPrev; // previous link - CRNode* m_pNext; // next link + // LowerBuffer: Single packet units collected + // after being returned from the receiver buffer. + UnitContainer m_RecycledUnits; + mutable sync::Mutex m_LowerLock; - sync::atomic m_bOnList; // if the node is already on the list -}; + size_t m_zUnitSize; + size_t m_zSeriesSize; -class CRcvUList -{ -public: - CRcvUList(); - ~CRcvUList(); + sync::atomic m_zMaxSeries; public: - /// Insert a new UDT instance to the list. - /// @param [in] u pointer to the UDT instance - void insert(const CUDT* u); + // For UT only, to verify parameters + size_t condenser_size() const + { + m_LowerLock.lock(); + size_t r = m_RecycledUnits.size(); + m_LowerLock.unlock(); + return r; + } - /// Remove the UDT instance from the list. - /// @param [in] u pointer to the UDT instance + size_t solid_size() const + { + m_UpperLock.lock(); + size_t r = m_Series.size(); + m_UpperLock.unlock(); + return r * m_zSeriesSize; + } - void remove(const CUDT* u); + size_t total_size() const + { + return condenser_size() + solid_size() + hand_size(); + } - /// 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 + // Parameter order is consistent with those of CUnitQueue. + // series_size: the size of the series (amortization size) + // unit_size: size of every packet unit + CPacketUnitPool(size_t series_size, size_t unitsize): + m_zUnitSize(unitsize), + m_zSeriesSize(series_size), + m_zMaxSeries(MAX_SERIES_ALLOWED) // default, changeable + { + m_RecycledUnits.reserve(series_size); + } - void update(const CUDT* u); + void setMaxSeries(size_t max) + { + m_zMaxSeries = max; + updateLimits(); + } -public: - CRNode* m_pUList; // the head node + void updateLimits(); -private: - CRNode* m_pLast; // the last node + // To be called by Multiplexer's reader to get fresh units + // to read packets into. + bool retrieveSeries(UnitContainer& series); -private: - CRcvUList(const CRcvUList&); - CRcvUList& operator=(const CRcvUList&); + // The receiver buffer has revoked that entry and wants + // to delete it (or give it back to the pool). + void returnUnit(UnitPtr& returned_entry); + + void returnUnitSeries(UnitContainer& series); + + // For assertion only. + bool verifySeries(const UnitContainer& series) const + { + bool out = true; + for (UnitContainer::const_iterator i = series.begin(); i != series.end(); ++i) + out = out && i->ptr; + return out; + } }; -class CHash +#endif + + +// NOTE: SocketHolder was moved here because it's a dependency of +// CSendOrderList, so it must be first defined. +struct SocketHolder { -public: - CHash(); - ~CHash(); + typedef std::list socklist_t; + typedef socklist_t::iterator sockiter_t; + static socklist_t empty_list; + static const size_t heap_npos = std::string::npos; + static sockiter_t none() { return empty_list.end(); } -public: - /// Initialize the hash table. - /// @param [in] size hash table size + enum State + { + NONEXISTENT = -2, + BROKEN = -1, + INIT = 0, + PENDING = 1, + ACTIVE = 2 + }; - void init(int size); + // Used by SendOrder. + enum EReschedule + { + DONT_RESCHEDULE = 0, + DO_RESCHEDULE = 1 + }; - /// 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. + static std::string StateStr(State); - CUDT* lookup(int32_t id); + State m_State; + class CUDTSocket* m_pSocket; - /// Insert an entry to the hash table. - /// @param [in] id socket ID - /// @param [in] u pointer to the UDT instance + // Time when the connection request should expire. Contains zero, + // if there was no request. + sync::steady_clock::time_point m_tsRequestTTL; - void insert(int32_t id, CUDT* u); + // SRT connection peer address + sockaddr_any m_PeerAddr; - /// Remove an entry from the hash table. - /// @param [in] id socket ID + struct UpdateNode + { + // Time when the socket needs to be picked up for update. + typedef sync::steady_clock::time_point key_type; + key_type time; + size_t pos; - void remove(int32_t id); + // Access methods + static key_type& key(sockiter_t i) { return i->m_UpdateOrder.time; } + static size_t& position(sockiter_t i) { return i->m_UpdateOrder.pos; } + static sockiter_t none() { return empty_list.end(); } + static bool order(key_type left, key_type right) { return left < right; } -private: - struct CBucket + UpdateNode() : pos(heap_npos) {} + + } m_UpdateOrder; + + struct SendNode { - int32_t m_iID; // Socket ID - CUDT* m_pUDT; // Socket instance + // Time when sending through this socket should happen. + typedef sync::steady_clock::time_point key_type; + key_type time; + size_t pos; - CBucket* m_pNext; // next bucket - } * *m_pBucket; // list of buckets (the hash table) + // Access methods + static key_type& key(sockiter_t i) { return i->m_SendOrder.time; } + static size_t& position(sockiter_t i) { return i->m_SendOrder.pos; } + static sockiter_t none() { return empty_list.end(); } + static bool order(key_type left, key_type right) { return left < right; } - int m_iHashSize; // size of hash table + SendNode() : pos(heap_npos) {} -private: - CHash(const CHash&); - CHash& operator=(const CHash&); + // private utilities + + // Checks if the position is not set to a trap representation + bool pinned() const { return pos != heap_npos; } + + // Position 0 means that this is the earliest element and this + // element would be returned from the next pop() call. + bool is_top() const { return pos == 0; } + + } m_SendOrder; + +#if SRT_ENABLE_THREAD_DEBUG + UniquePtr m_sanitized_cond; + + // Declare given condition variable that the thread running this + // object will be responsible for notifying this CV. + void addCondSanitizer(sync::Condition& cond) + { + m_sanitized_cond.reset(new sync::Condition::ScopedNotifier(cond)); + } +#endif + + SocketHolder(): + m_State(INIT), + m_pSocket(NULL), + m_tsRequestTTL() + { + } + + // To return true the socket must be: + // - at least in PENDING state + // - have equal address + // The w_ttl and w_state are filled always, regardless of the result. + enum MatchState { MS_OK = 0, MS_INVALID_STATE = 1, MS_INVALID_ADDRESS = 2, MS_INVALID_DATA = 3 }; + static std::string MatchStr(MatchState); + + MatchState checkIncoming(const sockaddr_any& peer_addr, + sync::steady_clock::time_point& w_ttl, + State& w_state) const + { + w_ttl = m_tsRequestTTL; + w_state = m_State; + + if (!m_pSocket) + return MS_INVALID_DATA; + + if (peer_addr != m_PeerAddr) + return MS_INVALID_ADDRESS; + + if (int(m_State) > int(INIT)) + return MS_OK; + + return MS_INVALID_STATE; + } + + SRTSOCKET id() const; + SRTSOCKET peerID() const; + sockaddr_any peerAddr() const; + + static SocketHolder initial(CUDTSocket* so) + { + SocketHolder that; + + that.m_pSocket = so; + that.m_State = INIT; + + return that; + } + + void setConnector(const sockaddr_any& addr, const sync::steady_clock::time_point& ttl) + { + m_State = PENDING; + m_PeerAddr = addr; + m_tsRequestTTL = ttl; + } + + // This function is executed when the connection-pending state + // is withdrawn and the socket turns into CONNECTED or BROKEN + // state, according to the flags. + void setConnectedState(); + + SRTSOCKET setBrokenPeer() + { + m_State = BROKEN; + return peerID(); + } + + // Debug support + std::string report() const; }; -/// @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 +// REPLACEMENT FOR CSndUList + +class CSendOrderList { + // TEST IF REQUIRED API public: - CRendezvousQueue(); - ~CRendezvousQueue(); + CSendOrderList(sync::Mutex& emx); -public: - /// @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); + void resetAtFork(); - /// @brief Remove a socket from the connection pending list. - /// @param id socket ID. - void remove(const SRTSOCKET& id); + /// Advice the given socket to be scheduled for sending in the sender queue. + /// If the socket isn't yet in the queue, it will be added with given time. + /// If it's there already, then depending on @a reschedule: + /// * with DONT_RESCHEDULE, nothing will be done + /// * with DO_RESCHEDULE, the socket will be updated with given time (@a ts) + /// @param [in] point node pointer to the socket holder in the multiplexer + /// @param [in] reschedule if the timestamp should be rescheduled + /// @param [in] ts the next time to trigger sending logic on the CUDT + /// @return True, if the socket was scheduled for given time + SRT_TSA_NEEDS_LOCKED(m_ExternLock) + bool update(SocketHolder::sockiter_t point, SocketHolder::EReschedule reschedule, sync::steady_clock::time_point ts = sync::steady_clock::now()); + + /// Blocks until the time comes to pick up the heap top. + /// The call remains blocked as long as: + /// - the heap is empty + /// - the heap top element's run time is in the future + /// - no other thread has forcefully interrupted the wait + /// @return the node that is ready to run, or NULL on interrupt + SRT_TSA_NEEDS_LOCKED(m_ExternLock) + SocketHolder::sockiter_t wait(sync::UniqueLock& lk); + + // This function moves the node throughout the heap to put + // it into the right place. + SRT_TSA_NEEDS_LOCKED(m_ExternLock) + bool requeue(SocketHolder::sockiter_t point, const sync::steady_clock::time_point& uptime); - /// @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; + /// Remove UDT instance from the list. + /// @param [in] u pointer to the UDT instance + SRT_TSA_NEEDS_LOCKED(m_ExternLock) + void remove(SocketHolder::sockiter_t point) + { + m_Schedule.erase(point); + } - /// @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 / nothing 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); + SRT_TSA_NEEDS_LOCKED(m_ExternLock) + void notify_schedule() + { + m_ListCond.notify_all(); + } -private: - struct LinkStatusInfo + /// Signal to stop waiting in waitNonEmpty(). + SRT_TSA_NEEDS_NONLOCKED(m_ExternLock) // will lock itself + void signalInterrupt(); + + void setRunning() { - CUDT* u; - SRTSOCKET id; - int errorcode; - sockaddr_any peeraddr; - int token; + m_bRunning = true; + } - struct HasID - { - SRTSOCKET id; - HasID(SRTSOCKET p) - : id(p) - { - } - bool operator()(const LinkStatusInfo& i) { return i.id == id; } - }; - }; + void stop() + { + m_bRunning = false; + } - /// @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 / nothing 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); + // Design-patching. + // This lock must be applied when a socket is removed from the + // multiplexer. This must be done so to prevent another thread from + // reaching out to the order container containing nodes begin now removed. private: - struct CRL + + HeapSet m_Schedule; + + friend class CSndQueue; + + sync::Mutex& m_ExternLock; // pinned into CMultiplexer::m_SocketsLock + mutable sync::Condition m_ListCond; + sync::atomic m_bRunning; +}; + +struct LinkStatusInfo +{ + CUDT* u; + SRTSOCKET id; + int errorcode; + sockaddr_any peeraddr; + int token; + + struct HasID { - 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 + SRTSOCKET id; + HasID(SRTSOCKET p) + : id(p) + { + } + bool operator()(const LinkStatusInfo& i) { return i.id == id; } }; - std::list m_lRendezvousID; // The sockets currently in rendezvous mode - - mutable sync::Mutex m_RIDListLock; }; +struct CMultiplexer; + class CSndQueue { friend class CUDT; friend class CUDTUnited; + friend struct CMultiplexer; + CMultiplexer* m_parent; + + CSndQueue(CMultiplexer* parent); public: - CSndQueue(); ~CSndQueue(); public: @@ -412,49 +682,32 @@ class CSndQueue /// 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; - int sockoptQuery(int level, int type) const; + void init(CChannel* c); void setClosing() { m_bClosing = true; } void stop(); private: - static void* worker(void* param); + static void* worker_fwd(void* param) + { + CSndQueue* self = (CSndQueue*)param; + self->workerSendOrder(); + + return NULL; + } + + void workerSendOrder(); sync::CThread m_WorkerThread; private: - CSndUList* m_pSndUList; // List of UDT instances for data sending + CSendOrderList m_SendOrderList; // List of socket instances for data sending CChannel* m_pChannel; // The UDP channel for data sending - sync::CTimer* m_pTimer; // Timing facility sync::atomic m_bClosing; // closing the worker 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; @@ -471,8 +724,8 @@ class CSndQueue private: -#if ENABLE_LOGGING - static srt::sync::atomic m_counter; +#if HVU_ENABLE_LOGGING + static sync::atomic m_counter; #endif CSndQueue(const CSndQueue&); @@ -483,9 +736,12 @@ class CRcvQueue { friend class CUDT; friend class CUDTUnited; + friend struct CMultiplexer; + CMultiplexer* m_parent; + + CRcvQueue(CMultiplexer* parent); public: - CRcvQueue(); ~CRcvQueue(); public: @@ -503,49 +759,88 @@ class CRcvQueue /// @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, size_t payload, CChannel* c); /// Read a packet for a specific UDT socket id. /// @param [in] id Socket ID /// @param [out] packet received packet /// @return Data size of the packet - int recvfrom(int32_t id, CPacket& to_packet); - - void stopWorker(); + //int recvfrom(SRTSOCKET id, CPacket& to_packet); void setClosing() { m_bClosing = true; } - int getIPversion() { return m_iIPversion; } - void stop(); + +#if USE_RECEIVER_UNIT_POOL + // No need to expose it. Use viewUnit or retrieveUnit directly. +#else + CUnitQueue* getBufferQueue() { return m_pUnitQueue; } +#endif + private: - static void* worker(void* param); + static void* worker_fwd(void* param); + void worker(); 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); + EReadStatus worker_RetrieveAndProcessUnit(EConnectStatus& w_cst, const CPacket*& w_pkt, SRTSOCKET& w_id); + EReadStatus worker_DropIncomingPacket(sockaddr_any& w_addr); + EConnectStatus worker_ProcessUnit(SRTSOCKET id, CUnit* unit, const sockaddr_any& sa, const CPacket*& w_pkt); + EConnectStatus worker_ProcessConnectionRequest(CPacket& packet, const sockaddr_any& sa); + EConnectStatus worker_RetryOrRendezvous(CUDT* u, const CPacket& packet); + EConnectStatus worker_ProcessAddressedPacket(SRTSOCKET id, CUnit* unit, const sockaddr_any& sa); + bool worker_TryAcceptedSocket(const CPacket& packet, const sockaddr_any& addr); + +#if USE_RECEIVER_UNIT_POOL + CPacketUnitPool::Unit* viewUnit(); + + // XXX Consider making these two public, instead of friending PacketFilterCollector + void retrieveUnit_raw(CPacketUnitPool::UnitPtr& to); + bool retrieveUnit(CPacketUnitPool::UnitPtr& to); + + void returnUnit(CPacketUnitPool::UnitPtr& from) + { + SRT_ASSERT(!! from); + m_pUnitPool->hand_return((from)); + SRT_ASSERT(!! m_pUnitPool->m_HandSeries.back().ptr); + } + friend struct PacketFilterCollector; // unsure, may be not required +#endif private: + +#if USE_RECEIVER_UNIT_POOL + + // EITHER of these two is set to a certain value. + // If this is m_pUnitPool, it's managed inside this object. + UniquePtr m_pUnitPool; +#else 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 +#endif 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 + size_t m_zPayloadSize; // 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. +#if HVU_ENABLE_LOGGING + static sync::atomic m_counter; // A static counter to log RcvQueue worker thread number. #endif private: bool setListener(CUDT* u); CUDT* getListener(); bool removeListener(CUDT* u); + void storePktClone(SRTSOCKET id, const CPacket& pkt); + void kick(); + + /// @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 / nothing 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, const CPacket* pkt); + +private: + sync::CSharedObjectPtr m_pListener; // pointer to the (unique, if any) listening UDT entity void registerConnector(const SRTSOCKET& id, CUDT* u, @@ -553,73 +848,257 @@ class CRcvQueue const sync::steady_clock::time_point& ttl); void removeConnector(const SRTSOCKET& id); - void setNewEntry(CUDT* u); - bool ifNewEntry(); - CUDT* getNewEntry(); - - void storePktClone(int32_t id, const CPacket& pkt); - -private: - sync::CSharedObjectPtr 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 - sync::Mutex m_IDLock; - - std::map > m_mBuffer; // temporary buffer for rendezvous connection request - sync::Mutex m_BufferLock; - sync::Condition m_BufferCond; + typedef std::map > qmap_t; + qmap_t m_mBuffer; // temporary buffer for rendezvous connection request + sync::Mutex m_BufferLock; + sync::Condition m_BufferCond; private: CRcvQueue(const CRcvQueue&); CRcvQueue& operator=(const CRcvQueue&); }; + struct CMultiplexer { - CSndQueue* m_pSndQueue; // The sending queue - CRcvQueue* m_pRcvQueue; // The receiving queue + typedef std::list socklist_t; + typedef socklist_t::iterator sockiter_t; + typedef srt::hash_map sockmap_t; + + struct CRL + { + SRTSOCKET m_iID; // SRT socket ID (self) + CUDT* m_pUDT; // CUDT instance + socklist_t::iterator m_it; + 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 + + size_t nsockets() const { return m_zSockets; } + bool empty() const { return m_zSockets == 0; } + + enum AcquisitionControl + { + ACQ_RELAXED = 0, + ACQ_ACQUIRE = 1 + }; + +private: + + // Clang TSA is so stupid that it would block ordering + // declaration just because it is in the private section +#if SRT_ENABLE_CLANG_TSA + friend class CUDTUnited; +#endif + + // Dissolution might be a better idea, but this is better for separation. + friend class CSndQueue; + + int m_iID; // multiplexer ID + + mutable sync::Mutex m_SocketsLock; + + socklist_t m_Sockets; + sync::atomic m_zSockets; // size cache + + // Mapper id -> node + sockmap_t m_SocketMap; + + // Functional orders + HeapSet m_UpdateOrderList; + // Send order is contained in the CSndQueue class. + + // Peer ID to Agent ID mapping + std::map m_RevPeerMap; + + CSndQueue m_SndQueue; // The sending queue + CRcvQueue m_RcvQueue; // 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 + sockaddr_any m_SelfAddr; CSrtMuxerConfig m_mcfg; - int m_iID; // multiplexer ID + // XXX if this helps anyhow, this field can be also + // just boolean. It's not checked, if it contains the + // right thread number, only if this is set to a valid + // thread value or remains default ("no thread"). This + // value might be useful with debugging though. + sync::CThread::id m_ReservedDisposal; + +public: + + // CAREFUL with this function. This will close the channel + // regardless if it's in use. + bool tryCloseIfEmpty(); + + CChannel* channel() { return m_pChannel; } + const CChannel* channel() const { return m_pChannel; } + int id() const { return m_iID; } + sockaddr_any selfAddr() const { return m_SelfAddr; } + const CSrtMuxerConfig& cfg() const { return m_mcfg; } + + void setClosing() + { + m_SndQueue.setClosing(); + m_RcvQueue.setClosing(); + } + + // This function checks if the current thread isn't any of + // the worker threads. If it is, destruction of a multiplexer + // must be rejected. This may still happen later in the GC, + // but synchronous closing in this situation is simply not possible. + bool isSelfDestructAttempt() + { + return sync::this_thread_is(m_SndQueue.m_WorkerThread) + || sync::this_thread_is(m_RcvQueue.m_WorkerThread); + } + + void stopWorkers() + { + m_SndQueue.stop(); + m_RcvQueue.stop(); + } + + // This call attempts to reserve the disposal action to the + // current thread. This is successful, if the disposal reservation + // has not been set. If it was set, reservation fails, and this + // function returns false; in this case the thread that attempted + // the reservation shall not try to access this multiplexer after + // it releases m_GlobControlLock. If reservation succeeds, the thread + // that attempted the reservation is obliged to call stopWorkers() + // to make sure that all threads using this multiplexer have exit + // (with m_GlobControlLock lifted for that action), and then delete it, + // under restored m_GlobControlLock. + bool reserveDisposal(); + + // For testing + std::string testAllSocketsClear(); + + bool addSocket(CUDTSocket* s); + bool deleteSocket(SRTSOCKET id); + bool setConnected(SRTSOCKET id); + bool setBroken(SRTSOCKET id); + + SRT_TSA_NEEDS_LOCKED(m_SocketsLock) + bool setBrokenInternal(SRTSOCKET id); + + void setBrokenDirect(sockiter_t); + + CUDTSocket* findAgent(SRTSOCKET id, const sockaddr_any& remote_addr, SocketHolder::State& w_state, AcquisitionControl acq = ACQ_RELAXED); + CUDTSocket* findPeer(SRTSOCKET id, const sockaddr_any& remote_addr, AcquisitionControl acq = ACQ_RELAXED); + + /// @brief Remove a socket from the connection pending list. + /// @param id socket ID. + void removeRID(const SRTSOCKET& id); + + void expirePending(SocketHolder& sh); + + bool qualifyToHandleRID(EReadStatus rst, + EConnectStatus cst, + SRTSOCKET iDstSockID, + std::vector& toRemove, + std::vector& toProcess); + + /// @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* retrieveRID(const sockaddr_any& addr, SRTSOCKET id) const; + + void resetExpiredRID(const std::vector& toRemove); + void registerCRL(const CRL& setup); + void removeConnector(const SRTSOCKET& id) { return m_RcvQueue.removeConnector(id); } + void setReceiver(CUDT* u); + + // Order handling + void updateUpdateOrder(SRTSOCKET id, const sync::steady_clock::time_point& tnow); + void rollUpdateSockets(const sync::steady_clock::time_point& tnow_minus_syn); + +#if USE_RECEIVER_UNIT_POOL + CPacketUnitPool* getBufferQueue() { return m_RcvQueue.m_pUnitPool.get(); } +#else + CUnitQueue* getBufferQueue() { return m_RcvQueue.m_pUnitQueue; } +#endif // Constructor should reset all pointers to NULL // to prevent dangling pointer when checking for memory alloc fails +#if HAVE_CXX11 + CMultiplexer() - : m_pSndQueue(NULL) - , m_pRcvQueue(NULL) + : m_iID(-1) + , m_SndQueue(this) + , m_RcvQueue(this) , m_pChannel(NULL) - , m_pTimer(NULL) - , m_iPort(0) - , m_iIPversion(0) - , m_iRefCount(1) - , m_iID(-1) + , m_ReservedDisposal() { + m_SocketMap.reserve(1024); // reserve buckets - std::unordered_map version } - ~CMultiplexer() +#else + + CMultiplexer() + : m_iID(-1) + , m_SocketMap(1024) // reserve buckets - gnu::hash_map version + , m_SndQueue(this) + , m_RcvQueue(this) + , m_pChannel(NULL) + , m_ReservedDisposal() + { + } + + // "Copying" means to create an empty multiplexer. You can't + // copy a multiplexer; copying is only formally required to + // fulfill the Copiable requirements so that it can be used + // in containers. This is required for map::operator[], and + // CopyConstructible requirement for a mapped type is lifted + // only in C++11. + CMultiplexer(const CMultiplexer&) + : m_iID(-1) + , m_SocketMap(1024) // reserve buckets - gnu::hash_map version + , m_SndQueue(this) + , m_RcvQueue(this) + , m_pChannel(NULL) + , m_ReservedDisposal() { - if (m_pRcvQueue != NULL) - delete m_pRcvQueue; - if (m_pSndQueue != NULL) - delete m_pSndQueue; - if (m_pTimer != NULL) - delete m_pTimer; - close(); } +#endif + void resetAtFork(); void close(); void stop(); - void destroy(); + ~CMultiplexer(); + + bool removeListener(CUDT* u) { return m_RcvQueue.removeListener(u); } + int setListener(CUDT* u) { return m_RcvQueue.setListener(u); } + CUDT* getListener() { return m_RcvQueue.getListener(); } + + void configure(int32_t id, const CSrtConfig& config, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock); + + // Update the socket in the sender list according to current time. + // Already scheduled sockets with future time will be ordered after it. + sync::steady_clock::time_point updateSendNormal(CUDTSocket* s); + + // Update the socket in the sender list with high priority (should + // precede everything that is in the list, except earlier added high + // priority packets). + void updateSendFast(CUDTSocket* s); + + void removeSender(CUDT* u); }; } // namespace srt +#if USE_RECEIVER_UNIT_POOL +// Allow calling the global swap +namespace std +{ + inline void swap(::srt::CPacketUnitPool::UnitPtr& u1, ::srt::CPacketUnitPool::UnitPtr& u2) + { + u1.swap(u2); + } +} +#endif + #endif diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index e3ad5cde2..32220a8c4 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -51,13 +51,19 @@ written by #include "srt.h" #include "socketconfig.h" +#include "ofmt.h" + +using namespace hvu; // fmt namespace srt { int RcvBufferSizeOptionToValue(int val, int flightflag, int mss) { // Minimum recv buffer size is 32 packets - const int mssin_size = mss - CPacket::UDP_HDR_SIZE; + // We take the size per packet as maximum allowed for AF_INET, + // as we don't know which one is used, and this requires more + // space than AF_INET6. + const int mssin_size = mss - CPacket::udpHeaderSize(AF_INET); int bufsize; if (val > mssin_size * CSrtConfig::DEF_MIN_FLIGHT_PKT) @@ -90,9 +96,15 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { + using namespace srt::logging; const int ival = cast_optval(optval, optlen); - if (ival < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) + const int handshake_size = CHandShake::m_iContentSize + (sizeof(uint32_t) * SRT_HS_E_SIZE); + const int minval = int(CPacket::udpHeaderSize(AF_INET6) + CPacket::HDR_SIZE + handshake_size); + if (ival < minval) + { + LOGC(kmlog.Error, log << "SRTO_MSS: minimum value allowed is " << minval << " = [IPv6][UDP][SRT] headers + minimum SRT handshake"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } co.iMSS = ival; @@ -109,7 +121,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; const int fc = cast_optval(optval, optlen); if (fc < co.DEF_MIN_FLIGHT_PKT) { @@ -130,7 +142,7 @@ struct CSrtConfigSetter if (bs <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - co.iSndBufSize = bs / (co.iMSS - CPacket::UDP_HDR_SIZE); + co.iSndBufSize = bs / co.bytesPerPkt(); } }; @@ -143,7 +155,17 @@ struct CSrtConfigSetter if (val <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - co.iRcvBufSize = srt::RcvBufferSizeOptionToValue(val, co.iFlightFlagSize, co.iMSS); + co.iRcvBufSize = RcvBufferSizeOptionToValue(val, co.iFlightFlagSize, co.iMSS); + const int mssin_size = co.bytesPerPkt(); + + 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; } }; @@ -247,7 +269,7 @@ struct CSrtConfigSetter } }; -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW template<> struct CSrtConfigSetter { @@ -287,7 +309,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; #ifdef SRT_ENABLE_BINDTODEVICE using namespace std; @@ -361,7 +383,7 @@ struct CSrtConfigSetter #ifdef SRT_ENABLE_ENCRYPTION if (val == false && co.iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) { - using namespace srt_logging; + 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); } @@ -432,7 +454,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; #ifdef SRT_ENABLE_ENCRYPTION // Password must be 10-80 characters. // Or it can be empty to clear the password. @@ -459,7 +481,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; #ifdef SRT_ENABLE_ENCRYPTION const int v = cast_optval(optval, optlen); int const allowed[4] = { @@ -536,8 +558,7 @@ struct CSrtConfigSetter if (val < 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - using namespace srt::sync; - co.tdConnTimeOut = milliseconds_from(val); + co.tdConnTimeOut = sync::milliseconds_from(val); } }; @@ -614,16 +635,20 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + 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) + // We don't know at this point, how bit the payloadsize can be set, + // so we limit it to the biggest value of the two. + // When this payloadsize would be then too big to be used with given MSS and IPv6, + // this problem should be reported appropriately from srt_connect and srt_bind calls. + if (val > SRT_MAX_PLSIZE_AF_INET) { - LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE << ", maximum payload per MTU."); + LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_MAX_PLSIZE_AF_INET << ", maximum payload per MTU."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } @@ -696,7 +721,7 @@ struct CSrtConfigSetter } }; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING template<> struct CSrtConfigSetter { @@ -712,7 +737,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; const int val = cast_optval(optval, optlen); if (val < 0) @@ -735,8 +760,8 @@ struct CSrtConfigSetter { 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); + log << "SRTO_KMREFRESHRATE=0x" << fmt(km_refresh, std::hex) << ": setting SRTO_KMPREANNOUNCE=0x" + << fmt(co.uKmPreAnnouncePkt, std::hex)); } } }; @@ -746,7 +771,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; const int val = cast_optval(optval, optlen); if (val < 0) @@ -761,7 +786,8 @@ struct CSrtConfigSetter if (km_preanno > (kmref - 1) / 2) { LOGC(aclog.Error, - log << "SRTO_KMPREANNOUNCE=0x" << std::hex << km_preanno << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2) + log << "SRTO_KMPREANNOUNCE=0x" << fmt(km_preanno, std::hex) + << " exceeds KmRefresh/2, 0x" << fmt((kmref - 1) / 2, std::hex) << " - OPTION REJECTED."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } @@ -806,7 +832,7 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + using namespace srt::logging; std::string arg((const char*)optval, optlen); // Parse the configuration string prematurely SrtFilterConfig fc; @@ -826,7 +852,7 @@ struct CSrtConfigSetter throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; + size_t efc_max_payload_size = SRT_MAX_PLSIZE_AF_INET - fc.extra_size; if (co.zExpPayloadSize > efc_max_payload_size) { LOGC(aclog.Warn, @@ -839,13 +865,13 @@ struct CSrtConfigSetter } }; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + 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 @@ -889,15 +915,14 @@ struct CSrtConfigSetter } }; -#ifdef ENABLE_AEAD_API_PREVIEW +#if defined(SRT_ENABLE_AEAD) && defined(SRT_ENABLE_ENCRYPTION) template<> struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { - using namespace srt_logging; + 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); @@ -914,11 +939,22 @@ struct CSrtConfigSetter } co.iCryptoMode = val; + + } +}; +#else +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& , const void* , int ) + { + using namespace srt::logging; +#ifdef SRT_ENABLE_ENCRYPTION + LOGC(aclog.Error, log << "SRT was built without AEAD enabled."); #else LOGC(aclog.Error, log << "SRT was built without crypto module."); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif - + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } }; #endif @@ -968,7 +1004,7 @@ int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int opt DISPATCH(SRTO_MESSAGEAPI); DISPATCH(SRTO_PAYLOADSIZE); DISPATCH(SRTO_TRANSTYPE); -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING DISPATCH(SRTO_GROUPCONNECT); DISPATCH(SRTO_GROUPMINSTABLETIMEO); #endif @@ -979,10 +1015,8 @@ int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int opt DISPATCH(SRTO_IPV6ONLY); DISPATCH(SRTO_PACKETFILTER); DISPATCH(SRTO_RETRANSMITALGO); -#ifdef ENABLE_AEAD_API_PREVIEW - DISPATCH(SRTO_CRYPTOMODE); -#endif -#ifdef ENABLE_MAXREXMITBW + DISPATCH(SRTO_CRYPTOMODE); // STUB if not supported +#ifdef SRT_ENABLE_MAXREXMITBW DISPATCH(SRTO_MAXREXMITBW); #endif @@ -999,40 +1033,54 @@ int CSrtConfig::set(SRT_SOCKOPT optName, const void* optval, int optlen) return dispatchSet(optName, *this, optval, optlen); } -bool CSrtConfig::payloadSizeFits(size_t val, int /*ip_family*/, std::string& w_errmsg) ATR_NOTHROW +int CSrtConfig::extraPayloadReserve(std::string& w_info) ATR_NOTHROW { + int resv = 0; + if (!this->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(this->sPacketFilterConfig.str(), fc)) + if (!ParseFilterConfig(this->sPacketFilterConfig.str(), (fc))) { // Break silently. This should not happen - w_errmsg = "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"; - return false; + w_info = "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"; + return -1; } - const size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; - if (size_t(val) > efc_max_payload_size) - { - std::ostringstream log; - log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE << " bytes decreased by " << fc.extra_size - << " required for packet filter header"; - w_errmsg = log.str(); - return false; - } + resv += fc.extra_size; + w_info = "Packet Filter"; + } + + if (this->iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) + { + resv += HAICRYPT_AUTHTAG_MAX; + if (!w_info.empty()) + w_info += " and "; + w_info += "AES_GCM mode"; } - // Not checking AUTO to allow default 1456 bytes. - if ((this->iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) - && (val > (SRT_LIVE_MAX_PLSIZE - HAICRYPT_AUTHTAG_MAX))) + return resv; +} + +bool CSrtConfig::payloadSizeFits(size_t val, int ip_family, std::string& w_errmsg) ATR_NOTHROW +{ + int resv = extraPayloadReserve((w_errmsg)); + if (resv == -1) + return false; + + size_t valmax = CPacket::srtPayloadSize(ip_family) - resv; + + if (val > valmax) { std::ostringstream log; - log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE - << " bytes decreased by " << HAICRYPT_AUTHTAG_MAX - << " required for AES-GCM."; + log << "SRTO_PAYLOADSIZE: value " << val << "exceeds " << valmax + << " bytes"; + if (!w_errmsg.empty()) + log << " as limited by " << w_errmsg; + w_errmsg = log.str(); return false; } @@ -1040,7 +1088,7 @@ bool CSrtConfig::payloadSizeFits(size_t val, int /*ip_family*/, std::string& w_e return true; } -#if ENABLE_BONDING +#if SRT_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 diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index f44e3c24c..5c0623652 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -56,11 +56,12 @@ written by #include #endif #include +#include "srt.h" #include "haicrypt.h" #include "congctl.h" #include "packet.h" #include "handshake.h" -#include "logger_defs.h" +#include "logger_fas.h" #include "packetfilter.h" // SRT Version constants @@ -192,8 +193,8 @@ class StringStorage struct CSrtConfig: CSrtMuxerConfig { - typedef srt::sync::steady_clock::time_point time_point; - typedef srt::sync::steady_clock::duration duration; + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; static const int DEF_MSS = 1500, @@ -214,8 +215,8 @@ struct CSrtConfig: CSrtMuxerConfig // Minimum 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_SID_LENGTH = SRT_STREAMID_MAX; + static const size_t MAX_PFILTER_LENGTH = SRT_PACKETFILTER_MAX; static const size_t MAX_CONG_LENGTH = 16; int iMSS; // Maximum Segment Size, in bytes @@ -234,8 +235,13 @@ struct CSrtConfig: CSrtMuxerConfig bool bDriftTracer; int iSndTimeOut; // sending timeout in milliseconds int iRcvTimeOut; // receiving timeout in milliseconds + + // XXX NOTE: these values may be altered in the main thread + // by setting an option, also at any time after the connection, + // while they are being read by the receiver worker thread when + // calling checkTimer(). FIND A WAY TO PROTECT THEM. int64_t llMaxBW; // maximum data transfer rate (threshold) -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW int64_t llMaxRexmitBW; // maximum bandwidth limit for retransmissions (Bytes/s). #endif @@ -295,12 +301,12 @@ struct CSrtConfig: CSrtMuxerConfig , iSndBufSize(DEF_BUFFER_SIZE) , iRcvBufSize(DEF_BUFFER_SIZE) , bRendezvous(false) - , tdConnTimeOut(srt::sync::seconds_from(DEF_CONNTIMEO_S)) + , tdConnTimeOut(sync::seconds_from(DEF_CONNTIMEO_S)) , bDriftTracer(true) , iSndTimeOut(-1) , iRcvTimeOut(-1) , llMaxBW(-1) -#ifdef ENABLE_MAXREXMITBW +#ifdef SRT_ENABLE_MAXREXMITBW , llMaxRexmitBW(-1) #endif , bDataSender(false) @@ -355,7 +361,9 @@ struct CSrtConfig: CSrtMuxerConfig // This function returns the number of bytes that are allocated // for a single packet in the sender and receiver buffer. - int bytesPerPkt() const { return iMSS - int(CPacket::UDP_HDR_SIZE); } + int bytesPerPkt() const { return iMSS - int(CPacket::udpHeaderSize(AF_INET)); } + + int extraPayloadReserve(std::string& w_errmsg) ATR_NOTHROW; }; template diff --git a/srtcore/srt.h b/srtcore/srt.h index 9fd9b20c8..c6a2c9297 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -16,6 +16,12 @@ written by #ifndef INC_SRTC_H #define INC_SRTC_H +// Behavior-controlling macros that can be set in the command line: +// +// * SRT_DYNAMIC: Define when creating a dynamic library on Windows +// * SRT_EXPORTS: Define when compiling SRT as dynamic on Windows +// * SRT_NO_DEPRECATED: Disable warnings for deprecated API + #ifndef SRT_API #ifdef _WIN32 #ifdef SRT_DYNAMIC @@ -53,10 +59,19 @@ written by // You can use these constants with SRTO_MINVERSION option. #define SRT_VERSION_FEAT_HSv5 0x010300 -#if defined(__cplusplus) && __cplusplus > 201406 +#ifdef __cplusplus + +#if __cplusplus > 201406L #define SRT_HAVE_CXX17 1 +#define SRT_HAVE_CXX11 1 +#elif __cplusplus > 199711L +#define SRT_HAVE_CXX17 0 +#define SRT_HAVE_CXX11 1 #else #define SRT_HAVE_CXX17 0 +#define SRT_HAVE_CXX11 0 +#endif + #endif @@ -126,12 +141,28 @@ written by #define SRT_ATR_NODISCARD #endif +// The SRT_TEST_FORCED_CONSTANT macro enables the strict +// version of SRTSOCKET and SRTSTATUS types. This allows you +// to detect constant type violations by compiling the code +// with the C++20 compliant compiler and the following cmake +// variables set: +// * USE_CXX_STD=c++20 +// * ENFORCE_SRT_TEST_FORCED_CONSTANT=1 +#ifndef SRT_TEST_FORCED_CONSTANT +// This is normal and should be normally used. +typedef int32_t SRTSOCKET; +typedef int SRTSTATUS; +typedef int SRTRUNSTATUS; +#else +// Used for development only. +#include "../common/devel_util.h" +#endif + + #ifdef __cplusplus extern "C" { #endif -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 @@ -140,15 +171,14 @@ typedef int32_t SRTSOCKET; // socket or a socket group. static const int32_t SRTGROUP_MASK = (1 << 30); +#define SRT_IS_GROUP(id) ((int32_t(id) & SRTGROUP_MASK) != 0) + #ifdef _WIN32 typedef SOCKET SYSSOCKET; #else typedef int SYSSOCKET; #endif - -#ifndef ENABLE_BONDING -#define ENABLE_BONDING 0 -#endif +static const SYSSOCKET SYSSOCKET_INVALID = -1; typedef SYSSOCKET UDPSOCKET; @@ -226,17 +256,13 @@ typedef enum SRT_SOCKOPT { SRTO_IPV6ONLY, // IPV6_V6ONLY mode SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms] 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_GROUPCONNECT, // Set on a listener to allow group connection (SRT_ENABLE_BONDING) + SRTO_GROUPMINSTABLETIMEO, // Minimum Link Stability timeout (backup mode) in milliseconds (SRT_ENABLE_BONDING) + SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake (SRT_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; @@ -244,33 +270,29 @@ typedef enum SRT_SOCKOPT { #ifdef __cplusplus - -#if __cplusplus > 199711L // C++11 +#if SRT_HAVE_CXX11 // 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) + #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) + #define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT_DEPRECATED)value) #endif - -#else +#else // C version // deprecated enum labels are supported only since gcc 6, so in C there // will be a whole deprecated enum type, as it's not an error in C to mix // enum types enum SRT_ATR_DEPRECATED SRT_SOCKOPT_DEPRECATED { - // Dummy last option, as every entry ends with a comma SRTO_DEPRECATED_END = 0 - }; #define SRT_DEPRECATED_OPTION(value) ((enum SRT_SOCKOPT_DEPRECATED)value) #endif @@ -279,6 +301,7 @@ enum SRT_ATR_DEPRECATED SRT_SOCKOPT_DEPRECATED // stays so that it can be used in future. Example: // #define SRTO_STRICTENC SRT_DEPRECATED_OPTION(53) +// Values used for SRTO_TRANSTYPE option typedef enum SRT_TRANSTYPE { SRTT_LIVE, @@ -292,13 +315,27 @@ typedef enum SRT_TRANSTYPE // This is for MPEG TS and it's a default SRTO_PAYLOADSIZE for SRTT_LIVE. static const int SRT_LIVE_DEF_PLSIZE = 1316; // = 188*7, recommended for MPEG TS -// This is the maximum payload size for Live mode, should you have a different -// payload type than MPEG TS. -static const int SRT_LIVE_MAX_PLSIZE = 1456; // MTU(1500) - UDP.hdr(28) - SRT.hdr(16) +// DEPRECATED. Use one of these below instead. +SRT_ATR_DEPRECATED_PX static const int SRT_LIVE_MAX_PLSIZE SRT_ATR_DEPRECATED = 1456; // MTU(1500) - UDP.hdr(28) - SRT.hdr(16) + +// These constants define the maximum size of the payload +// in a single UDP packet, depending on the IP version, and +// with the default socket options, that is: +// * default 1500 bytes of MTU (see SRTO_MSS) +// * without FEC packet filter (see SRTO_PACKETFILTER) +// * without AEAD through AES-GCM (see SRTO_CRYPTOMODE) +static const int SRT_MAX_PLSIZE_AF_INET = 1456; // MTU(1500) - IPv4.hdr(20) - UDP.hdr(8) - SRT.hdr(16) +static const int SRT_MAX_PLSIZE_AF_INET6 = 1444; // MTU(1500) - IPv6.hdr(32) - UDP.hdr(8) - SRT.hdr(16) // Latency for Live transmission: default is 120 static const int SRT_LIVE_DEF_LATENCY_MS = 120; +// Maximum number of characters for SRTO_STREAMID +static const size_t SRT_STREAMID_MAX = 512; + +// Maximum number of characters for packet filter configuration, SRTO_PACKETFILTER +static const size_t SRT_PACKETFILTER_MAX = 64; + // Importrant note: please add new fields to this structure to the end and don't remove any existing fields struct CBytePerfMon { @@ -550,18 +587,13 @@ enum SRT_REJECT_REASON SRT_REJ_CONGESTION, // incompatible congestion-controller type SRT_REJ_FILTER, // incompatible packet filter SRT_REJ_GROUP, // incompatible group - SRT_REJ_TIMEOUT, // connection timeout -#ifdef ENABLE_AEAD_API_PREVIEW + SRT_REJ_TIMEOUT = 16,// connection timeout SRT_REJ_CRYPTO, // conflicting cryptographic configurations -#endif + SRT_REJ_CONFIG = 18, // socket settings on both sides collide and can't be negotiated 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)) @@ -569,69 +601,36 @@ enum SRT_REJECT_REASON #define SRT_REJC_PREDEFINED 1000 // Standard server error codes #define SRT_REJC_USERDEFINED 2000 // User defined error codes +enum SRT_CLOSE_REASON +{ + SRT_CLS_UNKNOWN, // Unset + SRT_CLS_INTERNAL, // Closed by internal reasons during connection attempt + SRT_CLS_PEER, // Received SHUTDOWN message from the peer + SRT_CLS_RESOURCE, // Problem with resource allocation + SRT_CLS_ROGUE, // Received wrong data in the packet + SRT_CLS_OVERFLOW, // Emergency close due to receiver buffer overflow + SRT_CLS_IPE, // Internal program error + SRT_CLS_API, // The application called srt_close() + SRT_CLS_FALLBACK, // Used for peer that do not support close reason feature + SRT_CLS_LATE, // Accepted-socket late-rejection or in-handshake rollback + SRT_CLS_CLEANUP, // All sockets are being closed due to srt_cleanup() call + SRT_CLS_DEADLSN, // This is an accepted socket off a dead listener + SRT_CLS_PEERIDLE, // Peer didn't send any packet for a time of SRTO_PEERIDLETIMEO + SRT_CLS_UNSTABLE, // Requested to be broken as unstable in Backup group + + SRT_CLS_E_SIZE +}; -// Logging API - specialization for SRT. - -// 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. - -// 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 management - -#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 +typedef struct SRT_CLOSE_INFO +{ + enum SRT_CLOSE_REASON agent; + enum SRT_CLOSE_REASON peer; + int64_t time; +} SRT_CLOSE_INFO; -// } SRT_LOGFA END GENERATED SECTION +#define SRT_CLSC_INTERNAL 0 +#define SRT_CLSC_USER 100 -// To make a typical int64_t size, although still use std::bitset. -// C API will carry it over. -#define SRT_LOGFA_LASTNONE 63 enum SRT_KM_STATE { @@ -639,10 +638,8 @@ enum SRT_KM_STATE 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 + SRT_KM_S_BADSECRET = 4, // Stream encrypted and wrong secret is used, cannot decrypt Keying Material + SRT_KM_S_BADCRYPTOMODE = 5 // Stream encrypted but wrong cryptographic mode is used, cannot decrypt. Since v1.5.2. }; enum SRT_EPOLL_OPT @@ -737,16 +734,33 @@ inline SRT_EPOLL_OPT operator|(SRT_EPOLL_OPT a1, SRT_EPOLL_OPT a2) return SRT_EPOLL_OPT( (int)a1 | (int)a2 ); } +static const SRTSOCKET SRT_INVALID_SOCK (-1); +static const SRTSOCKET SRT_SOCKID_CONNREQ (0); +static const SRTSTATUS SRT_ERROR (-1); +static const SRTSTATUS SRT_STATUS_OK (0); +static const SRTRUNSTATUS SRT_RUN_ERROR (-1); +static const SRTRUNSTATUS SRT_RUN_OK (0); +static const SRTRUNSTATUS SRT_RUN_ALREADY (1); + + +#else // C version + +static const SRTSOCKET SRT_INVALID_SOCK = -1; +static const SRTSOCKET SRT_SOCKID_CONNREQ = 0; +static const SRTSTATUS SRT_ERROR = -1; +static const SRTSTATUS SRT_STATUS_OK = 0; +static const SRTRUNSTATUS SRT_RUN_ERROR = -1; +static const SRTRUNSTATUS SRT_RUN_OK = 0; +static const SRTRUNSTATUS SRT_RUN_ALREADY = 1; + #endif typedef struct CBytePerfMon SRT_TRACEBSTATS; -static const SRTSOCKET SRT_INVALID_SOCK = -1; -static const int SRT_ERROR = -1; // library initialization -SRT_API int srt_startup(void); -SRT_API int srt_cleanup(void); +SRT_API SRTRUNSTATUS srt_startup(void); +SRT_API SRTSTATUS srt_cleanup(void); // // Socket operations @@ -757,33 +771,39 @@ SRT_API int srt_cleanup(void); 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_acquire (SRTSOCKET u, UDPSOCKET sys_udp_sock); +SRT_API SRTSTATUS srt_bind (SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSTATUS 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_ATR_DEPRECATED_PX static inline SRTSTATUS srt_bind_peerof(SRTSOCKET u, UDPSOCKET sys_udp_sock) SRT_ATR_DEPRECATED; +static inline SRTSTATUS srt_bind_peerof (SRTSOCKET u, UDPSOCKET sys_udp_sock) { return srt_bind_acquire(u, sys_udp_sock); } +SRT_API SRTSTATUS 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); +SRT_API SRTSTATUS 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, +SRT_API SRTSTATUS srt_connect_callback(SRTSOCKET clr, srt_connect_callback_fn* hook_fn, void* hook_opaque); +SRT_API SRTSOCKET srt_connect (SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSOCKET srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); +SRT_API SRTSOCKET 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, +SRT_API SRTSTATUS 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); -SRT_API int srt_getsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, void* optval, int* optlen); -SRT_API int srt_setsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, const void* optval, int optlen); -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); +SRT_API SRTSTATUS srt_close (SRTSOCKET u); +SRT_API SRTSTATUS srt_close_withreason(SRTSOCKET u, int reason); +SRT_API SRTSTATUS srt_close_getreason(SRTSOCKET u, SRT_CLOSE_INFO* info); +SRT_API SRTSTATUS srt_getpeername (SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS srt_getsockname (SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS srt_getsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, void* optval, int* optlen); +SRT_API SRTSTATUS srt_getsockdevname(SRTSOCKET u, char* name, size_t* namelen); +SRT_API SRTSTATUS srt_setsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, const void* optval, int optlen); +SRT_API SRTSTATUS srt_getsockflag (SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); +SRT_API SRTSTATUS srt_setsockflag (SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); + +SRT_API int srt_getmaxpayloadsize(SRTSOCKET sock); + typedef struct SRT_SocketGroupData_ SRT_SOCKGROUPDATA; @@ -858,27 +878,27 @@ SRT_API int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int // last error detection SRT_API const char* srt_getlasterror_str(void); -SRT_API int srt_getlasterror(int* errno_loc); +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 tracking // Performance monitor with Byte counters for better bitrate estimation. -SRT_API int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); +SRT_API SRTSTATUS srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // 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); +SRT_API SRTSTATUS 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); -SRT_API int srt_epoll_remove_ssock(int eid, SYSSOCKET s); -SRT_API int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); -SRT_API int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); +SRT_API int srt_epoll_create(void); +SRT_API SRTSTATUS srt_epoll_clear_usocks(int eid); +SRT_API SRTSTATUS srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); +SRT_API SRTSTATUS srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); +SRT_API SRTSTATUS srt_epoll_remove_usock(int eid, SRTSOCKET u); +SRT_API SRTSTATUS srt_epoll_remove_ssock(int eid, SYSSOCKET s); +SRT_API SRTSTATUS srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); +SRT_API SRTSTATUS 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); @@ -888,13 +908,13 @@ typedef struct SRT_EPOLL_EVENT_STR 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. + SRT_EPOLL_EVENT_STR(): fd(SRT_INVALID_SOCK), 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); SRT_API int32_t srt_epoll_set(int eid, int32_t flags); -SRT_API int srt_epoll_release(int eid); +SRT_API SRTSTATUS srt_epoll_release(int eid); // Logging control @@ -905,18 +925,16 @@ SRT_API void srt_resetlogfa(const int* fara, size_t fara_size); // This isn't predicted, will be only available in SRT C++ API. // For the time being, until this API is ready, use UDT::setlogstream. // SRT_API void srt_setlogstream(std::ostream& stream); -SRT_API void srt_setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); +SRT_API void srt_setloghandler(void* opaque, HVU_LOG_HANDLER_FN* handler); SRT_API void srt_setlogflags(int flags); SRT_API int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); 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 SRTSTATUS srt_setrejectreason(SRTSOCKET sock, int value); SRT_API const char* srt_rejectreason_str(int id); +SRT_API const char* srt_rejectreasonx_str(int id); SRT_API uint32_t srt_getversion(void); @@ -936,7 +954,7 @@ SRT_API int64_t srt_connection_time(SRTSOCKET sock); SRT_API int srt_clock_type(void); -// SRT Socket Groups API (ENABLE_BONDING) +// SRT Socket Groups API (SRT_ENABLE_BONDING) typedef enum SRT_GROUP_TYPE { @@ -966,7 +984,7 @@ struct SRT_SocketGroupData_ SRT_SOCKSTATUS sockstate; uint16_t weight; SRT_MEMBERSTATUS memberstate; - int result; + SRTSTATUS result; int token; }; @@ -985,17 +1003,38 @@ typedef struct SRT_GroupMemberConfig_ 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 SRTSTATUS 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 SRTSTATUS 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); +SRT_API SRTSOCKET srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name[], int arraysize); #ifdef __cplusplus -} +} // END: extern "C" + +// Extra C++ API + +namespace srt +{ + +SRT_API void setloglevel(hvu::logging::LogLevel::type ll); +SRT_API void addlogfa(int fa); +SRT_API void dellogfa(int 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, HVU_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 srt + #endif #endif diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index 1c489658f..af57c26bd 100644 --- a/srtcore/srt_attr_defs.h +++ b/srtcore/srt_attr_defs.h @@ -30,7 +30,9 @@ used by SRT library internally. #define HAVE_GCC 0 #endif -#if defined(__cplusplus) && __cplusplus > 199711L +#if defined(__cplusplus) && __cplusplus > 199711L \ + || (defined(_MSVC_LANG) && _MSVC_LANG > 199711L) // Some earlier versions get this wrong + #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". @@ -99,105 +101,126 @@ used by SRT library internally. // - MSVC SAL (partially). // - Other compilers: none. /////////////////////////////////////////////////////////////////////////////// + +// TSA SYMBOLS available: +// +// * SRT_TSA_CAPABILITY(x) +// The defined C++ class type has a lockable object capability. +// +// * SRT_TSA_SCOPED_CAPABILITY +// The defined C++ class type has a scoped locking capability. +// +// * SRT_TSA_GUARDED_BY(x) +// Accessing THIS object requires locking x for access. +// +// * SRT_TSA_PT_GUARDED_BY(x) +// The pointer-type field points to an object that should be guarded access by x +// +// * SRT_TSA_LOCK_ORDERS_BEFORE(...) +// THIS mutex must be locked prior to locking given mutex objects +// +// * SRT_TSA_LOCK_ORDERS_AFTER(...) +// THIS mutex must be locked next to locking given mutex objects +// +// * SRT_TSA_NEEDS_LOCKED(...) +// This function requires that given mutexes be locked prior to calling it +// +// * SRT_TSA_NEEDS_LOCKED2(...) +// Same as SRT_TSA_NEEDS_LOCKED, provided for portability with MSVC +// +// * SRT_TSA_NEEDS_LOCKED_SHARED(...) +// Same as SRT_TSA_NEEDS_LOCKED, but requires a shared lock. +// +// * SRT_TSA_WILL_LOCK(...) +// Declares that after this function has been called, it will leave given mutexes locked. +// +// * SRT_TSA_WILL_LOCK_SHARED(...) +// Like SRT_TSA_WILL_LOCK, but applies to a shared lock +// +// * SRT_TSA_WILL_UNLOCK(...) +// Declares that this function's call will leave given mutexes unlocked. +// +// * SRT_TSA_WILL_UNLOCK_SHARED(...) +// Like SRT_TSA_WILL_UNLOCK, but a shared lock. +// +// * SRT_TSA_WILL_UNLOCK_GENERIC(...) +// Like SRT_TSA_WILL_UNLOCK, but any kind of lock. +// +// * SRT_TSA_WILL_TRY_LOCK(...) +// * SRT_TSA_WILL_TRY_LOCK_SHARED(...) +// This function will try to lock and leave with locked if succeeded +// +// * SRT_TSA_NEEDS_NONLOCKED(...) +// Requires that to call this function the given mutexes must not be locked. +// +// * SRT_TSA_ASSERT_CAPABILITY(x) +// * SRT_TSA_ASSERT_SHARED_CAPABILITY(x) +// Will assert that the mutex is locked + +// * SRT_TSA_RETURN_CAPABILITY(x) +// This function will return an access to an object that is a mutex. + +// * SRT_TSA_DISABLED +// For this function the TSA will not be done. + #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; +// E.g. SRT_TSA_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) +// SRT_TSA_NEEDS_NONLOCKED(m) SRT_TSA_WILL_LOCK(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(...) // the caller must not hold the given capabilities -#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 +#define SRT_TSA_CAPABILITY(expr) +#define SRT_TSA_SCOPED_CAPABILITY +#define SRT_TSA_GUARDED_BY(expr) _Guarded_by_(expr) +#define SRT_TSA_PT_GUARDED_BY(expr) +#define SRT_TSA_LOCK_ORDERS_BEFORE(...) +#define SRT_TSA_LOCK_ORDERS_AFTER(...) +#define SRT_TSA_NEEDS_LOCKED(expr) _Requires_lock_held_(expr) +#define SRT_TSA_NEEDS_LOCKED2(expr1, expr2) _Requires_lock_held_(expr1) _Requires_lock_held_(expr2) +#define SRT_TSA_NEEDS_LOCKED_SHARED(...) +#define SRT_TSA_WILL_LOCK(expr) _Acquires_nonreentrant_lock_(expr) +#define SRT_TSA_WILL_LOCK_SHARED(...) +#define SRT_TSA_WILL_UNLOCK(expr) _Releases_lock_(expr) +#define SRT_TSA_WILL_UNLOCK_SHARED(...) +#define SRT_TSA_WILL_UNLOCK_GENERIC(...) +#define SRT_TSA_WILL_TRY_LOCK(...) _Acquires_nonreentrant_lock_(expr) +#define SRT_TSA_WILL_TRY_LOCK_SHARED(...) +#define SRT_TSA_NEEDS_NONLOCKED(...) +#define SRT_TSA_ASSERT_CAPABILITY(expr) +#define SRT_TSA_ASSERT_SHARED_CAPABILITY(x) +#define SRT_TSA_RETURN_CAPABILITY(x) +#define SRT_TSA_DISABLED #else -#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ > 5) -#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +// Common for clang supporting TCA and unsupported. +#if defined(SRT_ENABLE_CLANG_TSA) && defined(__clang__) && defined(__clang_major__) && (__clang_major__ > 5) +#define SRT_TSA_EXPR(x) __attribute__((x)) #else -#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#define SRT_TSA_EXPR(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_TSA_CAPABILITY(x) SRT_TSA_EXPR(capability(x)) +#define SRT_TSA_SCOPED_CAPABILITY SRT_TSA_EXPR(scoped_lockable) +#define SRT_TSA_GUARDED_BY(x) SRT_TSA_EXPR(guarded_by(x)) +#define SRT_TSA_PT_GUARDED_BY(x) SRT_TSA_EXPR(pt_guarded_by(x)) +#define SRT_TSA_LOCK_ORDERS_BEFORE(...) SRT_TSA_EXPR(acquired_before(__VA_ARGS__)) +#define SRT_TSA_LOCK_ORDERS_AFTER(...) SRT_TSA_EXPR(acquired_after(__VA_ARGS__)) +#define SRT_TSA_NEEDS_LOCKED(...) SRT_TSA_EXPR(requires_capability(__VA_ARGS__)) +#define SRT_TSA_NEEDS_LOCKED2(...) SRT_TSA_EXPR(requires_capability(__VA_ARGS__)) +#define SRT_TSA_NEEDS_LOCKED_SHARED(...) SRT_TSA_EXPR(requires_shared_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_LOCK(...) SRT_TSA_EXPR(acquire_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_LOCK_SHARED(...) SRT_TSA_EXPR(acquire_shared_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_UNLOCK(...) SRT_TSA_EXPR(release_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_UNLOCK_SHARED(...) SRT_TSA_EXPR(release_shared_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_UNLOCK_GENERIC(...) SRT_TSA_EXPR(release_generic_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_TRY_LOCK(...) SRT_TSA_EXPR(try_acquire_capability(__VA_ARGS__)) +#define SRT_TSA_WILL_TRY_LOCK_SHARED(...) SRT_TSA_EXPR(try_acquire_shared_capability(__VA_ARGS__)) +#define SRT_TSA_NEEDS_NONLOCKED(...) SRT_TSA_EXPR(locks_excluded(__VA_ARGS__)) +#define SRT_TSA_ASSERT_CAPABILITY(x) SRT_TSA_EXPR(assert_capability(x)) +#define SRT_TSA_ASSERT_SHARED_CAPABILITY(x) SRT_TSA_EXPR(assert_shared_capability(x)) +#define SRT_TSA_RETURN_CAPABILITY(x) SRT_TSA_EXPR(lock_returned(x)) +#define SRT_TSA_DISABLED SRT_TSA_EXPR(no_thread_safety_analysis) // The caller must not hold the given capabilities. -#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/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp index cf82467c2..90aab02f2 100644 --- a/srtcore/srt_c_api.cpp +++ b/srtcore/srt_c_api.cpp @@ -17,15 +17,19 @@ written by #include #include +#include +#include #include "srt.h" +#include "access_control.h" #include "common.h" #include "packet.h" #include "core.h" +#include "api.h" #include "utilities.h" using namespace std; using namespace srt; - +using namespace srt::logging; namespace srt { // This is provided in strerror_defs.cpp, which doesn't have @@ -37,18 +41,18 @@ const char* strerror_get_message(size_t major, size_t minor); extern "C" { -int srt_startup() { return CUDT::startup(); } -int srt_cleanup() { return CUDT::cleanup(); } +SRTRUNSTATUS srt_startup() { return CUDT::startup(); } +SRTSTATUS srt_cleanup() { return CUDT::cleanup(); } // Socket creation. SRTSOCKET srt_socket(int , int , int ) { return CUDT::socket(); } SRTSOCKET srt_create_socket() { return CUDT::socket(); } -#if ENABLE_BONDING +#if defined(SRT_ENABLE_BONDING) && SRT_ENABLE_BONDING == 1 // 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) +SRTSTATUS srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen) { return CUDT::getGroupData(socketgroup, output, inoutlen); } @@ -58,44 +62,51 @@ SRT_SOCKOPT_CONFIG* srt_create_config() return new SRT_SocketOptionObject; } -int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) +SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) { if (!config) - return -1; + return SRT_ERROR; if (!config->add(option, contents, len)) - return -1; + return SRT_ERROR; - return 0; + return SRT_STATUS_OK; } -int srt_connect_group(SRTSOCKET group, +SRTSOCKET srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name[], int arraysize) { return CUDT::connectLinks(group, name, arraysize); } +void srt_delete_config(SRT_SOCKOPT_CONFIG* in) +{ + delete in; +} + #else -SRTSOCKET srt_create_group(SRT_GROUP_TYPE) { return SRT_INVALID_SOCK; } -SRTSOCKET srt_groupof(SRTSOCKET) { return SRT_INVALID_SOCK; } -int srt_group_data(SRTSOCKET, SRT_SOCKGROUPDATA*, size_t*) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } +SRTSOCKET srt_create_group(SRT_GROUP_TYPE) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL), SRT_INVALID_SOCK; } +SRTSOCKET srt_groupof(SRTSOCKET) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL), SRT_INVALID_SOCK; } +SRTSTATUS 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); } +SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG*, SRT_SOCKOPT, const void*, int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } + +SRTSOCKET srt_connect_group(SRTSOCKET, SRT_SOCKGROUPCONFIG[], int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; } -int srt_connect_group(SRTSOCKET, SRT_SOCKGROUPCONFIG[], int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } +void srt_delete_config(SRT_SOCKOPT_CONFIG*) { } #endif SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src, const struct sockaddr* dst, int namelen) { SRT_SOCKGROUPCONFIG data; -#if ENABLE_BONDING +#if SRT_ENABLE_BONDING data.errorcode = SRT_SUCCESS; #else data.errorcode = SRT_EINVOP; #endif - data.id = -1; + data.id = SRT_INVALID_SOCK; data.token = -1; data.weight = 0; data.config = NULL; @@ -111,29 +122,29 @@ SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src, const struc 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_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } -int srt_listen(SRTSOCKET u, int backlog) { return CUDT::listen(u, backlog); } +SRTSTATUS srt_bind(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::bind(u, name, namelen); } +SRTSTATUS srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } +SRTSTATUS 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); } 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, +SRTSOCKET srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } +SRTSOCKET srt_connect_debug(SRTSOCKET u, const struct sockaddr * name, int namelen, int forced_isn) { return CUDT::connect(u, name, namelen, forced_isn); } +SRTSOCKET 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, +SRTSTATUS srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen) { +#if SRT_ENABLE_BONDING + if (CUDT::isgroup(u)) + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); +#endif + bool yes = 1; CUDT::setsockopt(u, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); @@ -143,14 +154,20 @@ int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_nam || local_name->sa_family != remote_name->sa_family) return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); - const int st = srt_bind(u, local_name, local_namelen); - if (st != 0) + const SRTSTATUS st = srt_bind(u, local_name, local_namelen); + if (st != SRT_STATUS_OK) return st; - return srt_connect(u, remote_name, remote_namelen); + // Note: srt_connect may potentially return a socket value if it is used + // to connect a group. But rendezvous is not supported for groups. + const SRTSOCKET sst = srt_connect(u, remote_name, remote_namelen); + if (sst == SRT_INVALID_SOCK) + return SRT_ERROR; + + return SRT_STATUS_OK; } -int srt_close(SRTSOCKET u) +SRTSTATUS srt_close(SRTSOCKET u) { SRT_SOCKSTATUS st = srt_getsockstate(u); @@ -159,24 +176,54 @@ int srt_close(SRTSOCKET u) (st == SRTS_CLOSING) ) { // It's closed already. Do nothing. - return 0; + return SRT_STATUS_OK; } - return CUDT::close(u); + return CUDT::close(u, SRT_CLS_API); +} + +SRTSTATUS srt_close_withreason(SRTSOCKET u, int reason) +{ + SRT_SOCKSTATUS st = srt_getsockstate(u); + + if ((st == SRTS_NONEXIST) || + (st == SRTS_CLOSED) || + (st == SRTS_CLOSING) ) + { + // It's closed already. Do nothing. + return SRT_STATUS_OK; + } + + if (reason < SRT_CLSC_USER) + reason = SRT_CLS_API; + + return CUDT::close(u, reason); +} + +SRTSTATUS srt_close_getreason(SRTSOCKET u, SRT_CLOSE_INFO* info) +{ + if (!info || u == SRT_INVALID_SOCK) + return SRT_ERROR; + + return CUDT::uglobal().getCloseReason(u, *info); } -int srt_getpeername(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getpeername(u, name, namelen); } -int srt_getsockname(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getsockname(u, name, namelen); } -int srt_getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen) +SRTSTATUS srt_getpeername(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getpeername(u, name, namelen); } +SRTSTATUS srt_getsockname(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getsockname(u, name, namelen); } +SRTSTATUS srt_getsockdevname(SRTSOCKET u, char* devname, size_t * devnamelen) +{ return CUDT::getsockdevname(u, devname, devnamelen); } + +SRTSTATUS srt_getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen) { return CUDT::getsockopt(u, level, optname, optval, optlen); } -int srt_setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen) +SRTSTATUS srt_setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen) { return CUDT::setsockopt(u, level, optname, optval, optlen); } - -int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen) +SRTSTATUS srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen) { return CUDT::getsockopt(u, 0, opt, optval, optlen); } -int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen) +SRTSTATUS srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen) { return CUDT::setsockopt(u, 0, opt, optval, optlen); } +int srt_getmaxpayloadsize(SRTSOCKET u) { return CUDT::getMaxPayloadSize(u); } + 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); } @@ -185,12 +232,12 @@ int64_t srt_sendfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t siz { if (!path || !offset ) { - return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } fstream ifs(path, ios::binary | ios::in); if (!ifs) { - return CUDT::APIError(MJ_FILESYSTEM, MN_READFAIL, 0); + return CUDT::APIError(MJ_FILESYSTEM, MN_READFAIL, 0).as(); } int64_t ret = CUDT::sendfile(u, ifs, *offset, size, block); ifs.close(); @@ -201,12 +248,12 @@ int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t siz { if (!path || !offset ) { - return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } fstream ofs(path, ios::binary | ios::out); if (!ofs) { - return CUDT::APIError(MJ_FILESYSTEM, MN_WRAVAIL, 0); + return CUDT::APIError(MJ_FILESYSTEM, MN_WRAVAIL, 0).as(); } int64_t ret = CUDT::recvfile(u, ofs, *offset, size, block); ofs.close(); @@ -247,12 +294,12 @@ int srt_recvmsg2(SRTSOCKET u, char * buf, int len, SRT_MSGCTRL *mctrl) return CUDT::recvmsg2(u, buf, len, (mignore)); } -const char* srt_getlasterror_str() { return UDT::getlasterror().getErrorMessage(); } +const char* srt_getlasterror_str() { return srt::CUDT::getlasterror().getErrorMessage(); } int srt_getlasterror(int* loc_errno) { if ( loc_errno ) - *loc_errno = UDT::getlasterror().getErrno(); + *loc_errno = srt::CUDT::getlasterror().getErrno(); return CUDT::getlasterror().getErrorCode(); } @@ -264,24 +311,24 @@ const char* srt_strerror(int code, int /*err ignored*/) void srt_clearlasterror() { - UDT::getlasterror().clear(); + srt::CUDT::getlasterror().clear(); } -int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear) { return CUDT::bstats(u, perf, 0!= clear); } -int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous) { return CUDT::bstats(u, perf, 0!= clear, 0!= instantaneous); } +SRTSTATUS srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear) { return CUDT::bstats(u, perf, 0!= clear); } +SRTSTATUS srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous) { return CUDT::bstats(u, perf, 0!= clear, 0!= instantaneous); } SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u) { return SRT_SOCKSTATUS((int)CUDT::getsockstate(u)); } // event mechanism int srt_epoll_create() { return CUDT::epoll_create(); } -int srt_epoll_clear_usocks(int eit) { return CUDT::epoll_clear_usocks(eit); } +SRTSTATUS 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); } +SRTSTATUS srt_epoll_add_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_add_usock(eid, u, events); } -int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) +SRTSTATUS srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) { int flag = 0; @@ -295,15 +342,15 @@ int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) return CUDT::epoll_add_ssock(eid, s, &flag); } -int srt_epoll_remove_usock(int eid, SRTSOCKET u) { return CUDT::epoll_remove_usock(eid, u); } -int srt_epoll_remove_ssock(int eid, SYSSOCKET s) { return CUDT::epoll_remove_ssock(eid, s); } +SRTSTATUS srt_epoll_remove_usock(int eid, SRTSOCKET u) { return CUDT::epoll_remove_usock(eid, u); } +SRTSTATUS srt_epoll_remove_ssock(int eid, SYSSOCKET s) { return CUDT::epoll_remove_ssock(eid, s); } -int srt_epoll_update_usock(int eid, SRTSOCKET u, const int * events) +SRTSTATUS srt_epoll_update_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_update_usock(eid, u, events); } -int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int * events) +SRTSTATUS srt_epoll_update_ssock(int eid, SYSSOCKET s, const int * events) { int flag = 0; @@ -322,8 +369,8 @@ int srt_epoll_wait( SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum) - { - return UDT::epoll_wait2( +{ + return srt::CUDT::epoll_wait2( eid, readfds, rnum, writefds, wnum, msTimeOut, @@ -332,7 +379,7 @@ int srt_epoll_wait( int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { - return UDT::epoll_uwait( + return srt::CUDT::epoll_uwait( eid, fdsSet, fdsSize, @@ -344,36 +391,39 @@ int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTim // Pass -1 to not change anything (but still get the current flag value). int32_t srt_epoll_set(int eid, int32_t flags) { return CUDT::epoll_set(eid, flags); } -int srt_epoll_release(int eid) { return CUDT::epoll_release(eid); } +SRTSTATUS srt_epoll_release(int eid) { return CUDT::epoll_release(eid); } void srt_setloglevel(int ll) { - UDT::setloglevel(srt_logging::LogLevel::type(ll)); + logger_config().set_maxlevel(hvu::logging::LogLevel::type(ll)); } void srt_addlogfa(int fa) { - UDT::addlogfa(srt_logging::LogFA(fa)); + int farray[1] = { fa }; + logger_config().enable_fa(farray, 1, true); } void srt_dellogfa(int fa) { - UDT::dellogfa(srt_logging::LogFA(fa)); + int farray[1] = { fa }; + logger_config().enable_fa(farray, 1, false); } void srt_resetlogfa(const int* fara, size_t fara_size) { - UDT::resetlogfa(fara, fara_size); + logger_config().enable_fa(0, 0, false); + logger_config().enable_fa(fara, fara_size, true); } -void srt_setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) +void srt_setloghandler(void* opaque, HVU_LOG_HANDLER_FN* handler) { - UDT::setloghandler(opaque, handler); + logger_config().set_handler(opaque, handler); } void srt_setlogflags(int flags) { - UDT::setlogflags(flags); + logger_config().set_flags(flags); } int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes) @@ -386,17 +436,17 @@ int srt_getrejectreason(SRTSOCKET sock) return CUDT::rejectReason(sock); } -int srt_setrejectreason(SRTSOCKET sock, int value) +SRTSTATUS srt_setrejectreason(SRTSOCKET sock, int value) { return CUDT::rejectReason(sock, value); } -int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { return CUDT::installAcceptHook(lsn, hook, opaq); } -int srt_connect_callback(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS srt_connect_callback(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { return CUDT::installConnectHook(lsn, hook, opaq); } @@ -421,6 +471,8 @@ int srt_clock_type() return SRT_SYNC_CLOCK; } +// NOTE: crypto mode is defined regardless of the setting of enabled AEAD. This +// can only block the symbol, but it doesn't change the symbol layout. const char* const srt_rejection_reason_msg [] = { "Unknown or erroneous", "Error in system calls", @@ -438,38 +490,18 @@ const char* const srt_rejection_reason_msg [] = { "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 + "Connection timeout", + "Crypto mode", + "Invalid configuration" }; const char* srt_rejectreason_str(int id) { + if (id == SRT_REJX_FALLBACK) + { + return "Application fallback (default) rejection reason"; + } + if (id >= SRT_REJC_PREDEFINED) { return "Application-defined rejection reason"; @@ -477,8 +509,65 @@ const char* srt_rejectreason_str(int id) static const size_t ra_size = Size(srt_rejection_reason_msg); if (size_t(id) >= ra_size) + { + HLOGC(gglog.Error, log << "Invalid rejection code #" << id); return srt_rejection_reason_msg[0]; + } return srt_rejection_reason_msg[id]; } +// NOTE: values in the first field must be sorted by numbers. +static pair srt_rejectionx_reason_msg [] = { + + // Internal + make_pair(SRT_REJX_FALLBACK, "Default fallback reason"), + make_pair(SRT_REJX_KEY_NOTSUP, "Unsupported streamid key"), + make_pair(SRT_REJX_FILEPATH, "Incorrect resource path"), + make_pair(SRT_REJX_HOSTNOTFOUND, "Unrecognized host under h key"), + + // HTTP adopted codes + make_pair(SRT_REJX_BAD_REQUEST, "Bad request"), + make_pair(SRT_REJX_UNAUTHORIZED, "Unauthorized"), + make_pair(SRT_REJX_OVERLOAD, "Server overloaded or underpaid"), + make_pair(SRT_REJX_FORBIDDEN, "Resource access forbidden"), + make_pair(SRT_REJX_BAD_MODE, "Bad mode specified with m key"), + make_pair(SRT_REJX_UNACCEPTABLE, "Unacceptable parameters for specified resource"), + make_pair(SRT_REJX_CONFLICT, "Access conflict for a locked resource"), + make_pair(SRT_REJX_NOTSUP_MEDIA, "Unsupported media type specified with t key"), + make_pair(SRT_REJX_LOCKED, "Resource locked for any access"), + make_pair(SRT_REJX_FAILED_DEPEND, "Dependent session id expired"), + make_pair(SRT_REJX_ISE, "Internal server error"), + make_pair(SRT_REJX_GW, "Gateway target rejected connection"), + make_pair(SRT_REJX_DOWN, "Service is down for maintenance"), + make_pair(SRT_REJX_VERSION, "Unsupported version for the request"), + make_pair(SRT_REJX_NOROOM, "Storage capacity exceeded"), +}; + +struct FCompareItems +{ + bool operator()(const pair& a, int b) + { + return a.first < b; + } +}; + +const char* srt_rejectreasonx_str(int id) +{ + if (id < SRT_REJX_FALLBACK) + { + return "System-defined rejection reason (not extended)"; + } + + pair* begin = srt_rejectionx_reason_msg; + pair* end = begin + Size(srt_rejectionx_reason_msg); + pair* found = lower_bound(begin, end, id, FCompareItems()); + + if (found == end || found->first != id) + { + return "Undefined extended rejection code"; + } + + return found->second; +} + } diff --git a/srtcore/srt_sync_cxx98.h b/srtcore/srt_sync_cxx98.h new file mode 100644 index 000000000..b9ed40159 --- /dev/null +++ b/srtcore/srt_sync_cxx98.h @@ -0,0 +1,31 @@ +/* + * 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. + *****************************************************************************/ + +// This file should provide the standard way of sync facilities (mutex, scoped lock, atomic, threads) +// This requires, however, at least C++11. For C++98 and C++03 you need to provide some external +// facility. + +#ifndef INC_HVU_SYNC_H +#define INC_HVU_SYNC_H + +#include "sync.h" +#include "atomic.h" +#define HVU_EXT_MUTEX srt::sync::Mutex +#define HVU_EXT_LOCKGUARD srt::sync::ScopedLock +#define HVU_EXT_ATOMIC srt::sync::atomic +#define HVU_EXT_THIS_THREAD srt::sync::this_thread + +#endif + diff --git a/srtcore/sync.cpp b/srtcore/sync.cpp index ff16a7146..bbf1754df 100644 --- a/srtcore/sync.cpp +++ b/srtcore/sync.cpp @@ -14,21 +14,35 @@ #include #include "sync.h" #include "srt.h" -#include "srt_compat.h" +#include "hvu_compat.h" +#include "hvu_threadname.h" #include "logging.h" +#include "logger_fas.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 +// This condition is defined in the Linux manpage for rand_r +#elif _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE +#define SRT_HAVE_RAND_R 1 +#else +#define SRT_HAVE_RAND_R 0 #endif -namespace srt_logging -{ - extern Logger inlog; -} -using namespace srt_logging; +// IMPORTANT NOTE on the thread-safe initialization of static local variables: +// 1. C++11 guarantees thread-safe deadlock-free initialization +// 2. C++03 STANDARD doesn't give such a guarantee, however compilers do: +// - gcc, clang, Intel: Implemented and turned on by default, unless +// overridden by -fno-threadsafe-statics; gcc supports it since version 4.0 +// released in 2005 +// - Microsoft Visual Studio: this is supported since VS 2019 +// +// Therefore this code relies everywhere on that the static locals are initialized +// safely in the multithreaded environment and pthread_once() is not in use. + +using namespace srt::logging; using namespace std; namespace srt @@ -38,6 +52,7 @@ namespace sync std::string FormatTime(const steady_clock::time_point& timestamp) { + using namespace hvu; if (is_zero(timestamp)) { // Use special string for 0 @@ -50,18 +65,26 @@ std::string FormatTime(const steady_clock::time_point& timestamp) 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; + steady_clock::time_point frac = timestamp - seconds_from(total_sec); + ofmtbufstream 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]"; + out << days << OFMT_RAWSTR("D "); + + fmtc d02 = fmtc().dec().fillzero().width(2), + dec0 = fmtc().dec().fillzero().width(decimals); + + out << fmt(hours, d02) << OFMT_RAWSTR(":") + << fmt(minutes, d02) << OFMT_RAWSTR(":") + << fmt(seconds, d02) << OFMT_RAWSTR(".") + << fmt(frac.time_since_epoch().count(), dec0) + << OFMT_RAWSTR(" [STDY]"); return out.str(); } std::string FormatTimeSys(const steady_clock::time_point& timestamp) { + using namespace hvu; + 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); @@ -70,31 +93,49 @@ std::string FormatTimeSys(const steady_clock::time_point& timestamp) 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]"; + size_t tmp_size = strftime(tmp_buf, 512, "%X.", &tm); + // Mind the theoretically possible error case + if (!tmp_size) + return "