diff --git a/.appveyor.yml b/.appveyor.yml index 96b785d93..1254c9c16 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -3,9 +3,9 @@ configuration: - Debug image: + - Visual Studio 2022 - Visual Studio 2019 - Visual Studio 2015 - - Visual Studio 2013 platform: - x64 @@ -14,12 +14,8 @@ platform: build_script: - ps: $VSIMG = $Env:APPVEYOR_BUILD_WORKER_IMAGE; $CNFG = $Env:CONFIGURATION # use a few differing arguments depending on VS version to exercise different options during builds - - ps: if ($VSIMG -match '2019' -and $CNFG -eq "Release") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON -UNIT_TESTS ON -BONDING ON } - - ps: if ($VSIMG -match '2019' -and $CNFG -eq "Debug") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON } - - ps: if ($VSIMG -match '2015' -and $CNFG -eq "Release") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON -UNIT_TESTS ON -BONDING ON} - - ps: if ($VSIMG -match '2015' -and $CNFG -eq "Debug") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS OFF } - - ps: if ($VSIMG -match '2013' -and $CNFG -eq "Release") { .\scripts\build-windows.ps1 -CXX11 OFF -BUILD_APPS ON } - - ps: if ($VSIMG -match '2013' -and $CNFG -eq "Debug") { Exit-AppveyorBuild } # just skip 2013 debug build for speed + - ps: if ($CNFG -eq "Release") { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON -UNIT_TESTS ON -BONDING ON} + - ps: if ($CNFG -eq "Debug") { if ($VSIMG -match '2015') { .\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS OFF } else {.\scripts\build-windows.ps1 -STATIC_LINK_SSL ON -BUILD_APPS ON }} test_script: - ps: if ( $Env:RUN_UNIT_TESTS ) { cd ./_build; ctest -E "TestIPv6.v6_calls_v4" --extra-verbose -C $Env:CONFIGURATION; cd ../ } diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml new file mode 100644 index 000000000..2c05cc06b --- /dev/null +++ b/.github/workflows/abi.yml @@ -0,0 +1,61 @@ +name: ABI checks + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + SRT_BASE: v1.5.0 + +jobs: + build: + name: ABI checks + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v3 + with: + path: pull_request + - name: configure + run: | + cd pull_request + mkdir _build && cd _build + cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_UNITTESTS=ON ../ + - name: build + run: | + sudo apt install -y abi-dumper + cd pull_request/_build && cmake --build ./ + make install DESTDIR=./installdir + SRT_TAG_VERSION=$(cat version.h |grep SRT_VERSION_MINOR |head -n1 |awk {'print $3'}) + abi-dumper libsrt.so -o libsrt-pr.dump -public-headers installdir/usr/local/include/srt/ -lver 0 + SRT_BASE="v1.$SRT_TAG_VERSION.0" + echo "SRT_BASE=$SRT_BASE" >> "$GITHUB_ENV" + - uses: actions/checkout@v3 + with: + path: tag + ref: ${{ env.SRT_BASE }} + - name: configure_tag + run: | + echo $SRT_TAG_VERSION + cd tag + mkdir _build && cd _build + cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_UNITTESTS=ON ../ + - name: build_tag + run: | + cd tag + cd _build && cmake --build ./ + make install DESTDIR=./installdir + abi-dumper libsrt.so -o libsrt-tag.dump -public-headers installdir/usr/local/include/srt/ -lver 1 + - name: abi-check + run: | + git clone https://github.com/lvc/abi-compliance-checker.git + cd abi-compliance-checker && sudo make install && cd ../ + abi-compliance-checker -l libsrt -old tag/_build/libsrt-tag.dump -new pull_request/_build/libsrt-pr.dump + RES=$? + if (( $RES != 0 )) + then + echo "ABI/API Compatibility check failed with value $?" + exit $RES + fi diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index 500ff1beb..aa74e1fce 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -5,19 +5,34 @@ on: branches: [ master ] pull_request: branches: [ master ] - + types: [opened, synchronize, reopened] jobs: build: name: ubuntu runs-on: ubuntu-20.04 - + env: + BUILD_WRAPPER_OUT_DIR: sonar-output # Directory where build-wrapper output will be placed steps: - uses: actions/checkout@v3 + - name: Install sonar-scanner and build-wrapper + uses: sonarsource/sonarcloud-github-c-cpp@v2 - name: configure run: | mkdir _build && cd _build - cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON + cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON - name: build - run: cd _build && cmake --build ./ + run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . - name: test - run: cd _build && ctest --extra-verbose + run: | + cd _build && ctest --extra-verbose + - name: codecov + run: | + source ./scripts/collect-gcov.sh + bash <(curl -s https://codecov.io/bash) + - name: Run SonarCloud Scan for C and C++ + if: ${{ !github.event.pull_request.head.repo.fork }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + # Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options. + run: sonar-scanner --define sonar.cfamily.build-wrapper-output=_build/"${{ env.BUILD_WRAPPER_OUT_DIR }}" diff --git a/.github/workflows/s390x-focal.yaml b/.github/workflows/s390x-focal.yaml new file mode 100644 index 000000000..f1b6c7508 --- /dev/null +++ b/.github/workflows/s390x-focal.yaml @@ -0,0 +1,41 @@ +name: QEMU to run s390x-focal + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + Tests: + runs-on: ubuntu-latest + steps: + - name: Setup multiarch/qemu-user-static + run: | + docker run --rm --privileged multiarch/qemu-user-static:register --reset + - name: ubuntu-core:s390x-focal + uses: docker://multiarch/ubuntu-core:s390x-focal + with: + args: > + bash -c + "uname -a && + lscpu | grep Endian + " + - name: Checkout + uses: actions/checkout@v3 + - name: configure + uses: docker://multiarch/ubuntu-core:s390x-focal + with: + args: > + bash -c + "apt-get -y update && + export DEBIAN_FRONTEND=noninteractive && + export TZ=Etc/UTC && + apt-get -y install tzdata && + uname -a && + lscpu | grep Endian && + apt-get -y install cmake g++ libssl-dev git && + mkdir _build && cd _build && + cmake ../ -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON && + cmake --build ./ && + ./test-srt -disable-ipv6" diff --git a/.travis.yml b/.travis.yml index 72df01bf1..94d5dac3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,6 @@ addons: - build-essential - libmbedtls-dev - gdb - sonarcloud: - organization: "haivision" - token: - secure: "wJZC0kyyjuf4SZyonZ6p/5Ga9asEqSnKWF9NpRbu6S6ceERO7vbebuSJF5qX3A6ivPuw0TTk5WASOdnvIyfA28FU/D0MWRdH8K7T3w77wdE9EgAEYTUXzdrbzJY18+9pxjljHwWXWALPSGf3MClg4irWrdk1e6uHK+68R39+ZvBGBFpWeeZy/+at9+xwhtAGKBlSHe8zc+3wPxuYdvviLVJ25qbpNmnzkUR0X89G+UBl90raCPSN32EHFdImHZ5DxfEQQJgZFRjzQUY4EW/iYwaMel7jufAq0ClgV4psKujl9Lz8cPqx3WgqRfJyiIthOMTsac7G4zAw8LK2CI0VsssBp0JalLXaumi6vG7o6c3rIwKckzSKccq3pHa7h45praIVVn9s3nq+Q/JGA11FMkKQxdQtmwgFsLhbi6ZxabgsUi5KtWoWY2z6MgpJuROuAjNxZi9XJzUoJs7zSTUtRRW7V8Q2lRiOnknYh25N6TCA5bpyy1EZmRdJErm071YNI9P01gbFz5137FWJFiJzro9TGF0KoHSGiCIdUt3WlMzwr/i/wFLxFBQOZQ2rjTXvhs4hxONxMZV3gzxA1NdLaf9i5Mh6jxVMV+ujaRSV7JmPGzxqiAlpT9cJUhTCYuar9diLLeDrpe7RawEZR8V1xVDQ7yT8ruDNQ78VbSn/sC0=" homebrew: update: false packages: @@ -25,9 +21,7 @@ matrix: - os: linux env: - BUILD_TYPE=Debug - - BUILD_OPTS='-DENABLE_CODE_COVERAGE=ON -DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' - - RUN_SONARCUBE=1 - - RUN_CODECOV=1 + - BUILD_OPTS='-DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' - env: - BUILD_TYPE=Debug - BUILD_OPTS='-DENABLE_LOGGING=OFF -DUSE_ENCLIB=mbedtls -DENABLE_MONOTONIC_CLOCK=ON -DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' @@ -81,17 +75,7 @@ script: export PKG_CONFIG_PATH=$(brew --prefix openssl)"/lib/pkgconfig"; cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="ON"; fi - - echo "TRAVIS_REPO_SLUG=$TRAVIS_REPO_SLUG" - - echo "TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST" - - if [[ "$TRAVIS_REPO_SLUG" != "Haivision/srt" || "$TRAVIS_PULL_REQUEST" -gt 0 ]]; then - export RUN_SONARCUBE=0; - fi - - echo "RUN_SONARCUBE=$RUN_SONARCUBE" - - if (( "$RUN_SONARCUBE" )); then - build-wrapper-linux-x86-64 --out-dir bw-output make; - else - make -j$(nproc); - fi + - make -j$(nproc); - if [ "$TRAVIS_COMPILER" != "x86_64-w64-mingw32-g++" ]; then ulimit -c unlimited; ./test-srt -disable-ipv6; @@ -99,11 +83,3 @@ script: if [ -f core ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; test $SUCCESS == 0; fi -after_success: - - if (( "$RUN_CODECOV" )); then - source ./scripts/collect-gcov.sh; - bash <(curl -s https://codecov.io/bash); - fi - - if (( "$RUN_SONARCUBE" )); then - sonar-scanner -D"sonar.cfamily.gcov.reportPath=."; - fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 82b0e3cff..9a2fc63d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ # cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) -set (SRT_VERSION 1.5.3) +set (SRT_VERSION 1.5.4) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(haiUtil) # needed for set_version_variables @@ -38,7 +38,8 @@ 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 "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)) @@ -73,7 +74,12 @@ if (NOT MICROSOFT) # that ENABLE_DEBUG is set as it should. if (ENABLE_DEBUG EQUAL 2) set (CMAKE_BUILD_TYPE "RelWithDebInfo") - add_definitions(-DNDEBUG) + 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") @@ -232,12 +238,12 @@ if (NOT USE_ENCLIB) message("NOTE: USE_GNUTLS is deprecated. Use -DUSE_ENCLIB=gnutls instead.") set (USE_ENCLIB gnutls) else() - set (USE_ENCLIB openssl) + set (USE_ENCLIB openssl-evp) endif() endif() set(USE_ENCLIB "${USE_ENCLIB}" CACHE STRING "The crypto library that SRT uses") -set_property(CACHE USE_ENCLIB PROPERTY STRINGS "openssl" "gnutls" "mbedtls" "botan") +set_property(CACHE USE_ENCLIB PROPERTY STRINGS "openssl" "openssl-evp" "gnutls" "mbedtls" "botan") # Make sure DLLs and executabes go to the same path regardles of subdirectory set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -360,11 +366,11 @@ if (ENABLE_ENCRYPTION) set (SSL_INCLUDE_DIRS ${MBEDTLS_INCLUDE_DIR}) set (SSL_LIBRARIES ${MBEDTLS_LIBRARIES}) endif() + if (WIN32) + set (SSL_LIBRARIES ${SSL_LIBRARIES} bcrypt) + endif() if ("${SSL_LIBRARIES}" STREQUAL "") set (SSL_LIBRARIES mbedtls mbedcrypto) - if (WIN32) - set (SSL_LIBRARIES ${SSL_LIBRARIES} bcrypt) - endif() endif() message(STATUS "SSL enforced mbedtls: -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") @@ -1377,7 +1383,6 @@ if (ENABLE_APPS) # srt-multiplex temporarily blocked #srt_add_application(srt-multiplex ${VIRTUAL_srtsupport}) srt_add_application(srt-tunnel ${VIRTUAL_srtsupport}) - target_compile_definitions(srt-tunnel PUBLIC -DSRT_ENABLE_VERBOSE_LOCK) endif() if (ENABLE_TESTING) @@ -1415,7 +1420,6 @@ if (ENABLE_APPS) srt_add_testprogram(srt-test-relay) srt_make_application(srt-test-relay) - target_compile_definitions(srt-test-relay PUBLIC -DSRT_ENABLE_VERBOSE_LOCK) srt_add_testprogram(srt-test-multiplex) srt_make_application(srt-test-multiplex) @@ -1522,15 +1526,11 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) ) #set_tests_properties(test-srt PROPERTIES RUN_SERIAL TRUE) else() - gtest_add_tests( - TEST_LIST tests_srt - TARGET test-srt - ) set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) + gtest_discover_tests(test-srt) endif() enable_testing() - endif() diff --git a/README.md b/README.md index 8b263aebb..0bf810144 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ [![License: MPLv2.0][license-badge]](./LICENSE) [![Latest release][release-badge]][github releases] +[![Quality Gate Status][sonarcloud-badge]][sonarcloud-project] [![codecov][codecov-badge]][codecov-project] [![Build Status Linux and macOS][travis-badge]][travis] [![Build Status Windows][appveyor-badge]][appveyor] @@ -225,6 +226,9 @@ By contributing code to the SRT project, you agree to license your contribution [ConanCenter-package]: https://repology.org/project/srt/versions [ConanCenter-badge]: https://repology.org/badge/version-for-repo/conancenter/srt.svg +[sonarcloud-project]: https://sonarcloud.io/project/overview?id=srt +[sonarcloud-badge]: https://sonarcloud.io/api/project_badges/measure?project=srt&metric=alert_status + [codecov-project]: https://codecov.io/gh/haivision/srt [codecov-badge]: https://codecov.io/gh/haivision/srt/branch/master/graph/badge.svg diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index a4ed93a29..0a918e6b0 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -65,7 +65,7 @@ #include #include - +#include "srt_compat.h" #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" diff --git a/apps/srt-tunnel.cpp b/apps/srt-tunnel.cpp index 569a0c26e..90bd43eae 100644 --- a/apps/srt-tunnel.cpp +++ b/apps/srt-tunnel.cpp @@ -27,6 +27,7 @@ #include #include +#include "srt_compat.h" #include "apputil.hpp" // CreateAddr #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" diff --git a/apps/verbose.cpp b/apps/verbose.cpp index 58cf83fbe..4ea3b47dc 100644 --- a/apps/verbose.cpp +++ b/apps/verbose.cpp @@ -9,14 +9,13 @@ */ #include "verbose.hpp" +#include "sync.h" // srt::sync namespace Verbose { bool on = false; std::ostream* cverb = &std::cerr; -#if SRT_ENABLE_VERBOSE_LOCK - std::mutex vlock; -#endif + srt::sync::Mutex vlock; Log& Log::operator<<(LogNoEol) { @@ -28,19 +27,16 @@ namespace Verbose return *this; } -#if SRT_ENABLE_VERBOSE_LOCK Log& Log::operator<<(LogLock) { lockline = true; return *this; } -#endif Log::~Log() { if (on && !noeol) { -#if SRT_ENABLE_VERBOSE_LOCK if (lockline) { // Lock explicitly, as requested, and wait for the opportunity. @@ -48,7 +44,7 @@ namespace Verbose } else if (vlock.try_lock()) { - // Successfully locked, so unlock immediately, locking wasn't requeted. + // Successfully locked, so unlock immediately, locking wasn't requested. vlock.unlock(); } else @@ -62,15 +58,12 @@ namespace Verbose vlock.lock(); vlock.unlock(); } -#endif (*cverb) << std::endl; -#if SRT_ENABLE_VERBOSE_LOCK // If lockline is set, the lock was requested and WAS DONE, so unlock. // Otherwise locking WAS NOT DONE. if (lockline) vlock.unlock(); -#endif } } } diff --git a/apps/verbose.hpp b/apps/verbose.hpp index 10591888b..e3d20732a 100644 --- a/apps/verbose.hpp +++ b/apps/verbose.hpp @@ -12,9 +12,7 @@ #define INC_SRT_VERBOSE_HPP #include -#if SRT_ENABLE_VERBOSE_LOCK -#include -#endif +#include "atomic.h" namespace Verbose { @@ -23,22 +21,22 @@ extern bool on; extern std::ostream* cverb; struct LogNoEol { LogNoEol() {} }; -#if SRT_ENABLE_VERBOSE_LOCK struct LogLock { LogLock() {} }; -#endif class Log { bool noeol = false; -#if SRT_ENABLE_VERBOSE_LOCK - bool lockline = false; -#endif + srt::sync::atomic lockline; // Disallow creating dynamic objects - void* operator new(size_t); + void* operator new(size_t) = delete; public: + Log() {} + Log(const Log& ) {} + + template Log& operator<<(const V& arg) { @@ -50,9 +48,7 @@ class Log } Log& operator<<(LogNoEol); -#if SRT_ENABLE_VERBOSE_LOCK Log& operator<<(LogLock); -#endif ~Log(); }; @@ -96,11 +92,16 @@ inline void Verb(Args&&... args) Verbose::Print(log, args...); } +template +inline void Verror(Args&&... args) +{ + Verbose::ErrLog log; + Verbose::Print(log, args...); +} + // Manipulator tags static const Verbose::LogNoEol VerbNoEOL; -#if SRT_ENABLE_VERBOSE_LOCK static const Verbose::LogLock VerbLock; -#endif #endif diff --git a/codecov.yml b/codecov.yml index f91e5c1fe..2716c7f60 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,3 +6,7 @@ coverage: threshold: null patch: false changes: false +ignore: + - "testing" + - "apps" + - "example" \ No newline at end of file diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 368216005..753395af6 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -174,6 +174,8 @@ Since SRT v1.5.0. | [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | The connection was rejected due to an unsupported or mismatching encryption mode | | | | | +See the full list in [Rejection Reason Codes](./rejection-codes.md). +

Error Codes

| *Error Code* | *Description* | @@ -678,19 +680,19 @@ 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. The -actual connection reporter is a socket, like before, but once you call -`srt_accept` and receive this group ID, it is the group considered connected, -and any other member connections of the same group will be handled in the -background. +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. While the group is connected, potentially new -connections may be added and any existing ones get broken at any time. The -information about all member connections, that are active 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) +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 diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index ef6f87513..a06f8556d 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -58,7 +58,7 @@ Exchange for the initial key is done in the handshake. - `SRT_KM_S_SECURED` (`2`): KM exchange was successful and the data will be sent encrypted and will be decrypted by the receiver. This state is only possible on -both sides in both directions simultaneously. +both sides in both directions simultaneously. Any unencrypted packet will be dropped by the receiver. - `SRT_KM_S_NOSECRET` (`3`): If this state is in the sending direction (`SRTO_SNDKMSTATE`), then it means that the sending party has set a passphrase, but the peer did not. @@ -301,7 +301,8 @@ connection is rejected - **however** you may also change the value of this option for the accepted socket in the listener callback (see `srt_listen_callback`) if an appropriate instruction was given in the Stream ID. -Currently supported congestion controllers are designated as "live" and "file" +Currently supported congestion controllers are designated as "live" and "file", +which correspond to the Live and File modes. Note that it is not recommended to change this option manually, but you should rather change the whole set of options using the [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) option. @@ -772,7 +773,8 @@ for more details. | `SRTO_LATENCY` | 1.0.2 | pre | `int32_t` | ms | 120 * | 0.. | RW | GSD | This option sets both [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY) and [`SRTO_PEERLATENCY`](#SRTO_PEERLATENCY) -to the same value specified. +to the same value specified. Note that the default value for `SRTO_RCVLATENCY` is modified by the +[`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) option. Prior to SRT version 1.3.0 `SRTO_LATENCY` was the only option to set the latency. However it is effectively equivalent to setting `SRTO_PEERLATENCY` in the sending direction @@ -1212,6 +1214,8 @@ considered broken on timeout. The latency value (as described in [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY)) provided by the sender side as a minimum value for the receiver. +This value is only significant when [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) is enabled. + Reading the value of the option on an unconnected socket reports the configured value. Reading the value on a connected socket reports the effective receiver buffering latency of the peer. @@ -1296,16 +1300,22 @@ This value is only significant when [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) is enabl **Default value**: 120 ms in Live mode, 0 in File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)). The latency value defines the **minimum** receiver buffering delay before delivering an SRT data packet -from a receiving SRT socket to a receiving application. The provided value is used in the connection establishment (handshake exchange) stage -to fix the end-to-end latency of the transmission. The effective end-to-end latency `L` will be fixed -as the network transmission time of the final handshake packet (~1/2 RTT) plus the **negotiated** latency value `Ln`. -Data packets will stay in the receiver buffer for at least `L` microseconds since the timestamp of the -packet, independent of the actual network transmission times (RTT variations) of these packets. +from a receiving SRT socket to a receiving application. The actual value of the receiver buffering delay `Ln` (the negotiated latency) used on a connection is determined by the negotiation in the connection establishment (handshake exchange) phase as the maximum of the `SRTO_RCVLATENCY` value and the value of [`SRTO_PEERLATENCY`](#SRTO_PEERLATENCY) set by the peer. +The general idea for the latency mechanism is to keep the time distance between two consecutive +received packets the same as the time when these same packets were scheduled for sending by the +sender application (or per the time explicitly declared when sending - see +[`srt_sendmsg2`](API-functions.md#srt_sendmsg2) for details). This keeps any packets that have arrived +earlier than their delivery time in the receiver buffer until their delivery time comes. This should +compensate for any jitter in the network and provides an extra delay needed for a packet retransmission. + +For detailed information on how the latency setting influences the actual packet delivery time and +how this time is defined, refer to the [latency documentation](../features/latency.md). + Reading the `SRTO_RCVLATENCY` value on a socket after the connection is established provides the actual (negotiated) latency value `Ln`. @@ -1638,9 +1648,19 @@ enabled in sender if receiver supports it. Sets the transmission type for the socket, in particular, setting this option sets multiple other parameters to their default values as required for a -particular transmission type. +particular transmission type. This sets the following options to their defaults +in particular mode: + +* [`SRTO_CONGESTION`](#SRTO_CONGESTION) +* [`SRTO_MESSAGEAPI`](#SRTO_MESSAGEAPI) +* [`SRTO_NAKREPORT`](#SRTO_NAKREPORT) +* [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY), also set as [`SRTO_LATENCY`](#SRTO_LATENCY) +* [`SRTO_TLPKTDROP`](#SRTO_TLPKTDROP) +* [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) + + -Values defined by enum `SRT_TRANSTYPE` (see above for possible values) +Values defined by enum [`SRT_TRANSTYPE`](#SRT_TRANSTYPE). [Return to list](#list-of-options) diff --git a/docs/API/rejection-codes.md b/docs/API/rejection-codes.md new file mode 100644 index 000000000..0b8418b5f --- /dev/null +++ b/docs/API/rejection-codes.md @@ -0,0 +1,473 @@ +# SRT Rejection Codes + +This document provides an overview of the rejection (error) codes used by and supported within SRT and SRT-based applications. For information on other types of error codes refer to the [API Socket Options](./docs/API/API-socket-options.md) document. + +[:arrow_down:   Jump to list of rejection codes](#api-function-rejection-codes) + + +## Summary of Rejection Codes + +Rejection codes are used in the SRT API, and are transferred on the wire as a part of a Handshake packet (refer to the `Handshake Type` field of the [Handshake](./docs/features/handshake.md) packet). +The rejection codes are divided into several ranges: + + - SRT internal + - Predefined application level codes + - User defined (custom) codes + +In the SRT API these ranges are marked with the following constants (preprocessor definitions): + + - `SRT_REJC_INTERNAL` = 0 + - `SRT_REJC_PREDEFINED` = 1000 + - `SRT_REJC_USERDEFINED` = 2000 + +When transferred on the wire, the API value is incremented by 1000 to become the `Handshake Type` field value. In the following sections the values of rejection reason codes are given in accordance with the API values. + + +### SRT Internal Rejection Codes + +Defined in [**srt.h**](srtcore/srt.h), these codes provide the reason why a connection is rejected by SRT. They cover the reserved range 0 - 999 (below `SRT_REJC_PREDEFINED`). These codes cannot be used by applications to report a rejection reason. + +Naming: `SRT_REJ_*` + + - `SRT_REJ_UNKNOWN` = 0 + - `SRT_REJ_SYSTEM` = 1 + - ... + - `SRT_REJ_CRYPTO` = 17 +See [the list below](#api-function-rejection-codes) for details. + +### Extended Rejection Codes + +As defined in [**access_control.h**](srtcore/access_control.h), these are standard server error codes including those adopted from HTTP. They provide the reason why an application rejects a connection. The value is expected to be set by an application via the listener callback if it wants to reject an incoming connection request. These codes cover the reserved range 1000 - 1999 (`SRT_REJC_PREDEFINED` - `SRT_REJC_USERDEFINED`). + + +Subranges (1000 + value): + + - **0 - 99**: Reserved for unique SRT-specific codes (unused by HTTP) + - **100 - 399**: Info, Success, and Redirection in HTTP (unused by SRT) + - **400 - 599**: Client and server errors in HTTP (adopted by SRT) + +Naming: `SRT_REJX_*` + +Example: + + - `SRT_REJX_KEY_NOTSUP` (1001): The key used in the StreamID keyed string is not supported by the service. + - `SRT_REJX_BAD_REQUEST` (1400) + - ... + +### User Defined Rejection Codes + +These codes can be freely defined by an application. They can be custom codes, not adopted by other vendors. For example, `2005: “Makito license expired”`. They cover the reserved range 2000 - 2999 (higher than `SRT_REJC_USERDEFINED`). + + +## API Function Rejection Codes + +SRT's API function rejection codes refer to system-level error conditions caused by SRT-specific settings or operating conditions. They are uninfluenced by application-related events, and applications are not permitted to use or simulate these codes. + +The table below lists the rejection codes as defined in [**srt.h**](srtcore/srt.h) (click the *Rejection Reason* link to view a complete description). + +| *Code* | *Rejection Reason* | *Since* | *Description* | +|:------:|:-------------------------------------------------- |:-------:|:-------------------------------------------------------------------------------------------------------------- | +| 0 | [SRT_REJ_UNKNOWN](#SRT_REJ_UNKNOWN) | 1.3.4 | Fallback value for cases where connection is not rejected. | +| 1 | [SRT_REJ_SYSTEM](#SRT_REJ_SYSTEM) | 1.3.4 | System function reported a failure. | +| 2 | [SRT_REJ_PEER](#SRT_REJ_PEER) | 1.3.4 | Connection rejected by peer, with no additional details. | +| 3 | [SRT_REJ_RESOURCE](#SRT_REJ_RESOURCE) | 1.3.4 | Problem with resource allocation (usually memory). | +| 4 | [SRT_REJ_ROGUE](#SRT_REJ_ROGUE) | 1.3.4 | Data sent by one party cannot be interpreted. | +| 5 | [SRT_REJ_BACKLOG](#SRT_REJ_BACKLOG) | 1.3.4 | Listener's backlog has been exceeded. | +| 6 | [SRT_REJ_IPE](#SRT_REJ_IPE) | 1.3.4 | Internal Program Error. | +| 7 | [SRT_REJ_CLOSE](#SRT_REJ_CLOSE) | 1.3.4 | Listener socket received a request as it is being closed. | +| 8 | [SRT_REJ_VERSION](#SRT_REJ_VERSION) | 1.3.4 | Minimum version requirement for a connection not satisfied by one party. | +| 9 | [SRT_REJ_RDVCOOKIE](#SRT_REJ_RDVCOOKIE) | 1.3.4 | Rendezvous cookie collision. | +| 10 | [SRT_REJ_BADSECRET](#SRT_REJ_BADSECRET) | 1.3.4 | Both parties have defined connection passphrases that differ. | +| 11 | [SRT_REJ_UNSECURE](#SRT_REJ_UNSECURE) | 1.3.4 | Only one party has set up a connection password. | +| 12 | [SRT_REJ_MESSAGEAPI](#SRT_REJ_MESSAGEAPI) | 1.3.4 | [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on both connection parties. | +| 13 | [SRT_REJ_CONGESTION](#SRT_REJ_CONGESTION) | 1.3.4 | Incompatible congestion-controller type. | +| 14 | [SRT_REJ_FILTER](#SRT_REJ_FILTER) | 1.3.4 | [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option is different on both connection parties. | +| 15 | [SRT_REJ_GROUP](#SRT_REJ_GROUP) | 1.4.2 | Group type or group settings are incompatible between connection parties. | +| 16 | [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | 1.4.2 | Connection not rejected, but timed out. | +| 17 | [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | Connection rejected due to unsupported or mismatching encryption mode. | +| | | | | + + +## Access Control Rejection Codes + +SRT's access control rejection codes are intended for use by applications to forcefully reject connections in SRT listener callbacks. They are intended only as a guide to promote standardization. If they are used in an application, a description of their specific implementation should be published (the descriptions in this documentation are not definitive). + +The table below lists the rejection codes as defined in [**access_control.h**](srtcore/access_control.h) (click the *Rejection Reason* link to view a complete description). + +| *Code* | *Rejection Reason* | *Since* | *Description* | +|:------:|:------------------------------------------------- |:-------:|:-------------------------------------------------------------------------------------------------------------- | +| 1000 | [SRT_REJX_FALLBACK](#SRT_REJX_FALLBACK) | 1.4.2 | Callback handler has interrupted an incoming connection. | +| 1001 | [SRT_REJX_KEY_NOTSUP](#SRT_REJX_KEY_NOTSUP) | 1.4.2 | Key specified in StreamID string not supported by application. | +| 1002 | [SRT_REJX_FILEPATH](#SRT_REJX_FILEPATH) | 1.4.2 | Resource type designates file where path has wrong syntax or is not found. | +| 1003 | [SRT_REJX_HOSTNOTFOUND](#SRT_REJX_HOSTNOTFOUND) | 1.4.2 | The host specified in the `h` key cannot be identified. | +| 1400 | [SRT_REJX_BAD_REQUEST](#SRT_REJX_BAD_REQUEST) | 1.4.2 | General syntax error. | +| 1401 | [SRT_REJX_UNAUTHORIZED](#SRT_REJX_UNAUTHORIZED) | 1.4.2 | Authentication failed; client unauthorized to access the resource. | +| 1402 | [SRT_REJX_OVERLOAD](#SRT_REJX_OVERLOAD) | 1.4.2 | Server load too heavy to process request, or credit limit exceeded. | +| 1403 | [SRT_REJX_FORBIDDEN](#SRT_REJX_FORBIDDEN) | 1.4.2 | Access denied to the resource for any reason. | +| 1404 | [SRT_REJX_NOTFOUND](#SRT_REJX_NOTFOUND) | 1.4.2 | Resource specified by `r` and `h` keys cannot be found. | +| 1405 | [SRT_REJX_BAD_MODE](#SRT_REJX_BAD_MODE) | 1.4.2 | Mode specified in the `m` key in StreamID is not supported for this request. | +| 1406 | [SRT_REJX_UNACCEPTABLE](#SRT_REJX_UNACCEPTABLE) | 1.4.2 | Unavailable parameters in `StreamID`, or `m=publish` data format not supported. | +| 1409 | [SRT_REJX_CONFLICT](#SRT_REJX_CONFLICT) | 1.4.2 | Resource specified by `r` and `h` keys is locked for modification. | +| 1415 | [SRT_REJX_NOTSUP_MEDIA](#SRT_REJX_NOTSUP_MEDIA) | 1.4.2 | Media type not supported by the application. | +| 1423 | [SRT_REJX_LOCKED](#SRT_REJX_LOCKED) | 1.4.2 | Resource is locked against any access. | +| 1424 | [SRT_REJX_FAILED_DEPEND](#SRT_REJX_FAILED_DEPEND) | 1.4.2 | Dependent entity for the request is not present. | +| 1500 | [SRT_REJX_ISE](#SRT_REJX_ISE) | 1.4.2 | Internal server error. | +| 1501 | [SRT_REJX_UNIMPLEMENTED](#SRT_REJX_UNIMPLEMENTED) | 1.4.2 | Request not supported by current version of the service. | +| 1502 | [SRT_REJX_GW](#SRT_REJX_GW) | 1.4.2 | Target endpoint rejected connection from gateway server | +| 1503 | [SRT_REJX_DOWN](#SRT_REJX_DOWN) | 1.4.2 | Service is down for maintenance. | +| 1505 | [SRT_REJX_VERSION](#SRT_REJX_VERSION) | 1.4.2 | SRT application version not supported. | +| 1507 | [SRT_REJX_NOROOM](#SRT_REJX_NOROOM) | 1.4.2 | Data stream cannot be archived due to lack of storage space. | +| | | | | + + +**NOTE**: SRT rejection codes follow this prefix convention: + + - `SRT_REJ`: standard rejection codes from SRT API functions (0 - 99) + - `SRT_REJC`: mark the border values between ranges. + - `SRT_REJX`: extended rejection codes (code values above 1000).above)?* + + +## API Function Rejection Reasons + + +#### SRT_REJ_UNKNOWN + +A fallback value for cases when there was no connection rejected. + + +#### SRT_REJ_SYSTEM + +One system function reported a failure. Usually this means some system +error or lack of system resources to complete the task. + + +#### SRT_REJ_PEER + +The connection has been rejected by the peer, but no further details are available. +This usually means that the peer doesn't support rejection reason reporting. + + +#### SRT_REJ_RESOURCE + +A problem with resource allocation (usually memory). + + +#### SRT_REJ_ROGUE + +The data sent by one party to another cannot be properly interpreted. This +should not happen during normal usage, unless it's a bug, or some weird +events are happening on the network. + + +#### SRT_REJ_BACKLOG + +The listener's backlog has exceeded its queue limit (there are many other callers +waiting for the opportunity to be connected and the "wait queue" has +reached its limit). + + +#### SRT_REJ_IPE + +Internal Program Error. This should not happen during normal usage. It +usually indicates a bug in the software (although this can be reported by both +local and foreign hosts). + + +#### SRT_REJ_CLOSE + +The listener socket was able to receive the request, but is currently +being closed. It's likely that the next request will result in a timeout. + + +#### SRT_REJ_VERSION + +One party in the connection has set up a minimum version that is required for +that connection, but the other party doesn't satisfy this requirement. + + +#### SRT_REJ_RDVCOOKIE + +Rendezvous cookie collision. Normally, the probability that this will happen is +negligible. However, it *can* result from a misconfiguration when, in attempting +to make a rendezvous connection, both parties try to bind to the same IP address, +or both are local addresses of the same host. In such a case the sent handshake +packets are returned to the same host as if they were sent by the peer (i.e. a +party is sending to itself). In such situations, this reject reason will be +reported for every attempt. + + +#### SRT_REJ_BADSECRET + +Both parties have defined a passphrase for a connection, but they differ. + + +#### SRT_REJ_UNSECURE + +Only one connection party has set up a password. See also the +[`SRTO_ENFORCEDENCRYPTION`](API-socket-options.md#SRTO_ENFORCEDENCRYPTION) flag. + + +#### SRT_REJ_MESSAGEAPI + +The value of the [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) +flag is different on both parties in a connection. + + +#### SRT_REJ_CONGESTION + +The [`SRTO_CONGESTION`](API-socket-options.md#SRTO_CONGESTION)option has +been set up differently on both parties in a connection. + + +#### SRT_REJ_FILTER + +The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option +has been set differently on both parties in a connection. + + +#### SRT_REJ_GROUP + +The group type or some group settings are incompatible between connection parties. +While every connection within a bonding group may have different target addresses, +they should all designate the same endpoint and the same SRT application. If this +condition isn't satisfied, then the peer will respond with a different peer group +ID for the connection that is trying to contact a machine/application that is +completely different from the existing connections in the bonding group. + + +#### SRT_REJ_TIMEOUT + +The connection wasn't rejected, but it timed out. This code is always sent on +a connection timeout, but this is the only way to get this state in non-blocking +mode (see [`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN)). + +There may also be server and user rejection codes, as defined by the +`SRT_REJC_INTERNAL`, `SRT_REJC_PREDEFINED`, and `SRT_REJC_USERDEFINED` +constants. Note that the number space from the value of `SRT_REJC_PREDEFINED` +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 + +The connection was rejected due to a mismatch in crypto modes. See `SRTO_CRYPTOMODE`. + +[:arrow_up:   Back to top](#srt-rejection-codes) + + +## Access Control Rejection Reasons + +An SRT listener callback handler can decide to reject an incoming connection. +Under normal circumstances, the rejection code is predefined as `SRT_REJ_RESOURCE`. +The handler can, however, set its own rejection code. There are two numbered spaces +intended for this purpose (as the range below `SRT_REJC_PREDEFINED` is reserved +for internal codes): + +- `SRT_REJC_PREDEFINED` and above: These are predefined errors. Errors from this +range (that is, below `SRT_REJC_USERDEFINED`) have their definitions provided in +the `access_control.h` public header file. The intention is that applications +using these codes understand the situations they describe in a standard way. + +- `SRT_REJC_USERDEFINED` and above: These are errors that are freely defined by +the application. Codes from this range can be only understood if each application +knows the code definitions of the other. These codes should be used only after +making sure that the applications at either end of a connection understand them. + +The intention here is for the predefined codes to be consistent with the HTTP +standard codes. Such code can be set by using the [`srt_setrejectreason`](docs/api/api-functions.md#srt-setrejectreason) function. + +The SRT-specific codes are: + +#### SRT_REJX_FALLBACK + +This code should be set by the callback handler in the beginning in case +the application needs to be informed that the callback handler +actually has interpreted the incoming connection, but hasn't set a +more appropriate code describing the situation. + +#### SRT_REJX_KEY_NOTSUP + +Indicates there was a key specified in the StreamID string that this application +doesn't support. Note that it's not obligatory for the application to +react this way - it may chose to ignore unknown keys completely, or +to have some keys in the ignore list (which it won't interpret, but tolerate) +while rejecting any others. It is also up to the application +to decide to return this specific error, or more generally report +the syntax error with `SRT_REJX_BAD_REQUEST`. + +#### SRT_REJX_FILEPATH + +The resource type designates a file, and the path either has the wrong syntax +or is not found. In the case where `t=file`, the path should be specified under +the `r` key, and the file specified there must be able to be saved this way. +It's up to the application to decide how to treat this path, how to parse it, +and what this path specifically means. For the `r` key, the application should +at least handle the single filename, and have storage space available to save +it (provided a file of the same name does not already exist there). The +application should decide whether and how to handle all other situations (like +directory path, special markers in the path to be interpreted by the +application, etc.), or to report this error. + +#### SRT_REJX_HOSTNOTFOUND + +The host specified in the `h` key cannot be identified. The `h` key is +generally for a situation when you have multiple DNS names for a host, +so an application may want to extract the name from the URI and set it +to the `h` key so that the application can distinguish the request also by +the target host name. The application may, however, limit the number of +recognized services by host name to some predefined names and not +handle the others, even if this is properly resolved by DNS. In this +case it should report this error. + +The other error codes are HTTP codes adapted for SRT: + +#### SRT_REJX_BAD_REQUEST + +General syntax error. This can be reported in any case when parsing +the StreamID contents failed, or it cannot be properly interpreted. + +#### SRT_REJX_UNAUTHORIZED + +Authentication failed, which makes the client unauthorized to access the +resource. This error, however, confirms that the syntax is correct and +the resource has been properly identified. Note that this cannot be +reported when you use a simple user-password authentication +method because in this case the password is verified only after the +listener callback handler accepts the connection. This error is rather +intended to be reported in the case of `t=auth` when the authentication +process has generated some valid session ID, but then the session +connection has specified a resource that is not within the frames +of that authentication. + +#### SRT_REJX_OVERLOAD + +The server is too heavily loaded to process the request, or the credit limit +for accessing the service and the resource has been exceeded. +In HTTP the description mentions payment for a service, but +it is also used by some services for general "credit" management +for a client. In SRT it should be used when the service is doing +any kind of credit management to limit access to selected clients +that "have" enough credit, even if the credit is something the client +can recharge itself, or that can be granted depending on available +service resources. + +#### SRT_REJX_FORBIDDEN + +Access denied to the resource for any reason. This error is +independent of an authorization or authentication error (as reported +by `SRT_REJX_UNAUTHORIZED`). The application can decide which +is more appropriate. This error is usually intended for +a resource that should only be accessed after a successful +authorization over a separate auth-only connection, where the query +in StreamID has correctly specified the resource identity and mode, +but the session ID (in the `s` key) is either (a) not specified, or +(b) specifies a valid session, but the authorization region for this +session does not include the specified resource. + +#### SRT_REJX_NOTFOUND + +The resource specified in the `r` key (in combination with the `h` key) +is not found at this time. This error should be only reported if the +information about resource accessibility is allowed to be publicly +visible. Otherwise, the application might report authorization +errors. + +#### SRT_REJX_BAD_MODE + +The mode specified in the `m` key in StreamID is not supported for this request. +This may apply to read-only or write-only resources, as well when interactive +(bidirectional) access is not valid for a resource. + +#### SRT_REJX_UNACCEPTABLE + +Applies when the parameters specified in StreamID cannot be satisfied for the +requested resource, or when `m=publish` but the data format is not acceptable. +This is a general error reporting an unsupported format for data that appears to +be wrong when sending, or a restriction on the data format (as specified in the +details of the resource specification) such that it cannot be provided +when receiving. + +#### SRT_REJX_CONFLICT + +The resource being accessed (as specified by `r` and `h` keys) is locked for +modification. This error should only be reported for `m=publish` when the +resource being accessed is read-only because another client (not necessarily +connected through SRT): + +- is currently publishing into this resource +- has reserved this resource ID for publishing + +Note that this error should be reported when there is no other reason for +having a problem accessing the resource. + +#### SRT_REJX_NOTSUP_MEDIA + +The media type is not supported by the application. The media type is +specified in the `t` key. The currently standard types are +`stream`, `file` and `auth`. An application may extend this list, and +is not obliged to support all of the standard types. + +#### SRT_REJX_LOCKED + +The resource being accessed is locked against any access. This is similar to +`SRT_REJX_CONFLICT`, but in this case the resource is locked for reading +and writing. This is for when the resource should be shown as existing and +available to the client, but access is temporarily blocked. + +#### SRT_REJX_FAILED_DEPEND + +The dependent entity for the request is not present. In this case the +dependent entity is the session, which should be specified in the `s` +key. This means that the specified session ID is nonexistent, or it +has already expired. + +#### SRT_REJX_ISE + +Internal server error. This is for a general case when a request has +been correctly verified, with no related problems found, but an +unexpected error occurs after the processing of the request has started. + +#### SRT_REJX_UNIMPLEMENTED + +The request was correctly recognized, but the current software version +of the service (be it SRT or any other software component) doesn't +support it. This should be reported for a case where some features to +be specified in the StreamID request are supposed to be supported in a +predictable future, but the current version of the server does not +support it, or the support for this feature in this version has been +temporarily blocked. This shouldn't be reported for existing features that are +being deprecated, or older features that are no longer supported +(for this case the general `SRT_REJX_BAD_REQUEST` is more appropriate). + +#### SRT_REJX_GW + +The server acts as a gateway and the target endpoint rejected the +connection. The reason the connection was rejected is unspecified. +The gateway cannot forward the original rejection code from the +target endpoint because this would suggest the error was on the +gateway itself. Use this error with some other mechanism to report +the original target error, if possible. + +#### SRT_REJX_DOWN + +The service is down for maintenance. This can only be reported +when the service has been temporarily replaced by a stub that is only +reporting this error, while the real service is down for maintenance. + +#### SRT_REJX_VERSION + +Application version not supported. This can refer to an application feature +that is unsupported (possibly from an older SRT version), or to a feature +that is no longer supported because of backward compatibility requirements. + +#### SRT_REJX_NOROOM + +The data stream cannot be archived due to a lack of storage space. This is +reported when a request to send a file or a live stream to be archived is +unsuccessful. Note that the length of a file transmission is usually +pre-declared, so this error can be reported early. It can also be reported when +the stream is of undefined length, and there is no more storage space +available. + + +[:arrow_up:   Back to top](#srt-rejection-codes) diff --git a/docs/API/statistics.md b/docs/API/statistics.md index 60bba59ee..bc34ecca8 100644 --- a/docs/API/statistics.md +++ b/docs/API/statistics.md @@ -245,6 +245,8 @@ Packets may be dropped conditionally when both `SRTO_TSBPDMODE` and `SRTO_TLPKTD #### pktRcvUndecryptTotal The total number of packets that failed to be decrypted at the receiver side. Available for receiver. +The statistic also counts unencrypted packets that were expected to be uncrypted on a secured connection (see [SRTO_KM_S_SECURED](API-socket-options.md#srt_km_state)) +and hence dropped as not encrypted (undecrypted). #### pktSndFilterExtraTotal @@ -822,4 +824,4 @@ The ratio of unrecovered by the socket group packets `Dropped Packets Ratio` can ``` Dropped Packets Ratio = pktRcvDropTotal / pktSentUniqueTotal; in case both sender and receiver statistics is available Dropped Packets Ratio = pktRcvDropTotal / (pktRecvUniqueTotal + pktRcvDropTotal); in case receiver only statistics is available -``` \ No newline at end of file +``` diff --git a/docs/README.md b/docs/README.md index 9e3bdb198..8c07ee515 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ | [SRT API](API/API.md) | [API](API/) | [API.md](API/API.md) | Detailed description of the SRT C API. | | [SRT API Functions](API/API-functions.md) | [API](API/) | [API-functions.md](API/API-functions.md) | Reference document for SRT API functions. | | [SRT API Socket Options](API/API-socket-options.md) | [API](API/) | [API-socket-options.md](API/API-socket-options.md) | Instructions and list of socket options for SRT API. | +| [SRT Rejection Codes](API/rejections-codes.md) | [API](API/) | [rejection-codes.md](API/rejection-codes.md) | The list of SRT rejections codes. | | [SRT Statistics](API/statistics.md) | [API](API/) | [statistics.md](API/statistics.md) | How to use SRT socket and socket group statistics. | | [Configuration Guidelines](API/configuration-guidelines.md) | [API](API/) | [configuration-guidelines.md](API/configuration-guidelines.md) | How to configure SRT buffers. | | | | | | diff --git a/docs/build/build-options.md b/docs/build/build-options.md index 88fcb85bb..529bd5cd7 100644 --- a/docs/build/build-options.md +++ b/docs/build/build-options.md @@ -597,8 +597,8 @@ remember that: Encryption library to be used. Possible options for ``: -* openssl (default) -* openssl-evp (OpenSSL EVP API, since 1.5.1) +* openssl-evp (default) +* openssl * gnutls (with nettle) * mbedtls * botan diff --git a/docs/features/access-control.md b/docs/features/access-control.md index 99fe891fa..6375802b2 100644 --- a/docs/features/access-control.md +++ b/docs/features/access-control.md @@ -154,198 +154,7 @@ standard codes. Therefore the following sub-ranges are used: Such a code can be set by using the `srt_setrejectreason` function. -The SRT-specific codes are: - -#### SRT_REJX_FALLBACK - -This code should be set by the callback handler in the beginning in case -the application needs to be informed that the callback handler -actually has interpreted the incoming connection, but hasn't set a -more appropriate code describing the situation. - -#### SRT_REJX_KEY_NOTSUP - -Indicates there was a key specified in the StreamID string that this application -doesn't support. Note that it's not obligatory for the application to -react this way - it may chose to ignore unknown keys completely, or -to have some keys in the ignore list (which it won't interpret, but tolerate) -while rejecting any others. It is also up to the application -to decide to return this specific error, or more generally report -the syntax error with `SRT_REJX_BAD_REQUEST`. - -#### SRT_REJX_FILEPATH - -The resource type designates a file, and the path either has the wrong syntax -or is not found. In the case where `t=file`, the path should be specified under -the `r` key, and the file specified there must be able to be saved this way. -It's up to the application to decide how to treat this path, how to parse it, -and what this path specifically means. For the `r` key, the application should -at least handle the single filename, and have storage space available to save -it (provided a file of the same name does not already exist there). The -application should decide whether and how to handle all other situations (like -directory path, special markers in the path to be interpreted by the -application, etc.), or to report this error. - -#### SRT_REJX_HOSTNOTFOUND - -The host specified in the `h` key cannot be identified. The `h` key is -generally for a situation when you have multiple DNS names for a host, -so an application may want to extract the name from the URI and set it -to `h` key so that the application can distinguish the request also by -the target host name. The application may however limit the number of -recognized services by host name to some predefined names and not -handle the others, even if this is properly resolved by DNS. In this -case it should report this error. - -The other error codes are HTTP codes adopted for SRT: - -#### SRT_REJX_BAD_REQUEST - -General syntax error. This can be reported in any case when parsing -the StreamID contents failed, or it cannot be properly interpreted. - -#### SRT_REJX_UNAUTHORIZED - -Authentication failed, which makes the client unauthorized to access the -resource. This error, however, confirms that the syntax is correct and -the resource has been properly identified. Note that this cannot be -reported when you use a simple user-password authentication -method because in this case the password is verified only after the -listener callback handler accepts the connection. This error is rather -intended to be reported in case of `t=auth` when the authentication -process has generated some valid session ID, but then the session -connection has specified a resource that is not within the frames -of that authentication. - -#### SRT_REJX_OVERLOAD - -The server is too heavily loaded to process your request, or you -have exceeded credits for accessing the service and the resource. -In HTTP the description mentions payment for a service, but -it is also used by some services for general "credit" management -for a client. In SRT it should be used when your service is doing -any kind of credit management to limit access to selected clients -that "have" enough credit, even if the credit is something the client -can recharge itself, or that can be granted depending on available -service resources. - -#### SRT_REJX_FORBIDDEN - -Access denied to the resource for any reason. This error is -independent of an authorization or authentication error (as reported -by `SRT_REJX_UNAUTHORIZED`). The application can decide which -is more appropriate. This error is usually intended for -a resource that should only be accessed after a successful -authorization over a separate auth-only connection, where the query -in StreamID has correctly specified the resource identity and mode, -but the session ID (in the `s` key) is either (a) not specified, or (b) does -specify a valid session, but the authorization region for this -session does not embrace the specified resource. - -#### SRT_REJX_NOTFOUND - -The resource specified in the `r` key (in combination with the `h` key) -is not found at this time. This error should be only reported if the -information about resource accessibility is allowed to be publicly -visible. Otherwise the application might report authorization -errors. - -#### SRT_REJX_BAD_MODE - -The mode specified in the `m` key in StreamID is not supported for this request. -This may apply to read-only or write-only resources, as well for when interactive -(bidirectional) access is not valid for a resource. - -#### SRT_REJX_UNACCEPTABLE - -Applies when the parameters specified in StreamID cannot be satisfied for the -requested resource, or when `m=publish` but the data format is not acceptable. -This is a general error reporting an unsupported format for data that appears to -be wrong when sending, or a restriction on the data format (as specified in the -details of the resource specification) such that it cannot be provided -when receiving. - -#### SRT_REJX_CONFLICT - -The resource being accessed (as specified by `r` and `h` keys) is locked for -modification. This error should only be reported for `m=publish` when the -resource being accessed is read-only because another client (not necessarily -connected through SRT): - -- is currently publishing into this resource -- has reserved this resource ID for publishing - -Note that this error should be reported when there is no other reason for -having a problem accessing the resource. - -#### SRT_REJX_NOTSUP_MEDIA - -The media type is not supported by the application. The media type is -specified in the `t` key. The currently standard types are -`stream`, `file` and `auth`. An application may extend this list, and -is not obliged to support all of the standard types. - -#### SRT_REJX_LOCKED - -The resource being accessed is locked against any access. This is similar to -`SRT_REJX_CONFLICT`, but in this case the resource is locked for reading -and writing. This is for when the resource should be shown as existing and -available to the client, but access is temporarily blocked. - -#### SRT_REJX_FAILED_DEPEND - -The dependent entity for the request is not present. In this case the -dependent entity is the session, which should be specified in the `s` -key. This means that the specified session ID is nonexistent or it -has already expired. - -#### SRT_REJX_ISE - -Internal server error. This is for a general case when a request has -been correctly verified, with no related problems found, but an -unexpected error occurs after the processing of the request has started. - -#### SRT_REJX_UNIMPLEMENTED - -The request was correctly recognized, but the current software version -of the service (be it SRT or any other software component) doesn't -support it. This should be reported for a case, when some features to -be specified in the StreamID request are supposed to be supported in a -predictable future, but the current version of the server does not -support it, or the support for this feature in this version has been -temporarily blocked. This shouldn't be reported for existing features that are -being deprecated, or older features that are no longer supported -(for this case the general `SRT_REJX_BAD_REQUEST` is more appropriate). - -#### SRT_REJX_GW - -The server acts as a gateway and the target endpoint rejected the -connection. The reason the connection was rejected is unspecified. -The gateway cannot forward the original rejection code from the -target endpoint because this would suggest the error was on the -gateway itself. Use this error with some other mechanism to report -the original target error, if possible. - -#### SRT_REJX_DOWN - -The service is down for maintenance. This can only be reported -when the service has been temporarily replaced by a stub that is only -reporting this error, while the real service is down for maintenance. - -#### SRT_REJX_VERSION - -Application version not supported. This can refer to an application feature -that is unsupported (possibly from an older SRT version), or to a feature -that is no longer supported because of backward compatibility requirements. - -#### SRT_REJX_NOROOM - -The data stream cannot be archived due to a lack of storage space. This is -reported when a request to send a file or a live stream to be archived is -unsuccessful. Note that the length of a file transmission is usually -pre-declared, so this error can be reported early. It can also be reported when -the stream is of undefined length, and there is no more storage space -available. +See the list of rejection codes in the [Rejection Codes](../API/rejection-codes.md) document. ## Example diff --git a/docs/features/latency.md b/docs/features/latency.md new file mode 100644 index 000000000..d287ac030 --- /dev/null +++ b/docs/features/latency.md @@ -0,0 +1,219 @@ +## General statement about latency + +In the live streaming there are many things happening between the +camera's lens and the screen of the video player, all of which contribute +to a delay that is generally referred to as "latency". This overall latency +includes the time it takes for the camera frame grabber device to pass +frames to the encoder, encoding, multiplexing, **sending over the network**, +splitting, decoding and then finally displaying. + +In SRT, however, "latency" is defined as only the delay introduced by **sending +over the network**. It's the time between the moment when the `srt_sendmsg2` +function is called at the sender side up to the moment when the `srt_recvmsg2` +function is called at the receiver side. This SRT latency is the actual time difference +between these two events. + + +## The goal of the latency (TSBPD) mechanism + +SRT employs a TimeStamp Based Packet Delivery (TSBPD) mechanism +with strict goal of keeping the time interval between two consecutive packets +on the receiver side identical to what they were at the sender side. This +requires introducing an extra delay that should define when exactly the packet +can be retrieved by the receiver application -- if the packet arrives early, it must +wait in the receiver buffer until the delivery time. This time for a packet N +is roughly defined as: + +``` +PTS[N] = ETS[N] + LATENCY(option) +``` + +where `ETS[N]` is the time when the packet would arrive, if all delays +from the network and the processing software on both sides are identical +to what they were for the very first received data packet. This means that +for the very first packet `ETS[0]` is equal to this packet's arrival time. +For every following packet the delivery time interval should be equal to the +that packet's declared scheduling time interval. + + +## SRT's approach to packet arrival time + +SRT provides two socket options `SRTO_PEERLATENCY` and `SRTO_RCVLATENCY`. +While they have "latency" in their names, they do *not* define the true time +interval between the `srt_sendmsg2` and `srt_recvmsg2` calls for the same +packet. They are only used to add an extra delay (at the receiver side) to +the time when the packet "should" arrive (ETS). This extra delay is used to +compensate for two things: + +* an extra network delay (that is, if the packet arrived later than it +"should have arrived"), or + +* a packet retransmission. + +Note that many of the values included in these formulas are not controllable and +some cannot be measured directly. In many cases there are measured values +that are sums of other values, but the component values can't be extracted. + +There are two values that we can obtain at the receiver side: + +* ATS: actual arrival time, which is the time when the UDP packet +has been extracted through the `recvmsg` system call. + +* TS: time recorded in the packet header, set on the sender side and extracted +from the packet at the receiver side + +Note that the timestamp in the packet's header is 32-bit, which gives +it more or less 2.5 minutes to roll over. Therefore timestamp +rollover is tracked and a segment increase is performed in order to keep an +eye on the overall actual time. For the needs of the formula definitions +it must be stated that TS is the true difference between the connection +start time and the time when the sending time has been declared when +the sender application is calling any of the `srt_send*` functions +(see [`srt_sendmsg2`](../API/API-functions.md#srt_sendmsg2) for details). + + +## SRT latency components + +To understand the latency components we need also other definitions: + +* **ETS** (Expected Time Stamp): The packet's expected arrival time, when it +"should" arrive according to its timestamp + +* **PTS** (Presentation Time Stamp): The packet's play time, when SRT gives the packet +to the `srt_recvmsg2` call (that is, it sets up the IN flag in epoll +and resumes the blocked function call, if it was in blocking mode). + +* **STS** (Sender Time Stamp): The time when the packet was +scheduled for sending at the sender side (if you don't use the +declared time, by default it's the monotonic time used when this +function is called). + +* **RTS** (Receiver Time Stamp): The same as STS, but calculated at the receiver side. The +only way to extract it is by using some initial statements. + +The "true latency" for a particular packet in SRT can be simply defined as: + +* `TD = PTS - STS` + +Note that this is a stable definition (independent of the packet), +but this value is not really controllable. So let's define the PTS +for a packet `x`: + +* `PTS[x] = ETS[x] + LATENCY + DRIFT` + +where `LATENCY` is the negotiated latency value (out of the +`SRTO_RCVLATENCY` on the agent and `SRTO_PEERLATENCY` on the peer) +and DRIFT will be described later (for simplification you can +state it's initially 0). + +These components undergo the following formula: + +* `ETS[x] = start_time + TS[x]` + +Note that it's not possible to simply define a "true" latency based on STS +because the sender and receiver are two different machines that can only +see one another through the network. Their clocks are separate, +and can even run at different or changing speeds, and the only +visible phenomena happen when packets arrive at the receiver machine. +However, the formula above does allow us to define the start time because +we state the following for the very first data packet: + +* `ETS[0] = ATS[0]` + +This means that from this formula we can define the start time: + +* `start_time = ATS[0] - TS[0]` + +Therefore we can state that if we have two identical clocks on +both machines with identical time bases and speeds, then: + +* `ATS[x] = program_delay[x] + network_delay[x] + STS[x]` + +Note that two machines communicating over a network do not typically have a +common clock base. Therefore, although this formula is correct, it involves +components that can neither be measured nor captured at the receiver side. + +This formula for ATS doesn't apply to the real latency, which is based strictly +on ETS. But you can apply this formula for the very first arriving packet, +because in this case they are equal: `ATS[0] = ETS[0]`. + +Therefore this formula is true for the very first packet: + +* `ETS[0] = prg_delay[0] + net_delay[0] + STS[0]` + +We know also that the TS set on the sender side is: + +* `TS[x] = STS[x] - snd_connect_time` + +Taking both formulas for ETS together: + +* `ETS[x] = start_time + TS[x] = prg_delay[0] + net_delay[0] + snd_connect_time + TS[x]` + +we have then: + +* `start_time = prg_delay[0] + net_delay[0] + snd_connect_time` + +**IMPORTANT**: `start_time` is not the time of arrival of the first packet, +but that time taken backwards by using the delay already recorded in TS. As TS should +represent the delay towards `snd_connect_time`, `start_time` should be simply the same +as `snd_connect_time`, just on the receiver side, and so shifted by the +first packet's delays of `prg_delay` and `net_delay`. + +So, as we have the start time defined, the above formulas: + +* `ETS[x] = start_time + TS[x]` +* `PTS[x] = ETS[x] + LATENCY + DRIFT` + +now define the packet delivery time as: + +* `PTS[x] = start_time + TS[x] + LATENCY + DRIFT` + +and after replacing the start time we have: + +* `PTS[x] = prg_delay[0] + net_delay[0] + snd_connect_time + TS[x] + LATENCY + DRIFT` + +and from the TS formula we get STS, so we replace it: + +* `PTS[x] = prg_delay[0] + net_delay[0] + STS[x] + LATENCY + DRIFT` + +We can now get the true network latency in SRT by moving STS to the other side: + +* `PTS[x] - STS[x] = prg_delay[0] + net_delay[0] + LATENCY + DRIFT` + + +## The DRIFT + +The DRIFT is a measure of the variance over time of the base time. +To simplify the calculations above, DRIFT is considered to be 0, +which is the initial state. In time, however, it changes based on the +value of the Arrival Time Deviation: + +* `ATD[x] = ATS[x] - ETS[x]` + +The drift is then calculated as: + +* `DRIFT[x] = average(ATD[x-N] ... ATD[x])` + +The value of the drift is tracked over an appropriate number of samples. If +it exceeds a threshold value, the drift value is applied to modify the +base time. However, as you can see from the formula for ATD, the drift is +simply taken from the actual time when the packet arrived, and the time +when it would have arrived if the `prg_delay` and `net_delay` values were +exactly the same as for the very first packet. ATD then represents the +changes in these values. There can be two main factors that could result +in having this value as non-zero: + +1. A phenomenon has been observed in several types of networks where +the very first packet arrives quickly, but as subsequent data packets +come in regularly, the network delay slightly increases and then remains fixed +for a long time at this increased value. This phenomenon can be +mitigated by having a reliable value for RTT. Once the increase is observed +a special factor could be applied to decrease the positive value +of the drift. This isn't currently implemented. This phenomenon also +isn't observed in every network, especially those covering longer distances. + +2. The clock speed on both machines (sender and receiver) isn't exactly the same, +which means that if you decipher the ETS basing on the TS, over time it may result +in values that even precede the STS (suggesting a negative network delay) or that +have an enormous delay (with ATS exceeding PTS). This is actually the main reason +for tracking the drift. diff --git a/examples/recvlive.cpp b/examples/recvlive.cpp index 81c2b1ef7..e14420a66 100644 --- a/examples/recvlive.cpp +++ b/examples/recvlive.cpp @@ -147,7 +147,7 @@ int main(int argc, char* argv[]) clientservice, sizeof(clientservice), NI_NUMERICHOST|NI_NUMERICSERV); cout << "new connection: " << clienthost << ":" << clientservice << endl; - int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; + events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (SRT_ERROR == srt_epoll_add_usock(epid, fhandle, &events)) { cout << "srt_epoll_add_usock: " << srt_getlasterror_str() << endl; diff --git a/examples/sendmsg.cpp b/examples/sendmsg.cpp index 53a0f66a5..af551849b 100644 --- a/examples/sendmsg.cpp +++ b/examples/sendmsg.cpp @@ -150,25 +150,25 @@ int main(int argc, char* argv[]) } else { - int lpos = 0; + int lpos2 = 0; int nparsed = 0; if (line[0] == '+') { - nparsed = sscanf(line.c_str(), "+%d %d %n%*s", &ttl, &id, &lpos); + nparsed = sscanf(line.c_str(), "+%d %d %n%*s", &ttl, &id, &lpos2); if (nparsed != 2) { - cout << "ERROR: syntax error in input (" << nparsed << " parsed pos=" << lpos << ")\n"; + cout << "ERROR: syntax error in input (" << nparsed << " parsed pos=" << lpos2 << ")\n"; status = SRT_ERROR; break; } } else { - nparsed = sscanf(line.c_str(), "%d %n%*s", &id, &lpos); + nparsed = sscanf(line.c_str(), "%d %n%*s", &id, &lpos2); if (nparsed != 1) { - cout << "ERROR: syntax error in input (" << nparsed << " parsed pos=" << lpos << ")\n"; + cout << "ERROR: syntax error in input (" << nparsed << " parsed pos=" << lpos2 << ")\n"; status = SRT_ERROR; break; } diff --git a/haicrypt/cryspr.c b/haicrypt/cryspr.c index 2e3a68a59..a078aad73 100644 --- a/haicrypt/cryspr.c +++ b/haicrypt/cryspr.c @@ -17,6 +17,10 @@ written by CRYSPR/4SRT Initial implementation. *****************************************************************************/ +#ifndef _WIN32 +#include /* htonl */ +#endif + #include "hcrypt.h" #include "cryspr.h" @@ -429,6 +433,8 @@ static int crysprFallback_MsEncrypt( /* Auth tag produced by AES GCM. */ unsigned char tag[HAICRYPT_AUTHTAG_MAX]; + /* Additional authenticated data used by AES-GCM. */ + unsigned char aad[HAICRYPT_AAD_MAX]; /* * Get buffer room from the internal circular output buffer. @@ -452,33 +458,30 @@ static int crysprFallback_MsEncrypt( /* Get input packet index (in network order) */ hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); - /* - * Compute the Initial Vector - * IV (128-bit): - * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | 0s | pki | ctr | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * XOR - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | nonce + - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * - * pki (32-bit): packet index - * ctr (16-bit): block counter - * nonce (112-bit): number used once (salt) - */ - hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); - if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) { - const int iret = cryspr_cb->cryspr->aes_gcm_cipher(true, aes_key, iv, in_data[0].pfx, pfx_len, in_data[0].payload, in_data[0].len, + const bool old_aead = ctx->use_gcm_153; // SRT v1.5.2 to v1.5.3. + if (old_aead) + { + hcrypt_SetCtrIV((unsigned char*)&pki, ctx->salt, iv); + memcpy(aad, in_data[0].pfx, sizeof(aad)); + } + else + { + hcrypt_SetGcmIV((unsigned char*)&pki, ctx->salt, iv); + + for (size_t i = 0; i < sizeof(aad) / 4; ++i) + *((uint32_t*)aad + i) = htonl(*((uint32_t*)in_data[0].pfx + i)); + } + + const int iret = cryspr_cb->cryspr->aes_gcm_cipher(true, aes_key, iv, aad, sizeof(aad), in_data[0].payload, in_data[0].len, &out_msg[pfx_len], tag); if (iret) { return(iret); } } else { + hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); #if CRYSPR_HAS_AESCTR cryspr_cb->cryspr->aes_ctr_cipher(true, aes_key, iv, in_data[0].payload, in_data[0].len, &out_msg[pfx_len]); @@ -599,28 +602,26 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, /* Get input packet index (in network order) */ hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); - /* - * Compute the Initial Vector - * IV (128-bit): - * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | 0s | pki | ctr | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * XOR - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * | nonce + - * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - * - * pki (32-bit): packet index - * ctr (16-bit): block counter - * nonce (112-bit): number used once (salt) - */ - hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); - if (ctx->mode == HCRYPT_CTX_MODE_AESGCM) { + /* Additional authenticated data used by AES-GCM. */ + unsigned char aad[HAICRYPT_AAD_MAX]; + const bool old_aead = ctx->use_gcm_153; // SRT v1.5.2 to v1.5.3. + if (old_aead) + { + hcrypt_SetCtrIV((unsigned char*)&pki, ctx->salt, iv); + memcpy(aad, in_data[0].pfx, sizeof(aad)); + } + else + { + hcrypt_SetGcmIV((unsigned char*)&pki, ctx->salt, iv); + + for (size_t i = 0; i < sizeof(aad) / 4; ++i) + *((uint32_t*)aad + i) = htonl(*((uint32_t*)in_data[0].pfx + i)); + } + unsigned char* tag = in_data[0].payload + in_data[0].len - HAICRYPT_AUTHTAG_MAX; - int liret = cryspr_cb->cryspr->aes_gcm_cipher(false, aes_key, iv, in_data[0].pfx, ctx->msg_info->pfx_len, in_data[0].payload, in_data[0].len - HAICRYPT_AUTHTAG_MAX, + int liret = cryspr_cb->cryspr->aes_gcm_cipher(false, aes_key, iv, aad, sizeof(aad), in_data[0].payload, in_data[0].len - HAICRYPT_AUTHTAG_MAX, out_txt, tag); if (liret) { return(liret); @@ -628,6 +629,7 @@ static int crysprFallback_MsDecrypt(CRYSPR_cb *cryspr_cb, hcrypt_Ctx *ctx, out_len = in_data[0].len - HAICRYPT_AUTHTAG_MAX; } else { + hcrypt_SetCtrIV((unsigned char*)&pki, ctx->salt, iv); #if CRYSPR_HAS_AESCTR cryspr_cb->cryspr->aes_ctr_cipher(false, aes_key, iv, in_data[0].payload, in_data[0].len, out_txt); diff --git a/haicrypt/haicrypt.h b/haicrypt/haicrypt.h index b6e83ad7a..da0ad3493 100644 --- a/haicrypt/haicrypt.h +++ b/haicrypt/haicrypt.h @@ -37,6 +37,7 @@ HaiCrypt_Cryspr HaiCryptCryspr_Get_Instance (void); /* Return a default crys #define HAICRYPT_KEY_MAX_SZ 32 /* MAX key */ #define HAICRYPT_SECRET_MAX_SZ (HAICRYPT_PWD_MAX_SZ > HAICRYPT_KEY_MAX_SZ ? HAICRYPT_PWD_MAX_SZ : HAICRYPT_KEY_MAX_SZ) #define HAICRYPT_AUTHTAG_MAX 16 /* maximum length of the auth tag (e.g. GCM) */ +#define HAICRYPT_AAD_MAX 16 /* maximum length of the additional authenticated data (GCM mode) */ #define HAICRYPT_SALT_SZ 16 @@ -96,6 +97,7 @@ typedef struct hcrypt_Session_str* HaiCrypt_Handle; int HaiCrypt_SetLogLevel(int level, int logfa); int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); +int HaiCrypt_UpdateGcm153(HaiCrypt_Handle hhc, unsigned use_gcm_153); int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handle *phhc); int HaiCrypt_Close(HaiCrypt_Handle hhc); int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_p); diff --git a/haicrypt/hcrypt.c b/haicrypt/hcrypt.c index 2568654b1..a2b81d832 100644 --- a/haicrypt/hcrypt.c +++ b/haicrypt/hcrypt.c @@ -178,6 +178,18 @@ int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) return(0); } +int HaiCrypt_UpdateGcm153(HaiCrypt_Handle hhc, unsigned use_gcm_153) +{ + ASSERT(hhc != NULL); + hcrypt_Session* crypto = hhc; + if (!crypto) + return (-1); + + crypto->ctx_pair[0].use_gcm_153 = use_gcm_153; + crypto->ctx_pair[1].use_gcm_153 = use_gcm_153; + return (0); +} + int HaiCrypt_ExtractConfig(HaiCrypt_Handle hhcSrc, HaiCrypt_Cfg* pcfg) { hcrypt_Session *crypto = (hcrypt_Session *)hhcSrc; @@ -320,6 +332,7 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl cryptoClone->ctx_pair[1].flags &= ~HCRYPT_CTX_F_ENCRYPT; memset(cryptoClone->ctx_pair[0].salt, 0, sizeof(cryptoClone->ctx_pair[0].salt)); cryptoClone->ctx_pair[0].salt_len = 0; + cryptoClone->ctx = &cryptoClone->ctx_pair[0]; } *phhc = (void *)cryptoClone; diff --git a/haicrypt/hcrypt.h b/haicrypt/hcrypt.h index 34e744e8a..e28a29777 100644 --- a/haicrypt/hcrypt.h +++ b/haicrypt/hcrypt.h @@ -135,6 +135,25 @@ typedef struct hcrypt_Session_str { hcrypt_XorStream(&(iv)[0], (nonce), 112/8); \ } while(0) +/* HaiCrypt-TP GCM mode IV (96-bit) - SRT 1.5.4: + * 0 1 2 3 4 5 6 7 8 9 10 11 + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0s | pki | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * XOR + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * | nonce + + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * + * pki (32-bit): packet index + * nonce (96-bit): number used once (salt) + */ +#define hcrypt_SetGcmIV(pki, nonce, iv) do { \ + memset(&(iv)[0], 0, 96/8); \ + memcpy(&(iv)[8], (pki), HCRYPT_PKI_SZ); \ + hcrypt_XorStream(&(iv)[0], (nonce), 96/8); \ + } while(0) + #define hcrypt_XorStream(dst, strm, len) do { \ int __XORSTREAMi; \ for (__XORSTREAMi = 0 \ diff --git a/haicrypt/hcrypt_ctx.h b/haicrypt/hcrypt_ctx.h index 3a46fd40f..0d962c430 100644 --- a/haicrypt/hcrypt_ctx.h +++ b/haicrypt/hcrypt_ctx.h @@ -70,6 +70,7 @@ typedef struct tag_hcrypt_Ctx { #define HCRYPT_CTX_MODE_AESCBC 3 /* Cipher-block chaining mode */ #define HCRYPT_CTX_MODE_AESGCM 4 /* AES GCM authenticated encryption */ unsigned mode; + bool use_gcm_153; /* AES-GCM compatibility mode (SRT v1.5.3 and earlier) */ struct { size_t key_len; diff --git a/haicrypt/hcrypt_ctx_rx.c b/haicrypt/hcrypt_ctx_rx.c index 9fcba6c30..2b67490d3 100644 --- a/haicrypt/hcrypt_ctx_rx.c +++ b/haicrypt/hcrypt_ctx_rx.c @@ -28,8 +28,8 @@ int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cf ctx->mode = (cfg->flags & HAICRYPT_CFG_F_GCM) ? HCRYPT_CTX_MODE_AESGCM : HCRYPT_CTX_MODE_AESCTR; } ctx->status = HCRYPT_CTX_S_INIT; - ctx->msg_info = crypto->msg_info; + ctx->use_gcm_153 = false; // Default initialization. if (cfg && hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { return(-1); diff --git a/haicrypt/hcrypt_ctx_tx.c b/haicrypt/hcrypt_ctx_tx.c index 9ed5ba36c..e66c9497b 100644 --- a/haicrypt/hcrypt_ctx_tx.c +++ b/haicrypt/hcrypt_ctx_tx.c @@ -35,7 +35,7 @@ int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cf ctx->mode = (cfg->flags & HAICRYPT_CFG_F_GCM) ? HCRYPT_CTX_MODE_AESGCM : HCRYPT_CTX_MODE_AESCTR; ctx->status = HCRYPT_CTX_S_INIT; - + ctx->use_gcm_153 = false; // Default initialization. ctx->msg_info = crypto->msg_info; if (hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { @@ -184,7 +184,6 @@ int hcryptCtx_Tx_Refresh(hcrypt_Session *crypto) ASSERT(HCRYPT_CTX_S_SARDY <= new_ctx->status); /* Keep same KEK, configuration, and salt */ -// memcpy(&new_ctx->aes_kek, &ctx->aes_kek, sizeof(new_ctx->aes_kek)); memcpy(&new_ctx->cfg, &ctx->cfg, sizeof(new_ctx->cfg)); new_ctx->salt_len = ctx->salt_len; diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index 4db3fe627..66586158e 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -1,238 +1,235 @@ -################################################################################ -# Windows SRT Build Script -#============================ -# Usable on a Windows PC with Powershell and Visual studio, -# or called by CI systems like AppVeyor -# -# By default produces a VS2019 64-bit Release binary using C++11 threads, without -# encryption or unit tests enabled, but including test apps. -# Before enabling any encryption options, install OpenSSL or set VCKPG flag to build -################################################################################ - -param ( - [Parameter()][String]$VS_VERSION = "2019", - [Parameter()][String]$CONFIGURATION = "Release", - [Parameter()][String]$DEVENV_PLATFORM = "x64", - [Parameter()][String]$ENABLE_ENCRYPTION = "OFF", - [Parameter()][String]$STATIC_LINK_SSL = "OFF", - [Parameter()][String]$CXX11 = "ON", - [Parameter()][String]$BUILD_APPS = "ON", - [Parameter()][String]$UNIT_TESTS = "OFF", - [Parameter()][String]$BUILD_DIR = "_build", - [Parameter()][String]$VCPKG_OPENSSL = "OFF", - [Parameter()][String]$BONDING = "OFF" -) - -# cmake can be optionally installed (useful when running interactively on a developer station). -# The URL for automatic download is defined later in the script, but it should be possible to just vary the -# specific version set below and the URL should be stable enough to still work - you have been warned. -$cmakeVersion = "3.23.2" - -# make all errors trigger a script stop, rather than just carry on -$ErrorActionPreference = "Stop" - -$projectRoot = Join-Path $PSScriptRoot "/.." -Resolve - -# if running within AppVeyor, use environment variables to set params instead of passed-in values -if ( $Env:APPVEYOR ) { - if ( $Env:PLATFORM -eq 'x86' ) { $DEVENV_PLATFORM = 'Win32' } else { $DEVENV_PLATFORM = 'x64' } - if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2019' ) { $VS_VERSION='2019' } - if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2015' ) { $VS_VERSION='2015' } - if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2013' ) { $VS_VERSION='2013' } - - #if not statically linking OpenSSL, set flag to gather the specific openssl package from the build server into package - if ( $STATIC_LINK_SSL -eq 'OFF' ) { $Env:GATHER_SSL_INTO_PACKAGE = $true } - - #if unit tests are on, set flag to actually execute ctest step - if ( $UNIT_TESTS -eq 'ON' ) { $Env:RUN_UNIT_TESTS = $true } - - $CONFIGURATION = $Env:CONFIGURATION - - #appveyor has many openssl installations - place the latest one in the default location unless VS2013 - if( $VS_VERSION -ne '2013' ) { - Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null - Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null - } -} - -# persist VS_VERSION so it can be used in an artifact name later -$Env:VS_VERSION = $VS_VERSION - -# select the appropriate cmake generator string given the environment -if ( $VS_VERSION -eq '2019' ) { $CMAKE_GENERATOR = 'Visual Studio 16 2019'; $MSBUILDVER = "16.0"; } -if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015'; $MSBUILDVER = "14.0"; } -if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015 Win64'; $MSBUILDVER = "14.0"; } -if ( $VS_VERSION -eq '2013' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 12 2013'; $MSBUILDVER = "12.0"; } -if ( $VS_VERSION -eq '2013' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 12 2013 Win64'; $MSBUILDVER = "12.0"; } - -# clear any previous build and create & enter the build directory -$buildDir = Join-Path "$projectRoot" "$BUILD_DIR" -Write-Output "Creating (or cleaning if already existing) the folder $buildDir for project files and outputs" -Remove-Item -Path $buildDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null -New-Item -ItemType Directory -Path $buildDir -ErrorAction SilentlyContinue | Out-Null -Push-Location $buildDir - -# check cmake is installed -if ( $null -eq (Get-Command "cmake.exe" -ErrorAction SilentlyContinue) ) { - $installCmake = Read-Host "Unable to find cmake in your PATH - would you like to download and install automatically? [yes/no]" - - if ( $installCmake -eq "y" -or $installCmake -eq "yes" ) { - # download cmake and run MSI for user - $client = New-Object System.Net.WebClient - $tempDownloadFile = New-TemporaryFile - - $cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v$cmakeVersion/cmake-$cmakeVersion-win64-x64.msi" - $cmakeMsiFile = "$tempDownloadFile.cmake-$cmakeVersion-win64-x64.msi" - Write-Output "Downloading cmake from $cmakeUrl (temporary file location $cmakeMsiFile)" - Write-Output "Note: select the option to add cmake to path for this script to operate" - $client.DownloadFile("$cmakeUrl", "$cmakeMsiFile") - Start-Process $cmakeMsiFile -Wait - Remove-Item $cmakeMsiFile - Write-Output "Cmake should have installed, this script will now exit because of path updates - please now re-run this script" - throw - } - else{ - Write-Output "Quitting because cmake is required" - throw - } -} - -# get pthreads from nuget if CXX11 is not enabled -if ( $CXX11 -eq "OFF" ) { - # get pthreads (this is legacy, and is only available in nuget for VS2015 and VS2013) - if ( $VS_VERSION -gt 2015 ) { - Write-Output "Pthreads is not recommended for use beyond VS2015 and is not supported by this build script - aborting build" - throw - } - if ( $DEVENV_PLATFORM -eq 'Win32' ) { - nuget install cinegy.pthreads-win32-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages - } - else { - nuget install cinegy.pthreads-win64-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages - } -} - -# check to see if static SSL linking was requested, and enable encryption if not already ON -if ( $STATIC_LINK_SSL -eq "ON" ) { - if ( $ENABLE_ENCRYPTION -eq "OFF" ) { - # requesting a static link implicitly requires encryption support - Write-Output "Static linking to OpenSSL requested, will force encryption feature ON" - $ENABLE_ENCRYPTION = "ON" - } -} - -# check to see if VCPKG is marked to provide OpenSSL, and enable encryption if not already ON -if ( $VCPKG_OPENSSL -eq "ON" ) { - if ( $ENABLE_ENCRYPTION -eq "OFF" ) { - # requesting VCPKG to provide OpenSSL requires encryption support - Write-Output "VCPKG compilation of OpenSSL requested, will force encryption feature ON" - $ENABLE_ENCRYPTION = "ON" - } -} - -# build the cmake command flags from arguments -$cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " + - "-DENABLE_STDCXX_SYNC=$CXX11 " + - "-DENABLE_APPS=$BUILD_APPS " + - "-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " + - "-DENABLE_BONDING=$BONDING " + - "-DENABLE_UNITTESTS=$UNIT_TESTS" - -# if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package -if ( $VCPKG_OPENSSL -eq 'ON' ) { - Push-Location $projectRoot - Write-Output "Cloning VCPKG into: $(Get-Location)" - if (Test-Path -Path ".\vcpkg") { - Set-Location .\vcpkg - git pull - } else { - git clone https://github.com/microsoft/vcpkg - Set-Location .\vcpkg - } - - .\bootstrap-vcpkg.bat - - if($DEVENV_PLATFORM -EQ "x64"){ - if($STATIC_LINK_SSL -EQ "ON"){ - .\vcpkg install openssl:x64-windows-static - $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x64-windows-static" - } - else{ - .\vcpkg install openssl:x64-windows - } - } - else{ - if($STATIC_LINK_SSL -EQ "ON"){ - .\vcpkg install openssl:x86-windows-static - $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x86-windows-static" - } - else{ - .\vcpkg install openssl:x86-windows - } - } - - .\vcpkg integrate install - Pop-Location - $cmakeFlags += " -DCMAKE_TOOLCHAIN_FILE=$projectRoot\vcpkg\scripts\buildsystems\vcpkg.cmake" -} -else { - $cmakeFlags += " -DOPENSSL_USE_STATIC_LIBS=$STATIC_LINK_SSL " -} - -# cmake uses a flag for architecture from vs2019, so add that as a suffix -if ( $VS_VERSION -eq '2019' ) { - $cmakeFlags += " -A `"$DEVENV_PLATFORM`"" -} - -# fire cmake to build project files -$execVar = "cmake ../ -G`"$CMAKE_GENERATOR`" $cmakeFlags" -Write-Output $execVar - -# Reset reaction to Continue for cmake as it sometimes tends to print -# things on stderr, which is understood by PowerShell as error. The -# exit code from cmake will be checked anyway. -$ErrorActionPreference = "Continue" -Invoke-Expression "& $execVar" - -# check build ran OK, exit if cmake failed -if( $LASTEXITCODE -ne 0 ) { - Write-Output "Non-zero exit code from cmake: $LASTEXITCODE" - throw -} - -$ErrorActionPreference = "Stop" - -# run the set-version-metadata script to inject build numbers into appveyors console and the resulting DLL -. $PSScriptRoot/set-version-metadata.ps1 - -# look for msbuild -$msBuildPath = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue -if ( $null -eq $msBuildPath ) { - # no mbsuild in the path, so try to locate with 'vswhere' - $vsWherePath = Get-Command "vswhere.exe" -ErrorAction SilentlyContinue - if ( $null -eq $vsWherePath ) { - # no vswhere in the path, so check the Microsoft published location (true since VS2017 Update 2) - $vsWherePath = Get-Command "${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -ErrorAction SilentlyContinue - if ( $null -eq $vsWherePath ) { - Write-Output "Cannot find vswhere (used to locate msbuild). Please install VS2017 update 2 (or later) or add vswhere to your path and try again" - throw - } - } - $msBuildPath = & $vsWherePath -products * -version $MSBUILDVER -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1 - if ( $null -eq $msBuildPath ) { - Write-Output "vswhere.exe cannot find msbuild for the specified Visual Studio version - please check the installation" - throw - } -} - -& $msBuildPath SRT.sln -m /p:Configuration=$CONFIGURATION /p:Platform=$DEVENV_PLATFORM - -# return to the directory previously occupied before running the script -Pop-Location - -# if msbuild returned non-zero, throw to cause failure in CI -if( $LASTEXITCODE -ne 0 ) { - throw -} +################################################################################ +# Windows SRT Build Script +#============================ +# Usable on a Windows PC with Powershell and Visual studio, +# or called by CI systems like AppVeyor +# +# By default produces a VS2019 64-bit Release binary using C++11 threads, without +# encryption or unit tests enabled, but including test apps. +# Before enabling any encryption options, install OpenSSL or set VCKPG flag to build +################################################################################ + +param ( + [Parameter()][String]$VS_VERSION = "2022", + [Parameter()][String]$CONFIGURATION = "Release", + [Parameter()][String]$DEVENV_PLATFORM = "x64", + [Parameter()][String]$ENABLE_ENCRYPTION = "OFF", + [Parameter()][String]$STATIC_LINK_SSL = "OFF", + [Parameter()][String]$CXX11 = "ON", + [Parameter()][String]$BUILD_APPS = "ON", + [Parameter()][String]$UNIT_TESTS = "OFF", + [Parameter()][String]$BUILD_DIR = "_build", + [Parameter()][String]$VCPKG_OPENSSL = "OFF", + [Parameter()][String]$BONDING = "OFF" +) + +# cmake can be optionally installed (useful when running interactively on a developer station). +# The URL for automatic download is defined later in the script, but it should be possible to just vary the +# specific version set below and the URL should be stable enough to still work - you have been warned. +$cmakeVersion = "3.23.2" + +# make all errors trigger a script stop, rather than just carry on +$ErrorActionPreference = "Stop" + +$projectRoot = Join-Path $PSScriptRoot "/.." -Resolve + +# if running within AppVeyor, use environment variables to set params instead of passed-in values +if ( $Env:APPVEYOR ) { + if ( $Env:PLATFORM -eq 'x86' ) { $DEVENV_PLATFORM = 'Win32' } else { $DEVENV_PLATFORM = 'x64' } + if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2022' ) { $VS_VERSION='2022' } + if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2019' ) { $VS_VERSION='2019' } + if ( $Env:APPVEYOR_BUILD_WORKER_IMAGE -eq 'Visual Studio 2015' ) { $VS_VERSION='2015' } + + #if not statically linking OpenSSL, set flag to gather the specific openssl package from the build server into package + if ( $STATIC_LINK_SSL -eq 'OFF' ) { $Env:GATHER_SSL_INTO_PACKAGE = $true } + + #if unit tests are on, set flag to actually execute ctest step + if ( $UNIT_TESTS -eq 'ON' ) { $Env:RUN_UNIT_TESTS = $true } + + $CONFIGURATION = $Env:CONFIGURATION + + #appveyor has many openssl installations - place the latest one in the default location unless VS2013 + Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null + Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null +} + +# persist VS_VERSION so it can be used in an artifact name later +$Env:VS_VERSION = $VS_VERSION + +# select the appropriate cmake generator string given the environment +if ( $VS_VERSION -eq '2022' ) { $CMAKE_GENERATOR = 'Visual Studio 17 2022'; $MSBUILDVER = "17.10"; } +if ( $VS_VERSION -eq '2019' ) { $CMAKE_GENERATOR = 'Visual Studio 16 2019'; $MSBUILDVER = "16.0"; } +if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'Win32' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015'; $MSBUILDVER = "14.0"; } +if ( $VS_VERSION -eq '2015' -and $DEVENV_PLATFORM -eq 'x64' ) { $CMAKE_GENERATOR = 'Visual Studio 14 2015 Win64'; $MSBUILDVER = "14.0"; } + +# clear any previous build and create & enter the build directory +$buildDir = Join-Path "$projectRoot" "$BUILD_DIR" +Write-Output "Creating (or cleaning if already existing) the folder $buildDir for project files and outputs" +Remove-Item -Path $buildDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null +New-Item -ItemType Directory -Path $buildDir -ErrorAction SilentlyContinue | Out-Null +Push-Location $buildDir + +# check cmake is installed +if ( $null -eq (Get-Command "cmake.exe" -ErrorAction SilentlyContinue) ) { + $installCmake = Read-Host "Unable to find cmake in your PATH - would you like to download and install automatically? [yes/no]" + + if ( $installCmake -eq "y" -or $installCmake -eq "yes" ) { + # download cmake and run MSI for user + $client = New-Object System.Net.WebClient + $tempDownloadFile = New-TemporaryFile + + $cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v$cmakeVersion/cmake-$cmakeVersion-win64-x64.msi" + $cmakeMsiFile = "$tempDownloadFile.cmake-$cmakeVersion-win64-x64.msi" + Write-Output "Downloading cmake from $cmakeUrl (temporary file location $cmakeMsiFile)" + Write-Output "Note: select the option to add cmake to path for this script to operate" + $client.DownloadFile("$cmakeUrl", "$cmakeMsiFile") + Start-Process $cmakeMsiFile -Wait + Remove-Item $cmakeMsiFile + Write-Output "Cmake should have installed, this script will now exit because of path updates - please now re-run this script" + throw + } + else{ + Write-Output "Quitting because cmake is required" + throw + } +} + +# get pthreads from nuget if CXX11 is not enabled +if ( $CXX11 -eq "OFF" ) { + # get pthreads (this is legacy, and is only available in nuget for VS2015 and VS2013) + if ( $VS_VERSION -gt 2015 ) { + Write-Output "Pthreads is not recommended for use beyond VS2015 and is not supported by this build script - aborting build" + throw + } + if ( $DEVENV_PLATFORM -eq 'Win32' ) { + nuget install cinegy.pthreads-win32-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages + } + else { + nuget install cinegy.pthreads-win64-$VS_VERSION -version 2.9.1.24 -OutputDirectory ../_packages + } +} + +# check to see if static SSL linking was requested, and enable encryption if not already ON +if ( $STATIC_LINK_SSL -eq "ON" ) { + if ( $ENABLE_ENCRYPTION -eq "OFF" ) { + # requesting a static link implicitly requires encryption support + Write-Output "Static linking to OpenSSL requested, will force encryption feature ON" + $ENABLE_ENCRYPTION = "ON" + } +} + +# check to see if VCPKG is marked to provide OpenSSL, and enable encryption if not already ON +if ( $VCPKG_OPENSSL -eq "ON" ) { + if ( $ENABLE_ENCRYPTION -eq "OFF" ) { + # requesting VCPKG to provide OpenSSL requires encryption support + Write-Output "VCPKG compilation of OpenSSL requested, will force encryption feature ON" + $ENABLE_ENCRYPTION = "ON" + } +} + +# build the cmake command flags from arguments +$cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " + + "-DENABLE_STDCXX_SYNC=$CXX11 " + + "-DENABLE_APPS=$BUILD_APPS " + + "-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " + + "-DENABLE_BONDING=$BONDING " + + "-DENABLE_UNITTESTS=$UNIT_TESTS" + +# if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package +if ( $VCPKG_OPENSSL -eq 'ON' ) { + Push-Location $projectRoot + Write-Output "Cloning VCPKG into: $(Get-Location)" + if (Test-Path -Path ".\vcpkg") { + Set-Location .\vcpkg + git pull + } else { + git clone https://github.com/microsoft/vcpkg + Set-Location .\vcpkg + } + + .\bootstrap-vcpkg.bat + + if($DEVENV_PLATFORM -EQ "x64"){ + if($STATIC_LINK_SSL -EQ "ON"){ + .\vcpkg install openssl:x64-windows-static + $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x64-windows-static" + } + else{ + .\vcpkg install openssl:x64-windows + } + } + else{ + if($STATIC_LINK_SSL -EQ "ON"){ + .\vcpkg install openssl:x86-windows-static + $cmakeFlags += " -DVCPKG_TARGET_TRIPLET=x86-windows-static" + } + else{ + .\vcpkg install openssl:x86-windows + } + } + + .\vcpkg integrate install + Pop-Location + $cmakeFlags += " -DCMAKE_TOOLCHAIN_FILE=$projectRoot\vcpkg\scripts\buildsystems\vcpkg.cmake" +} +else { + $cmakeFlags += " -DOPENSSL_USE_STATIC_LIBS=$STATIC_LINK_SSL " +} + +# cmake uses a flag for architecture from vs2019, so add that as a suffix +if ( $VS_VERSION -ge '2019' ) { + $cmakeFlags += " -A `"$DEVENV_PLATFORM`"" +} + +# fire cmake to build project files +$execVar = "cmake ../ -G`"$CMAKE_GENERATOR`" $cmakeFlags" +Write-Output $execVar + +# Reset reaction to Continue for cmake as it sometimes tends to print +# things on stderr, which is understood by PowerShell as error. The +# exit code from cmake will be checked anyway. +$ErrorActionPreference = "Continue" +Invoke-Expression "& $execVar" + +# check build ran OK, exit if cmake failed +if( $LASTEXITCODE -ne 0 ) { + Write-Output "Non-zero exit code from cmake: $LASTEXITCODE" + throw +} + +$ErrorActionPreference = "Stop" + +# run the set-version-metadata script to inject build numbers into appveyors console and the resulting DLL +. $PSScriptRoot/set-version-metadata.ps1 + +# look for msbuild +$msBuildPath = Get-Command "msbuild.exe" -ErrorAction SilentlyContinue +if ( $null -eq $msBuildPath ) { + # no mbsuild in the path, so try to locate with 'vswhere' + $vsWherePath = Get-Command "vswhere.exe" -ErrorAction SilentlyContinue + if ( $null -eq $vsWherePath ) { + # no vswhere in the path, so check the Microsoft published location (true since VS2017 Update 2) + $vsWherePath = Get-Command "${Env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -ErrorAction SilentlyContinue + if ( $null -eq $vsWherePath ) { + Write-Output "Cannot find vswhere (used to locate msbuild). Please install VS2017 update 2 (or later) or add vswhere to your path and try again" + throw + } + } + $msBuildPath = & $vsWherePath -products * -version $MSBUILDVER -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1 + if ( $null -eq $msBuildPath ) { + Write-Output "vswhere.exe cannot find msbuild for the specified Visual Studio version - please check the installation" + throw + } +} + +& $msBuildPath SRT.sln -m /p:Configuration=$CONFIGURATION /p:Platform=$DEVENV_PLATFORM + +# return to the directory previously occupied before running the script +Pop-Location + +# if msbuild returned non-zero, throw to cause failure in CI +if( $LASTEXITCODE -ne 0 ) { + throw +} diff --git a/scripts/release-notes/README.md b/scripts/release-notes/README.md index d6c4adbfc..d34b07bfa 100644 --- a/scripts/release-notes/README.md +++ b/scripts/release-notes/README.md @@ -11,7 +11,7 @@ git log --pretty=format:"%h|%s|%an|%ae" v1.4.0...HEAD > commits.csv Use the produced `commits.csv` file as an input to the script: ```shell -python scripts/release-notes/generate-release-notes.py commits.csv +python scripts/release-notes/generate_release_notes.py commits.csv ``` The script produces `release-notes.md` as an output. diff --git a/scripts/visionOS.cmake b/scripts/visionOS.cmake new file mode 100644 index 000000000..0f0f60b69 --- /dev/null +++ b/scripts/visionOS.cmake @@ -0,0 +1,171 @@ +# This file is based off of the Platform/Darwin.cmake and Platform/UnixPaths.cmake +# files which are included with CMake 2.8.4 +# It has been altered for VISIONOS development + +# Options: +# +# VISION_PLATFORM = OS (default) or SIMULATOR or SIMULATOR64 +# This decides if SDKS will be selected from the XROS.platform or XRSimulator.platform folders +# OS - the default, used to build for Vision Pro physical device, which have an arm arch. +# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. +# +# VISIONOS_ARCH = arm64 (default for OS), x86_64 (addiitonal support for SIMULATOR64) +# +# CMAKE_VISIONOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder +# By default this location is automatcially chosen based on the VISIONOS_PLATFORM value above. +# If set manually, it will override the default location and force the user of a particular Developer Platform +# +# CMAKE_VISIONOS_SDK_ROOT = automatic(default) or /path/to/platform/Developer/SDKs/SDK folder +# By default this location is automatcially chosen based on the CMAKE_VISIONOS_DEVELOPER_ROOT value. +# In this case it will always be the most up-to-date SDK found in the CMAKE_VISIONOS_DEVELOPER_ROOT path. +# If set manually, this will force the use of a specific SDK version +# + +# Standard settings +set (CMAKE_SYSTEM_NAME Darwin) +set (CMAKE_SYSTEM_VERSION 1) +set (UNIX True) +set (APPLE True) +set (VISIONOS True) + +# Required as of cmake 2.8.10 +set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for visionOs" FORCE) + +# Determine the cmake host system version so we know where to find the visionOS SDKs +find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) +if (CMAKE_UNAME) + execute_process(COMMAND uname -r + OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) + string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") +endif (CMAKE_UNAME) + + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(CMAKE_AR ar CACHE FILEPATH "" FORCE) + +set (CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG "-compatibility_version ") +set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") +set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") +set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") + +if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR ENABLE_DEBUG) + set(VISIONOS_DEBUG_OPTIONS "-glldb -gmodules") +else() + set(VISIONOS_DEBUG_OPTIONS "-fvisibility=hidden -fvisibility-inlines-hidden") +endif() + +set (CMAKE_C_FLAGS_INIT "${VISIONOS_DEBUG_OPTIONS} ${EMBED_OPTIONS}") +set (CMAKE_CXX_FLAGS_INIT "${VISIONOS_DEBUG_OPTIONS} ${EMBED_OPTIONS}") + +set (CMAKE_C_LINK_FLAGS "-Wl,-search_paths_first ${EMBED_OPTIONS} ${CMAKE_C_LINK_FLAGS}") +set (CMAKE_CXX_LINK_FLAGS "-Wl,-search_paths_first ${EMBED_OPTIONS} ${CMAKE_CXX_LINK_FLAGS}") + + +set (CMAKE_PLATFORM_HAS_INSTALLNAME 1) +set (CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-dynamiclib") +set (CMAKE_SHARED_MODULE_CREATE_C_FLAGS "-bundle") +set (CMAKE_SHARED_MODULE_LOADER_C_FLAG "-Wl,-bundle_loader,") +set (CMAKE_SHARED_MODULE_LOADER_CXX_FLAG "-Wl,-bundle_loader,") +set (CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".so" ".a") + +# Specify install_name_tool and pkg-config since it outside of SDK path and therefore can't be found by CMake +if (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + find_program(CMAKE_INSTALL_NAME_TOOL install_name_tool) +endif (NOT DEFINED CMAKE_INSTALL_NAME_TOOL) + +if (NOT DEFINED PKG_CONFIG_EXECUTABLE) + find_program(PKG_CONFIG_EXECUTABLE NAMES pkg-config) + if (DEFINED PKG_CONFIG_EXECUTABLE) + execute_process(COMMAND pkg-config --version OUTPUT_VARIABLE PKG_CONFIG_VERSION_STRING) + endif(DEFINED PKG_CONFIG_EXECUTABLE) +endif(NOT DEFINED PKG_CONFIG_EXECUTABLE) + + +# fffio Specify path to install shared library on device +set (CMAKE_INSTALL_NAME_DIR "@executable_path/Frameworks") +set (CMAKE_BUILD_WITH_INSTALL_NAME_DIR TRUE) + +# Setup visionOS platform unless specified manually with VISIONOS_PLATFORM +if (NOT DEFINED VISIONOS_PLATFORM) + set (VISIONOS_PLATFORM "OS") +endif (NOT DEFINED VISIONOS_PLATFORM) +set (VISIONOS_PLATFORM ${VISIONOS_PLATFORM} CACHE STRING "Type of visionOS Platform") + +# Check the platform selection and setup for developer root +if (${VISIONOS_PLATFORM} STREQUAL OS) + set (VISIONOS_PLATFORM_LOCATION "XROS.platform") + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xros") +elseif (${VISIONOS_PLATFORM} STREQUAL SIMULATOR) + set (SIMULATOR true) + set (VISIONOS_PLATFORM_LOCATION "XRSimulator.platform") + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xrsimulator") +elseif (${VISIONOS_PLATFORM} STREQUAL SIMULATOR64) + set (SIMULATOR true) + set (VISIONOS_PLATFORM_LOCATION "XRSimulator.platform") + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-xrsimulator") +else (${VISIONOS_PLATFORM} STREQUAL OS) + message (FATAL_ERROR "Unsupported VISIONOS_PLATFORM value selected. Please choose OS or SIMULATOR") +endif (${VISIONOS_PLATFORM} STREQUAL OS) + +# Setup visionOS developer location unless specified manually with CMAKE_VISIONOS_DEVELOPER_ROOT +if (NOT DEFINED CMAKE_VISIONOS_DEVELOPER_ROOT) + execute_process(COMMAND /usr/bin/xcode-select -print-path + OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR) + string(STRIP "${CMAKE_XCODE_DEVELOPER_DIR}" CMAKE_XCODE_DEVELOPER_DIR) # FIXED: remove new line character, otherwise it complain no visionOS SDK's found in default search path + set (CMAKE_VISIONOS_DEVELOPER_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${VISIONOS_PLATFORM_LOCATION}/Developer") +endif (NOT DEFINED CMAKE_VISIONOS_DEVELOPER_ROOT) +set (CMAKE_VISIONOS_DEVELOPER_ROOT ${CMAKE_VISIONOS_DEVELOPER_ROOT} CACHE PATH "Location of visionOS Platform") + +# Find and use the most recent visionOS sdk unless specified manually with CMAKE_VISIONOS_SDK_ROOT +if (NOT DEFINED CMAKE_VISIONOS_SDK_ROOT) + file (GLOB _CMAKE_VISIONOS_SDKS "${CMAKE_VISIONOS_DEVELOPER_ROOT}/SDKs/*") + if (_CMAKE_VISIONOS_SDKS) + list (SORT _CMAKE_VISIONOS_SDKS) + list (REVERSE _CMAKE_VISIONOS_SDKS) + list (GET _CMAKE_VISIONOS_SDKS 0 CMAKE_VISIONOS_SDK_ROOT) + else (_CMAKE_VISIONOS_SDKS) + message (FATAL_ERROR "No visionOS SDK's found in default search path ${CMAKE_VISIONOS_DEVELOPER_ROOT}. Manually set CMAKE_VISIONOS_SDK_ROOT or install the visionOS SDK.") + endif (_CMAKE_VISIONOS_SDKS) + message (STATUS "Toolchain using default visionOS SDK: ${CMAKE_VISIONOS_SDK_ROOT}") +endif (NOT DEFINED CMAKE_VISIONOS_SDK_ROOT) +set (CMAKE_VISIONOS_SDK_ROOT ${CMAKE_VISIONOS_SDK_ROOT} CACHE PATH "Location of the selected visionOS SDK") + +# Set the sysroot default to the most recent SDK +set (CMAKE_OSX_SYSROOT ${CMAKE_VISIONOS_SDK_ROOT} CACHE PATH "Sysroot used for visionOS support") + +# set the architecture for visionOS +if (NOT DEFINED VISIONOS_ARCH) + if (${VISIONOS_PLATFORM} STREQUAL OS) + set (VISIONOS_ARCH arm64) + elseif (${VISIONOS_PLATFORM} STREQUAL SIMULATOR) + set (VISIONOS_ARCH arm64) + elseif (${VISIONOS_PLATFORM} STREQUAL SIMULATOR64) + set (VISIONOS_ARCH x86_64) + endif (${VISIONOS_PLATFORM} STREQUAL OS) +endif(NOT DEFINED VISIONOS_ARCH) +set (CMAKE_OSX_ARCHITECTURES ${VISIONOS_ARCH} CACHE STRING "Build architecture for visionOS") + +# Set the find root to the visionOS developer roots and to user defined paths +set (CMAKE_FIND_ROOT_PATH ${CMAKE_VISIONOS_DEVELOPER_ROOT} ${CMAKE_VISIONOS_SDK_ROOT} ${CMAKE_PREFIX_PATH} CACHE STRING "visionOS find search path root") + +# default to searching for frameworks first +set (CMAKE_FIND_FRAMEWORK FIRST) + +# set up the default search directories for frameworks +set (CMAKE_SYSTEM_FRAMEWORK_PATH + ${CMAKE_VISIONOS_SDK_ROOT}/System/Library/Frameworks + ${CMAKE_VISIONOS_SDK_ROOT}/System/Library/PrivateFrameworks + ${CMAKE_VISIONOS_SDK_ROOT}/Developer/Library/Frameworks +) + +# only search the visionOS sdks, not the remainder of the host filesystem (except for programs, so that we can still find Python if needed) +set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + diff --git a/sonar-project.properties b/sonar-project.properties index 22b16d549..d11323df8 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -19,7 +19,6 @@ sonar.organization=haivision # ===================================================== sonar.links.homepage=https://github.com/Haivision/srt -sonar.links.ci=https://travis-ci.org/Haivision/srt sonar.links.scm=https://github.com/Haivision/srt sonar.links.issue=https://github.com/Haivision/srt/issues @@ -29,8 +28,9 @@ sonar.links.issue=https://github.com/Haivision/srt/issues # ===================================================== # SQ standard properties -sonar.sources=. +sonar.sources=srtcore/,apps/,common/,examples/,haicrypt/,scripts/,testing/ +sonar.tests=test/ # Properties specific to the C/C++ analyzer: -sonar.cfamily.build-wrapper-output=bw-output +sonar.cfamily.build-wrapper-output=_build/sonar-output sonar.cfamily.gcov.reportsPath=. diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 8954f183c..0cef76383 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -174,8 +174,7 @@ srt::CUDTUnited::CUDTUnited() , m_GlobControlLock() , m_IDLock() , m_mMultiplexer() - , m_MultiplexerLock() - , m_pCache(NULL) + , m_pCache(new CCache) , m_bClosing(false) , m_GCStopCond() , m_InitLock() @@ -195,8 +194,6 @@ srt::CUDTUnited::CUDTUnited() setupMutex(m_GlobControlLock, "GlobControl"); setupMutex(m_IDLock, "ID"); setupMutex(m_InitLock, "Init"); - - m_pCache = new CCache; } srt::CUDTUnited::~CUDTUnited() @@ -239,6 +236,8 @@ string srt::CUDTUnited::CONID(SRTSOCKET sock) int srt::CUDTUnited::startup() { ScopedLock gcinit(m_InitLock); + if (m_bGCStatus) + return 1; if (m_iInstanceCount++ > 0) return 1; @@ -257,9 +256,6 @@ int srt::CUDTUnited::startup() PacketFilter::globalInit(); - if (m_bGCStatus) - return 1; - m_bClosing = false; if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC")) @@ -541,6 +537,8 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, try { + // Protect the config of the listener socket from a data race. + ScopedLock lck(ls->core().m_ConnectionLock); ns = new CUDTSocket(*ls); // No need to check the peer, this is the address from which the request has come. ns->m_PeerAddr = peer; @@ -656,6 +654,9 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, HLOGC(cnlog.Debug, log << "newConnection: mapping peer " << ns->m_PeerID << " to that socket (" << ns->m_SocketID << ")"); m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID); + + LOGC(cnlog.Note, log << "@" << ns->m_SocketID << " connection on listener @" << listen + << " (" << ns->m_SelfAddr.str() << ") from peer @" << ns->m_PeerID << " (" << peer.str() << ")"); } catch (...) { @@ -771,7 +772,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, enterCS(ls->m_AcceptLock); try { - ls->m_QueuedSockets.insert(ns->m_SocketID); + ls->m_QueuedSockets[ns->m_SocketID] = ns->m_PeerAddr; } catch (...) { @@ -867,7 +868,7 @@ SRT_EPOLL_T srt::CUDTSocket::getListenerEvents() #else // Could do #endif here, but the compiler would complain about unreachable code. - set sockets_copy; + map sockets_copy; { ScopedLock accept_lock (m_AcceptLock); sockets_copy = m_QueuedSockets; @@ -878,7 +879,7 @@ SRT_EPOLL_T srt::CUDTSocket::getListenerEvents() } #if ENABLE_BONDING -int srt::CUDTUnited::checkQueuedSocketsEvents(const set& sockets) +int srt::CUDTUnited::checkQueuedSocketsEvents(const map& sockets) { SRT_EPOLL_T flags = 0; @@ -890,9 +891,9 @@ int srt::CUDTUnited::checkQueuedSocketsEvents(const set& sockets) // that they should re-read the group list and re-check readiness. // Now we can do lock once and for all - for (set::iterator i = sockets.begin(); i != sockets.end(); ++i) + for (map::const_iterator i = sockets.begin(); i != sockets.end(); ++i) { - CUDTSocket* s = locateSocket_LOCKED(*i); + CUDTSocket* s = locateSocket_LOCKED(i->first); if (!s) continue; // wiped in the meantime - ignore @@ -1177,8 +1178,22 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int } else if (ls->m_QueuedSockets.size() > 0) { - set::iterator b = ls->m_QueuedSockets.begin(); - u = *b; + map::iterator b = ls->m_QueuedSockets.begin(); + + if (pw_addr != NULL && pw_addrlen != NULL) + { + // Check if the length of the buffer to fill the name in + // was large enough. + const int len = b->second.size(); + if (*pw_addrlen < len) + { + // 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); + } + } + + u = b->first; ls->m_QueuedSockets.erase(b); accepted = true; } @@ -1253,14 +1268,8 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int if (pw_addr != NULL && pw_addrlen != NULL) { - // Check if the length of the buffer to fill the name in - // was large enough. - const int len = s->m_PeerAddr.size(); - if (*pw_addrlen < len) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - - memcpy((pw_addr), &s->m_PeerAddr, len); - *pw_addrlen = len; + memcpy((pw_addr), s->m_PeerAddr.get(), s->m_PeerAddr.size()); + *pw_addrlen = s->m_PeerAddr.size(); } return u; @@ -1304,7 +1313,7 @@ void srt::CUDTUnited::removePendingForGroup(const CUDTGroup* g) for (list::iterator m = members.begin(), mx = m; m != members.end(); m = mx) { ++mx; - std::set::iterator q = s->m_QueuedSockets.find(*m); + std::map::iterator q = s->m_QueuedSockets.find(*m); if (q != s->m_QueuedSockets.end()) { HLOGC(cnlog.Debug, log << "accept: listener @" << s->m_SocketID @@ -1541,7 +1550,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 = "setting group-derived option: #" + Sprint(g.m_config[i].so); + error_reason = "group-derived option: #" + Sprint(g.m_config[i].so); ns->core().setOpt(g.m_config[i].so, &g.m_config[i].value[0], (int)g.m_config[i].value.size()); } @@ -2052,11 +2061,28 @@ int srt::CUDTUnited::close(const SRTSOCKET u) return 0; } #endif - CUDTSocket* s = locateSocket(u); - if (!s) - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); +#if ENABLE_HEAVY_LOGGING + // Wrapping the log into a destructor so that it + // is printed AFTER the destructor of SocketKeeper. + struct ScopedExitLog + { + const CUDTSocket* const ps; + ScopedExitLog(const CUDTSocket* p): ps(p){} + ~ScopedExitLog() + { + if (ps) // Could be not acquired by SocketKeeper, occasionally + { + HLOGC(smlog.Debug, log << "CUDTUnited::close/end: @" << ps->m_SocketID << " busy=" << ps->isStillBusy()); + } + } + }; +#endif + + SocketKeeper k(*this, u, ERH_THROW); + IF_HEAVY_LOGGING(ScopedExitLog slog(k.socket)); + HLOGC(smlog.Debug, log << "CUDTUnited::close/begin: @" << u << " busy=" << k.socket->isStillBusy()); - return close(s); + return close(k.socket); } #if ENABLE_BONDING @@ -2690,6 +2716,45 @@ srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) } #endif +srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh) +{ + ScopedLock cg(m_GlobControlLock); + + CUDTSocket* s = locateSocket_LOCKED(u); + if (!s) + { + if (erh == ERH_THROW) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + return NULL; + } + + s->apiAcquire(); + return s; +} + +bool srt::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 + // one more GC cycle to live. In other words, you must be certain + // that this pointer passed here isn't dangling and was obtained + // directly from m_Sockets, or even better, has been acquired + // by some other functionality already, which is only about to + // be released earlier than you need. + ScopedLock cg(m_GlobControlLock); + s->apiAcquire(); + // 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) + { + s->apiRelease(); + return false; + } + + return true; +} + srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) { ScopedLock cg(m_GlobControlLock); @@ -2756,7 +2821,7 @@ void srt::CUDTUnited::checkBrokenSockets() if (s->m_Status == SRTS_LISTENING) { - const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp; + const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp.load(); // A listening socket should wait an extra 3 seconds // in case a client is connecting. if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS)) @@ -2814,31 +2879,48 @@ void srt::CUDTUnited::checkBrokenSockets() for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j) { + CUDTSocket* ps = j->second; + + // NOTE: There is still a hypothetical risk here that ps + // was made busy while the socket was already moved to m_ClosedSocket, + // if the socket was acquired through CUDTUnited::acquireSocket (that is, + // busy flag acquisition was done through the CUDTSocket* pointer rather + // than through the numeric ID). Therefore this way of busy acquisition + // should be done only if at the moment of acquisition there are certainly + // 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."); + continue; + } + + CUDT& u = ps->core(); + // HLOGC(smlog.Debug, log << "checking CLOSED socket: " << j->first); - if (!is_zero(j->second->core().m_tsLingerExpiration)) + if (!is_zero(u.m_tsLingerExpiration)) { // asynchronous close: - if ((!j->second->core().m_pSndBuffer) || (0 == j->second->core().m_pSndBuffer->getCurrBufSize()) || - (j->second->core().m_tsLingerExpiration <= steady_clock::now())) + if ((!u.m_pSndBuffer) || (0 == u.m_pSndBuffer->getCurrBufSize()) || + (u.m_tsLingerExpiration <= steady_clock::now())) { - HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED qualified @" << j->second->m_SocketID); - j->second->core().m_tsLingerExpiration = steady_clock::time_point(); - j->second->core().m_bClosing = true; - j->second->m_tsClosureTimeStamp = steady_clock::now(); + HLOGC(smlog.Debug, log << "checkBrokenSockets: marking CLOSED qualified @" << ps->m_SocketID); + u.m_tsLingerExpiration = steady_clock::time_point(); + u.m_bClosing = true; + ps->m_tsClosureTimeStamp = steady_clock::now(); } } // timeout 1 second to destroy a socket AND it has been removed from // RcvUList const steady_clock::time_point now = steady_clock::now(); - const steady_clock::duration closed_ago = now - j->second->m_tsClosureTimeStamp; + const steady_clock::duration closed_ago = now - ps->m_tsClosureTimeStamp.load(); if (closed_ago > seconds_from(1)) { - CRNode* rnode = j->second->core().m_pRNode; + CRNode* rnode = u.m_pRNode; if (!rnode || !rnode->m_bOnList) { HLOGC(smlog.Debug, - log << "checkBrokenSockets: @" << j->second->m_SocketID << " closed " + log << "checkBrokenSockets: @" << ps->m_SocketID << " closed " << FormatDuration(closed_ago) << " ago and removed from RcvQ - will remove"); // HLOGC(smlog.Debug, log << "will unref socket: " << j->first); @@ -2881,6 +2963,14 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) if (rn && rn->m_bOnList) return; + if (s->isStillBusy()) + { + HLOGC(smlog.Debug, log << "@" << s->m_SocketID << " is still busy, NOT deleting"); + return; + } + + LOGC(smlog.Note, log << "@" << s->m_SocketID << " busy=" << s->isStillBusy()); + #if ENABLE_BONDING if (s->m_GroupOf) { @@ -2897,23 +2987,24 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) // if it is a listener, close all un-accepted sockets in its queue // and remove them later - for (set::iterator q = s->m_QueuedSockets.begin(); q != s->m_QueuedSockets.end(); ++q) + for (map::iterator q = s->m_QueuedSockets.begin(); + q != s->m_QueuedSockets.end(); ++ q) { - sockets_t::iterator si = m_Sockets.find(*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) << " being queued for listener socket @" - << s->m_SocketID << " is GONE in the meantime ???"); + 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] = as; - m_Sockets.erase(*q); + m_ClosedSockets[q->first] = as; + m_Sockets.erase(q->first); } } @@ -3075,7 +3166,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons bool reuse_attempt = false; for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) { - CMultiplexer& m = i->second; + CMultiplexer const& m = i->second; // First, we need to find a multiplexer with the same port. if (m.m_iPort != port) @@ -3530,8 +3621,7 @@ int srt::CUDT::cleanup() SRTSOCKET srt::CUDT::socket() { - if (!uglobal().m_bGCStatus) - uglobal().startup(); + uglobal().startup(); try { @@ -3581,8 +3671,7 @@ srt::CUDTGroup& srt::CUDT::newGroup(const int type) SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) { // Doing the same lazy-startup as with srt_create_socket() - if (!uglobal().m_bGCStatus) - uglobal().startup(); + uglobal().startup(); try { @@ -3959,7 +4048,7 @@ 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) { - if (!optval) + if (!optval || optlen < 0) return APIError(MJ_NOTSUP, MN_INVAL, 0); try @@ -4805,21 +4894,18 @@ void setloglevel(LogLevel::type ll) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.max_level = ll; - srt_logger_config.updateLoggersState(); } void addlogfa(LogFA fa) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.set(fa, true); - srt_logger_config.updateLoggersState(); } void dellogfa(LogFA fa) { ScopedLock gg(srt_logger_config.mutex); srt_logger_config.enabled_fa.set(fa, false); - srt_logger_config.updateLoggersState(); } void resetlogfa(set fas) @@ -4827,7 +4913,6 @@ 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_logger_config.updateLoggersState(); } void resetlogfa(const int* fara, size_t fara_size) @@ -4836,7 +4921,6 @@ void resetlogfa(const int* fara, size_t fara_size) 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_logger_config.updateLoggersState(); } void setlogstream(std::ostream& stream) diff --git a/srtcore/api.h b/srtcore/api.h index 412375688..6c36c9717 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -123,6 +123,18 @@ class CUDTSocket void construct(); +private: + srt::sync::atomic m_iBusy; +public: + void apiAcquire() { ++m_iBusy; } + void apiRelease() { --m_iBusy; } + + int isStillBusy() const + { + return m_iBusy; + } + + SRT_ATTR_GUARDED_BY(m_ControlLock) sync::atomic m_Status; //< current socket state @@ -131,7 +143,8 @@ class CUDTSocket /// of sockets in order to prevent other methods from accessing invalid address. /// A timer is started and the socket will be removed after approximately /// 1 second (see CUDTUnited::checkBrokenSockets()). - sync::steady_clock::time_point m_tsClosureTimeStamp; + //sync::steady_clock::time_point m_tsClosureTimeStamp; + sync::AtomicClock m_tsClosureTimeStamp; sockaddr_any m_SelfAddr; //< local address of the socket sockaddr_any m_PeerAddr; //< peer address of the socket @@ -151,7 +164,7 @@ class CUDTSocket CUDT m_UDT; //< internal SRT socket logic public: - std::set m_QueuedSockets; //< set of connections waiting for accept() + std::map m_QueuedSockets; //< set of connections waiting for accept() sync::Condition m_AcceptCond; //< used to block "accept" call sync::Mutex m_AcceptLock; //< mutex associated to m_AcceptCond @@ -277,7 +290,7 @@ class CUDTUnited #if ENABLE_BONDING SRT_ATTR_REQUIRES(m_GlobControlLock) - int checkQueuedSocketsEvents(const std::set& sockets); + int checkQueuedSocketsEvents(const std::map& sockets); void removePendingForGroup(const CUDTGroup* g); #endif @@ -332,7 +345,7 @@ class CUDTUnited int epoll_release(const int eid); #if ENABLE_BONDING - // [[using locked(m_GlobControlLock)]] + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_GlobControlLock) CUDTGroup& addGroup(SRTSOCKET id, SRT_GROUP_TYPE type) { // This only ensures that the element exists. @@ -354,7 +367,7 @@ class CUDTUnited void deleteGroup(CUDTGroup* g); void deleteGroup_LOCKED(CUDTGroup* g); - // [[using locked(m_GlobControlLock)]] + SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_GlobControlLock) CUDTGroup* findPeerGroup_LOCKED(SRTSOCKET peergroup) { for (groups_t::iterator i = m_Groups.begin(); i != m_Groups.end(); ++i) @@ -393,11 +406,13 @@ class CUDTUnited private: typedef std::map sockets_t; // stores all the socket structures - sockets_t m_Sockets; + SRT_ATTR_GUARDED_BY(m_GlobControlLock) + sockets_t m_Sockets; #if ENABLE_BONDING typedef std::map groups_t; - groups_t m_Groups; + SRT_ATTR_GUARDED_BY(m_GlobControlLock) + groups_t m_Groups; #endif sync::Mutex m_GlobControlLock; // used to synchronize UDT API @@ -407,6 +422,7 @@ class CUDTUnited SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID SRTSOCKET m_SocketIDGenerator_init; // Keeps track of the very first one + SRT_ATTR_GUARDED_BY(m_GlobControlLock) std::map > m_PeerRec; // record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn @@ -450,8 +466,52 @@ class CUDTUnited } } }; - #endif + + CUDTSocket* locateAcquireSocket(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); + bool acquireSocket(CUDTSocket* s); + +public: + struct SocketKeeper + { + CUDTSocket* socket; + + SocketKeeper(): socket(NULL) {} + + // This is intended for API functions to lock the socket's existence + // for the lifetime of their call. + SocketKeeper(CUDTUnited& glob, SRTSOCKET id, ErrorHandling erh = ERH_RETURN) { socket = glob.locateAcquireSocket(id, erh); } + + // This is intended for TSBPD thread that should lock the socket's + // existence until it exits. + SocketKeeper(CUDTUnited& glob, CUDTSocket* s) + { + acquire(glob, s); + } + + // 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) + { + if (s == NULL) + return false; + const bool caught = glob.acquireSocket(s); + socket = caught ? s : NULL; + return caught; + } + + ~SocketKeeper() + { + if (socket) + { + SRT_ASSERT(socket->isStillBusy() > 0); + socket->apiRelease(); + } + } + }; + +private: + void updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* = NULL); bool updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); @@ -468,11 +528,13 @@ class CUDTUnited const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket); private: + SRT_ATTR_GUARDED_BY(m_GlobControlLock) std::map m_mMultiplexer; // UDP multiplexer - sync::Mutex m_MultiplexerLock; -private: - CCache* m_pCache; // UDT network information cache + /// UDT network information cache. + /// Existence is guarded by m_GlobControlLock, but the cache itself is thread-safe. + SRT_ATTR_GUARDED_BY(m_GlobControlLock) + CCache* const m_pCache; private: srt::sync::atomic m_bClosing; @@ -480,14 +542,19 @@ class CUDTUnited sync::Condition m_GCStopCond; sync::Mutex m_InitLock; + SRT_ATTR_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_ATTR_GUARDED_BY(m_InitLock) sync::CThread m_GCThread; static void* garbageCollect(void*); + SRT_ATTR_GUARDED_BY(m_GlobControlLock) sockets_t m_ClosedSockets; // temporarily store closed sockets #if ENABLE_BONDING + SRT_ATTR_GUARDED_BY(m_GlobControlLock) groups_t m_ClosedGroups; #endif diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index 7ea0945bc..363bd7e9c 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -206,7 +206,7 @@ int CRcvBuffer::insert(CUnit* unit) return 0; } -int CRcvBuffer::dropUpTo(int32_t seqno) +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); @@ -215,16 +215,23 @@ int CRcvBuffer::dropUpTo(int32_t seqno) if (len <= 0) { IF_RCVBUF_DEBUG(scoped_log.ss << ". Nothing to drop."); - return 0; + return std::make_pair(0, 0); } m_iMaxPosOff -= len; if (m_iMaxPosOff < 0) m_iMaxPosOff = 0; - const int iDropCnt = len; + int iNumDropped = 0; // Number of dropped packets that were missing. + int iNumDiscarded = 0; // The number of dropped packets that existed in the buffer. while (len > 0) { + // 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; + else if (m_entries[m_iStartPos].status == EntryState_Empty) + ++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); @@ -239,14 +246,14 @@ int CRcvBuffer::dropUpTo(int32_t seqno) // If the nonread position is now behind the starting position, set it to the starting position and update. // Preceding packets were likely missing, and the non read position can probably be moved further now. - if (CSeqNo::seqcmp(m_iFirstNonreadPos, m_iStartPos) < 0) + if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; updateNonreadPos(); } if (!m_tsbpd.isEnabled() && m_bMessageAPI) updateFirstReadableOutOfOrder(); - return iDropCnt; + return std::make_pair(iNumDropped, iNumDiscarded); } int CRcvBuffer::dropAll() @@ -255,7 +262,8 @@ int CRcvBuffer::dropAll() return 0; const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); - return dropUpTo(end_seqno); + const std::pair numDropped = dropUpTo(end_seqno); + return numDropped.first + numDropped.second; } int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting) @@ -752,6 +760,12 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no return unreadableInfo; } +int32_t CRcvBuffer::getFirstNonreadSeqNo() const +{ + const int offset = offPos(m_iStartPos, m_iFirstNonreadPos); + return CSeqNo::incseq(m_iStartSeqNo, offset); +} + void CRcvBuffer::countBytes(int pkts, int bytes) { ScopedLock lock(m_BytesCountLock); diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index d4b50fab7..c5fca428b 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -66,8 +66,8 @@ class CRcvBuffer /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). /// @param [in] seqno drop units up to this sequence number - /// @return number of dropped packets. - int dropUpTo(int32_t seqno); + /// @return number of dropped (missing) and discarded (available) packets as a pair(dropped, discarded). + std::pair dropUpTo(int32_t seqno); /// @brief Drop all the packets in the receiver buffer. /// The starting position and seqno are shifted right after the last packet in the buffer. @@ -187,6 +187,11 @@ class CRcvBuffer PacketInfo getFirstReadablePacketInfo(time_point time_now) const; + /// @brief Get the sequence number of the first packet that can't be read + /// (either because it is missing, or because it is a part of a bigger message + /// that is not fully available yet). + int32_t getFirstNonreadSeqNo() const; + /// Get information on packets available to be read. /// @returns a pair of sequence numbers (first available; first unavailable). /// @@ -200,6 +205,20 @@ class CRcvBuffer return (m_iMaxPosOff == 0); } + /// Returns the currently used number of cells, including + /// gaps with empty cells, or in other words, the distance + /// between the initial position and the youngest received packet. + size_t size() const + { + return m_iMaxPosOff; + } + + // Returns true if the buffer is full. Requires locking. + bool full() const + { + return size() == capacity(); + } + /// Return buffer capacity. /// One slot had to be empty in order to tell the difference between "empty buffer" and "full buffer". /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continiously. @@ -239,9 +258,15 @@ class CRcvBuffer 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 { - // XXX maybe not the best implementation, but this keeps up to the rule + // 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; @@ -327,9 +352,8 @@ class CRcvBuffer EntryStatus status; }; - //static Entry emptyEntry() { return Entry { NULL, EntryState_Empty }; } - - FixedArray m_entries; + typedef FixedArray entries_t; + entries_t m_entries; const size_t m_szSize; // size of the array of units (buffer) CUnitQueue* m_pUnitQueue; // the shared unit queue diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 4edfe80c0..ef1bc498c 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -317,7 +317,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, w_packet.m_pcData = m_pCurrBlock->m_pcData; readlen = m_pCurrBlock->m_iLength; w_packet.setLength(readlen, m_iBlockLen); - w_packet.m_iSeqNo = m_pCurrBlock->m_iSeqNo; + 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. @@ -343,7 +343,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, } Block* p = m_pCurrBlock; - w_packet.m_iMsgNo = m_pCurrBlock->m_iMsgNoBitset; + w_packet.set_msgflags(m_pCurrBlock->m_iMsgNoBitset); w_srctime = m_pCurrBlock->m_tsOriginTime; m_pCurrBlock = m_pCurrBlock->m_pNext; @@ -358,7 +358,7 @@ int CSndBuffer::readData(CPacket& w_packet, steady_clock::time_point& w_srctime, HLOGC(bslog.Debug, log << CONID() << "CSndBuffer: picked up packet to send: size=" << readlen << " #" << w_packet.getMsgSeq() - << " %" << w_packet.m_iSeqNo + << " %" << w_packet.seqno() << " !" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); break; @@ -422,9 +422,10 @@ int32_t CSndBuffer::getMsgNoAt(const int offset) return p->getMsgSeq(); } -int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, int& w_msglen) +int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time_point& w_srctime, DropRange& w_drop) { - int32_t& msgno_bitset = w_packet.m_iMsgNo; + // NOTE: w_packet.m_iSeqNo is expected to be set to the value + // of the sequence number with which this packet should be sent. ScopedLock bufferguard(m_BufLock); @@ -439,19 +440,23 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time if (p == m_pLastBlock) { LOGC(qslog.Error, log << "CSndBuffer::readData: offset " << offset << " too large!"); - return 0; + return READ_NONE; } #if 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()); + // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. // If so, then inform the caller that it should first take care of the whole // message (all blocks with that message id). Shift the m_pCurrBlock pointer // to the position past the last of them. Then return -1 and set the - // msgno_bitset return reference to the message id that should be dropped as + // msgno bitset packet field to the message id that should be dropped as // a whole. // After taking care of that, the caller should immediately call this function again, @@ -463,11 +468,11 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time if ((p->m_iTTL >= 0) && (count_milliseconds(steady_clock::now() - p->m_tsOriginTime) > p->m_iTTL)) { - int32_t msgno = p->getMsgSeq(); - w_msglen = 1; - p = p->m_pNext; - bool move = false; - while (p != m_pLastBlock && msgno == p->getMsgSeq()) + 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; @@ -477,18 +482,27 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time p = p->m_pNext; if (move) m_pCurrBlock = p; - w_msglen++; + msglen++; } HLOGC(qslog.Debug, - log << "CSndBuffer::readData: due to TTL exceeded, SEQ " << first_seq << " - " << last_seq << ", " - << w_msglen << " packets to drop, msgno=" << msgno); - - // If readData returns -1, then msgno_bitset is understood as a Message ID to drop. - // This means that in this case it should be written by the message sequence value only - // (not the whole 4-byte bitset written at PH_MSGNO). - msgno_bitset = msgno; - return -1; + 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)); + return READ_DROP; } w_packet.m_pcData = p->m_pcData; @@ -501,7 +515,7 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time // encrypted, and with all ENC flags already set. So, the first call to send // the packet originally (the other overload of this function) must set these // flags. - w_packet.m_iMsgNo = p->m_iMsgNoBitset; + w_packet.set_msgflags(p->m_iMsgNoBitset); w_srctime = p->m_tsOriginTime; // This function is called when packet retransmission is triggered. @@ -509,7 +523,7 @@ int CSndBuffer::readData(const int offset, CPacket& w_packet, steady_clock::time p->m_tsRexmitTime = steady_clock::now(); HLOGC(qslog.Debug, - log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.m_iSeqNo + log << CONID() << "CSndBuffer: getting packet %" << p->m_iSeqNo << " as per %" << w_packet.seqno() << " size=" << readlen << " to send [REXMIT]"); return readlen; diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index 1b1aa6222..afd52110b 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -116,6 +116,10 @@ class CSndBuffer SRT_ATTR_EXCLUDES(m_BufLock) int addBufferFromFile(std::fstream& ifs, int len); + // Special values that can be returned by readData. + 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 @@ -130,14 +134,29 @@ class CSndBuffer SRT_ATTR_EXCLUDES(m_BufLock) time_point peekNextOriginal() const; + struct DropRange + { + static const size_t BEGIN = 0, END = 1; + 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 [out] packet the packet to read. - /// @param [out] origintime origin time stamp of the message - /// @param [out] msglen length of the message - /// @return Actual length of data read (return 0 if offset too large, -1 if TTL exceeded). + /// @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, int& w_msglen); + int readData(const int offset, CPacket& w_packet, time_point& w_origintime, DropRange& w_drop); /// 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) diff --git a/srtcore/buffer_tools.cpp b/srtcore/buffer_tools.cpp index 3f7a77be6..7473e3824 100644 --- a/srtcore/buffer_tools.cpp +++ b/srtcore/buffer_tools.cpp @@ -234,7 +234,7 @@ void CSndRateEstimator::addSample(const time_point& ts, int pkts, size_t bytes) } else { - m_iRateBps = sum.m_iBytesCount * 1000 / (iNumPeriods * SAMPLE_DURATION_MS); + m_iRateBps = (sum.m_iBytesCount + CPacket::HDR_SIZE * sum.m_iPktsCount) * 1000 / (iNumPeriods * SAMPLE_DURATION_MS); } HLOGC(bslog.Note, @@ -260,7 +260,8 @@ void CSndRateEstimator::addSample(const time_point& ts, int pkts, size_t bytes) int CSndRateEstimator::getCurrentRate() const { SRT_ASSERT(m_iCurSampleIdx >= 0 && m_iCurSampleIdx < NUM_PERIODS); - return (int) avg_iir<16, unsigned long long>(m_iRateBps, m_Samples[m_iCurSampleIdx].m_iBytesCount * 1000 / SAMPLE_DURATION_MS); + 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 diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h index aacbd8310..88479827d 100644 --- a/srtcore/buffer_tools.h +++ b/srtcore/buffer_tools.h @@ -141,10 +141,11 @@ class CSndRateEstimator /// @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 + /// 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 inluding the current sampling interval. + /// Retrieve estimated bitrate in bytes per second (with 16-byte packet header) + /// including the current sampling interval. int getCurrentRate() const; private: @@ -191,10 +192,10 @@ class CSndRateEstimator Sample m_Samples[NUM_PERIODS]; - time_point m_tsFirstSampleTime; //< Start time of the first sameple. + 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; // Input Rate in Bytes/sec + int m_iRateBps; //< Rate in Bytes/sec. }; } // namespace srt diff --git a/srtcore/cache.cpp b/srtcore/cache.cpp index 4cda9a70f..2c6f624c4 100644 --- a/srtcore/cache.cpp +++ b/srtcore/cache.cpp @@ -38,7 +38,6 @@ written by Yunhong Gu, last updated 05/05/2009 *****************************************************************************/ - #include "platform_sys.h" #include @@ -49,70 +48,70 @@ using namespace std; srt::CInfoBlock& srt::CInfoBlock::copyFrom(const CInfoBlock& obj) { - std::copy(obj.m_piIP, obj.m_piIP + 4, m_piIP); - m_iIPversion = obj.m_iIPversion; - m_ullTimeStamp = obj.m_ullTimeStamp; - m_iSRTT = obj.m_iSRTT; - m_iBandwidth = obj.m_iBandwidth; - m_iLossRate = obj.m_iLossRate; - m_iReorderDistance = obj.m_iReorderDistance; - m_dInterval = obj.m_dInterval; - m_dCWnd = obj.m_dCWnd; - - return *this; + std::copy(obj.m_piIP, obj.m_piIP + 4, m_piIP); + m_iIPversion = obj.m_iIPversion; + m_ullTimeStamp = obj.m_ullTimeStamp; + m_iSRTT = obj.m_iSRTT; + m_iBandwidth = obj.m_iBandwidth; + m_iLossRate = obj.m_iLossRate; + m_iReorderDistance = obj.m_iReorderDistance; + m_dInterval = obj.m_dInterval; + m_dCWnd = obj.m_dCWnd; + + return *this; } bool srt::CInfoBlock::operator==(const CInfoBlock& obj) const { - if (m_iIPversion != obj.m_iIPversion) - return false; + if (m_iIPversion != obj.m_iIPversion) + return false; - else if (m_iIPversion == AF_INET) - return (m_piIP[0] == obj.m_piIP[0]); + else if (m_iIPversion == AF_INET) + return (m_piIP[0] == obj.m_piIP[0]); - for (int i = 0; i < 4; ++ i) - { - if (m_piIP[i] != obj.m_piIP[i]) - return false; - } + for (int i = 0; i < 4; ++i) + { + if (m_piIP[i] != obj.m_piIP[i]) + return false; + } - return true; + return true; } srt::CInfoBlock* srt::CInfoBlock::clone() { - CInfoBlock* obj = new CInfoBlock; - - std::copy(m_piIP, m_piIP + 4, obj->m_piIP); - obj->m_iIPversion = m_iIPversion; - obj->m_ullTimeStamp = m_ullTimeStamp; - obj->m_iSRTT = m_iSRTT; - obj->m_iBandwidth = m_iBandwidth; - obj->m_iLossRate = m_iLossRate; - obj->m_iReorderDistance = m_iReorderDistance; - obj->m_dInterval = m_dInterval; - obj->m_dCWnd = m_dCWnd; - - return obj; + CInfoBlock* obj = new CInfoBlock; + + std::copy(m_piIP, m_piIP + 4, obj->m_piIP); + obj->m_iIPversion = m_iIPversion; + obj->m_ullTimeStamp = m_ullTimeStamp; + obj->m_iSRTT = m_iSRTT; + obj->m_iBandwidth = m_iBandwidth; + obj->m_iLossRate = m_iLossRate; + obj->m_iReorderDistance = m_iReorderDistance; + obj->m_dInterval = m_dInterval; + obj->m_dCWnd = m_dCWnd; + + return obj; } int srt::CInfoBlock::getKey() { - if (m_iIPversion == AF_INET) - return m_piIP[0]; + if (m_iIPversion == AF_INET) + return m_piIP[0]; - return m_piIP[0] + m_piIP[1] + m_piIP[2] + m_piIP[3]; + 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]) { - if (addr.family() == AF_INET) - { - aw_ip[0] = addr.sin.sin_addr.s_addr; - aw_ip[1] = aw_ip[2] = aw_ip[3] = 0; - } - else - { - memcpy((aw_ip), addr.sin6.sin6_addr.s6_addr, sizeof addr.sin6.sin6_addr.s6_addr); - } + if (addr.family() == AF_INET) + { + aw_ip[0] = addr.sin.sin_addr.s_addr; + aw_ip[1] = aw_ip[2] = aw_ip[3] = 0; + } + else + { + memcpy((aw_ip), addr.sin6.sin6_addr.s6_addr, sizeof addr.sin6.sin6_addr.s6_addr); + } } diff --git a/srtcore/cache.h b/srtcore/cache.h index 47633706a..d7f586c20 100644 --- a/srtcore/cache.h +++ b/srtcore/cache.h @@ -54,218 +54,215 @@ namespace srt class CCacheItem { public: - virtual ~CCacheItem() {} + virtual ~CCacheItem() {} public: - virtual CCacheItem& operator=(const CCacheItem&) = 0; + virtual CCacheItem& operator=(const CCacheItem&) = 0; - // The "==" operator SHOULD only compare key values. - virtual bool operator==(const CCacheItem&) = 0; + // The "==" operator SHOULD only compare key values. + virtual bool operator==(const CCacheItem&) = 0; - /// get a deep copy clone of the current item - /// @return Pointer to the new item, or NULL if failed. + /// get a deep copy clone of the current item + /// @return Pointer to the new item, or NULL if failed. - virtual CCacheItem* clone() = 0; + virtual CCacheItem* clone() = 0; - /// get a random key value between 0 and MAX_INT to be used for the hash in cache - /// @return A random hash key. + /// get a random key value between 0 and MAX_INT to be used for the hash in cache + /// @return A random hash key. - virtual int getKey() = 0; + virtual int getKey() = 0; - // If there is any shared resources between the cache item and its clone, - // the shared resource should be released by this function. - virtual void release() {} + // If there is any shared resources between the cache item and its clone, + // the shared resource should be released by this function. + virtual void release() {} }; -template class CCache +template +class CCache { public: - CCache(int size = 1024): - m_iMaxSize(size), - m_iHashSize(size * 3), - m_iCurrSize(0) - { - m_vHashPtr.resize(m_iHashSize); - // Exception: -> CUDTUnited ctor - srt::sync::setupMutex(m_Lock, "Cache"); - } - - ~CCache() - { - clear(); - } + CCache(int size = 1024) + : m_iMaxSize(size) + , m_iHashSize(size * 3) + , m_iCurrSize(0) + { + m_vHashPtr.resize(m_iHashSize); + // Exception: -> CUDTUnited ctor + srt::sync::setupMutex(m_Lock, "Cache"); + } + + ~CCache() { clear(); } public: - /// find the matching item in the cache. - /// @param [in,out] data storage for the retrieved item; initially it must carry the key information - /// @return 0 if found a match, otherwise -1. - - int lookup(T* data) - { - srt::sync::ScopedLock cacheguard(m_Lock); - - int key = data->getKey(); - if (key < 0) - return -1; - if (key >= m_iMaxSize) - key %= m_iHashSize; - - const ItemPtrList& item_list = m_vHashPtr[key]; - for (typename ItemPtrList::const_iterator i = item_list.begin(); i != item_list.end(); ++ i) - { - if (*data == ***i) - { - // copy the cached info - *data = ***i; - return 0; - } - } - - return -1; - } - - /// update an item in the cache, or insert one if it doesn't exist; oldest item may be removed - /// @param [in] data the new item to updated/inserted to the cache - /// @return 0 if success, otherwise -1. - - int update(T* data) - { - srt::sync::ScopedLock cacheguard(m_Lock); - - int key = data->getKey(); - if (key < 0) - return -1; - if (key >= m_iMaxSize) - key %= m_iHashSize; - - T* curr = NULL; - - ItemPtrList& item_list = m_vHashPtr[key]; - for (typename ItemPtrList::iterator i = item_list.begin(); i != item_list.end(); ++ i) - { - if (*data == ***i) - { - // update the existing entry with the new value - ***i = *data; - curr = **i; - - // remove the current entry - m_StorageList.erase(*i); - item_list.erase(i); - - // re-insert to the front - m_StorageList.push_front(curr); - item_list.push_front(m_StorageList.begin()); - - return 0; - } - } - - // create new entry and insert to front - curr = data->clone(); - m_StorageList.push_front(curr); - item_list.push_front(m_StorageList.begin()); - - ++ m_iCurrSize; - if (m_iCurrSize >= m_iMaxSize) - { - // Cache overflow, remove oldest entry. - T* last_data = m_StorageList.back(); - int last_key = last_data->getKey() % m_iHashSize; - - ItemPtrList& last_item_list = m_vHashPtr[last_key]; - for (typename ItemPtrList::iterator i = last_item_list.begin(); i != last_item_list.end(); ++ i) - { - if (*last_data == ***i) + /// find the matching item in the cache. + /// @param [in,out] data storage for the retrieved item; initially it must carry the key information + /// @return 0 if found a match, otherwise -1. + + int lookup(T* data) + { + srt::sync::ScopedLock cacheguard(m_Lock); + + int key = data->getKey(); + if (key < 0) + return -1; + if (key >= m_iMaxSize) + key %= m_iHashSize; + + const ItemPtrList& item_list = m_vHashPtr[key]; + for (typename ItemPtrList::const_iterator i = item_list.begin(); i != item_list.end(); ++i) + { + if (*data == ***i) { - last_item_list.erase(i); - break; + // copy the cached info + *data = ***i; + return 0; } - } - - last_data->release(); - delete last_data; - m_StorageList.pop_back(); - -- m_iCurrSize; - } - - return 0; - } - - /// Specify the cache size (i.e., max number of items). - /// @param [in] size max cache size. - - void setSizeLimit(int size) - { - m_iMaxSize = size; - m_iHashSize = size * 3; - m_vHashPtr.resize(m_iHashSize); - } - - /// Clear all entries in the cache, restore to initialization state. - - void clear() - { - for (typename std::list::iterator i = m_StorageList.begin(); i != m_StorageList.end(); ++ i) - { - (*i)->release(); - delete *i; - } - m_StorageList.clear(); - for (typename std::vector::iterator i = m_vHashPtr.begin(); i != m_vHashPtr.end(); ++ i) - i->clear(); - m_iCurrSize = 0; - } + } + + return -1; + } + + /// update an item in the cache, or insert one if it doesn't exist; oldest item may be removed + /// @param [in] data the new item to updated/inserted to the cache + /// @return 0 if success, otherwise -1. + + int update(T* data) + { + srt::sync::ScopedLock cacheguard(m_Lock); + + int key = data->getKey(); + if (key < 0) + return -1; + if (key >= m_iMaxSize) + key %= m_iHashSize; + + T* curr = NULL; + + ItemPtrList& item_list = m_vHashPtr[key]; + for (typename ItemPtrList::iterator i = item_list.begin(); i != item_list.end(); ++i) + { + if (*data == ***i) + { + // update the existing entry with the new value + ***i = *data; + curr = **i; + + // remove the current entry + m_StorageList.erase(*i); + item_list.erase(i); + + // re-insert to the front + m_StorageList.push_front(curr); + item_list.push_front(m_StorageList.begin()); + + return 0; + } + } + + // create new entry and insert to front + curr = data->clone(); + m_StorageList.push_front(curr); + item_list.push_front(m_StorageList.begin()); + + ++m_iCurrSize; + if (m_iCurrSize >= m_iMaxSize) + { + // Cache overflow, remove oldest entry. + T* last_data = m_StorageList.back(); + int last_key = last_data->getKey() % m_iHashSize; + + ItemPtrList& last_item_list = m_vHashPtr[last_key]; + for (typename ItemPtrList::iterator i = last_item_list.begin(); i != last_item_list.end(); ++i) + { + if (*last_data == ***i) + { + last_item_list.erase(i); + break; + } + } + + last_data->release(); + delete last_data; + m_StorageList.pop_back(); + --m_iCurrSize; + } + + return 0; + } private: - std::list m_StorageList; - typedef typename std::list::iterator ItemPtr; - typedef std::list ItemPtrList; - std::vector m_vHashPtr; + /// Specify the cache size (i.e., max number of items). + /// Private or else must be protected by a lock. + /// @param [in] size max cache size. + void setSizeLimit(int size) + { + m_iMaxSize = size; + m_iHashSize = size * 3; + m_vHashPtr.resize(m_iHashSize); + } + + /// Clear all entries in the cache, restore to initialization state. + /// Private or else must be protected by a lock. + void clear() + { + for (typename std::list::iterator i = m_StorageList.begin(); i != m_StorageList.end(); ++i) + { + (*i)->release(); + delete *i; + } + m_StorageList.clear(); + for (typename std::vector::iterator i = m_vHashPtr.begin(); i != m_vHashPtr.end(); ++i) + i->clear(); + m_iCurrSize = 0; + } - int m_iMaxSize; - int m_iHashSize; - int m_iCurrSize; +private: + std::list m_StorageList; + typedef typename std::list::iterator ItemPtr; + typedef std::list ItemPtrList; + std::vector m_vHashPtr; + + int m_iMaxSize; + int m_iHashSize; + int m_iCurrSize; - srt::sync::Mutex m_Lock; + srt::sync::Mutex m_Lock; private: - CCache(const CCache&); - CCache& operator=(const CCache&); + CCache(const CCache&); + CCache& operator=(const CCache&); }; - class CInfoBlock { public: - uint32_t m_piIP[4]; // IP address, machine read only, not human readable format. - int m_iIPversion; // Address family: AF_INET or AF_INET6. - uint64_t m_ullTimeStamp; // Last update time. - int m_iSRTT; // Smoothed RTT. - int m_iBandwidth; // Estimated link bandwidth. - int m_iLossRate; // Average loss rate. - int m_iReorderDistance; // Packet reordering distance. - double m_dInterval; // Inter-packet time (Congestion Control). - double m_dCWnd; // Congestion window size (Congestion Control). + uint32_t m_piIP[4]; // IP address, machine read only, not human readable format. + int m_iIPversion; // Address family: AF_INET or AF_INET6. + uint64_t m_ullTimeStamp; // Last update time. + int m_iSRTT; // Smoothed RTT. + int m_iBandwidth; // Estimated link bandwidth. + int m_iLossRate; // Average loss rate. + int m_iReorderDistance; // Packet reordering distance. + double m_dInterval; // Inter-packet time (Congestion Control). + double m_dCWnd; // Congestion window size (Congestion Control). public: - CInfoBlock() {} // NOTE: leaves uninitialized - CInfoBlock& copyFrom(const CInfoBlock& obj); - CInfoBlock(const CInfoBlock& src) { copyFrom(src); } - CInfoBlock& operator=(const CInfoBlock& src) { return copyFrom(src); } - bool operator==(const CInfoBlock& obj) const; - CInfoBlock* clone(); - int getKey(); - void release() {} + CInfoBlock() {} // NOTE: leaves uninitialized + CInfoBlock& copyFrom(const CInfoBlock& obj); + CInfoBlock(const CInfoBlock& src) { copyFrom(src); } + CInfoBlock& operator=(const CInfoBlock& src) { return copyFrom(src); } + bool operator==(const CInfoBlock& obj) const; + CInfoBlock* clone(); + int getKey(); + void release() {} public: + /// convert sockaddr structure to an integer array + /// @param [in] addr network address + /// @param [in] ver IP version + /// @param [out] ip the result machine readable IP address in integer array - /// convert sockaddr structure to an integer array - /// @param [in] addr network address - /// @param [in] ver IP version - /// @param [out] ip the result machine readable IP address in integer array - - static void convert(const sockaddr_any& addr, uint32_t ip[4]); + static void convert(const sockaddr_any& addr, uint32_t ip[4]); }; } // namespace srt diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 0b448d681..0a4e1e318 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -51,7 +51,6 @@ modified by *****************************************************************************/ #include "platform_sys.h" - #include #include // Logging #include @@ -143,14 +142,6 @@ srt::CChannel::CChannel() , m_bBindMasked(true) #endif { -#ifdef _WIN32 - SecureZeroMemory((PVOID)&m_SendOverlapped, sizeof(WSAOVERLAPPED)); - m_SendOverlapped.hEvent = WSACreateEvent(); - if (m_SendOverlapped.hEvent == NULL) { - LOGC(kmlog.Error, log << CONID() << "IPE: WSACreateEvent failed with error: " << NET_ERROR); - throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); - } -#endif #ifdef SRT_ENABLE_PKTINFO // Do the check for ancillary data buffer size, kinda assertion static const size_t CMSG_MAX_SPACE = sizeof (CMSGNodeIPv4) + sizeof (CMSGNodeIPv6); @@ -166,12 +157,7 @@ srt::CChannel::CChannel() #endif } -srt::CChannel::~CChannel() -{ -#ifdef _WIN32 - WSACloseEvent(m_SendOverlapped.hEvent); -#endif -} +srt::CChannel::~CChannel() {} void srt::CChannel::createSocket(int family) { @@ -189,6 +175,7 @@ void srt::CChannel::createSocket(int family) 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 @@ -197,17 +184,18 @@ void srt::CChannel::createSocket(int family) throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); #if ENABLE_SOCK_CLOEXEC -#ifdef _WIN32 - // XXX ::SetHandleInformation(hInputWrite, HANDLE_FLAG_INHERIT, 0) -#else + if (cloexec_flag) { +#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 #endif // ENABLE_SOCK_CLOEXEC if ((m_mcfg.iIpV6Only != -1) && (family == AF_INET6)) // (not an error if it fails) @@ -683,8 +671,8 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka #endif LOGC(kslog.Debug, - log << "CChannel::sendto: SENDING NOW DST=" << addr.str() << " target=@" << packet.m_iID - << " size=" << packet.getLength() << " pkt.ts=" << packet.m_iTimeStamp + log << "CChannel::sendto: SENDING NOW DST=" << addr.str() << " target=@" << packet.id() + << " size=" << packet.getLength() << " pkt.ts=" << packet.timestamp() << dsrc.str() << " " << packet.Info()); #endif @@ -747,7 +735,7 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka #endif // convert control information into network order - packet.toNL(); + packet.toNetworkByteOrder(); #ifndef _WIN32 msghdr mh; @@ -786,38 +774,65 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka const int res = (int)::sendmsg(m_iSocket, &mh, 0); #else - DWORD size = (DWORD)(CPacket::HDR_SIZE + packet.getLength()); - int addrsize = addr.size(); + class WSAEventRef + { + public: + WSAEventRef() + : e(::WSACreateEvent()) + { + } + ~WSAEventRef() + { + ::WSACloseEvent(e); + e = NULL; + } + void reset() + { + ::WSAResetEvent(e); + } + WSAEVENT Handle() + { + return e; + } - int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, &m_SendOverlapped, NULL); + private: + WSAEVENT e; + }; +#if !defined(__MINGW32__) && defined(ENABLE_CXX11) + thread_local WSAEventRef lEvent; +#else + WSAEventRef lEvent; +#endif + WSAOVERLAPPED overlapped; + ::SecureZeroMemory(&overlapped, sizeof(overlapped)); + overlapped.hEvent = lEvent.Handle(); + + 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); if (res == SOCKET_ERROR) { if (NET_ERROR == WSA_IO_PENDING) { - res = WSAWaitForMultipleEvents(1, &m_SendOverlapped.hEvent, TRUE, 100 /*ms*/, FALSE); - if (res == WAIT_FAILED) - { - LOGC(kslog.Warn, log << "CChannel::WSAWaitForMultipleEvents: failed with " << NET_ERROR); - res = -1; - } + DWORD dwFlags = 0; + const bool bCompleted = WSAGetOverlappedResult(m_iSocket, &overlapped, &size, TRUE, &dwFlags); + if (bCompleted) + res = 0; else - { - DWORD dwFlags = 0; - const bool bCompleted = WSAGetOverlappedResult(m_iSocket, &m_SendOverlapped, &size, false, &dwFlags); - res = bCompleted ? 0 : -1; - } + LOGC(kslog.Warn, log << "CChannel::sendto call on ::WSAGetOverlappedResult failed with error: " << NET_ERROR); + lEvent.reset(); } else { LOGC(kmlog.Error, log << CONID() << "WSASendTo failed with error: " << NET_ERROR); } } - WSAResetEvent(m_SendOverlapped.hEvent); + res = (0 == res) ? size : -1; #endif - packet.toHL(); + packet.toHostByteOrder(); return res; } @@ -1066,25 +1081,7 @@ srt::EReadStatus srt::CChannel::recvfrom(sockaddr_any& w_addr, CPacket& w_packet } w_packet.setLength(recv_size - CPacket::HDR_SIZE); - - // convert back into local host order - // XXX use NtoHLA(). - // for (int i = 0; i < 4; ++ i) - // w_packet.m_nHeader[i] = ntohl(w_packet.m_nHeader[i]); - { - uint32_t* p = w_packet.m_nHeader; - for (size_t i = 0; i < SRT_PH_E_SIZE; ++i) - { - *p = ntohl(*p); - ++p; - } - } - - if (w_packet.isControl()) - { - for (size_t j = 0, n = w_packet.getLength() / sizeof(uint32_t); j < n; ++j) - *((uint32_t*)w_packet.m_pcData + j) = ntohl(*((uint32_t*)w_packet.m_pcData + j)); - } + w_packet.toHostByteOrder(); return RST_OK; diff --git a/srtcore/channel.h b/srtcore/channel.h index 6df7ec0ce..e12310001 100644 --- a/srtcore/channel.h +++ b/srtcore/channel.h @@ -49,13 +49,6 @@ written by modified by Haivision Systems Inc. *****************************************************************************/ -#if HAVE_CXX11 -#define SRT_ATR_ALIGNAS(n) alignas(n) -#elif HAVE_GCC -#define SRT_ATR_ALIGNAS(n) __attribute__((aligned(n))) -#else -#define SRT_ATR_ALIGNAS(n) -#endif #ifndef INC_SRT_CHANNEL_H #define INC_SRT_CHANNEL_H @@ -176,9 +169,6 @@ class CChannel private: UDPSOCKET m_iSocket; // socket descriptor -#ifdef _WIN32 - mutable WSAOVERLAPPED m_SendOverlapped; -#endif // Mutable because when querying original settings // this comprises the cache for extracted values, diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 7f7132b77..d3fd4f22c 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -68,8 +68,6 @@ modified by #include "packet.h" #include "threadname.h" -#include // SysStrError - using namespace std; using namespace srt::sync; using namespace srt_logging; diff --git a/srtcore/core.cpp b/srtcore/core.cpp index c5ab431a4..c31d2a6fc 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -119,6 +119,7 @@ const int UDT::ERROR = srt::CUDT::ERROR; 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] = { SRTO_SNDSYN, SRTO_RCVSYN, @@ -127,11 +128,14 @@ extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = { SRTO_RCVTIMEO, SRTO_MAXBW, SRTO_INPUTBW, - SRTO_MININPUTBW, SRTO_OHEADBW, SRTO_SNDDROPDELAY, SRTO_DRIFTTRACER, + SRTO_MININPUTBW, SRTO_LOSSMAXTTL +#ifdef ENABLE_MAXREXMITBW + ,SRTO_MAXREXMITBW +#endif }; const int32_t @@ -294,7 +298,7 @@ void srt::CUDT::construct() m_iPeerTsbPdDelay_ms = 0; m_bPeerTsbPd = false; m_bTsbPd = false; - m_bTsbPdAckWakeup = false; + m_bTsbPdNeedsWakeup = false; m_bGroupTsbPd = false; m_bPeerTLPktDrop = false; m_bBufferWasFull = false; @@ -559,6 +563,15 @@ void srt::CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen) optlen = sizeof(int32_t); break; +#ifdef ENABLE_MAXREXMITBW + case SRTO_MAXREXMITBW: + if (size_t(optlen) < sizeof(m_config.llMaxRexmitBW)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + *(int64_t*)optval = m_config.llMaxRexmitBW; + optlen = sizeof(int64_t); + break; +#endif + case SRTO_STATE: *(int32_t *)optval = uglobal().getStatus(m_SocketID); optlen = sizeof(int32_t); @@ -916,6 +929,7 @@ void srt::CUDT::clearData() m_stats.tsLastSampleTime = steady_clock::now(); m_stats.traceReorderDistance = 0; + m_stats.traceBelatedTime = 0; m_stats.sndDuration = m_stats.m_sndDurationTotal = 0; } @@ -1982,7 +1996,7 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData; size_t len = ctrlpkt->getLength(); int etype = ctrlpkt->getExtendedType(); - uint32_t ts = ctrlpkt->m_iTimeStamp; + uint32_t ts = ctrlpkt->timestamp(); int res = SRT_CMD_NONE; @@ -2007,7 +2021,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, + res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, (srtdata_out), (len_out)); if (res == SRT_CMD_KMRSP) { @@ -2044,7 +2058,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, CUDT::HS_VERSION_UDT4); + m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion); return true; // nothing to do } @@ -2088,9 +2102,9 @@ int srt::CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint return SRT_CMD_NONE; } - LOGC(cnlog.Note, log << "HSREQ/rcv: cmd=" << SRT_CMD_HSREQ << "(HSREQ) len=" << bytelen - << hex << " vers=0x" << srtdata[SRT_HS_VERSION] << " opts=0x" << srtdata[SRT_HS_FLAGS] - << dec << " delay=" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY])); + 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])); m_uPeerSrtVersion = srtdata[SRT_HS_VERSION]; m_uPeerSrtFlags = srtdata[SRT_HS_FLAGS]; @@ -2513,7 +2527,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return false; // don't interpret } - int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1); + int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.timestamp(), HS_VERSION_SRT1); // Interpreted? Then it should be responded with SRT_CMD_HSRSP. if (rescmd != SRT_CMD_HSRSP) { @@ -2540,7 +2554,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return false; // don't interpret } - int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1); + int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.timestamp(), HS_VERSION_SRT1); // Interpreted? Then it should be responded with SRT_CMD_NONE. // (nothing to be responded for HSRSP, unless there was some kinda problem) if (rescmd != SRT_CMD_NONE) @@ -2633,7 +2647,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return false; } - int res = m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, + int res = m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1, m_uPeerSrtVersion, (out_data), (*pw_len)); if (res != SRT_CMD_KMRSP) { @@ -2680,7 +2694,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } else if (cmd == SRT_CMD_KMRSP) { - int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, HS_VERSION_SRT1); + int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion); if (m_config.bEnforcedEnc && res == -1) { if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADSECRET) @@ -3573,7 +3587,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // control of methods.) // ID = 0, connection request - reqpkt.m_iID = 0; + reqpkt.set_id(0); size_t hs_size = m_iMaxSRTPayloadSize; m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); @@ -3588,7 +3602,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) setPacketTS(reqpkt, tnow); HLOGC(cnlog.Debug, - log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (TimeStamp: " << reqpkt.m_iTimeStamp + log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (TimeStamp: " << reqpkt.timestamp() << "). SENDING HS: " << m_ConnReq.show()); /* @@ -3660,7 +3674,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) << " > 250 ms). size=" << reqpkt.getLength()); if (m_config.bRendezvous) - reqpkt.m_iID = m_ConnRes.m_iID; + reqpkt.set_id(m_ConnRes.m_iID); #if ENABLE_HEAVY_LOGGING { @@ -3898,17 +3912,17 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, // This should have got the original value returned from // processConnectResponse through processAsyncConnectResponse. - CPacket request; - request.setControl(UMSG_HANDSHAKE); - request.allocate(m_iMaxSRTPayloadSize); + CPacket reqpkt; + reqpkt.setControl(UMSG_HANDSHAKE); + reqpkt.allocate(m_iMaxSRTPayloadSize); const steady_clock::time_point now = steady_clock::now(); - setPacketTS(request, 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 - request.m_iID = !m_config.bRendezvous ? 0 : m_ConnRes.m_iID; + reqpkt.set_id(!m_config.bRendezvous ? 0 : m_ConnRes.m_iID); bool status = true; @@ -3919,7 +3933,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, if (cst == CONN_RENDEZVOUS) { HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: passing to processRendezvous"); - cst = processRendezvous(pResponse, serv_addr, rst, (request)); + cst = processRendezvous(pResponse, serv_addr, rst, (reqpkt)); if (cst == CONN_ACCEPT) { HLOGC(cnlog.Debug, @@ -3939,7 +3953,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, if (m_RejectReason == SRT_REJ_UNKNOWN) m_RejectReason = SRT_REJ_ROGUE; - sendRendezvousRejection(serv_addr, (request)); + sendRendezvousRejection(serv_addr, (reqpkt)); status = false; } } @@ -3956,8 +3970,8 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, { // (this procedure will be also run for HSv4 rendezvous) HLOGC(cnlog.Debug, - log << CONID() << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength()); - if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (request), (m_ConnReq))) + log << CONID() << "processAsyncConnectRequest: serializing HS: buffer size=" << reqpkt.getLength()); + if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (reqpkt), (m_ConnReq))) { // All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired". LOGC(cnlog.Error, @@ -3969,7 +3983,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) - << " to socket " << request.m_iID << " size=" << request.getLength()); + << " to socket " << reqpkt.id() << " size=" << reqpkt.getLength()); } } @@ -3979,16 +3993,16 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, /* XXX Shouldn't it send a single response packet for the rejection? // Set the version to 0 as "handshake rejection" status and serialize it CHandShake zhs; - size_t size = request.getLength(); - zhs.store_to((request.m_pcData), (size)); - request.setLength(size); + size_t size = reqpkt.getLength(); + zhs.store_to((reqpkt.m_pcData), (size)); + reqpkt.setLength(size); */ } HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectRequest: setting REQ-TIME HIGH, SENDING HS:" << m_ConnReq.show()); m_tsLastReqTime = steady_clock::now(); - m_pSndQueue->sendto(serv_addr, request, m_SourceAddr); + m_pSndQueue->sendto(serv_addr, reqpkt, m_SourceAddr); return status; } @@ -4956,8 +4970,9 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, } */ - - LOGC(cnlog.Note, log << CONID() << "Connection established to: " << m_PeerAddr.str()); + + LOGC(cnlog.Note, log << CONID() << "Connection established from (" + << m_SourceAddr.str() << ") to peer @" << m_PeerID << " (" << m_PeerAddr.str() << ")"); return CONN_ACCEPT; } @@ -5392,7 +5407,7 @@ void * srt::CUDT::tsbpd(void* param) CUniqueSync recvdata_lcc (self->m_RecvLock, self->m_RecvDataCond); CSync tsbpd_cc(self->m_RcvTsbPdCond, recvdata_lcc.locker()); - self->m_bTsbPdAckWakeup = true; + self->m_bTsbPdNeedsWakeup = true; while (!self->m_bClosing) { steady_clock::time_point tsNextDelivery; // Next packet delivery time @@ -5412,6 +5427,21 @@ void * srt::CUDT::tsbpd(void* param) const bool is_time_to_deliver = !is_zero(info.tsbpd_time) && (tnow >= info.tsbpd_time); tsNextDelivery = info.tsbpd_time; +#if ENABLE_HEAVY_LOGGING + if (info.seqno == SRT_SEQNO_NONE) + { + HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: NO PACKETS"); + } + else + { + HLOGC(tslog.Debug, log << self->CONID() << "sok/tsbpd: packet check: %" + << info.seqno << " T=" << FormatTime(tsNextDelivery) + << " diff-now-playtime=" << FormatDuration(tnow - tsNextDelivery) + << " ready=" << is_time_to_deliver + << " ondrop=" << info.seq_gap); + } +#endif + if (!self->m_bTLPktDrop) { rxready = !info.seq_gap && is_time_to_deliver; @@ -5457,8 +5487,8 @@ void * srt::CUDT::tsbpd(void* param) if (rxready) { HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " - << (count_milliseconds(steady_clock::now() - info.tsbpd_time)) << "ms)"); + log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << info.seqno << " (belated " + << FormatDuration(steady_clock::now() - info.tsbpd_time) << ")"); /* * There are packets ready to be delivered * signal a waiting "recv" call if there is any data available @@ -5521,6 +5551,8 @@ void * srt::CUDT::tsbpd(void* param) if (self->m_bClosing) break; + SRT_ATR_UNUSED bool bWokeUpOnSignal = true; + if (!is_zero(tsNextDelivery)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); @@ -5528,12 +5560,12 @@ void * srt::CUDT::tsbpd(void* param) * Buffer at head of queue is not ready to play. * Schedule wakeup when it will be. */ - self->m_bTsbPdAckWakeup = false; + self->m_bTsbPdNeedsWakeup = false; HLOGC(tslog.Debug, - log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno - << " T=" << FormatTime(tsNextDelivery) << " - waiting " << count_milliseconds(timediff) << "ms"); + log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << info.seqno + << " T=" << FormatTime(tsNextDelivery) << " - waiting " << FormatDuration(timediff)); THREAD_PAUSED(); - tsbpd_cc.wait_until(tsNextDelivery); + bWokeUpOnSignal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); } else @@ -5550,20 +5582,22 @@ void * srt::CUDT::tsbpd(void* param) * - Closing the connection */ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"); - self->m_bTsbPdAckWakeup = true; + self->m_bTsbPdNeedsWakeup = true; THREAD_PAUSED(); tsbpd_cc.wait(); THREAD_RESUMED(); } - HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!"); + 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) +int srt::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) @@ -5571,16 +5605,22 @@ int srt::CUDT::rcvDropTooLateUpTo(int seqno) dropFromLossLists(SRT_SEQNO_NONE, CSeqNo::decseq(seqno)); - const int iDropCnt = m_pRcvBuffer->dropUpTo(seqno); - if (iDropCnt > 0) + const std::pair iDropDiscardedPkts = m_pRcvBuffer->dropUpTo(seqno); + const int iDropCnt = iDropDiscardedPkts.first; + const int iDiscardedCnt = iDropDiscardedPkts.second; + const int iDropCntTotal = iDropCnt + iDiscardedCnt; + + // In case of DROP_TOO_LATE discarded packets should also be counted because they are not read from another member socket. + const int iDropStatCnt = (reason == DROP_DISCARD) ? iDropCnt : iDropCntTotal; + if (iDropStatCnt > 0) { enterCS(m_StatsLock); // Estimate dropped bytes from average payload size. const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize(); - m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropStatCnt * avgpayloadsz, (uint32_t)iDropStatCnt)); leaveCS(m_StatsLock); } - return iDropCnt; + return iDropCntTotal; } void srt::CUDT::setInitialRcvSeq(int32_t isn) @@ -5650,6 +5690,14 @@ bool srt::CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd return true; } +int srt::CUDT::getAuthTagSize() const +{ + if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + return HAICRYPT_AUTHTAG_MAX; + + return 0; +} + bool srt::CUDT::prepareBuffers(CUDTException* eout) { if (m_pSndBuffer) @@ -5661,7 +5709,7 @@ bool srt::CUDT::prepareBuffers(CUDTException* eout) try { // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. - const int authtag = (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) ? HAICRYPT_AUTHTAG_MAX : 0; + const int authtag = getAuthTagSize(); SRT_ASSERT(m_iMaxSRTPayloadSize != 0); @@ -5709,14 +5757,6 @@ void srt::CUDT::rewriteHandshakeData(const sockaddr_any& peer, CHandShake& w_hs) void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs) { HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: setting up data according to handshake"); -#if ENABLE_BONDING - // Keep the group alive for the lifetime of this function, - // and do it BEFORE acquiring m_ConnectionLock to avoid - // lock inversion. - // This will check if a socket belongs to a group and if so - // it will remember this group and keep it alive here. - CUDTUnited::GroupKeeper group_keeper(uglobal(), m_parent); -#endif ScopedLock cg(m_ConnectionLock); @@ -5804,6 +5844,18 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } +#if 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, + // and do it BEFORE acquiring m_ConnectionLock to avoid + // lock inversion. + // This will check if a socket belongs to a group and if so + // it will remember this group and keep it alive here. + CUDTUnited::GroupKeeper group_keeper(uglobal(), m_parent); + m_ConnectionLock.lock(); +#endif + if (!prepareBuffers(NULL)) { HLOGC(cnlog.Debug, @@ -5870,15 +5922,15 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& size_t size = m_iMaxSRTPayloadSize; // Allocate the maximum possible memory for an SRT payload. // This is a maximum you can send once. - CPacket response; - response.setControl(UMSG_HANDSHAKE); - response.allocate(size); + CPacket rsppkt; + rsppkt.setControl(UMSG_HANDSHAKE); + rsppkt.allocate(size); // 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); - if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (response), (w_hs))) + 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); @@ -5892,10 +5944,10 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // To make sure what REALLY is being sent, parse back the handshake // data that have been just written into the buffer. CHandShake debughs; - debughs.load_from(response.m_pcData, response.getLength()); + debughs.load_from(rsppkt.m_pcData, rsppkt.getLength()); HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: sending HS from agent @" - << debughs.m_iID << " to peer @" << response.m_iID + << debughs.m_iID << " to peer @" << rsppkt.id() << "HS:" << debughs.show() << " sourceIP=" << m_SourceAddr.str()); } @@ -5906,7 +5958,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // When missed this message, the caller should not accept packets // coming as connected, but continue repeated handshake until finally // received the listener's handshake. - addressAndSend((response)); + addressAndSend((rsppkt)); } bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::string& w_why) @@ -5916,7 +5968,7 @@ bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::st #endif bool is_suppressed = IsSet(m_LogSlowDownExpired, BIT(logid)); - bool isnow = (m_tsLogSlowDown.load() + milliseconds_from(SRT_LOG_SLOWDOWN_FREQ_MS)) <= tnow; + const bool isnow = (m_tsLogSlowDown[logid].load() + milliseconds_from(SRT_LOG_SLOWDOWN_FREQ_MS)) <= tnow; if (isnow) { // Theoretically this should prevent other calls of this function to take @@ -5928,11 +5980,11 @@ bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::st // Note: it may happen that two threads could intermix one another between // the check and setting up, but this will at worst case set the slightly // later time again. - m_tsLogSlowDown.store(tnow); + m_tsLogSlowDown[logid].store(tnow); is_suppressed = false; - int supr = m_aSuppressedMsg[logid]; + const int supr = m_aSuppressedMsg[logid]; if (supr > 0) w_why = Sprint("++SUPPRESSED: ", supr); @@ -5940,7 +5992,7 @@ bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::st } else { - w_why = Sprint("Too early - last one was ", FormatDuration(tnow - m_tsLogSlowDown.load())); + w_why = Sprint("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]; @@ -5974,13 +6026,15 @@ bool srt::CUDT::createCrypter(HandshakeSide side, bool bidirectional) // they have outdated values. m_pCryptoControl->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); } - return m_pCryptoControl->init(side, m_config, bidirectional); + return m_pCryptoControl->init(side, m_config, bidirectional, useGcm153); } SRT_REJECT_REASON srt::CUDT::setupCC() @@ -6152,7 +6206,7 @@ void srt::CUDT::checkSndKMRefresh() void srt::CUDT::addressAndSend(CPacket& w_pkt) { - w_pkt.m_iID = m_PeerID; + w_pkt.set_id(m_PeerID); setPacketTS(w_pkt, steady_clock::now()); // NOTE: w_pkt isn't modified in this call, @@ -6924,6 +6978,12 @@ bool srt::CUDT::isRcvBufferReadyNoLock() const return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } +bool srt::CUDT::isRcvBufferFull() const +{ + ScopedLock lck(m_RcvBufferLock); + return m_pRcvBuffer->full(); +} + // int by_exception: accepts values of CUDTUnited::ErrorHandling: // - 0 - by return value // - 1 - by exception @@ -7067,7 +7127,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ do { - if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady(steady_clock::now())) + if (stillConnected() && !timeout && !isRcvBufferReady()) { /* Kick TsbPd thread to schedule next wakeup (if running) */ if (m_bTsbPd) @@ -7708,11 +7768,11 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) // - m_dCWndSize m_tdSendInterval = microseconds_from((int64_t)m_CongCtl->pktSndPeriod_us()); const double cgwindow = m_CongCtl->cgWindowSize(); - m_iCongestionWindow = cgwindow; + m_iCongestionWindow = (int) cgwindow; #if ENABLE_HEAVY_LOGGING HLOGC(rslog.Debug, - log << CONID() << "updateCC: updated values from congctl: interval=" << count_microseconds(m_tdSendInterval) << " us (" - << "tk (" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" + log << CONID() << "updateCC: updated values from congctl: interval=" << FormatDuration(m_tdSendInterval) + << " (cfg:" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << cgwindow); #endif } @@ -7791,40 +7851,6 @@ void srt::CUDT::releaseSynch() leaveCS(m_RecvLock); } - -#if ENABLE_BONDING -void srt::CUDT::dropToGroupRecvBase() -{ - int32_t group_recv_base = SRT_SEQNO_NONE; - if (m_parent->m_GroupOf) - { - // Check is first done before locking to avoid unnecessary - // mutex locking. The condition for this field is that it - // can be either never set, already reset, or ever set - // and possibly dangling. The re-check after lock eliminates - // the dangling case. - ScopedLock glock (uglobal().m_GlobControlLock); - - // Note that getRcvBaseSeqNo() will lock m_GroupOf->m_GroupLock, - // but this is an intended order. - if (m_parent->m_GroupOf) - group_recv_base = m_parent->m_GroupOf->getRcvBaseSeqNo(); - } - if (group_recv_base == SRT_SEQNO_NONE) - return; - - ScopedLock lck(m_RcvBufferLock); - int cnt = rcvDropTooLateUpTo(CSeqNo::incseq(group_recv_base)); - if (cnt > 0) - { - HLOGC(grlog.Debug, - log << CONID() << "dropToGroupRecvBase: dropped " << cnt << " packets before ACK: group_recv_base=" - << group_recv_base << " m_iRcvLastAck=" << m_iRcvLastAck - << " m_iRcvCurrSeqNo=" << m_iRcvCurrSeqNo << " m_bTsbPd=" << m_bTsbPd); - } -} -#endif - namespace srt { #if ENABLE_HEAVY_LOGGING static void DebugAck(string hdr, int prev, int ack) @@ -7875,7 +7901,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement ctrlpkt.pack(pkttype, lparam); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; @@ -7890,7 +7916,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp size_t bytes = sizeof(*lossdata) * size; ctrlpkt.pack(pkttype, NULL, lossdata, bytes); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); enterCS(m_StatsLock); @@ -7911,7 +7937,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp if (0 < losslen) { ctrlpkt.pack(pkttype, NULL, data, losslen * 4); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); enterCS(m_StatsLock); @@ -7941,7 +7967,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp case UMSG_CGWARNING: // 100 - Congestion Warning ctrlpkt.pack(pkttype); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); m_tsLastWarningTime = steady_clock::now(); @@ -7950,14 +7976,14 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp case UMSG_KEEPALIVE: // 001 - Keep-alive ctrlpkt.pack(pkttype); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_HANDSHAKE: // 000 - Handshake ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake)); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; @@ -7966,21 +7992,21 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp if (m_PeerID == 0) // Dont't send SHUTDOWN if we don't know peer ID. break; ctrlpkt.pack(pkttype); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_DROPREQ: // 111 - Msg drop request ctrlpkt.pack(pkttype, lparam, rparam, 8); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error ctrlpkt.pack(pkttype, lparam); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; @@ -8000,6 +8026,27 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp // [[using locked(m_RcvBufferLock)]] bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) { + if (m_config.bTSBPD || !m_config.bMessageAPI) + { + // The getFirstNonreadSeqNo() function retuens 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. + w_seq = m_pRcvBuffer->getFirstNonreadSeqNo(); + + const int32_t iNextSeqNo = CSeqNo::incseq(m_iRcvCurrSeqNo); + SRT_ASSERT(CSeqNo::seqcmp(w_seq, iNextSeqNo) <= 0); + w_log_reason = iNextSeqNo != w_seq ? "first lost" : "expected next"; + + 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); + w_seq = iNextSeqNo; + } + + return true; + } + { ScopedLock losslock (m_RcvLossLock); const int32_t seq = m_pRcvLossList->getFirstLostSeq(); @@ -8023,7 +8070,6 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { - SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); int nbsent = 0; int local_prevack = 0; #if ENABLE_HEAVY_LOGGING @@ -8041,10 +8087,6 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) #endif string reason; // just for "a reason" of giving particular % for ACK -#if ENABLE_BONDING - dropToGroupRecvBase(); -#endif - // The TSBPD thread may change the first lost sequence record (TLPKTDROP). // To avoid it the m_RcvBufferLock has to be acquired. UniqueLock bufflock(m_RcvBufferLock); @@ -8052,6 +8094,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // since the last full ACK. It should unblock the sender to proceed further. const bool bNeedFullAck = (m_bBufferWasFull && getAvailRcvBufferSizeNoLock() > 0); int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). + if (!getFirstNoncontSequence((ack), (reason))) return nbsent; @@ -8067,7 +8110,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { bufflock.unlock(); ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); - ctrlpkt.m_iID = m_PeerID; + ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); DebugAck(CONID() + "sendCtrl(lite): ", local_prevack, ack); return nbsent; @@ -8152,7 +8195,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) /* Newly acknowledged data, signal TsbPD thread */ CUniqueSync tslcc (m_RecvLock, m_RcvTsbPdCond); // m_bTsbPdAckWakeup is protected by m_RecvLock in the tsbpd() thread - if (m_bTsbPdAckWakeup) + if (m_bTsbPdNeedsWakeup) tslcc.notify_one(); } else @@ -8215,7 +8258,8 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) else if (!bNeedFullAck) { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) - LOGC(xtlog.Error, log << CONID() << "sendCtrl(UMSG_ACK): IPE: curr %" << ack << " <% last %" << m_iRcvLastAck); + LOGC(xtlog.Error, log << CONID() << "sendCtrl(UMSG_ACK): IPE: curr(" << reason << ") %" + << ack << " <% last %" << m_iRcvLastAck); return nbsent; } @@ -8271,7 +8315,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); } - ctrlpkt.m_iID = m_PeerID; + 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); @@ -8647,10 +8691,21 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr { if (ctrlpkt.getAckSeqNo() > (m_iAckSeqNo - static_cast(ACK_WND_SIZE)) && ctrlpkt.getAckSeqNo() <= m_iAckSeqNo) { - LOGC(inlog.Note, - log << CONID() << "ACKACK out of order, skipping RTT calculation " - << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo - << ", RTT (EWMA): " << m_iSRTT << ")"); + string why; + if (frequentLogAllowed(FREQLOGFA_ACKACK_OUTOFORDER, tsArrival, (why))) + { + LOGC(inlog.Note, + log << CONID() << "ACKACK out of order, skipping RTT calculation " + << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo + << ", RTT (EWMA): " << m_iSRTT << ")." << why); + } +#if SRT_ENABLE_FREQUENT_LOG_TRACE + else + { + LOGC(qrlog.Note, log << "SUPPRESSED: ACKACK out of order LOG: " << why); + } +#endif + return; } @@ -8706,16 +8761,15 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr // srt_recvfile (which doesn't make any sense), you'll have a deadlock. if (m_config.bDriftTracer) { - const bool drift_updated SRT_ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); +#if ENABLE_BONDING + ScopedLock 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) - { - ScopedLock glock(uglobal().m_GlobControlLock); - if (m_parent->m_GroupOf) - { - m_parent->m_GroupOf->synchronizeDrift(this); - } - } + m_parent->m_GroupOf->synchronizeDrift(this); #endif } @@ -8969,20 +9023,20 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType) << (have_hsreq ? " WITH SRT HS response extensions" : "")); - CPacket response; - response.setControl(UMSG_HANDSHAKE); - response.allocate(m_iMaxSRTPayloadSize); + CPacket rsppkt; + rsppkt.setControl(UMSG_HANDSHAKE); + rsppkt.allocate(m_iMaxSRTPayloadSize); // If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE. // There is also no possible IPE condition in case of HSv4 - for this version it will always return true. enterCS(m_ConnectionLock); - bool create_ok = createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (response), (initdata)); + bool create_ok = createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (initdata)); leaveCS(m_ConnectionLock); if (create_ok) { - response.m_iID = m_PeerID; - setPacketTS(response, steady_clock::now()); - const int nbsent = m_pSndQueue->sendto(m_PeerAddr, response, m_SourceAddr); + rsppkt.set_id(m_PeerID); + setPacketTS(rsppkt, steady_clock::now()); + const int nbsent = m_pSndQueue->sendto(m_PeerAddr, rsppkt, m_SourceAddr); if (nbsent) { m_tsLastSndTime.store(steady_clock::now()); @@ -9114,7 +9168,7 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID); + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.id()); switch (ctrlpkt.getType()) { @@ -9289,37 +9343,44 @@ int srt::CUDT::packLostData(CPacket& w_packet) const steady_clock::time_point time_now = steady_clock::now(); const steady_clock::time_point time_nak = time_now - microseconds_from(m_iSRTT - 4 * m_iRTTVar); - while ((w_packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0) + for (;;) { + w_packet.set_seqno(m_pSndLossList->popLostSeq()); + if (w_packet.seqno() == SRT_SEQNO_NONE) + break; + // XXX See the note above the m_iSndLastDataAck declaration in core.h // This is the place where the important sequence numbers for // sender buffer are actually managed by this field here. - const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.m_iSeqNo); + const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.seqno()); 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(qrlog.Error, - log << CONID() << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " - << w_packet.m_iSeqNo << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset - << ". Continue"); + log << CONID() << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() " + << w_packet.seqno() << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset + << ". Continue, request DROP"); // No matter whether this is right or not (maybe the attack case should be // considered, and some LOSSREPORT flood prevention), send the drop request // to the peer. int32_t seqpair[2] = { - w_packet.m_iSeqNo, + w_packet.seqno(), CSeqNo::decseq(m_iSndLastDataAck) }; - w_packet.m_iMsgNo = 0; // Message number is not known, setting all 32 bits to 0. HLOGC(qrlog.Debug, - log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: msg=" - << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" + log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: #" + << MSGNO_SEQ::unwrap(w_packet.msgflags()) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)"); - sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); + // 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)); continue; } @@ -9329,35 +9390,34 @@ int srt::CUDT::packLostData(CPacket& w_packet) if (tsLastRexmit >= time_nak) { HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno " - << w_packet.m_iSeqNo << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit)) + << w_packet.seqno() << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit)) << " RTT=" << m_iSRTT << " RTTVar=" << m_iRTTVar << " now=" << FormatTime(time_now)); continue; } } - int msglen; + typedef CSndBuffer::DropRange DropRange; + + DropRange buffer_drop; steady_clock::time_point tsOrigin; - const int payload = m_pSndBuffer->readData(offset, (w_packet), (tsOrigin), (msglen)); - if (payload == -1) + const int payload = m_pSndBuffer->readData(offset, (w_packet), (tsOrigin), (buffer_drop)); + if (payload == CSndBuffer::READ_DROP) { - int32_t seqpair[2]; - seqpair[0] = w_packet.m_iSeqNo; - SRT_ASSERT(msglen >= 1); - seqpair[1] = CSeqNo::incseq(seqpair[0], msglen - 1); + SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); HLOGC(qrlog.Debug, - log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: msgno=" - << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " msglen=" << msglen << " SEQ:" << seqpair[0] << " - " - << seqpair[1]); - sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair)); + 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(seqpair[1]); - m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, seqpair[1]); + m_pSndLossList->removeUpTo(buffer_drop.seqno[DropRange::END]); + m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); continue; } - else if (payload == 0) + else if (payload == CSndBuffer::READ_NONE) continue; // The packet has been ecrypted, thus the authentication tag is expected to be stored @@ -9382,7 +9442,7 @@ int srt::CUDT::packLostData(CPacket& w_packet) // So, set here the rexmit flag if the peer understands it. if (m_bPeerRexmitFlag) { - w_packet.m_iMsgNo |= PACKET_SND_REXMIT; + w_packet.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT); } setDataPacketTS(w_packet, tsOrigin); @@ -9487,7 +9547,7 @@ void srt::CUDT::setPacketTS(CPacket& p, const time_point& ts) enterCS(m_StatsLock); const time_point tsStart = m_stats.tsStartTime; leaveCS(m_StatsLock); - p.m_iTimeStamp = makeTS(ts, tsStart); + p.set_timestamp(makeTS(ts, tsStart)); } void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) @@ -9499,14 +9559,14 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) if (!m_bPeerTsbPd) { // If TSBPD is disabled, use the current time as the source (timestamp using the sending time). - p.m_iTimeStamp = makeTS(steady_clock::now(), tsStart); + p.set_timestamp(makeTS(steady_clock::now(), tsStart)); return; } // TODO: Might be better for performance to ensure this condition is always false, and just use SRT_ASSERT here. if (ts < tsStart) { - p.m_iTimeStamp = makeTS(steady_clock::now(), tsStart); + p.set_timestamp(makeTS(steady_clock::now(), tsStart)); LOGC(qslog.Warn, log << CONID() << "setPacketTS: reference time=" << FormatTime(ts) << " is in the past towards start time=" << FormatTime(tsStart) @@ -9515,7 +9575,7 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) } // Use the provided source time for the timestamp. - p.m_iTimeStamp = makeTS(ts, tsStart); + p.set_timestamp(makeTS(ts, tsStart)); } bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) @@ -9626,14 +9686,14 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime new_packet_packed = true; // every 16 (0xF) packets, a packet pair is sent - if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) + if ((w_packet.seqno() & PUMASK_SEQNO_PROBE) == 0) probe = true; payload = (int) w_packet.getLength(); IF_HEAVY_LOGGING(reason = "normal"); } - w_packet.m_iID = m_PeerID; // Set the destination SRT socket ID. + w_packet.set_id(m_PeerID); // Set the destination SRT socket ID. if (new_packet_packed && m_PacketFilter) { @@ -9643,7 +9703,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime #if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr() HLOGC(qslog.Debug, - log << CONID() << "packData: " << reason << " packet seq=" << w_packet.m_iSeqNo << " (ACK=" << m_iSndLastAck + log << CONID() << "packData: " << reason << " packet seq=" << w_packet.seqno() << " (ACK=" << m_iSndLastAck << " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")"); #endif @@ -9662,7 +9722,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime // Left untouched for historical reasons. // Might be possible that it was because of that this is send from // different thread than the rest of the signals. - // m_pSndTimeWindow->onPktSent(w_packet.m_iTimeStamp); + // m_pSndTimeWindow->onPktSent(w_packet.timestamp()); enterCS(m_StatsLock); m_stats.sndr.sent.count(payload); @@ -9762,7 +9822,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) // Fortunately the group itself isn't being accessed. if (m_parent->m_GroupOf) { - const int packetspan = CSeqNo::seqoff(current_sequence_number, w_packet.m_iSeqNo); + const int packetspan = CSeqNo::seqoff(current_sequence_number, w_packet.seqno()); if (packetspan > 0) { // After increasing by 1, but being previously set as ISN-1, this should be == ISN, @@ -9776,7 +9836,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) // initialized from ISN just after connection. LOGC(qslog.Note, log << CONID() << "packUniqueData: Fixing EXTRACTION sequence " << current_sequence_number - << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " for the first packet: DIFF=" + << " from SCHEDULING sequence " << w_packet.seqno() << " for the first packet: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); } else @@ -9784,7 +9844,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) // There will be a serious data discrepancy between the agent and the peer. LOGC(qslog.Error, log << CONID() << "IPE: packUniqueData: Fixing EXTRACTION sequence " << current_sequence_number - << " from SCHEDULING sequence " << w_packet.m_iSeqNo << " in the middle of transition: DIFF=" + << " from SCHEDULING sequence " << w_packet.seqno() << " in the middle of transition: DIFF=" << packetspan << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); } @@ -9793,7 +9853,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) // Don't do it if the difference isn't positive or exceeds the threshold. int32_t seqpair[2]; seqpair[0] = current_sequence_number; - seqpair[1] = CSeqNo::decseq(w_packet.m_iSeqNo); + seqpair[1] = CSeqNo::decseq(w_packet.seqno()); const int32_t no_msgno = 0; LOGC(qslog.Debug, log << CONID() << "packUniqueData: Sending DROPREQ: SEQ: " << seqpair[0] << " - " << seqpair[1] << " (" @@ -9805,16 +9865,16 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) // Override extraction sequence with scheduling sequence. ScopedLock ackguard(m_RecvAckLock); - m_iSndCurrSeqNo = w_packet.m_iSeqNo; - m_iSndLastAck = w_packet.m_iSeqNo; - m_iSndLastDataAck = w_packet.m_iSeqNo; - m_iSndLastFullAck = w_packet.m_iSeqNo; - m_iSndLastAck2 = w_packet.m_iSeqNo; + m_iSndCurrSeqNo = w_packet.seqno(); + m_iSndLastAck = w_packet.seqno(); + m_iSndLastDataAck = 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.m_iSeqNo + log << CONID() << "IPE: packData: 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? @@ -9826,15 +9886,15 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) { HLOGC(qslog.Debug, log << CONID() << "packUniqueData: Applying EXTRACTION sequence " << current_sequence_number - << " over SCHEDULING sequence " << w_packet.m_iSeqNo << " for socket not in group:" - << " DIFF=" << CSeqNo::seqcmp(current_sequence_number, w_packet.m_iSeqNo) + << " over SCHEDULING sequence " << w_packet.seqno() << " for socket not in group:" + << " DIFF=" << CSeqNo::seqcmp(current_sequence_number, w_packet.seqno()) << " STAMP=" << BufferStamp(w_packet.m_pcData, w_packet.getLength())); // Do this always when not in a group. - w_packet.m_iSeqNo = current_sequence_number; + w_packet.set_seqno(current_sequence_number); } // Set missing fields before encrypting the packet, because those fields might be used for encryption. - w_packet.m_iID = m_PeerID; // Destination SRT Socket ID + w_packet.set_id(m_PeerID); // Destination SRT Socket ID setDataPacketTS(w_packet, tsOrigin); if (kflg != EK_NOENC) @@ -9854,7 +9914,7 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) } #if SRT_DEBUG_TRACE_SND - g_snd_logger.state.iPktSeqno = w_packet.m_iSeqNo; + g_snd_logger.state.iPktSeqno = w_packet.seqno(); g_snd_logger.state.isRetransmitted = w_packet.getRexmitFlag(); g_snd_logger.trace(); #endif @@ -9970,12 +10030,13 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) int srt::CUDT::checkLazySpawnTsbPdThread() { const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd; + if (!need_tsbpd) + return 0; - if (need_tsbpd && !m_RcvTsbPdThread.joinable()) + ScopedLock lock(m_RcvTsbPdStartupLock); + if (!m_RcvTsbPdThread.joinable()) { - ScopedLock lock(m_RcvTsbPdStartupLock); - - if (m_bClosing) // Check again to protect join() in CUDT::releaseSync() + if (m_bClosing) // Check m_bClosing to protect join() in CUDT::releaseSync(). return -1; HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread"); @@ -10023,7 +10084,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& bool adding_successful = true; - const int32_t bufidx = CSeqNo::seqoff(bufseq, rpkt.m_iSeqNo); + const int32_t bufidx = CSeqNo::seqoff(bufseq, rpkt.seqno()); IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED"); @@ -10048,7 +10109,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& // which never contains losses, so discarding this packet does not // discard a loss coverage, even if this were past ACK. - if (bufidx < 0 || CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvLastAck) < 0) + if (bufidx < 0 || CSeqNo::seqcmp(rpkt.seqno(), m_iRcvLastAck) < 0) { time_point pts = getPktTsbPdTime(NULL, rpkt); @@ -10061,7 +10122,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& m_stats.rcvr.recvdBelated.count(rpkt.getLength()); leaveCS(m_StatsLock); HLOGC(qrlog.Debug, - log << CONID() << "RECEIVED: %" << rpkt.m_iSeqNo << " bufidx=" << bufidx << " (BELATED/" + log << CONID() << "RECEIVED: %" << rpkt.seqno() << " bufidx=" << bufidx << " (BELATED/" << s_rexmitstat_str[pktrexmitflag] << ") with ACK %" << m_iRcvLastAck << " FLAGS: " << rpkt.MessageFlagStr()); continue; @@ -10083,7 +10144,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& LOGC(qrlog.Error, log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION." - " %" << rpkt.m_iSeqNo + " %" << rpkt.seqno() << " buffer=(%" << bufseq << ":%" << m_iRcvCurrSeqNo // -1 = size to last index << "+%" << CSeqNo::incseq(bufseq, int(m_pRcvBuffer->capacity()) - 1) @@ -10094,7 +10155,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } else { - LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.m_iSeqNo + LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet seqno " << rpkt.seqno() << ", insert offset " << bufidx << ". " << m_pRcvBuffer->strFullnessState(m_iRcvLastAck, steady_clock::now()) ); @@ -10164,7 +10225,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& #endif } } - else if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + else if (m_pCryptoControl && m_pCryptoControl->m_RcvKmState == SRT_KM_S_SECURED) { // 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); @@ -10213,7 +10274,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& // Empty buffer info in case of groupwise receiver. // There's no way to obtain this information here. - LOGC(qrlog.Debug, log << CONID() << "RECEIVED: %" << rpkt.m_iSeqNo + LOGC(qrlog.Debug, log << CONID() << "RECEIVED: %" << rpkt.seqno() << bufinfo.str() << " RSL=" << expectspec.str() << " SN=" << s_rexmitstat_str[pktrexmitflag] @@ -10227,12 +10288,12 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& { HLOGC(qrlog.Debug, log << CONID() - << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo)); + << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.seqno())); - if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. + if (CSeqNo::seqcmp(rpkt.seqno(), CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection. { int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); - int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo); + int32_t seqhi = CSeqNo::decseq(rpkt.seqno()); w_srt_loss_seqs.push_back(make_pair(seqlo, seqhi)); HLOGC(qrlog.Debug, log << "pkt/LOSS DETECTED: %" << seqlo << " - %" << seqhi); } @@ -10240,9 +10301,9 @@ 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.m_iSeqNo, m_iRcvCurrSeqNo) > 0) + if (CSeqNo::seqcmp(rpkt.seqno(), m_iRcvCurrSeqNo) > 0) { - m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received + m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received } else { @@ -10290,7 +10351,7 @@ int srt::CUDT::processData(CUnit* in_unit) // Search the sequence in the loss record. rexmit_reason = " by "; ScopedLock lock(m_RcvLossLock); - if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo)) + if (!m_pRcvLossList->find(packet.seqno(), packet.seqno())) rexmit_reason += "BLIND"; else rexmit_reason += "NAKREPORT"; @@ -10334,7 +10395,7 @@ int srt::CUDT::processData(CUnit* in_unit) // Conditions and any extra data required for the packet // this function will extract and test as needed. - const bool unordered = CSeqNo::seqcmp(packet.m_iSeqNo, m_iRcvCurrSeqNo) <= 0; + const bool unordered = CSeqNo::seqcmp(packet.seqno(), m_iRcvCurrSeqNo) <= 0; // Retransmitted and unordered packets do not provide expected measurement. // We expect the 16th and 17th packet to be sent regularly, @@ -10360,7 +10421,7 @@ int srt::CUDT::processData(CUnit* in_unit) // supply the missing packet(s), and the loss will no longer be visible for the code that follows. if (packet.getMsgSeq(m_bPeerRexmitFlag) != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing { - const int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo); + const int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.seqno()); // Difference between these two sequence numbers is expected to be: // 0 - duplicated last packet (theory only) // 1 - subsequent packet (alright) @@ -10376,13 +10437,13 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " " - << CSeqNo::decseq(packet.m_iSeqNo) << "]"); + << CSeqNo::decseq(packet.seqno()) << "]"); } if (diff > 0) { // Record if it was further than latest - m_iRcvCurrPhySeqNo = packet.m_iSeqNo; + m_iRcvCurrPhySeqNo = packet.seqno(); } } @@ -10448,7 +10509,7 @@ int srt::CUDT::processData(CUnit* in_unit) // offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() UniqueLock recvbuf_acklock(m_RcvBufferLock); // Needed for possibly check for needsQuickACK. - const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_pRcvBuffer->getStartSeqNo()) < 0); + const bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.seqno(), m_pRcvBuffer->getStartSeqNo()) < 0); const int res = handleSocketPacketReception(incoming, (new_inserted), @@ -10733,7 +10794,7 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) void srt::CUDT::unlose(const CPacket &packet) { ScopedLock lg(m_RcvLossLock); - int32_t sequence = packet.m_iSeqNo; + int32_t sequence = packet.seqno(); m_pRcvLossList->remove(sequence); // Rest of this code concerns only the "belated lossreport" feature. @@ -10753,7 +10814,7 @@ void srt::CUDT::unlose(const CPacket &packet) { HLOGC(qrlog.Debug, log << "received out-of-band packet %" << sequence); - const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo)); + const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.seqno())); enterCS(m_StatsLock); m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance); leaveCS(m_StatsLock); @@ -10968,7 +11029,7 @@ int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int co int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) { // XXX ASSUMPTIONS: - // [[using assert(packet.m_iID == 0)]] + // [[using assert(packet.id() == 0)]] HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received a connection request"); @@ -11056,7 +11117,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) // array need not be aligned to int32_t - changed to union in a hope that using int32_t // inside a union will enforce whole union to be aligned to int32_t. hs.m_iCookie = cookie_val; - packet.m_iID = hs.m_iID; + packet.set_id(hs.m_iID); // Ok, now's the time. The listener sets here the version 5 handshake, // even though the request was 4. This is because the old client would @@ -11124,7 +11185,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."); } - int32_t id = hs.m_iID; + SRTSOCKET id = hs.m_iID; // HANDSHAKE: The old client sees the version that does not match HS_VERSION_UDT4 (5). // In this case it will respond with URQ_ERROR_REJECT. Rest of the data are the same @@ -11172,8 +11233,8 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) hs.m_iReqType = URQFailure(m_RejectReason); size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); - packet.m_iID = id; - setPacketTS(packet, steady_clock::now()); + 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); } @@ -11284,7 +11345,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) CPacket rsp; setPacketTS((rsp), steady_clock::now()); rsp.pack(UMSG_SHUTDOWN); - rsp.m_iID = m_PeerID; + rsp.set_id(m_PeerID); m_pSndQueue->sendto(addr, rsp, use_source_addr); } else @@ -11295,14 +11356,14 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) size_t size = CHandShake::m_iContentSize; hs.store_to((packet.m_pcData), (size)); packet.setLength(size); - packet.m_iID = id; + 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); } } } - LOGC(cnlog.Note, log << CONID() << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); + LOGC(cnlog.Debug, log << CONID() << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType)); return RejectReasonForURQ(hs.m_iReqType); } @@ -11679,17 +11740,20 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) // Bound to one call because this requires locking pg->updateFailedLink(); } + // Sockets that never succeeded to connect must be deleted + // explicitly, otherwise they will never be deleted. OTOH + // the socket can be on the path of deletion already, so + // this only makes sure that the socket will be deleted, + // one way or another. + if (pending_broken) + { + // XXX This somehow can cause a deadlock + // uglobal()->close(m_parent); + LOGC(smlog.Debug, log << "updateBrokenConnection...: BROKEN SOCKET @" << m_SocketID << " - CLOSING, to be removed from group."); + m_parent->setBrokenClosed(); + } } - // Sockets that never succeeded to connect must be deleted - // explicitly, otherwise they will never be deleted. - if (pending_broken) - { - // XXX This somehow can cause a deadlock - // uglobal()->close(m_parent); - LOGC(smlog.Debug, log << "updateBrokenConnection...: BROKEN SOCKET @" << m_SocketID << " - CLOSING, to be removed from group."); - m_parent->setBrokenClosed(); - } #endif } @@ -11941,7 +12005,7 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr if (m_parent->m_GroupOf) { // Lock GlobControlLock in order to make sure that - // the state if the socket having the group and the + // 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. diff --git a/srtcore/core.h b/srtcore/core.h index 9d5fa4c3a..ed250c641 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -120,7 +120,12 @@ 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 []; enum GroupDataItem @@ -322,6 +327,7 @@ class CUDT #endif int32_t rcvSeqNo() const { return m_iRcvCurrSeqNo; } + SRT_ATTR_REQUIRES(m_RecvAckLock) int flowWindowSize() const { return m_iFlowWindowSize; } int32_t deliveryRate() const { return m_iDeliveryRate; } int bandwidth() const { return m_iBandwidth; } @@ -383,6 +389,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) int32_t getFlightSpan() const { return getFlightSpan(m_iSndLastAck, m_iSndCurrSeqNo); @@ -409,7 +416,7 @@ class CUDT static void setPacketTS(CPacket& p, const time_point& start_time, const time_point& ts) { - p.m_iTimeStamp = makeTS(ts, start_time); + p.set_timestamp(makeTS(ts, start_time)); } /// @brief Set the timestamp field of the packet using the provided value (no check) @@ -517,6 +524,7 @@ class CUDT /// Allocates sender and receiver buffers and loss lists. SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) bool prepareBuffers(CUDTException* eout); + int getAuthTagSize() const; SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) EConnectStatus postConnect(const CPacket* response, bool rendezvous, CUDTException* eout) ATR_NOEXCEPT; @@ -691,6 +699,8 @@ class CUDT /// the receiver fresh loss list. void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); + + SRT_ATTR_REQUIRES(m_RecvAckLock) bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); SRT_ATTR_EXCLUDES(m_ConnectionLock) @@ -746,14 +756,25 @@ class CUDT SRT_ATTR_REQUIRES(m_RcvBufferLock) bool isRcvBufferReadyNoLock() const; + SRT_ATTR_EXCLUDES(m_RcvBufferLock) + bool isRcvBufferFull() const; + // TSBPD thread main function. static void* tsbpd(void* param); + enum DropReason + { + DROP_TOO_LATE, //< Drop to keep up to the live pace (TLPKTDROP). + DROP_DISCARD //< Drop because another group member already provided these packets. + }; + /// Drop too late packets (receiver side). Update loss lists and ACK positions. /// The @a seqno packet itself is not dropped. /// @param seqno [in] The sequence number of the first packets following those to be dropped. + /// @param reason A reason for dropping (see @a DropReason). /// @return The number of packets dropped. - int rcvDropTooLateUpTo(int seqno); + SRT_ATTR_EXCLUDES(m_RcvBufferLock, 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); @@ -927,13 +948,15 @@ class CUDT int32_t m_iReXmitCount; // Re-Transmit Count since last ACK static const size_t - MAX_FREQLOGFA = 2, FREQLOGFA_ENCRYPTION_FAILURE = 0, - FREQLOGFA_RCV_DROPPED = 1; - atomic_time_point m_tsLogSlowDown; // The last time a log message from the "slow down" group was shown. - // The "slow down" group of logs are those that can be printed too often otherwise, but can't be turned off (warnings and errors). - // Currently only used by decryption failure message, therefore no mutex protection needed. - sync::atomic m_LogSlowDownExpired; // Can't use bitset because atomic + FREQLOGFA_RCV_DROPPED = 1, + FREQLOGFA_ACKACK_OUTOFORDER = 2, + MAX_FREQLOGFA = 3; + + atomic_time_point m_tsLogSlowDown[MAX_FREQLOGFA]; // The last time a log message from the "slow down" group was shown. + // The "slow down" group of logs are those that can be printed too often otherwise, but can't be turned off (warnings and errors). + // Currently only used by decryption failure message, therefore no mutex protection needed. + sync::atomic m_LogSlowDownExpired; // Can't use bitset because atomic sync::atomic m_aSuppressedMsg[MAX_FREQLOGFA]; /// @brief Check if a frequent log can be shown. @@ -942,6 +965,7 @@ class CUDT bool frequentLogAllowed(size_t logid, const time_point& tnow, std::string& why); private: // Receiving related data + SRT_ATTR_GUARDED_BY(m_RcvBufferLock) CRcvBuffer* m_pRcvBuffer; //< Receiver buffer SRT_ATTR_GUARDED_BY(m_RcvLossLock) CRcvLossList* m_pRcvLossList; //< Receiver loss list @@ -972,10 +996,11 @@ class CUDT bool m_bTsbPd; // Peer sends TimeStamp-Based Packet Delivery Packets bool m_bGroupTsbPd; // TSBPD should be used for GROUP RECEIVER instead + SRT_ATTR_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_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent - sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creating and joining + bool m_bTsbPdNeedsWakeup; // Signal TsbPd thread to wake up on RCV buffer state change. + sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creation and joining. CallbackHolder m_cbAcceptHook; CallbackHolder m_cbConnectHook; @@ -1106,6 +1131,8 @@ class CUDT /// @retval false Nothing was extracted for sending, @a nexttime should be ignored bool packData(CPacket& packet, time_point& nexttime, sockaddr_any& src_addr); + /// Also excludes srt::CUDTUnited::m_GlobControlLock. + SRT_ATTR_EXCLUDES(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) int processData(CUnit* unit); /// This function passes the incoming packet to the initial processing @@ -1123,13 +1150,16 @@ class CUDT /// @return -2 The incoming packet exceeds the expected sequence by more than a length of the buffer (irrepairable discrepancy). int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); - /// Get the packet's TSBPD time. + /// 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. time_point getPktTsbPdTime(void* grp, const CPacket& packet); + SRT_ATTR_EXCLUDES(m_RcvTsbPdStartupLock) /// Checks and spawns the TSBPD thread if required. int checkLazySpawnTsbPdThread(); + void processClose(); /// Process the request after receiving the handshake from caller. @@ -1143,18 +1173,12 @@ class CUDT static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi); int32_t bake(const sockaddr_any& addr, int32_t previous_cookie = 0, int correction = 0); -#if ENABLE_BONDING - /// @brief Drop packets in the recv buffer behind group_recv_base. - /// Updates m_iRcvLastSkipAck if it's behind group_recv_base. - void dropToGroupRecvBase(); -#endif - void processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival); + SRT_ATTR_REQUIRES(m_RcvBufferLock) /// Retrieves the available size of the receiver buffer. /// Expects that m_RcvBufferLock is locked. - SRT_ATTR_REQUIRES(m_RcvBufferLock) size_t getAvailRcvBufferSizeNoLock() const; private: // Trace diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index fdd643f22..e7fe0be89 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -138,7 +138,7 @@ void srt::CCryptoControl::createFakeSndContext() int srt::CCryptoControl::processSrtMsg_KMREQ( const uint32_t* srtdata SRT_ATR_UNUSED, size_t bytelen SRT_ATR_UNUSED, - int hsv SRT_ATR_UNUSED, + int hsv SRT_ATR_UNUSED, unsigned srtv SRT_ATR_UNUSED, uint32_t pw_srtdata_out[], size_t& w_srtlen) { //Receiver @@ -172,6 +172,8 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM) || (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM); + m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3); + // What we have to do: // If encryption is on (we know that by having m_KmSecret nonempty), create // the crypto context (if bidirectional, create for both sending and receiving). @@ -331,6 +333,13 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update 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: @@ -359,7 +368,7 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( return SRT_CMD_KMRSP; } -int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int /* XXX unused? hsv*/) +int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) { /* All 32-bit msg fields (if present) swapped on reception * But HaiCrypt expect network order message @@ -371,9 +380,6 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len int retstatus = -1; - // Unused? - //bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; - // 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; @@ -459,6 +465,14 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len } HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); + + m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3); +#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 } LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len)); @@ -593,6 +607,7 @@ srt::CCryptoControl::CCryptoControl(SRTSOCKET id) , m_KmRefreshRatePkt(0) , m_KmPreAnnouncePkt(0) , m_iCryptoMode(CSrtConfig::CIPHER_MODE_AUTO) + , m_bUseGcm153(false) , m_bErrorReported(false) { m_KmSecret.len = 0; @@ -606,7 +621,7 @@ srt::CCryptoControl::CCryptoControl(SRTSOCKET id) m_hRcvCrypto = NULL; } -bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool bidirectional SRT_ATR_UNUSED) +bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool bidirectional SRT_ATR_UNUSED, bool bUseGcm153 SRT_ATR_UNUSED) { // NOTE: initiator creates m_hSndCrypto. When bidirectional, // it creates also m_hRcvCrypto with the same key length. @@ -620,6 +635,7 @@ bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool b // Set UNSECURED state as default m_RcvKmState = SRT_KM_S_UNSECURED; m_iCryptoMode = cfg.iCryptoMode; + m_bUseGcm153 = bUseGcm153; #ifdef SRT_ENABLE_ENCRYPTION if (!cfg.bTSBPD && m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO) @@ -807,7 +823,7 @@ srt::EncryptionStatus srt::CCryptoControl::encrypt(CPacket& w_packet SRT_ATR_UNU { return ENCS_FAILED; } - else if ( rc > 0 ) + else if (rc > 0) { // XXX what happens if the encryption is said to be "succeeded", // but the length is 0? Shouldn't this be treated as unwanted? diff --git a/srtcore/crypto.h b/srtcore/crypto.h index 370d5529c..613ded8dd 100644 --- a/srtcore/crypto.h +++ b/srtcore/crypto.h @@ -68,6 +68,7 @@ class CCryptoControl int m_KmRefreshRatePkt; int m_KmPreAnnouncePkt; int m_iCryptoMode; + bool m_bUseGcm153; // Older AES-GCM version existed up to SRT v1.5.3. HaiCrypt_Secret m_KmSecret; //Key material shared secret // Sender @@ -127,15 +128,18 @@ class CCryptoControl // Needed for CUDT void updateKmState(int cmd, size_t srtlen); - // Detailed processing - int processSrtMsg_KMREQ(const uint32_t* srtdata, size_t len, int hsv, + /// Process the KM request message. + /// @param srtv peer's SRT version. + int processSrtMsg_KMREQ(const uint32_t* srtdata, size_t len, int hsv, unsigned srtv, uint32_t srtdata_out[], size_t&); - // This returns: - // 1 - the given payload is the same as the currently used key - // 0 - there's no key in agent or the payload is error message with agent NOSECRET. - // -1 - the payload is error message with other state or it doesn't match the key - int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int hsv); + /// Process the KM response message. + /// @param srtv peer's SRT version. + /// @returns + /// 1 - the given payload is the same as the currently used key + /// 0 - there's no key in agent or the payload is error message with agent NOSECRET. + /// -1 - the payload is error message with other state or it doesn't match the key + int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv); void createFakeSndContext(); const unsigned char* getKmMsg_data(size_t ki) const { return m_SndKmMsg[ki].Msg; } @@ -207,7 +211,7 @@ class CCryptoControl std::string CONID() const; std::string FormatKmMessage(std::string hdr, int cmd, size_t srtlen); - bool init(HandshakeSide, const CSrtConfig&, bool); + bool init(HandshakeSide, const CSrtConfig&, bool bidir, bool bUseGcm153); SRT_ATTR_EXCLUDES(m_mtxLock) void close(); diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index 9830a3bc8..fd762b88b 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -598,6 +598,9 @@ void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg, g.flag_clip = g.flag_clip ^ kflg; g.timestamp_clip = g.timestamp_clip ^ timestamp_hw; + HLOGC(pflog.Debug, log << "FEC CLIP: data pkt.size=" << payload_size + << " to a clip buffer size=" << payloadSize()); + // Payload goes "as is". for (size_t i = 0; i < payload_size; ++i) { diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 63b815eb8..7e755da6e 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -259,6 +259,7 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) , m_uOPT_MinStabilityTimeout_us(1000 * CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS) // -1 = "undefined"; will become defined with first added socket , m_iMaxPayloadSize(-1) + , m_iAvgPayloadSize(-1) , m_bSynRecving(true) , m_bSynSending(true) , m_bTsbPd(true) @@ -718,6 +719,16 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) return true; } +struct FOptionValue +{ + SRT_SOCKOPT expected; + FOptionValue(SRT_SOCKOPT v): expected(v) {} + bool operator()(const CUDTGroup::ConfigItem& i) const + { + return i.so == expected; + } +}; + void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) { // Options handled in group @@ -733,46 +744,133 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) w_optlen = sizeof(bool); return; + case SRTO_SNDTIMEO: + *(int*)pw_optval = m_iSndTimeOut; + w_optlen = sizeof(int); + return; + + case SRTO_RCVTIMEO: + *(int*)pw_optval = m_iRcvTimeOut; + w_optlen = sizeof(int); + return; + + case SRTO_GROUPMINSTABLETIMEO: + *(uint32_t*)pw_optval = m_uOPT_MinStabilityTimeout_us / 1000; + w_optlen = sizeof(uint32_t); + return; + + case SRTO_KMSTATE: + *(uint32_t*)pw_optval = getGroupEncryptionState(); + w_optlen = sizeof(uint32_t); + return; + + // Write-only options for security reasons or + // options that refer to a socket state, that + // makes no sense for a group. + case SRTO_PASSPHRASE: + case SRTO_KMPREANNOUNCE: + case SRTO_KMREFRESHRATE: + case SRTO_BINDTODEVICE: + case SRTO_GROUPCONNECT: + case SRTO_STATE: + case SRTO_EVENT: + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + default:; // pass on } - // XXX Suspicous: may require locking of GlobControlLock - // to prevent from deleting a socket in the meantime. - // Deleting a socket requires removing from the group first, - // so after GroupLock this will be either already NULL or - // a valid socket that will only be closed after time in - // the GC, so this is likely safe like all other API functions. - CUDTSocket* ps = 0; + bool is_set_on_socket = false; + { + // 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. + enterCS(m_GroupLock); + gli_t gi = m_Group.begin(); + CUDTSocket* const ps = (gi != m_Group.end()) ? gi->ps : NULL; + CUDTUnited::SocketKeeper sk(CUDT::uglobal(), ps); + leaveCS(m_GroupLock); + if (sk.socket) + { + // Return the value from the first member socket, if any is present + // Note: Will throw exception if the request is wrong. + sk.socket->core().getOpt(optname, (pw_optval), (w_optlen)); + is_set_on_socket = true; + } + } + + // Check if the option is in the storage, which means that + // it was modified on the group. + + vector::const_iterator i = find_if(m_config.begin(), m_config.end(), + FOptionValue(optname)); + + if (i == m_config.end()) + { + // Already written to the target variable. + if (is_set_on_socket) + return; + + // Not found, see the defaults + if (!getOptDefault(optname, (pw_optval), (w_optlen))) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + return; + } + // NOTE: even if is_set_on_socket, if it was also found in the group + // settings, overwrite with the value from the group. + + // Found, return the value from the storage. + // Check the size first. + if (w_optlen < int(i->value.size())) + throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); + w_optlen = i->value.size(); + memcpy((pw_optval), &i->value[0], i->value.size()); +} + +SRT_KM_STATE CUDTGroup::getGroupEncryptionState() +{ + multiset kmstates; { - // In sockets. All sockets should have all options - // set the same and should represent the group state - // well enough. If there are no sockets, just use default. + ScopedLock lk (m_GroupLock); - // Group lock to protect the container itself. - // Once a socket is extracted, we state it cannot be - // closed without the group send/recv function or closing - // being involved. - ScopedLock lg(m_GroupLock); + // First check the container. If empty, return UNSECURED if (m_Group.empty()) - { - if (!getOptDefault(optname, (pw_optval), (w_optlen))) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + return SRT_KM_S_UNSECURED; - return; + 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; + // 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 ain't true). + if (gst == SRT_KM_S_UNSECURED && cc->m_SndKmState == SRT_KM_S_NOSECRET) + gst = SRT_KM_S_NOSECRET; + kmstates.insert(gst); } + } - ps = m_Group.begin()->ps; + // Criteria are: + // 1. UNSECURED, if no member sockets, or at least one UNSECURED found. + // 2. SECURED, if at least one SECURED found (cut off the previous criteria). + // 3. BADSECRET otherwise, although return NOSECRET if no BADSECRET is found. - // Release the lock on the group, as it's not necessary, - // as well as it might cause a deadlock when combined - // with the others. - } + if (kmstates.count(SRT_KM_S_UNSECURED)) + return SRT_KM_S_UNSECURED; - if (!ps) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + // Now we have UNSECURED ruled out. Remaining may be NOSECRET, BADSECRET or SECURED. + // NOTE: SECURING is an intermediate state for HSv4 and can't occur in groups. + if (kmstates.count(SRT_KM_S_SECURED)) + return SRT_KM_S_SECURED; + + if (kmstates.count(SRT_KM_S_BADSECRET)) + return SRT_KM_S_BADSECRET; - return ps->core().getOpt(optname, (pw_optval), (w_optlen)); + return SRT_KM_S_NOSECRET; } SRT_SOCKSTATUS CUDTGroup::getStatus() @@ -846,18 +944,9 @@ void CUDTGroup::syncWithSocket(const CUDT& core, const HandshakeSide side) set_currentSchedSequence(core.ISN()); } - // XXX - // Might need further investigation as to whether this isn't - // wrong for some cases. By having this -1 here the value will be - // laziliy set from the first reading one. It is believed that - // it covers all possible scenarios, that is: - // - // - no readers - no problem! - // - have some readers and a new is attached - this is set already - // - connect multiple links, but none has read yet - you'll be the first. - // - // Previous implementation used setting to: core.m_iPeerISN - resetInitialRxSequence(); + // 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); // Get the latency (possibly fixed against the opposite side) // from the first socket (core.m_iTsbPdDelay_ms), @@ -1486,7 +1575,10 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) int ercode = 0; - if (was_blocked) + // This block causes waiting for any socket to accept the payload. + // This should be done only in blocking mode and only if no other socket + // accepted the payload. + if (was_blocked && none_succeeded && m_bSynSending) { m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); if (!m_bSynSending) @@ -1633,6 +1725,19 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) throw CUDTException(major, minor, 0); } + for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is) + { + // Here we have a situation that at least 1 link successfully sent a packet. + // All links for which sending has failed must be closed. + if (is->stat == -1) + { + // 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); + is->mb->ps->setBrokenClosed(); + } + } + // Now that at least one link has succeeded, update sending stats. m_stats.sent.count(len); @@ -2025,10 +2130,14 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& } else { - // No read-readiness reported by epoll, but probably missed or not yet handled - // as the receiver buffer is read-ready. + // No read-readiness reported by epoll, but can be missed or not yet handled + // while the receiver buffer is in fact read-ready. ScopedLock lg(sock->core().m_RcvBufferLock); - if (sock->core().m_pRcvBuffer && sock->core().m_pRcvBuffer->isRcvDataReady()) + if (!sock->core().m_pRcvBuffer) + continue; + // Checking for the next packet in the RCV buffer is safer that isReadReady(tnow). + const CRcvBuffer::PacketInfo info = sock->core().m_pRcvBuffer->getFirstValidPacketInfo(); + if (info.seqno != SRT_SEQNO_NONE && !info.seq_gap) readReady.push_back(sock); } } @@ -2198,6 +2307,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) } // Find the first readable packet among all member sockets. + steady_clock::time_point tnow = steady_clock::now(); CUDTSocket* socketToRead = NULL; CRcvBuffer::PacketInfo infoToRead = {-1, false, time_point()}; for (vector::const_iterator si = readySockets.begin(); si != readySockets.end(); ++si) @@ -2218,7 +2328,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) } const CRcvBuffer::PacketInfo info = - ps->core().m_pRcvBuffer->getFirstReadablePacketInfo(steady_clock::now()); + 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."); @@ -2238,6 +2348,12 @@ 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)) + { + // We have the next packet. No need to check other read-ready sockets. + break; + } } } @@ -2286,6 +2402,20 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) } fillGroupData((w_mc), w_mc); + // m_RcvBaseSeqNo is expected to be set to the PeerISN with the first connected member, + // 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; + if (iNumDropped > 0) + { + m_stats.recvDrop.count(stats::BytesPackets(iNumDropped * static_cast(avgRcvPacketSize()), iNumDropped)); + LOGC(grlog.Warn, + log << "@" << m_GroupID << " GROUP RCV-DROPPED " << iNumDropped << " packet(s): seqno %" + << CSeqNo::incseq(m_RcvBaseSeqNo) << " to %" << CSeqNo::decseq(w_mc.pktseq)); + } + } + HLOGC(grlog.Debug, log << "grp/recv: $" << id() << ": Update m_RcvBaseSeqNo: %" << m_RcvBaseSeqNo << " -> %" << w_mc.pktseq); m_RcvBaseSeqNo = w_mc.pktseq; @@ -2301,7 +2431,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) ScopedLock lg(ps->core().m_RcvBufferLock); if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) { - const int cnt = ps->core().rcvDropTooLateUpTo(CSeqNo::incseq(m_RcvBaseSeqNo)); + const int cnt = ps->core().rcvDropTooLateUpTo(CSeqNo::incseq(m_RcvBaseSeqNo), CUDT::DROP_DISCARD); if (cnt > 0) { HLOGC(grlog.Debug, @@ -2534,7 +2664,7 @@ class StabilityTracer str_tnow.replace(str_tnow.find(':'), 1, 1, '_'); } const std::string fname = "stability_trace_" + str_tnow + ".csv"; - m_fout.open(fname, std::ofstream::out); + m_fout.open(fname.c_str(), std::ofstream::out); if (!m_fout) std::cerr << "IPE: Failed to open " << fname << "!!!\n"; @@ -3143,7 +3273,7 @@ void CUDTGroup::send_CloseBrokenSockets(vector& w_wipeme) InvertedLock ug(m_GroupLock); // With unlocked GroupLock, we can now lock GlobControlLock. - // This is needed prevent any of them be deleted from the container + // This is needed to prevent any of them deleted from the container // at the same time. ScopedLock globlock(CUDT::uglobal().m_GlobControlLock); diff --git a/srtcore/group.h b/srtcore/group.h index 3ee51bb47..e73856df1 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -319,6 +319,8 @@ class CUDTGroup void send_CheckValidSockets(); + SRT_KM_STATE getGroupEncryptionState(); + public: int recv(char* buf, int len, SRT_MSGCTRL& w_mc); diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 0a175ee9d..0fe7d2b4b 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -653,6 +653,8 @@ bool srt::CRcvLossList::remove(int32_t seqno) } m_iLength--; + if (m_iLength == 0) + m_iLargestSeq = SRT_SEQNO_NONE; return true; } @@ -708,6 +710,8 @@ bool srt::CRcvLossList::remove(int32_t seqno) } m_iLength--; + if (m_iLength == 0) + m_iLargestSeq = SRT_SEQNO_NONE; return true; } diff --git a/srtcore/logging.cpp b/srtcore/logging.cpp index d309b1b8a..d0ba3fd4a 100644 --- a/srtcore/logging.cpp +++ b/srtcore/logging.cpp @@ -14,7 +14,7 @@ written by *****************************************************************************/ - +#include "srt_compat.h" #include "logging.h" using namespace std; @@ -23,69 +23,6 @@ using namespace std; namespace srt_logging { -// 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(); - } -} - -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 !ENABLE_LOGGING -// - PrintLogLine, which has empty body when !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) << msg; - src_config->log_stream->flush(); - } - src_config->unlock(); -} - #if ENABLE_LOGGING diff --git a/srtcore/logging.h b/srtcore/logging.h index 608234eab..3f4efb286 100644 --- a/srtcore/logging.h +++ b/srtcore/logging.h @@ -20,7 +20,6 @@ written by #include #include #include -#include #include #include #ifdef _WIN32 @@ -34,7 +33,6 @@ written by #include "utilities.h" #include "threadname.h" #include "logging_api.h" -#include "srt_compat.h" #include "sync.h" #ifdef __GNUC__ @@ -114,9 +112,8 @@ struct LogConfig std::ostream* log_stream; SRT_LOG_HANDLER_FN* loghandler_fn; void* loghandler_opaque; - srt::sync::Mutex mutex; + mutable srt::sync::Mutex mutex; int flags; - std::vector loggers; LogConfig(const fa_bitset_t& efa, LogLevel::type l = LogLevel::warning, @@ -135,14 +132,10 @@ struct LogConfig } SRT_ATTR_ACQUIRE(mutex) - void lock() { mutex.lock(); } + void lock() const { mutex.lock(); } SRT_ATTR_RELEASE(mutex) - void unlock() { mutex.unlock(); } - - void subscribe(LogDispatcher*); - void unsubscribe(LogDispatcher*); - void updateLoggersState(); + void unlock() const { mutex.unlock(); } }; // The LogDispatcher class represents the object that is responsible for @@ -154,7 +147,6 @@ struct SRT_API LogDispatcher LogLevel::type level; static const size_t MAX_PREFIX_SIZE = 32; char prefix[MAX_PREFIX_SIZE+1]; - bool enabled; LogConfig* src_config; bool isset(int flg) { return (src_config->flags & flg) != 0; } @@ -165,7 +157,6 @@ struct SRT_API LogDispatcher const char* logger_pfx /*[[nullable]]*/, LogConfig& config): fa(functional_area), level(log_level), - enabled(false), src_config(&config) { // XXX stpcpy desired, but not enough portable @@ -193,18 +184,13 @@ struct SRT_API LogDispatcher prefix[MAX_PREFIX_SIZE] = '\0'; #endif } - config.subscribe(this); - Update(); } ~LogDispatcher() { - src_config->unsubscribe(this); } - void Update(); - - bool CheckEnabled() { return enabled; } + bool CheckEnabled(); void CreateLogLinePrefix(std::ostringstream&); void SendLogLine(const char* file, int line, const std::string& area, const std::string& sl); @@ -217,16 +203,16 @@ struct SRT_API LogDispatcher template void PrintLogLine(const char* file, int line, const std::string& area, Args&&... args); - template - void operator()(Arg1&& arg1, Args&&... args) + template + void operator()(Args&&... args) { - PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg1, args...); + PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", args...); } - template - void printloc(const char* file, int line, const std::string& area, Arg1&& arg1, Args&&... args) + template + void printloc(const char* file, int line, const std::string& area, Args&&... args) { - PrintLogLine(file, line, area, arg1, args...); + PrintLogLine(file, line, area, args...); } #else template @@ -428,6 +414,24 @@ class Logger } }; +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 @@ -438,7 +442,7 @@ inline void PrintArgs(std::ostream&) {} template inline void PrintArgs(std::ostream& serr, Arg1&& arg1, Args&&... args) { - serr << arg1; + serr << std::forward(arg1); PrintArgs(serr, args...); } @@ -478,7 +482,24 @@ inline void LogDispatcher::PrintLogLine(const char* file SRT_ATR_UNUSED, int lin #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) << msg; + (*src_config->log_stream).flush(); + } + src_config->unlock(); } -#endif // INC_SRT_LOGGING_H +} +#endif // INC_SRT_LOGGING_H diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index fbb56a42c..180623039 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -159,7 +159,7 @@ modified by // the original sequence numbers in the field. #include "platform_sys.h" - +#include #include #include "packet.h" #include "handshake.h" @@ -179,10 +179,6 @@ CPacket::CPacket() : m_nHeader() // Silences GCC 12 warning "used uninitialized". , m_extra_pad() , m_data_owned(false) - , m_iSeqNo((int32_t&)(m_nHeader[SRT_PH_SEQNO])) - , m_iMsgNo((int32_t&)(m_nHeader[SRT_PH_MSGNO])) - , m_iTimeStamp((int32_t&)(m_nHeader[SRT_PH_TIMESTAMP])) - , m_iID((int32_t&)(m_nHeader[SRT_PH_ID])) , m_pcData((char*&)(m_PacketVector[PV_DATA].dataRef())) { m_nHeader.clear(); @@ -436,38 +432,29 @@ void CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, } } -void CPacket::toNL() +void CPacket::toNetworkByteOrder() { - // XXX USE HtoNLA! + // The payload of data packet should remain in network byte order. if (isControl()) { - for (ptrdiff_t i = 0, n = getLength() / 4; i < n; ++i) - *((uint32_t*)m_pcData + i) = htonl(*((uint32_t*)m_pcData + i)); + HtoNLA((uint32_t*) m_pcData, (const uint32_t*) m_pcData, getLength() / 4); } - // convert packet header into network order + // Convert packet header independent of packet type. uint32_t* p = m_nHeader; - for (int j = 0; j < 4; ++j) - { - *p = htonl(*p); - ++p; - } + HtoNLA(p, p, 4); } -void CPacket::toHL() +void CPacket::toHostByteOrder() { - // convert back into local host order + // Convert packet header independent of packet type. uint32_t* p = m_nHeader; - for (int k = 0; k < 4; ++k) - { - *p = ntohl(*p); - ++p; - } + NtoHLA(p, p, 4); + // The payload of data packet should remain in network byte order. if (isControl()) { - for (ptrdiff_t l = 0, n = getLength() / 4; l < n; ++l) - *((uint32_t*)m_pcData + l) = ntohl(*((uint32_t*)m_pcData + l)); + NtoHLA((uint32_t*)m_pcData, (const uint32_t*)m_pcData, getLength() / 4); } } @@ -601,7 +588,7 @@ inline void SprintSpecialWord(std::ostream& os, int32_t val) std::string CPacket::Info() { std::ostringstream os; - os << "TARGET=@" << m_iID << " "; + os << "TARGET=@" << id() << " "; if (isControl()) { diff --git a/srtcore/packet.h b/srtcore/packet.h index 027d5f0b3..5a6d6eb15 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -74,6 +74,8 @@ class IOVector #endif { public: + IOVector() { set(NULL, 0); } + inline void set(void* buffer, size_t length) { #ifdef _WIN32 @@ -331,8 +333,10 @@ class CPacket }; public: - void toNL(); - void toHL(); + /// @brief Convert the packet inline to a network byte order (Little-endian). + void toNetworkByteOrder(); + /// @brief Convert the packet inline to a host byte order. + void toHostByteOrder(); protected: // DynamicStruct is the same as array of given type and size, just it @@ -352,12 +356,15 @@ class CPacket CPacket(const CPacket&); public: - int32_t& m_iSeqNo; // alias: sequence number - int32_t& m_iMsgNo; // alias: message number - int32_t& m_iTimeStamp; // alias: timestamp - int32_t& m_iID; // alias: destination SRT socket ID char*& m_pcData; // alias: payload (data packet) / control information fields (control packet) + SRTU_PROPERTY_RO(SRTSOCKET, id, SRTSOCKET(m_nHeader[SRT_PH_ID])); + SRTU_PROPERTY_WO_ARG(SRTSOCKET, id, m_nHeader[SRT_PH_ID] = int32_t(arg)); + + SRTU_PROPERTY_RW(int32_t, seqno, m_nHeader[SRT_PH_SEQNO]); + SRTU_PROPERTY_RW(int32_t, msgflags, m_nHeader[SRT_PH_MSGNO]); + SRTU_PROPERTY_RW(int32_t, timestamp, m_nHeader[SRT_PH_TIMESTAMP]); + // Experimental: sometimes these references don't work! char* getData(); char* release(); diff --git a/srtcore/packetfilter.cpp b/srtcore/packetfilter.cpp index 37785f43a..dc7e5b422 100644 --- a/srtcore/packetfilter.cpp +++ b/srtcore/packetfilter.cpp @@ -226,7 +226,7 @@ bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_pack // - Crypto // - Message Number // will be set to 0/false - w_packet.m_iMsgNo = SRT_MSGNO_CONTROL | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); + w_packet.set_msgflags(SRT_MSGNO_CONTROL | MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO)); // ... and then fix only the Crypto flags w_packet.setMsgCryptoFlags(EncryptionKeySpec(kflg)); @@ -314,9 +314,15 @@ bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::strin init.socket_id = parent->socketID(); init.snd_isn = parent->sndSeqNo(); init.rcv_isn = parent->rcvSeqNo(); - init.payload_size = parent->OPT_PayloadSize(); + + // 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.rcvbuf_size = parent->m_config.iRcvBufSize; + HLOGC(pflog.Debug, log << "PFILTER: @" << init.socket_id << " payload size=" + << init.payload_size << " rcvbuf size=" << init.rcvbuf_size); + // Found a filter, so call the creation function m_filter = selector->second->Create(init, m_provided, confstr); if (!m_filter) diff --git a/srtcore/platform_sys.h b/srtcore/platform_sys.h index e2f0aa4d9..83763e5ea 100644 --- a/srtcore/platform_sys.h +++ b/srtcore/platform_sys.h @@ -21,6 +21,20 @@ // // SRT_IMPORT_TIME (mach time on Mac, portability gettimeofday on WIN32) // SRT_IMPORT_EVENT (includes kevent on Mac) +#ifdef _WIN32 + #ifndef __MINGW32__ + // Explicitly define 32-bit and 64-bit numbers + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int32 uint32_t; + #ifndef LEGACY_WIN32 + typedef unsigned __int64 uint64_t; + #else + // VC 6.0 does not support unsigned __int64: may cause potential problems. + typedef __int64 uint64_t; + #endif + #endif +#endif #ifdef _WIN32 diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index cf0016901..6cb4faeb1 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -444,7 +444,7 @@ int srt::CSndQueue::sockoptQuery(int level, int type) const } #if ENABLE_LOGGING -int srt::CSndQueue::m_counter = 0; +srt::sync::atomic srt::CSndQueue::m_counter(0); #endif void srt::CSndQueue::init(CChannel* c, CTimer* t) @@ -504,11 +504,9 @@ void* srt::CSndQueue::worker(void* param) { CSndQueue* self = (CSndQueue*)param; -#if ENABLE_LOGGING - THREAD_STATE_INIT(("SRT:SndQ:w" + Sprint(m_counter)).c_str()); -#else - THREAD_STATE_INIT("SRT:SndQ:worker"); -#endif + std::string thname; + ThreadName::get(thname); + THREAD_STATE_INIT(thname.c_str()); #if defined(SRT_DEBUG_SNDQ_HIGHRATE) #define IF_DEBUG_HIGHRATE(statement) statement @@ -576,6 +574,13 @@ void* srt::CSndQueue::worker(void* param) 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; + } + // pack a packet from the socket CPacket pkt; steady_clock::time_point next_send_time; @@ -900,7 +905,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst // Need a stub value for a case when there's no unit provided ("storage depleted" case). // It should be normally NOT IN USE because in case of "storage depleted", rst != RST_OK. - const SRTSOCKET dest_id = pkt ? pkt->m_iID : 0; + const SRTSOCKET dest_id = pkt ? pkt->id() : 0; // If no socket were qualified for further handling, finish here. // Otherwise toRemove and toProcess contain items to handle. @@ -931,6 +936,16 @@ 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; + } + + if (cst != CONN_RENDEZVOUS && dest_id != 0) { if (i->id != dest_id) @@ -976,14 +991,22 @@ 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. // // TODO: maybe lock i->u->m_ConnectionLock? i->u->m_bConnecting = false; - remove(i->u->m_SocketID); // DO NOT close the socket here because in this case it might be // unable to get status from at the right moment. Also only member @@ -994,6 +1017,11 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst CUDT::uglobal().m_EPoll.update_events( i->u->m_SocketID, i->u->m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true); + // Make sure that the socket wasn't deleted in the meantime. + // Skip this part if it was. Note also that if the socket was + // decided to be deleted, it's already moved to m_ClosedSockets + // and should have been therefore already processed for deletion. + i->u->completeBrokenConnectionDependencies(i->errorcode); } @@ -1092,8 +1120,8 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, if ((rst == RST_AGAIN || i->m_iID != iDstSockID) && tsNow <= tsRepeat) { HLOGC(cnlog.Debug, - log << "RID:@" << i->m_iID << std::fixed << count_microseconds(tsNow - tsLastReq) / 1000.0 - << " ms passed since last connection request."); + log << "RID:@" << i->m_iID << " " << FormatDuration(tsNow - tsLastReq) + << " passed since last connection request."); continue; } @@ -1129,8 +1157,6 @@ srt::CRcvQueue::CRcvQueue() , m_iIPversion() , m_szPayloadSize() , m_bClosing(false) - , m_LSLock() - , m_pListener(NULL) , m_pRendezvousQueue(NULL) , m_vNewEntry() , m_IDLock() @@ -1208,11 +1234,9 @@ void* srt::CRcvQueue::worker(void* param) sockaddr_any sa(self->getIPversion()); int32_t id = 0; -#if ENABLE_LOGGING - THREAD_STATE_INIT(("SRT:RcvQ:w" + Sprint(m_counter)).c_str()); -#else - THREAD_STATE_INIT("SRT:RcvQ:worker"); -#endif + std::string thname; + ThreadName::get(thname); + THREAD_STATE_INIT(thname.c_str()); CUnit* unit = 0; EConnectStatus cst = CONN_AGAIN; @@ -1385,7 +1409,7 @@ srt::EReadStatus srt::CRcvQueue::worker_RetrieveUnit(int32_t& w_id, CUnit*& w_un if (rst == RST_OK) { - w_id = w_unit->m_Packet.m_iID; + w_id = w_unit->m_Packet.id(); HLOGC(qrlog.Debug, log << "INCOMING PACKET: FROM=" << w_addr.str() << " BOUND=" << m_pChannel->bindAddressAny().str() << " " << w_unit->m_Packet.Info()); @@ -1404,11 +1428,13 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, int listener_ret = SRT_REJ_UNKNOWN; bool have_listener = false; { - ScopedLock cg(m_LSLock); - if (m_pListener) + SharedLock shl(m_pListener); + CUDT* pListener = m_pListener.getPtrNoLock(); + + if (pListener) { - LOGC(cnlog.Note, log << "PASSING request from: " << addr.str() << " to agent:" << m_pListener->socketID()); - listener_ret = m_pListener->processConnectRequest(addr, unit->m_Packet); + LOGC(cnlog.Debug, log << "PASSING request from: " << addr.str() << " to listener:" << pListener->socketID()); + listener_ret = pListener->processConnectRequest(addr, unit->m_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 @@ -1426,8 +1452,8 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, if (have_listener) // That is, the above block with m_pListener->processConnectRequest was executed { - LOGC(cnlog.Note, - log << CONID() << "Listener managed the connection request from: " << addr.str() + LOGC(cnlog.Debug, + log << CONID() << "Listener got the connection request from: " << addr.str() << " result:" << RequestTypeStr(UDTRequestType(listener_ret))); return listener_ret == SRT_REJ_UNKNOWN ? CONN_CONTINUE : CONN_REJECT; } @@ -1446,6 +1472,12 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CU HLOGC(cnlog.Debug, log << "worker_ProcessAddressedPacket: resending to QUEUED socket @" << id); return worker_TryAsyncRend_OrStore(id, unit, addr); } + // 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. @@ -1690,21 +1722,15 @@ int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet) int srt::CRcvQueue::setListener(CUDT* u) { - ScopedLock lslock(m_LSLock); - - if (NULL != m_pListener) + if (!m_pListener.set(u)) return -1; - m_pListener = u; return 0; } void srt::CRcvQueue::removeListener(const CUDT* u) { - ScopedLock lslock(m_LSLock); - - if (u == m_pListener) - m_pListener = NULL; + m_pListener.clearIf(u); } void srt::CRcvQueue::registerConnector(const SRTSOCKET& id, diff --git a/srtcore/queue.h b/srtcore/queue.h index dd68a7721..132b670b3 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -469,7 +469,7 @@ class CSndQueue private: #if ENABLE_LOGGING - static int m_counter; + static srt::sync::atomic m_counter; #endif CSndQueue(const CSndQueue&); @@ -554,9 +554,8 @@ class CRcvQueue void storePktClone(int32_t id, const CPacket& pkt); private: - sync::Mutex m_LSLock; - CUDT* m_pListener; // pointer to the (unique, if any) listening UDT entity - CRendezvousQueue* m_pRendezvousQueue; // The list of sockets in rendezvous mode + 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; diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index 8708e90a1..1c067d059 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -292,10 +292,8 @@ struct CSrtConfigSetter using namespace std; string val; - if (optlen == -1) - val = (const char *)optval; - else - val.assign((const char *)optval, optlen); + + val.assign((const char *)optval, optlen); if (val.size() >= IFNAMSIZ) { LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE: device name too long (max: IFNAMSIZ=" << IFNAMSIZ << ")"); @@ -561,15 +559,6 @@ struct CSrtConfigSetter } }; -template<> -struct CSrtConfigSetter -{ - static void set(CSrtConfig& co, const void* optval, int optlen) - { - co.uSrtVersion = cast_optval(optval, optlen); - } -}; - template<> struct CSrtConfigSetter { @@ -597,10 +586,7 @@ struct CSrtConfigSetter static void set(CSrtConfig& co, const void* optval, int optlen) { std::string val; - if (optlen == -1) - val = (const char*)optval; - else - val.assign((const char*)optval, optlen); + val.assign((const char*)optval, optlen); // Translate alias if (val == "vod") @@ -976,7 +962,6 @@ int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int opt DISPATCH(SRTO_CONNTIMEO); DISPATCH(SRTO_DRIFTTRACER); DISPATCH(SRTO_LOSSMAXTTL); - DISPATCH(SRTO_VERSION); DISPATCH(SRTO_MINVERSION); DISPATCH(SRTO_STREAMID); DISPATCH(SRTO_CONGESTION); diff --git a/srtcore/srt.h b/srtcore/srt.h index 53b6fd274..614a85aea 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -16,6 +16,22 @@ written by #ifndef INC_SRTC_H #define INC_SRTC_H +#ifndef SRT_API +#ifdef _WIN32 + #ifdef SRT_DYNAMIC + #ifdef SRT_EXPORTS + #define SRT_API __declspec(dllexport) + #else + #define SRT_API __declspec(dllimport) + #endif + #else // !SRT_DYNAMIC + #define SRT_API + #endif +#else + #define SRT_API __attribute__ ((visibility("default"))) +#endif +#endif + #include "version.h" #include "platform_sys.h" @@ -33,34 +49,6 @@ written by //if compiling with MinGW, it only works on XP or above //use -D_WIN32_WINNT=0x0501 - -#ifdef _WIN32 - #ifndef __MINGW32__ - // Explicitly define 32-bit and 64-bit numbers - typedef __int32 int32_t; - typedef __int64 int64_t; - typedef unsigned __int32 uint32_t; - #ifndef LEGACY_WIN32 - typedef unsigned __int64 uint64_t; - #else - // VC 6.0 does not support unsigned __int64: may cause potential problems. - typedef __int64 uint64_t; - #endif - #endif - #ifdef SRT_DYNAMIC - #ifdef SRT_EXPORTS - #define SRT_API __declspec(dllexport) - #else - #define SRT_API __declspec(dllimport) - #endif - #else // !SRT_DYNAMIC - #define SRT_API - #endif -#else - #define SRT_API __attribute__ ((visibility("default"))) -#endif - - // For feature tests if you need. // You can use these constants with SRTO_MINVERSION option. #define SRT_VERSION_FEAT_HSv5 0x010300 diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index 84daabeb1..726c4a03b 100644 --- a/srtcore/srt_attr_defs.h +++ b/srtcore/srt_attr_defs.h @@ -31,6 +31,14 @@ used by SRT library internally. #define ATR_DEPRECATED #endif +#if HAVE_CXX11 +#define SRT_ATR_ALIGNAS(n) alignas(n) +#elif HAVE_GCC +#define SRT_ATR_ALIGNAS(n) __attribute__((aligned(n))) +#else +#define SRT_ATR_ALIGNAS(n) +#endif + #if defined(__cplusplus) && __cplusplus > 199711L #define HAVE_CXX11 1 // For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on, @@ -110,7 +118,7 @@ used by SRT library internally. #define SRT_ATTR_RELEASE_GENERIC(...) #define SRT_ATTR_TRY_ACQUIRE(...) _Acquires_nonreentrant_lock_(expr) #define SRT_ATTR_TRY_ACQUIRE_SHARED(...) -#define SRT_ATTR_EXCLUDES(...) +#define SRT_ATTR_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) @@ -171,6 +179,7 @@ used by SRT library internally. #define SRT_ATTR_TRY_ACQUIRE_SHARED(...) \ THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__)) +// The caller must not hold the given capabilities. #define SRT_ATTR_EXCLUDES(...) \ THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) diff --git a/srtcore/srt_compat.c b/srtcore/srt_compat.c index fbf4859ae..bbdf7f795 100644 --- a/srtcore/srt_compat.c +++ b/srtcore/srt_compat.c @@ -17,7 +17,6 @@ written by // Prevents from misconfiguration through preprocessor. #include "platform_sys.h" - #include #include diff --git a/srtcore/srt_compat.h b/srtcore/srt_compat.h index 960c1b85a..d4fd2361e 100644 --- a/srtcore/srt_compat.h +++ b/srtcore/srt_compat.h @@ -20,55 +20,12 @@ written by #include #include -#ifndef SRT_API -#ifdef _WIN32 - #ifndef __MINGW32__ - #ifdef SRT_DYNAMIC - #ifdef SRT_EXPORTS - #define SRT_API __declspec(dllexport) - #else - #define SRT_API __declspec(dllimport) - #endif - #else - #define SRT_API - #endif - #else - #define SRT_API - #endif -#else - #define SRT_API __attribute__ ((visibility("default"))) -#endif -#endif - -#ifdef _WIN32 - // https://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx - // printf() Format for ssize_t - #if !defined(PRIzd) - #define PRIzd "Id" - #endif - // printf() Format for size_t - #if !defined(PRIzu) - #define PRIzu "Iu" - #endif -#else - // http://www.gnu.org/software/libc/manual/html_node/Integer-Conversions.html - // printf() Format for ssize_t - #if !defined(PRIzd) - #define PRIzd "zd" - #endif - // printf() Format for size_t - #if !defined(PRIzu) - #define PRIzu "zu" - #endif -#endif - - #ifdef __cplusplus extern "C" { #endif /* Ensures that we store the error in the buffer and return the bufer. */ -SRT_API const char * SysStrError(int errnum, char * buf, size_t buflen); +const char * SysStrError(int errnum, char * buf, size_t buflen); #ifdef __cplusplus } // extern C diff --git a/srtcore/sync.cpp b/srtcore/sync.cpp index a7cebb909..bfe153657 100644 --- a/srtcore/sync.cpp +++ b/srtcore/sync.cpp @@ -357,3 +357,98 @@ int srt::sync::genRandomInt(int minVal, int maxVal) #endif // HAVE_CXX11 } + +//////////////////////////////////////////////////////////////////////////////// +// +// Shared Mutex +// +//////////////////////////////////////////////////////////////////////////////// + +srt::sync::SharedMutex::SharedMutex() + : m_LockWriteCond() + , m_LockReadCond() + , m_Mutex() + , m_iCountRead(0) + , m_bWriterLocked(false) +{ + setupCond(m_LockReadCond, "SharedMutex::m_pLockReadCond"); + setupCond(m_LockWriteCond, "SharedMutex::m_pLockWriteCond"); + setupMutex(m_Mutex, "SharedMutex::m_pMutex"); +} + +srt::sync::SharedMutex::~SharedMutex() +{ + releaseMutex(m_Mutex); + releaseCond(m_LockWriteCond); + releaseCond(m_LockReadCond); +} + +void srt::sync::SharedMutex::lock() +{ + UniqueLock l1(m_Mutex); + while (m_bWriterLocked) + m_LockWriteCond.wait(l1); + + m_bWriterLocked = true; + + while (m_iCountRead) + m_LockReadCond.wait(l1); +} + +bool srt::sync::SharedMutex::try_lock() +{ + UniqueLock l1(m_Mutex); + if (m_bWriterLocked || m_iCountRead > 0) + return false; + + m_bWriterLocked = true; + return true; +} + +void srt::sync::SharedMutex::unlock() +{ + ScopedLock lk(m_Mutex); + m_bWriterLocked = false; + + m_LockWriteCond.notify_all(); +} + +void srt::sync::SharedMutex::lock_shared() +{ + UniqueLock lk(m_Mutex); + while (m_bWriterLocked) + m_LockWriteCond.wait(lk); + + m_iCountRead++; +} + +bool srt::sync::SharedMutex::try_lock_shared() +{ + UniqueLock lk(m_Mutex); + if (m_bWriterLocked) + return false; + + m_iCountRead++; + return true; +} + +void srt::sync::SharedMutex::unlock_shared() +{ + ScopedLock lk(m_Mutex); + + m_iCountRead--; + + SRT_ASSERT(m_iCountRead >= 0); + if (m_iCountRead < 0) + m_iCountRead = 0; + + if (m_bWriterLocked && m_iCountRead == 0) + m_LockReadCond.notify_one(); + +} + +int srt::sync::SharedMutex::getReaderCount() const +{ + ScopedLock lk(m_Mutex); + return m_iCountRead; +} \ No newline at end of file diff --git a/srtcore/sync.h b/srtcore/sync.h index fb6d56432..9d282304c 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -343,15 +343,19 @@ class SRT_ATTR_CAPABILITY("mutex") Mutex pthread_mutex_t m_mutex; }; -/// A pthread version of std::chrono::scoped_lock (or lock_guard for C++11) +/// A pthread version of std::scoped_lock (or lock_guard for C++11). class SRT_ATTR_SCOPED_CAPABILITY ScopedLock { public: SRT_ATTR_ACQUIRE(m) - explicit ScopedLock(Mutex& m); + explicit ScopedLock(Mutex& m) + : m_mutex(m) + { + m_mutex.lock(); + } SRT_ATTR_RELEASE() - ~ScopedLock(); + ~ScopedLock() { m_mutex.unlock(); } private: Mutex& m_mutex; @@ -481,6 +485,122 @@ class Condition inline void setupCond(Condition& cv, const char*) { cv.init(); } inline void releaseCond(Condition& cv) { cv.destroy(); } +/////////////////////////////////////////////////////////////////////////////// +// +// Shared Mutex section +// +/////////////////////////////////////////////////////////////////////////////// + +/// Implementation of a read-write mutex. +/// This allows multiple readers at a time, or a single writer. +/// TODO: The class can be improved if needed to give writer a preference +/// by adding additional m_iWritersWaiting member variable (counter). +/// TODO: The m_iCountRead could be made atomic to make unlok_shared() faster and lock-free. +class SharedMutex +{ +public: + SharedMutex(); + ~SharedMutex(); + +public: + /// Acquire the lock for writting purposes. Only one thread can acquire this lock at a time + /// Once it is locked, no reader can acquire it + void lock(); + bool try_lock(); + void unlock(); + + /// Acquire the lock if no writter already has it. For read purpose only + /// Several readers can lock this at the same time. + void lock_shared(); + bool try_lock_shared(); + void unlock_shared(); + + int getReaderCount() const; + +protected: + Condition m_LockWriteCond; + Condition m_LockReadCond; + + mutable Mutex m_Mutex; + + int m_iCountRead; + bool m_bWriterLocked; +}; + +/// A version of std::scoped_lock (or lock_guard for C++11). +/// We could have used the srt::sync::ScopedLock making it a template-based class. +/// But in that case all usages would have to be specificed like ScopedLock in C++03. +class SRT_ATTR_SCOPED_CAPABILITY ExclusiveLock +{ +public: + SRT_ATTR_ACQUIRE(m) + explicit ExclusiveLock(SharedMutex& m) + : m_mutex(m) + { + m_mutex.lock(); + } + + SRT_ATTR_RELEASE(m_mutex) + ~ExclusiveLock() { m_mutex.unlock(); } + +private: + SharedMutex& m_mutex; +}; + +/// A reduced implementation of the std::shared_lock functionality (available in C++14). +class SRT_ATTR_SCOPED_CAPABILITY SharedLock +{ +public: + SRT_ATTR_ACQUIRE_SHARED(m) + explicit SharedLock(SharedMutex& m) + : m_mtx(m) + { + m_mtx.lock_shared(); + } + + SRT_ATTR_RELEASE_SHARED(m_mtx) + ~SharedLock() { m_mtx.unlock_shared(); } + +private: + SharedMutex& m_mtx; +}; + +/// A class template for a shared object. It is a wrapper around a pointer to an object +/// and a shared mutex. It allows multiple readers to access the object at the same time, +/// but only one writer can access the object at a time. +template +class CSharedObjectPtr : public SharedMutex +{ +public: + CSharedObjectPtr() + : m_pObj(NULL) + { + } + + bool set(T* pObj) + { + ExclusiveLock lock(*this); + if (m_pObj) + return false; + m_pObj = pObj; + return true; + } + + bool clearIf(const T* pObj) + { + ExclusiveLock lock(*this); + if (m_pObj != pObj) + return false; + m_pObj = NULL; + return true; + } + + T* getPtrNoLock() const { return m_pObj; } + +private: + T* m_pObj; +}; + /////////////////////////////////////////////////////////////////////////////// // // Event (CV) section diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp index 8cb475ea7..8d7561a19 100644 --- a/srtcore/sync_posix.cpp +++ b/srtcore/sync_posix.cpp @@ -230,18 +230,6 @@ bool srt::sync::Mutex::try_lock() return (pthread_mutex_trylock(&m_mutex) == 0); } -srt::sync::ScopedLock::ScopedLock(Mutex& m) - : m_mutex(m) -{ - m_mutex.lock(); -} - -srt::sync::ScopedLock::~ScopedLock() -{ - m_mutex.unlock(); -} - - srt::sync::UniqueLock::UniqueLock(Mutex& m) : m_Mutex(m) { diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 258a2fdc8..1786cf0ae 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -35,6 +35,7 @@ written by #include #include #include +#include #if HAVE_CXX11 #include @@ -236,17 +237,20 @@ written by #endif -// Hardware <--> Network (big endian) convention +/// Hardware --> Network (big-endian) byte order conversion +/// @param size source length in four octets inline void HtoNLA(uint32_t* dst, const uint32_t* src, size_t size) { for (size_t i = 0; i < size; ++ i) - dst[i] = htonl(src[i]); + dst[i] = htobe32(src[i]); } +/// Network (big-endian) --> Hardware byte order conversion +/// @param size source length in four octets inline void NtoHLA(uint32_t* dst, const uint32_t* src, size_t size) { for (size_t i = 0; i < size; ++ i) - dst[i] = ntohl(src[i]); + dst[i] = be32toh(src[i]); } // Hardware <--> Intel (little endian) convention @@ -575,7 +579,7 @@ inline Stream& Print(Stream& in) { return in;} template inline Stream& Print(Stream& sout, Arg1&& arg1, Args&&... args) { - sout << arg1; + sout << std::forward(arg1); return Print(sout, args...); } @@ -682,7 +686,7 @@ class UniquePtr: public std::auto_ptr bool operator==(const element_type* two) const { return get() == two; } bool operator!=(const element_type* two) const { return get() != two; } - operator bool () { return 0!= get(); } + operator bool () const { return 0!= get(); } }; // A primitive one-argument versions of Sprint and Printable @@ -1280,6 +1284,7 @@ inline ValueType avg_iir_w(ValueType old_value, ValueType new_value, size_t new_ #define SRTU_PROPERTY_RR(type, name, field) type name() { return field; } #define SRTU_PROPERTY_RO(type, name, field) type name() const { return field; } #define SRTU_PROPERTY_WO(type, name, field) void set_##name(type arg) { field = arg; } +#define SRTU_PROPERTY_WO_ARG(type, name, expr) void set_##name(type arg) { expr; } #define SRTU_PROPERTY_WO_CHAIN(otype, type, name, field) otype& set_##name(type arg) { field = arg; return *this; } #define SRTU_PROPERTY_RW(type, name, field) SRTU_PROPERTY_RO(type, name, field); SRTU_PROPERTY_WO(type, name, field) #define SRTU_PROPERTY_RRW(type, name, field) SRTU_PROPERTY_RR(type, name, field); SRTU_PROPERTY_WO(type, name, field) diff --git a/srtcore/window.h b/srtcore/window.h index 132440368..dbe4b7179 100644 --- a/srtcore/window.h +++ b/srtcore/window.h @@ -244,7 +244,7 @@ class CPktTimeWindow: CPktTimeWindowTools void probeArrival(const CPacket& pkt, bool unordered) { SRT_ASSERT(m_zHeaderSize != 0 && m_zPayloadSize != 0); - const int inorder16 = pkt.m_iSeqNo & PUMASK_SEQNO_PROBE; + const int inorder16 = pkt.seqno() & PUMASK_SEQNO_PROBE; // for probe1, we want 16th packet if (inorder16 == 0) @@ -266,7 +266,7 @@ class CPktTimeWindow: CPktTimeWindowTools void probe1Arrival(const CPacket& pkt, bool unordered) { SRT_ASSERT(m_zHeaderSize != 0 && m_zPayloadSize != 0); - if (unordered && pkt.m_iSeqNo == m_Probe1Sequence) + if (unordered && pkt.seqno() == m_Probe1Sequence) { // Reset the starting probe into "undefined", when // a packet has come as retransmitted before the @@ -276,7 +276,7 @@ class CPktTimeWindow: CPktTimeWindowTools } m_tsProbeTime = sync::steady_clock::now(); - m_Probe1Sequence = pkt.m_iSeqNo; // Record the sequence where 16th packet probe was taken + m_Probe1Sequence = pkt.seqno(); // Record the sequence where 16th packet probe was taken } /// Record the arrival time of the second probing packet and the interval between packet pairs. @@ -292,7 +292,7 @@ class CPktTimeWindow: CPktTimeWindowTools // expected packet pair, behave as if the 17th packet was lost. // no start point yet (or was reset) OR not very next packet - if (m_Probe1Sequence == SRT_SEQNO_NONE || CSeqNo::incseq(m_Probe1Sequence) != pkt.m_iSeqNo) + if (m_Probe1Sequence == SRT_SEQNO_NONE || CSeqNo::incseq(m_Probe1Sequence) != pkt.seqno()) return; // Grab the current time before trying to acquire diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index a03b7c5a0..f40a52eb5 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -330,3 +330,157 @@ TEST(Bonding, CloseGroupAndSocket) listen_promise.wait(); } +TEST(Bonding, Options) +{ + using namespace std; + using namespace srt; + + TestInit srtinit; + + // Create a group + const SRTSOCKET grp = srt_create_group(SRT_GTYPE_BROADCAST); + + // rendezvous shall not be allowed to be set on the group + // XXX actually it is possible, but no one tested it. POSTPONE. + //int yes = 1; + //EXPECT_EQ(srt_setsockflag(grp, SRTO_RENDEZVOUS, &yes, sizeof yes), SRT_ERROR); + +#ifdef SRT_ENABLE_ENCRYPTION + string pass = "longenoughpassword"; + // passphrase should be ok. + EXPECT_NE(srt_setsockflag(grp, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR); + + uint32_t val = 16; + EXPECT_NE(srt_setsockflag(grp, SRTO_PBKEYLEN, &val, sizeof val), SRT_ERROR); + +#ifdef ENABLE_AEAD_API_PREVIEW + val = 1; + EXPECT_NE(srt_setsockflag(grp, SRTO_CRYPTOMODE, &val, sizeof val), SRT_ERROR); +#endif +#endif + + int lat = 500; + EXPECT_NE(srt_setsockflag(grp, SRTO_RCVLATENCY, &lat, sizeof lat), SRT_ERROR); + + mutex mx; + condition_variable latch; + atomic started {false}; + + thread accept_and_close { [&]() { + + unique_lock ux(mx); + + SRTSOCKET lsn = srt_create_socket(); +#ifdef SRT_ENABLE_ENCRYPTION + EXPECT_NE(srt_setsockflag(lsn, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR); +#endif + int allow = 1; + ASSERT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR); + sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET); + ASSERT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR); + ASSERT_NE(srt_listen(lsn, 1), SRT_ERROR); + started = true; + + // First wait - until it's let go with accepting + latch.wait(ux); + + sockaddr_any revsa; + SRTSOCKET gs = srt_accept(lsn, revsa.get(), &revsa.len); + ASSERT_NE(gs, SRT_ERROR); + + // Connected, wait to close + latch.wait(ux); + + srt_close(gs); + srt_close(lsn); + }}; + + // Give the thread a chance to start + this_thread::yield(); + + while (!started) + { + // In case of a bad luck, just wait for the thread to + // acquire the mutex before you do + this_thread::sleep_for(chrono::milliseconds(10)); + } + + // Wait for the possibility to connect + { + // Make sure that the thread reached the wait() call. + unique_lock ux(mx); + latch.notify_all(); + } + + // Now the thread is accepting, so we call the connect. + sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET); + SRTSOCKET member = srt_connect(grp, sa.get(), sa.size()); + + // We've released the mutex and signaled the CV, so accept should proceed now. + // Exit from srt_connect() means also exit from srt_accept(). + + EXPECT_NE(member, SRT_INVALID_SOCK); + + // conenct_res should be a socket + EXPECT_NE(member, 0); // XXX Change to SRT_SOCKID_CONNREQ + + // Now get the option value from the group + + int revlat = -1; + int optsize = sizeof revlat; + EXPECT_NE(srt_getsockflag(grp, SRTO_RCVLATENCY, &revlat, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, (int) sizeof revlat); + EXPECT_EQ(revlat, 500); + + revlat = -1; + optsize = sizeof revlat; + // Expect the same value set on the member socket + EXPECT_NE(srt_getsockflag(member, SRTO_RCVLATENCY, &revlat, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, (int) sizeof revlat); + EXPECT_EQ(revlat, 500); + + // Individual socket option modified on group + int ohead = 12; + optsize = sizeof ohead; + EXPECT_NE(srt_setsockflag(grp, SRTO_OHEADBW, &ohead, optsize), SRT_ERROR); + + // Modifyting a post-option should be possible on a socket + ohead = 11; + optsize = sizeof ohead; + EXPECT_NE(srt_setsockflag(member, SRTO_OHEADBW, &ohead, optsize), SRT_ERROR); + + // But getting the option value should be equal to the group setting + EXPECT_NE(srt_getsockflag(grp, SRTO_OHEADBW, &ohead, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, (int) sizeof ohead); + EXPECT_EQ(ohead, 12); + +#if SRT_ENABLE_ENCRYPTION + + uint32_t kms = -1; + + EXPECT_NE(srt_getsockflag(grp, SRTO_KMSTATE, &kms, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, (int) sizeof kms); + EXPECT_EQ(kms, int(SRT_KM_S_SECURED)); + + EXPECT_NE(srt_getsockflag(grp, SRTO_PBKEYLEN, &kms, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, (int) sizeof kms); + EXPECT_EQ(kms, 16); + +#ifdef ENABLE_AEAD_API_PREVIEW + EXPECT_NE(srt_getsockflag(grp, SRTO_CRYPTOMODE, &kms, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, sizeof kms); + EXPECT_EQ(kms, 1); +#endif +#endif + + // We're done, the thread can close connection and exit + { + // Make sure that the thread reached the wait() call. + std::unique_lock ux(mx); + latch.notify_all(); + } + + accept_and_close.join(); + srt_close(grp); +} + diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 0c67fa91c..511c2dcb1 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -54,22 +54,25 @@ class CRcvBufferReadMsg EXPECT_NE(unit, nullptr); CPacket& packet = unit->m_Packet; - packet.m_iSeqNo = seqno; - packet.m_iTimeStamp = ts; + packet.set_seqno(seqno); + packet.set_timestamp(ts); packet.setLength(m_payload_sz); - generatePayload(packet.data(), packet.getLength(), packet.m_iSeqNo); + generatePayload(packet.data(), packet.getLength(), packet.seqno()); - packet.m_iMsgNo = msgno; - packet.m_iMsgNo |= PacketBoundaryBits(PB_SUBSEQUENT); + int32_t pktMsgFlags = msgno; + pktMsgFlags |= PacketBoundaryBits(PB_SUBSEQUENT); if (pb_first) - packet.m_iMsgNo |= PacketBoundaryBits(PB_FIRST); + pktMsgFlags |= PacketBoundaryBits(PB_FIRST); if (pb_last) - packet.m_iMsgNo |= PacketBoundaryBits(PB_LAST); + pktMsgFlags |= PacketBoundaryBits(PB_LAST); + + if (!out_of_order) + pktMsgFlags |= MSGNO_PACKET_INORDER::wrap(1); + packet.set_msgflags(pktMsgFlags); if (!out_of_order) { - packet.m_iMsgNo |= MSGNO_PACKET_INORDER::wrap(1); EXPECT_TRUE(packet.getMsgOrderFlag()); } @@ -233,7 +236,7 @@ TEST_F(CRcvBufferReadMsg, OnePacketGap) { const size_t msg_bytelen = m_payload_sz; EXPECT_TRUE(rcv_buffer.isRcvDataReady()); - EXPECT_EQ(readMessage(buff.data(), buff.size()), msg_bytelen); + EXPECT_EQ(readMessage(buff.data(), buff.size()), (int) msg_bytelen); EXPECT_TRUE(verifyPayload(buff.data(), msg_bytelen, CSeqNo::incseq(m_init_seqno, pktno))); } EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); @@ -314,7 +317,7 @@ TEST_F(CRcvBufferReadMsg, PacketDropByMsgNoSeqNo) // Let's say SND does not have the very first packet of the message, // therefore seqnolo of the msg drop request starts with the second packet of the message. - EXPECT_EQ(rcv_buffer.dropMessage(CSeqNo::incseq(m_init_seqno), CSeqNo::incseq(m_init_seqno, msg_len_pkts - 1), msgno, CRcvBuffer::KEEP_EXISTING), msg_len_pkts); + EXPECT_EQ(rcv_buffer.dropMessage(CSeqNo::incseq(m_init_seqno), CSeqNo::incseq(m_init_seqno, msg_len_pkts - 1), msgno, CRcvBuffer::KEEP_EXISTING), (int) msg_len_pkts); EXPECT_FALSE(hasAvailablePackets()); EXPECT_FALSE(rcv_buffer.isRcvDataReady()); @@ -340,7 +343,7 @@ TEST_F(CRcvBufferReadMsg, OnePacket) EXPECT_TRUE(hasAvailablePackets()); const int res2 = readMessage(buff.data(), buff.size()); - EXPECT_EQ(res2, msg_bytelen); + EXPECT_EQ(res2, (int) msg_bytelen); EXPECT_TRUE(verifyPayload(buff.data(), res2, m_init_seqno)); EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); } @@ -416,7 +419,7 @@ TEST_F(CRcvBufferReadMsg, MsgAcked) EXPECT_TRUE(hasAvailablePackets()); const int res = readMessage(buff.data(), buff.size()); - EXPECT_EQ(res, msg_bytelen); + EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { const ptrdiff_t offset = i * m_payload_sz; @@ -475,7 +478,7 @@ TEST_F(CRcvBufferReadMsg, MsgHalfAck) EXPECT_TRUE(hasAvailablePackets()); const int res = readMessage(buff.data(), buff.size()); - EXPECT_EQ(res, msg_bytelen); + EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { const ptrdiff_t offset = i * m_payload_sz; @@ -499,7 +502,7 @@ TEST_F(CRcvBufferReadMsg, OutOfOrderMsgNoACK) const size_t msg_bytelen = msg_pkts * m_payload_sz; array buff; const int res = readMessage(buff.data(), buff.size()); - EXPECT_EQ(res, msg_bytelen); + EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { const ptrdiff_t offset = i * m_payload_sz; @@ -526,7 +529,7 @@ TEST_F(CRcvBufferReadMsg, OutOfOrderMsgGap) const size_t msg_bytelen = msg_pkts * m_payload_sz; array buff; const int res = readMessage(buff.data(), buff.size()); - EXPECT_EQ(res, msg_bytelen); + EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { const ptrdiff_t offset = i * m_payload_sz; @@ -595,7 +598,7 @@ TEST_F(CRcvBufferReadMsg, LongMsgReadReady) EXPECT_TRUE(hasAvailablePackets()); const int res = readMessage(buff.data(), buff.size()); - EXPECT_EQ(res, msg_bytelen); + EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { const ptrdiff_t offset = i * m_payload_sz; @@ -618,7 +621,7 @@ TEST_F(CRcvBufferReadMsg, MsgOutOfOrderDrop) const size_t msg_bytelen = msg_pkts * m_payload_sz; array buff; int res = m_rcv_buffer->readMessage(buff.data(), buff.size()); - EXPECT_EQ(res, msg_bytelen); + EXPECT_EQ(res, (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { EXPECT_TRUE(verifyPayload(buff.data() + i * m_payload_sz, m_payload_sz, msg_seqno + int(i))); @@ -657,7 +660,7 @@ TEST_F(CRcvBufferReadMsg, MsgOutOfOrderAfterInOrder) for (int msg_i = 0; msg_i < 3; ++msg_i) { EXPECT_TRUE(m_rcv_buffer->isRcvDataReady()); - EXPECT_EQ(m_rcv_buffer->readMessage(buff.data(), buff.size()), msg_bytelen); + EXPECT_EQ(m_rcv_buffer->readMessage(buff.data(), buff.size()), (int) msg_bytelen); for (size_t i = 0; i < msg_pkts; ++i) { EXPECT_TRUE(verifyPayload(buff.data() + i * m_payload_sz, m_payload_sz, int(m_init_seqno + msg_i * msg_pkts + i))); @@ -703,7 +706,7 @@ TEST_F(CRcvBufferReadMsg, OnePacketTSBPD) // Read out the first message const int read_len = m_rcv_buffer->readMessage(buff.data(), buff.size()); - EXPECT_EQ(read_len, msg_bytelen); + EXPECT_EQ(read_len, (int) msg_bytelen); EXPECT_TRUE(verifyPayload(buff.data(), read_len, m_init_seqno)); // Check the state after a packet was read @@ -765,7 +768,7 @@ TEST_F(CRcvBufferReadMsg, TSBPDGapBeforeValid) const size_t msg_bytelen = m_payload_sz; array buff; - EXPECT_EQ(readMessage(buff.data(), buff.size()), msg_bytelen); + EXPECT_EQ(readMessage(buff.data(), buff.size()), (int) msg_bytelen); EXPECT_TRUE(verifyPayload(buff.data(), m_payload_sz, seqno)); EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); } diff --git a/test/test_connection_timeout.cpp b/test/test_connection_timeout.cpp index 0b8bb7874..ec52d9dd8 100644 --- a/test/test_connection_timeout.cpp +++ b/test/test_connection_timeout.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "test_env.h" @@ -12,6 +13,7 @@ typedef int SOCKET; #include"platform_sys.h" #include "srt.h" +#include "netinet_any.h" using namespace std; @@ -204,3 +206,68 @@ TEST_F(TestConnectionTimeout, BlockingLoop) } +TEST(TestConnectionAPI, Accept) +{ + using namespace std::chrono; + using namespace srt; + + srt_startup(); + + const SRTSOCKET caller_sock = srt_create_socket(); + const SRTSOCKET listener_sock = srt_create_socket(); + + const int eidl = srt_epoll_create(); + const int eidc = srt_epoll_create(); + const int ev_conn = SRT_EPOLL_OUT | SRT_EPOLL_ERR; + srt_epoll_add_usock(eidc, caller_sock, &ev_conn); + const int ev_acp = SRT_EPOLL_IN | SRT_EPOLL_ERR; + srt_epoll_add_usock(eidl, listener_sock, &ev_acp); + + sockaddr_any sa = srt::CreateAddr("localhost", 5555, AF_INET); + + ASSERT_NE(srt_bind(listener_sock, sa.get(), sa.size()), -1); + ASSERT_NE(srt_listen(listener_sock, 1), -1); + + // Set non-blocking mode so that you can wait for readiness + bool no = false; + srt_setsockflag(caller_sock, SRTO_RCVSYN, &no, sizeof no); + srt_setsockflag(listener_sock, SRTO_RCVSYN, &no, sizeof no); + + srt_connect(caller_sock, sa.get(), sa.size()); + + SRT_EPOLL_EVENT ready[2]; + int nready = srt_epoll_uwait(eidl, ready, 2, 1000); // Wait 1s + EXPECT_EQ(nready, 1); + EXPECT_EQ(ready[0].fd, listener_sock); + // EXPECT_EQ(ready[0].events, SRT_EPOLL_IN); + + // Now call the accept function incorrectly + int size = 0; + sockaddr_storage saf; + + EXPECT_EQ(srt_accept(listener_sock, (sockaddr*)&saf, &size), SRT_ERROR); + + std::this_thread::sleep_for(seconds(1)); + + // Set correctly + size = sizeof (sockaddr_in6); + EXPECT_NE(srt_accept(listener_sock, (sockaddr*)&saf, &size), SRT_ERROR); + + // Ended up with error, but now you should also expect error on the caller side. + + // Wait 5s until you get a connection broken. + nready = srt_epoll_uwait(eidc, ready, 2, 5000); + EXPECT_EQ(nready, 1); + if (nready == 1) + { + // Do extra checks only if you know that this was returned. + EXPECT_EQ(ready[0].fd, caller_sock); + EXPECT_EQ(ready[0].events & SRT_EPOLL_ERR, 0u); + } + srt_close(caller_sock); + srt_close(listener_sock); + + srt_cleanup(); +} + + diff --git a/test/test_crypto.cpp b/test/test_crypto.cpp index ce68dd32e..47b18dd1a 100644 --- a/test/test_crypto.cpp +++ b/test/test_crypto.cpp @@ -44,7 +44,7 @@ namespace srt m_crypt.setCryptoKeylen(cfg.iSndCryptoKeyLen); cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_GCM; - EXPECT_EQ(m_crypt.init(HSD_INITIATOR, cfg, true), HaiCrypt_IsAESGCM_Supported() != 0); + EXPECT_TRUE(m_crypt.init(HSD_INITIATOR, cfg, true, HaiCrypt_IsAESGCM_Supported())); const unsigned char* kmmsg = m_crypt.getKmMsg_data(0); const size_t km_len = m_crypt.getKmMsg_size(0); @@ -53,7 +53,7 @@ namespace srt std::array km_nworder; NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); - m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, kmout, kmout_len); + m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), kmout, kmout_len); } void TearDown() override @@ -85,9 +85,9 @@ namespace srt const int inorder = 1; const int kflg = m_crypt.getSndCryptoFlags(); - pkt.m_iSeqNo = seqno; - pkt.m_iMsgNo = msgno | inorder | PacketBoundaryBits(PB_SOLO) | MSGNO_ENCKEYSPEC::wrap(kflg);; - pkt.m_iTimeStamp = 356; + pkt.set_seqno(seqno); + pkt.set_msgflags(msgno | inorder | PacketBoundaryBits(PB_SOLO) | MSGNO_ENCKEYSPEC::wrap(kflg)); + pkt.set_timestamp(356); std::iota(pkt.data(), pkt.data() + pld_size, '0'); pkt.setLength(pld_size); @@ -103,7 +103,6 @@ namespace srt // Modify the payload and expect auth to fail. pkt_enc->data()[10] = '5'; EXPECT_EQ(m_crypt.decrypt(*pkt_enc.get()), ENCS_FAILED); - } } // namespace srt diff --git a/test/test_enforced_encryption.cpp b/test/test_enforced_encryption.cpp index 6fd284c23..717b4549c 100644 --- a/test/test_enforced_encryption.cpp +++ b/test/test_enforced_encryption.cpp @@ -257,8 +257,16 @@ class TestEnforcedEncryption { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. - EXPECT_NE(srt_close(m_caller_socket), SRT_ERROR) << srt_getlasterror_str(); - EXPECT_NE(srt_close(m_listener_socket), SRT_ERROR) << srt_getlasterror_str(); + + if (m_caller_socket != SRT_INVALID_SOCK) + { + EXPECT_NE(srt_close(m_caller_socket), SRT_ERROR) << srt_getlasterror_str(); + } + + if (m_listener_socket != SRT_INVALID_SOCK) + { + EXPECT_NE(srt_close(m_listener_socket), SRT_ERROR) << srt_getlasterror_str(); + } } @@ -541,6 +549,7 @@ class TestEnforcedEncryption // Just give it some time and close the socket. std::this_thread::sleep_for(std::chrono::milliseconds(50)); ASSERT_NE(srt_close(m_listener_socket), SRT_ERROR); + m_listener_socket = SRT_INVALID_SOCK; // mark closed already accepting_thread.join(); } } diff --git a/test/test_env.h b/test/test_env.h index a0fa5c668..b425fa808 100644 --- a/test/test_env.h +++ b/test/test_env.h @@ -67,18 +67,26 @@ class TestInit class UniqueSocket { int32_t sock; + std::string lab, f; + int l; public: - UniqueSocket(int32_t s): sock(s) + UniqueSocket(int32_t s, const char* label, const char* file, int line): sock(s) { if (s == -1) throw std::invalid_argument("Invalid socket"); + lab = label; + f = file; + l = line; } - UniqueSocket(): sock(-1) +#define MAKE_UNIQUE_SOCK(name, label, expr) srt::UniqueSocket name (expr, label, __FILE__, __LINE__) + + UniqueSocket(): sock(-1), l(0) { } + void close(); ~UniqueSocket(); operator int32_t() const diff --git a/test/test_epoll.cpp b/test/test_epoll.cpp index f4198ac29..fdbd65fc1 100644 --- a/test/test_epoll.cpp +++ b/test/test_epoll.cpp @@ -67,7 +67,7 @@ TEST(CEPoll, WaitEmptyCall) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); ASSERT_NE(client_sock, SRT_ERROR); const int no = 0; @@ -90,7 +90,7 @@ TEST(CEPoll, UWaitEmptyCall) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); ASSERT_NE(client_sock, SRT_ERROR); const int no = 0; @@ -113,7 +113,7 @@ TEST(CEPoll, WaitAllSocketsInEpollReleased) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); ASSERT_NE(client_sock, SRT_ERROR); const int yes = 1; @@ -147,7 +147,7 @@ TEST(CEPoll, WaitAllSocketsInEpollReleased2) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); ASSERT_NE(client_sock, SRT_ERROR); const int yes = 1; @@ -175,7 +175,7 @@ TEST(CEPoll, WrongEpoll_idOnAddUSock) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); ASSERT_NE(client_sock, SRT_ERROR); const int no = 0; @@ -198,7 +198,7 @@ TEST(CEPoll, HandleEpollEvent) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); EXPECT_NE(client_sock, SRT_ERROR); const int yes = 1; @@ -259,7 +259,7 @@ TEST(CEPoll, NotifyConnectionBreak) srt::TestInit srtinit; // 1. Prepare client - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); ASSERT_NE(client_sock, SRT_ERROR); const int yes SRT_ATR_UNUSED = 1; @@ -281,7 +281,7 @@ TEST(CEPoll, NotifyConnectionBreak) ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa_client.sin_addr), 1); // 2. Prepare server - srt::UniqueSocket server_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(server_sock, "server_sock", srt_create_socket()); ASSERT_NE(server_sock, SRT_ERROR); ASSERT_NE(srt_setsockopt(server_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect @@ -373,7 +373,7 @@ TEST(CEPoll, HandleEpollEvent2) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); EXPECT_NE(client_sock, SRT_ERROR); const int yes = 1; @@ -434,7 +434,7 @@ TEST(CEPoll, HandleEpollNoEvent) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); EXPECT_NE(client_sock, SRT_ERROR); const int yes = 1; @@ -484,7 +484,7 @@ TEST(CEPoll, ThreadedUpdate) { srt::TestInit srtinit; - srt::UniqueSocket client_sock = srt_create_socket(); + MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); EXPECT_NE(client_sock, SRT_ERROR); const int no = 0; diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index 602ff6d13..ac57e5265 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -305,9 +305,12 @@ TEST(TestFEC, Connection) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); - SRTSOCKET la[] = { l }; + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + // Given 2s timeout for accepting as it has occasionally happened with Travis // that 1s might not be enough. + SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -362,6 +365,9 @@ TEST(TestFEC, ConnectionReorder) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); @@ -417,6 +423,9 @@ TEST(TestFEC, ConnectionFull1) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); @@ -472,6 +481,9 @@ TEST(TestFEC, ConnectionFull2) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); @@ -527,9 +539,12 @@ TEST(TestFEC, ConnectionMess) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); - ASSERT_NE(a, SRT_ERROR); + ASSERT_NE(a, SRT_ERROR) << srt_getlasterror_str(); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -580,6 +595,9 @@ TEST(TestFEC, ConnectionForced) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); @@ -630,6 +648,9 @@ TEST(TestFEC, RejectionConflict) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + EXPECT_EQ(connect_res.get(), SRT_ERROR); EXPECT_EQ(srt_getrejectreason(s), SRT_REJ_FILTER); @@ -671,6 +692,9 @@ TEST(TestFEC, RejectionIncompleteEmpty) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + EXPECT_EQ(connect_res.get(), SRT_ERROR); EXPECT_EQ(srt_getrejectreason(s), SRT_REJ_FILTER); @@ -716,6 +740,9 @@ TEST(TestFEC, RejectionIncomplete) return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); }); + // Make sure that the async call to srt_connect() is already kicked. + std::this_thread::yield(); + EXPECT_EQ(connect_res.get(), SRT_ERROR); EXPECT_EQ(srt_getrejectreason(s), SRT_REJ_FILTER); @@ -809,7 +836,7 @@ TEST_F(TestFECRebuilding, NoRebuild) // - Crypto // - Message Number // will be set to 0/false - fecpkt->m_iMsgNo = MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); + fecpkt->set_msgflags(MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO)); // ... and then fix only the Crypto flags fecpkt->setMsgCryptoFlags(EncryptionKeySpec(0)); @@ -886,7 +913,7 @@ TEST_F(TestFECRebuilding, Rebuild) // - Crypto // - Message Number // will be set to 0/false - fecpkt->m_iMsgNo = MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO); + fecpkt->set_msgflags(MSGNO_PACKET_BOUNDARY::wrap(PB_SOLO)); // ... and then fix only the Crypto flags fecpkt->setMsgCryptoFlags(EncryptionKeySpec(0)); @@ -904,7 +931,7 @@ TEST_F(TestFECRebuilding, Rebuild) // Set artificially the SN_REXMIT flag in the skipped source packet // because the rebuilt packet shall have REXMIT flag set. - skipped.m_iMsgNo |= MSGNO_REXMIT::wrap(true); + skipped.set_msgflags(skipped.msgflags() | MSGNO_REXMIT::wrap(true)); // Compare the header EXPECT_EQ(skipped.getHeader()[SRT_PH_SEQNO], rebuilt.hdr[SRT_PH_SEQNO]); diff --git a/test/test_file_transmission.cpp b/test/test_file_transmission.cpp index 97f9e684a..bfd668ac7 100644 --- a/test/test_file_transmission.cpp +++ b/test/test_file_transmission.cpp @@ -25,12 +25,14 @@ #include #include #include +#include //#pragma comment (lib, "ws2_32.lib") TEST(Transmission, FileUpload) { srt::TestInit srtinit; + srtinit.HandlePerTestOptions(); // Generate the source file // We need a file that will contain more data @@ -93,7 +95,7 @@ TEST(Transmission, FileUpload) // Start listener-receiver thread - bool thread_exit = false; + std::atomic thread_exit { false }; auto client = std::thread([&] { @@ -116,12 +118,17 @@ TEST(Transmission, FileUpload) for (;;) { int n = srt_recv(accepted_sock, buf.data(), 1456); - ASSERT_NE(n, SRT_ERROR); + EXPECT_NE(n, SRT_ERROR) << srt_getlasterror_str(); if (n == 0) { std::cerr << "Received 0 bytes, breaking.\n"; break; } + else if (n == -1) + { + std::cerr << "READ FAILED, breaking anyway\n"; + break; + } // Write to file any amount of data received copyfile.write(buf.data(), n); @@ -173,7 +180,7 @@ TEST(Transmission, FileUpload) std::cout << "Sockets closed, joining receiver thread\n"; client.join(); - std::ifstream tarfile("file.target"); + std::ifstream tarfile("file.target", std::ios::in | std::ios::binary); EXPECT_EQ(!!tarfile, true); tarfile.seekg(0, std::ios::end); @@ -182,8 +189,14 @@ TEST(Transmission, FileUpload) std::cout << "Comparing files\n"; // Compare files - tarfile.seekg(0, std::ios::end); - ifile.seekg(0, std::ios::beg); + + // Theoretically it should work if you just rewind to 0, but + // on Windows this somehow doesn't work. + tarfile.close(); + tarfile.open("file.target", std::ios::in | std::ios::binary); + + ifile.close(); + ifile.open("file.source", std::ios::in | std::ios::binary); for (size_t i = 0; i < tar_size; ++i) { diff --git a/test/test_losslist_rcv.cpp b/test/test_losslist_rcv.cpp index 9ab4f382b..94fea6ceb 100644 --- a/test/test_losslist_rcv.cpp +++ b/test/test_losslist_rcv.cpp @@ -81,7 +81,7 @@ TEST(CRcvFreshLossListTest, CheckFreshLossList) CRcvFreshLoss (45, 80, 100) }; - EXPECT_EQ(floss.size(), 4); + EXPECT_EQ(floss.size(), 4u); // Ok, now let's do element removal @@ -90,7 +90,7 @@ TEST(CRcvFreshLossListTest, CheckFreshLossList) EXPECT_EQ(rm, true); EXPECT_EQ(had_ttl, 10); - EXPECT_EQ(floss.size(), 5); + EXPECT_EQ(floss.size(), 5u); // Now we expect to have [10-15] [25-25] [27-35]... // After revoking 25 it should have removed it. @@ -99,41 +99,41 @@ TEST(CRcvFreshLossListTest, CheckFreshLossList) rm = CRcvFreshLoss::removeOne((floss), 27, &had_ttl); EXPECT_EQ(rm, true); EXPECT_EQ(had_ttl, 10); - EXPECT_EQ(floss.size(), 5); + EXPECT_EQ(floss.size(), 5u); // STRIP rm = CRcvFreshLoss::removeOne((floss), 28, &had_ttl); EXPECT_EQ(rm, true); EXPECT_EQ(had_ttl, 10); - EXPECT_EQ(floss.size(), 5); + EXPECT_EQ(floss.size(), 5u); // DELETE rm = CRcvFreshLoss::removeOne((floss), 25, &had_ttl); EXPECT_EQ(rm, true); EXPECT_EQ(had_ttl, 10); - EXPECT_EQ(floss.size(), 4); + EXPECT_EQ(floss.size(), 4u); // SPLIT rm = CRcvFreshLoss::removeOne((floss), 50, &had_ttl); EXPECT_EQ(rm, true); EXPECT_EQ(had_ttl, 100); - EXPECT_EQ(floss.size(), 5); + EXPECT_EQ(floss.size(), 5u); // DELETE rm = CRcvFreshLoss::removeOne((floss), 30, &had_ttl); EXPECT_EQ(rm, true); EXPECT_EQ(had_ttl, 3); - EXPECT_EQ(floss.size(), 4); + EXPECT_EQ(floss.size(), 4u); // Remove nonexistent sequence, but existing before. rm = CRcvFreshLoss::removeOne((floss), 25, NULL); EXPECT_EQ(rm, false); - EXPECT_EQ(floss.size(), 4); + EXPECT_EQ(floss.size(), 4u); // Remove nonexistent sequence that didn't exist before. rm = CRcvFreshLoss::removeOne((floss), 31, &had_ttl); EXPECT_EQ(rm, false); EXPECT_EQ(had_ttl, 0); - EXPECT_EQ(floss.size(), 4); + EXPECT_EQ(floss.size(), 4u); } diff --git a/test/test_main.cpp b/test/test_main.cpp index cc5acc487..e2243a306 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -113,6 +113,11 @@ void TestInit::HandlePerTestOptions() { srt_setloglevel(LOG_DEBUG); } + + if (TestEnv::me->OptionPresent("lognote")) + { + srt_setloglevel(LOG_NOTICE); + } } // Copied from ../apps/apputil.cpp, can't really link this file here. @@ -178,7 +183,33 @@ sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_f UniqueSocket::~UniqueSocket() { - srt_close(sock); + // Could be closed explicitly + if (sock != -1) + close(); +} + +void UniqueSocket::close() +{ + int close_result = srt_close(sock); + int close_error = srt_getlasterror(nullptr); + + // XXX SRT_EINVSOCK is reported when the socket + // has been already wiped out, which may happen to a broken socket. + // This isn't exactly intended, although trying to close a nonexistent + // socket is not a problem, as long as it happens before the id value rollover + // (that is, when it's closed immediately after getting broken). + // This solution is still slick though and should be fixed. + // + // Restore this, when fixed + // EXPECT_NE(srt_close(sock), SRT_ERROR) << lab << " CREATED: "<< f << ":" << l; + if (close_result == SRT_ERROR) + { + EXPECT_NE(close_error, SRT_EINVSOCK) << lab << " CREATED: "<< f << ":" << l; + } + else + { + EXPECT_EQ(close_result, 0) << lab << " CREATED: "<< f << ":" << l; + } } } diff --git a/test/test_many_connections.cpp b/test/test_many_connections.cpp index 20deccf70..29cd6bdd2 100644 --- a/test/test_many_connections.cpp +++ b/test/test_many_connections.cpp @@ -135,6 +135,8 @@ TEST_F(TestConnection, Multiple) cerr << "Opening " << NSOCK << " connections\n"; + bool overall_test = true; + for (size_t i = 0; i < NSOCK; i++) { m_connections[i] = srt_create_socket(); @@ -145,13 +147,18 @@ TEST_F(TestConnection, Multiple) int conntimeo = 60; srt_setsockflag(m_connections[i], SRTO_CONNTIMEO, &conntimeo, sizeof conntimeo); + SRTSOCKET connres = SRT_INVALID_SOCK; + //cerr << "Connecting #" << i << " to " << sockaddr_any(psa).str() << "...\n"; //cerr << "Connecting to: " << sockaddr_any(psa).str() << endl; - ASSERT_NE(srt_connect(m_connections[i], psa, sizeof lsa), SRT_ERROR); + connres = srt_connect(m_connections[i], psa, sizeof lsa); + EXPECT_NE(connres, SRT_INVALID_SOCK) << "conn #" << i << ": " << srt_getlasterror_str(); + if (connres == SRT_INVALID_SOCK) + overall_test = false; // Set now async sending so that sending isn't blocked int no = 0; - ASSERT_NE(srt_setsockflag(m_connections[i], SRTO_SNDSYN, &no, sizeof no), -1); + EXPECT_NE(srt_setsockflag(m_connections[i], SRTO_SNDSYN, &no, sizeof no), -1); } for (size_t j = 1; j <= 100; j++) @@ -170,6 +177,7 @@ TEST_F(TestConnection, Multiple) EXPECT_FALSE(m_accept_exit) << "AcceptLoop already broken for some reason!"; // Up to this moment the server sock should survive + cerr << "Closing server socket\n"; // Close server socket to break the accept loop EXPECT_EQ(srt_close(m_server_sock), 0); @@ -177,6 +185,8 @@ TEST_F(TestConnection, Multiple) cerr << "Synchronize with the accepting thread\n"; ex.wait(); cerr << "Synchronization done\n"; + + ASSERT_TRUE(overall_test); } diff --git a/test/test_reuseaddr.cpp b/test/test_reuseaddr.cpp index fe9027311..df4aec487 100644 --- a/test/test_reuseaddr.cpp +++ b/test/test_reuseaddr.cpp @@ -1,5 +1,6 @@ #include #include +#include #ifndef _WIN32 #include #endif @@ -30,7 +31,7 @@ struct AtReturnJoin // iphlp library to be attached to the executable, which is kinda // problematic. Temporarily block tests using this function on Windows. -std::string GetLocalIP(int af = AF_UNSPEC) +static std::string GetLocalIP(int af = AF_UNSPEC) { std::cout << "!!!WARNING!!!: GetLocalIP not supported, test FORCEFULLY passed\n"; return ""; @@ -50,7 +51,7 @@ struct IfAddr } }; -std::string GetLocalIP(int af = AF_UNSPEC) +static std::string GetLocalIP(int af = AF_UNSPEC) { struct ifaddrs * ifa=NULL; void * tmpAddrPtr=NULL; @@ -101,306 +102,360 @@ std::string GetLocalIP(int af = AF_UNSPEC) } #endif -int client_pollid = SRT_ERROR; -SRTSOCKET g_client_sock = SRT_ERROR; - -void clientSocket(std::string ip, int port, bool expect_success) +class ReuseAddr : public srt::Test { - int yes = 1; - int no = 0; +protected: - int family = AF_INET; - std::string famname = "IPv4"; - if (ip.substr(0, 2) == "6.") + std::string showEpollContents(const char* label, int* array, int length) { - family = AF_INET6; - ip = ip.substr(2); - famname = "IPv6"; - } - - std::cout << "[T/C] Creating client socket\n"; - - g_client_sock = srt_create_socket(); - ASSERT_NE(g_client_sock, SRT_ERROR); + std::ostringstream out; + out << label << ":["; + if (length) + { + // Now is at least 1 + out << "@" << array[0]; - ASSERT_NE(srt_setsockopt(g_client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockflag(g_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); + for (int i = 1; i < length; ++i) + out << " @" << array[i]; + } + out << "]"; + return out.str(); + } - ASSERT_NE(srt_setsockopt(g_client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + struct UniquePollid + { + int pollid = SRT_ERROR; + UniquePollid() + { + pollid = srt_epoll_create(); + } - int epoll_out = SRT_EPOLL_OUT; - srt_epoll_add_usock(client_pollid, g_client_sock, &epoll_out); + ~UniquePollid() + { + srt_epoll_release(pollid); + } - sockaddr_any sa = srt::CreateAddr(ip, port, family); + operator int() const + { + return pollid; + } + }; - std::cout << "[T/C] Connecting to: " << sa.str() << " (" << famname << ")" << std::endl; + void clientSocket(SRTSOCKET client_sock, std::string ip, int port, bool expect_success) + { + using namespace std; - int connect_res = srt_connect(g_client_sock, sa.get(), sa.size()); + int yes = 1; + int no = 0; - if (connect_res == -1) - { - std::cout << "srt_connect: " << srt_getlasterror_str() << std::endl; - } + int family = AF_INET; + string famname = "IPv4"; + if (ip.substr(0, 2) == "6.") + { + family = AF_INET6; + ip = ip.substr(2); + famname = "IPv6"; + } - if (expect_success) - { - EXPECT_NE(connect_res, -1); - if (connect_res == -1) - return; + cout << "[T/C] Setting up client socket\n"; + ASSERT_NE(client_sock, SRT_INVALID_SOCK); + ASSERT_EQ(srt_getsockstate(client_sock), SRTS_INIT); - // Socket readiness for connection is checked by polling on WRITE allowed sockets. + EXPECT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); + EXPECT_NE(srt_setsockflag(client_sock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); - if (connect_res != -1) - { - int rlen = 2; - SRTSOCKET read[2]; + UniquePollid client_pollid; + ASSERT_NE(int(client_pollid), SRT_ERROR); - int wlen = 2; - SRTSOCKET write[2]; + int epoll_out = SRT_EPOLL_OUT; + srt_epoll_add_usock(client_pollid, client_sock, &epoll_out); - std::cout << "[T/C] Waiting for connection readiness...\n"; + sockaddr_any sa = srt::CreateAddr(ip, port, family); - EXPECT_NE(srt_epoll_wait(client_pollid, read, &rlen, - write, &wlen, - -1, // -1 is set for debuging purpose. - // in case of production we need to set appropriate value - 0, 0, 0, 0), SRT_ERROR); + cout << "[T/C] Connecting to: " << sa.str() << " (" << famname << ")" << endl; + int connect_res = srt_connect(client_sock, sa.get(), sa.size()); - EXPECT_EQ(rlen, 0); // get exactly one write event without reads - EXPECT_EQ(wlen, 1); // get exactly one write event without reads - EXPECT_EQ(write[0], g_client_sock); // for our client socket + if (connect_res == -1) + { + cout << "srt_connect: " << srt_getlasterror_str() << endl; + } - char buffer[1316] = {1, 2, 3, 4}; - EXPECT_NE(srt_sendmsg(g_client_sock, buffer, sizeof buffer, - -1, // infinit ttl - true // in order must be set to true - ), - SRT_ERROR); + if (expect_success) + { + EXPECT_NE(connect_res, -1); + if (connect_res == -1) + return; + + // Socket readiness for connection is checked by polling on WRITE allowed sockets. + + if (connect_res != -1) + { + int rlen = 2; + SRTSOCKET read[2]; + + int wlen = 2; + SRTSOCKET write[2]; + + cout << "[T/C] Waiting for connection readiness...\n"; + + EXPECT_NE(srt_epoll_wait(client_pollid, read, &rlen, + write, &wlen, + -1, // -1 is set for debuging purpose. + // in case of production we need to set appropriate value + 0, 0, 0, 0), SRT_ERROR) << srt_getlasterror_str(); + + EXPECT_EQ(rlen, 0) << showEpollContents("[T/C] R", read, rlen); // get exactly one write event without reads + EXPECT_EQ(wlen, 1) << showEpollContents("[T/C] W", write, wlen); // get exactly one write event without reads + EXPECT_EQ(write[0], client_sock); // for our client socket + + char buffer[1316] = {1, 2, 3, 4}; + EXPECT_NE(srt_sendmsg(client_sock, buffer, sizeof buffer, + -1, // infinit ttl + true // in order must be set to true + ), + SRT_ERROR); + } + else + { + cout << "[T/C] (NOT TESTING TRANSMISSION - CONNECTION FAILED ALREADY)\n"; + } } else { - std::cout << "[T/C] (NOT TESTING TRANSMISSION - CONNECTION FAILED ALREADY)\n"; + EXPECT_EQ(connect_res, -1); } + + cout << "[T/C] Client exit\n"; } - else + + SRTSOCKET prepareServerSocket() { - EXPECT_EQ(connect_res, -1); - } + SRTSOCKET bindsock = srt_create_socket(); + EXPECT_NE(bindsock, SRT_ERROR); - std::cout << "[T/C] Client exit\n"; -} + int yes = 1; + int no = 0; -int server_pollid = SRT_ERROR; + EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); -SRTSOCKET prepareSocket() -{ - SRTSOCKET bindsock = srt_create_socket(); - EXPECT_NE(bindsock, SRT_ERROR); + return bindsock; + } - int yes = 1; - int no = 0; + bool bindSocket(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) + { + sockaddr_any sa = srt::CreateAddr(ip, port, AF_INET); - EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + std::string fam = (sa.family() == AF_INET) ? "IPv4" : "IPv6"; - int epoll_in = SRT_EPOLL_IN; + std::cout << "[T/S] Bind @" << bindsock << " to: " << sa.str() << " (" << fam << ")" << std::endl; - std::cout << "[T/S] Listener/binder sock @" << bindsock << " added to server_pollid\n"; - srt_epoll_add_usock(server_pollid, bindsock, &epoll_in); + int bind_res = srt_bind(bindsock, sa.get(), sa.size()); - return bindsock; -} + std::cout << "[T/S] ... result " << bind_res << " (expected to " + << (expect_success ? "succeed" : "fail") << ")\n"; -bool bindSocket(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) -{ - sockaddr_any sa = srt::CreateAddr(ip, port, AF_INET); + if (!expect_success) + { + std::cout << "[T/S] Binding should fail: " << srt_getlasterror_str() << std::endl; + EXPECT_EQ(bind_res, SRT_ERROR); + return false; + } - std::string fam = (sa.family() == AF_INET) ? "IPv4" : "IPv6"; + EXPECT_NE(bind_res, SRT_ERROR); + return true; + } - std::cout << "[T/S] Bind @" << bindsock << " to: " << sa.str() << " (" << fam << ")" << std::endl; + bool bindListener(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) + { + if (!bindSocket(bindsock, ip, port, expect_success)) + return false; - int bind_res = srt_bind(bindsock, sa.get(), sa.size()); + EXPECT_NE(srt_listen(bindsock, SOMAXCONN), SRT_ERROR); - std::cout << "[T/S] ... result " << bind_res << " (expected to " - << (expect_success ? "succeed" : "fail") << ")\n"; + return true; + } - if (!expect_success) + SRTSOCKET createListener(std::string ip, int port, bool expect_success) { - std::cout << "[T/S] Binding should fail: " << srt_getlasterror_str() << std::endl; - EXPECT_EQ(bind_res, SRT_ERROR); - return false; - } + std::cout << "[T/S] serverSocket: creating listener socket\n"; - EXPECT_NE(bind_res, SRT_ERROR); - return true; -} + SRTSOCKET bindsock = prepareServerSocket(); -bool bindListener(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) -{ - if (!bindSocket(bindsock, ip, port, expect_success)) - return false; + if (!bindListener(bindsock, ip, port, expect_success)) + return SRT_INVALID_SOCK; - EXPECT_NE(srt_listen(bindsock, SOMAXCONN), SRT_ERROR); + return bindsock; + } - return true; -} + SRTSOCKET createBinder(std::string ip, int port, bool expect_success) + { + std::cout << "[T/S] serverSocket: creating binder socket\n"; -SRTSOCKET createListener(std::string ip, int port, bool expect_success) -{ - std::cout << "[T/S] serverSocket: creating listener socket\n"; + SRTSOCKET bindsock = prepareServerSocket(); - SRTSOCKET bindsock = prepareSocket(); + if (!bindSocket(bindsock, ip, port, expect_success)) + { + srt_close(bindsock); + return SRT_INVALID_SOCK; + } - if (!bindListener(bindsock, ip, port, expect_success)) - return SRT_INVALID_SOCK; + return bindsock; + } - return bindsock; -} + void testAccept(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) + { + MAKE_UNIQUE_SOCK(client_sock, "[T/S]connect", srt_create_socket()); -SRTSOCKET createBinder(std::string ip, int port, bool expect_success) -{ - std::cout << "[T/S] serverSocket: creating binder socket\n"; + auto run = [this, &client_sock, ip, port, expect_success]() { clientSocket(client_sock, ip, port, expect_success); }; - SRTSOCKET bindsock = prepareSocket(); + auto launched = std::async(std::launch::async, run); - if (!bindSocket(bindsock, ip, port, expect_success)) - { - srt_close(bindsock); - return SRT_INVALID_SOCK; - } + AtReturnJoin atreturn_join {launched}; - return bindsock; -} + int server_pollid = srt_epoll_create(); + int epoll_in = SRT_EPOLL_IN; + std::cout << "[T/S] Listener/binder sock @" << bindsock << " added to server_pollid\n"; + srt_epoll_add_usock(server_pollid, bindsock, &epoll_in); -void testAccept(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) -{ + { // wait for connection from client + int rlen = 2; + SRTSOCKET read[2]; - auto run = [ip, port, expect_success]() { clientSocket(ip, port, expect_success); }; + int wlen = 2; + SRTSOCKET write[2]; - auto launched = std::async(std::launch::async, run); + std::cout << "[T/S] Wait 10s on E" << server_pollid << " for acceptance on @" << bindsock << " ...\n"; - AtReturnJoin atreturn_join {launched}; + EXPECT_NE(srt_epoll_wait(server_pollid, + read, &rlen, + write, &wlen, + 10000, // -1 is set for debuging purpose. + // in case of production we need to set appropriate value + 0, 0, 0, 0), SRT_ERROR) << srt_getlasterror_str(); - { // wait for connection from client - int rlen = 2; - SRTSOCKET read[2]; - int wlen = 2; - SRTSOCKET write[2]; + EXPECT_EQ(rlen, 1) << showEpollContents("[T/S] R", read, rlen); // get exactly one read event without writes + EXPECT_EQ(wlen, 0) << showEpollContents("[T/S] W", write, wlen); // get exactly one read event without writes + ASSERT_EQ(read[0], bindsock); // read event is for bind socket + } - std::cout << "[T/S] Wait 10s for acceptance on @" << bindsock << " ...\n"; + { + sockaddr_any scl; + MAKE_UNIQUE_SOCK(accepted_sock, "[T/S]accept", srt_accept(bindsock, scl.get(), &scl.len)); - ASSERT_NE(srt_epoll_wait(server_pollid, - read, &rlen, - write, &wlen, - 10000, // -1 is set for debuging purpose. - // in case of production we need to set appropriate value - 0, 0, 0, 0), SRT_ERROR ); + if (accepted_sock == -1) + { + std::cout << "srt_accept: " << srt_getlasterror_str() << std::endl; + } + EXPECT_NE(accepted_sock.ref(), SRT_INVALID_SOCK); + sockaddr_any showacp = (sockaddr*)&scl; + std::cout << "[T/S] Accepted from: " << showacp.str() << std::endl; - ASSERT_EQ(rlen, 1); // get exactly one read event without writes - ASSERT_EQ(wlen, 0); // get exactly one read event without writes - ASSERT_EQ(read[0], bindsock); // read event is for bind socket - } + epoll_in = SRT_EPOLL_IN; + srt_epoll_add_usock(server_pollid, accepted_sock, &epoll_in); // wait for input - sockaddr_any scl; + char buffer[1316]; + { // wait for 1316 packet from client + int rlen = 2; + SRTSOCKET read[2]; - SRTSOCKET accepted_sock = srt_accept(bindsock, scl.get(), &scl.len); - if (accepted_sock == -1) - { - std::cout << "srt_accept: " << srt_getlasterror_str() << std::endl; - } - ASSERT_NE(accepted_sock, SRT_INVALID_SOCK); + int wlen = 2; + SRTSOCKET write[2]; - sockaddr_any showacp = (sockaddr*)&scl; - std::cout << "[T/S] Accepted from: " << showacp.str() << std::endl; + std::cout << "[T/S] Wait for data reception...\n"; - int epoll_in = SRT_EPOLL_IN; - srt_epoll_add_usock(server_pollid, accepted_sock, &epoll_in); // wait for input + EXPECT_NE(srt_epoll_wait(server_pollid, + read, &rlen, + write, &wlen, + -1, // -1 is set for debuging purpose. + // in case of production we need to set appropriate value + 0, 0, 0, 0), SRT_ERROR) << srt_getlasterror_str(); - char buffer[1316]; - { // wait for 1316 packet from client - int rlen = 2; - SRTSOCKET read[2]; - int wlen = 2; - SRTSOCKET write[2]; + EXPECT_EQ(rlen, 1) << showEpollContents("[T/S] R", read, rlen); // get exactly one read event without writes + EXPECT_EQ(wlen, 0) << showEpollContents("[T/S] W", write, wlen); // get exactly one read event without writes + EXPECT_EQ(read[0], accepted_sock.ref()); // read event is for bind socket + } - std::cout << "[T/S] Wait for data reception...\n"; + char pattern[4] = {1, 2, 3, 4}; - ASSERT_NE(srt_epoll_wait(server_pollid, - read, &rlen, - write, &wlen, - -1, // -1 is set for debuging purpose. - // in case of production we need to set appropriate value - 0, 0, 0, 0), SRT_ERROR ); + EXPECT_EQ(srt_recvmsg(accepted_sock, buffer, sizeof buffer), + 1316); + EXPECT_EQ(memcmp(pattern, buffer, sizeof pattern), 0); - ASSERT_EQ(rlen, 1); // get exactly one read event without writes - ASSERT_EQ(wlen, 0); // get exactly one read event without writes - ASSERT_EQ(read[0], accepted_sock); // read event is for bind socket - } + // XXX There is a possibility that a broken socket can be closed automatically, + // just the srt_close() call would simply return error in case of nonexistent + // socket. Therefore close them both at once; this problem needs to be fixed + // separately. + // + // The test only intends to send one portion of data from the client, so once + // received, the client has nothing more to do and should exit. + std::cout << "[T/S] closing client socket\n"; + client_sock.close(); + std::cout << "[T/S] closing sockets: ACP:@" << accepted_sock << "...\n"; + } + srt_epoll_release(server_pollid); - char pattern[4] = {1, 2, 3, 4}; + // client_sock closed through UniqueSocket. + // cannot close client_sock after srt_sendmsg because of issue in api.c:2346 - ASSERT_EQ(srt_recvmsg(accepted_sock, buffer, sizeof buffer), - 1316); + std::cout << "[T/S] joining client async \n"; + launched.get(); + } - EXPECT_EQ(memcmp(pattern, buffer, sizeof pattern), 0); + static void shutdownListener(SRTSOCKET bindsock) + { + // Silently ignore. Usually it should have been checked earlier, + // and an invalid sock might be expected in particular tests. + if (bindsock == SRT_INVALID_SOCK) + return; - std::cout << "[T/S] closing sockets: ACP:@" << accepted_sock << " LSN:@" << bindsock << " CLR:@" << g_client_sock << " ...\n"; - ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); - ASSERT_NE(srt_close(g_client_sock), SRT_ERROR); // cannot close g_client_sock after srt_sendmsg because of issue in api.c:2346 + int yes = 1; + EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_ERROR); // for async connect + EXPECT_NE(srt_close(bindsock), SRT_ERROR); - std::cout << "[T/S] joining client async...\n"; - launched.get(); -} + std::chrono::milliseconds check_period (100); + int credit = 400; // 10 seconds + auto then = std::chrono::steady_clock::now(); -void shutdownListener(SRTSOCKET bindsock) -{ - // Silently ignore. Usually it should have been checked earlier, - // and an invalid sock might be expected in particular tests. - if (bindsock == SRT_INVALID_SOCK) - return; + std::cout << "[T/S] waiting for cleanup of @" << bindsock << " up to 10s" << std::endl; + while (srt_getsockstate(bindsock) != SRTS_NONEXIST) + { + std::this_thread::sleep_for(check_period); + --credit; + if (!credit) + break; + } + auto now = std::chrono::steady_clock::now(); + auto dur = std::chrono::duration_cast(now - then); - int yes = 1; - EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_ERROR); // for async connect - EXPECT_NE(srt_close(bindsock), SRT_ERROR); + // Keep as single string because this tends to be mixed from 2 threads. + std::ostringstream sout; + sout << "[T/S] @" << bindsock << " dissolved after " + << (dur.count() / 1000.0) << "s" << std::endl; + std::cout << sout.str() << std::flush; + + EXPECT_NE(credit, 0); + } - std::chrono::milliseconds check_period (100); - int credit = 400; // 10 seconds - auto then = std::chrono::steady_clock::now(); +private: - std::cout << "[T/S] waiting for cleanup of @" << bindsock << " up to 10s" << std::endl; - while (srt_getsockstate(bindsock) != SRTS_NONEXIST) + void setup() { - std::this_thread::sleep_for(check_period); - --credit; - if (!credit) - break; } - auto now = std::chrono::steady_clock::now(); - auto dur = std::chrono::duration_cast(now - then); - // Keep as single string because this tends to be mixed from 2 threads. - std::ostringstream sout; - sout << "[T/S] @" << bindsock << " dissolved after " - << (dur.count() / 1000.0) << "s" << std::endl; - std::cout << sout.str() << std::flush; - - EXPECT_NE(credit, 0); -} + void teardown() + { + } +}; -TEST(ReuseAddr, SameAddr1) +TEST_F(ReuseAddr, SameAddr1) { - srt::TestInit srtinit; - - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); SRTSOCKET bindsock_1 = createBinder("127.0.0.1", 5000, true); SRTSOCKET bindsock_2 = createListener("127.0.0.1", 5000, true); @@ -413,24 +468,14 @@ TEST(ReuseAddr, SameAddr1) s1.join(); s2.join(); - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, SameAddr2) +TEST_F(ReuseAddr, SameAddr2) { - srt::TestInit srtinit; std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. - - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); - SRTSOCKET bindsock_1 = createBinder(localip, 5000, true); SRTSOCKET bindsock_2 = createListener(localip, 5000, true); @@ -445,21 +490,11 @@ TEST(ReuseAddr, SameAddr2) testAccept(bindsock_3, localip, 5000, true); shutdownListener(bindsock_3); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, SameAddrV6) +TEST_F(ReuseAddr, SameAddrV6) { SRTST_REQUIRES(IPv6); - srt::TestInit srtinit; - - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); SRTSOCKET bindsock_1 = createBinder("::1", 5000, true); SRTSOCKET bindsock_2 = createListener("::1", 5000, true); @@ -475,26 +510,15 @@ TEST(ReuseAddr, SameAddrV6) testAccept(bindsock_3, "::1", 5000, true); shutdownListener(bindsock_3); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, DiffAddr) +TEST_F(ReuseAddr, DiffAddr) { - srt::TestInit srtinit; std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. - - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); - SRTSOCKET bindsock_1 = createBinder("127.0.0.1", 5000, true); SRTSOCKET bindsock_2 = createListener(localip, 5000, true); @@ -505,14 +529,10 @@ TEST(ReuseAddr, DiffAddr) shutdownListener(bindsock_1); shutdownListener(bindsock_2); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, Wildcard) +TEST_F(ReuseAddr, Wildcard) { - srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; @@ -524,14 +544,6 @@ TEST(ReuseAddr, Wildcard) std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. - - - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); - SRTSOCKET bindsock_1 = createListener("0.0.0.0", 5000, true); // Binding a certain address when wildcard is already bound should fail. @@ -541,15 +553,11 @@ TEST(ReuseAddr, Wildcard) shutdownListener(bindsock_1); shutdownListener(bindsock_2); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, Wildcard6) +TEST_F(ReuseAddr, Wildcard6) { SRTST_REQUIRES(IPv6); - srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; @@ -567,17 +575,10 @@ TEST(ReuseAddr, Wildcard6) // performed there. std::string localip_v4 = GetLocalIP(AF_INET); - - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); - // This must be obligatory set before binding a socket to "::" int strict_ipv6 = 1; - SRTSOCKET bindsock_1 = prepareSocket(); + SRTSOCKET bindsock_1 = prepareServerSocket(); srt_setsockflag(bindsock_1, SRTO_IPV6ONLY, &strict_ipv6, sizeof strict_ipv6); bindListener(bindsock_1, "::", 5000, true); @@ -601,7 +602,7 @@ TEST(ReuseAddr, Wildcard6) strict_ipv6 = 0; - bindsock_1 = prepareSocket(); + bindsock_1 = prepareServerSocket(); srt_setsockflag(bindsock_1, SRTO_IPV6ONLY, &strict_ipv6, sizeof strict_ipv6); bindListener(bindsock_1, "::", 5000, true); @@ -620,33 +621,23 @@ TEST(ReuseAddr, Wildcard6) shutdownListener(bindsock_1); shutdownListener(bindsock_2); shutdownListener(bindsock_3); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, ProtocolVersion6) +TEST_F(ReuseAddr, ProtocolVersion6) { SRTST_REQUIRES(IPv6); - srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; return; #endif - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); - SRTSOCKET bindsock_1 = createListener("0.0.0.0", 5000, true); // We need a small interception in this one. - // createListener = prepareSocket | bindListener - SRTSOCKET bindsock_2 = prepareSocket(); + // createListener = prepareServerSocket | bindListener + SRTSOCKET bindsock_2 = prepareServerSocket(); { int yes = 1; @@ -659,32 +650,23 @@ TEST(ReuseAddr, ProtocolVersion6) shutdownListener(bindsock_1); shutdownListener(bindsock_2); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } -TEST(ReuseAddr, ProtocolVersionFaux6) +TEST_F(ReuseAddr, ProtocolVersionFaux6) { SRTST_REQUIRES(IPv6); - srt::TestInit srtinit; + #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; return; #endif - client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, client_pollid); - - server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, server_pollid); - SRTSOCKET bindsock_1 = createListener("0.0.0.0", 5000, true); // We need a small interception in this one. - // createListener = prepareSocket | bindListener - SRTSOCKET bindsock_2 = prepareSocket(); + // createListener = prepareServerSocket | bindListener + SRTSOCKET bindsock_2 = prepareServerSocket(); { int no = 0; @@ -696,7 +678,4 @@ TEST(ReuseAddr, ProtocolVersionFaux6) shutdownListener(bindsock_1); shutdownListener(bindsock_2); - - (void)srt_epoll_release(client_pollid); - (void)srt_epoll_release(server_pollid); } diff --git a/test/test_snd_rate_estimator.cpp b/test/test_snd_rate_estimator.cpp index e3d512424..c658c659c 100644 --- a/test/test_snd_rate_estimator.cpp +++ b/test/test_snd_rate_estimator.cpp @@ -3,6 +3,7 @@ #include "gtest/gtest.h" #include "buffer_tools.h" #include "sync.h" +#include "packet.h" using namespace srt; using namespace std; @@ -53,6 +54,7 @@ TEST_F(CSndRateEstFixture, Empty) TEST_F(CSndRateEstFixture, CBRSending) { + const size_t hdrBytes = srt::CPacket::HDR_SIZE; // Generate CBR sending for 2.1 seconds to wrap the buffer around. for (int i = 0; i < 2100; ++i) { @@ -61,7 +63,7 @@ TEST_F(CSndRateEstFixture, CBRSending) const auto rate = m_rateEst.getRate(); if (i >= 100) - EXPECT_EQ(rate, 1316000) << "i=" << i; + EXPECT_EQ(rate, 1316000 + 1000 * hdrBytes) << "i=" << i; else EXPECT_EQ(rate, 0) << "i=" << i; } @@ -72,6 +74,7 @@ TEST_F(CSndRateEstFixture, CBRSending) // only for one sampling period. TEST_F(CSndRateEstFixture, CBRSendingAfterPause) { + const size_t hdrBytes = srt::CPacket::HDR_SIZE; // Send 100 packets with 1000 bytes each for (int i = 0; i < 3100; ++i) { @@ -82,7 +85,7 @@ TEST_F(CSndRateEstFixture, CBRSendingAfterPause) const auto rate = m_rateEst.getRate(); if (i >= 100 && !(i >= 2000 && i < 2100)) - EXPECT_EQ(rate, 1316000) << "i=" << i; + EXPECT_EQ(rate, 1316000 + 1000 * hdrBytes) << "i=" << i; else EXPECT_EQ(rate, 0) << "i=" << i; } @@ -92,6 +95,7 @@ TEST_F(CSndRateEstFixture, CBRSendingAfterPause) // Those empty samples should be included in bitrate estimation. TEST_F(CSndRateEstFixture, CBRSendingShortPause) { + const size_t hdrBytes = srt::CPacket::HDR_SIZE; // Send 100 packets with 1000 bytes each for (int i = 0; i < 3100; ++i) { @@ -102,9 +106,9 @@ TEST_F(CSndRateEstFixture, CBRSendingShortPause) const auto rate = m_rateEst.getRate(); if (i >= 1500 && i < 2000) - EXPECT_EQ(rate, 658000) << "i=" << i; + EXPECT_EQ(rate, 658000 + 500 * hdrBytes) << "i=" << i; else if (i >= 100) - EXPECT_EQ(rate, 1316000) << "i=" << i; + EXPECT_EQ(rate, 1316000 + 1000 * hdrBytes) << "i=" << i; else EXPECT_EQ(rate, 0) << "i=" << i; } diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index b7acda37a..a4c90af37 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -70,6 +70,9 @@ class TestSocketOptions }; auto accept_res = async(launch::async, accept_async, m_listen_sock); + // Make sure the thread was kicked + this_thread::yield(); + const int connect_res = Connect(); EXPECT_EQ(connect_res, SRT_SUCCESS); @@ -188,9 +191,12 @@ const OptionTestEntry g_test_matrix_options[] = { SRTO_LATENCY, "SRTO_LATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 200, {-1} }, //SRTO_LINGER { SRTO_LOSSMAXTTL, "SRTO_LOSSMAXTTL", RestrictionType::POST, sizeof(int), 0, INT32_MAX, 0, 10, {} }, - //SRTO_MAXBW + { SRTO_MAXBW, "SRTO_MAXBW", RestrictionType::POST, sizeof(int64_t), int64_t(-1), INT64_MAX, int64_t(-1), int64_t(200000), {int64_t(-2)}}, +#ifdef ENABLE_MAXREXMITBW + { SRTO_MAXREXMITBW, "SRTO_MAXREXMITBW", RestrictionType::POST, sizeof(int64_t), int64_t(-1), INT64_MAX, int64_t(-1), int64_t(200000), {int64_t(-2)}}, +#endif { SRTO_MESSAGEAPI, "SRTO_MESSAGEAPI", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, - //SRTO_MININPUTBW + { SRTO_MININPUTBW, "SRTO_MININPUTBW", RestrictionType::POST, sizeof(int64_t), int64_t(0), INT64_MAX, int64_t(0), int64_t(200000), {int64_t(-1)}}, { SRTO_MINVERSION, "SRTO_MINVERSION", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0x010000, 0x010300, {} }, { SRTO_MSS, "SRTO_MSS", RestrictionType::PREBIND, sizeof(int), 76, 65536, 1500, 1400, {-1, 0, 75} }, { SRTO_NAKREPORT, "SRTO_NAKREPORT", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, @@ -234,12 +240,12 @@ template void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const ValueType& value, const char* desc) { ValueType opt_val; - int opt_len = 0; + int opt_len = (int) entry.opt_len; EXPECT_EQ(srt_getsockopt(sock, 0, entry.optid, &opt_val, &opt_len), SRT_SUCCESS) << "Getting " << entry.optname << " returned error: " << srt_getlasterror_str(); EXPECT_EQ(opt_val, value) << desc << ": Wrong " << entry.optname << " value " << opt_val; - EXPECT_EQ(opt_len, entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; + EXPECT_EQ(opt_len, (int) entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; } typedef char const* strptr; @@ -252,7 +258,7 @@ void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const << "Getting " << entry.optname << " returned error: " << srt_getlasterror_str(); EXPECT_EQ(strncmp(opt_val, value, min(opt_len, (int)entry.opt_len)), 0) << desc << ": Wrong " << entry.optname << " value " << opt_val; - EXPECT_EQ(opt_len, entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; + EXPECT_EQ(opt_len, (int) entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; } template @@ -850,6 +856,29 @@ TEST_F(TestSocketOptions, StreamIDWrongLen) EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); } +//Check if setting -1 as optlen returns an error +TEST_F(TestSocketOptions, StringOptLenInvalid) +{ + const string test_string = "test1234567"; + const string srto_congestion_string ="live"; + const string fec_config = "fec,cols:10,rows:10"; + + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, test_string.c_str(), -1), SRT_ERROR); + EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_BINDTODEVICE, test_string.c_str(), -1), SRT_ERROR); + EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_CONGESTION, srto_congestion_string.c_str(), -1), SRT_ERROR); + EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_PACKETFILTER, fec_config.c_str(), -1), SRT_ERROR); + EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_PASSPHRASE, test_string.c_str(), -1), SRT_ERROR); + EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); +} + // Try to set/get a 13-character string in SRTO_STREAMID. // This tests checks that the StreamID is set to the correct size // while it is transmitted as 16 characters in the Stream ID HS extension. diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 844705ea6..d06b5e5f5 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -159,7 +159,7 @@ TEST(SyncRandom, GenRandomInt) { const int rand_val = genRandomInt(0, int(mn.size()) - 1); ASSERT_GE(rand_val, 0); - ASSERT_LT(rand_val, mn.size()); + ASSERT_LT(rand_val, (int) mn.size()); ++mn[rand_val]; } @@ -445,7 +445,7 @@ TEST(SyncEvent, WaitForTwoNotifyOne) using future_t = decltype(async(launch::async, wait_async, &cond, &mutex, timeout, 0)); - future_t future_result[2] = { + std::array future_result = { async(launch::async, wait_async, &cond, &mutex, timeout, 0), async(launch::async, wait_async, &cond, &mutex, timeout, 1) }; @@ -462,9 +462,9 @@ TEST(SyncEvent, WaitForTwoNotifyOne) using wait_t = decltype(future_t().wait_for(chrono::microseconds(0))); - wait_t wait_state[2] = { - move(future_result[0].wait_for(chrono::microseconds(500))), - move(future_result[1].wait_for(chrono::microseconds(500))) + std::array wait_state = { + future_result[0].wait_for(chrono::microseconds(500)), + future_result[1].wait_for(chrono::microseconds(500)) }; cerr << "SyncEvent::WaitForTwoNotifyOne: NOTIFICATION came from " << notified_clients.size() @@ -587,7 +587,8 @@ TEST(SyncEvent, WaitForNotifyAll) /*****************************************************************************/ void* dummythread(void* param) { - *(bool*)(param) = true; + auto& thread_finished = *(srt::sync::atomic*)param; + thread_finished = true; return nullptr; } @@ -608,6 +609,91 @@ TEST(SyncThread, Joinable) EXPECT_FALSE(foo.joinable()); } +/*****************************************************************************/ +/* + * SharedMutex + */ + /*****************************************************************************/ +TEST(SharedMutex, LockWriteRead) +{ + SharedMutex mut; + + mut.lock(); + EXPECT_FALSE(mut.try_lock_shared()); + +} + +TEST(SharedMutex, LockReadWrite) +{ + SharedMutex mut; + + mut.lock_shared(); + EXPECT_FALSE(mut.try_lock()); + +} + +TEST(SharedMutex, LockReadTwice) +{ + SharedMutex mut; + + mut.lock_shared(); + mut.lock_shared(); + EXPECT_TRUE(mut.try_lock_shared()); +} + +TEST(SharedMutex, LockWriteTwice) +{ + SharedMutex mut; + + mut.lock(); + EXPECT_FALSE(mut.try_lock()); +} + +TEST(SharedMutex, LockUnlockWrite) +{ + SharedMutex mut; + mut.lock(); + EXPECT_FALSE(mut.try_lock()); + mut.unlock(); + EXPECT_TRUE(mut.try_lock()); +} + +TEST(SharedMutex, LockUnlockRead) +{ + SharedMutex mut; + + mut.lock_shared(); + EXPECT_FALSE(mut.try_lock()); + + mut.unlock_shared(); + EXPECT_TRUE(mut.try_lock()); +} + +TEST(SharedMutex, LockedReadCount) +{ + SharedMutex mut; + int count = 0; + + mut.lock_shared(); + count++; + ASSERT_EQ(mut.getReaderCount(), count); + + mut.lock_shared(); + count++; + ASSERT_EQ(mut.getReaderCount(), count); + + mut.unlock_shared(); + count--; + ASSERT_EQ(mut.getReaderCount(), count); + + mut.unlock_shared(); + count--; + ASSERT_EQ(mut.getReaderCount(), count); + + EXPECT_TRUE(mut.try_lock()); +} + + /*****************************************************************************/ /* * FormatTime diff --git a/test/test_utilities.cpp b/test/test_utilities.cpp index 1d2c1aa59..28c14044e 100644 --- a/test/test_utilities.cpp +++ b/test/test_utilities.cpp @@ -160,11 +160,11 @@ TEST(CircularBuffer, Overall) ASSERT_EQ(output.d, 22.1); IF_HEAVY_LOGGING(cerr << "Pushing 1 aslong there is capacity:\n"); - int i = 0; + IF_HEAVY_LOGGING(int i = 0); while (buf.push(1) != -1) { IF_HEAVY_LOGGING(cerr << "Pushed, begin=" << buf.m_xBegin << " end=" << buf.m_xEnd << endl); - ++i; + IF_HEAVY_LOGGING(++i); } IF_HEAVY_LOGGING(cerr << "Done " << i << " operations, buffer:\n"); IF_HEAVY_LOGGING(ShowCircularBuffer(buf)); diff --git a/testing/srt-test-live.cpp b/testing/srt-test-live.cpp index aa6b45d6a..12478e461 100644 --- a/testing/srt-test-live.cpp +++ b/testing/srt-test-live.cpp @@ -64,6 +64,7 @@ #include #include +#include "srt_compat.h" #include "apputil.hpp" #include "uriparser.hpp" // UriParser #include "socketoptions.hpp" @@ -267,7 +268,7 @@ bool CheckMediaSpec(const string& prefix, const vector& spec, string& w_ for (string& a: adrs) w_outspec += a + ","; - Verb() << "NOTE: " << prefix << " specification set as: " << (w_outspec); + Verb("NOTE: ", prefix, " specification set as: ", (w_outspec)); return true; } @@ -293,7 +294,7 @@ extern "C" int SrtCheckGroupHook(void* , SRTSOCKET acpsock, int , const sockaddr int type; int size = sizeof type; srt_getsockflag(acpsock, SRTO_GROUPCONNECT, &type, &size); - Verb() << "listener: @" << acpsock << " - accepting " << (type ? "GROUP" : "SINGLE") << VerbNoEOL; + Verb("listener: @", acpsock, " - accepting ", (type ? "GROUP" : "SINGLE"), VerbNoEOL); if (type != 0) { SRT_GROUP_TYPE gt; @@ -301,12 +302,12 @@ extern "C" int SrtCheckGroupHook(void* , SRTSOCKET acpsock, int , const sockaddr if (-1 != srt_getsockflag(acpsock, SRTO_GROUPTYPE, >, &size)) { if (size_t(gt) < Size(gtypes)) - Verb() << " type=" << gtypes[gt] << VerbNoEOL; + Verb(" type=", gtypes[gt], VerbNoEOL); else - Verb() << " type=" << int(gt) << VerbNoEOL; + Verb(" type=", int(gt), VerbNoEOL); } } - Verb() << " connection"; + Verb(" connection"); return 0; } @@ -316,7 +317,7 @@ extern "C" int SrtUserPasswordHook(void* , SRTSOCKET acpsock, int hsv, const soc { if (hsv < 5) { - Verb() << "SrtUserPasswordHook: HS version 4 doesn't support extended handshake"; + Verb("SrtUserPasswordHook: HS version 4 doesn't support extended handshake"); return -1; } @@ -817,7 +818,7 @@ int main( int argc, char** argv ) return 0; } - Verb() << "MEDIA CREATION FAILED: " << x.what() << " - exiting."; + Verb("MEDIA CREATION FAILED: ", x.what(), " - exiting."); // Don't speak anything when no -v option. // (the "requested interrupt" will be printed anyway) @@ -846,10 +847,10 @@ int main( int argc, char** argv ) if (transmit_use_sourcetime && src->uri.type() != UriParser::SRT) { - Verb() << "WARNING: -st option is effective only if the target type is SRT"; + Verb("WARNING: -st option is effective only if the target type is SRT"); } - Verb() << "STARTING TRANSMISSION: '" << source_spec << "' --> '" << target_spec << "'"; + Verb("STARTING TRANSMISSION: '", source_spec, "' --> '", target_spec, "'"); // After the time has been spent in the creation // (including waiting for connection) @@ -875,41 +876,41 @@ int main( int argc, char** argv ) { if (stoptime == 0 && timeout != -1 ) { - Verb() << "[." << VerbNoEOL; + Verb("[.", VerbNoEOL); alarm(timeout); } else { alarm(0); } - Verb() << " << ... " << VerbNoEOL; + Verb(", ... ", VerbNoEOL); g_interrupt_reason = "reading"; const MediaPacket& data = src->Read(chunk); - Verb() << " << " << data.payload.size() << " -> " << VerbNoEOL; + Verb(", ", data.payload.size(), " -> ", VerbNoEOL); if ( data.payload.empty() && src->End() ) { - Verb() << "EOS"; + Verb("EOS"); break; } g_interrupt_reason = "writing"; tar->Write(data); if (stoptime == 0 && timeout != -1 ) { - Verb() << ".] " << VerbNoEOL; + Verb(".] ", VerbNoEOL); alarm(0); } if ( tar->Broken() ) { - Verb() << " OUTPUT broken"; + Verb(" OUTPUT broken"); break; } - Verb() << "sent"; + Verb("sent"); if (::transmit_int_state) { - Verror() << "\n (interrupted on request)"; + Verror("\n (interrupted on request)"); break; } @@ -921,7 +922,7 @@ int main( int argc, char** argv ) int remain = int(stoptime - final_delay - elapsed); if (remain < 0) { - Verror() << "\n (interrupted on timeout: elapsed " << elapsed << "s) - waiting " << final_delay << "s for cleanup"; + Verror("\n (interrupted on timeout: elapsed ", elapsed, "s) - waiting ", final_delay, "s for cleanup"); this_thread::sleep_for(chrono::seconds(final_delay)); break; } @@ -933,17 +934,17 @@ int main( int argc, char** argv ) if (!skip_flushing) { - Verror() << "(DEBUG) EOF when reading file. Looping until the sending bufer depletes.\n"; + Verror("(DEBUG) EOF when reading file. Looping until the sending bufer depletes.\n"); for (;;) { size_t still = tar->Still(); if (still == 0) { - Verror() << "(DEBUG) DEPLETED. Done.\n"; + Verror("(DEBUG) DEPLETED. Done.\n"); break; } - Verror() << "(DEBUG)... still " << still << " bytes (sleep 1s)\n"; + Verror("(DEBUG)... still ", still, " bytes (sleep 1s)\n"); this_thread::sleep_for(chrono::seconds(1)); } } @@ -951,16 +952,16 @@ int main( int argc, char** argv ) if (stoptime != 0 && ::timer_state) { - Verror() << "Exit on timeout."; + Verror("Exit on timeout."); } else if (::transmit_int_state) { - Verror() << "Exit on interrupt."; + Verror("Exit on interrupt."); // Do nothing. } else { - Verror() << "STD EXCEPTION: " << x.what(); + Verror("STD EXCEPTION: ", x.what()); } if ( crashonx ) @@ -968,7 +969,7 @@ int main( int argc, char** argv ) if (final_delay > 0) { - Verror() << "Waiting " << final_delay << "s for possible cleanup..."; + Verror("Waiting ", final_delay, "s for possible cleanup..."); this_thread::sleep_for(chrono::seconds(final_delay)); } if (stoptime != 0 && ::timer_state) @@ -978,7 +979,7 @@ int main( int argc, char** argv ) } catch (...) { - Verror() << "UNKNOWN type of EXCEPTION"; + Verror("UNKNOWN type of EXCEPTION"); if ( crashonx ) throw; diff --git a/testing/srt-test-mpbond.cpp b/testing/srt-test-mpbond.cpp index 03066363a..60d91df2c 100644 --- a/testing/srt-test-mpbond.cpp +++ b/testing/srt-test-mpbond.cpp @@ -42,6 +42,10 @@ srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-mpbond"); +using namespace srt; +using namespace std; + + volatile bool mpbond_int_state = false; void OnINT_SetIntState(int) { @@ -49,9 +53,6 @@ void OnINT_SetIntState(int) mpbond_int_state = true; } -using namespace srt; - - int main( int argc, char** argv ) { // This is mainly required on Windows to initialize the network system, @@ -208,7 +209,6 @@ int main( int argc, char** argv ) return 1; } - auto s = new SrtSource; unique_ptr src; unique_ptr tar; @@ -221,6 +221,7 @@ int main( int argc, char** argv ) Verb() << "SRT -> " << outspec; tar = Target::Create(outspec); + auto s = new SrtSource; s->Acquire(conngrp); src.reset(s); } diff --git a/testing/srt-test-multiplex.cpp b/testing/srt-test-multiplex.cpp index 62c7fbd3d..deb36554c 100644 --- a/testing/srt-test-multiplex.cpp +++ b/testing/srt-test-multiplex.cpp @@ -403,7 +403,7 @@ void Stall() ++i_next; if (i->has_quit) { - Verb() << "Found QUIT mediumpair: " << i->name << " - removing from base"; + Verb("Found QUIT mediumpair: ", i->name, " - removing from base"); i->Stop(); g_media_base.media.erase(i); } @@ -411,7 +411,7 @@ void Stall() if (g_media_base.media.empty()) { - Verb() << "All media have quit. Marking exit."; + Verb("All media have quit. Marking exit."); break; } } diff --git a/testing/srt-test-relay.cpp b/testing/srt-test-relay.cpp index dfabef62b..e7e5ae574 100755 --- a/testing/srt-test-relay.cpp +++ b/testing/srt-test-relay.cpp @@ -45,6 +45,7 @@ written by #include "threadname.h" +using namespace std; bool Upload(UriParser& srt, UriParser& file); @@ -366,9 +367,9 @@ SrtMainLoop::SrtMainLoop(const string& srt_uri, bool input_echoback, const strin Verb() << "SRT set up as input source and the first output target"; // Add SRT medium to output targets, and keep input medium empty. - unique_ptr m { new TargetMedium }; - m->Setup(m_srt_relay.get()); - m_output_media.push_back(move(m)); + unique_ptr med { new TargetMedium }; + med->Setup(m_srt_relay.get()); + m_output_media.push_back(move(med)); } else { diff --git a/testing/testactivemedia.cpp b/testing/testactivemedia.cpp index 7d062f49d..96344f0b2 100644 --- a/testing/testactivemedia.cpp +++ b/testing/testactivemedia.cpp @@ -1,6 +1,9 @@ #include "testactivemedia.hpp" +using namespace std; + + void SourceMedium::Runner() { srt::ThreadName::set("SourceRN"); diff --git a/testing/testactivemedia.hpp b/testing/testactivemedia.hpp index f4bc360ba..011dcbfe7 100644 --- a/testing/testactivemedia.hpp +++ b/testing/testactivemedia.hpp @@ -59,7 +59,7 @@ struct Medium std::ostringstream tns; tns << typeid(*this).name() << ":" << this; srt::ThreadName tn(tns.str()); - thr = thread( [this] { RunnerBase(); } ); + thr = std::thread( [this] { RunnerBase(); } ); } void quit() @@ -89,12 +89,12 @@ struct Medium if (Verbose::on) Verb() << VerbLock << "Medium " << this << " exited with Transmission Error:\n\t" << e.what(); else - cerr << "Transmission Error: " << e.what() << endl; + std::cerr << "Transmission Error: " << e.what() << std::endl; } catch (...) { if (Verbose::on) Verb() << VerbLock << "Medium " << this << " exited with UNKNOWN EXCEPTION:"; else - cerr << "UNKNOWN EXCEPTION on medium\n"; + std::cerr << "UNKNOWN EXCEPTION on medium\n"; } } @@ -150,7 +150,7 @@ struct TargetMedium: Medium bool Schedule(const MediaPacket& data) { LOGP(applog.Debug, "TargetMedium::Schedule LOCK ... "); - lock_guard lg(buffer_lock); + std::lock_guard lg(buffer_lock); LOGP(applog.Debug, "TargetMedium::Schedule LOCKED - checking: running=", running, " interrupt=", ::transmit_int_state); if (!running || ::transmit_int_state) { @@ -166,13 +166,13 @@ struct TargetMedium: Medium void Clear() { - lock_guard lg(buffer_lock); + std::lock_guard lg(buffer_lock); buffer.clear(); } void Interrupt() { - lock_guard lg(buffer_lock); + std::lock_guard lg(buffer_lock); running = false; ready.notify_one(); } diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index 2d6635288..b9d8a0413 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -1059,8 +1059,8 @@ void SrtCommon::OpenGroupClient() if (!extras.empty()) { Verb() << "?" << extras[0] << VerbNoEOL; - for (size_t i = 1; i < extras.size(); ++i) - Verb() << "&" << extras[i] << VerbNoEOL; + for (size_t ii = 1; ii < extras.size(); ++ii) + Verb() << "&" << extras[ii] << VerbNoEOL; } Verb(); @@ -1130,15 +1130,15 @@ void SrtCommon::OpenGroupClient() // spread the setting on all sockets. ConfigurePost(m_sock); - for (size_t i = 0; i < targets.size(); ++i) + for (size_t j = 0; j < targets.size(); ++j) { // As m_group_nodes is simply transformed into 'targets', // one index can be used to index them all. You don't // have to check if they have equal addresses because they // are equal by definition. - if (targets[i].id != -1 && targets[i].errorcode == SRT_SUCCESS) + if (targets[j].id != -1 && targets[j].errorcode == SRT_SUCCESS) { - m_group_nodes[i].socket = targets[i].id; + m_group_nodes[j].socket = targets[j].id; } } @@ -1159,9 +1159,9 @@ void SrtCommon::OpenGroupClient() } m_group_data.resize(size); - for (size_t i = 0; i < m_group_nodes.size(); ++i) + for (size_t j = 0; j < m_group_nodes.size(); ++j) { - SRTSOCKET insock = m_group_nodes[i].socket; + SRTSOCKET insock = m_group_nodes[j].socket; if (insock == -1) { Verb() << "TARGET '" << sockaddr_any(targets[i].peeraddr).str() << "' connection failed."; @@ -1194,11 +1194,11 @@ void SrtCommon::OpenGroupClient() NULL, NULL) != -1) { Verb() << "[C]" << VerbNoEOL; - for (int i = 0; i < len1; ++i) - Verb() << " " << ready_conn[i] << VerbNoEOL; + for (int ii = 0; ii < len1; ++ii) + Verb() << " " << ready_conn[ii] << VerbNoEOL; Verb() << "[E]" << VerbNoEOL; - for (int i = 0; i < len2; ++i) - Verb() << " " << ready_err[i] << VerbNoEOL; + for (int ii = 0; ii < len2; ++ii) + Verb() << " " << ready_err[ii] << VerbNoEOL; Verb() << ""; diff --git a/testing/testmedia.hpp b/testing/testmedia.hpp index 337f5f365..be72471d1 100644 --- a/testing/testmedia.hpp +++ b/testing/testmedia.hpp @@ -29,8 +29,6 @@ extern std::atomic transmit_int_state; extern std::shared_ptr transmit_stats_writer; -using namespace std; - const srt_logging::LogFA SRT_LOGFA_APP = 10; extern srt_logging::Logger applog; @@ -57,7 +55,7 @@ class SrtCommon struct ConnectionBase { - string host; + std::string host; int port; int weight = 0; SRTSOCKET socket = SRT_INVALID_SOCK; @@ -65,7 +63,7 @@ class SrtCommon srt::sockaddr_any target; int token = -1; - ConnectionBase(string h, int p): host(h), port(p), source(AF_INET) {} + ConnectionBase(std::string h, int p): host(h), port(p), source(AF_INET) {} }; struct Connection: ConnectionBase @@ -76,7 +74,7 @@ class SrtCommon int error = SRT_SUCCESS; int reason = SRT_REJ_UNKNOWN; - Connection(string h, int p): ConnectionBase(h, p) {} + Connection(std::string h, int p): ConnectionBase(h, p) {} Connection(Connection&& old): ConnectionBase(old) { #if ENABLE_BONDING @@ -101,14 +99,14 @@ class SrtCommon int m_timeout = 0; //< enforces using SRTO_SNDTIMEO or SRTO_RCVTIMEO, depending on @a m_direction bool m_tsbpdmode = true; int m_outgoing_port = 0; - string m_mode; - string m_adapter; - map m_options; // All other options, as provided in the URI - vector m_group_nodes; - string m_group_type; - string m_group_config; + std::string m_mode; + std::string m_adapter; + std::map m_options; // All other options, as provided in the URI + std::vector m_group_nodes; + std::string m_group_type; + std::string m_group_config; #if ENABLE_BONDING - vector m_group_data; + std::vector m_group_data; #ifdef SRT_OLD_APP_READER int32_t m_group_seqno = -1; @@ -117,7 +115,7 @@ class SrtCommon int32_t sequence; bytevector packet; }; - map m_group_positions; + std::map m_group_positions; SRTSOCKET m_group_active; // The link from which the last packet was delivered #endif #endif @@ -131,8 +129,8 @@ class SrtCommon void UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpdata_size); public: - void InitParameters(string host, string path, map par); - void PrepareListener(string host, int port, int backlog); + void InitParameters(std::string host, std::string path, std::map par); + void PrepareListener(std::string host, int port, int backlog); void StealFrom(SrtCommon& src); void AcceptNewClient(); @@ -150,22 +148,22 @@ class SrtCommon protected: - void Error(string src, int reason = SRT_REJ_UNKNOWN, int force_result = 0); - void Init(string host, int port, string path, map par, SRT_EPOLL_OPT dir); + void Error(std::string src, int reason = SRT_REJ_UNKNOWN, int force_result = 0); + void Init(std::string host, int port, std::string path, std::map par, SRT_EPOLL_OPT dir); int AddPoller(SRTSOCKET socket, int modes); virtual int ConfigurePost(SRTSOCKET sock); virtual int ConfigurePre(SRTSOCKET sock); - void OpenClient(string host, int port); + void OpenClient(std::string host, int port); #if ENABLE_BONDING void OpenGroupClient(); #endif void PrepareClient(); void SetupAdapter(const std::string& host, int port); - void ConnectClient(string host, int port); - void SetupRendezvous(string adapter, string host, int port); + void ConnectClient(std::string host, int port); + void SetupRendezvous(std::string adapter, std::string host, int port); - void OpenServer(string host, int port, int backlog = 1) + void OpenServer(std::string host, int port, int backlog = 1) { PrepareListener(host, port, backlog); if (transmit_accept_hook_fn) @@ -175,7 +173,7 @@ class SrtCommon AcceptNewClient(); } - void OpenRendezvous(string adapter, string host, int port) + void OpenRendezvous(std::string adapter, std::string host, int port) { PrepareClient(); SetupRendezvous(adapter, host, port); @@ -284,11 +282,11 @@ class SrtModel: public SrtCommon public: bool is_caller = false; bool is_rend = false; - string m_host; + std::string m_host; int m_port = 0; - SrtModel(string host, int port, map par); + SrtModel(std::string host, int port, std::map par); void Establish(std::string& w_name); void Close() @@ -320,7 +318,7 @@ class UdpSource: public virtual Source, public virtual UdpCommon bool eof = true; public: - UdpSource(string host, int port, const map& attr); + UdpSource(std::string host, int port, const std::map& attr); MediaPacket Read(size_t chunk) override; @@ -331,7 +329,7 @@ class UdpSource: public virtual Source, public virtual UdpCommon class UdpTarget: public virtual Target, public virtual UdpCommon { public: - UdpTarget(string host, int port, const map& attr); + UdpTarget(std::string host, int port, const std::map& attr); void Write(const MediaPacket& data) override; bool IsOpen() override { return m_sock != -1; } @@ -341,7 +339,7 @@ class UdpTarget: public virtual Target, public virtual UdpCommon class UdpRelay: public Relay, public UdpSource, public UdpTarget { public: - UdpRelay(string host, int port, const map& attr): + UdpRelay(std::string host, int port, const std::map& attr): UdpSource(host, port, attr), UdpTarget(host, port, attr) {