diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml index 2c05cc06b..a144ee407 100644 --- a/.github/workflows/abi.yml +++ b/.github/workflows/abi.yml @@ -2,60 +2,94 @@ name: ABI checks on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] env: SRT_BASE: v1.5.0 jobs: - build: - name: ABI checks + build_pr: + name: Build current version runs-on: ubuntu-20.04 + outputs: + SRT_BASE: ${{ steps.commands.outputs.SRT_BASE }} steps: - uses: actions/checkout@v3 with: - path: pull_request + path: gitview_pr - name: configure run: | - cd pull_request + cd gitview_pr 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 ./ + sudo apt install -y tcl + cd gitview_pr/_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" + SRT_TAG_VERSION=v$(../scripts/get-build-version.tcl full) + SRT_TAG=${SRT_TAG_VERSION}dev-$(git rev-parse --short HEAD) + abi-dumper libsrt.so -o libsrt-pr.dump -public-headers installdir/usr/local/include/srt/ -lver $SRT_TAG + SRT_BASE=v$(../scripts/get-build-version.tcl base) + if [[ $SRT_TAG_VERSION == $SRT_BASE ]]; then + echo "NOT CHECKING ABI: base version is being built: $SRT_TAG" + echo "SRT_BASE=''" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "SRT_BASE=$SRT_BASE" >> "$GITHUB_OUTPUT" + + build_base: + name: Build base version + runs-on: ubuntu-20.04 + needs: build_pr + if: ${{ needs.build_pr.outputs.SRT_BASE != '' }} + + env: + SRT_BASE: ${{ needs.build_pr.outputs.SRT_BASE }} + steps: - uses: actions/checkout@v3 with: - path: tag + path: gitview_base ref: ${{ env.SRT_BASE }} - name: configure_tag run: | echo $SRT_TAG_VERSION - cd tag + cd gitview_base mkdir _build && cd _build cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_UNITTESTS=ON ../ - name: build_tag run: | - cd tag + cd gitview_base 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 + abi-dumper libsrt.so -o libsrt-base.dump -public-headers installdir/usr/local/include/srt/ -lver $SRT_BASE + + check_abi: + name: Compare ABI + runs-on: ubuntu-20.04 + needs: [build_pr, build_base] + env: + SRT_BASE: ${{ needs.build_pr.outputs.SRT_BASE }} + + steps: - 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 + #git clone https://github.com/lvc/abi-compliance-checker.git + cd gitview_pr/submodules + git submodule update --init abi-compliance-checker + cd abi-compliance-checker && sudo make install && cd ../ + cd ../.. + abi-compliance-checker -l libsrt -old gitview_base/_build/libsrt-base.dump -new gitview_pr/_build/libsrt-pr.dump + RES=$? + if (( $RES != 0 )); then + echo "ABI/API Compatibility check failed with value $?" + exit $RES + fi + - name: Download report + uses: actions/download-artifact@v4 + with: + path: compat_reports diff --git a/.github/workflows/android.yaml b/.github/workflows/android.yaml index 0af85fda3..f1599e411 100644 --- a/.github/workflows/android.yaml +++ b/.github/workflows/android.yaml @@ -2,9 +2,9 @@ name: Android on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] jobs: build: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4a337b6ca..f7f417259 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,9 +2,9 @@ name: "CodeQL" on: push: - branches: [ "master", "experimental/socket-groups" ] + branches: [ "master", "dev" ] pull_request: - branches: [ "master" ] + branches: [ "master", "dev" ] jobs: analyze: diff --git a/.github/workflows/cxx11-macos.yaml b/.github/workflows/cxx11-macos.yaml index e3e6b1f8a..b6ca2ca5e 100644 --- a/.github/workflows/cxx11-macos.yaml +++ b/.github/workflows/cxx11-macos.yaml @@ -2,9 +2,9 @@ name: cxx11 on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] jobs: build: diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index 5566ed801..944337a7f 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -2,14 +2,14 @@ name: cxx11 on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] types: [opened, synchronize, reopened] jobs: build: name: ubuntu - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 env: BUILD_WRAPPER_OUT_DIR: sonar-output # Directory where build-wrapper output will be placed steps: diff --git a/.github/workflows/cxx11-win.yaml b/.github/workflows/cxx11-win.yaml index f1554053d..ee2488666 100644 --- a/.github/workflows/cxx11-win.yaml +++ b/.github/workflows/cxx11-win.yaml @@ -2,9 +2,9 @@ name: cxx11 on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] jobs: build: @@ -17,7 +17,7 @@ jobs: - name: configure run: | md _build && cd _build - cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DUSE_CXX_STD=c++11 + cmake ../ -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_LOCALIF_WIN32=ON -DUSE_CXX_STD=c++11 - name: build run: cd _build && cmake --build ./ --config Release --verbose - name: test diff --git a/.github/workflows/iOS.yaml b/.github/workflows/iOS.yaml index 0fb11542b..30e54167b 100644 --- a/.github/workflows/iOS.yaml +++ b/.github/workflows/iOS.yaml @@ -2,9 +2,9 @@ name: iOS on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] jobs: build: diff --git a/.github/workflows/s390x-focal.yaml b/.github/workflows/s390x-focal.yaml index f1b6c7508..d91991456 100644 --- a/.github/workflows/s390x-focal.yaml +++ b/.github/workflows/s390x-focal.yaml @@ -2,9 +2,9 @@ name: QEMU to run s390x-focal on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] jobs: Tests: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..0c81e9233 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/abi-compliance-checker"] + path = submodules/abi-compliance-checker + url = https://github.com/lvc/abi-compliance-checker.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e5aa190cd..7e7cf83a7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ # cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) -set (SRT_VERSION 1.0.2) +set (SRT_VERSION 1.6.0) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(haiUtil) # needed for set_version_variables @@ -173,6 +173,7 @@ option(ENABLE_PKTINFO "Enable using IP_PKTINFO to allow the listener extracting option(ENABLE_RELATIVE_LIBPATH "Should application contain relative library paths, like ../lib" OFF) option(ENABLE_GETNAMEINFO "In-logs sockaddr-to-string should do rev-dns" OFF) option(ENABLE_UNITTESTS "Enable unit tests" OFF) +option(ENABLE_UNITTESTS_DISCOVERY "Do not discover unit tests when enabled" ON) option(ENABLE_ENCRYPTION "Enable encryption in SRT" ON) option(ENABLE_AEAD_API_PREVIEW "Enable AEAD API preview in SRT" Off) option(ENABLE_MAXREXMITBW "Enable SRTO_MAXREXMITBW (v1.6.0 API preview)" Off) @@ -186,6 +187,7 @@ option(USE_OPENSSL_PC "Use pkg-config to find OpenSSL libraries" ON) option(SRT_USE_OPENSSL_STATIC_LIBS "Link OpenSSL libraries statically." OFF) option(USE_BUSY_WAITING "Enable more accurate sending times at a cost of potentially higher CPU load" OFF) option(USE_GNUSTL "Get c++ library/headers from the gnustl.pc" OFF) +option(ENABLE_LOCALIF_WIN32 "Enable local interface check ability on Windows (adds Iphlpapi.lib dep)" OFF) option(ENABLE_SOCK_CLOEXEC "Enable setting SOCK_CLOEXEC on a socket" ON) option(ENABLE_SHOW_PROJECT_CONFIG "Enable show Project Configuration" OFF) @@ -739,7 +741,7 @@ if (FORCE_CXX_STANDARD) endif() # add extra warning flags for gccish compilers -if (HAVE_COMPILER_GNU_COMPAT) +if ((HAVE_COMPILER_GNU_COMPAT) AND (NOT DEFINED SRT_GCC_WARN)) set (SRT_GCC_WARN "-Wall -Wextra") if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0 AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set (SRT_GCC_WARN "${SRT_GCC_WARN} -Wshadow=local") @@ -1113,6 +1115,10 @@ if (srt_libspec_shared) if (ENABLE_ENCRYPTION) target_link_libraries(${TARGET_srt}_shared PRIVATE ${SSL_LIBRARIES}) endif() + if (WIN32 AND ENABLE_LOCALIF_WIN32) + target_link_libraries(${TARGET_srt}_shared PRIVATE Iphlpapi) + add_definitions(-DSRT_ENABLE_LOCALIF_WIN32) + endif() if (MICROSOFT) target_link_libraries(${TARGET_srt}_shared PRIVATE ws2_32.lib) if (NOT (ENABLE_ENCRYPTION AND "${USE_ENCLIB}" STREQUAL "botan")) @@ -1134,6 +1140,10 @@ endif() if (srt_libspec_static) add_library(${TARGET_srt}_static STATIC ${OBJECT_LIB_SUPPORT} ${VIRTUAL_srt}) + if (WIN32 AND ENABLE_LOCALIF_WIN32) + target_link_libraries(${TARGET_srt}_static PRIVATE Iphlpapi) + add_definitions(-DSRT_ENABLE_LOCALIF_WIN32) + endif() # For Windows, leave the name to be "srt_static.lib". # Windows generates two different library files: @@ -1333,6 +1343,10 @@ macro(srt_add_program_dont_install name) add_executable(${name} ${ARGN}) target_include_directories(${name} PRIVATE apps) target_include_directories(${name} PRIVATE common) + if (WIN32 AND ENABLE_LOCALIF_WIN32) + target_link_libraries(${name} Iphlpapi) + add_definitions(-DSRT_ENABLE_LOCALIF_WIN32) + endif() endmacro() macro(srt_add_program name) @@ -1580,7 +1594,9 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) #set_tests_properties(test-srt PROPERTIES RUN_SERIAL TRUE) else() set_tests_properties(${tests_srt} PROPERTIES RUN_SERIAL TRUE) - gtest_discover_tests(test-srt) + if (ENABLE_UNITTESTS_DISCOVERY) + gtest_discover_tests(test-srt) + endif() endif() enable_testing() diff --git a/apps/apputil.cpp b/apps/apputil.cpp index 22c31521b..1efabd927 100644 --- a/apps/apputil.cpp +++ b/apps/apputil.cpp @@ -17,6 +17,7 @@ #include #include "srt.h" // Required for SRT_SYNC_CLOCK_* definitions. +#include "common.h" #include "apputil.hpp" #include "netinet_any.h" #include "srt_compat.h" @@ -389,3 +390,40 @@ void PrintLibVersion() const int patch = srtver % 0x100; cerr << "SRT Library version: " << major << "." << minor << "." << patch << ", clock type: " << SRTClockTypeStr() << endl; } + +bool IsTargetAddrSelf(const sockaddr* boundaddr, const sockaddr* targetaddr) +{ + sockaddr_any bound = boundaddr; + sockaddr_any target = targetaddr; + + if (!bound.isany()) + { + // Bound to a specific local address, so only check if + // this isn't the same address as 'target'. + if (target.equal_address(bound)) + { + return true; + } + } + else + { + // Bound to INADDR_ANY, so check matching with any local IP address + const vector& locals = srt::GetLocalInterfaces(); + + // If any of the above function fails, it will collect + // no local interfaces, so it's impossible to check anything. + // OTOH it should also mean that the network isn't working, + // so it's unlikely, as well as no address should match the + // local address anyway. + for (size_t i = 0; i < locals.size(); ++i) + { + if (locals[i].addr.equal_address(target)) + { + return true; + } + } + } + + return false; +} + diff --git a/apps/apputil.hpp b/apps/apputil.hpp index 1a0b158e0..9242f0c36 100644 --- a/apps/apputil.hpp +++ b/apps/apputil.hpp @@ -336,6 +336,7 @@ std::string OptionHelpItem(const OptionName& o); const char* SRTClockTypeStr(); void PrintLibVersion(); +bool IsTargetAddrSelf(const sockaddr* boundaddr, const sockaddr* targetaddr); namespace srt @@ -344,7 +345,7 @@ namespace srt struct OptionSetterProxy { SRTSOCKET s; - int result = -1; + SRTSTATUS result = SRT_ERROR; OptionSetterProxy(SRTSOCKET ss): s(ss) {} @@ -384,7 +385,7 @@ struct OptionSetterProxy return OptionProxy {*this, opt}; } - operator int() { return result; } + operator SRTSTATUS() { return result; } }; inline OptionSetterProxy setopt(SRTSOCKET socket) diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp index b8aa67b86..1ffb2febb 100644 --- a/apps/socketoptions.hpp +++ b/apps/socketoptions.hpp @@ -58,25 +58,27 @@ struct SocketOption bool applyt(Object socket, std::string value) const; template - static int setso(Object socket, int protocol, int symbol, const void* data, size_t size); + static int setso(Object , int , int , const void* , size_t) + { + typename Object::wrong_version error; + return -1; + } template bool extract(std::string value, OptionValue& val) const; }; template<> -inline int SocketOption::setso(int socket, int /*ignored*/, int sym, const void* data, size_t size) +inline int SocketOption::setso(SRTSOCKET socket, int /*ignored*/, int sym, const void* data, size_t size) { - return srt_setsockopt(socket, 0, SRT_SOCKOPT(sym), data, (int) size); + return (int)srt_setsockflag(socket, SRT_SOCKOPT(sym), data, (int) size); } -#if ENABLE_BONDING template<> inline int SocketOption::setso(SRT_SOCKOPT_CONFIG* obj, int /*ignored*/, int sym, const void* data, size_t size) { - return srt_config_add(obj, SRT_SOCKOPT(sym), data, (int) size); + return (int)srt_config_add(obj, SRT_SOCKOPT(sym), data, (int) size); } -#endif template<> @@ -184,7 +186,7 @@ inline bool SocketOption::applyt(Object socket, std::string value) const int result = -1; if (extract(value, o)) result = setso(socket, protocol, symbol, o.value, o.size); - return result != -1; + return result != int(SRT_ERROR); } @@ -247,20 +249,12 @@ const SocketOption srt_options [] { { "ipv6only", 0, SRTO_IPV6ONLY, SocketOption::PRE, SocketOption::INT, nullptr }, { "peeridletimeo", 0, SRTO_PEERIDLETIMEO, SocketOption::PRE, SocketOption::INT, nullptr }, { "packetfilter", 0, SRTO_PACKETFILTER, SocketOption::PRE, SocketOption::STRING, nullptr }, -#if ENABLE_BONDING { "groupconnect", 0, SRTO_GROUPCONNECT, SocketOption::PRE, SocketOption::INT, nullptr}, { "groupminstabletimeo", 0, SRTO_GROUPMINSTABLETIMEO, SocketOption::PRE, SocketOption::INT, nullptr}, -#endif -#ifdef SRT_ENABLE_BINDTODEVICE { "bindtodevice", 0, SRTO_BINDTODEVICE, SocketOption::PRE, SocketOption::STRING, nullptr}, -#endif - { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr } -#ifdef ENABLE_AEAD_API_PREVIEW - ,{ "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr } -#endif -#ifdef ENABLE_MAXREXMITBW - ,{ "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr } -#endif + { "retransmitalgo", 0, SRTO_RETRANSMITALGO, SocketOption::PRE, SocketOption::INT, nullptr }, + { "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr }, + { "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr } }; } diff --git a/apps/srt-file-transmit.cpp b/apps/srt-file-transmit.cpp index 327ad6809..e5b76784c 100644 --- a/apps/srt-file-transmit.cpp +++ b/apps/srt-file-transmit.cpp @@ -180,7 +180,7 @@ int parse_args(FileTransmitConfig &cfg, int argc, char** argv) return 2; } - cfg.chunk_size = stoul(Option(params, "1456", o_chunk)); + cfg.chunk_size = stoul(Option(params, "0", o_chunk)); cfg.skip_flushing = Option(params, false, o_no_flush); cfg.bw_report = stoi(Option(params, "0", o_bwreport)); cfg.stats_report = stoi(Option(params, "0", o_statsrep)); @@ -309,7 +309,7 @@ bool DoUpload(UriParser& ut, string path, string filename, int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, - tar->GetSRTSocket(), &events)) + tar->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; @@ -349,7 +349,7 @@ bool DoUpload(UriParser& ut, string path, string filename, s = tar->GetSRTSocket(); int events = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - if (srt_epoll_add_usock(pollid, s, &events)) + if (srt_epoll_add_usock(pollid, s, &events) == SRT_ERROR) { cerr << "Failed to add SRT client to poll" << endl; goto exit; @@ -391,7 +391,7 @@ bool DoUpload(UriParser& ut, string path, string filename, int st = tar->Write(buf.data() + shift, n, 0, out_stats); Verb() << "Upload: " << n << " --> " << st << (!shift ? string() : "+" + Sprint(shift)); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { cerr << "Upload: SRT error: " << srt_getlasterror_str() << endl; @@ -429,7 +429,7 @@ bool DoUpload(UriParser& ut, string path, string filename, size_t bytes; size_t blocks; int st = srt_getsndbuffer(s, &blocks, &bytes); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str() << endl; @@ -490,7 +490,7 @@ bool DoDownload(UriParser& us, string directory, string filename, int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; if (srt_epoll_add_usock(pollid, - src->GetSRTSocket(), &events)) + src->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT source to poll, " << src->GetSRTSocket() << endl; @@ -528,7 +528,7 @@ bool DoDownload(UriParser& us, string directory, string filename, s = src->GetSRTSocket(); int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; - if (srt_epoll_add_usock(pollid, s, &events)) + if (srt_epoll_add_usock(pollid, s, &events) == SRT_ERROR) { cerr << "Failed to add SRT client to poll" << endl; goto exit; @@ -593,7 +593,7 @@ bool DoDownload(UriParser& us, string directory, string filename, } int n = src->Read(cfg.chunk_size, packet, out_stats); - if (n == SRT_ERROR) + if (n == int(SRT_ERROR)) { cerr << "Download: SRT error: " << srt_getlasterror_str() << endl; goto exit; @@ -681,8 +681,11 @@ int main(int argc, char** argv) // // Set global config variables // - if (cfg.chunk_size != SRT_LIVE_MAX_PLSIZE) + if (cfg.chunk_size != 0) transmit_chunk_size = cfg.chunk_size; + else + transmit_chunk_size = SRT_MAX_PLSIZE_AF_INET; + transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf); transmit_bw_report = cfg.bw_report; transmit_stats_report = cfg.stats_report; diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index 932bcd142..3a12a1a71 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -519,7 +519,7 @@ int main(int argc, char** argv) { case UriParser::SRT: if (srt_epoll_add_usock(pollid, - src->GetSRTSocket(), &events)) + src->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT source to poll, " << src->GetSRTSocket() << endl; @@ -529,7 +529,7 @@ int main(int argc, char** argv) case UriParser::UDP: case UriParser::RTP: if (srt_epoll_add_ssock(pollid, - src->GetSysSocket(), &events)) + src->GetSysSocket(), &events) == SRT_ERROR) { cerr << "Failed to add " << src->uri.proto() << " source to poll, " << src->GetSysSocket() @@ -541,7 +541,7 @@ int main(int argc, char** argv) { const int con = src->GetSysSocket(); // try to make the standard input non blocking - if (srt_epoll_add_ssock(pollid, con, &events)) + if (srt_epoll_add_ssock(pollid, con, &events) == SRT_ERROR) { cerr << "Failed to add FILE source to poll, " << src->GetSysSocket() << endl; @@ -573,7 +573,7 @@ int main(int argc, char** argv) { case UriParser::SRT: if (srt_epoll_add_usock(pollid, - tar->GetSRTSocket(), &events)) + tar->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; @@ -647,7 +647,7 @@ int main(int argc, char** argv) SRTSOCKET ns = (issource) ? src->GetSRTSocket() : tar->GetSRTSocket(); int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; - if (srt_epoll_add_usock(pollid, ns, &events)) + if (srt_epoll_add_usock(pollid, ns, &events) == SRT_ERROR) { cerr << "Failed to add SRT client to poll, " << ns << endl; @@ -745,7 +745,7 @@ int main(int argc, char** argv) const int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; // Disable OUT event polling when connected if (srt_epoll_update_usock(pollid, - tar->GetSRTSocket(), &events)) + tar->GetSRTSocket(), &events) == SRT_ERROR) { cerr << "Failed to add SRT destination to poll, " << tar->GetSRTSocket() << endl; @@ -811,7 +811,7 @@ int main(int argc, char** argv) std::shared_ptr pkt(new MediaPacket(transmit_chunk_size)); const int res = src->Read(transmit_chunk_size, *pkt, out_stats); - if (res == SRT_ERROR && src->uri.type() == UriParser::SRT) + if (res == int(SRT_ERROR) && src->uri.type() == UriParser::SRT) { if (srt_getlasterror(NULL) == SRT_EASYNCRCV) break; diff --git a/apps/srt-tunnel.cpp b/apps/srt-tunnel.cpp index 90bd43eae..060256351 100644 --- a/apps/srt-tunnel.cpp +++ b/apps/srt-tunnel.cpp @@ -420,7 +420,7 @@ void Engine::Worker() class SrtMedium: public Medium { - SRTSOCKET m_socket = SRT_ERROR; + SRTSOCKET m_socket = SRT_INVALID_SOCK; friend class Medium; public: @@ -441,10 +441,10 @@ class SrtMedium: public Medium { Verb() << "Closing SRT socket for " << uri(); lock_guard lk(access); - if (m_socket == SRT_ERROR) + if (m_socket == SRT_INVALID_SOCK) return; srt_close(m_socket); - m_socket = SRT_ERROR; + m_socket = SRT_INVALID_SOCK; } // Forwarded in order to separate the implementation from @@ -625,16 +625,16 @@ void SrtMedium::CreateListener() sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); - int stat = srt_bind(m_socket, sa.get(), sizeof sa); + SRTSTATUS stat = srt_bind(m_socket, sa.get(), sizeof sa); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { srt_close(m_socket); Error(UDT::getlasterror(), "srt_bind"); } stat = srt_listen(m_socket, backlog); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { srt_close(m_socket); Error(UDT::getlasterror(), "srt_listen"); @@ -675,7 +675,7 @@ unique_ptr SrtMedium::Accept() { sockaddr_any sa; SRTSOCKET s = srt_accept(m_socket, (sa.get()), (&sa.len)); - if (s == SRT_ERROR) + if (s == SRT_INVALID_SOCK) { Error(UDT::getlasterror(), "srt_accept"); } @@ -735,8 +735,8 @@ void SrtMedium::Connect() { sockaddr_any sa = CreateAddr(m_uri.host(), m_uri.portno()); - int st = srt_connect(m_socket, sa.get(), sizeof sa); - if (st == SRT_ERROR) + SRTSOCKET st = srt_connect(m_socket, sa.get(), sizeof sa); + if (st == SRT_INVALID_SOCK) Error(UDT::getlasterror(), "srt_connect"); ConfigurePost(m_socket); @@ -767,7 +767,7 @@ int SrtMedium::ReadInternal(char* w_buffer, int size) do { st = srt_recv(m_socket, (w_buffer), size); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { int syserr; if (srt_getlasterror(&syserr) == SRT_EASYNCRCV && !m_broken) @@ -886,7 +886,7 @@ Medium::ReadStatus Medium::Read(bytevector& w_output) void SrtMedium::Write(bytevector& w_buffer) { int st = srt_send(m_socket, w_buffer.data(), (int)w_buffer.size()); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { Error(UDT::getlasterror(), "srt_send"); } diff --git a/apps/statswriter.cpp b/apps/statswriter.cpp index f1aa343b5..7fdf54bcb 100644 --- a/apps/statswriter.cpp +++ b/apps/statswriter.cpp @@ -146,7 +146,7 @@ class SrtStatsJson : public SrtStatsWriter } public: - string WriteStats(int sid, const CBytePerfMon& mon) override + string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) override { std::ostringstream output; @@ -235,7 +235,7 @@ class SrtStatsCsv : public SrtStatsWriter public: SrtStatsCsv() : first_line_printed(false) {} - string WriteStats(int sid, const CBytePerfMon& mon) override + string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) override { std::ostringstream output; @@ -286,7 +286,7 @@ class SrtStatsCsv : public SrtStatsWriter class SrtStatsCols : public SrtStatsWriter { public: - string WriteStats(int sid, const CBytePerfMon& mon) override + string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) override { std::ostringstream output; output << "======= SRT STATS: sid=" << sid << endl; diff --git a/apps/statswriter.hpp b/apps/statswriter.hpp index 1b4fd4fbc..0babc7a00 100644 --- a/apps/statswriter.hpp +++ b/apps/statswriter.hpp @@ -71,7 +71,7 @@ struct SrtStatDataType: public SrtStatData class SrtStatsWriter { public: - virtual std::string WriteStats(int sid, const CBytePerfMon& mon) = 0; + virtual std::string WriteStats(SRTSOCKET sid, const CBytePerfMon& mon) = 0; virtual std::string WriteBandwidth(double mbpsBandwidth) = 0; virtual ~SrtStatsWriter() {} diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 862875fa2..025df1229 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -44,7 +44,7 @@ bool g_stats_are_printed_to_stdout = false; bool transmit_total_stats = false; unsigned long transmit_bw_report = 0; unsigned long transmit_stats_report = 0; -unsigned long transmit_chunk_size = SRT_LIVE_MAX_PLSIZE; +unsigned long transmit_chunk_size = SRT_MAX_PLSIZE_AF_INET6; class FileSource: public Source { @@ -179,6 +179,40 @@ void SrtCommon::InitParameters(string host, map par) m_adapter = host; } + unsigned int max_payload_size = 0; + + // Try to interpret host and adapter first + sockaddr_any host_sa, adapter_sa; + + if (host != "") + { + host_sa = CreateAddr(host); + if (host_sa.family() == AF_UNSPEC) + Error("Failed to interpret 'host' spec: " + host); + + if (host_sa.family() == AF_INET) + max_payload_size = SRT_MAX_PLSIZE_AF_INET; + } + + if (adapter != "" && adapter != host) + { + adapter_sa = CreateAddr(adapter); + + if (adapter_sa.family() == AF_UNSPEC) + Error("Failed to interpret 'adapter' spec: " + adapter); + + if (host_sa.family() != AF_UNSPEC && host_sa.family() != adapter_sa.family()) + { + Error("Both host and adapter specified and they use different IP versions"); + } + + if (max_payload_size == 0 && host_sa.family() == AF_INET) + max_payload_size = SRT_MAX_PLSIZE_AF_INET; + } + + if (!max_payload_size) + max_payload_size = SRT_MAX_PLSIZE_AF_INET6; + if (par.count("tsbpd") && false_names.count(par.at("tsbpd"))) { m_tsbpdmode = false; @@ -195,11 +229,17 @@ void SrtCommon::InitParameters(string host, map par) if ((par.count("transtype") == 0 || par["transtype"] != "file") && transmit_chunk_size > SRT_LIVE_DEF_PLSIZE) { - if (transmit_chunk_size > SRT_LIVE_MAX_PLSIZE) - throw std::runtime_error("Chunk size in live mode exceeds 1456 bytes; this is not supported"); + if (transmit_chunk_size > max_payload_size) + Error(Sprint("Chunk size in live mode exceeds ", max_payload_size, " bytes; this is not supported")); par["payloadsize"] = Sprint(transmit_chunk_size); } + else + { + // set it so without making sure that it was set to "file". + // worst case it will be rejected in settings + m_transtype = SRTT_FILE; + } // Assign the others here. m_options = par; @@ -208,11 +248,11 @@ void SrtCommon::InitParameters(string host, map par) void SrtCommon::PrepareListener(string host, int port, int backlog) { m_bindsock = srt_create_socket(); - if ( m_bindsock == SRT_ERROR ) + if (m_bindsock == SRT_INVALID_SOCK) Error("srt_create_socket"); - int stat = ConfigurePre(m_bindsock); - if ( stat == SRT_ERROR ) + SRTSTATUS stat = ConfigurePre(m_bindsock); + if (stat == SRT_ERROR) Error("ConfigurePre"); sockaddr_any sa = CreateAddr(host, port); @@ -220,7 +260,7 @@ void SrtCommon::PrepareListener(string host, int port, int backlog) Verb() << "Binding a server on " << host << ":" << port << " ..."; stat = srt_bind(m_bindsock, psa, sizeof sa); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { srt_close(m_bindsock); Error("srt_bind"); @@ -229,7 +269,7 @@ void SrtCommon::PrepareListener(string host, int port, int backlog) Verb() << " listen..."; stat = srt_listen(m_bindsock, backlog); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) { srt_close(m_bindsock); Error("srt_listen"); @@ -263,6 +303,21 @@ bool SrtCommon::AcceptNewClient() Error("srt_accept"); } + int maxsize = srt_getmaxpayloadsize(m_sock); + if (maxsize == int(SRT_ERROR)) + { + srt_close(m_bindsock); + srt_close(m_sock); + Error("srt_getmaxpayloadsize"); + } + + if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) + { + srt_close(m_bindsock); + srt_close(m_sock); + Error(Sprint("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + } + // we do one client connection at a time, // so close the listener. srt_close(m_bindsock); @@ -272,8 +327,8 @@ bool SrtCommon::AcceptNewClient() // ConfigurePre is done on bindsock, so any possible Pre flags // are DERIVED by sock. ConfigurePost is done exclusively on sock. - int stat = ConfigurePost(m_sock); - if ( stat == SRT_ERROR ) + SRTSTATUS stat = ConfigurePost(m_sock); + if (stat == SRT_ERROR) Error("ConfigurePost"); return true; @@ -299,14 +354,14 @@ void SrtCommon::Init(string host, int port, map par, bool dir_out } } -int SrtCommon::ConfigurePost(SRTSOCKET sock) +SRTSTATUS SrtCommon::ConfigurePost(SRTSOCKET sock) { bool no = false; - int result = 0; + SRTSTATUS result = SRT_STATUS_OK; if ( m_output_direction ) { result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; if ( m_timeout ) @@ -315,7 +370,7 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) else { result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; if ( m_timeout ) @@ -339,23 +394,23 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) } } - return 0; + return SRT_STATUS_OK; } -int SrtCommon::ConfigurePre(SRTSOCKET sock) +SRTSTATUS SrtCommon::ConfigurePre(SRTSOCKET sock) { - int result = 0; + SRTSTATUS result = SRT_STATUS_OK; bool no = false; if ( !m_tsbpdmode ) { result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; } result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; @@ -380,14 +435,14 @@ int SrtCommon::ConfigurePre(SRTSOCKET sock) return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } void SrtCommon::SetupAdapter(const string& host, int port) { sockaddr_any localsa = CreateAddr(host, port); sockaddr* psa = localsa.get(); - int stat = srt_bind(m_sock, psa, sizeof localsa); + SRTSTATUS stat = srt_bind(m_sock, psa, sizeof localsa); if ( stat == SRT_ERROR ) Error("srt_bind"); } @@ -407,10 +462,10 @@ void SrtCommon::OpenClient(string host, int port) void SrtCommon::PrepareClient() { m_sock = srt_create_socket(); - if ( m_sock == SRT_ERROR ) + if ( m_sock == SRT_INVALID_SOCK) Error("srt_create_socket"); - int stat = ConfigurePre(m_sock); + SRTSTATUS stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); } @@ -418,20 +473,32 @@ void SrtCommon::PrepareClient() void SrtCommon::ConnectClient(string host, int port) { - sockaddr_any sa = CreateAddr(host, port); sockaddr* psa = sa.get(); Verb() << "Connecting to " << host << ":" << port; - int stat = srt_connect(m_sock, psa, sizeof sa); - if ( stat == SRT_ERROR ) + SRTSOCKET cstat = srt_connect(m_sock, psa, sizeof sa); + if (cstat == SRT_INVALID_SOCK) { srt_close(m_sock); Error("srt_connect"); } - stat = ConfigurePost(m_sock); + int maxsize = srt_getmaxpayloadsize(m_sock); + if (maxsize == int(SRT_ERROR)) + { + srt_close(m_sock); + Error("srt_getmaxpayloadsize"); + } + + if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) + { + srt_close(m_sock); + Error(Sprint("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + } + + SRTSTATUS stat = ConfigurePost(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePost"); } @@ -449,13 +516,13 @@ void SrtCommon::Error(string src) void SrtCommon::OpenRendezvous(string adapter, string host, int port) { m_sock = srt_create_socket(); - if ( m_sock == SRT_ERROR ) + if (m_sock == SRT_INVALID_SOCK) Error("srt_create_socket"); bool yes = true; srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); - int stat = ConfigurePre(m_sock); + SRTSTATUS stat = ConfigurePre(m_sock); if ( stat == SRT_ERROR ) Error("ConfigurePre"); @@ -480,8 +547,8 @@ void SrtCommon::OpenRendezvous(string adapter, string host, int port) Verb() << "Connecting to " << host << ":" << port; - stat = srt_connect(m_sock, sa.get(), sizeof sa); - if ( stat == SRT_ERROR ) + SRTSOCKET cstat = srt_connect(m_sock, sa.get(), sizeof sa); + if ( cstat == SRT_INVALID_SOCK) { srt_close(m_sock); Error("srt_connect"); @@ -565,10 +632,10 @@ int SrtSource::Read(size_t chunk, MediaPacket& pkt, ostream &out_stats) return stat; } -int SrtTarget::ConfigurePre(SRTSOCKET sock) +SRTSTATUS SrtTarget::ConfigurePre(SRTSOCKET sock) { - int result = SrtCommon::ConfigurePre(sock); - if ( result == -1 ) + SRTSTATUS result = SrtCommon::ConfigurePre(sock); + if ( result == SRT_ERROR ) return result; int yes = 1; @@ -577,10 +644,10 @@ int SrtTarget::ConfigurePre(SRTSOCKET sock) // In HSv4 this setting is obligatory; otherwise the SRT handshake // extension will not be done at all. result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); - if ( result == -1 ) + if ( result == SRT_ERROR ) return result; - return 0; + return SRT_STATUS_OK; } int SrtTarget::Write(const char* data, size_t size, int64_t src_time, ostream &out_stats) @@ -590,7 +657,7 @@ int SrtTarget::Write(const char* data, size_t size, int64_t src_time, ostream &o SRT_MSGCTRL ctrl = srt_msgctrl_default; ctrl.srctime = src_time; int stat = srt_sendmsg2(m_sock, data, (int) size, &ctrl); - if (stat == SRT_ERROR) + if (stat == int(SRT_ERROR)) { return stat; } diff --git a/apps/transmitmedia.hpp b/apps/transmitmedia.hpp index 527f005d9..1da244340 100644 --- a/apps/transmitmedia.hpp +++ b/apps/transmitmedia.hpp @@ -42,6 +42,7 @@ class SrtCommon string m_mode; string m_adapter; map m_options; // All other options, as provided in the URI + SRT_TRANSTYPE m_transtype = SRTT_LIVE; SRTSOCKET m_sock = SRT_INVALID_SOCK; SRTSOCKET m_bindsock = SRT_INVALID_SOCK; bool IsUsable() { SRT_SOCKSTATUS st = srt_getsockstate(m_sock); return st > SRTS_INIT && st < SRTS_BROKEN; } @@ -63,8 +64,8 @@ class SrtCommon void Error(string src); void Init(string host, int port, map par, bool dir_output); - virtual int ConfigurePost(SRTSOCKET sock); - virtual int ConfigurePre(SRTSOCKET sock); + virtual SRTSTATUS ConfigurePost(SRTSOCKET sock); + virtual SRTSTATUS ConfigurePre(SRTSOCKET sock); void OpenClient(string host, int port); void PrepareClient(); @@ -132,7 +133,7 @@ class SrtTarget: public Target, public SrtCommon SrtTarget() {} - int ConfigurePre(SRTSOCKET sock) override; + SRTSTATUS ConfigurePre(SRTSOCKET sock) override; int Write(const char* data, size_t size, int64_t src_time, ostream &out_stats = cout) override; bool IsOpen() override { return IsUsable(); } bool Broken() override { return IsBroken(); } diff --git a/common/devel_util.h b/common/devel_util.h new file mode 100644 index 000000000..4e31af07e --- /dev/null +++ b/common/devel_util.h @@ -0,0 +1,118 @@ +/* + * SRT - Secure, Reliable, Transport + * Copyright (c) 2018 Haivision Systems Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +// IMPORTANT!!! +// +// This is normally not a part of SRT source files. This is a developer utility +// that allows developers to perform a compile test on a version instrumented +// with type checks. To do that, you can do one of two things: +// +// - configure the compiling process with extra -DSRT_TEST_FORCED_CONSTANT=1 flag +// - unblock the commented out #define in srt.h file for that constant +// +// Note that there's no use of such a compiled code. This is done only so that +// the compiler can detect any misuses of the SRT symbolic type names and +// constants. + + +#include + +template +concept Streamable = requires(OS& os, T value) { + { os << value }; +}; + +template +struct IntWrapper +{ + INT v; + + IntWrapper() {} + explicit IntWrapper(INT val): v(val) {} + + bool operator==(const IntWrapper& x) const + { + return v == x.v; + } + + bool operator!=(const IntWrapper& x) const + { + return !(*this == x); + } + + explicit operator INT() const + { + return v; + } + + bool operator<(const IntWrapper& w) const + { + return v < w.v; + } + + template + requires Streamable + friend Str& operator<<(Str& out, const IntWrapper& x) + { + out << x.v; + return out; + } + + friend std::ostream& operator<<(std::ostream& out, const IntWrapper& x) + { + out << x.v; + return out; + } +}; + +template +struct IntWrapperLoose: IntWrapper +{ + typedef IntWrapper base_t; + explicit IntWrapperLoose(INT val): base_t(val) {} + + bool operator==(const IntWrapper& x) const + { + return this->v == x.v; + } + + friend bool operator==(const IntWrapper& x, const IntWrapperLoose& y) + { + return x.v == y.v; + } + + bool operator==(INT val) const + { + return this->v == val; + } + + friend bool operator==(INT val, const IntWrapperLoose& x) + { + return val == x.v; + } + + operator INT() const + { + return this->v; + } +}; + + +typedef IntWrapper SRTSOCKET; +typedef IntWrapper SRTSTATUS; +typedef IntWrapper SRTRUNSTATUS; +typedef IntWrapperLoose SRTSTATUS_LOOSE; + + diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 2fdb8a661..ff2090232 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -19,7 +19,9 @@ | [srt_bind_acquire](#srt_bind_acquire) | Acquires a given UDP socket instead of creating one | | [srt_getsockstate](#srt_getsockstate) | Gets the current status of the socket | | [srt_getsndbuffer](#srt_getsndbuffer) | Retrieves information about the sender buffer | +| [srt_getmaxpayloadsize](#srt_getmaxpayloadsize) | Retrieves the information about the maximum payload size in a single packet | | [srt_close](#srt_close) | Closes the socket or group and frees all used resources | +| [srt_close_withreason](#srt_close_withreason) | Closes the socket or group and frees all used resources (with setting the reason code) | | | |

Connecting

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

Rejection Reasons

-| *Rejection Reason* | *Since* | *Description* | -|:-------------------------------------------- |:--------- |:-------------------------------------------------------------------------------------------------------------- | -| [SRT_REJ_UNKNOWN](#SRT_REJ_UNKNOWN) | 1.3.4 | A fallback value for cases when there was no connection rejected | -| [SRT_REJ_SYSTEM](#SRT_REJ_SYSTEM) | 1.3.4 | A system function reported a failure | -| [SRT_REJ_PEER](#SRT_REJ_PEER) | 1.3.4 | The connection has been rejected by peer, but no further details are available | -| [SRT_REJ_RESOURCE](#SRT_REJ_RESOURCE) | 1.3.4 | A problem with resource allocation (usually memory) | -| [SRT_REJ_ROGUE](#SRT_REJ_ROGUE) | 1.3.4 | The data sent by one party to another cannot be properly interpreted | -| [SRT_REJ_BACKLOG](#SRT_REJ_BACKLOG) | 1.3.4 | The listener's backlog has exceeded | -| [SRT_REJ_IPE](#SRT_REJ_IPE) | 1.3.4 | Internal Program Error | -| [SRT_REJ_CLOSE](#SRT_REJ_CLOSE) | 1.3.4 | The listener socket received a request as it is being closed | -| [SRT_REJ_VERSION](#SRT_REJ_VERSION) | 1.3.4 | A party did not satisfy the minimum version requirement that had been set up for a connection | -| [SRT_REJ_RDVCOOKIE](#SRT_REJ_RDVCOOKIE) | 1.3.4 | Rendezvous cookie collision | -| [SRT_REJ_BADSECRET](#SRT_REJ_BADSECRET) | 1.3.4 | Both parties have defined a passphrase for connection and they differ | -| [SRT_REJ_UNSECURE](#SRT_REJ_UNSECURE) | 1.3.4 | Only one connection party has set up a password | -| [SRT_REJ_MESSAGEAPI](#SRT_REJ_MESSAGEAPI) | 1.3.4 | The value for [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on both connection parties | -| [SRT_REJ_FILTER](#SRT_REJ_FILTER) | 1.3.4 | The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option has been set differently on both connection parties | -| [SRT_REJ_GROUP](#SRT_REJ_GROUP) | 1.4.2 | The group type or some group settings are incompatible for both connection parties | -| [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | 1.4.2 | The connection wasn't rejected, but it timed out | -| [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | The connection was rejected due to an unsupported or mismatching encryption mode | -| | | | +| *Rejection Reason* | *Since* | *Description* | +|:-------------------------------------------- |:--------- |:---------------------------------------------------------------------------------------------------------------- | +| [SRT_REJ_UNKNOWN](#SRT_REJ_UNKNOWN) | 1.3.4 | A fallback value for cases when there was no connection rejected | +| [SRT_REJ_SYSTEM](#SRT_REJ_SYSTEM) | 1.3.4 | A system function reported a failure | +| [SRT_REJ_PEER](#SRT_REJ_PEER) | 1.3.4 | The connection has been rejected by peer, but no further details are available | +| [SRT_REJ_RESOURCE](#SRT_REJ_RESOURCE) | 1.3.4 | A problem with resource allocation (usually memory) | +| [SRT_REJ_ROGUE](#SRT_REJ_ROGUE) | 1.3.4 | The data sent by one party to another cannot be properly interpreted | +| [SRT_REJ_BACKLOG](#SRT_REJ_BACKLOG) | 1.3.4 | The listener's backlog has exceeded | +| [SRT_REJ_IPE](#SRT_REJ_IPE) | 1.3.4 | Internal Program Error | +| [SRT_REJ_CLOSE](#SRT_REJ_CLOSE) | 1.3.4 | The listener socket received a request as it is being closed | +| [SRT_REJ_VERSION](#SRT_REJ_VERSION) | 1.3.4 | A party did not satisfy the minimum version requirement that had been set up for a connection | +| [SRT_REJ_RDVCOOKIE](#SRT_REJ_RDVCOOKIE) | 1.3.4 | Rendezvous cookie collision | +| [SRT_REJ_BADSECRET](#SRT_REJ_BADSECRET) | 1.3.4 | Both parties have defined a passphrase for connection and they differ | +| [SRT_REJ_UNSECURE](#SRT_REJ_UNSECURE) | 1.3.4 | Only one connection party has set up a password | +| [SRT_REJ_MESSAGEAPI](#SRT_REJ_MESSAGEAPI) | 1.3.4 | The value for [`SRTO_MESSAGEAPI`](API-socket-options.md#SRTO_MESSAGEAPI) flag is different on the peer | +| [SRT_REJ_FILTER](#SRT_REJ_FILTER) | 1.3.4 | The [`SRTO_PACKETFILTER`](API-socket-options.md#SRTO_PACKETFILTER) option is set differently on the peer | +| [SRT_REJ_GROUP](#SRT_REJ_GROUP) | 1.4.2 | The group type or some group settings are incompatible for both connection parties | +| [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | 1.4.2 | The connection wasn't rejected, but it timed out | +| [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | The connection was rejected due to an unsupported or mismatching encryption mode | +| [SRT_REJ_CONFIG](#SRT_REJ_CONFIG) | 1.6.0 | The connection was rejected because settings on both parties are in collision and cannot negotiate common values | +| | | | See the full list in [Rejection Reason Codes](./rejection-codes.md). @@ -224,6 +228,33 @@ See the full list in [Rejection Reason Codes](./rejection-codes.md). | | | +## Diagnostics and return types + +The SRT API functions usually report a status of the operation that they attempt to perform. +There are three general possibilities to report a success or failure, possibly with some +extra information: + +1. `SRTSTATUS` is usually an integer value with two possible variants: + * `SRT_STATUS_OK` (value: 0): the operation completed successfully + * `SRT_ERROR` (value: -1): the operation failed + +2. `SRTSOCKET` can be returned by some of the functions, which can be: + * A positive value greater than 0, which is a valid Socket ID value + * `SRT_SOCKID_CONNREQ` for a success report when a Socket ID needs not be returned + * `SRT_INVALID_SOCK` for a failure report + +3. A value of type `int` that should be a positive value or 0 in case of a success, +and the value equal to `SRT_ERROR` (that is, -1) in case of failure. + +In the below function description, functions returning `SRTSTATUS` will not +have the provided return value description, as it always maches the one above. +For all other types the function-specific return value description will be provided. + +If the function returns `SRT_ERROR`, `SRT_INVALID_SOCK` or a value equal to -1 +in case of returning an `int` value, additional error code can be obtained +through the [`srt_getlasterror`](#srt_getlasterror) call. Possible codes for a +particular function are listed in the **Errors** table. + ## Library Initialization @@ -233,7 +264,7 @@ See the full list in [Rejection Reason Codes](./rejection-codes.md). ### srt_startup ``` -int srt_startup(void); +SRTRUNSTATUS srt_startup(void); ``` This function shall be called at the start of an application that uses the SRT @@ -244,10 +275,10 @@ relying on this behavior is strongly discouraged. | Returns | | |:----------------------------- |:--------------------------------------------------------------- | -| 0 | Successfully run, or already started | -| 1 | This is the first startup, but the GC thread is already running | -| -1 | Failed | -| | | +| `SRT_RUN_OK` (0) | Successfully started | +| `SRT_RUN_ALREADY` (1) | The GC thread is already running or it was called once already | +| `SRT_RUN_ERROR` (-1) | Failed | +| | | | Errors | | |:----------------------------- |:--------------------------------------------------------------- | @@ -261,7 +292,7 @@ relying on this behavior is strongly discouraged. ### srt_cleanup ``` -int srt_cleanup(void); +SRTSTATUS srt_cleanup(void); ``` This function cleans up all global SRT resources and shall be called just before @@ -269,10 +300,8 @@ exiting the application that uses the SRT library. This cleanup function will st be called from the C++ global destructor, if not called by the application, although relying on this behavior is strongly discouraged. -| Returns | | -|:----------------------------- |:--------------------------------------------------------------- | -| 0 | A possibility to return other values is reserved for future use | -| | | +Currently this function can only return `SRT_STATUS_OK` and a possibility to return +`SRT_ERROR` is reserved for future use. **IMPORTANT**: Note that the startup/cleanup calls have an instance counter. This means that if you call [`srt_startup`](#srt_startup) multiple times, you need to call the @@ -294,7 +323,9 @@ This means that if you call [`srt_startup`](#srt_startup) multiple times, you ne * [srt_bind_acquire](#srt_bind_acquire) * [srt_getsockstate](#srt_getsockstate) * [srt_getsndbuffer](#srt_getsndbuffer) +* [srt_getmaxpayloadsize](#srt_getmaxpayloadsize) * [srt_close](#srt_close) +* [srt_close_withreason](#srt_close_withreason) ### srt_socket @@ -337,11 +368,11 @@ Note that socket IDs always have the `SRTGROUP_MASK` bit clear. |:----------------------------- |:------------------------------------------------------- | | Socket ID | A valid socket ID on success | | `SRT_INVALID_SOCK` | (`-1`) on error | -| | | +| | | -| Errors | | -|:----------------------------- |:------------------------------------------------------------ | -| [`SRT_ENOBUF`](#srt_enobuf) | Not enough memory to allocate required resources . | +| Errors | | +|:----------------------------- |:-------------------------------------------------- | +| [`SRT_ENOBUF`](#srt_enobuf) | Not enough memory to allocate required resources | | | | **NOTE:** This is probably a design flaw (:warning:   **BUG?**). Usually underlying system @@ -355,7 +386,7 @@ errors are reported by [`SRT_ECONNSETUP`](#srt_econnsetup). ### srt_bind ``` -int srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRTSTATUS srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); ``` Binds a socket to a local address and port. Binding specifies the local network @@ -363,10 +394,11 @@ interface and the UDP port number to be used for the socket. When the local address is a wildcard (`INADDR_ANY` for IPv4 or `in6addr_any` for IPv6), then it's bound to all interfaces. -**IMPORTANT**: When you bind an IPv6 wildcard address, note that the -`SRTO_IPV6ONLY` option must be set on the socket explicitly to 1 or 0 prior to -calling this function. See -[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) for more details. +**IMPORTANT**: In the case of IPv6 wildcard address, this may mean either "all +IPv6 interfaces" or "all IPv4 and IPv6 interfaces", depending on the value of +[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) option. Therefore this +option must be explicitly set to 0 or 1 prior to calling this function, otherwise +(when the default -1 value of this option is left) this function will fail. Binding is necessary for every socket to be used for communication. If the socket is to be used to initiate a connection to a listener socket, which can be done, @@ -413,7 +445,7 @@ binding ("shared binding") is possessed by an SRT socket created in the same application, and: * Its binding address and UDP-related socket options match the socket to be bound. -* Its [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to *true* (default). +* Its [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDR) is set to *true* (default). If none of the free, side and shared binding options is currently possible, this function will fail. If the socket blocking the requested endpoint is an SRT @@ -421,14 +453,15 @@ socket in the current application, it will report the `SRT_EBINDCONFLICT` error, while if it was another socket in the system, or the problem was in the system in general, it will report `SRT_ESOCKFAIL`. Here is the table that shows possible situations: -| Requested binding | vs. Existing bindings... | | | | | -|---------------------|------------------------------|-----------|-----------------------------|---------------|---------------| -| | A.B.C.D | 0.0.0.0 | ::X | :: / V6ONLY=1 | :: / V6ONLY=0 | -| 1.2.3.4 | 1.2.3.4 shareable, else free | blocked | free | free | blocked | -| 0.0.0.0 | blocked | shareable | free | free | blocked | -| 8080::1 | free | free | 8080::1 sharable, else free | blocked | blocked | -| :: / V6ONLY=1 | free | free | blocked | sharable | blocked | -| :: / V6ONLY=0 | blocked | blocked | blocked | blocked | sharable | +| Requested binding | vs. Existing bindings... | | | | | +|---------------------|---------------------------------|-----------|-----------------------------|---------------|---------------| +| | A.B.C.D (explicit IPv4 addr.) | 0.0.0.0 | ::X (explicit IPv6 addr.) | :: / V6ONLY=1 | :: / V6ONLY=0 | +|---------------------|---------------------------------|-----------|-----------------------------|---------------|---------------| +| 1.2.3.4 | shareable if 1.2.3.4, else free | blocked | free | free | blocked | +| 0.0.0.0 | blocked | shareable | free | free | blocked | +| 8080::1 | free | free | 8080::1 sharable, else free | blocked | blocked | +| :: / V6ONLY=1 | free | free | blocked | sharable | blocked | +| :: / V6ONLY=0 | blocked | blocked | blocked | blocked | sharable | Where: @@ -438,7 +471,7 @@ Where: * shareable: This binding can be shared with the requested binding if it's compatible. -* (ADDRESS) shareable, else free: this binding is shareable if the existing binding address is +* shareable if (ADDRESS), else free: this binding is shareable if the existing binding address is equal to the requested ADDRESS. Otherwise it's free. If the binding is shareable, then the operation will succeed if the socket that currently @@ -457,13 +490,9 @@ or set the appropriate source address using **IMPORTANT information about IPv6**: If you are going to bind to the `in6addr_any` IPv6 wildcard address (known as `::`), the `SRTO_IPV6ONLY` option must be first set explicitly to 0 or 1, otherwise the binding -will fail. In all other cases this option is meaningless. See `SRTO_IPV6ONLY` -option for more information. - -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) on error, otherwise 0 | -| | | +will fail. In all other cases this option is meaningless. See +[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) option for more +information. | Errors | | |:---------------------------------------- |:-------------------------------------------------------------------- | @@ -483,7 +512,7 @@ option for more information. ### srt_bind_acquire ``` -int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock); +SRTSTATUS srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock); ``` A version of [`srt_bind`](#srt_bind) that acquires a given UDP socket instead of creating one. @@ -526,7 +555,7 @@ Gets the current status of the socket. Possible states are: ### srt_getsndbuffer ``` -int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); +SRTSTATUS srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); ``` Retrieves information about the sender buffer. @@ -545,20 +574,73 @@ socket needs to be closed asynchronously. --- -### srt_close +### srt_getmaxpayloadsize ``` -int srt_close(SRTSOCKET u); +int srt_getmaxpayloadsize(SRTSOCKET u); +``` + +Returns the maximum number of bytes that fit in a single packet. Useful only in +live mode (when `SRTO_TSBPDMODE` is true). The socket must be bound (see +[srt_bind](#srt_bind)) or connected (see [srt_connect](#srt_connect)) +to use this function. Note that in case when the socket is bound to an IPv6 +wildcard address and it is dual-stack (`SRTO_IPV6ONLY` is set to false), this +function returns the correct value only if the socket is connected, otherwise +it will return the value always as if the connection was made from an IPv6 peer +(including when you call it on a listening socket). + +This function is only useful for the application to check if it is able to use +a payload of certain size in the live mode, or after connection, if the application +can send payloads of certain size. This is useful only in assertions, as if the +[`SRTO_PAYLOADSIZE`](API_socket-options.md#SRTO_PAYLOADSIZE) option is to be +set to a non-default value (for which the one returned by this function is the +maximum value), this option should be modified before connection and on both +parties, regarding the settings applied on the socket. + +The returned value is the maximum number of bytes that can be put in a single +packet regarding: + +* The current MTU size (`SRTO_MSS`) +* The IP version (IPv4 or IPv6) +* The `SRTO_CRYPTOMODE` setting (bytes reserved for AEAD authentication tag) +* The `SRTO_PACKETFILTER` setting (bytes reserved for extra field in a FEC control packet) + +With default options this value should be 1456 for IPv4 and 1444 for IPv6. + + +| Returns | | +|:----------------------------- |:------------------------------------------------- | +| The maximum payload size (>0) | If succeeded | +| `SRT_ERROR` | Usage error | +| | | + +| Errors | | +|:--------------------------------------- |:----------------------------------------------- | +| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | +| [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) | Socket [`u`](#u) is not bound | +| | | + + + +[:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) + +--- + +### srt_close, srt_close_withreason + +``` +SRTSTATUS srt_close(SRTSOCKET u); +SRTSTATUS srt_close_withreason(SRTSOCKET u, int reason); ``` Closes the socket or group and frees all used resources. Note that underlying UDP sockets may be shared between sockets, so these are freed only with the last user closed. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | +**Arguments**: + +* `u`: Socket or group to close +* `reason`: Reason code for closing. You should use numbers from `SRT_CLSC_USER` up. | Errors | | |:------------------------------- |:----------------------------------------------- | @@ -587,7 +669,7 @@ last user closed. ### srt_listen ``` -int srt_listen(SRTSOCKET u, int backlog); +SRTSTATUS srt_listen(SRTSOCKET u, int backlog); ``` This sets up the listening state on a socket with a backlog setting that @@ -602,11 +684,6 @@ be called before [`srt_accept`](#srt_accept) can happen * [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) option allows the listener socket to accept group connections -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0. | -| | | - | Errors | | |:--------------------------------------- |:-------------------------------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | Value of `backlog` is 0 or negative. | @@ -629,41 +706,109 @@ the listener socket to accept group connections SRTSOCKET srt_accept(SRTSOCKET lsn, struct sockaddr* addr, int* addrlen); ``` -Accepts a pending connection, then creates and returns a new socket or -group ID that handles this connection. The group and socket can be -distinguished by checking the `SRTGROUP_MASK` bit on the returned ID. +Extracts the first connection request on the queue of pending connections for +the listening socket, `lsn`, then creates and returns a new socket or group ID +that handles this connection. The group and socket can be distinguished by +checking the `SRTGROUP_MASK` bit on the returned ID. Note that by default group +connections will be rejected - this feature can be only enabled on demand (see +below). -* `lsn`: the listener socket previously configured by [`srt_listen`](#srt_listen) -* `addr`: the IP address and port specification for the remote party +* `lsn`: the listening socket +* `addr`: a location to store the remote IP address and port for the connection * `addrlen`: INPUT: size of `addr` pointed object. OUTPUT: real size of the returned object -**NOTE:** `addr` is allowed to be NULL, in which case it's understood that the -application is not interested in the address from which the connection originated. -Otherwise `addr` should specify an object into which the address will be written, -and `addrlen` must also specify a variable to contain the object size. Note also -that in the case of group connection only the initial connection that -establishes the group connection is returned, together with its address. As -member connections are added or broken within the group, you can obtain this -information through [`srt_group_data`](#srt_group_data) or the data filled by -[`srt_sendmsg2`](#srt_sendmsg) and [`srt_recvmsg2`](#srt_recvmsg2). - -If the `lsn` listener socket is configured for blocking mode -([`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) set to true, default), -the call will block until the incoming connection is ready. Otherwise, the -call always returns immediately. The `SRT_EPOLL_IN` epoll event should be -checked on the `lsn` socket prior to calling this function in that case. - -If the pending connection is a group connection (initiated on the peer side by -calling the connection function using a group ID, and permitted on the listener -socket by the [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) -flag), then the value returned is a group ID. This function then creates a new -group, as well as a new socket for this connection, that will be added to the -group. Once the group is created this way, further connections within the same -group, as well as sockets for them, will be created in the background. The -[`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) event is raised on the `lsn` socket when -a new background connection is attached to the group, although it's usually for -internal use only. +General requirements for a parameter correctness: + +* `lsn` must be first [bound](#srt_bind) and [listening](#srt_listen) + +* `addr` may be NULL, or otherwise it must be a pointer to an object +that can be treated as an instance of `sockaddr_in` or `sockaddr_in6` + +* `addrlen` should be a pointer to a variable set to the size of the object +specified in `addr`, if `addr` is not NULL. Otherwise it's ignored. + +If `addr` is not NULL, the information about the source IP address and +port of the peer will be written into this object. Note that whichever +type of object is expected here (`sockaddr_in` or `sockaddr_in6`), it +depends on the address type used in the `srt_bind` call for `lsn`. +If unsure in a particular situation, it is recommended that you use +`sockaddr_storage` or `srt::sockaddr_any`. + +If the `lsn` listener socket is in the blocking mode (if +[`SRTO_RCVSYN`](API-socket-options.md#SRTO_RCVSYN) is set to true, +which is default), the call will block until the incoming connection is ready +for extraction. Otherwise, the call always returns immediately, possibly with +failure, if there was no pending connection waiting on the listening socket +`lsn`. + +The listener socket can be checked for any pending connections prior to calling +`srt_accept` by checking the `SRT_EPOLL_ACCEPT` epoll event (which is an alias +to `SRT_EPOLL_IN`). This event might be spurious in certain cases though, for +example, when the connection has been closed by the peer or broken before the +application extracts it. The call to `srt_accept` would then still fail in +such a case. + +In order to allow the listening socket `lsn` to accept a group connection, +the [`SRTO_GROUPCONNECT`](API-socket-options.md#SRTO_GROUPCONNECT) socket option +for the listening socket must be set to 1. Note that single socket connections +can still be reported to that socket. The application can distinguish the socket +and group connection by checking the `SRTGROUP_MASK` bit on the returned +successful value. There are some important differences to single socket +connections: + +1. Accepting a group connection can be done only once per connection, even +though particular member connections can get broken or established while +the group is connected. The actual connection reporter (listener) is a socket, +like before, but once you call `srt_accept` and receive this group ID, it is +the group considered connected, and any member connections of the same group +will be handled in the background. + +2. If a group was extracted from the `srt_accept` call, the address reported in +`addr` parameter is still the address of the connection that has triggered the +group connection extraction. The information about all member links in the +group at the moment can be obtained at any time through +[`srt_group_data`](#srt_group_data) or the data filled by +[`srt_sendmsg2`](#srt_sendmsg2) and [`srt_recvmsg2`](#srt_recvmsg2) +in the [`SRT_MSGCTRL`](#SRT_MSGCTRL) structure. + +3. Listening sockets are not bound to groups anyhow. You can allow multiple +listening sockets to accept group connections and the connection extracted +from the listener, if it is declared to be a group member, will join its +group, no matter which of the listening sockets has received the connection +request. This feature is prone to more tricky rules, however: + + * If you use multiple listener sockets, all of them in blocking mode, + allowed for group connections, and receiving connection requests for + the same group at the moment, and you run one thread per `srt_accept` + call, it is undefined, which of them will extract the group ID + for the connection, but still only one will, while the others will + continue blocking. If you want to use only one thread for accepting + connections from potentially multiple listening sockets in the blocking + mode, you should use [`srt_accept_bond`](#srt_accept_bond) instead. + Note though that this function is actually a wrapper that changes locally + to the nonblocking mode on all these listeners and uses epoll internally. + + * If at the moment multiple listener sockets have received connection + request and you query them all for readiness epoll flags (by calling + an epoll waiting function), all of them will get the `SRT_EPOLL_ACCEPT` + flag set, but still only one of them will return the group ID from the + `srt_accept` call. After this call, from all listener sockets in the + whole application the `SRT_EPOLL_ACCEPT` flag, that was set by the reason + of a pending connection for the same group, will be withdrawn (that is, + it will be cleared if there are no other pending connections). This is + then yet another situation when this flag can be spurious. + +4. If you query a listening socket for epoll flags after the `srt_accept` +function has once returned the group ID, the listening sockets that have +received new member connection requests within that group will report only the +[`SRT_EPOLL_UPDATE`](#SRT_EPOLL_UPDATE) flag. This flag is edge-triggered-only +because there is no operation you can perform in response in order to clear +this flag. This flag is mostly used internally and the application may use it +if it would like to trigger updating the current group information due to +having one newly added member connection. + + | Returns | | |:----------------------------- |:----------------------------------------------------------------------- | @@ -673,7 +818,7 @@ internal use only. | Errors | | |:--------------------------------- |:----------------------------------------------------------------------- | -| [`SRT_EINVPARAM`](#srt_einvparam) | NULL specified as `addrlen`, when `addr` is not NULL | +| [`SRT_EINVPARAM`](#srt_einvparam) | Invalid `addr` or `addrlen` (see requirements in the begininng) | | [`SRT_EINVSOCK`](#srt_einvsock) | `lsn` designates no valid socket ID. | | [`SRT_ENOLISTEN`](#srt_enolisten) | `lsn` is not set up as a listener ([`srt_listen`](#srt_listen) not called). | | [`SRT_EASYNCRCV`](#srt_easyncrcv) | No connection reported so far. This error is reported only in the non-blocking mode | @@ -723,7 +868,7 @@ calling this function. | Returns | | |:----------------------------- |:---------------------------------------------------------------------- | -| SRT socket
group ID | On success, a valid SRT socket or group ID to be used for transmission | +| SRT socket/group ID | On success, a valid SRT socket or group ID to be used for transmission | | `SRT_INVALID_SOCK` | (-1) on failure | | | | @@ -743,7 +888,7 @@ calling this function. ### srt_listen_callback ``` -int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); +SRTSTATUS srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); ``` This call installs a callback hook, which will be executed on a socket that is @@ -760,12 +905,6 @@ i.e. before `srt_listen` is called. * `hook_fn`: The callback hook function pointer (or NULL to remove the callback) * `hook_opaque`: The pointer value that will be passed to the callback function -| Returns | | -|:----------------------------- |:---------------------------------------------------------- | -| 0 | Successful | -| -1 | Error | -| | | - | Errors | | |:--------------------------------- |:----------------------------------------- | | [`SRT_ECONNSOCK`](#srt_econnsock) | It can't be modified in a connected socket| @@ -831,7 +970,7 @@ database you have to check against the data received in `streamid` or `peeraddr` ### srt_connect ``` -int srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRTSOCKET srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); ``` Connects a socket or a group to a remote party with a specified address and port. @@ -849,8 +988,8 @@ Connects a socket or a group to a remote party with a specified address and port or binding and connection can be done in one function ([`srt_connect_bind`](#srt_connect_bind)), such that it uses a predefined network interface or local outgoing port. This is optional in the case of a caller-listener arrangement, but obligatory for a rendezvous arrangement. -If not used, the binding will be done automatically to `INADDR_ANY` (which binds on all -interfaces) and port 0 (which makes the system assign the port automatically). +If not used, the binding will be done automatically to a wildcard address and port 0. See +[`srt_bind](#srt_bind) for details. 2. This function is used for both connecting to the listening peer in a caller-listener arrangement, and calling the peer in rendezvous mode. For the latter, the @@ -866,16 +1005,21 @@ automatically for every call of this function. mode, you might want to use [`srt_connect_group`](#srt_connect_group) instead. This function also allows you to use additional settings, available only for groups. +The returned value is a socket ID value. When `u` is a socket ID, the returned +is a special value `SRT_SOCKID_CONNREQ`. When `u` is a group ID, the returned +value is the socket ID of the newly created member for the requested link. In +the case of failure, `SRT_INVALID_SOCK` is returned. + | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error | -| 0 | In case when used for [`u`](#u) socket | +| `SRT_INVALID_SOCK` | (-1) in case of error | +| `SRT_SOCKID_CONNREQ` | In case when used for [`u`](#u) socket | | Socket ID | Created for connection for [`u`](#u) group | | | | | Errors | | |:------------------------------------- |:----------------------------------------------------------- | -| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | +| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket or group ID | | [`SRT_ERDVUNBOUND`](#srt_erdvunbound) | Socket [`u`](#u) is in rendezvous mode, but it wasn't bound (see note #2) | | [`SRT_ECONNSOCK`](#srt_econnsock) | Socket [`u`](#u) is already connected | | [`SRT_ECONNREJ`](#srt_econnrej) | Connection has been rejected | @@ -900,7 +1044,9 @@ In the case of "late" failures you can additionally call information. Note that in blocking mode only for the `SRT_ECONNREJ` error this function may return any additional information. In non-blocking mode a detailed "late" failure cannot be distinguished, and therefore it -can also be obtained from this function. +can also be obtained from this function. Note that the connection timeout +error can be also recognized through this call, even though it is reported +by `SRT_ENOSERVER` in the blocking mode. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -910,7 +1056,7 @@ can also be obtained from this function. ### srt_connect_bind ``` -int srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, +SRTSOCKET srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int len); ``` @@ -928,8 +1074,8 @@ first on the automatically created socket for the connection. | Returns | | |:----------------------------- |:-------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error | -| 0 | In case when used for [`u`](#u) socket | +| `SRT_INVALID_SOCK` | (-1) in case of error | +| `SRT_SOCKID_CONNREQ` | In case when used for [`u`](#u) socket | | Socket ID | Created for connection for [`u`](#u) group | | | | @@ -958,7 +1104,7 @@ different families (that is, both `source` and `target` must be `AF_INET` or ### srt_connect_debug ``` -int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); +SRTSOCKET srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); ``` This function is for developers only and can be used for testing. It does the @@ -973,7 +1119,7 @@ is generated randomly. ### srt_rendezvous ``` -int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, +SRTSTATUS srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); ``` Performs a rendezvous connection. This is a shortcut for doing bind locally, @@ -986,11 +1132,6 @@ to true, and doing [`srt_connect`](#srt_connect). * `local_name`: specifies the local network interface and port to bind * `remote_name`: specifies the remote party's IP address and port -| Returns | | -|:----------------------------- |:-------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | - | Errors | | |:------------------------------------- |:-------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket passed as [`u`](#u) designates no valid socket | @@ -1012,7 +1153,7 @@ allowed (that is, both `local_name` and `remote_name` must be `AF_INET` or `AF_I ### srt_connect_callback ``` -int srt_connect_callback(SRTSOCKET u, srt_connect_callback_fn* hook_fn, void* hook_opaque); +SRTSTATUS srt_connect_callback(SRTSOCKET u, srt_connect_callback_fn* hook_fn, void* hook_opaque); ``` This call installs a callback hook, which will be executed on a given [`u`](#u) @@ -1044,16 +1185,10 @@ connection failures. * `hook_opaque`: The pointer value that will be passed to the callback function -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Successful | -| -1 | Error | -| | | - -| Errors | | -|:---------------------------------- |:------------------------------------------| -| [`SRT_ECONNSOCK`](#srt_econnsock) | It can't be modified in a connected socket| -| | | +| Errors | | +|:---------------------------------- |:-------------------------------------------| +| [`SRT_ECONNSOCK`](#srt_econnsock) | It can't be modified in a connected socket | +| | | The callback function signature has the following type definition: @@ -1125,7 +1260,7 @@ where: * `token`: An integer value unique for every connection, or -1 if unused The `srt_prepare_endpoint` sets these fields to default values. After that -you can change the value of `weight` and `config` and `token` fields. The +you can change the value of `weight`, `config` and `token` fields. The `weight` parameter's meaning is dependent on the group type: * BROADCAST: not used @@ -1175,6 +1310,28 @@ where: * `result`: result of the operation (if this operation recently updated this structure) * `token`: A token value set for that connection (see [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG)) +The weight is set to 0 by default by `srt_prepare_endpoint()` - you can set +it to a different value afterwards. The meaning of weight depends on the group +type: + +1. Backup groups: in this case it defines the link priority. The default 0 +value is the lowest priority and greater values declare higher priorities. The +priority for the backup groups determines which link is activated first when +the currently active link is unstable, and which should keep transmitting when +multiple active links are currently stable. + +2. Balancing groups with "fixed" algorithm: in this case it defines the +desired link load share. You can think of it as a percentage of link load, +but indeed a load percentage is defined as this weight value divided by a sum +of all weight values from all member links. Note however that the sum is +calculated out of all links that have been successfully connected. The +default 0 is also a special value that defines an "equalized" load share +(it's set to the arithmetic average of the weights from all links). + +The `SRT_SOCKGROUPDATA` structure is used in multiple purposes: + +* Prepare data for connection +* Getting the current member status [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -1269,10 +1426,12 @@ Retrieves the group SRT socket ID that corresponds to the member socket ID `memb | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| `SRTSOCKET` | Corresponding group SRT socket ID of the member socket. | +| `SRTSOCKET` | Corresponding group SRT socket ID of the `member` socket. | | `SRT_INVALID_SOCK` | The socket doesn't exist, it is not a member of any group, or bonding API is disabled. | | | | +In the case of `SRT_INVALID_SOCK`, the error is set to `SRT_EINVPARAM`. + [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -1281,7 +1440,7 @@ Retrieves the group SRT socket ID that corresponds to the member socket ID `memb #### srt_group_data ``` -int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* inoutlen); +SRTSTATUS srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* inoutlen); ``` **Arguments**: @@ -1292,23 +1451,26 @@ int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA output[], size_t* in and is set to the filled array's size This function obtains the current member state of the group specified in -`socketgroup`. The `output` should point to an array large enough to hold all -the elements. The `inoutlen` should point to a variable initially set to the size -of the `output` array. The current number of members will be written back to `inoutlen`. +`socketgroup`. -If the size of the `output` array is enough for the current number of members, -the `output` array will be filled with group data and the function will return -the number of elements filled. Otherwise the array will not be filled and -`SRT_ERROR` will be returned. +The `inoutlen` should point to a variable initially set to the size +of the `output` array. The current number of members will be written back to +the variable specified in `inoutlen`. This paramterer cannot be NULL. -This function can be used to get the group size by setting `output` to `NULL`, -and providing `socketgroup` and `inoutlen`. +If `output` is specified and the size of the array is at least equal to the +number of group members, the `output` array will be filled with group data. -| Returns | | -|:----------------------------- |:-------------------------------------------------- | -| # of elements | The number of data elements filled, on success | -| -1 | Error | -| | | +If `output` is NULL then the function will only retrieve the number of elements +in `inoutlen`. + +This call will fail and return `SRT_ERROR` if: + +* The `socketgroup` parameter is invalid + +* The `inoutlen` parameter is NULL + +* The size specified in a variable passed via `inoutlen` is less than the number +of group members | Errors | | @@ -1318,13 +1480,13 @@ and providing `socketgroup` and `inoutlen`. | | | -| in:output | in:inoutlen | returns | out:output | out:inoutlen | Error | -|:---------:|:--------------:|:------------:|:----------:|:------------:|:---------------------------------:| -| NULL | NULL | -1 | NULL | NULL | [`SRT_EINVPARAM`](#srt_einvparam) | -| NULL | ptr | 0 | NULL | group.size() | ✖️ | -| ptr | NULL | -1 | ✖️ | NULL | [`SRT_EINVPARAM`](#srt_einvparam) | -| ptr | ≥ group.size | group.size() | group.data | group.size | ✖️ | -| ptr | < group.size | -1 | ✖️ | group.size | [`SRT_ELARGEMSG`](#srt_elargemsg) | +| in:output | in:inoutlen | returns | out:output | out:inoutlen | Error | +|:---------:|:--------------:|:---------------:|:----------:|:------------:|:---------------------------------:| +| ptr | ≥ group.size | `SRT_STATUS_OK` | group.data | group.size() | ✖️ | +| NULL | ptr | `SRT_STATUS_OK` | (unused) | group.size() | ✖️ | +| NULL | NULL | `SRT_ERROR` | (unused) | (not filled) | [`SRT_EINVPARAM`](#srt_einvparam) | +| ptr | NULL | `SRT_ERROR` | (unused) | (not filled) | [`SRT_EINVPARAM`](#srt_einvparam) | +| ptr | < group.size | `SRT_ERROR` | (unused) | group.size() | [`SRT_ELARGEMSG`](#srt_elargemsg) | [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -1334,14 +1496,14 @@ and providing `socketgroup` and `inoutlen`. #### srt_connect_group ``` -int srt_connect_group(SRTSOCKET group, - SRT_SOCKGROUPCONFIG name [], int arraysize); +SRTSOCKET srt_connect_group(SRTSOCKET group, + SRT_SOCKGROUPCONFIG links [], int arraysize); ``` This function does almost the same as calling [`srt_connect`](#srt_connect) or [`srt_connect_bind`](#srt_connect_bind) (when the source was specified for [`srt_prepare_endpoint`](#srt_prepare_endpoint)) in a loop for every item specified -in the `name` array. However if blocking mode is being used, the first call to +in the `links` array. However if blocking mode is being used, the first call to [`srt_connect`](#srt_connect) would block until the connection is established, whereas this function blocks until any of the specified connections is established. @@ -1350,21 +1512,21 @@ option), there's no difference, except that the [`SRT_SOCKGROUPCONFIG`](#SRT_SOC structure allows adding extra configuration data used by groups. Note also that this function accepts only groups, not sockets. -The elements of the `name` array need to be prepared with the use of the +The elements of the `links` array need to be prepared with the use of the [`srt_prepare_endpoint`](#srt_prepare_endpoint) function. Note that it is **NOT** required that every target address specified is of the same family. Return value and errors in this function are the same as in [`srt_connect`](#srt_connect), although this function reports success when at least one connection has succeeded. If none has succeeded, this function reports an [`SRT_ECONNLOST`](#srt_econnlost) -error. Particular connection states can be obtained from the `name` -array upon return from the [`errorcode`](#error-codes) field. +error. Particular connection states can be obtained from the `links` array upon +return from the [`errorcode`](#error-codes) field. The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the following meaning: **Input**: -* `id`: unused, should be -1 (default when created by [`srt_prepare_endpoint`](#srt_prepare_endpoint)) +* `id`: unused, should be `SRT_INVALID_SOCK` (default when created by [`srt_prepare_endpoint`](#srt_prepare_endpoint)) * `srcaddr`: address to bind before connecting, if specified (see below for details) * `peeraddr`: target address to connect * `weight`: weight value to be set on the link @@ -1374,7 +1536,7 @@ The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the f **Output**: -* `id`: The socket created for that connection (-1 if failed to create) +* `id`: The socket created for that connection (`SRT_INVALID_SOCK` if failed to create) * `srcaddr`: unchanged * `peeraddr`: unchanged * `weight`: unchanged @@ -1384,8 +1546,8 @@ The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the f | Returns | | |:----------------------------- |:-------------------------------------------------- | -| `SRT_SOCKET` | The socket ID of the first connected member. | -| -1 | Error | +| Socket ID | The socket ID of the first connected member. | +| `SRT_INVALID_SOCK` | Error | | | | @@ -1396,7 +1558,7 @@ The fields of [`SRT_SOCKGROUPCONFIG`](#SRT_SOCKGROUPCONFIG) structure have the f | | | The procedure of connecting for every connection definition specified -in the `name` array is performed the following way: +in the `links` array is performed the following way: 1. The socket for this connection is first created @@ -1423,7 +1585,8 @@ then, and for which the connection attempt has at least successfully started, remain group members, although the function will return immediately with an error status (that is, without waiting for the first successful connection). If your application wants to do any partial recovery from this situation, it can -only use the epoll mechanism to wait for readiness. +only check the current member status via [`srt_group_data`](#srt_group_data) +and wait for group's write readiness (`SRT_EPOLL_OUT`) by using epoll. 2. In any other case, if an error occurs at any stage of the above process, the processing is interrupted for this very array item only, the socket used for it @@ -1431,16 +1594,18 @@ is immediately closed, and the processing of the next elements continues. In the of a connection process, it also passes two stages - parameter check and the process itself. Failure at the parameter check breaks this process, while if the check passes, this item is considered correctly processed, even if the connection -attempt is going to fail later. If this function is called in blocking mode, -it then blocks until at least one connection reports success, or if all of them -fail. The status of connections that continue in the background after this function -exits can then be checked by [`srt_group_data`](#srt_group_data). +attempt is going to fail later. + +If this function is called in blocking mode, it then blocks until at least one +connection reports success, or if all of them fail. The status of connections +that continue in the background after this function exits can then be checked +by [`srt_group_data`](#srt_group_data). As member socket connections are running in the background, for determining if a particular connection has succeeded or failed it is recommended to use [`srt_connect_callback`](#srt_connect_callback). In this case the `token` callback function parameter will be the same as the `token` value used -for the particular item in the `name` connection table. +for the particular item in the `links` connection array. The `token` value doesn't have any limitations except that the -1 value is a "trap representation", that is, when set on input it will make the internals @@ -1542,7 +1707,7 @@ Deletes the configuration object. #### srt_config_add ``` -int srt_config_add(SRT_SOCKOPT_CONFIG* c, SRT_SOCKOPT opt, void* val, int len); +SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG* c, SRT_SOCKOPT opt, void* val, int len); ``` Adds a configuration option to the configuration object. @@ -1572,12 +1737,6 @@ The following options are allowed to be set on the member socket: * [`SRTO_UDP_SNDBUF`](API-socket-options.md#SRTO_UDP_SNDBUF): UDP sender buffer, if this link has a big flight window -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:---------------------------------- |:--------------------------------------------------------------------- | | [`SRT_EINVPARAM`](#srt_einvparam) | This option is not allowed to be set on a socket being a group member. Or if bonding API is disabled. | @@ -1603,15 +1762,11 @@ The following options are allowed to be set on the member socket: ### srt_getpeername ``` -int srt_getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRTSTATUS srt_getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); ``` Retrieves the remote address to which the socket is connected. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | | Errors | | |:------------------------------- |:------------------------------------------------------------------------ | @@ -1626,7 +1781,7 @@ Retrieves the remote address to which the socket is connected. ### srt_getsockname ``` -int srt_getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRTSTATUS srt_getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); ``` Extracts the address to which the socket was bound. Although you should know @@ -1635,10 +1790,6 @@ useful for extracting the local outgoing port number when it was specified as 0 with binding for system autoselection. With this function you can extract the port number after it has been autoselected. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | | Errors | | |:------------------------------- |:---------------------------------------------- | @@ -1667,8 +1818,8 @@ if (res < 0) { ### srt_getsockflag ```c++ -int srt_getsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, void* optval, int* optlen); -int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); +SRTSTATUS srt_getsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, void* optval, int* optlen); +SRTSTATUS srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); ``` Gets the value of the given socket option (from a socket or a group). @@ -1686,11 +1837,6 @@ For most options, it will be the size of an integer. Some options, however, use The application is responsible for allocating sufficient memory space as defined and pointed to by `optval`. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | - | Errors | | |:-------------------------------- |:---------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | @@ -1705,8 +1851,8 @@ The application is responsible for allocating sufficient memory space as defined ### srt_setsockflag ```c++ -int srt_setsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, const void* optval, int optlen); -int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); +SRTSTATUS srt_setsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT opt, const void* optval, int optlen); +SRTSTATUS srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); ``` Sets a value for a socket option in the socket or group. @@ -1723,11 +1869,6 @@ Please note that some of the options can only be set on sockets or only on groups, although most of the options can be set on the groups so that they are then derived by the member sockets. -| Returns | | -|:----------------------------- |:----------------------------------------------- | -| `SRT_ERROR` | (-1) in case of error, otherwise 0 | -| | | - | Errors | | |:----------------------------------- |:--------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid socket ID | @@ -1736,7 +1877,7 @@ are then derived by the member sockets. | [`SRT_ECONNSOCK`](#srt_econnsock) | Tried to set an option with PRE_BIND or PRE restriction on a socket in connecting/listening/connected state. | | | | -**NOTE*: Various other errors may result from problems when setting a +**NOTE**: Various other errors may result from problems when setting a specific option (see option description in [API-socket-options.md](./API-socket-options.md) for details). @@ -1751,7 +1892,7 @@ uint32_t srt_getversion(); ``` Get SRT version value. The version format in hex is 0xXXYYZZ for x.y.z in human -readable form, where x = ("%d", (version>>16) & 0xff), etc. +readable form. E.g. 0x012033 means version 1.20.33. | Returns | | |:----------------------------- |:--------------------------------------------------------- | @@ -1844,6 +1985,15 @@ the array; otherwise both fields are updated to reflect the current connection s of the group. For details, see the [SRT Connection Bonding: Quick Start](../features/bonding-intro.md) and [SRT Connection Bonding: Socket Groups](../features/socket-groups.md) documents. +For more information about `SRT_SOCKGROUPDATA` and obtaining the group +data, please refer to [srt_group_data](#srt_group_data). Note that the +group data filling by `srt_sendmsg2` and `srt_recvmsg2` calls differs in one +aspect to `srt_group_data`: member sockets that were found broken after the +operation will appear in the group data with `SRTS_BROKEN` state once after the +operation was done, although the sockets assigned to these members are already +closed and they are removed as members already. In case of `srt_group_data` +they will not appear at all. + **Helpers for [`SRT_MSGCTRL`](#SRT_MSGCTRL):** ``` @@ -1919,7 +2069,7 @@ single call to this function determines a message's boundaries. |:----------------------------- |:--------------------------------------------------------- | | Size | Size of the data sent, if successful | | `SRT_ERROR` | In case of error (-1) | -| | | +| | | **NOTE**: Note that in **file/stream mode** the returned size may be less than `len`, which means that it didn't send the whole contents of the buffer. You would need to @@ -1998,10 +2148,10 @@ the currently lost one, it will be delivered and the lost one dropped. | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| Size | Size (\>0) of the data received, if successful. | +| Size value \> 0 | Size of the data received, if successful. | | 0 | If the connection has been closed | | `SRT_ERROR` | (-1) when an error occurs | -| | | +| | | | Errors | | |:--------------------------------------------- |:--------------------------------------------------------- | @@ -2054,7 +2204,7 @@ You need to pass them to the [`srt_sendfile`](#srt_sendfile) or | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| Size | The size (\>0) of the transmitted data of a file. It may be less than `size`, if the size was greater
than the free space in the buffer, in which case you have to send rest of the file next time. | +| Size value \> 0 | The size of the transmitted data of a file. It may be less than `size`, if the size was greater
than the free space in the buffer, in which case you have to send rest of the file next time. | | -1 | in case of error | | | | @@ -2098,10 +2248,10 @@ the required range already, so for a numbers like 0x7FFFFFF0 and 0x10, for which ### srt_bistats ``` // Performance monitor with Byte counters for better bitrate estimation. -int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); +SRTSTATUS srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // Performance monitor with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. -int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); +SRTSTATUS srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); ``` Reports the current statistics @@ -2117,12 +2267,6 @@ Reports the current statistics of the fields please refer to [SRT Statistics](statistics.md). -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Invalid socket ID provided. @@ -2175,11 +2319,11 @@ function can then be used to block until any readiness status in the whole int srt_epoll_create(void); ``` -Creates a new epoll container. +Creates a new epoll container and returns its identifier (EID). | Returns | | |:----------------------------- |:--------------------------------------------------------- | -| valid EID | Success | +| valid EID >= 0 | Success | | -1 | Failure | | | | @@ -2199,10 +2343,10 @@ Creates a new epoll container. ### srt_epoll_update_ssock ``` -int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); -int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); -int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); -int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); +SRTSTATUS srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); +SRTSTATUS srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); +SRTSTATUS srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); +SRTSTATUS srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); ``` Adds a socket to a container, or updates an existing socket subscription. @@ -2282,12 +2426,6 @@ as level-triggered, you can do two separate subscriptions for the same socket. any possible flag, you must use [`srt_epoll_uwait`](#srt_epoll_uwait). Note that this function doesn't work with system file descriptors. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2306,8 +2444,8 @@ the [`SRT_ECONNSETUP`](#srt_econnsetup) code is predicted. ### srt_epoll_remove_ssock ``` -int srt_epoll_remove_usock(int eid, SRTSOCKET u); -int srt_epoll_remove_ssock(int eid, SYSSOCKET s); +SRTSTATUS srt_epoll_remove_usock(int eid, SRTSOCKET u); +SRTSTATUS srt_epoll_remove_ssock(int eid, SYSSOCKET s); ``` Removes a specified socket from an epoll container and clears all readiness @@ -2316,12 +2454,6 @@ states recorded for that socket. The `_usock` suffix refers to a user socket (SRT socket). The `_ssock` suffix refers to a system socket. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2380,7 +2512,7 @@ the only way to know what kind of error has occurred on the socket. | Returns | | |:----------------------------- |:------------------------------------------------------------ | -| Number | The number (\>0) of ready sockets, of whatever kind (if any) | +| Number \> 0 | The number of ready sockets, of whatever kind (if any) | | -1 | Error | | | | @@ -2419,7 +2551,7 @@ indefinitely until a readiness state occurs. | Returns | | |:----------------------------- |:-------------------------------------------------------------------------------------------------------------------------------------- | -| Number | The number of user socket (SRT socket) state changes that have been reported in `fdsSet`,
if this number isn't greater than `fdsSize` | +| Number \> 0 | The number of user socket (SRT socket) state changes that have been reported in `fdsSet`,
if this number isn't greater than `fdsSize` | | `fdsSize` + 1 | This means that there was not enough space in the output array to report all events.
For events subscribed with the [`SRT_EPOLL_ET`](#SRT_EPOLL_ET) flag only those will be cleared that were reported.
Others will wait for the next call. | | 0 | If no readiness state was found on any socket and the timeout has passed
(this is not possible when waiting indefinitely) | | -1 | Error | @@ -2428,8 +2560,14 @@ indefinitely until a readiness state occurs. | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | -| [`SRT_EINVPARAM`](#srt_einvparam) | One of possible usage errors:
* `fdsSize` is < 0
* `fdsSize` is > 0 and `fdsSet` is a null pointer
* [`eid`](#eid) was subscribed to any system socket | -| | | +| [`SRT_EINVPARAM`](#srt_einvparam) | Usage error (see below) | +| | | + +Usage errors reported as `SRT_EINVPARAM`: + +* `fdsSize` is \< 0 +* `fdsSize` is \> 0 and `fdsSet` is a null pointer +* [`eid`](#eid) was subscribed to any system socket **IMPORTANT**: This function reports timeout by returning 0, not by [`SRT_ETIMEOUT`](#srt_etimeout) error. @@ -2459,18 +2597,12 @@ closed and its state can be verified with a call to [`srt_getsockstate`](#srt_ge ### srt_epoll_clear_usocks ``` -int srt_epoll_clear_usocks(int eid); +SRTSTATUS srt_epoll_clear_usocks(int eid); ``` This function removes all SRT ("user") socket subscriptions from the epoll container identified by [`eid`](#eid). -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Success | -| -1 | Failure | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2512,7 +2644,7 @@ the general output array is not empty. | Returns | | |:----------------------------- |:-------------------------------------------------------------------------- | | | This function returns the state of the flags at the time before the call | -| -1 | Special value in case when an error occurred | +| `SRT_ERROR` (-1) | Special value in case when an error occurred | | | | | Errors | | @@ -2527,17 +2659,11 @@ the general output array is not empty. ### srt_epoll_release ``` -int srt_epoll_release(int eid); +SRTSTATUS srt_epoll_release(int eid); ``` Deletes the epoll container. -| Returns | | -|:----------------------------- |:-------------------------------------------------------------- | -| | The number (\>0) of ready sockets, of whatever kind (if any) | -| -1 | Error | -| | | - | Errors | | |:----------------------------------- |:----------------------------------------------------------------- | | [`SRT_EINVPOLLID`](#srt_einvpollid) | [`eid`](#eid) parameter doesn't refer to a valid epoll container | @@ -2753,7 +2879,7 @@ and `msTimeStamp` value of the `SRT_TRACEBSTATS` (see [SRT Statistics](statistic | Returns | | |:----------------------------- |:--------------------------------------------------------------------------- | | | Connection time in microseconds elapsed since epoch of SRT internal clock | -| -1 | Error | +| `SRT_ERROR` (-1) | Error | | | | | Errors | | @@ -2807,6 +2933,7 @@ to timestamp packets submitted to SRT is not recommended and must be done with a * [srt_getrejectreason](#srt_getrejectreason) * [srt_rejectreason_str](#srt_rejectreason_str) * [srt_setrejectreason](#srt_setrejectreason) +* [srt_close_getreason](#srt_close_getreason) General notes concerning the `getlasterror` diagnostic functions: when an API function ends up with error, this error information is stored in a thread-local @@ -2894,10 +3021,6 @@ For other values below `SRT_REJC_PREDEFINED` it returns the string for [`SRT_REJ_UNKNOWN`](#SRT_REJ_UNKNOWN). For values since `SRT_REJC_PREDEFINED` on, returns "Application-defined rejection reason". -The actual messages assigned to the internal rejection codes, that is, less than -`SRT_REJ_E_SIZE`, can be also obtained from the `srt_rejectreason_msg` array. - - [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) --- @@ -2905,7 +3028,7 @@ The actual messages assigned to the internal rejection codes, that is, less than ### srt_setrejectreason ``` -int srt_setrejectreason(SRTSOCKET sock, int value); +SRTSTATUS srt_setrejectreason(SRTSOCKET sock, int value); ``` Sets the rejection code on the socket. This call is only useful in the listener @@ -2919,12 +3042,6 @@ can inform the calling side that the resource specified under the `r` key in the StreamID string (see [`SRTO_STREAMID`](API-socket-options.md#SRTO_STREAMID)) is not available - it then sets the value to `SRT_REJC_PREDEFINED + 404`. -| Returns | | -|:----------------------------- |:--------------------------------------------------------- | -| 0 | Error | -| -1 | Success | -| | | - | Errors | | |:--------------------------------- |:-------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket `sock` is not an ID of a valid socket | @@ -2950,6 +3067,40 @@ used for the connection, the function should also be called when the a numeric code, which can be translated into a message by [`srt_rejectreason_str`](#srt_rejectreason_str). +The returned value is one of the values listed in enum `SRT_REJECT_REASON`. +For an invalid value of `sock` the `SRT_REJ_UNKNOWN` is returned. + +[:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) + +--- + +### srt_close_getreason + +``` +int srt_close_getreason(SRTSOCKET u, SRT_CLOSE_INFO* info); +``` + +Retrieves the reason code for closing the socket. This designates the +very first reason of closing the socket or group (if there could have +been multiple reasons for closing it, only the first one counts). + +Note that this information may be retrieved even if the socket is already +physically closed, but only for up to 10 seconds after that happens (more +precisely, 10 cycles of GC, which run every 1 second) and only up to 10 such +records are remembered (newer closed ones push off the oldest one). + +**Arguments**: + +* `u`: Socket or group that you believe is closed or broken +* `info`: The structure where the reason is written, see [`SRT_CLOSE_INFO`](#srt_close_info) + +Returns 0 in case of success. Returns `SRT_ERROR` (-1) in case of error, +which may be because: + +* `info` is a NULL pointer +* `u` is an `SRT_INVALID_SOCK` value +* `u` was not found in the closed socket database (expired or was pushed off) + [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -2960,7 +3111,8 @@ a numeric code, which can be translated into a message by #### SRT_REJ_UNKNOWN -A fallback value for cases when there was no connection rejected. +A fallback value for cases when there was no connection rejected or the +reason cannot be obtained. #### SRT_REJ_SYSTEM @@ -3077,6 +3229,151 @@ and above is reserved for "predefined codes" (`SRT_REJC_PREDEFINED` value plus adopted HTTP codes). Values above `SRT_REJC_USERDEFINED` are freely defined by the application. +#### SRT_REJ_CRYPTO + +Settings for `SRTO_CRYPTOMODE` on both parties are not compatible with one another. +See [`SRTO_CRYPTOMODE`](API-socket-options.md#SRTO_CRYPTOMODE) for details. + +#### SRT_REJ_CONFIG + +Settings for various transmission parameters that are supposed to be negotiated +during the handshake (in order to agree upon a common value) are under restrictions +that make finding common values for them impossible. Cases include: + +* `SRTO_PAYLOADSIZE`, which is nonzero in live mode, is set to a value that +exceeds the free space in a single packet that results from the value of the +negotiated MSS value + + +[:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) + +--- + +### `SRT_CLOSE_INFO` + +This structure can be used to get the closing reason (simplified definition): + +``` +struct SRT_CLOSE_INFO +{ + SRT_CLOSE_REASON agent; + SRT_CLOSE_REASON peer; + int64_t time; +}; +``` + +Where: + +* `agent`: The reason code set on the agent ("this machine") side of the connection +if the very first reason of closing has happened on the agent. If the closing was +initiated by the peer, this field contains `SRT_CLS_PEER` value + +* `peer`: If `agent` == `SRT_CLS_PEER`, then closing was initiated by peer and this +field contains the value of this reason + +* `time`: Time when closing has happened, in the same convention as the time value +supplied by [`srt_time_now`](#srt_time_now) + +The values for `agent` and `peer` can be internal, out of the below shown list, or +it can be a user code, if the value is at least `SRT_CLSC_USER`. + + +### Closing reasons + +#### SRT_CLS_UNKNOWN + +The reason not set. The value is used as a fallback if the reason wasn't properly set. + +#### SRT_CLS_INTERNAL + +Closed by internal reasons during connection attempt. + +#### SRT_CLS_PEER + +Closed by the peer (the value is to be used on agent). This happens when the closing +action has been initiated by peer through sending the `UMSG_SHUTDOWN` message. + +#### SRT_CLS_RESOURCE + +A problem with resource allocation. + +#### SRT_CLS_ROGUE + +Received wrong data in the packet. This happens when the socket was closed +due to security reasons, when the data in the packet do not match the expected +protocol specification. + +#### SRT_CLS_OVERFLOW + +Emergency close due to receiver buffer's double overflow that has lead to +an irrecoverable situation. This happens when too slow reading data by the +application has caused that first, incoming packets cannot be inserted into +the buffer because the position in the buffer mapped to their sequence number +locates them outside the buffer. If this situation isn't quickly recovered +from, it causes eventually that the sequence number distance between the last +packet still stored in the buffer and the newly incoming packet exceeds the +size of the receiver buffer. This is then an irrecoverable situation and in +result the socket is closed with this code as a reason. + +#### SRT_CLS_IPE + +Internal program error. Currently used if the incoming acknowledge packet +represents the sequence number that has never been sent, or the value is +out of any valid range. + +#### SRT_CLS_API + +The socket has been closed by the API call of `srt_close()`. This code +is set also if the reason value used in `srt_close_withreason()` is +less than `SRT_CLSC_USER`. + +#### SRT_CLS_FALLBACK + +This value is set on the `peer` field in case when the peer runs the SRT +version that does not support this feature. If this feature is supported, +then the peer should send `UMSG_SHUTDOWN` message with the reason value, +which will be then set on the `peer` field. + +#### SRT_CLS_LATE + +Accepted-socket late-rejection or in-handshake rollback. The late rejection +is something that may happen when the listener side responds to the caller +with a proper handshake message, but the caller rejects that message by +some reason. This way, the caller gets closed with a rejection reason, but +at this moment the accepted socket on the listener side considers itself +connected. Therefore the caller socket in this situation sends first +the `UMSG_SHUTDOWN` message to the peer (that is, the accepted socket) +and in result the accepted socket gets closed with this reason code. + +#### SRT_CLS_CLEANUP + +All sockets are being closed due to srt_cleanup() call. + +#### SRT_CLS_DEADLSN + +This is an accepted socket off a dead listener. If the listener +socket has been closed before the accepted socket could be completed, +the socket can be returned as valid, but could not be used due to +having the listener socket closed too early. + +#### SRT_CLS_PEERIDLE + +Peer didn't send any packet for a time of `SRTO_PEERIDLETIMEO`. This +means that the peer idle timeout has been reached while waiting for +any packet incoming from the peer. + +#### SRT_CLS_UNSTABLE + +Requested to be broken as unstable in Backup group. This happens +exclusively in the group of type `SRT_GTYPE_BACKUP` in case when +the link that was used so far for transmission, has become too +slowly responsive, which caused activation of one of the backup +links, and then this link didn't get back to stability in a given +time (the minimum is configured in `SRTO_GROUPMINSTABLETIMEO`), +which caused that the newly activated link has taken over transmission +and the socket using the unstable link has been closed with this +reason code. + [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) @@ -3147,7 +3444,8 @@ is no longer usable. #### SRT_ECONNFAIL -General connection failure of unknown details. +General connection failure of unknown details (currently is not reported +directly by any API function and it's reserved for future use). #### SRT_ECONNLOST @@ -3347,9 +3645,18 @@ you can subscribe them later from another thread. #### `SRT_EBINDCONFLICT` -The binding you are attempting to set up a socket with cannot be completed because -it conflicts with another existing binding. This is because an intersecting binding -was found that cannot be reused according to the specification in `srt_bind` call. +The binding you are attempting to set up a socket with, using the `srt_bind` +call, cannot be completed because it conflicts with another existing binding. + +An attempt of binding a socket, in the conditions of having some other socket already +bound to the same port number, can result in one of three possibilities: + +1. The binding is separate to the existing one (succeeds). +2. The binding intersects with the exiting one (fails). +3. The binding is exactly identical to the existing one (see below). + +See the [`srt_bind`](#srt_bind) for a reference about what kinds of binding +addresses can coexist without conflicts. A binding is considered intersecting if the existing binding has the same port and covers at least partially the range as that of the attempted binding. These @@ -3365,30 +3672,33 @@ Example 1: * Socket 1: bind to IPv4 0.0.0.0 * Socket 2: bind to IPv6 :: with `SRTO_IPV6ONLY` = true -* Result: NOT intersecting +* Result: NOT intersecting, allowed to proceed Example 2: * Socket 1: bind to IPv4 1.2.3.4 * Socket 2: bind to IPv4 0.0.0.0 -* Result: intersecting (and conflicting) +* Result: failure: 0.0.0.0 encloses 1.2.3.4, so they are in conflict Example 3: * Socket 1: bind to IPv4 1.2.3.4 * Socket 2: bind to IPv6 :: with `SRTO_IPV6ONLY` = false -* Result: intersecting (and conflicting) +* Result: failure: this encloses all IPv4, so it conflicts with 1.2.3.4 -If any common range coverage is found between the attempted binding specification -(in `srt_bind` call) and the found existing binding with the same port number, -then all of the following conditions must be satisfied between them: +Binding another socket to an endpoint that is already bound by another +socket is possible, and results in a shared binding, as long as the binding +address that is enclosed by this existing binding is exactly identical to +the specified one and all of the following conditions must be satisfied between +them: -1. The `SRTO_REUSEADDR` must be true (default) in both. +1. The `SRTO_REUSEADDR` must be true (default) in both the attempted and +existing bindings. -2. The IP address specification (in case of IPv6, also including the value of -`SRTO_IPV6ONLY` flag) must be exactly identical. +2. The IP address specification, also as a wildcard (in case of IPv6, also +including the value of `SRTO_IPV6ONLY` flag), must be exactly identical. -3. The UDP-specific settings must be identical. +3. The UDP-specific settings (SRT options that map to UDP options) must be identical. If any of these conditions isn't satisfied, the `srt_bind` function results in conflict and report this error. diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index c550845a7..094678acc 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -549,6 +549,11 @@ allowed must take this into consideration. It's up to the caller of this function to make this distinction and to take appropriate action depending on the type of entity returned. +Note: this flag should be altered **before** calling `srt_listen`. If you do +this after this call, you might have some pending group connections in the +meantime that will be rejected because group connections are not **yet** +allowed on this listener socket. + When this flag is set to 1 on an accepted socket that is passed to the listener callback handler, it means that this socket is created for a group connection and it will become a member of a group. Note that in this case @@ -972,20 +977,88 @@ The default value is 0x010000 (SRT v1.0.0). | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | -| `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 76.. | RW | GSD | - -Maximum Segment Size. Used for buffer allocation and rate calculation using -packet counter assuming fully filled packets. Each party can set its own MSS -value independently. During a handshake the parties exchange MSS values, and -the lowest is used. - -*Generally on the internet MSS is 1500 by default. This is the maximum -size of a UDP packet and can be only decreased, unless you have some unusual -dedicated network settings. MSS is not to be confused with the size of the UDP -payload or SRT payload - this size is the size of the IP packet, including the -UDP and SRT headers* - -THe value of `SRTO_MSS` must not exceed `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`. +| `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 116.. | RW | GSD | + +Maximum Segment Size. This value represents the maximum size of a UDP packet +sent by the system. Therefore the value of `SRTO_MSS` must not exceed the +values of `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`. It is used for buffer +allocation and rate calculation using a packet counter that assumes fully filled +packets. + +This value is a sum of: + +* IP header (20 bytes for IPv4, or 32 bytes for IPv6) +* UDP header (8 bytes) +* SRT header (16 bytes) +* remaining space (as the maximum payload size available for a packet) + +For the default 1500 the "remaining space" is effectively 1456 for IPv4 +and 1444 for IPv6, although it can be limited by nondefault values of some +other socket options. + +Note that the IP version used here is not the domain of the underlying UDP +socket, but the in-transmission IP version. This is effectively IPv4 in the +following cases: + +* when the current socket's binding address is of IPv4 domain +* when the peer's address is an IPv6-mapped-IPv4 address + +The IPv6 transmission case is assumed only if the peer's address is a true IPv6 +address (not IPv4 mapped). It is then not possible to determine the payload +size limit until the connection is established. SRT operations that must +allocate any resources according to this value prior to connecting will assume +IPv4 transmission because this way, in the worst case, they allocate more space +than needed. + +This value can be set on both connection parties independently, but after +connection `SRTO_MSS` gets a negotiated value, which is the lesser of the two. +If this effective value is too small for either of the connection peers, the +connection is rejected (or late-rejected on the caller side). + +This value then controls: + +* The maximum size of the payload in a single UDP packet ("remaining space"). + +* The size of the memory space allocated for a single packet in the sender +and receiver buffers. This value is equal to "SRT header" + "remaining space" +in the IPv4 layout case (1472 bytes per packet for MSS=1500). The reason for it +is that some buffer resources are allocated prior to the connection, so this +value must fit both IPv4 and IPv6 for buffer memory allocation. + +The default value of 1500 corresponds to the standard MTU size for network +devices. It is recommended that this value be set to the maximum MTU size of +the network device that you will use for the connection. + +The recommendations for the value of `SRTO_MSS` differ between file and live modes. + +In live mode a single call to the `srt_send\*` function may only send data that +fit in one packet. This size is defined by the `SRTO_PAYLOADSIZE` option +(defult: 1316), and it is also the size of the data in a single UDP packet. To +save memory space, you may want then to set `SRTO_MSS` in live mode to a value +for which the "remaining space" matches the `SRTO_PAYLOADSIZE` value (for the +default value of 1316 this will be 1360 for IPv4 and 1372 for IPv6). For +security reasons, this is not done by default: it may potentially lead to the +inability to read an incoming UDP packet if its size is for some reason bigger +than the negotiated MSS, which may in turn lead to unpredictable behaviour and +hard-to-detect errors. You should set such a value only if the peer is trusted +(that is, you can be certain that you will never receive an oversized UDP +packet over the link used for the connection). You should also consider the +limitations of `SRTO_PAYLOADSIZE`. + +In file mode `SRTO_PAYLOADSIZE` has a special value 0 that means no limit +for sending a single packet, and therefore bigger portions of data are +internally split into smaller portions, each one using the maximum available +"remaining space". The best value of `SRTO_MSS` for this case is then equal to +the current network device's MTU size. Setting a greater value is possible +(maximum for the system API is 65535), but it may lead to packet fragmentation +on the system level. This is highly unwanted in SRT because: + +* SRT also performs its own fragmentation, so it would be counter-productive +* It would use more system resources to no advantage +* SRT is unaware of it, so the resulting statistics would be slightly misleading + +System-level packet fragmentation cannot be reliably turned off, +so the safest approach is to avoid it by using appropriate parameters. [Return to list](#list-of-options) @@ -1082,7 +1155,7 @@ Cases when negotiation succeeds: | fec,cols:10 | fec,cols:10,rows:20 | fec,cols:10,rows:20,arq:onreq,layout:even | fec,layout:staircase | fec,cols:10 | fec,cols:10,rows:1,arq:onreq,layout:staircase -In these cases the configuration is rejected with SRT_REJ_FILTER code: +In these cases the configuration is rejected with `SRT_REJ_FILTER` code: | Peer A | Peer B | Error reason |-----------------------|---------------------|-------------------------- @@ -1137,14 +1210,60 @@ encrypted connection, they have to simply set the same passphrase. | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | | -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | -| `SRTO_PAYLOADSIZE` | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | W | GSD | +| `SRTO_PAYLOADSIZE` | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | RW | GSD | + +Sets the mode that determines the limitations on how data is sent, including the maximum +size of payload data sent within a single UDP packet. This option can be only set prior +to connecting, but it can be read also after the connection has been established. + +The default value is 1316 in live mode (which is default) and 0 in file mode (when file +mode is set through the `SRTO_TRANSTYPE` option). + +In file mode (`SRTO_PAYLOADSIZE` = 0) the call to `srt_send\*` is not limited +to the size of a single packet. If necessary, the supplied data will be split +into multiple pieces, each fitting into a single UDP packet. Every data payload +(except the last one in the stream or in the message) will use the maximum +space available in a UDP packet, as determined by `SRTO_MSS` and other settings +that may influence this size (such as [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER) +and [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE)). + +Also when this option is set to 0 prior to connecting, then reading this option +from a connected socket will return the maximum size of the payload that fits +in a single packet according to the current connection parameters. + +In live mode (`SRTO_PAYLOADSIZE` > 0) the value defines the maximum size of: + +* a single call to a sending function (`srt_send\*`) +* the payload supplied in each data packet + +as well as the minimum size of the buffer used for the `srt_recv\*` call. + +This value can be set to a greater value than the default 1316, but the maximum +possible value is limited by the following factors: + +* 1500 bytes is the default MSS (see [`SRTO_MSS`](#SRTO_MSS)), including headers, which occupy: + * 20 bytes for IPv4, or 32 bytes for IPv6 + * 8 bytes for UDP + * 16 bytes for SRT + +This alone gives a limit of 1456 for IPv4 and 1444 for IPv6. This limit may +be further decreased in the following cases: + +* 4 bytes reserved for FEC, if you use the built in FEC packet filter (see [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER)) +* 16 bytes reserved for the authentication tag, if you use AES GCM (see [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE)) + +**WARNING**: The party setting the options will reject a value that is too +large, but note that not every limitation can be checked prior to connection. +This includes: -Sets the maximum declared size of a single call to sending function in Live -mode. When set to 0, there's no limit for a single sending call. +* the MSS value defined by a peer, which may override the MSS set by an agent +* the in-transmission IP version (see [SRTO_MSS](#SRTO_MSS) for details) -For Live mode: Default value is 1316, but can be increased up to 1456. Note that -with the `SRTO_PACKETFILTER` option additional header space is usually required, -which decreases the maximum possible value for `SRTO_PAYLOADSIZE`. +These values also influence the "remaining space" in the packet to be used for +payload. If during the handshake it turns out that this "remaining space" is +less than the value set for `SRTO_PAYLOADSIZE` (including when it remains with +the default value), the connection will be rejected with the `SRT_REJ_SETTINGS` +code. For File mode: Default value is 0 and it's recommended not to be changed. diff --git a/docs/API/API.md b/docs/API/API.md index 3bd303830..24e721987 100644 --- a/docs/API/API.md +++ b/docs/API/API.md @@ -211,7 +211,7 @@ extra data for the operation. Functions with the `msg2` suffix use the `SRT_MSGCTRL` object, and have the following interpretation (except `flags` and `boundary` which are reserved for -future use and should be 0): +future use and should not be set other value than the initial one): - `srt_sendmsg2`: - `msgttl`: [IN] maximum time (in ms) to wait for successful delivery (-1: indefinitely) @@ -227,6 +227,20 @@ future use and should be 0): - `pktseq`: [OUT] packet sequence number (first packet from the message, if it spans multiple UDP packets) - `msgno`: [OUT] message number assigned to the currently received message +- both above [IN] + - grpdata_size: size of the passed grpdata array + +- both above [OUT] + - grpdata: pointer to the array of group data to be filled by the call + - grpdata_size: actual size of the filled array + +**IMPORTANT**: no matter that fields are particularly marked as `[OUT]` (or +unused), values specified there could be used for input under certain +circumstances. Especially in `srt_sendmsg2` you should not reuse existing +objects of `SRT_MSGCTRL` type, but create always new ones and initialize them +with default `srt_msgctl_default` (or overwrite them first with it and set the +desired values anew). + Please note that the `msgttl` and `inorder` arguments and fields in `SRT_MSGCTRL` are meaningful only when you use the message API in file mode (this will be explained later). In live mode, which is the SRT default, packets are always delivered when diff --git a/docs/dev/developers-guide.md b/docs/dev/developers-guide.md index 3b0e1c7b6..2802ab975 100644 --- a/docs/dev/developers-guide.md +++ b/docs/dev/developers-guide.md @@ -276,6 +276,32 @@ does its best to make sure that no option is missing. Note that some options might be provided by an external dependent script (like `build-gmock`) and therefore mistakenly added to the list. +### Release and configuration management + +The release management follows the rules of the "Git Flow" tool. + +The `master` branch should contain the version on the latest stable release. + +The ongoing development should be merged to `dev` branch. + +New feature branches should be drawn off `dev` branch. They should use +`feature/` prefix. + +Finished feature branches should be merged back to `dev`. + +Once a release is defined, a new release branch is drawn off `dev` +and it has a name with `release/` prefix. All bugfix branches for this +release are merged to this branch, and then `dev` branch updated +with this fix. + +Once the release reaches stability, it is merged to `master`. Any +hotfixes are merged to the release branch, and then `master` is updated, +then so is the `dev` branch. + +The `git-flow` tool can be used to support this workflow, but it can be +also implemented manually. + + [git-setup]: https://help.github.com/articles/set-up-git [github-issues]: https://github.com/Haivision/srt/issues [github-guide-prs]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork diff --git a/docs/features/bonding-intro.md b/docs/features/bonding-intro.md index d455afb57..dc4fac570 100644 --- a/docs/features/bonding-intro.md +++ b/docs/features/bonding-intro.md @@ -21,6 +21,14 @@ used. How the links are utilized within a group depends on the group type. The simplest type, broadcast, utilizes all links at once to send the same data. +There are two main features for which the groups exist: + +1. Redundancy: keep the signal constantly delivered even if one of the +links gets unexpectedly broken. + +2. Balancing: send a stream with a bitrate that would be too high for +a single link, but enough if every link gets only a share of load. + To learn more about socket groups and their abilities, please read the [SRT Socket Groups](socket-groups.md) document. diff --git a/docs/features/socket-groups.md b/docs/features/socket-groups.md index cef0ee98e..2afa594be 100644 --- a/docs/features/socket-groups.md +++ b/docs/features/socket-groups.md @@ -30,11 +30,48 @@ The groups types generally split into two categories: This category contains currently only one Multicast type (**CONCEPT! NOT IMPLEMENTED!**). Multicast group has a behavior dependent on the connection side and it is - predicted to be only used in case when the listener side is a stream sender + intended to be used only in the case when the listener side is a stream sender with possibly multiple callers being stream receivers. It utilizes the UDP multicast feature in order to send payloads, while the control communication is still sent over the unicast link. +From the application point of view it is important to remember several rules +concerning groups: + +1. On the caller side the group has to be created, like a socket, and then it's + ready to connect. In distinction to socket, you can connect the group multiple + times. The group is considered connected, if at least one connection has + been successfully established, then other connections can be added at any time. + +2. On the listener side you create the listener socket, and you call + the `srt_accept` function, from which you get the group ID, if that listener + socket has received a group connection request. Once accepted, you get the + connected group this way and every next connection is handled in the background. + +3. Disconnected links are removed from the group and are not reconnected. The + application simply has to connect that link again, if it chooses to do so. + +4. You can remove a single link from the group by simply closing the member + socket. This socket is provided in the group member status table together + with other data that allow to identify particular link. + +In other words, links in socket groups are never "defined" - they can only be +"established". When they get broken, they are simply removed from the group. +It's up to the application to re-establish them. + +The group members can be also in appropriate states. The freshly created member +that is in the process of connecting is in "pending" state. When the connection +succeeds, it's in "idle" state. Then, when it's used for transmission, it's in +"active" state. If an operation on the link fails at any stage, it is removed +from the group. + +In Broadcast and Balancing group types, the "idle" links are activated once +they are found ready for sending as well as they report readiness for reading - +"idle" is only a temporary state between being freshly connected and being used +for transmission. In case of Main/Backup groups, the "idle" state is usually +more permanent and is only turned to "active" when necessary, while it can be +as well put back to "idle". + ## Details for the Group Types ### 1. Broadcast @@ -55,28 +92,46 @@ Every next link in this group gives then another 100% overhead. ### 2. Main/Backup -This solution is more complicated and more challenging for the settings, -and in contradiction to Broadcast group, it costs some penalties. - -In this group, only one link out of member links is used for transmission -in a normal situation. Other links may start being used when there's happening -an event of "disturbance" on a link, which makes it considered "unstable". This -term is introduced beside "broken" because SRT normally uses 5 seconds to be -sure that the link is broken, and this is way too much to be used as a latency -penalty, if you still want to have a relatively low latency. - -Because of that there's a configurable timeout (with `SRTO_GROUPSTABTIMEO` -option), which is the maximum time distance between two consecutive responses -sent from the receiver back to the sender. If this time was exceeded, the link -is considered unstable. This can mean either some short-living minor -disturbance, as well as that the link is broken, just SRT hasn't a proof of -that yet. - -At the moment when one link becomes unstable, another link is immediately -activated, and all packets that have been kept in the sender buffer since -the last ACK are first sent. Since this moment there are two links active -until the moment when the matter finally resolves - either the unstable -link will become stable again, or it will be broken. +The configuration of this type of groups is somewhat more complicated than with +the other group types. In particular, it may be challenging to arrive at the +optimal settings for a given set of network conditions and desired latency. +Unlike Broadcast group type, there are some penalties, but there are also +advantages. Whereas the overhead for redundancy in the case of Broadcast groups +is 100% per every next redundant link, this is usually kept at a negligible +minimum for Main/Backup groups. + +The idea of the Main/Backup group is to use only one link for transmission +of the data, but be ready to quickly activate the other links, if it turns +out that the currently used link is "likely broken" (by not having received any +packet from the peer for a given timeout). The unstable state is stricter than +broken connection: while broken connection is recognized by response time +exceeding the "peer idle" timeout (`SRTO_PEERIDLETIMEO`, default: 5s), the +unstable state is recognized by exceeding the "group stability" timeout, +which is 10ms of the ACK period with addition of some jitter tolerance (this +value is dependent on the current latency and average-tolerated RTT and the +minimum can be controlled by `SRTO_GROUPMINSTABLETIMEO`). As this still doesn't +mean broken, the transmission continues over multiple links since that time. +Activation of a link means that all packets since the last ACK sequence +are first sent over this link, then it continues with ongoing packets, so that, +if everything goes well (the new link is successfully keeping up with the pace +and any packet loss caused by the initial burst is recovered), the application +should see completely no disturbance due to this new link activation. + +Note that there doesn't happen anything like "switching" of the link. You should +rather think of it as turning into a "temporary broadcast" mode, where multiple +links are used for transmission (still, only for those links that were activated). +This situation can then get resolved into one of the following: + +* The link turns back to stable, so there are multiple stable links +* The link gets really broken, so only the newly activated link transmits +* The link is unstable for too long, so it is forcefully closed + +In the first case we have then continued the "temporary broadcast" mode. In +this situation, after a short cooldown time, out of all currently active links +there is selected one that is considered the "best" (where priority matters, +but also the response jitter is taken into account) and this one continues +with the transmission, while all others are "silenced", that is, transmission +over these links is stopped. The state maintenance always keep up to the following rules: @@ -87,8 +142,8 @@ and remains ready to take over if there is a necessity. b) Unstable links continue to be used no matter that it may mean parallel sending for a short time. This state should last at most as long as it takes -for SRT to determie the link broken - either by getting the link broken by -itself, or by closing the link when it's remaining unstable too long time. +for SRT to determine the link broken - either by breaking the link by +itself, or by closing the link when it has been unstable for too long. This mode allows also to set link priorities - the greater, the more preferred. This priority decides mainly, which link is "best" and which is selected to @@ -144,45 +199,53 @@ any quite probable packet loss that may occur during this process. The idea of balancing means that there are multiple network links used for carrying out the same transmission, however a single input signal should distribute the incoming packets between the links so that one link can -leverage the bandwidth burden of the other. Note that this group is not -directly used as protection - it is normally intended to work with a +leverage the bandwidth burden of the other. Note that this group only +partially can provide the redundancy - it is normally intended to work with a condition that a single link out of all links in the group would not be -able to withstand the bitrate of the signal. In order to utilize a -protection, the mechanism should quickly detect a link as broken so -that packets lost on the broken link can be resent over the others, -but no such mechanism has been provided for balancing group. - -As there could be various ways as to how to implement balancing -algorithm, there's a framework provided to implement various methods, -and two algorithms are currently provided: - -1. `plain` (default). This is a simple round-robin - next link selected -to send the next packet is the oldest used so far. - -2. `window`. This algorithm is performing cyclic measurement of the +able to withstand the bitrate of the signal. So, to stay safe, you need +to make sure that you always have one link that provides the excessive +capacity so that breaking one link doesn't lower the overall capacity +below the requirement for the signal's bitrate. + +Please also keep in mind that the group is considered connected when +it contains at least one connected member link. This means also that the +group becomes ready for transmission after connecting the first link, +as well as it remains ready even if some member links get broken. So, +if the application wants to make sure that a transmission is balanced between +links (where only together can they maintain the bandwidth capacity required +for a signal), it must make sure that all "required" links are established by +monitoring the group data. For example, if you need a minimum of 3 links to +balance the load, you should delay starting the transmission until all 3 links +are established (that is, all of them report "idle" state), and also stop it +(or quickly reconfigure the stream to a lower bandwidth) in case when a broken +link caused that the others do not cover the required capacity. + +As there could be more than one way to implement a balancing algorithm, there +is a framework for implementing various methods, so that new algorithms are +easier to provide in future. Currently there are two algorithms provided: + +1. `fixed`. This is based on the simple round-robin method, but the usage +of particular link grows invertedly towards the share value, which is +controlled by the `weight` parameter (that is, a link with more weight can +be proportionally more burdened). You can easily think of the weight values as +a percentage of load burden for particular link - however in reality the share +of the load is calculated as a percentage that particular link's weight +comprises among the sum of all weight values. Additionally, a value of 0 is +special and it is translated into the arithmetic average of all non-zero +weighted links, and if all links have weight 0, all links have equal share. Be +careful here though with the non-established and broken links. For example, if +you have 3 links with weight 10, 20 and 30, it results in a load balance of +16.6%, 33.3% and 50% respectively. However if the second link gets broken, +there are then 2 links with 10 and 30, which results in load balance of 25% and +75% respectively. + +2. `window` (default). This algorithm performs cyclic measurement of the minimum flight window and this way determines the "cost of sending" -of a packet over particular link. The link is then "paid" for sending -a packet appropriate "price", which is collected in the link's "pocket". -To send the next packet the link with lowest state of the "pocket" is -selected. The "cost of sending" measurement is being repeated once per -a time with a distance of 16 packets on each link. - -There are possible also other methods and algorithms, like: - -a) Explicit share definition. You declare, how much bandwidth you expect -the links to withstand as a percentage of the signal's bitrate. This -shall not exceed 100%. This is merely like the above Window algorithm, -but the "cost of sending" is defined by this percentage. - -b) Bandwidth measurement. This relies on the fact that the current -sending on particular link should use only some percentage of its -overall possible bandwidth. This requires a reliable way of measuring -the bandwidth, which is currently not good enough yet. This needs to -use a similar method as in "window" algorithm, that is, start with -equal round-robin and then perform actively a measurement and update -the cost of sending by assigning so much of a share of the signal -bitrte as it is represented by the share of the link in the sum of -all maximum bandwidth values from every link. +of a packet over a particular link (the bigger the flight span of the link, +the higher the sending cost). This evaluated cost is then added to the current +burden state of the link, and then the link with lowest burden is selected to +send the next packet. The "cost of sending" measurement is being repeated once +per a time at an interval of 16 packets on each link. ### 4. Multicast (**CONCEPT! NOT IMPLEMENTED!**) @@ -192,7 +255,7 @@ receiving a data stream sent from a stream server by multiple receivers. Multicast sending is using the feature of UDP multicast, however the connection concept is still in force. The concept of multicast groups -is predicted to facilitate the multicast abilities provided by the router +is intended to facilitate the multicast abilities provided by the router in the LAN, while still maintain the advantages of SRT. When you look at the difference that UDP multicast provides you towards diff --git a/examples/sendmsg.cpp b/examples/sendmsg.cpp index af551849b..ce80564d9 100644 --- a/examples/sendmsg.cpp +++ b/examples/sendmsg.cpp @@ -100,7 +100,7 @@ int main(int argc, char* argv[]) // 2. Otherwise the first number is the ID, followed by a space, to be filled in first 4 bytes. // 3. Rest of the characters, up to the end of line, should be put into a solid block and sent at once. - int status = 0; + int status = SRT_STATUS_OK; int ordinal = 1; int lpos = 0; diff --git a/scripts/abi-check.tcl b/scripts/abi-check.tcl new file mode 100755 index 000000000..7f9c70e79 --- /dev/null +++ b/scripts/abi-check.tcl @@ -0,0 +1,76 @@ +#!/usr/bin/tclsh + +set here [file dirname $argv0] ;# points to [git top]/scripts +set top [file normalize $here/..] + +set abichecker [file normalize [file join $top submodules abi-compliance-checker abi-compliance-checker.pl]] + +if { ![file exists $abichecker] } { + puts stderr "Please update submodules first (compliance checker not found in the current view)" + exit 1 +} + +# Check if abi-dumper is installed + +set abidumper [auto_execok abi-dumper] +if {$abidumper == ""} { + set installer "" + foreach ii {zypper dnf apt} { + if {[auto_execok $ii] != ""} { + set installer $ii + break + } + } + if {$installer != ""} { + puts stderr "ABI dumper not installed. Use such commands to install\n" + puts stderr " $installer install abi-dumper" + } else { + puts stderr "ABI dumper not installed. Find out how to install abi-dumper in your system." + } + exit 1 +} + +# Arguments: +# [directory-with-base] [directory-with-pr] + +# NOTE: ABI dump will be done in every build directory as specified. + +proc generate-abi-dump {directory lver} { + set wd [pwd] + cd $directory + global top + + # You should have libsrt.so in this directory. + # Use [glob] to use exception if no file exists + glob libsrt.so + + exec >@stdout 2>@stderr abi-dumper libsrt.so -o libsrt-abi.dump -public-headers $top/srtcore -lver $lver + cd $wd +} + +set olddir [lindex $argv 0] +set newdir [lindex $argv 1] + +if {![file isdirectory $olddir] || ![file isdirectory $newdir]} { + puts stderr "Wrong arguments. Required build directory" + exit 1 +} + +set wd [pwd] +cd $olddir +set base_ver [exec $top/scripts/get-build-version.tcl] +cd $wd +cd $newdir +set new_ver [exec $top/scripts/get-build-version.tcl] +cd $wd + +generate-abi-dump $olddir $base_ver +generate-abi-dump $newdir $new_ver + +set res [catch {exec >@stdout 2>@stderr $abichecker -l libsrt -old $olddir/libsrt-abi.dump -new $newdir/libsrt-abi.dump} out] + +if {$res} { + puts stderr "ABI compat problems found!!!\nSee the HTML report" +} + + diff --git a/scripts/get-build-version.tcl b/scripts/get-build-version.tcl new file mode 100755 index 000000000..fafda061a --- /dev/null +++ b/scripts/get-build-version.tcl @@ -0,0 +1,47 @@ +#!/usr/bin/tclsh + +if {![file exists version.h]} { + puts stderr "No version.h file found - run this in the build directory!" + exit 1 +} + +set fd [open version.h r] + +set version "" +set line "" +while {[gets $fd line] != -1} { + # Use regexp because this is safer to avoid + # unexpected list interpretation mistakes + if {[regexp {#define SRT_VERSION_STRING} $line]} { + # Once matched this way, we can safely use tcl-list-like access + set version [lindex $line 2 0] + break + } +} +close $fd + +if {$version == ""} { + puts stderr "No version extracted (no SRT_VERSION_STRING found)" + exit 1 +} + +lassign $argv model part + +if {$model == ""} { + set model current +} + +lassign [split $version .] major minor patch + +if {$part == "minor"} { + set prefix "$minor" +} else { + set prefix "$major.$minor" +} + +if {$model == "base"} { + puts "$prefix.0" +} else { + puts "$prefix.$patch" +} + diff --git a/scripts/googletest-download.cmake b/scripts/googletest-download.cmake index 7469f4f7b..be0adb7d6 100644 --- a/scripts/googletest-download.cmake +++ b/scripts/googletest-download.cmake @@ -11,7 +11,7 @@ ExternalProject_Add( BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build" GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG release-1.10.0 + GIT_TAG release-1.12.0 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/sonar-project.properties b/sonar-project.properties index 4bdaccdb2..afe611c78 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -28,8 +28,9 @@ sonar.links.issue=https://github.com/Haivision/srt/issues # ===================================================== # SQ standard properties -sonar.sources=srtcore/,apps/,common/,examples/,haicrypt/,scripts/,testing/ -sonar.tests=test/ +sonar.sources=srtcore/,common/,haicrypt/,scripts/ +sonar.tests=test/,testing/,apps/,examples/ +sonar.verbose=true # Properties specific to the C/C++ analyzer: #sonar.cfamily.build-wrapper-output=_build/sonar-output diff --git a/srtcore/access_control.h b/srtcore/access_control.h index 611e1dad8..18357aeee 100644 --- a/srtcore/access_control.h +++ b/srtcore/access_control.h @@ -66,7 +66,7 @@ written by #define SRT_REJX_GW 1502 // The server acts as a gateway and the target endpoint rejected the connection. #define SRT_REJX_DOWN 1503 // The service has been temporarily taken over by a stub reporting this error. The real service can be down for maintenance or crashed. // CODE NOT IN USE 504: unused: timeout not supported -#define SRT_REJX_VERSION 1505 // SRT version not supported. This might be either unsupported backward compatibility, or an upper value of a version. +#define SRT_REJX_VERSION 1505 // Version not supported - either SRT or the application itself. This might be either unsupported backward compatibility, or an upper value of a version. // CODE NOT IN USE 506: unused: negotiation and references not supported #define SRT_REJX_NOROOM 1507 // The data stream cannot be archived due to lacking storage space. This is in case when the request type was to send a file or the live stream to be archived. // CODE NOT IN USE 508: unused: no redirection supported diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 1108c0a1d..54d37ffbd 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -117,14 +117,14 @@ SRT_SOCKSTATUS srt::CUDTSocket::getStatus() } // [[using locked(m_GlobControlLock)]] -void srt::CUDTSocket::breakSocket_LOCKED() +void srt::CUDTSocket::breakSocket_LOCKED(int reason) { // This function is intended to be called from GC, // under a lock of m_GlobControlLock. m_UDT.m_bBroken = true; m_UDT.m_iBrokenCounter = 0; HLOGC(smlog.Debug, log << "@" << m_SocketID << " CLOSING AS SOCKET"); - m_UDT.closeInternal(); + m_UDT.closeInternal(reason); setClosed(); } @@ -180,7 +180,6 @@ srt::CUDTUnited::CUDTUnited() , m_InitLock() , m_iInstanceCount(0) , m_bGCStatus(false) - , m_ClosedSockets() { // Socket ID MUST start from a random value m_SocketIDGenerator = genRandomInt(1, MAX_SOCKET_VAL); @@ -239,11 +238,11 @@ srt::CUDTUnited::~CUDTUnited() string srt::CUDTUnited::CONID(SRTSOCKET sock) { - if (sock == 0) + if (int32_t(sock) <= 0) // embraces SRT_INVALID_SOCK, SRT_SOCKID_CONNREQ and illegal negative domain return ""; std::ostringstream os; - os << "@" << sock << ":"; + os << "@" << int(sock) << ":"; return os.str(); } @@ -283,10 +282,14 @@ void srt::CUDTUnited::closeAllSockets() { ScopedLock glock(m_GlobControlLock); + // Do not do generative expiry removal - there's no chance + // anyone can extract the close reason information since this point on. + m_ClosedDatabase.clear(); + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { CUDTSocket* s = i->second; - s->breakSocket_LOCKED(); + s->breakSocket_LOCKED(SRT_CLS_CLEANUP); #if ENABLE_BONDING if (s->m_GroupOf) @@ -297,20 +300,26 @@ void srt::CUDTUnited::closeAllSockets() s->removeFromGroup(false); } #endif - m_ClosedSockets[i->first] = s; + // NOTE: not removing the socket from m_Sockets. + // This is a loop over m_Sockets and after this loop ends, + // this whole container will be cleared. + swipeSocket_LOCKED(i->first, s, SWIPE_LATER); - // remove from listener's queue - sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); - if (ls == m_Sockets.end()) + if (s->m_ListenSocket != SRT_SOCKID_CONNREQ) { - ls = m_ClosedSockets.find(s->m_ListenSocket); - if (ls == m_ClosedSockets.end()) - continue; - } + // remove from listener's queue + sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); + if (ls == m_Sockets.end()) + { + ls = m_ClosedSockets.find(s->m_ListenSocket); + if (ls == m_ClosedSockets.end()) + continue; + } - enterCS(ls->second->m_AcceptLock); - ls->second->m_QueuedSockets.erase(s->m_SocketID); - leaveCS(ls->second->m_AcceptLock); + enterCS(ls->second->m_AcceptLock); + ls->second->m_QueuedSockets.erase(s->m_SocketID); + leaveCS(ls->second->m_AcceptLock); + } } m_Sockets.clear(); @@ -340,17 +349,17 @@ void srt::CUDTUnited::closeAllSockets() } -int srt::CUDTUnited::startup() +SRTRUNSTATUS srt::CUDTUnited::startup() { ScopedLock gcinit(m_InitLock); m_iInstanceCount++; if (m_bGCStatus) - return (m_iInstanceCount == 1) ? 1 : 0; + return (m_iInstanceCount == 1) ? SRT_RUN_ALREADY : SRT_RUN_OK; else - return startGarbageCollector() ? 0 : -1; + return startGarbageCollector() ? SRT_RUN_OK : SRT_RUN_ERROR; } -int srt::CUDTUnited::cleanup() +SRTSTATUS srt::CUDTUnited::cleanup() { // IMPORTANT!!! // In this function there must be NO LOGGING AT ALL. This function may @@ -363,11 +372,11 @@ int srt::CUDTUnited::cleanup() ScopedLock gcinit(m_InitLock); if (--m_iInstanceCount > 0) - return 0; + return SRT_STATUS_OK; stopGarbageCollector(); closeAllSockets(); - return 0; + return SRT_STATUS_OK; } SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) @@ -418,10 +427,10 @@ SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) const bool exists = #if ENABLE_BONDING for_group - ? m_Groups.count(sockval | SRTGROUP_MASK) + ? m_Groups.count(SRTSOCKET(sockval | SRTGROUP_MASK)) : #endif - m_Sockets.count(sockval); + m_Sockets.count(SRTSOCKET(sockval)); leaveCS(m_GlobControlLock); if (exists) @@ -470,7 +479,7 @@ SRTSOCKET srt::CUDTUnited::generateSocketID(bool for_group) LOGC(smlog.Debug, log << "generateSocketID: " << (for_group ? "(group)" : "") << ": @" << sockval); - return sockval; + return SRTSOCKET(sockval); } SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) @@ -500,7 +509,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) throw; } ns->m_Status = SRTS_INIT; - ns->m_ListenSocket = 0; + ns->m_ListenSocket = SRT_SOCKID_CONNREQ; // A value used for socket if it wasn't listener-spawned ns->core().m_SocketID = ns->m_SocketID; ns->core().m_pCache = m_pCache; @@ -527,7 +536,17 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) return ns->m_SocketID; } -int srt::CUDTUnited::newConnection(const SRTSOCKET listen, +// [[using locked(m_GlobControlLock)]] +void srt::CUDTUnited::swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, CUDTUnited::SwipeSocketTerm lateremove) +{ + m_ClosedSockets[id] = s; + if (!lateremove) + { + m_Sockets.erase(id); + } +} + +int srt::CUDTUnited::newConnection(const SRTSOCKET listener, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs, @@ -541,15 +560,15 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // Can't manage this error through an exception because this is // running in the listener loop. - CUDTSocket* ls = locateSocket(listen); + CUDTSocket* ls = locateSocket(listener); if (!ls) { - LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listen << " which DOES NOT EXIST."); + LOGC(cnlog.Error, log << "IPE: newConnection by listener socket id=" << listener << " which DOES NOT EXIST."); return -1; } HLOGC(cnlog.Debug, - log << "newConnection: creating new socket after listener @" << listen + log << "newConnection: creating new socket after listener @" << listener << " contacted with backlog=" << ls->m_uiBackLog); // if this connection has already been processed @@ -631,13 +650,13 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, return -1; } - ns->m_ListenSocket = listen; + ns->m_ListenSocket = listener; ns->core().m_SocketID = ns->m_SocketID; ns->m_PeerID = w_hs.m_iID; ns->m_iISN = w_hs.m_iISN; HLOGC(cnlog.Debug, - log << "newConnection: DATA: lsnid=" << listen << " id=" << ns->core().m_SocketID + log << "newConnection: DATA: lsnid=" << listener << " id=" << ns->core().m_SocketID << " peerid=" << ns->core().m_PeerID << " ISN=" << ns->m_iISN); int error = 0; @@ -718,7 +737,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, << " 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 + LOGC(cnlog.Note, log << "@" << ns->m_SocketID << " connection on listener @" << listener << " (" << ns->m_SelfAddr.str() << ") from peer @" << ns->m_PeerID << " (" << peer.str() << ")"); } catch (...) @@ -744,21 +763,14 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, goto ERR_ROLLBACK; } - // Check if this is the first socket in the group. - // If so, give it up to accept, otherwise just do nothing - // The client will be informed about the newly added connection at the - // first moment when attempting to get the group status. - for (CUDTGroup::gli_t gi = g->m_Group.begin(); gi != g->m_Group.end(); ++gi) - { - if (gi->laststatus == SRTS_CONNECTED) - { - HLOGC(cnlog.Debug, - log << "Found another connected socket in the group: $" << gi->id - << " - socket will be NOT given up for accepting"); - should_submit_to_accept = false; - break; - } - } + // Acceptance of the group will have to be done through accepting + // of one of the pending sockets. There can be, however, multiple + // such sockets at a time, some of them might get broken before + // being accepted, and therefore we need to make all sockets ready. + // But then, acceptance of a group may happen only once, so if any + // sockets of the same group were submitted to accept, they must + // be removed from the accept queue at this time. + should_submit_to_accept = g->groupPending(); // Update the status in the group so that the next // operation can include the socket in the group operation. @@ -771,34 +783,8 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, gm->rcvstate = SRT_GST_IDLE; gm->laststatus = SRTS_CONNECTED; - if (!g->m_bConnected) - { - HLOGC(cnlog.Debug, log << "newConnection(GROUP): First socket connected, SETTING GROUP CONNECTED"); - g->m_bConnected = true; - } + g->setGroupConnected(); - // XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily, - // but groupwise connections could be accepted from multiple listeners for the same group! - // m_listener MUST BE A CONTAINER, NOT POINTER!!! - // ALSO: Maybe checking "the same listener" is not necessary as subscruption may be done - // multiple times anyway? - if (!g->m_listener) - { - // Newly created group from the listener, which hasn't yet - // the listener set. - g->m_listener = ls; - - // Listen on both first connected socket and continued sockets. - // This might help with jump-over situations, and in regular continued - // sockets the IN event won't be reported anyway. - int listener_modes = SRT_EPOLL_ACCEPT | SRT_EPOLL_UPDATE; - epoll_add_usock_INTERNAL(g->m_RcvEID, ls, &listener_modes); - - // This listening should be done always when a first connected socket - // appears as accepted off the listener. This is for the sake of swait() calls - // inside the group receiving and sending functions so that they get - // interrupted when a new socket is connected. - } // Add also per-direction subscription for the about-to-be-accepted socket. // Both first accepted socket that makes the group-accept and every next @@ -838,7 +824,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, HLOGC(cnlog.Debug, log << "ACCEPT: new socket @" << ns->m_SocketID << " submitted for acceptance"); // acknowledge users waiting for new connections on the listening socket - m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, true); + m_EPoll.update_events(listener, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, true); CGlobEvent::triggerEvent(); @@ -859,7 +845,16 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // acknowledge INTERNAL users waiting for new connections on the listening socket // that are reported when a new socket is connected within an already connected group. - m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_UPDATE, true); + m_EPoll.update_events(listener, ls->core().m_sPollID, SRT_EPOLL_UPDATE, true); +#if ENABLE_BONDING + // Note that the code in this current IF branch can only be executed in case + // of group members. Otherwise should_submit_to_accept will be always true. + if (ns->m_GroupOf) + { + HLOGC(gmlog.Debug, log << "GROUP UPDATE $" << ns->m_GroupOf->id() << " per connected socket @" << ns->m_SocketID); + m_EPoll.update_events(ns->m_GroupOf->id(), ns->m_GroupOf->m_sPollID, SRT_EPOLL_UPDATE, true); + } +#endif CGlobEvent::triggerEvent(); } @@ -876,7 +871,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, #endif SRTSOCKET id = ns->m_SocketID; - ns->core().closeInternal(); + ns->core().closeInternal(SRT_CLS_LATE); ns->setClosed(); // The mapped socket should be now unmapped to preserve the situation that @@ -896,8 +891,10 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, ns->removeFromGroup(true); } #endif - m_Sockets.erase(id); - m_ClosedSockets[id] = ns; + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(id, ns->core().m_sPollID); + + swipeSocket_LOCKED(id, ns, SWIPE_NOW); } return -1; @@ -906,13 +903,75 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, return 1; } +SRT_EPOLL_T srt::CUDTSocket::getListenerEvents() +{ + // You need to check EVERY socket that has been queued + // and verify its internals. With independent socket the + // matter is simple - if it's present, you light up the + // SRT_EPOLL_ACCEPT flag. + +#if !ENABLE_BONDING + ScopedLock accept_lock (m_AcceptLock); + + // Make it simplified here - nonempty container = have acceptable sockets. + // Might make sometimes spurious acceptance, but this can also happen when + // the incoming accepted socket was suddenly broken. + return m_QueuedSockets.empty() ? 0 : int(SRT_EPOLL_ACCEPT); + +#else // Could do #endif here, but the compiler would complain about unreachable code. + + map sockets_copy; + { + ScopedLock accept_lock (m_AcceptLock); + sockets_copy = m_QueuedSockets; + } + return CUDT::uglobal().checkQueuedSocketsEvents(sockets_copy); + +#endif +} + +#if ENABLE_BONDING +int srt::CUDTUnited::checkQueuedSocketsEvents(const map& sockets) +{ + SRT_EPOLL_T flags = 0; + + // But with the member sockets an appropriate check must be + // done first: if this socket belongs to a group that is + // already in the connected state, you should light up the + // SRT_EPOLL_UPDATE flag instead. This flag is only for + // internal informing the waiters on the listening sockets + // that they should re-read the group list and re-check readiness. + + // Now we can do lock once and for all + for (map::const_iterator i = sockets.begin(); i != sockets.end(); ++i) + { + CUDTSocket* s = locateSocket_LOCKED(i->first); + if (!s) + continue; // wiped in the meantime - ignore + + // If this pending socket is a group member, but the group + // to which it belongs is NOT waiting to be accepted, then + // light up the UPDATE event only. Light up ACCEPT only if + // this is a single socket, or this single socket has turned + // the mirror group to be first time available for accept(), + // and this accept() hasn't been done yet. + if (s->m_GroupOf && !s->m_GroupOf->groupPending()) + flags |= SRT_EPOLL_UPDATE; + else + flags |= SRT_EPOLL_ACCEPT; + } + + return flags; +} +#endif + // static forwarder -int srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS srt::CUDT::installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { return uglobal().installAcceptHook(lsn, hook, opaq); } -int srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { try { @@ -925,24 +984,24 @@ int srt::CUDTUnited::installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_ return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } -int srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS srt::CUDT::installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { return uglobal().installConnectHook(lsn, hook, opaq); } -int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_fn* hook, void* opaq) { try { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); k.group->installConnectHook(hook, opaq); - return 0; + return SRT_STATUS_OK; } #endif CUDTSocket* s = locateSocket(u, ERH_THROW); @@ -954,7 +1013,7 @@ int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_ return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) @@ -974,7 +1033,38 @@ SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) return i->second->getStatus(); } -int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) +SRTSTATUS srt::CUDTUnited::getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& info) +{ + // protects the m_Sockets structure + ScopedLock cg(m_GlobControlLock); + + // We need to search for the socket in: + // m_Sockets, if it is somehow still alive, + // m_ClosedSockets, if it's when it should be, + // m_ClosedDatabase, if it has been already garbage-collected and deleted. + + sockets_t::const_iterator i = m_Sockets.find(u); + if (i != m_Sockets.end()) + { + i->second->core().copyCloseInfo((info)); + return SRT_STATUS_OK; + } + + i = m_ClosedSockets.find(u); + if (i != m_ClosedSockets.end()) + { + i->second->core().copyCloseInfo((info)); + } + + map::iterator c = m_ClosedDatabase.find(u); + if (c == m_ClosedDatabase.end()) + return SRT_ERROR; + + info = c->second.info; + return SRT_STATUS_OK; +} + +SRTSTATUS srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) { ScopedLock cg(s->m_ControlLock); @@ -998,10 +1088,10 @@ int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) // copy address information of local node s->core().m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr)); - return 0; + return SRT_STATUS_OK; } -int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) +SRTSTATUS srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) { ScopedLock cg(s->m_ControlLock); @@ -1027,10 +1117,10 @@ int srt::CUDTUnited::bind(CUDTSocket* s, UDPSOCKET udpsock) // copy address information of local node s->core().m_pSndQueue->m_pChannel->getSockAddr(s->m_SelfAddr); - return 0; + return SRT_STATUS_OK; } -int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) +SRTSTATUS srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) { if (backlog <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -1053,7 +1143,7 @@ int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) // do nothing if the socket is already listening if (s->m_Status == SRTS_LISTENING) - return 0; + return SRT_STATUS_OK; // a socket can listen only if is in OPENED status if (s->m_Status != SRTS_OPENED) @@ -1073,7 +1163,7 @@ int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) // if thrown, remains in OPENED state if so. s->m_Status = SRTS_LISTENING; - return 0; + return SRT_STATUS_OK; } SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut) @@ -1115,7 +1205,7 @@ SRTSOCKET srt::CUDTUnited::accept_bond(const SRTSOCKET listeners[], int lsize, i // Theoretically we can have a situation that more than one // listener is ready for accept. In this case simply get // only the first found. - int lsn = st.begin()->first; + SRTSOCKET lsn = st.begin()->first; sockaddr_storage dummy; int outlen = sizeof dummy; return accept(lsn, ((sockaddr*)&dummy), (&outlen)); @@ -1155,7 +1245,7 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); } - SRTSOCKET u = CUDT::INVALID_SOCK; + SRTSOCKET u = SRT_INVALID_SOCK; bool accepted = false; // !!only one connection can be set up each time!! @@ -1202,7 +1292,7 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int m_EPoll.update_events(listen, ls->core().m_sPollID, SRT_EPOLL_ACCEPT, false); } - if (u == CUDT::INVALID_SOCK) + if (u == SRT_INVALID_SOCK) { // non-blocking receiving, no connection available if (!ls->core().m_config.bSynRecving) @@ -1239,12 +1329,16 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int // it's a theoretically possible scenario if (s->m_GroupOf) { - u = s->m_GroupOf->m_GroupID; - s->core().m_config.iGroupConnect = 1; // should be derived from ls, but make sure - + CUDTGroup* g = s->m_GroupOf; // Mark the beginning of the connection at the moment // when the group ID is returned to the app caller - s->m_GroupOf->m_stats.tsLastSampleTime = steady_clock::now(); + g->m_stats.tsLastSampleTime = steady_clock::now(); + + HLOGC(cnlog.Debug, log << "accept: reporting group $" << g->m_GroupID << " instead of member socket @" << u); + u = g->m_GroupID; + s->core().m_config.iGroupConnect = 1; // should be derived from ls, but make sure + g->m_bPending = false; + CUDT::uglobal().removePendingForGroup(g); } else { @@ -1264,7 +1358,80 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int return u; } -int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) +#if ENABLE_BONDING + +// [[using locked(m_GlobControlLock)]] +void srt::CUDTUnited::removePendingForGroup(const CUDTGroup* g) +{ + // We don't have a list of listener sockets that have ever + // reported a pending connection for a group, so the only + // way to find them is to ride over the list of all sockets... + + list members; + g->getMemberSockets((members)); + + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + // Check if any of them is a listener socket... + + /* XXX This is left for information only that we are only + interested with listener sockets - with the current + implementation checking it is pointless because the + m_QueuedSockets structure is present in every socket + anyway even if it's not a listener, and only listener + sockets may have this container nonempty. So checking + the container should suffice. + + if (!s->core().m_bListening) + continue; + */ + + if (s->m_QueuedSockets.empty()) + continue; + + // Somehow fortunate for us that it's a set, so we + // can simply check if this allegedly listener socket + // contains any of them. + for (list::iterator m = members.begin(), mx = m; m != members.end(); m = mx) + { + ++mx; + std::map::iterator q = s->m_QueuedSockets.find(*m); + if (q != s->m_QueuedSockets.end()) + { + HLOGC(cnlog.Debug, log << "accept: listener @" << s->m_SocketID + << " had ququed member @" << *m << " -- removed"); + // Found an intersection socket. + // Remove it from the listener queue + s->m_QueuedSockets.erase(q); + + // NOTE ALSO that after this removal the queue may be EMPTY, + // and if so, the listener socket should be no longer ready for accept. + if (s->m_QueuedSockets.empty()) + { + m_EPoll.update_events(s->m_SocketID, s->core().m_sPollID, SRT_EPOLL_ACCEPT, false); + } + + // and remove it also from the members list. + // This can be done safely because we use a SAFE LOOP. + // We can also do it safely because a socket may be + // present in only one listener socket in the whole app. + members.erase(m); + } + } + + // It may happen that the list of members can be + // eventually purged even if we haven't checked every socket. + // If it happens so, quit immediately because there's nothing + // left to do. + if (members.empty()) + return; + } +} + +#endif + +SRTSOCKET srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int namelen) { // Here both srcname and tarname must be specified if (!srcname || !tarname || namelen < int(sizeof(sockaddr_in))) @@ -1286,7 +1453,7 @@ int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockadd // Check affiliation of the socket. It's now allowed for it to be // a group or socket. For a group, add automatically a socket to // the group. - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); // Note: forced_isn is ignored when connecting a group. @@ -1307,10 +1474,11 @@ int srt::CUDTUnited::connect(SRTSOCKET u, const sockaddr* srcname, const sockadd // For a single socket, just do bind, then connect bind(s, source_addr); - return connectIn(s, target_addr, SRT_SEQNO_NONE); + connectIn(s, target_addr, SRT_SEQNO_NONE); + return SRT_SOCKID_CONNREQ; } -int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { if (!name || namelen < int(sizeof(sockaddr_in))) { @@ -1326,7 +1494,7 @@ int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namele // Check affiliation of the socket. It's now allowed for it to be // a group or socket. For a group, add automatically a socket to // the group. - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); @@ -1343,31 +1511,29 @@ int srt::CUDTUnited::connect(const SRTSOCKET u, const sockaddr* name, int namele if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); - return connectIn(s, target_addr, forced_isn); + connectIn(s, target_addr, forced_isn); + return SRT_SOCKID_CONNREQ; } #if ENABLE_BONDING -int srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) +SRTSOCKET srt::CUDTUnited::singleMemberConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* gd) { - int gstat = groupConnect(pg, gd, 1); - if (gstat == -1) + SRTSOCKET gstat = groupConnect(pg, gd, 1); + if (gstat == SRT_INVALID_SOCK) { // We have only one element here, so refer to it. // Sanity check if (gd->errorcode == SRT_SUCCESS) gd->errorcode = SRT_EINVPARAM; - CodeMajor mj = CodeMajor(gd->errorcode / 1000); - CodeMinor mn = CodeMinor(gd->errorcode % 1000); - - return CUDT::APIError(mj, mn); + return CUDT::APIError(gd->errorcode), SRT_INVALID_SOCK; } return gstat; } // [[using assert(pg->m_iBusy > 0)]] -int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) +SRTSOCKET srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, int arraysize) { CUDTGroup& g = *pg; SRT_ASSERT(g.m_iBusy > 0); @@ -1409,7 +1575,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // This also should happen if ERR flag was set, as IN and OUT could be set, too. m_EPoll.update_events(g.id(), g.m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT, false); } - SRTSOCKET retval = -1; + SRTSOCKET retval = SRT_INVALID_SOCK; int eid = -1; int connect_modes = SRT_EPOLL_CONNECT | SRT_EPOLL_ERR; @@ -1556,7 +1722,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } else { - targets[tii].id = CUDT::INVALID_SOCK; + targets[tii].id = SRT_INVALID_SOCK; delete ns; m_Sockets.erase(sid); @@ -1618,7 +1784,10 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // to avoid locking more than one mutex at a time. erc_rloc = e.getErrorCode(); targets[tii].errorcode = e.getErrorCode(); - targets[tii].id = CUDT::INVALID_SOCK; + targets[tii].id = SRT_INVALID_SOCK; + + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(ns->m_SocketID, ns->core().m_sPollID); ScopedLock cl(m_GlobControlLock); ns->removeFromGroup(false); @@ -1631,9 +1800,11 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i { LOGC(aclog.Fatal, log << "groupConnect: IPE: UNKNOWN EXCEPTION from connectIn"); targets[tii].errorcode = SRT_ESYSOBJ; - targets[tii].id = CUDT::INVALID_SOCK; + targets[tii].id = SRT_INVALID_SOCK; ScopedLock cl(m_GlobControlLock); ns->removeFromGroup(false); + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(ns->m_SocketID, ns->core().m_sPollID); m_Sockets.erase(ns->m_SocketID); // Intercept to delete the socket on failure. delete ns; @@ -1680,7 +1851,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // set BROKEN or PENDING. f->sndstate = SRT_GST_PENDING; f->rcvstate = SRT_GST_PENDING; - retval = -1; + retval = SRT_INVALID_SOCK; break; } @@ -1728,7 +1899,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } } - if (retval == -1) + if (retval == SRT_INVALID_SOCK) { HLOGC(aclog.Debug, log << "groupConnect: none succeeded as background-spawn, exit with error"); block_new_opened = false; // Avoid executing further while loop @@ -1741,13 +1912,13 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i if (spawned.empty()) { // All were removed due to errors. - retval = -1; + retval = SRT_INVALID_SOCK; break; } HLOGC(aclog.Debug, log << "groupConnect: first connection, applying EPOLL WAITING."); int len = (int)spawned.size(); vector ready(spawned.size()); - const int estat = srt_epoll_wait(eid, + const int estat = srt_epoll_wait(eid, NULL, NULL, // IN/ACCEPT &ready[0], @@ -1759,7 +1930,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i NULL); // Sanity check. Shouldn't happen if subs are in sync with spawned. - if (estat == -1) + if (estat == int(SRT_ERROR)) { #if ENABLE_LOGGING CUDTException& x = CUDT::getlasterror(); @@ -1771,7 +1942,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } #endif HLOGC(aclog.Debug, log << "groupConnect: srt_epoll_wait failed - breaking the wait loop"); - retval = -1; + retval = SRT_INVALID_SOCK; break; } @@ -1882,7 +2053,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i continue; // This will also automatically remove it from the group and all eids - close(s); + close(s, SRT_CLS_INTERNAL); } // There's no possibility to report a problem on every connection @@ -1893,14 +2064,14 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // standing. Each one could, however, break by a different reason, // for example, one by timeout, another by wrong passphrase. Check // the `errorcode` field to determine the reaon for particular link. - if (retval == -1) + if (retval == SRT_INVALID_SOCK) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); return retval; } #endif -int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn) +void srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, int32_t forced_isn) { ScopedLock cg(s->m_ControlLock); // a socket can "connect" only if it is in the following states: @@ -1962,19 +2133,17 @@ int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, i s->m_Status = SRTS_OPENED; throw; } - - return 0; } -int srt::CUDTUnited::close(const SRTSOCKET u) +SRTSTATUS srt::CUDTUnited::close(const SRTSOCKET u, int reason) { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); k.group->close(); deleteGroup(k.group); - return 0; + return SRT_STATUS_OK; } #endif #if ENABLE_HEAVY_LOGGING @@ -1998,7 +2167,7 @@ int srt::CUDTUnited::close(const SRTSOCKET u) IF_HEAVY_LOGGING(ScopedExitLog slog(k.socket)); HLOGC(smlog.Debug, log << "CUDTUnited::close/begin: @" << u << " busy=" << k.socket->isStillBusy()); - return close(k.socket); + return close(k.socket, reason); } #if ENABLE_BONDING @@ -2049,9 +2218,96 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) } #endif -int srt::CUDTUnited::close(CUDTSocket* s) +// [[using locked(m_GlobControlLock)]] +void srt::CUDTUnited::recordCloseReason(CUDTSocket* s) +{ + SRT_CLOSE_INFO info; + info.agent = SRT_CLOSE_REASON(s->core().m_AgentCloseReason.load()); + info.peer = SRT_CLOSE_REASON(s->core().m_PeerCloseReason.load()); + info.time = s->core().m_CloseTimeStamp.load().time_since_epoch().count(); + + CloseInfo ci; + ci.info = info; + + m_ClosedDatabase[s->m_SocketID] = ci; + + // As a DOS attack prevention, do not allow to keep more than 10 records. + // In a normal functioning of the application this shouldn't be necessary, + // but it is still needed that a record of a dead socket is kept for + // 10 gc cycles more to ensure that the application can obtain it even after + // the socket has been physically removed. But if we don't limit the number + // of these records, this could be vulnerable for DOS attack if the user + // forces the application to create and close SRT sockets very quickly. + // Hence remove the oldest record, which can be recognized from the `time` + // field, if the number of records exceeds 10. + if (m_ClosedDatabase.size() > MAX_CLOSE_RECORD_SIZE) + { + // remove the oldest one + // This can only be done by collecting all time info + map which; + + for (map::iterator x = m_ClosedDatabase.begin(); + x != m_ClosedDatabase.end(); ++x) + { + which[x->second.info.time] = x->first; + } + + map::iterator y = which.begin(); + size_t ntodel = m_ClosedDatabase.size() - MAX_CLOSE_RECORD_SIZE; + for (size_t i = 0; i < ntodel; ++i) + { + // Sanity check - should never happen because it's unlikely + // that two different sockets were closed exactly at the same + // nanosecond time. + if (y == which.end()) + break; + + m_ClosedDatabase.erase(y->second); + ++y; + } + } +} + +SRTSTATUS srt::CUDTUnited::close(CUDTSocket* s, int reason) { HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSE. Acquiring control lock"); + + // The check for whether m_pRcvQueue isn't NULL is safe enough; + // it can either be NULL after socket creation and without binding + // and then once it's assigned, it's never reset to NULL even when + // destroying the socket. + CUDT& e = s->core(); + if (e.m_pRcvQueue && e.m_bConnecting && !e.m_bConnected) + { + // Workaround for a design flaw. + // It's to work around the case when the socket is being + // closed in another thread while it's in the process of + // connecting in the blocking mode, that is, it runs the + // loop in `CUDT::startConnect` whole time under the lock + // of CUDT::m_ConnectionLock and CUDTSocket::m_ControlLock + // this way blocking the `srt_close` API call from continuing. + // We are setting here the m_bClosing flag prematurely so + // that the loop may check this flag periodically and exit + // immediately if it's set. + // + // The problem is that this flag shall NOT be set in case + // when you have a CONNECTED socket because not only isn't it + // not a problem in this case, but also it additionally + // turns the socket in a "confused" state in which it skips + // vital part of closing itself and therefore runs an infinite + // loop when trying to purge the sender buffer of the closing + // socket. + // + // XXX Consider refax on CUDT::startConnect and removing the + // connecting loop there and replace the "blocking mode specific" + // connecting procedure with delegation to the receiver queue, + // which will be then common with non-blocking mode, and synchronize + // the blocking through a CV. + + e.m_bClosing = true; + e.m_pRcvQueue->kick(); + } + ScopedLock socket_cg(s->m_ControlLock); HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSING (removing from listening, closing CUDT)"); @@ -2062,7 +2318,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) if (s->m_Status == SRTS_LISTENING) { if (s->core().m_bBroken) - return 0; + return SRT_STATUS_OK; s->m_tsClosureTimeStamp = steady_clock::now(); s->core().m_bBroken = true; @@ -2082,6 +2338,8 @@ int srt::CUDTUnited::close(CUDTSocket* s) // broadcast all "accept" waiting CSync::lock_notify_all(s->m_AcceptCond, s->m_AcceptLock); + + s->core().setAgentCloseReason(reason); } else { @@ -2091,7 +2349,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) // may block INDEFINITELY. As long as it's acceptable to block the // call to srt_close(), and all functions in all threads where this // very socket is used, this shall not block the central database. - s->core().closeInternal(); + s->core().closeInternal(reason); // synchronize with garbage collection. HLOGC(smlog.Debug, @@ -2112,7 +2370,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) if ((i == m_Sockets.end()) || (i->second->m_Status == SRTS_CLOSED)) { HLOGC(smlog.Debug, log << "@" << u << "U::close: NOT AN ACTIVE SOCKET, returning."); - return 0; + return SRT_STATUS_OK; } s = i->second; s->setClosed(); @@ -2126,8 +2384,12 @@ int srt::CUDTUnited::close(CUDTSocket* s) } #endif - m_Sockets.erase(s->m_SocketID); - m_ClosedSockets[s->m_SocketID] = s; + recordCloseReason(s); + + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(s->m_SocketID, s->core().m_sPollID); + + swipeSocket_LOCKED(s->m_SocketID, s, SWIPE_NOW); HLOGC(smlog.Debug, log << "@" << u << "U::close: Socket MOVED TO CLOSED for collecting later."); CGlobEvent::triggerEvent(); @@ -2210,7 +2472,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) */ CSync::notify_one_relaxed(m_GCStopCond); - return 0; + return SRT_STATUS_OK; } void srt::CUDTUnited::getpeername(const SRTSOCKET u, sockaddr* pw_name, int* pw_namelen) @@ -2261,6 +2523,39 @@ void srt::CUDTUnited::getsockname(const SRTSOCKET u, sockaddr* pw_name, int* pw_ *pw_namelen = len; } +void srt::CUDTUnited::getsockdevname(const SRTSOCKET u, char* pw_name, size_t* pw_namelen) +{ + if (!pw_name || !pw_namelen) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + CUDTSocket* s = locateSocket(u); + + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (s->core().m_bBroken) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (s->m_Status == SRTS_INIT) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + const vector& locals = GetLocalInterfaces(); + + for (vector::const_iterator i = locals.begin(); i != locals.end(); ++i) + { + if (i->addr.equal_address(s->m_SelfAddr)) + { + if (*pw_namelen < i->name.size() + 1) + throw CUDTException(MJ_NOTSUP, MN_INVAL); + memcpy((pw_name), i->name.c_str(), i->name.size()+1); + *pw_namelen = i->name.size(); + return; + } + } + + *pw_namelen = 0; // report empty one +} + int srt::CUDTUnited::select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout) { const steady_clock::time_point entertime = steady_clock::now(); @@ -2436,59 +2731,60 @@ int srt::CUDTUnited::epoll_create() return m_EPoll.create(); } -int srt::CUDTUnited::epoll_clear_usocks(int eid) +void srt::CUDTUnited::epoll_clear_usocks(int eid) { return m_EPoll.clear_usocks(eid); } -int srt::CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) +void srt::CUDTUnited::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { - int ret = -1; #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); - ret = m_EPoll.update_usock(eid, u, events); + m_EPoll.update_usock(eid, u, events); k.group->addEPoll(eid); - return 0; + return; } #endif - CUDTSocket* s = locateSocket(u); - if (s) - { - ret = epoll_add_usock_INTERNAL(eid, s, events); - } - else + // The call to epoll_add_usock_INTERNAL is expected + // to be called under m_GlobControlLock, so use this lock here, too. { - throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + ScopedLock cs (m_GlobControlLock); + CUDTSocket* s = locateSocket_LOCKED(u); + if (s) + { + epoll_add_usock_INTERNAL(eid, s, events); + } + else + { + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + } } - - return ret; } // NOTE: WILL LOCK (serially): // - CEPoll::m_EPollLock // - CUDT::m_RecvLock -int srt::CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) +void srt::CUDTUnited::epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events) { - int ret = m_EPoll.update_usock(eid, s->m_SocketID, events); + m_EPoll.update_usock(eid, s->m_SocketID, events); s->core().addEPoll(eid); - return ret; } -int srt::CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +void srt::CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { return m_EPoll.add_ssock(eid, s, events); } -int srt::CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +void srt::CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) { return m_EPoll.update_ssock(eid, s, events); } template -int srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) +void srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) { // XXX Not sure if this is anyhow necessary because setting readiness // to false doesn't actually trigger any action. Further research needed. @@ -2504,31 +2800,29 @@ int srt::CUDTUnited::epoll_remove_entity(const int eid, EntityType* ent) HLOGC(ealog.Debug, log << "epoll_remove_usock: CLEARING subscription on E" << eid << " of @" << ent->id()); int no_events = 0; - int ret = m_EPoll.update_usock(eid, ent->id(), &no_events); - - return ret; + m_EPoll.update_usock(eid, ent->id(), &no_events); } // Needed internal access! -int srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) +void srt::CUDTUnited::epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* s) { return epoll_remove_entity(eid, &s->core()); } #if ENABLE_BONDING -int srt::CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g) +void srt::CUDTUnited::epoll_remove_group_INTERNAL(const int eid, CUDTGroup* g) { return epoll_remove_entity(eid, g); } #endif -int srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) +void srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) { CUDTSocket* s = 0; #if ENABLE_BONDING CUDTGroup* g = 0; - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { GroupKeeper k(*this, u, ERH_THROW); g = k.group; @@ -2548,7 +2842,7 @@ int srt::CUDTUnited::epoll_remove_usock(const int eid, const SRTSOCKET u) return m_EPoll.update_usock(eid, u, &no_events); } -int srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) +void srt::CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) { return m_EPoll.remove_ssock(eid, s); } @@ -2563,7 +2857,7 @@ int32_t srt::CUDTUnited::epoll_set(int eid, int32_t flags) return m_EPoll.setflags(eid, flags); } -int srt::CUDTUnited::epoll_release(const int eid) +void srt::CUDTUnited::epoll_release(const int eid) { return m_EPoll.release(eid); } @@ -2769,23 +3063,35 @@ void srt::CUDTUnited::checkBrokenSockets() HLOGC(smlog.Debug, log << "checkBrokenSockets: moving BROKEN socket to CLOSED: @" << i->first); + // Note that this will not override the value that has been already + // set by some other functionality, only set it when not yet set. + s->core().setAgentCloseReason(SRT_CLS_INTERNAL); + + recordCloseReason(s); + // close broken connections and start removal timer s->setClosed(); tbc.push_back(i->first); - m_ClosedSockets[i->first] = s; - // remove from listener's queue - sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); - if (ls == m_Sockets.end()) + // NOTE: removal from m_SocketID POSTPONED + // to loop over removal of all from the `tbc` list + swipeSocket_LOCKED(i->first, s, SWIPE_LATER); + + if (s->m_ListenSocket != SRT_SOCKID_CONNREQ) { - ls = m_ClosedSockets.find(s->m_ListenSocket); - if (ls == m_ClosedSockets.end()) - continue; - } + // remove from listener's queue + sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket); + if (ls == m_Sockets.end()) + { + ls = m_ClosedSockets.find(s->m_ListenSocket); + if (ls == m_ClosedSockets.end()) + continue; + } - enterCS(ls->second->m_AcceptLock); - ls->second->m_QueuedSockets.erase(s->m_SocketID); - leaveCS(ls->second->m_AcceptLock); + enterCS(ls->second->m_AcceptLock); + ls->second->m_QueuedSockets.erase(s->m_SocketID); + leaveCS(ls->second->m_AcceptLock); + } } for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j) @@ -2890,9 +3196,6 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) s->removeFromGroup(true); } #endif - // decrease multiplexer reference count, and remove it if necessary - const int mid = s->m_iMuxID; - { ScopedLock cg(s->m_AcceptLock); @@ -2913,9 +3216,12 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) CUDTSocket* as = si->second; - as->breakSocket_LOCKED(); - m_ClosedSockets[q->first] = as; - m_Sockets.erase(q->first); + as->breakSocket_LOCKED(SRT_CLS_DEADLSN); + + // You won't be updating any EIDs anymore. + m_EPoll.wipe_usock(as->m_SocketID, as->core().m_sPollID); + + swipeSocket_LOCKED(q->first, as, SWIPE_NOW); } } @@ -2933,7 +3239,8 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) * remains forever causing epoll_wait to unblock continuously for inexistent * sockets. Get rid of all events for this socket. */ - m_EPoll.update_events(u, s->core().m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, false); + // (just in case, this should be wiped out already) + m_EPoll.wipe_usock(u, s->core().m_sPollID); // delete this one m_ClosedSockets.erase(i); @@ -2949,16 +3256,37 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) // Report: P04-1.28, P04-2.27, P04-2.50, P04-2.55 HLOGC(smlog.Debug, log << "GC/removeSocket: closing associated UDT @" << u); + leaveCS(m_GlobControlLock); - s->core().closeInternal(); + s->core().closeInternal(SRT_CLS_INTERNAL); enterCS(m_GlobControlLock); + + // IMPORTANT!!! + // + // The order of deletion must be: first delete socket, then multiplexer. + // The receiver buffer shares the use of CUnits from the multiplexer's unit queue, + // which is assigned to the multiplexer because this is where the incoming + // UDP packets are placed. The receiver buffer must be first deleted and + // so unreference all CUnits. Then the multiplexer can be deleted and drag all + // CUnits with itself. + const int mid = s->m_iMuxID; HLOGC(smlog.Debug, log << "GC/removeSocket: DELETING SOCKET @" << u); delete s; - HLOGC(smlog.Debug, log << "GC/removeSocket: socket @" << u << " DELETED. Checking muxer."); + HLOGC(smlog.Debug, log << "GC/removeSocket: socket @" << u << " DELETED. Checking muxer id=" << mid); + removeMux(mid); +} +/// decrease multiplexer reference count, and remove it if necessary +/// +/// @param mid Muxer ID that identifies the multiplexer in the socket +/// @param u Socket ID that was the last multiplexer's user (logging only) +// [[using locked(m_GlobControlLock)]] +void srt::CUDTUnited::removeMux(const int mid) +{ + // Ignore those never bound if (mid == -1) { - HLOGC(smlog.Debug, log << "GC/removeSocket: no muxer found, finishing."); + HLOGC(smlog.Debug, log << "MUXER not assigned to that socket"); return; } @@ -2966,19 +3294,18 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) m = m_mMultiplexer.find(mid); if (m == m_mMultiplexer.end()) { - LOGC(smlog.Fatal, log << "IPE: For socket @" << u << " MUXER id=" << mid << " NOT FOUND!"); + LOGC(smlog.Fatal, log << "IPE: MUXER id=" << mid << " NOT FOUND!"); return; } CMultiplexer& mx = m->second; mx.m_iRefCount--; - HLOGC(smlog.Debug, log << "unrefing underlying muxer " << mid << " for @" << u << ", ref=" << mx.m_iRefCount); - if (0 == mx.m_iRefCount) + HLOGC(smlog.Debug, log << "removeMux: unrefing muxer " << mid << ", ref=" << mx.m_iRefCount); + if (mx.m_iRefCount <= 0) { - HLOGC(smlog.Debug, - log << "MUXER id=" << mid << " lost last socket @" << u << " - deleting muxer bound to port " - << mx.m_pChannel->bindAddressAny().hport()); + HLOGC(smlog.Debug, log << "MUXER id=" << mid << " lost last socket - deleting muxer bound to " + << mx.m_pChannel->bindAddressAny().str()); // The channel has no access to the queues and // it looks like the multiplexer is the master of all of them. // The queues must be silenced before closing the channel @@ -2989,6 +3316,35 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) mx.destroy(); m_mMultiplexer.erase(m); } + else + { + HLOGC(smlog.Debug, log << "MUXER id=" << mid << " has still " << mx.m_iRefCount << " users"); + } +} + +void srt::CUDTUnited::checkTemporaryDatabases() +{ + ScopedLock cg(m_GlobControlLock); + + // It's not very efficient to collect first the keys of all + // elements to remove and then remove from the map by key. + + // In C++20 this is possible by doing + // m_ClosedDatabase.erase_if([](auto& c) { return --c.generation <= 0; }); + // but nothing equivalent in the earlier standards. + + vector expired; + + for (map::iterator c = m_ClosedDatabase.begin(); + c != m_ClosedDatabase.end(); ++c) + { + --c->second.generation; + if (c->second.generation <= 0) + expired.push_back(c->first); + } + + for (vector::iterator i = expired.begin(); i != expired.end(); ++i) + m_ClosedDatabase.erase(*i); } void srt::CUDTUnited::configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af) @@ -2996,7 +3352,7 @@ void srt::CUDTUnited::configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int w_m.m_mcfg = s->core().m_config; w_m.m_iIPversion = af; w_m.m_iRefCount = 1; - w_m.m_iID = s->m_SocketID; + w_m.m_iID = int32_t(s->m_SocketID); } uint16_t srt::CUDTUnited::installMuxer(CUDTSocket* w_s, CMultiplexer& fw_sm) @@ -3325,7 +3681,14 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons m.m_pSndQueue = new CSndQueue; m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer); m.m_pRcvQueue = new CRcvQueue; - m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer); + + // We can't use maxPayloadSize() because this value isn't valid until the connection is established. + // We need to "think big", that is, allocate a size that would fit both IPv4 and IPv6. + const size_t payload_size = s->core().m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(AF_INET); + + HLOGC(smlog.Debug, log << s->core().CONID() << "updateMux: config rcv queue qsize=" << 128 + << " plsize=" << payload_size << " hsize=" << 1024); + m.m_pRcvQueue->init(128, payload_size, m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer); // Rewrite the port here, as it might be only known upon return // from CChannel::open. @@ -3446,26 +3809,30 @@ void* srt::CUDTUnited::garbageCollect(void* p) UniqueLock gclock(self->m_GCStopLock); + // START LIBRARY RUNNING LOOP while (!self->m_bClosing) { INCREMENT_THREAD_ITERATIONS(); self->checkBrokenSockets(); + self->checkTemporaryDatabases(); HLOGC(inlog.Debug, log << "GC: sleep 1 s"); self->m_GCStopCond.wait_for(gclock, seconds_from(1)); } + // END. + THREAD_EXIT(); return NULL; } //////////////////////////////////////////////////////////////////////////////// -int srt::CUDT::startup() +SRTRUNSTATUS srt::CUDT::startup() { return uglobal().startup(); } -int srt::CUDT::cleanup() +SRTSTATUS srt::CUDT::cleanup() { return uglobal().cleanup(); } @@ -3478,19 +3845,19 @@ SRTSOCKET srt::CUDT::socket() } catch (const CUDTException& e) { - SetThreadLocalError(e); - return INVALID_SOCK; + APIError a(e); + return SRT_INVALID_SOCK; } catch (const bad_alloc&) { - SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; + APIError a(MJ_SYSTEMRES, MN_MEMORY, 0); + return SRT_INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "socket: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return INVALID_SOCK; + APIError a(MJ_UNKNOWN, MN_NONE, 0); + return SRT_INVALID_SOCK; } } @@ -3504,6 +3871,13 @@ srt::CUDT::APIError::APIError(CodeMajor mj, CodeMinor mn, int syserr) SetThreadLocalError(CUDTException(mj, mn, syserr)); } +srt::CUDT::APIError::APIError(int errorcode) +{ + CodeMajor mj = CodeMajor(errorcode / 1000); + CodeMinor mn = CodeMinor(errorcode % 1000); + SetThreadLocalError(CUDTException(mj, mn, 0)); +} + #if ENABLE_BONDING // This is an internal function; 'type' should be pre-checked if it has a correct value. // This doesn't have argument of GroupType due to header file conflicts. @@ -3531,14 +3905,12 @@ SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (...) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } - - return SRT_INVALID_SOCK; } // [[using locked(m_ControlLock)]] @@ -3580,14 +3952,14 @@ SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) ScopedLock glock(uglobal().m_GlobControlLock); CUDTSocket* s = uglobal().locateSocket_LOCKED(socket); if (!s || !s->m_GroupOf) - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; return s->m_GroupOf->id(); } -int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize) { - if ((groupid & SRTGROUP_MASK) == 0 || !psize) + if (!CUDT::isgroup(groupid) || !psize) { return APIError(MJ_NOTSUP, MN_INVAL, 0); } @@ -3603,7 +3975,7 @@ int srt::CUDT::getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* } #endif -int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) +SRTSTATUS srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) { try { @@ -3637,7 +4009,7 @@ int srt::CUDT::bind(SRTSOCKET u, const sockaddr* name, int namelen) } } -int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) +SRTSTATUS srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) { try { @@ -3662,7 +4034,7 @@ int srt::CUDT::bind(SRTSOCKET u, UDPSOCKET udpsock) } } -int srt::CUDT::listen(SRTSOCKET u, int backlog) +SRTSTATUS srt::CUDT::listen(SRTSOCKET u, int backlog) { try { @@ -3692,18 +4064,18 @@ SRTSOCKET srt::CUDT::accept_bond(const SRTSOCKET listeners[], int lsize, int64_t catch (const CUDTException& e) { SetThreadLocalError(e); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } catch (bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "accept_bond: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } } @@ -3716,22 +4088,22 @@ SRTSOCKET srt::CUDT::accept(SRTSOCKET u, sockaddr* addr, int* addrlen) catch (const CUDTException& e) { SetThreadLocalError(e); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } catch (const bad_alloc&) { SetThreadLocalError(CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "accept: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return INVALID_SOCK; + return SRT_INVALID_SOCK; } } -int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) +SRTSOCKET srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen) { try { @@ -3739,29 +4111,29 @@ int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0), SRT_INVALID_SOCK; } } #if ENABLE_BONDING -int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) +SRTSOCKET srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int arraysize) { if (arraysize <= 0) - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; - if ((grp & SRTGROUP_MASK) == 0) + if (!CUDT::isgroup(grp)) { // connectLinks accepts only GROUP id, not socket id. - return APIError(MJ_NOTSUP, MN_SIDINVAL, 0); + return APIError(MJ_NOTSUP, MN_SIDINVAL, 0), SRT_INVALID_SOCK; } try @@ -3771,21 +4143,21 @@ int srt::CUDT::connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG targets[], int ar } catch (CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } catch (std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0), SRT_INVALID_SOCK; } } #endif -int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { try { @@ -3793,24 +4165,24 @@ int srt::CUDT::connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t f } catch (const CUDTException& e) { - return APIError(e); + return APIError(e), SRT_INVALID_SOCK; } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0), SRT_INVALID_SOCK; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0), SRT_INVALID_SOCK; } } -int srt::CUDT::close(SRTSOCKET u) +SRTSTATUS srt::CUDT::close(SRTSOCKET u, int reason) { try { - return uglobal().close(u); + return uglobal().close(u, reason); } catch (const CUDTException& e) { @@ -3823,12 +4195,12 @@ int srt::CUDT::close(SRTSOCKET u) } } -int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) +SRTSTATUS srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) { try { uglobal().getpeername(u, name, namelen); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -3841,12 +4213,12 @@ int srt::CUDT::getpeername(SRTSOCKET u, sockaddr* name, int* namelen) } } -int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) +SRTSTATUS srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) { try { uglobal().getsockname(u, name, namelen); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -3859,7 +4231,25 @@ int srt::CUDT::getsockname(SRTSOCKET u, sockaddr* name, int* namelen) } } -int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) +SRTSTATUS srt::CUDT::getsockdevname(SRTSOCKET u, char* name, size_t* namelen) +{ + try + { + uglobal().getsockdevname(u, name, namelen); + return SRT_STATUS_OK; + } + catch (const CUDTException& e) + { + return APIError(e); + } + catch (const std::exception& ee) + { + LOGC(aclog.Fatal, log << "getsockname: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); + return APIError(MJ_UNKNOWN, MN_NONE, 0); + } +} + +SRTSTATUS srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval, int* pw_optlen) { if (!pw_optval || !pw_optlen) { @@ -3869,17 +4259,17 @@ int srt::CUDT::getsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, void* pw_optval try { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); k.group->getOpt(optname, (pw_optval), (*pw_optlen)); - return 0; + return SRT_STATUS_OK; } #endif CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); udt.getOpt(optname, (pw_optval), (*pw_optlen)); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -3892,7 +4282,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) +SRTSTATUS srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* optval, int optlen) { if (!optval || optlen < 0) return APIError(MJ_NOTSUP, MN_INVAL, 0); @@ -3900,17 +4290,17 @@ int srt::CUDT::setsockopt(SRTSOCKET u, int, SRT_SOCKOPT optname, const void* opt try { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); k.group->setOpt(optname, optval, optlen); - return 0; + return SRT_STATUS_OK; } #endif CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); udt.setOpt(optname, optval, optlen); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -3945,7 +4335,7 @@ int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) try { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); return k.group->send(buf, len, (w_m)); @@ -3956,16 +4346,16 @@ int srt::CUDT::sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL& w_m) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "sendmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -3989,7 +4379,7 @@ int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) try { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); return k.group->recv(buf, len, (w_m)); @@ -4000,12 +4390,12 @@ int srt::CUDT::recvmsg2(SRTSOCKET u, char* buf, int len, SRT_MSGCTRL& w_m) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -4018,16 +4408,16 @@ int64_t srt::CUDT::sendfile(SRTSOCKET u, fstream& ifs, int64_t& offset, int64_t } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "sendfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -4039,12 +4429,12 @@ int64_t srt::CUDT::recvfile(SRTSOCKET u, fstream& ofs, int64_t& offset, int64_t } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "recvfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -4052,7 +4442,7 @@ int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET { if ((!readfds) && (!writefds) && (!exceptfds)) { - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } try @@ -4061,16 +4451,16 @@ int srt::CUDT::select(int, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "select: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -4082,7 +4472,7 @@ int srt::CUDT::selectEx(const vector& fds, { if ((!readfds) && (!writefds) && (!exceptfds)) { - return APIError(MJ_NOTSUP, MN_INVAL, 0); + return APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } try @@ -4091,16 +4481,16 @@ int srt::CUDT::selectEx(const vector& fds, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (bad_alloc&) { - return APIError(MJ_SYSTEMRES, MN_MEMORY, 0); + return APIError(MJ_SYSTEMRES, MN_MEMORY, 0).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "selectEx: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN); + return APIError(MJ_UNKNOWN).as(); } } @@ -4112,20 +4502,21 @@ int srt::CUDT::epoll_create() } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_create: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::epoll_clear_usocks(int eid) +SRTSTATUS srt::CUDT::epoll_clear_usocks(int eid) { try { - return uglobal().epoll_clear_usocks(eid); + uglobal().epoll_clear_usocks(eid); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4139,11 +4530,12 @@ int srt::CUDT::epoll_clear_usocks(int eid) } } -int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) +SRTSTATUS srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* events) { try { - return uglobal().epoll_add_usock(eid, u, events); + uglobal().epoll_add_usock(eid, u, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4156,11 +4548,12 @@ int srt::CUDT::epoll_add_usock(const int eid, const SRTSOCKET u, const int* even } } -int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +SRTSTATUS srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) { try { - return uglobal().epoll_add_ssock(eid, s, events); + uglobal().epoll_add_ssock(eid, s, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4173,11 +4566,12 @@ int srt::CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* even } } -int srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) +SRTSTATUS srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* events) { try { - return uglobal().epoll_add_usock(eid, u, events); + uglobal().epoll_add_usock(eid, u, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4191,11 +4585,12 @@ int srt::CUDT::epoll_update_usock(const int eid, const SRTSOCKET u, const int* e } } -int srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +SRTSTATUS srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) { try { - return uglobal().epoll_update_ssock(eid, s, events); + uglobal().epoll_update_ssock(eid, s, events); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4209,11 +4604,12 @@ int srt::CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* e } } -int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) +SRTSTATUS srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) { try { - return uglobal().epoll_remove_usock(eid, u); + uglobal().epoll_remove_usock(eid, u); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4227,11 +4623,12 @@ int srt::CUDT::epoll_remove_usock(const int eid, const SRTSOCKET u) } } -int srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) +SRTSTATUS srt::CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) { try { - return uglobal().epoll_remove_ssock(eid, s); + uglobal().epoll_remove_ssock(eid, s); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4258,12 +4655,12 @@ int srt::CUDT::epoll_wait(const int eid, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_wait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -4275,12 +4672,12 @@ int srt::CUDT::epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_uwait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } @@ -4292,20 +4689,21 @@ int32_t srt::CUDT::epoll_set(const int eid, int32_t flags) } catch (const CUDTException& e) { - return APIError(e); + return APIError(e).as(); } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "epoll_set: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); - return APIError(MJ_UNKNOWN, MN_NONE, 0); + return APIError(MJ_UNKNOWN, MN_NONE, 0).as(); } } -int srt::CUDT::epoll_release(const int eid) +SRTSTATUS srt::CUDT::epoll_release(const int eid) { try { - return uglobal().epoll_release(eid); + uglobal().epoll_release(eid); + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4323,10 +4721,10 @@ srt::CUDTException& srt::CUDT::getlasterror() return GetThreadLocalError(); } -int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) +SRTSTATUS srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instantaneous) { #if ENABLE_BONDING - if (u & SRTGROUP_MASK) + if (CUDT::isgroup(u)) return groupsockbstats(u, perf, clear); #endif @@ -4334,7 +4732,7 @@ int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instanta { CUDT& udt = uglobal().locateSocket(u, CUDTUnited::ERH_THROW)->core(); udt.bstats(perf, clear, instantaneous); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { @@ -4348,24 +4746,24 @@ int srt::CUDT::bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear, bool instanta } #if ENABLE_BONDING -int srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) +SRTSTATUS srt::CUDT::groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear) { try { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); k.group->bstatsSocket(perf, clear); - return 0; + return SRT_STATUS_OK; } catch (const CUDTException& e) { SetThreadLocalError(e); - return ERROR; + return SRT_ERROR; } catch (const std::exception& ee) { LOGC(aclog.Fatal, log << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what()); SetThreadLocalError(CUDTException(MJ_UNKNOWN, MN_NONE, 0)); - return ERROR; + return SRT_ERROR; } } #endif @@ -4404,7 +4802,7 @@ SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) try { #if ENABLE_BONDING - if (isgroup(u)) + if (CUDT::isgroup(u)) { CUDTUnited::GroupKeeper k(uglobal(), u, CUDTUnited::ERH_THROW); return k.group->getStatus(); @@ -4425,32 +4823,73 @@ SRT_SOCKSTATUS srt::CUDT::getsockstate(SRTSOCKET u) } } +int srt::CUDT::getMaxPayloadSize(SRTSOCKET id) +{ + return uglobal().getMaxPayloadSize(id); +} + +int srt::CUDTUnited::getMaxPayloadSize(SRTSOCKET id) +{ + CUDTSocket* s = locateSocket(id); + if (!s) + { + return CUDT::APIError(MJ_NOTSUP, MN_SIDINVAL).as(); + } + + if (s->m_SelfAddr.family() == AF_UNSPEC) + { + return CUDT::APIError(MJ_NOTSUP, MN_ISUNBOUND).as(); + } + + int fam = s->m_SelfAddr.family(); + CUDT& u = s->core(); + + std::string errmsg; + int extra = u.m_config.extraPayloadReserve((errmsg)); + if (extra == -1) + { + LOGP(aclog.Error, errmsg); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL).as(); + } + + // Prefer transfer IP version, if defined. This is defined after + // the connection is established. Note that the call is rejected + // if the socket isn't bound, be it explicitly or implicitly by + // calling srt_connect(). + if (u.m_TransferIPVersion != AF_UNSPEC) + fam = u.m_TransferIPVersion; + + int payload_size = u.m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(fam) - extra; + + return payload_size; +} + //////////////////////////////////////////////////////////////////////////////// namespace UDT { -int startup() +SRTRUNSTATUS startup() { return srt::CUDT::startup(); } -int cleanup() +SRTSTATUS cleanup() { return srt::CUDT::cleanup(); } -int bind(SRTSOCKET u, const struct sockaddr* name, int namelen) +SRTSTATUS bind(SRTSOCKET u, const struct sockaddr* name, int namelen) { return srt::CUDT::bind(u, name, namelen); } -int bind2(SRTSOCKET u, UDPSOCKET udpsock) +SRTSTATUS bind2(SRTSOCKET u, UDPSOCKET udpsock) { return srt::CUDT::bind(u, udpsock); } -int listen(SRTSOCKET u, int backlog) +SRTSTATUS listen(SRTSOCKET u, int backlog) { return srt::CUDT::listen(u, backlog); } @@ -4460,39 +4899,39 @@ SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen) return srt::CUDT::accept(u, addr, addrlen); } -int connect(SRTSOCKET u, const struct sockaddr* name, int namelen) +SRTSOCKET connect(SRTSOCKET u, const struct sockaddr* name, int namelen) { return srt::CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } -int close(SRTSOCKET u) +SRTSTATUS close(SRTSOCKET u) { - return srt::CUDT::close(u); + return srt::CUDT::close(u, SRT_CLS_API); } -int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen) +SRTSTATUS getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen) { return srt::CUDT::getpeername(u, name, namelen); } -int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen) +SRTSTATUS getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen) { return srt::CUDT::getsockname(u, name, namelen); } -int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen) +SRTSTATUS getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen) { return srt::CUDT::getsockopt(u, level, optname, optval, optlen); } -int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen) +SRTSTATUS setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen) { return srt::CUDT::setsockopt(u, level, optname, optval, optlen); } // DEVELOPER API -int connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn) +SRTSOCKET connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn) { return srt::CUDT::connect(u, name, namelen, forced_isn); } @@ -4568,37 +5007,37 @@ int epoll_create() return srt::CUDT::epoll_create(); } -int epoll_clear_usocks(int eid) +SRTSTATUS epoll_clear_usocks(int eid) { return srt::CUDT::epoll_clear_usocks(eid); } -int epoll_add_usock(int eid, SRTSOCKET u, const int* events) +SRTSTATUS epoll_add_usock(int eid, SRTSOCKET u, const int* events) { return srt::CUDT::epoll_add_usock(eid, u, events); } -int epoll_add_ssock(int eid, SYSSOCKET s, const int* events) +SRTSTATUS epoll_add_ssock(int eid, SYSSOCKET s, const int* events) { return srt::CUDT::epoll_add_ssock(eid, s, events); } -int epoll_update_usock(int eid, SRTSOCKET u, const int* events) +SRTSTATUS epoll_update_usock(int eid, SRTSOCKET u, const int* events) { return srt::CUDT::epoll_update_usock(eid, u, events); } -int epoll_update_ssock(int eid, SYSSOCKET s, const int* events) +SRTSTATUS epoll_update_ssock(int eid, SYSSOCKET s, const int* events) { return srt::CUDT::epoll_update_ssock(eid, s, events); } -int epoll_remove_usock(int eid, SRTSOCKET u) +SRTSTATUS epoll_remove_usock(int eid, SRTSOCKET u) { return srt::CUDT::epoll_remove_usock(eid, u); } -int epoll_remove_ssock(int eid, SYSSOCKET s) +SRTSTATUS epoll_remove_ssock(int eid, SYSSOCKET s) { return srt::CUDT::epoll_remove_ssock(eid, s); } @@ -4689,7 +5128,7 @@ int epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut return srt::CUDT::epoll_uwait(eid, fdsSet, fdsSize, msTimeOut); } -int epoll_release(int eid) +SRTSTATUS epoll_release(int eid) { return srt::CUDT::epoll_release(eid); } @@ -4721,7 +5160,7 @@ const char* geterror_desc(int code, int err) return (e.getErrorMessage()); } -int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear) +SRTSTATUS bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear) { return srt::CUDT::bstats(u, perf, clear); } @@ -4740,18 +5179,21 @@ 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) @@ -4759,6 +5201,7 @@ 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) @@ -4767,6 +5210,7 @@ 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) @@ -4802,7 +5246,7 @@ int getrejectreason(SRTSOCKET u) return CUDT::rejectReason(u); } -int setrejectreason(SRTSOCKET u, int value) +SRTSTATUS setrejectreason(SRTSOCKET u, int value) { return CUDT::rejectReason(u, value); } diff --git a/srtcore/api.h b/srtcore/api.h index d0249ca10..ab7f8107d 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -84,7 +84,7 @@ class CUDTSocket CUDTSocket() : m_Status(SRTS_INIT) , m_SocketID(0) - , m_ListenSocket(0) + , m_ListenSocket(SRT_SOCKID_CONNREQ) , m_PeerID(0) #if ENABLE_BONDING , m_GroupMemberData() @@ -103,7 +103,7 @@ class CUDTSocket CUDTSocket(const CUDTSocket& ancestor) : m_Status(SRTS_INIT) , m_SocketID(0) - , m_ListenSocket(0) + , m_ListenSocket(SRT_SOCKID_CONNREQ) , m_PeerID(0) #if ENABLE_BONDING , m_GroupMemberData() @@ -134,7 +134,8 @@ class CUDTSocket return m_iBusy; } - + // XXX Controversial as to whether it should be guarded by this lock. + // It is used in many places without the lock, and it is also atomic. SRT_ATTR_GUARDED_BY(m_ControlLock) sync::atomic m_Status; //< current socket state @@ -171,6 +172,8 @@ class CUDTSocket unsigned int m_uiBackLog; //< maximum number of connections in queue + SRT_EPOLL_T getListenerEvents(); + // XXX A refactoring might be needed here. // There are no reasons found why the socket can't contain a list iterator to a @@ -189,7 +192,7 @@ class CUDTSocket CUDT& core() { return m_UDT; } const CUDT& core() const { return m_UDT; } - static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) { return (int64_t(id) << 30) + isn; } + static int64_t getPeerSpec(SRTSOCKET id, int32_t isn) { return (int64_t(int32_t(id)) << 30) + isn; } int64_t getPeerSpec() { return getPeerSpec(m_PeerID, m_iISN); } SRT_SOCKSTATUS getStatus(); @@ -199,7 +202,7 @@ class CUDTSocket /// from within the GC thread only (that is, only when /// the socket should be no longer visible in the /// connection, including for sending remaining data). - void breakSocket_LOCKED(); + void breakSocket_LOCKED(int reason); /// This makes the socket no longer capable of performing any transmission /// operation, but continues to be responsive in the connection in order @@ -246,6 +249,8 @@ class CUDTUnited // Public constants static const int32_t MAX_SOCKET_VAL = SRTGROUP_MASK - 1; // maximum value for a regular socket + static const int MAX_CLOSE_RECORD_TTL = 10; + static const size_t MAX_CLOSE_RECORD_SIZE = 10; public: enum ErrorHandling @@ -258,17 +263,29 @@ class CUDTUnited /// initialize the UDT library. /// @return 0 if success, otherwise -1 is returned. - int startup(); + SRTRUNSTATUS startup(); /// release the UDT library. /// @return 0 if success, otherwise -1 is returned. - int cleanup(); + SRTSTATUS cleanup(); /// Create a new UDT socket. /// @param [out] pps Variable (optional) to which the new socket will be written, if succeeded /// @return The new UDT socket ID, or INVALID_SOCK. SRTSOCKET newSocket(CUDTSocket** pps = NULL); + enum SwipeSocketTerm { SWIPE_NOW = 0, SWIPE_LATER = 1 }; + /// Removes the socket from the global socket container + /// and place it in the socket trashcan. The socket should + /// remain there until all still pending activities are + /// finished and there are no more users of this socket. + /// Note that the swiped socket is no longer dispatchable + /// by id. + /// @param id socket ID to swipe. + /// @param s pointer to the socket to swipe. + /// @param action only add to closed list or remove completely + void swipeSocket_LOCKED(SRTSOCKET id, CUDTSocket* s, SwipeSocketTerm); + /// Create (listener-side) a new socket associated with the incoming connection request. /// @param [in] listen the listening socket ID. /// @param [in] peer peer address. @@ -286,8 +303,14 @@ class CUDTUnited int& w_error, CUDT*& w_acpu); - int installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); - int installConnectHook(const SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); +#if ENABLE_BONDING + SRT_ATTR_REQUIRES(m_GlobControlLock) + int checkQueuedSocketsEvents(const std::map& sockets); + void removePendingForGroup(const CUDTGroup* g); +#endif + + SRTSTATUS installAcceptHook(const SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); + SRTSTATUS installConnectHook(const SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); /// Check the status of the UDT socket. /// @param [in] u the UDT socket ID. @@ -296,22 +319,23 @@ class CUDTUnited // socket APIs - int bind(CUDTSocket* u, const sockaddr_any& name); - int bind(CUDTSocket* u, UDPSOCKET udpsock); - int listen(const SRTSOCKET u, int backlog); + SRTSTATUS bind(CUDTSocket* u, const sockaddr_any& name); + SRTSTATUS bind(CUDTSocket* u, UDPSOCKET udpsock); + SRTSTATUS listen(const SRTSOCKET u, int backlog); SRTSOCKET accept(const SRTSOCKET listen, sockaddr* addr, int* addrlen); SRTSOCKET accept_bond(const SRTSOCKET listeners[], int lsize, int64_t msTimeOut); - int connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int tarlen); - int connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); - int connectIn(CUDTSocket* s, const sockaddr_any& target, int32_t forced_isn); + SRTSOCKET connect(SRTSOCKET u, const sockaddr* srcname, const sockaddr* tarname, int tarlen); + SRTSOCKET connect(const SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + void connectIn(CUDTSocket* s, const sockaddr_any& target, int32_t forced_isn); #if ENABLE_BONDING - int groupConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG targets[], int arraysize); - int singleMemberConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG* target); + SRTSOCKET groupConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG targets[], int arraysize); + SRTSOCKET singleMemberConnect(CUDTGroup* g, SRT_SOCKGROUPCONFIG* target); #endif - int close(const SRTSOCKET u); - int close(CUDTSocket* s); + SRTSTATUS close(const SRTSOCKET u, int reason); + SRTSTATUS close(CUDTSocket* s, int reason); void getpeername(const SRTSOCKET u, sockaddr* name, int* namelen); void getsockname(const SRTSOCKET u, sockaddr* name, int* namelen); + void getsockdevname(const SRTSOCKET u, char* name, size_t* namelen); int select(UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout); int selectEx(const std::vector& fds, std::vector* readfds, @@ -319,22 +343,22 @@ class CUDTUnited std::vector* exceptfds, int64_t msTimeOut); int epoll_create(); - int epoll_clear_usocks(int eid); - int epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); - int epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events); - int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - int epoll_remove_usock(const int eid, const SRTSOCKET u); + void epoll_clear_usocks(int eid); + void epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + void epoll_add_usock_INTERNAL(const int eid, CUDTSocket* s, const int* events); + void epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + void epoll_remove_usock(const int eid, const SRTSOCKET u); template - int epoll_remove_entity(const int eid, EntityType* ent); - int epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* ent); + void epoll_remove_entity(const int eid, EntityType* ent); + void epoll_remove_socket_INTERNAL(const int eid, CUDTSocket* ent); #if ENABLE_BONDING - int epoll_remove_group_INTERNAL(const int eid, CUDTGroup* ent); + void epoll_remove_group_INTERNAL(const int eid, CUDTGroup* ent); #endif - int epoll_remove_ssock(const int eid, const SYSSOCKET s); - int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); + void epoll_remove_ssock(const int eid, const SYSSOCKET s); + void epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); int32_t epoll_set(const int eid, int32_t flags); - int epoll_release(const int eid); + void epoll_release(const int eid); #if ENABLE_BONDING SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_GlobControlLock) @@ -411,8 +435,8 @@ class CUDTUnited sync::Mutex m_IDLock; // used to synchronize ID generation - SRTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID - SRTSOCKET m_SocketIDGenerator_init; // Keeps track of the very first one + int32_t m_SocketIDGenerator; // seed to generate a new unique socket ID + int32_t m_SocketIDGenerator_init; // Keeps track of the very first one SRT_ATTR_GUARDED_BY(m_GlobControlLock) std::map > @@ -428,6 +452,8 @@ class CUDTUnited CUDTSocket* locateSocket_LOCKED(SRTSOCKET u); CUDTSocket* locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn); + int getMaxPayloadSize(SRTSOCKET u); + #if ENABLE_BONDING CUDTGroup* locateAcquireGroup(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); CUDTGroup* acquireSocketsGroup(CUDTSocket* s); @@ -513,6 +539,7 @@ class CUDTUnited void updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* = NULL); bool updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); + void removeMux(const int mid); // Utility functions for updateMux void configureMuxer(CMultiplexer& w_m, const CUDTSocket* s, int af); @@ -563,6 +590,25 @@ class CUDTUnited CEPoll m_EPoll; // handling epoll data structures and events + struct CloseInfo + { + SRT_CLOSE_INFO info; + int generation; + + // The value here defines how many GC rolls it takes + // to remove the record. As GC rolls every 1 second, + // this is more-less the number of seconds this record + // will be alive AFTER you close the socket. + CloseInfo(): info(), generation(MAX_CLOSE_RECORD_TTL) {} + }; + std::map m_ClosedDatabase; + + void checkTemporaryDatabases(); + void recordCloseReason(CUDTSocket* s); + +public: + SRTSTATUS getCloseReason(const SRTSOCKET u, SRT_CLOSE_INFO& info); + private: CUDTUnited(const CUDTUnited&); CUDTUnited& operator=(const CUDTUnited&); diff --git a/srtcore/atomic_clock.h b/srtcore/atomic_clock.h index e01012313..840d35252 100644 --- a/srtcore/atomic_clock.h +++ b/srtcore/atomic_clock.h @@ -73,6 +73,12 @@ class AtomicClock dur.store(uint64_t(d.time_since_epoch().count())); } + void compare_exchange(const time_point_type& exp, const time_point_type& toset) + { + uint64_t val = exp.time_since_epoch().count(); + dur.compare_exchange(val, toset.time_since_epoch().count()); + } + AtomicClock& operator=(const time_point_type& s) { dur = s.time_since_epoch().count(); diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index e74d48c07..aef13d047 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -75,22 +75,6 @@ namespace { #define IF_RCVBUF_DEBUG(instr) (void)0 - // Check if iFirstNonreadPos is in range [iStartPos, (iStartPos + iMaxPosOff) % iSize]. - // The right edge is included because we expect iFirstNonreadPos to be - // right after the last valid packet position if all packets are available. - bool isInRange(int iStartPos, int iMaxPosOff, size_t iSize, int iFirstNonreadPos) - { - if (iFirstNonreadPos == iStartPos) - return true; - - const int iLastPos = (iStartPos + iMaxPosOff) % iSize; - const bool isOverrun = iLastPos < iStartPos; - - if (isOverrun) - return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; - - return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; - } } @@ -119,13 +103,15 @@ CRcvBuffer::CRcvBuffer(int initSeqNo, size_t size, CUnitQueue* unitqueue, bool b : m_entries(size) , m_szSize(size) // TODO: maybe just use m_entries.size() , m_pUnitQueue(unitqueue) - , m_iStartSeqNo(initSeqNo) + , m_iStartSeqNo(initSeqNo) // NOTE: SRT_SEQNO_NONE is allowed here. , m_iStartPos(0) + , m_iEndOff(0) + , m_iDropOff(0) , m_iFirstNonreadPos(0) , m_iMaxPosOff(0) , m_iNotch(0) - , m_numOutOfOrderPackets(0) - , m_iFirstReadableOutOfOrder(-1) + , m_numNonOrderPackets(0) + , m_iFirstNonOrderMsgPos(CPos_TRAP) , m_bPeerRexmitFlag(true) , m_bMessageAPI(bMessageAPI) , m_iBytesCount(0) @@ -142,85 +128,352 @@ CRcvBuffer::~CRcvBuffer() { if (!it->pUnit) continue; - + m_pUnitQueue->makeUnitFree(it->pUnit); it->pUnit = NULL; } } -int CRcvBuffer::insert(CUnit* unit) +void CRcvBuffer::debugShowState(const char* source SRT_ATR_UNUSED) +{ + HLOGC(brlog.Debug, log << "RCV-BUF-STATE(" << source + << ") start=" << m_iStartPos + << " end=+" << m_iEndOff + << " drop=+" << m_iDropOff + << " max-off=+" << m_iMaxPosOff + << " seq[start]=%" << m_iStartSeqNo.val()); +} + +CRcvBuffer::InsertInfo CRcvBuffer::insert(CUnit* unit) { SRT_ASSERT(unit != NULL); const int32_t seqno = unit->m_Packet.getSeqNo(); - const int offset = CSeqNo::seqoff(m_iStartSeqNo, seqno); + const COff offset = COff(CSeqNo(seqno) - m_iStartSeqNo); IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::insert: seqno " << seqno); IF_RCVBUF_DEBUG(scoped_log.ss << " msgno " << unit->m_Packet.getMsgSeq(m_bPeerRexmitFlag)); IF_RCVBUF_DEBUG(scoped_log.ss << " m_iStartSeqNo " << m_iStartSeqNo << " offset " << offset); - if (offset < 0) + if (offset < COff(0)) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -2"); - return -2; + return InsertInfo(InsertInfo::BELATED); } + IF_HEAVY_LOGGING(string debug_source = "insert %" + Sprint(seqno)); - if (offset >= (int)capacity()) + if (offset >= COff(capacity())) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -3"); - return -3; + + InsertInfo ireport (InsertInfo::DISCREPANCY); + getAvailInfo((ireport)); + + IF_HEAVY_LOGGING(debugShowState((debug_source + " overflow").c_str())); + + return ireport; } // TODO: Don't do assert here. Process this situation somehow. // If >= 2, then probably there is a long gap, and buffer needs to be reset. SRT_ASSERT((m_iStartPos + offset) / m_szSize < 2); - const int pos = (m_iStartPos + offset) % m_szSize; + const CPos newpktpos = incPos(m_iStartPos, offset); + const COff prev_max_off = m_iMaxPosOff; + bool extended_end = false; if (offset >= m_iMaxPosOff) - m_iMaxPosOff = offset + 1; + { + m_iMaxPosOff = offset + COff(1); + extended_end = true; + } // Packet already exists - SRT_ASSERT(pos >= 0 && pos < int(m_szSize)); - if (m_entries[pos].status != EntryState_Empty) + // (NOTE: the above extension of m_iMaxPosOff is + // possible even before checking that the packet + // exists because existence of a packet beyond + // the current max position is not possible). + SRT_ASSERT(newpktpos >= 0 && newpktpos < int(m_szSize)); + if (m_entries[newpktpos].status != EntryState_Empty) { IF_RCVBUF_DEBUG(scoped_log.ss << " returns -1"); - return -1; + IF_HEAVY_LOGGING(debugShowState((debug_source + " redundant").c_str())); + return InsertInfo(InsertInfo::REDUNDANT); } - SRT_ASSERT(m_entries[pos].pUnit == NULL); + SRT_ASSERT(m_entries[newpktpos].pUnit == NULL); m_pUnitQueue->makeUnitTaken(unit); - m_entries[pos].pUnit = unit; - m_entries[pos].status = EntryState_Avail; + m_entries[newpktpos].pUnit = unit; + m_entries[newpktpos].status = EntryState_Avail; countBytes(1, (int)unit->m_Packet.getLength()); + // Set to a value, if due to insertion there was added + // a packet that is earlier to be retrieved than the earliest + // currently available packet. + time_point earlier_time = updatePosInfo(unit, prev_max_off, offset, extended_end); + + InsertInfo ireport (InsertInfo::INSERTED); + ireport.first_time = earlier_time; + // If packet "in order" flag is zero, it can be read out of order. // With TSBPD enabled packets are always assumed in order (the flag is ignored). if (!m_tsbpd.isEnabled() && m_bMessageAPI && !unit->m_Packet.getMsgOrderFlag()) { - ++m_numOutOfOrderPackets; - onInsertNotInOrderPacket(pos); + ++m_numNonOrderPackets; + onInsertNonOrderPacket(newpktpos); } updateNonreadPos(); + + // This updates only the first_seq and avail_range fields. + getAvailInfo((ireport)); + IF_RCVBUF_DEBUG(scoped_log.ss << " returns 0 (OK)"); - return 0; + IF_HEAVY_LOGGING(debugShowState((debug_source + " ok").c_str())); + + return ireport; +} + +void CRcvBuffer::getAvailInfo(CRcvBuffer::InsertInfo& w_if) +{ + // This finds the first possible available packet, which is + // preferably at cell 0, but if not available, try also with + // given fallback position, if it's set + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + const CPacket* pkt = &packetAt(m_iStartPos); + SRT_ASSERT(pkt); + w_if.avail_range = m_iEndOff; + w_if.first_seq = CSeqNo(pkt->getSeqNo()); + return; + } + + // If not the first position, probe the skipped positions: + // - for live mode, check the DROP position + // (for potential after-drop reading) + // - for message mode, check the non-order message position + // (for potential out-of-oder message delivery) + + const CPacket* pkt = NULL; + if (m_tsbpd.isEnabled()) + { + // With TSBPD you can rely on drop position, if set + // Drop position must point always to a valid packet. + // Drop position must start from +1; 0 means no drop. + if (m_iDropOff) + { + pkt = &packetAt(incPos(m_iStartPos, m_iDropOff)); + SRT_ASSERT(pkt); + } + } + else + { + // Message-mode: try non-order read position. + if (m_iFirstNonOrderMsgPos != CPos_TRAP) + { + pkt = &packetAt(m_iFirstNonOrderMsgPos); + SRT_ASSERT(pkt); + } + } + + if (!pkt) + { + // This is default, but set just in case + // The default seq is SRT_SEQNO_NONE. + w_if.avail_range = COff(0); + return; + } + + // TODO: we know that at least 1 packet is available, but only + // with m_iEndOff we know where the true range is. This could also + // be implemented for message mode, but still this would employ + // a separate begin-end range declared for a complete out-of-order + // message. + w_if.avail_range = COff(1); + w_if.first_seq = CSeqNo(pkt->getSeqNo()); +} + + +// This function is called exclusively after packet insertion. +// This will update also m_iEndOff and m_iDropOff fields (the latter +// regardless of the TSBPD mode). +CRcvBuffer::time_point CRcvBuffer::updatePosInfo(const CUnit* unit, const COff prev_max_off, + const COff offset, + const bool extended_end) +{ + time_point earlier_time; + + // Update flags + // Case [A]: insertion of the packet has extended the busy region. + if (extended_end) + { + // THIS means that the buffer WAS CONTIGUOUS BEFORE. + if (m_iEndOff == prev_max_off) + { + // THIS means that the new packet didn't CAUSE a gap + if (m_iMaxPosOff == prev_max_off + 1) + { + // This means that m_iEndOff now shifts by 1, + // and m_iDropOff is set to 0 as there's no gap. + m_iEndOff = m_iMaxPosOff; + m_iDropOff = 0; + } + else + { + // Otherwise we have a drop-after-gap candidate + // which is the currently inserted packet. + // Therefore m_iEndOff STAYS WHERE IT IS. + m_iDropOff = m_iMaxPosOff - 1; + } + } + } + // + // Since this place, every 'offset' is in the range + // between m_iEndOff (inclusive) and m_iMaxPosOff. + else if (offset == m_iEndOff) + { + // Case [D]: inserted a packet at the first gap following the + // contiguous region. This makes a potential to extend the + // contiguous region and we need to find its end. + + // If insertion happened at the very first packet, it is the + // new earliest packet now. In any other situation under this + // condition there's some contiguous packet range preceding + // this position. + if (m_iEndOff == 0) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + + updateGapInfo(); + } + else if (offset < m_iDropOff) + { + // Case [C]: the newly inserted packet precedes the + // previous earliest delivery position after drop, + // that is, there is now a "better" after-drop delivery + // candidate. + + // New position updated a valid packet on an earlier + // position than the drop position was before, although still + // following a gap. + // + // We know it because if the position has filled a gap following + // a valid packet, this preceding valid packet would be pointed + // by m_iDropOff, or it would point to some earlier packet in a + // contiguous series of valid packets following a gap, hence + // the above condition wouldn't be satisfied. + m_iDropOff = offset; + + // If there's an inserted packet BEFORE drop-pos (which makes it + // a new drop-pos), while the very first packet is absent (the + // below condition), it means we have a new earliest-available + // packet. Otherwise we would have only a newly updated drop + // position, but still following some earlier contiguous range + // of valid packets - so it's earlier than previous drop, but + // not earlier than the earliest packet. + if (m_iEndOff == 0) + { + earlier_time = getPktTsbPdTime(unit->m_Packet.getMsgTimeStamp()); + } + } + // OTHERWISE: case [B] in which nothing is to be updated. + + return earlier_time; } +// This function is called when the m_iEndOff has been set to a new +// position and the m_iDropOff should be calculated since that position again. +void CRcvBuffer::updateGapInfo() +{ + COff from = m_iEndOff; + SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iMaxPosOff)].status == EntryState_Empty); + + CPos pos = incPos(m_iStartPos, from); + + if (m_entries[pos].status == EntryState_Avail) + { + CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); + + for (; pos != end_pos; pos = incPos(pos)) + { + if (m_entries[pos].status != EntryState_Avail) + break; + } + + m_iEndOff = offPos(m_iStartPos, pos); + } + + // XXX This should be this way, but there are still inconsistencies + // in the message code. + //USE: SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status == EntryState_Empty); + SRT_ASSERT(m_entries[incPos(m_iStartPos, m_iEndOff)].status != EntryState_Avail); + + // XXX Controversy: m_iDropOff is only used in case when SRTO_TLPKTDROP + // is set. This option is not handled in message mode, only in live mode. + // Dropping by packet makes sense only in case of packetwise reading, + // which isn't the case of neither stream nor message mode. + if (!m_tsbpd.isEnabled()) + { + m_iDropOff = 0; + return; + } + + // Do not touch m_iDropOff if it's still beside the contiguous + // region. DO NOT SEARCH for m_iDropOff if m_iEndOff is max + // because this means that the whole buffer is contiguous. + // That would simply find nothing and only uselessly burden the + // performance by searching for a not present empty cell. + + // Also check if the current drop position is a readable packet. + // If not, start over. + CPos drop_pos = incPos(m_iStartPos, m_iDropOff); + + if (m_iDropOff < m_iEndOff || m_entries[drop_pos].status != EntryState_Avail) + { + m_iDropOff = 0; + if (m_iEndOff < m_iMaxPosOff) + { + CPos start = incPos(m_iStartPos, m_iEndOff + 1), + end = incPos(m_iStartPos, m_iEndOff); + + for (CPos i = start; i != end; i = incPos(i)) + { + if (m_entries[i].status == EntryState_Avail) + { + m_iDropOff = offPos(m_iStartPos, i); + break; + } + } + + // Must be found somewhere, worst case at the position + // of m_iMaxPosOff-1. If no finding loop caught it somehow, + // it will remain at 0. The case when you have empty packets + // in the busy range is only with message mode after reading + // packets out-of-order, but this doesn't use tsbpd mode. + SRT_ASSERT(m_iDropOff != 0); + } + } +} + +/// Request to remove from the receiver buffer +/// all packets with earlier sequence than @a seqno. +/// (Meaning, the packet with given sequence shall +/// be the first packet in the buffer after the operation). std::pair CRcvBuffer::dropUpTo(int32_t seqno) { IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropUpTo: seqno " << seqno << " m_iStartSeqNo " << m_iStartSeqNo); - int len = CSeqNo::seqoff(m_iStartSeqNo, seqno); + COff len = COff(CSeqNo(seqno) - m_iStartSeqNo); if (len <= 0) { IF_RCVBUF_DEBUG(scoped_log.ss << ". Nothing to drop."); return std::make_pair(0, 0); } - m_iMaxPosOff -= len; - if (m_iMaxPosOff < 0) - m_iMaxPosOff = 0; + m_iMaxPosOff = decOff(m_iMaxPosOff, len); + m_iEndOff = decOff(m_iEndOff, len); + m_iDropOff = decOff(m_iDropOff, len); int iNumDropped = 0; // Number of dropped packets that were missing. int iNumDiscarded = 0; // The number of dropped packets that existed in the buffer. @@ -229,9 +482,9 @@ std::pair CRcvBuffer::dropUpTo(int32_t seqno) // Note! Dropping a EntryState_Read must not be counted as a drop because it was read. // Note! Dropping a EntryState_Drop must not be counted as a drop because it was already dropped and counted earlier. if (m_entries[m_iStartPos].status == EntryState_Avail) - ++iNumDiscarded; + ++iNumDiscarded; else if (m_entries[m_iStartPos].status == EntryState_Empty) - ++iNumDropped; + ++iNumDropped; dropUnitInPos(m_iStartPos); m_entries[m_iStartPos].status = EntryState_Empty; SRT_ASSERT(m_entries[m_iStartPos].pUnit == NULL && m_entries[m_iStartPos].status == EntryState_Empty); @@ -240,19 +493,23 @@ std::pair CRcvBuffer::dropUpTo(int32_t seqno) } // Update positions - m_iStartSeqNo = seqno; + m_iStartSeqNo = CSeqNo(seqno); // Move forward if there are "read/drop" entries. + // (This call MAY shift m_iStartSeqNo further.) releaseNextFillerEntries(); + updateGapInfo(); + // If the nonread position is now behind the starting position, set it to the starting position and update. // Preceding packets were likely missing, and the non read position can probably be moved further now. - if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + if (!isInUsedRange(m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; updateNonreadPos(); } if (!m_tsbpd.isEnabled() && m_bMessageAPI) - updateFirstReadableOutOfOrder(); + updateFirstReadableNonOrder(); + IF_HEAVY_LOGGING(debugShowState(("drop %" + Sprint(seqno)).c_str())); return std::make_pair(iNumDropped, iNumDiscarded); } @@ -261,7 +518,7 @@ int CRcvBuffer::dropAll() if (empty()) return 0; - const int end_seqno = CSeqNo::incseq(m_iStartSeqNo, m_iMaxPosOff); + const int32_t end_seqno = CSeqNo::incseq(m_iStartSeqNo.val(), m_iMaxPosOff); const std::pair numDropped = dropUpTo(end_seqno); return numDropped.first + numDropped.second; } @@ -272,26 +529,30 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropMessage(): %(" << seqnolo << " - " << seqnohi << ")" << " #" << msgno << " actionOnExisting=" << actionOnExisting << " m_iStartSeqNo=%" << m_iStartSeqNo); + if (msgno < 0) // Note that only SRT_MSGNO_CONTROL is allowed in the protocol. + { + HLOGC(rbuflog.Error, log << "EPE: received UMSG_DROPREQ with msgflag field set to a negative value!"); + } // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. - const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); - const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi); + const int offset_a = CSeqNo(seqnolo) - m_iStartSeqNo; + const int offset_b = CSeqNo(seqnohi) - m_iStartSeqNo; if (offset_b < 0) { LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " - << seqnohi << "]. Buffer start " << m_iStartSeqNo << "."); + << seqnohi << "]. Buffer start " << m_iStartSeqNo.val() << "."); return 0; } const bool bKeepExisting = (actionOnExisting == KEEP_EXISTING); - int minDroppedOffset = -1; + COff minDroppedOffset (-1); int iDropCnt = 0; - const int start_off = max(0, offset_a); - const int start_pos = incPos(m_iStartPos, start_off); - const int end_off = min((int) m_szSize - 1, offset_b + 1); - const int end_pos = incPos(m_iStartPos, end_off); + const COff start_off = COff(max(0, offset_a)); + const CPos start_pos = incPos(m_iStartPos, start_off); + const COff end_off = COff(min((int) m_szSize - 1, offset_b + 1)); + const CPos end_pos = incPos(m_iStartPos, end_off); bool bDropByMsgNo = msgno > SRT_MSGNO_CONTROL; // Excluding both SRT_MSGNO_NONE (-1) and SRT_MSGNO_CONTROL (0). - for (int i = start_pos; i != end_pos; i = incPos(i)) + for (CPos i = start_pos; i != end_pos; i = incPos(i)) { // Check if the unit was already dropped earlier. if (m_entries[i].status == EntryState_Drop) @@ -306,7 +567,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro if (bKeepExisting && bnd == PB_SOLO) { bDropByMsgNo = false; // Solo packet, don't search for the rest of the message. - LOGC(rbuflog.Debug, + HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO packet %" << packetAt(i).getSeqNo() << "."); continue; @@ -332,6 +593,14 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro minDroppedOffset = offPos(m_iStartPos, i); } + if (end_off > m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "CRcvBuffer::dropMessage: requested to drop up to %" << seqnohi + << " with highest in the buffer %" << CSeqNo::incseq(m_iStartSeqNo.val(), end_off) + << " - updating the busy region"); + m_iMaxPosOff = end_off; + } + if (bDropByMsgNo) { // If msgno is specified, potentially not the whole message was dropped using seqno range. @@ -339,8 +608,8 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro // The sender should have the last packet of the message it is requesting to be dropped. // Therefore we don't search forward, but need to check earlier packets in the RCV buffer. // Try to drop by the message number in case the message starts earlier than @a seqnolo. - const int stop_pos = decPos(m_iStartPos); - for (int i = start_pos; i != stop_pos; i = decPos(i)) + const CPos stop_pos = decPos(m_iStartPos); + for (CPos i = start_pos; i != stop_pos; i = decPos(i)) { // Can't drop if message number is not known. if (!m_entries[i].pUnit) // also dropped earlier. @@ -349,7 +618,7 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro const PacketBoundary bnd = packetAt(i).getMsgBoundary(); const int32_t msgseq = packetAt(i).getMsgSeq(m_bPeerRexmitFlag); if (msgseq != msgno) - break; + break; if (bKeepExisting && bnd == PB_SOLO) { @@ -372,9 +641,26 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro IF_RCVBUF_DEBUG(scoped_log.ss << " iDropCnt " << iDropCnt); } + if (iDropCnt) + { + // We don't need the drop position, if we allow to drop messages by number + // and with that value we risk that drop was pointing to a dropped packet. + // Theoretically to make it consistent we need to shift the value to the + // next found packet, but we don't need this information if we use the message + // mode (because drop-by-packet is not supported in this mode) and this + // will burden the performance for nothing. + m_iDropOff = 0; + } + // Check if units before m_iFirstNonreadPos are dropped. const bool needUpdateNonreadPos = (minDroppedOffset != -1 && minDroppedOffset <= getRcvDataSize()); releaseNextFillerEntries(); + + updateGapInfo(); + + IF_HEAVY_LOGGING(debugShowState( + ("dropmsg off %" + Sprint(seqnolo) + " #" + Sprint(msgno)).c_str())); + if (needUpdateNonreadPos) { m_iFirstNonreadPos = m_iStartPos; @@ -382,24 +668,46 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro } if (!m_tsbpd.isEnabled() && m_bMessageAPI) { - if (!checkFirstReadableOutOfOrder()) - m_iFirstReadableOutOfOrder = -1; - updateFirstReadableOutOfOrder(); + if (!checkFirstReadableNonOrder()) + m_iFirstNonOrderMsgPos = CPos_TRAP; + updateFirstReadableNonOrder(); } + IF_HEAVY_LOGGING(debugShowState(("dropmsg off %" + Sprint(seqnolo)).c_str())); return iDropCnt; } -int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) +bool CRcvBuffer::getContiguousEnd(int32_t& w_seq) const +{ + if (m_iEndOff == 0) + { + // Initial contiguous region empty (including empty buffer). + HLOGC(rbuflog.Debug, log << "CONTIG: empty, give up base=%" << m_iStartSeqNo.val()); + w_seq = m_iStartSeqNo.val(); + return m_iMaxPosOff > 0; + } + + w_seq = CSeqNo::incseq(m_iStartSeqNo.val(), m_iEndOff); + + HLOGC(rbuflog.Debug, log << "CONTIG: endD=" << m_iEndOff + << " maxD=" << m_iMaxPosOff + << " base=%" << m_iStartSeqNo.val() + << " end=%" << w_seq); + + return (m_iEndOff < m_iMaxPosOff); +} + +int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl, pair* pw_seqrange) { const bool canReadInOrder = hasReadableInorderPkts(); - if (!canReadInOrder && m_iFirstReadableOutOfOrder < 0) + if (!canReadInOrder && m_iFirstNonOrderMsgPos == CPos_TRAP) { LOGC(rbuflog.Warn, log << "CRcvBuffer.readMessage(): nothing to read. Ignored isRcvDataReady() result?"); return 0; } - const int readPos = canReadInOrder ? m_iStartPos : m_iFirstReadableOutOfOrder; + const CPos readPos = canReadInOrder ? m_iStartPos : m_iFirstNonOrderMsgPos; + const bool isReadingFromStart = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed IF_RCVBUF_DEBUG(ScopedLog scoped_log); IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::readMessage. m_iStartSeqNo " << m_iStartSeqNo << " m_iStartPos " << m_iStartPos << " readPos " << readPos); @@ -408,8 +716,21 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) char* dst = data; int pkts_read = 0; int bytes_extracted = 0; // The total number of bytes extracted from the buffer. - const bool updateStartPos = (readPos == m_iStartPos); // Indicates if the m_iStartPos can be changed - for (int i = readPos;; i = incPos(i)) + + int32_t out_seqlo = SRT_SEQNO_NONE; + int32_t out_seqhi = SRT_SEQNO_NONE; + + // As we have a green light for reading, it is already known that + // we're going to either remove or extract packets from the buffer, + // so drop position won't count anymore. + // + // The END position should be updated, that is: + // - remain just updated by the shifted start position if it's still ahead + // - recalculated from 0 again otherwise + m_iDropOff = 0; + int nskipped = 0; + + for (CPos i = readPos;; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); if (!m_entries[i].pUnit) @@ -422,6 +743,11 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) const size_t pktsize = packet.getLength(); const int32_t pktseqno = packet.getSeqNo(); + if (out_seqlo == SRT_SEQNO_NONE) + out_seqlo = pktseqno; + + out_seqhi = pktseqno; + // unitsize can be zero const size_t unitsize = std::min(remain, pktsize); memcpy(dst, packet.m_pcData, unitsize); @@ -434,8 +760,8 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (m_tsbpd.isEnabled()) updateTsbPdTimeBase(packet.getMsgTimeStamp()); - if (m_numOutOfOrderPackets && !packet.getMsgOrderFlag()) - --m_numOutOfOrderPackets; + if (m_numNonOrderPackets && !packet.getMsgOrderFlag()) + --m_numNonOrderPackets; const bool pbLast = packet.getMsgBoundary() & PB_LAST; if (msgctrl && (packet.getMsgBoundary() & PB_FIRST)) @@ -450,12 +776,11 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) msgctrl->pktseq = pktseqno; releaseUnitInPos(i); - if (updateStartPos) + if (isReadingFromStart) { m_iStartPos = incPos(i); - --m_iMaxPosOff; - SRT_ASSERT(m_iMaxPosOff >= 0); - m_iStartSeqNo = CSeqNo::incseq(pktseqno); + m_iStartSeqNo = CSeqNo(pktseqno) + 1; + ++nskipped; } else { @@ -465,26 +790,44 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (pbLast) { - if (readPos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + if (readPos == m_iFirstNonOrderMsgPos) + { + m_iFirstNonOrderMsgPos = CPos_TRAP; + m_iDropOff = 0; // always set to 0 in this mode. + } break; } } + if (nskipped) + { + // This means that m_iStartPos HAS BEEN shifted by that many packets. + // Update offset variables + m_iMaxPosOff -= nskipped; + + // This is checked as the PB_LAST flag marked packet should still + // be extracted in the existing period. + SRT_ASSERT(m_iMaxPosOff >= 0); + + m_iEndOff = decOff(m_iEndOff, len); + } countBytes(-pkts_read, -bytes_extracted); releaseNextFillerEntries(); - if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + // This will update the end position + updateGapInfo(); + + if (!isInUsedRange(m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; //updateNonreadPos(); } if (!m_tsbpd.isEnabled()) - // We need updateFirstReadableOutOfOrder() here even if we are reading inorder, + // We need updateFirstReadableNonOrder() here even if we are reading inorder, // incase readable inorder packets are all read out. - updateFirstReadableOutOfOrder(); + updateFirstReadableNonOrder(); const int bytes_read = int(dst - data); if (bytes_read < bytes_extracted) @@ -494,6 +837,10 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) IF_RCVBUF_DEBUG(scoped_log.ss << " pldi64 " << *reinterpret_cast(data)); + if (pw_seqrange) + *pw_seqrange = make_pair(out_seqlo, out_seqhi); + + IF_HEAVY_LOGGING(debugShowState("readmsg")); return bytes_read; } @@ -527,8 +874,8 @@ namespace { int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) { - int p = m_iStartPos; - const int end_pos = m_iFirstNonreadPos; + CPos p = m_iStartPos; + const CPos end_pos = m_iFirstNonreadPos; const bool bTsbPdEnabled = m_tsbpd.isEnabled(); const steady_clock::time_point now = (bTsbPdEnabled ? steady_clock::now() : steady_clock::time_point()); @@ -538,7 +885,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) { if (!m_entries[p].pUnit) { - p = incPos(p); + // REDUNDANT? p = incPos(p); // Return abandons the loop anyway. LOGC(rbuflog.Error, log << "readBufferTo: IPE: NULL unit found in file transmission"); return -1; } @@ -554,7 +901,7 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) << " PKT TS=" << FormatTime(tsPlay)); if ((tsPlay > now)) - break; /* too early for this unit, return whatever was copied */ + break; // too early for this unit, return whatever was copied } const int pktlen = (int)pkt.getLength(); @@ -573,7 +920,10 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) m_iStartPos = p; --m_iMaxPosOff; SRT_ASSERT(m_iMaxPosOff >= 0); - m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + m_iEndOff = decOff(m_iEndOff, 1); + m_iDropOff = decOff(m_iDropOff, 1); + + m_iStartSeqNo = m_iStartSeqNo.inc(); } else m_iNotch += rs; @@ -588,16 +938,18 @@ int CRcvBuffer::readBufferTo(int len, copy_to_dst_f funcCopyToDst, void* arg) // Update positions // Set nonread position to the starting position before updating, // because start position was increased, and preceding packets are invalid. - if (!isInRange(m_iStartPos, m_iMaxPosOff, m_szSize, m_iFirstNonreadPos)) + if (!isInUsedRange( m_iFirstNonreadPos)) { m_iFirstNonreadPos = m_iStartPos; } if (iBytesRead == 0) { - LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); + LOGC(rbuflog.Error, log << "readBufferTo: 0 bytes read. m_iStartPos=" << m_iStartPos + << ", m_iFirstNonreadPos=" << m_iFirstNonreadPos); } + IF_HEAVY_LOGGING(debugShowState("readbuf")); return iBytesRead; } @@ -613,15 +965,12 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) bool CRcvBuffer::hasAvailablePackets() const { - return hasReadableInorderPkts() || (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + return hasReadableInorderPkts() || (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != CPos_TRAP); } int CRcvBuffer::getRcvDataSize() const { - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - - return int(m_szSize + m_iFirstNonreadPos - m_iStartPos); + return offPos(m_iStartPos, m_iFirstNonreadPos); } int CRcvBuffer::getTimespan_ms() const @@ -632,7 +981,7 @@ int CRcvBuffer::getTimespan_ms() const if (m_iMaxPosOff == 0) return 0; - int lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); + CPos lastpos = incPos(m_iStartPos, m_iMaxPosOff - 1); // Normally the last position should always be non empty // if TSBPD is enabled (reading out of order is not allowed). // However if decryption of the last packet fails, it may be dropped @@ -642,11 +991,11 @@ int CRcvBuffer::getTimespan_ms() const { lastpos = decPos(lastpos); } - + if (m_entries[lastpos].pUnit == NULL) return 0; - int startpos = m_iStartPos; + CPos startpos = m_iStartPos; while (m_entries[startpos].pUnit == NULL && startpos != lastpos) { startpos = incPos(startpos); @@ -676,33 +1025,42 @@ int CRcvBuffer::getRcvDataSize(int& bytes, int& timespan) const CRcvBuffer::PacketInfo CRcvBuffer::getFirstValidPacketInfo() const { - const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); - for (int i = m_iStartPos; i != end_pos; i = incPos(i)) - { - // TODO: Maybe check status? - if (!m_entries[i].pUnit) - continue; + // Default: no packet available. + PacketInfo pi = { SRT_SEQNO_NONE, false, time_point() }; - const CPacket& packet = packetAt(i); - const PacketInfo info = { packet.getSeqNo(), i != m_iStartPos, getPktTsbPdTime(packet.getMsgTimeStamp()) }; - return info; + const CPacket* pkt = NULL; + + // Very first packet available with no gap. + if (m_entries[m_iStartPos].status == EntryState_Avail) + { + SRT_ASSERT(m_entries[m_iStartPos].pUnit); + pkt = &packetAt(m_iStartPos); + } + // If not, get the information from the drop + else if (m_iDropOff) + { + CPos drop_pos = incPos(m_iStartPos, m_iDropOff); + SRT_ASSERT(m_entries[drop_pos].pUnit); + pkt = &packetAt(drop_pos); + pi.seq_gap = true; // Available, but after a drop. + } + else + { + // If none of them point to a valid packet, + // there is no packet available; + return pi; } - const PacketInfo info = { -1, false, time_point() }; - return info; + pi.seqno = pkt->getSeqNo(); + pi.tsbpd_time = getPktTsbPdTime(pkt->getMsgTimeStamp()); + return pi; } std::pair CRcvBuffer::getAvailablePacketsRange() const { - const int seqno_last = CSeqNo::incseq(m_iStartSeqNo, (int) countReadable()); - return std::pair(m_iStartSeqNo, seqno_last); -} - -size_t CRcvBuffer::countReadable() const -{ - if (m_iFirstNonreadPos >= m_iStartPos) - return m_iFirstNonreadPos - m_iStartPos; - return m_szSize + m_iFirstNonreadPos - m_iStartPos; + const COff nonread_off = offPos(m_iStartPos, m_iFirstNonreadPos); + const CSeqNo seqno_last = m_iStartSeqNo + nonread_off; + return std::pair(m_iStartSeqNo.val(), seqno_last.val()); } bool CRcvBuffer::isRcvDataReady(time_point time_now) const @@ -713,8 +1071,8 @@ bool CRcvBuffer::isRcvDataReady(time_point time_now) const if (haveInorderPackets) return true; - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - return (m_numOutOfOrderPackets > 0 && m_iFirstReadableOutOfOrder != -1); + SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); + return (m_numNonOrderPackets > 0 && m_iFirstNonOrderMsgPos != CPos_TRAP); } if (!haveInorderPackets) @@ -738,11 +1096,11 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no const PacketInfo info = {packet.getSeqNo(), false, time_point()}; return info; } - SRT_ASSERT((!m_bMessageAPI && m_numOutOfOrderPackets == 0) || m_bMessageAPI); - if (m_iFirstReadableOutOfOrder >= 0) + SRT_ASSERT((!m_bMessageAPI && m_numNonOrderPackets == 0) || m_bMessageAPI); + if (m_iFirstNonOrderMsgPos != CPos_TRAP) { - SRT_ASSERT(m_numOutOfOrderPackets > 0); - const CPacket& packet = packetAt(m_iFirstReadableOutOfOrder); + SRT_ASSERT(m_numNonOrderPackets > 0); + const CPacket& packet = packetAt(m_iFirstNonOrderMsgPos); const PacketInfo info = {packet.getSeqNo(), true, time_point()}; return info; } @@ -763,7 +1121,7 @@ CRcvBuffer::PacketInfo CRcvBuffer::getFirstReadablePacketInfo(time_point time_no int32_t CRcvBuffer::getFirstNonreadSeqNo() const { const int offset = offPos(m_iStartPos, m_iFirstNonreadPos); - return CSeqNo::incseq(m_iStartSeqNo, offset); + return m_iStartSeqNo.inc(offset).val(); } void CRcvBuffer::countBytes(int pkts, int bytes) @@ -780,7 +1138,7 @@ void CRcvBuffer::countBytes(int pkts, int bytes) } } -void CRcvBuffer::releaseUnitInPos(int pos) +void CRcvBuffer::releaseUnitInPos(CPos pos) { CUnit* tmp = m_entries[pos].pUnit; m_entries[pos] = Entry(); // pUnit = NULL; status = Empty @@ -788,7 +1146,7 @@ void CRcvBuffer::releaseUnitInPos(int pos) m_pUnitQueue->makeUnitFree(tmp); } -bool CRcvBuffer::dropUnitInPos(int pos) +bool CRcvBuffer::dropUnitInPos(CPos pos) { if (!m_entries[pos].pUnit) return false; @@ -798,27 +1156,49 @@ bool CRcvBuffer::dropUnitInPos(int pos) } else if (m_bMessageAPI && !packetAt(pos).getMsgOrderFlag()) { - --m_numOutOfOrderPackets; - if (pos == m_iFirstReadableOutOfOrder) - m_iFirstReadableOutOfOrder = -1; + --m_numNonOrderPackets; + if (pos == m_iFirstNonOrderMsgPos) + m_iFirstNonOrderMsgPos = CPos_TRAP; } releaseUnitInPos(pos); return true; } -void CRcvBuffer::releaseNextFillerEntries() +int CRcvBuffer::releaseNextFillerEntries() { - int pos = m_iStartPos; + CPos pos = m_iStartPos; + int nskipped = 0; + while (m_entries[pos].status == EntryState_Read || m_entries[pos].status == EntryState_Drop) { - m_iStartSeqNo = CSeqNo::incseq(m_iStartSeqNo); + if (nskipped == m_iMaxPosOff) + { + // This should never happen. All the previously read- or drop-marked + // packets should be contained in the range up to m_iMaxPosOff. Do not + // let the buffer ride any further and report the problem. Still stay there. + LOGC(rbuflog.Error, log << "releaseNextFillerEntries: IPE: Read/Drop status outside the busy range!"); + break; + } + + m_iStartSeqNo = m_iStartSeqNo.inc(); releaseUnitInPos(pos); pos = incPos(pos); m_iStartPos = pos; - --m_iMaxPosOff; - if (m_iMaxPosOff < 0) - m_iMaxPosOff = 0; + ++nskipped; + } + + if (!nskipped) + { + return nskipped; } + + m_iMaxPosOff -= nskipped; + m_iEndOff = decOff(m_iEndOff, nskipped); + + // Drop off will be updated after that call, if needed. + m_iDropOff = 0; + + return nskipped; } // TODO: Is this function complete? There are some comments left inside. @@ -827,21 +1207,25 @@ void CRcvBuffer::updateNonreadPos() if (m_iMaxPosOff == 0) return; - const int end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. + const CPos end_pos = incPos(m_iStartPos, m_iMaxPosOff); // The empty position right after the last valid entry. - int pos = m_iFirstNonreadPos; + CPos pos = m_iFirstNonreadPos; while (m_entries[pos].pUnit && m_entries[pos].status == EntryState_Avail) { if (m_bMessageAPI && (packetAt(pos).getMsgBoundary() & PB_FIRST) == 0) break; - for (int i = pos; i != end_pos; i = incPos(i)) + for (CPos i = pos; i != end_pos; i = incPos(i)) { if (!m_entries[i].pUnit || m_entries[pos].status != EntryState_Avail) { break; } + // m_iFirstNonreadPos is moved to the first position BEHIND + // the PB_LAST packet of the message. There's no guaratnee that + // the cell at this position isn't empty. + // Check PB_LAST only in message mode. if (!m_bMessageAPI || packetAt(i).getMsgBoundary() & PB_LAST) { @@ -857,9 +1241,9 @@ void CRcvBuffer::updateNonreadPos() } } -int CRcvBuffer::findLastMessagePkt() +CPos CRcvBuffer::findLastMessagePkt() { - for (int i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) + for (CPos i = m_iStartPos; i != m_iFirstNonreadPos; i = incPos(i)) { SRT_ASSERT(m_entries[i].pUnit); @@ -869,12 +1253,12 @@ int CRcvBuffer::findLastMessagePkt() } } - return -1; + return CPos_TRAP; } -void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) +void CRcvBuffer::onInsertNonOrderPacket(CPos insertPos) { - if (m_numOutOfOrderPackets == 0) + if (m_numNonOrderPackets == 0) return; // If the following condition is true, there is already a packet, @@ -883,7 +1267,7 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) // // There might happen that the packet being added precedes the previously found one. // However, it is allowed to re bead out of order, so no need to update the position. - if (m_iFirstReadableOutOfOrder >= 0) + if (m_iFirstNonOrderMsgPos != CPos_TRAP) return; // Just a sanity check. This function is called when a new packet is added. @@ -896,34 +1280,34 @@ void CRcvBuffer::onInsertNotInOrderPacket(int insertPos) //if ((boundary & PB_FIRST) && (boundary & PB_LAST)) //{ // // This packet can be read out of order - // m_iFirstReadableOutOfOrder = insertPos; + // m_iFirstNonOrderMsgPos = insertPos; // return; //} const int msgNo = pkt.getMsgSeq(m_bPeerRexmitFlag); // First check last packet, because it is expected to be received last. - const bool hasLast = (boundary & PB_LAST) || (-1 < scanNotInOrderMessageRight(insertPos, msgNo)); + const bool hasLast = (boundary & PB_LAST) || (scanNonOrderMessageRight(insertPos, msgNo) != CPos_TRAP); if (!hasLast) return; - const int firstPktPos = (boundary & PB_FIRST) + const CPos firstPktPos = (boundary & PB_FIRST) ? insertPos - : scanNotInOrderMessageLeft(insertPos, msgNo); - if (firstPktPos < 0) + : scanNonOrderMessageLeft(insertPos, msgNo); + if (firstPktPos == CPos_TRAP) return; - m_iFirstReadableOutOfOrder = firstPktPos; + m_iFirstNonOrderMsgPos = firstPktPos; return; } -bool CRcvBuffer::checkFirstReadableOutOfOrder() +bool CRcvBuffer::checkFirstReadableNonOrder() { - if (m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder < 0 || m_iMaxPosOff == 0) + if (m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos == CPos_TRAP || m_iMaxPosOff == COff(0)) return false; - const int endPos = incPos(m_iStartPos, m_iMaxPosOff); + const CPos endPos = incPos(m_iStartPos, m_iMaxPosOff); int msgno = -1; - for (int pos = m_iFirstReadableOutOfOrder; pos != endPos; pos = incPos(pos)) + for (CPos pos = m_iFirstNonOrderMsgPos; pos != endPos; pos = incPos(pos)) // ++pos) { if (!m_entries[pos].pUnit) return false; @@ -944,30 +1328,31 @@ bool CRcvBuffer::checkFirstReadableOutOfOrder() return false; } -void CRcvBuffer::updateFirstReadableOutOfOrder() +void CRcvBuffer::updateFirstReadableNonOrder() { - if (hasReadableInorderPkts() || m_numOutOfOrderPackets <= 0 || m_iFirstReadableOutOfOrder >= 0) + if (hasReadableInorderPkts() || m_numNonOrderPackets <= 0 || m_iFirstNonOrderMsgPos != CPos_TRAP) return; if (m_iMaxPosOff == 0) return; // TODO: unused variable outOfOrderPktsRemain? - int outOfOrderPktsRemain = (int) m_numOutOfOrderPackets; + int outOfOrderPktsRemain = (int) m_numNonOrderPackets; // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; + const CPos lastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); - int posFirst = -1; - int posLast = -1; + CPos posFirst = CPos_TRAP; + CPos posLast = CPos_TRAP; int msgNo = -1; - for (int pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) + for (CPos pos = m_iStartPos; outOfOrderPktsRemain; pos = incPos(pos)) { if (!m_entries[pos].pUnit) { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -975,7 +1360,8 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() if (pkt.getMsgOrderFlag()) // Skip in order packet { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } @@ -990,13 +1376,14 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { - posFirst = posLast = msgNo = -1; + posFirst = posLast = CPos_TRAP; + msgNo = -1; continue; } if (boundary & PB_LAST) { - m_iFirstReadableOutOfOrder = posFirst; + m_iFirstNonOrderMsgPos = posFirst; return; } @@ -1007,15 +1394,15 @@ void CRcvBuffer::updateFirstReadableOutOfOrder() return; } -int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const +CPos CRcvBuffer::scanNonOrderMessageRight(const CPos startPos, int msgNo) const { // Search further packets to the right. // First check if there are packets to the right. - const int lastPos = (m_iStartPos + m_iMaxPosOff - 1) % m_szSize; + const CPos lastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (startPos == lastPos) - return -1; + return CPos_TRAP; - int pos = startPos; + CPos pos = startPos; do { pos = incPos(pos); @@ -1027,7 +1414,7 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { LOGC(rbuflog.Error, log << "Missing PB_LAST packet for msgNo " << msgNo); - return -1; + return CPos_TRAP; } const PacketBoundary boundary = pkt.getMsgBoundary(); @@ -1035,30 +1422,30 @@ int CRcvBuffer::scanNotInOrderMessageRight(const int startPos, int msgNo) const return pos; } while (pos != lastPos); - return -1; + return CPos_TRAP; } -int CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const +CPos CRcvBuffer::scanNonOrderMessageLeft(const CPos startPos, int msgNo) const { // Search preceding packets to the left. // First check if there are packets to the left. if (startPos == m_iStartPos) - return -1; + return CPos_TRAP; - int pos = startPos; + CPos pos = startPos; do { pos = decPos(pos); if (!m_entries[pos].pUnit) - return -1; + return CPos_TRAP; const CPacket& pkt = packetAt(pos); if (pkt.getMsgSeq(m_bPeerRexmitFlag) != msgNo) { LOGC(rbuflog.Error, log << "Missing PB_FIRST packet for msgNo " << msgNo); - return -1; + return CPos_TRAP; } const PacketBoundary boundary = pkt.getMsgBoundary(); @@ -1066,7 +1453,7 @@ int CRcvBuffer::scanNotInOrderMessageLeft(const int startPos, int msgNo) const return pos; } while (pos != m_iStartPos); - return -1; + return CPos_TRAP; } bool CRcvBuffer::addRcvTsbPdDriftSample(uint32_t usTimestamp, const time_point& tsPktArrival, int usRTTSample) @@ -1104,11 +1491,11 @@ void CRcvBuffer::updateTsbPdTimeBase(uint32_t usPktTimestamp) m_tsbpd.updateBaseTime(usPktTimestamp); } -string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const +string CRcvBuffer::strFullnessState(int32_t iFirstUnackSeqNo, const time_point& tsNow) const { stringstream ss; - ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo + ss << "iFirstUnackSeqNo=" << iFirstUnackSeqNo << " m_iStartSeqNo=" << m_iStartSeqNo.val() << " m_iStartPos=" << m_iStartPos << " m_iMaxPosOff=" << m_iMaxPosOff << ". "; ss << "Space avail " << getAvailSize(iFirstUnackSeqNo) << "/" << m_szSize << " pkts. "; @@ -1120,7 +1507,7 @@ string CRcvBuffer::strFullnessState(int iFirstUnackSeqNo, const time_point& tsNo if (!is_zero(nextValidPkt.tsbpd_time)) { ss << count_milliseconds(nextValidPkt.tsbpd_time - tsNow) << "ms"; - const int iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); + const CPos iLastPos = incPos(m_iStartPos, m_iMaxPosOff - 1); if (m_entries[iLastPos].pUnit) { ss << ", timespan "; @@ -1169,4 +1556,90 @@ void CRcvBuffer::updRcvAvgDataSize(const steady_clock::time_point& now) m_mavg.update(now, pkts, bytes, timespan_ms); } +int32_t CRcvBuffer::getFirstLossSeq(int32_t fromseq, int32_t* pw_end) +{ + // This means that there are no lost seqs at all, no matter + // from which position they would have to be checked. + if (m_iEndOff == m_iMaxPosOff) + return SRT_SEQNO_NONE; + + COff offset = COff(CSeqNo(fromseq) - m_iStartSeqNo); + + // Check if it's still inside the buffer. + // Skip the region from 0 to m_iEndOff because this + // region is by definition contiguous and contains no loss. + if (offset < m_iEndOff || offset >= m_iMaxPosOff) + { + HLOGC(rbuflog.Debug, log << "getFirstLossSeq: offset=" << offset << " for %" << fromseq + << " (with max=" << m_iMaxPosOff << ") - NO LOSS FOUND"); + return SRT_SEQNO_NONE; + } + + // Check if this offset is equal to m_iEndOff. If it is, + // then you have the loss sequence exactly the one that + // was passed. Skip now, pw_end was not requested. + if (offset == m_iEndOff) + { + if (pw_end) + { + // If the offset is exactly at m_iEndOff, then + // m_iDropOff will mark the end of gap. + if (m_iDropOff) + *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), m_iDropOff); + else + { + LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: drop-off=0 while seq-off == end-off != max-off"); + *pw_end = fromseq; + } + } + return fromseq; + } + + int ret_seq = SRT_SEQNO_NONE; + int loss_off = 0; + // Now find the first empty position since here, + // up to m_iMaxPosOff. Checking against m_iDropOff + // makes no sense because if it is not 0, you'll + // find it earlier by checking packet presence. + for (int off = offset; off < m_iMaxPosOff; ++off) + { + CPos ipos ((m_iStartPos + off) % m_szSize); + if (m_entries[ipos].status == EntryState_Empty) + { + ret_seq = CSeqNo::incseq(m_iStartSeqNo.val(), off); + loss_off = off; + break; + } + } + + if (ret_seq == SRT_SEQNO_NONE) + { + // This is theoretically possible if we search from behind m_iEndOff, + // after m_iDropOff. This simply means that we are trying to search + // behind the last gap in the buffer. + return ret_seq; + } + + // We get this position, so search for the end of gap + if (pw_end) + { + for (int off = loss_off+1; off < m_iMaxPosOff; ++off) + { + CPos ipos ((m_iStartPos + off) % m_szSize); + if (m_entries[ipos].status != EntryState_Empty) + { + *pw_end = CSeqNo::incseq(m_iStartSeqNo.val(), off); + return ret_seq; + } + } + + // Should not be possible to not find an existing packet + // following the gap, otherwise there would be no gap. + LOGC(rbuflog.Error, log << "getFirstLossSeq: IPE: gap since %" << ret_seq << " not covered by existing packet"); + *pw_end = ret_seq; + } + return ret_seq; +} + + } // namespace srt diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index c5fca428b..80fe1d1ec 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -15,32 +15,390 @@ #include "common.h" #include "queue.h" #include "tsbpd_time.h" +#include "utilities.h" + +#define USE_WRAPPERS 0 +#define USE_OPERATORS 0 namespace srt { -/* - * Circular receiver buffer. - * - * |<------------------- m_szSize ---------------------------->| - * | |<------------ m_iMaxPosOff ----------->| | - * | | | | - * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - * | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] - * +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ - * | | - * | \__last pkt received - * | - * \___ m_iStartPos: first message to read - * - * m_pUnit[i]->status_: 0: free, 1: good, 2: read, 3: dropped (can be combined with read?) - * - * thread safety: - * start_pos_: CUDT::m_RecvLock - * first_unack_pos_: CUDT::m_AckLock - * max_pos_inc_: none? (modified on add and ack - * first_nonread_pos_: - */ +// DEVELOPMENT TOOL - TO BE MOVED ELSEWHERE (like common.h) + +// NOTE: This below series of definitions for CPos and COff +// are here for development support only, but they are not in +// use in the release code - there CPos and COff are aliases to int. +#if USE_WRAPPERS +struct CPos +{ + int value; +#if USE_OPERATORS + const size_t* psize; + int isize() const {return *psize;} +#endif + +#if USE_OPERATORS + explicit CPos(const size_t* ps SRT_ATR_UNUSED, int val) + : value(val) + , psize(ps) + {} + +#else + explicit CPos(int val): value(val) {} +#endif + + int val() const { return value; } + explicit operator int() const {return value;} + + CPos(const CPos& src): value(src.value) +#if USE_OPERATORS + , psize(src.psize) +#endif + {} + CPos& operator=(const CPos& src) + { +#if USE_OPERATORS + psize = src.psize; +#endif + value = src.value; + return *this; + } + +#if USE_OPERATORS + int cmp(CPos other, CPos start) const + { + int pos2 = value; + int pos1 = other.value; + + const int off1 = pos1 >= start.value ? pos1 - start.value : pos1 + start.isize() - start.value; + const int off2 = pos2 >= start.value ? pos2 - start.value : pos2 + start.isize() - start.value; + + return off2 - off1; + } + + CPos& operator--() + { + if (value == 0) + value = isize() - 1; + else + --value; + return *this; + } + + CPos& operator++() + { + ++value; + if (value == isize()) + value = 0; + return *this; + } +#endif + + bool operator == (CPos other) const { return value == other.value; } + bool operator != (CPos other) const { return value != other.value; } +}; + +struct COff +{ + int value; + explicit COff(int v): value(v) {} + COff& operator=(int v) { value = v; return *this; } + + int val() const { return value; } + explicit operator int() const {return value;} + + COff& operator--() { --value; return *this; } + COff& operator++() { ++value; return *this; } + + COff operator--(int) { int v = value; --value; return COff(v); } + COff operator++(int) { int v = value; ++value; return COff(v); } + + COff operator+(COff other) const { return COff(value + other.value); } + COff operator-(COff other) const { return COff(value - other.value); } + COff& operator+=(COff other) { value += other.value; return *this;} + COff& operator-=(COff other) { value -= other.value; return *this;} + + bool operator == (COff other) const { return value == other.value; } + bool operator != (COff other) const { return value != other.value; } + bool operator < (COff other) const { return value < other.value; } + bool operator > (COff other) const { return value > other.value; } + bool operator <= (COff other) const { return value <= other.value; } + bool operator >= (COff other) const { return value >= other.value; } + + // Exceptionally allow modifications of COff by a bare integer + COff operator+(int other) const { return COff(value + other); } + COff operator-(int other) const { return COff(value - other); } + COff& operator+=(int other) { value += other; return *this;} + COff& operator-=(int other) { value -= other; return *this;} + + bool operator == (int other) const { return value == other; } + bool operator != (int other) const { return value != other; } + bool operator < (int other) const { return value < other; } + bool operator > (int other) const { return value > other; } + bool operator <= (int other) const { return value <= other; } + bool operator >= (int other) const { return value >= other; } + + friend bool operator == (int value, COff that) { return value == that.value; } + friend bool operator != (int value, COff that) { return value != that.value; } + friend bool operator < (int value, COff that) { return value < that.value; } + friend bool operator > (int value, COff that) { return value > that.value; } + friend bool operator <= (int value, COff that) { return value <= that.value; } + friend bool operator >= (int value, COff that) { return value >= that.value; } + + operator bool() const { return value != 0; } +}; + +#if USE_OPERATORS + +inline CPos operator+(const CPos& pos, COff off) +{ + int val = pos.value + off.value; + while (val >= pos.isize()) + val -= pos.isize(); + return CPos(pos.psize, val); +} + +inline CPos operator-(const CPos& pos, COff off) +{ + int val = pos.value - off.value; + while (val < 0) + val += pos.isize(); + return CPos(pos.psize, val); +} + +// Should verify that CPos use the same size! +inline COff operator-(CPos later, CPos earlier) +{ + if (later.value < earlier.value) + return COff(later.value + later.isize() - earlier.value); + + return COff(later.value - earlier.value); +} + +inline CSeqNo operator+(CSeqNo seq, COff off) +{ + int32_t val = CSeqNo::incseq(seq.val(), off.val()); + return CSeqNo(val); +} + +inline CSeqNo operator-(CSeqNo seq, COff off) +{ + int32_t val = CSeqNo::decseq(seq.val(), off.val()); + return CSeqNo(val); +} + + +#endif +const CPos CPos_TRAP (-1); + +#else +typedef int CPos; +typedef int COff; +const int CPos_TRAP = -1; +#endif + +// +// Circular receiver buffer. +// +// ICR = Initial Contiguous Region: all cells here contain valid packets +// SCRAP REGION: Region with possibly filled or empty cells +// NOTE: in scrap region, the first cell is empty and the last one filled. +// SPARE REGION: Region without packets +// +// | BUSY REGION | +// | | | | +// | ICR | SCRAP REGION | SPARE REGION...-> +// ......->| | | | +// | /FIRST-GAP | | +// |<------------------- m_szSize ---------------------------->| +// | |<------------ m_iMaxPosOff ----------->| | +// | | | | | | +// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +// | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] +// +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +// | | | | +// | | | \__last pkt received +// |<------------->| m_iDropOff | +// | | | +// |<--------->| m_iEndOff | +// | +// \___ m_iStartPos: first packet position in the buffer +// +// m_pUnit[i]->status: +// EntryState_Empty: No packet was ever received here +// EntryState_Avail: The packet is ready for reading +// EntryState_Read: The packet is non-order-read +// EntryState_Drop: The packet was requested to drop +// +// thread safety: +// m_iStartPos: CUDT::m_RecvLock +// first_unack_pos_: CUDT::m_AckLock +// m_iMaxPosOff: none? (modified on add and ack +// m_iFirstNonreadPos: +// +// +// m_iStartPos: the first packet that should be read (might be empty) +// m_iEndOff: shift to the end of contiguous range. This points always to an empty cell. +// m_iDropPos: shift a packet available for retrieval after a drop. If 0, no such packet. +// +// Operational rules: +// +// Initially: +// m_iStartPos = 0 +// m_iEndOff = 0 +// m_iDropOff = 0 +// +// When a packet has arrived, then depending on where it landed: +// +// 1. Position: next to the last read one and newest +// +// m_iStartPos unchanged. +// m_iEndOff shifted by 1 +// m_iDropOff = 0 +// +// 2. Position: after a loss, newest. +// +// m_iStartPos unchanged. +// m_iEndOff unchanged. +// m_iDropOff: +// - set to this packet, if m_iDropOff == 0 or m_iDropOff is past this packet +// - otherwise unchanged +// +// 3. Position: after a loss, but belated (retransmitted) -- not equal to m_iEndPos +// +// m_iStartPos unchanged. +// m_iEndPos unchanged. +// m_iDropPos: +// - if m_iDropPos == m_iEndPos, set to this +// - if m_iDropPos %> this sequence, set to this +// - otherwise unchanged +// +// 4. Position: after a loss, sealing -- seq equal to position of m_iEndPos +// +// m_iStartPos unchanged. +// m_iEndPos: +// - since this position, search the first free cell +// - if reached the end of filled region (m_iMaxPosOff), stay there. +// m_iDropPos: +// - start from the value equal to m_iEndPos +// - walk at maximum to m_iMaxPosOff +// - find the first existing packet +// NOTE: +// If there are no "after gap" packets, then m_iMaxPosOff == m_iEndPos. +// If there is one existing packet, then one loss, then one packet, it +// should be that m_iEndPos = m_iStartPos %+ 1, m_iDropPos can reach +// to m_iStartPos %+ 2 position, and m_iMaxPosOff == m_iStartPos %+ 3. +// +// To wrap up: +// +// Let's say we have the following possibilities in a general scheme: +// +// +// [D] [C] [B] [A] (insertion cases) +// | (start) --- (end) ===[gap]=== (after-loss) ... (max-pos) | +// +// See the CRcvBuffer::updatePosInfo method for detailed implementation. +// +// WHEN INSERTING A NEW PACKET: +// +// If the incoming sequence maps to newpktpos that is: +// +// * newpktpos <% (start) : discard the packet and exit +// * newpktpos %> (size) : report discrepancy, discard and exit +// * newpktpos %> (start) and: +// * EXISTS: discard and exit (NOTE: could be also < (end)) +// [A]* seq == m_iMaxPosOff +// --> INC m_iMaxPosOff +// * m_iEndPos == previous m_iMaxPosOff +// * previous m_iMaxPosOff + 1 == m_iMaxPosOff +// --> m_iEndPos = m_iMaxPosOff +// --> m_iDropPos = m_iEndPos +// * otherwise (means the new packet caused a gap) +// --> m_iEndPos REMAINS UNCHANGED +// --> m_iDropPos = POSITION(m_iMaxPosOff) +// COMMENT: +// If this above condition isn't satisfied, then there are +// gaps, first at m_iEndPos, and m_iDropPos is at furthest +// equal to m_iMaxPosOff %- 1. The inserted packet is outside +// both the contiguous region and the following scratched region, +// so no updates on m_iEndPos and m_iDropPos are necessary. +// +// NOTE +// SINCE THIS PLACE seq cannot be a sequence of an existing packet, +// which means that earliest newpktpos == m_iEndPos, up to == m_iMaxPosOff -% 2. +// +// * otherwise (newpktpos <% max-pos): +// [D]* newpktpos == m_iEndPos: +// --> (search FIRST GAP and FIRST AFTER-GAP) +// --> m_iEndPos: increase until reaching m_iMaxPosOff +// * m_iEndPos <% m_iMaxPosOff: +// --> m_iDropPos = first VALID packet since m_iEndPos +% 1 +// * otherwise: +// --> m_iDropPos = m_iEndPos +// [B]* newpktpos %> m_iDropPos +// --> store, but do not update anything +// [C]* otherwise (newpktpos %> m_iEndPos && newpktpos <% m_iDropPos) +// --> store +// --> set m_iDropPos = newpktpos +// COMMENT: +// It is guaratneed that between m_iEndPos and m_iDropPos +// there is only a gap (series of empty cells). So wherever +// this packet lands, if it's next to m_iEndPos and before m_iDropPos +// it will be the only packet that violates the gap, hence this +// can be the only drop pos preceding the previous m_iDropPos. +// +// -- information returned to the caller should contain: +// 1. Whether adding to the buffer was successful. +// 2. Whether the "freshest" retrievable packet has been changed, that is: +// * in live mode, a newly added packet has earlier delivery time than one before +// * in stream mode, the newly added packet was at cell[0] +// * in message mode, if the newly added packet has: +// * completed the very first message +// * completed any message further than first that has out-of-order flag +// +// The information about a changed packet is important for the caller in +// live mode in order to notify the TSBPD thread. +// +// +// +// WHEN CHECKING A PACKET +// +// 1. Check the position at m_iStartPos. If there is a packet, +// return info at its position. +// +// 2. If position on m_iStartPos is empty, get the value of m_iDropPos. +// +// NOTE THAT: +// * if the buffer is empty, m_iDropPos == m_iStartPos and == m_iEndPos; +// note that m_iDropPos == m_iStartPos suffices to check that +// * if there is a packet in the buffer, but the first cell is empty, +// then m_iDropPos points to this packet, while m_iEndPos == m_iStartPos. +// Check then m_iStartPos == m_iEndPos to recognize it, and if then +// m_iDropPos isn't equal to them, you can read with dropping. +// * If cell[0] is valid, there could be only at worst cell[1] empty +// and cell[2] pointed by m_iDropPos. +// +// 3. In case of time-based checking for live mode, return empty packet info, +// if this packet's time is later than given time. +// +// WHEN EXTRACTING A PACKET +// +// 1. Extraction is only possible if there is a packet at cell[0]. +// 2. If there's no packet at cell[0], the application may request to +// drop up to the given packet, or drop the whole message up to +// the beginning of the next message. +// 3. In message mode, extraction can only extract a full message, so +// if there's no full message ready, nothing is extracted. +// 4. When the extraction region is defined, the m_iStartPos is shifted +// by the number of extracted packets. +// 5. If m_iEndPos <% m_iStartPos (after update), m_iEndPos should be +// set by searching from m_iStartPos up to m_iMaxPosOff for an empty cell. +// 6. m_iDropPos must be always updated. If m_iEndPos == m_iMaxPosOff, +// m_iDropPos is set to their value. Otherwise start from m_iEndPos +// and search a valid packet up to m_iMaxPosOff. +// 7. NOTE: m_iMaxPosOff is a delta, hence it must be set anew after update +// for m_iStartPos. +// class CRcvBuffer { @@ -53,16 +411,70 @@ class CRcvBuffer ~CRcvBuffer(); public: - /// Insert a unit into the buffer. - /// Similar to CRcvBuffer::addData(CUnit* unit, int offset) + + void debugShowState(const char* source); + + struct InsertInfo + { + enum Result { INSERTED = 0, REDUNDANT = -1, BELATED = -2, DISCREPANCY = -3 } result; + + // Below fields are valid only if result == INSERTED. Otherwise they have trap repro. + + CSeqNo first_seq; // sequence of the first available readable packet + time_point first_time; // Time of the new, earlier packet that appeared ready, or null-time if this didn't change. + COff avail_range; + + InsertInfo(Result r, int fp_seq = SRT_SEQNO_NONE, int range = 0, + time_point fp_time = time_point()) + : result(r), first_seq(fp_seq), first_time(fp_time), avail_range(range) + { + } + + InsertInfo() + : result(REDUNDANT), first_seq(SRT_SEQNO_NONE), avail_range(0) + { + } + + }; + + /// Inserts the unit with the data packet into the receiver buffer. + /// The result inform about the situation with the packet attempted + /// to be inserted and the readability of the buffer. /// - /// @param [in] unit pointer to a data unit containing new packet - /// @param [in] offset offset from last ACK point. + /// @param [PASS] unit The unit that should be placed in the buffer /// - /// @return 0 on success, -1 if packet is already in buffer, -2 if packet is before m_iStartSeqNo. - /// -3 if a packet is offset is ahead the buffer capacity. - // TODO: Previously '-2' also meant 'already acknowledged'. Check usage of this value. - int insert(CUnit* unit); + /// @return The InsertInfo structure where: + /// * result: the result of insertion, which is: + /// * INSERTED: successfully placed in the buffer + /// * REDUNDANT: not placed, the packet is already there + /// * BELATED: not placed, its sequence is in the past + /// * DISCREPANCY: not placed, the sequence is far future or OOTB + /// * first_seq: the earliest sequence number now avail for reading + /// * avail_range: how many packets are available for reading (1 if unknown) + /// * first_time: the play time of the earliest read-available packet + /// If there is no available packet for reading, first_seq == SRT_SEQNO_NONE. + /// + InsertInfo insert(CUnit* unit); + + time_point updatePosInfo(const CUnit* unit, const COff prev_max_off, const COff offset, const bool extended_end); + void getAvailInfo(InsertInfo& w_if); + + /// Update the values of `m_iEndPos` and `m_iDropPos` in + /// case when `m_iEndPos` was updated to a position of a + /// nonempty cell. + /// + /// This function should be called after having m_iEndPos + /// has somehow be set to position of a non-empty cell. + /// This can happen by two reasons: + /// + /// - the cell has been filled by incoming packet + /// - the value has been reset due to shifted m_iStartPos + /// + /// This means that you have to search for a new gap and + /// update the m_iEndPos and m_iDropPos fields, or set them + /// both to the end of range if there are no loss gaps. + /// + void updateGapInfo(); /// Drop packets in the receiver buffer from the current position up to the seqno (excluding seqno). /// @param [in] seqno drop units up to this sequence number @@ -98,16 +510,28 @@ class CRcvBuffer /// @return the number of packets actually dropped. int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting); + /// Extract the "expected next" packet sequence. + /// Extract the past-the-end sequence for the first packet + /// that is expected to arrive next with preserving the packet order. + /// If the buffer is empty or the very first cell is lacking a packet, + /// it returns the sequence assigned to the first cell. Otherwise it + /// returns the sequence representing the first empty cell (the next + /// cell to the last received packet, if there are no loss-holes). + /// @param [out] w_seq: returns the sequence (always valid) + /// @return true if this sequence is followed by any valid packets + bool getContiguousEnd(int32_t& w_seq) const; + /// Read the whole message from one or several packets. /// - /// @param [in,out] data buffer to write the message into. + /// @param [out] data buffer to write the message into. /// @param [in] len size of the buffer. - /// @param [in,out] message control data + /// @param [out,opt] message control data to be filled + /// @param [out,opt] pw_seqrange range of sequence numbers for packets belonging to the message /// /// @return actual number of bytes extracted from the buffer. /// 0 if nothing to read. /// -1 on failure. - int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL); + int readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl = NULL, std::pair* pw_seqrange = NULL); /// Read acknowledged data into a user buffer. /// @param [in, out] dst pointer to the target user buffer. @@ -123,24 +547,25 @@ class CRcvBuffer public: /// Get the starting position of the buffer as a packet sequence number. - int getStartSeqNo() const { return m_iStartSeqNo; } + int32_t getStartSeqNo() const { return m_iStartSeqNo.val(); } /// Sets the start seqno of the buffer. /// Must be used with caution and only when the buffer is empty. - void setStartSeqNo(int seqno) { m_iStartSeqNo = seqno; } + void setStartSeqNo(int32_t seqno) { m_iStartSeqNo = CSeqNo(seqno); } /// Given the sequence number of the first unacknowledged packet /// tells the size of the buffer available for packets. /// Effective returns capacity of the buffer minus acknowledged packet still kept in it. // TODO: Maybe does not need to return minus one slot now to distinguish full and empty buffer. - size_t getAvailSize(int iFirstUnackSeqNo) const + size_t getAvailSize(int32_t iFirstUnackSeqNo) const { // Receiver buffer allows reading unacknowledged packets. // Therefore if the first packet in the buffer is ahead of the iFirstUnackSeqNo // then it does not have acknowledged packets and its full capacity is available. // Otherwise subtract the number of acknowledged but not yet read packets from its capacity. - const int iRBufSeqNo = getStartSeqNo(); + const int32_t iRBufSeqNo = m_iStartSeqNo.val(); if (CSeqNo::seqcmp(iRBufSeqNo, iFirstUnackSeqNo) >= 0) // iRBufSeqNo >= iFirstUnackSeqNo + //if (iRBufSeqNo >= CSeqNo(iFirstUnackSeqNo)) { // Full capacity is available. return capacity(); @@ -198,11 +623,11 @@ class CRcvBuffer /// @note CSeqNo::seqoff(first, second) is 0 if nothing to read. std::pair getAvailablePacketsRange() const; - size_t countReadable() const; + int32_t getFirstLossSeq(int32_t fromseq, int32_t* opt_end = NULL); bool empty() const { - return (m_iMaxPosOff == 0); + return (m_iMaxPosOff == COff(0)); } /// Returns the currently used number of cells, including @@ -255,54 +680,104 @@ class CRcvBuffer const CUnit* peek(int32_t seqno); private: - inline int incPos(int pos, int inc = 1) const { return (pos + inc) % m_szSize; } - inline int decPos(int pos) const { return (pos - 1) >= 0 ? (pos - 1) : int(m_szSize - 1); } - inline int offPos(int pos1, int pos2) const { return (pos2 >= pos1) ? (pos2 - pos1) : int(m_szSize + pos2 - pos1); } + //* + CPos incPos(CPos pos, COff inc = COff(1)) const { return CPos((pos + inc) % m_szSize); } + CPos decPos(CPos pos) const { return (pos - 1) >= 0 ? CPos(pos - 1) : CPos(m_szSize - 1); } + COff offPos(CPos pos1, CPos pos2) const + { + int diff = pos2 - pos1; + if (diff >= 0) + { + return COff(diff); + } + return COff(m_szSize + diff); + } + + COff posToOff(CPos pos) const { return offPos(m_iStartPos, pos); } + + static COff decOff(COff val, int shift) + { + int ival = val - shift; + if (ival < 0) + return COff(0); + return COff(ival); + } /// @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 + inline COff cmpPos(CPos pos2, CPos pos1) const { // XXX maybe not the best implementation, but this keeps up to the rule. // Maybe use m_iMaxPosOff to ensure a position is not behind the m_iStartPos. - const int off1 = pos1 >= m_iStartPos ? pos1 - m_iStartPos : pos1 + (int)m_szSize - m_iStartPos; - const int off2 = pos2 >= m_iStartPos ? pos2 - m_iStartPos : pos2 + (int)m_szSize - m_iStartPos; - return off2 - off1; + return posToOff(pos2) - posToOff(pos1); + } + // */ + + // Check if iFirstNonreadPos is in range [iStartPos, (iStartPos + iMaxPosOff) % iSize]. + // The right edge is included because we expect iFirstNonreadPos to be + // right after the last valid packet position if all packets are available. + static bool isInRange(CPos iStartPos, COff iMaxPosOff, size_t iSize, CPos iFirstNonreadPos) + { + if (iFirstNonreadPos == iStartPos) + return true; + + const CPos iLastPos = CPos((iStartPos + iMaxPosOff) % int(iSize)); + const bool isOverrun = iLastPos < iStartPos; + + if (isOverrun) + return iFirstNonreadPos > iStartPos || iFirstNonreadPos <= iLastPos; + + return iFirstNonreadPos > iStartPos && iFirstNonreadPos <= iLastPos; + } + + bool isInUsedRange(CPos iFirstNonreadPos) + { + if (iFirstNonreadPos == m_iStartPos) + return true; + + // DECODE the iFirstNonreadPos + int diff = iFirstNonreadPos - m_iStartPos; + if (diff < 0) + diff += m_szSize; + + return diff <= m_iMaxPosOff; } // NOTE: Assumes that pUnit != NULL - CPacket& packetAt(int pos) { return m_entries[pos].pUnit->m_Packet; } - const CPacket& packetAt(int pos) const { return m_entries[pos].pUnit->m_Packet; } + CPacket& packetAt(CPos pos) { return m_entries[pos].pUnit->m_Packet; } + const CPacket& packetAt(CPos pos) const { return m_entries[pos].pUnit->m_Packet; } private: void countBytes(int pkts, int bytes); void updateNonreadPos(); - void releaseUnitInPos(int pos); + void releaseUnitInPos(CPos pos); /// @brief Drop a unit from the buffer. /// @param pos position in the m_entries of the unit to drop. /// @return false if nothing to drop, true if the unit was dropped successfully. - bool dropUnitInPos(int pos); + bool dropUnitInPos(CPos pos); /// Release entries following the current buffer position if they were already /// read out of order (EntryState_Read) or dropped (EntryState_Drop). - void releaseNextFillerEntries(); + /// + /// @return the range for which the start pos has been shifted + int releaseNextFillerEntries(); bool hasReadableInorderPkts() const { return (m_iFirstNonreadPos != m_iStartPos); } /// Find position of the last packet of the message. - int findLastMessagePkt(); + CPos findLastMessagePkt(); /// Scan for availability of out of order packets. - void onInsertNotInOrderPacket(int insertpos); - // Check if m_iFirstReadableOutOfOrder is still readable. - bool checkFirstReadableOutOfOrder(); - void updateFirstReadableOutOfOrder(); - int scanNotInOrderMessageRight(int startPos, int msgNo) const; - int scanNotInOrderMessageLeft(int startPos, int msgNo) const; + void onInsertNonOrderPacket(CPos insertpos); + // Check if m_iFirstNonOrderMsgPos is still readable. + bool checkFirstReadableNonOrder(); + void updateFirstReadableNonOrder(); + CPos scanNonOrderMessageRight(CPos startPos, int msgNo) const; + CPos scanNonOrderMessageLeft(CPos startPos, int msgNo) const; typedef bool copy_to_dst_f(char* data, int len, int dst_offset, void* arg); @@ -358,15 +833,20 @@ class CRcvBuffer const size_t m_szSize; // size of the array of units (buffer) CUnitQueue* m_pUnitQueue; // the shared unit queue - int m_iStartSeqNo; - int m_iStartPos; // the head position for I/O (inclusive) - int m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) - int m_iMaxPosOff; // the furthest data position - int m_iNotch; // the starting read point of the first unit + CSeqNo m_iStartSeqNo; + CPos m_iStartPos; // the head position for I/O (inclusive) + COff m_iEndOff; // past-the-end of the contiguous region since m_iStartOff + COff m_iDropOff; // points past m_iEndOff to the first deliverable after a gap, or == m_iEndOff if no such packet + CPos m_iFirstNonreadPos; // First position that can't be read (<= m_iLastAckPos) + COff m_iMaxPosOff; // the furthest data position + int m_iNotch; // index of the first byte to read in the first ready-to-read packet (used in file/stream mode) + + size_t m_numNonOrderPackets; // The number of stored packets with "inorder" flag set to false - size_t m_numOutOfOrderPackets; // The number of stored packets with "inorder" flag set to false - int m_iFirstReadableOutOfOrder; // In case of out ouf order packet, points to a position of the first such packet to - // read + /// Points to the first packet of a message that has out-of-order flag + /// and is complete (all packets from first to last are in the buffer). + /// If there is no such message in the buffer, it contains -1. + CPos m_iFirstNonOrderMsgPos; bool m_bPeerRexmitFlag; // Needed to read message number correctly const bool m_bMessageAPI; // Operation mode flag: message or stream. @@ -396,7 +876,7 @@ class CRcvBuffer /// Form a string of the current buffer fullness state. /// number of packets acknowledged, TSBPD readiness, etc. - std::string strFullnessState(int iFirstUnackSeqNo, const time_point& tsNow) const; + std::string strFullnessState(int32_t iFirstUnackSeqNo, const time_point& tsNow) const; private: CTsbpdTime m_tsbpd; diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index ef1bc498c..9661048b0 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -682,6 +682,18 @@ int CSndBuffer::dropLateData(int& w_bytes, int32_t& w_first_msgno, const steady_ return (dpkts); } +int CSndBuffer::dropAll(int& w_bytes) +{ + ScopedLock bufferguard(m_BufLock); + const int dpkts = m_iCount; + w_bytes = m_iBytesCount; + m_pFirstBlock = m_pCurrBlock = m_pLastBlock; + m_iCount = 0; + m_iBytesCount = 0; + updAvgBufSize(steady_clock::now()); + return dpkts; +} + void CSndBuffer::increase() { int unitsize = m_pBuffer->m_iSize; diff --git a/srtcore/buffer_snd.h b/srtcore/buffer_snd.h index afd52110b..9e47d483e 100644 --- a/srtcore/buffer_snd.h +++ b/srtcore/buffer_snd.h @@ -177,6 +177,7 @@ class CSndBuffer SRT_ATTR_EXCLUDES(m_BufLock) int dropLateData(int& bytes, int32_t& w_first_msgno, const time_point& too_late_time); + int dropAll(int& bytes); void updAvgBufSize(const time_point& time); int getAvgBufSize(int& bytes, int& timespan); diff --git a/srtcore/buffer_tools.cpp b/srtcore/buffer_tools.cpp index e809b952e..471fc0b5c 100644 --- a/srtcore/buffer_tools.cpp +++ b/srtcore/buffer_tools.cpp @@ -102,12 +102,12 @@ void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms); } -CRateEstimator::CRateEstimator(int /*family*/) +CRateEstimator::CRateEstimator(int family) : m_iInRatePktsCount(0) , m_iInRateBytesCount(0) , m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start) , m_iInRateBps(INPUTRATE_INITIAL_BYTESPS) - , m_iFullHeaderSize(CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE) + , m_iFullHeaderSize(CPacket::udpHeaderSize(family) + CPacket::HDR_SIZE) {} void CRateEstimator::setInputRateSmpPeriod(int period) @@ -273,5 +273,65 @@ int CSndRateEstimator::incSampleIdx(int val, int inc) const return val; } +CMovingRateEstimator::CMovingRateEstimator() + : m_tsFirstSampleTime(sync::steady_clock::now()) + , m_iCurSampleIdx(0) + , m_iRateBps(0) + , m_Samples(NUM_PERIODS) +{ + resetRate(0, NUM_PERIODS); } +void CMovingRateEstimator::addSample(int pkts, double bytes) +{ + const time_point now = steady_clock::now(); + const int iSampleDeltaIdx = int(count_milliseconds(now - m_tsLastSlotTimestamp) / SAMPLE_DURATION_MS); + + if (iSampleDeltaIdx == 0) + { + m_Samples[m_iCurSampleIdx].m_iBytesCount += bytes; + m_Samples[m_iCurSampleIdx].m_iPktsCount += pkts; + } + else + { + if ((m_iCurSampleIdx + iSampleDeltaIdx) < NUM_PERIODS) + resetRate(m_iCurSampleIdx + 1, m_iCurSampleIdx + iSampleDeltaIdx); + else + { + int loopbackDiff = m_iCurSampleIdx + iSampleDeltaIdx - NUM_PERIODS; + resetRate(m_iCurSampleIdx + 1, NUM_PERIODS); + resetRate(0, loopbackDiff); + } + + m_iCurSampleIdx = ((m_iCurSampleIdx + iSampleDeltaIdx) % NUM_PERIODS); + m_Samples[m_iCurSampleIdx].m_iBytesCount = bytes; + m_Samples[m_iCurSampleIdx].m_iPktsCount = pkts; + + m_tsLastSlotTimestamp += milliseconds_from(SAMPLE_DURATION_MS * iSampleDeltaIdx); + + computeAverageValue(); + } +} + +void CMovingRateEstimator::resetRate(int from, int to) +{ + for (int i = max(0, from); i < min(int(NUM_PERIODS), to); i++) + m_Samples[i].reset(); +} + +void CMovingRateEstimator::computeAverageValue() +{ + const time_point now = steady_clock::now(); + const int startDelta = count_milliseconds(now - m_tsFirstSampleTime); + const bool isFirstPeriod = startDelta < (SAMPLE_DURATION_MS * NUM_PERIODS); + int newRateBps = 0; + + for (int i = 0; i < NUM_PERIODS; i++) + newRateBps += (m_Samples[i].m_iBytesCount + (CPacket::HDR_SIZE * m_Samples[i].m_iPktsCount)); + + if (isFirstPeriod) + newRateBps = newRateBps * SAMPLE_DURATION_MS * NUM_PERIODS / max(1, startDelta); + + m_iRateBps = newRateBps; +} +} // namespace srt diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h index 88479827d..ebc5e6b79 100644 --- a/srtcore/buffer_tools.h +++ b/srtcore/buffer_tools.h @@ -192,10 +192,87 @@ class CSndRateEstimator Sample m_Samples[NUM_PERIODS]; - time_point m_tsFirstSampleTime; //< Start time of the first sample. + time_point m_tsFirstSampleTime; //< Start time of the first sameple. int m_iFirstSampleIdx; //< Index of the first sample. int m_iCurSampleIdx; //< Index of the current sample being collected. - int m_iRateBps; //< Rate in Bytes/sec. + int m_iRateBps; // Input Rate in Bytes/sec +}; + +class CMovingRateEstimator +{ + typedef sync::steady_clock::time_point time_point; + +public: + CMovingRateEstimator(); + + /// Add sample. + /// @param [in] pkts number of packets in the sample. + /// @param [in] bytes number of payload bytes in the sample. + void addSample(int pkts = 0, double bytes = 0); + + /// Clean the mobile measures table to reset average value. + void resetRate() { resetRate(0, NUM_PERIODS); }; + + /// Retrieve estimated bitrate in bytes per second with 16-byte packet header. + int getRate() const { return m_iRateBps; } + +private: + // We would like responsiveness (accuracy) of rate estimation higher than 100 ms + // (ideally around 50 ms) for network adaptive algorithms. + static const int NUM_PERIODS = 100; // To get 1s of values + static const int SAMPLE_DURATION_MS = 10; // 10 ms + time_point m_tsFirstSampleTime; //< Start time of the first sample. + time_point m_tsLastSlotTimestamp; // Used to compute the delta between 2 calls + int m_iCurSampleIdx; //< Index of the current sample being collected. + int m_iRateBps; //< Rate in Bytes/sec. + + struct Sample + { + int m_iPktsCount; // number of payload packets + int m_iBytesCount; // number of payload bytes + + void reset() + { + m_iPktsCount = 0; + m_iBytesCount = 0; + } + + Sample() + : m_iPktsCount(0) + , m_iBytesCount(0) + { + } + + Sample(int iPkts, int iBytes) + : m_iPktsCount(iPkts) + , m_iBytesCount(iBytes) + { + } + + Sample operator+(const Sample& other) + { + return Sample(m_iPktsCount + other.m_iPktsCount, m_iBytesCount + other.m_iBytesCount); + } + + Sample& operator+=(const Sample& other) + { + *this = *this + other; + return *this; + } + + bool empty() const { return m_iPktsCount == 0; } + }; + + srt::FixedArray m_Samples; // Table of stored data + + /// This method will compute the average value based on all table's measures and the period + /// (NUM_PERIODS*SAMPLE_DURATION_MS) + void computeAverageValue(); + + /// Reset a part of the stored measures + /// @param from The beginning where the reset have to be applied + /// @param to The last data that have to be reset + void resetRate(int from, int to); }; } // namespace srt diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 81dbc4d35..8b35bbfef 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -721,12 +721,19 @@ void srt::CChannel::getPeerAddr(sockaddr_any& w_addr) const w_addr.len = namelen; } -int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const sockaddr_any& source_addr SRT_ATR_UNUSED) const +int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const CNetworkInterface& source_ni SRT_ATR_UNUSED) const { #if ENABLE_HEAVY_LOGGING ostringstream dsrc; #ifdef SRT_ENABLE_PKTINFO - dsrc << " sourceIP=" << (m_bBindMasked && !source_addr.isany() ? source_addr.str() : "default"); + if (m_bBindMasked && !source_ni.address.isany()) + { + dsrc << " sourceNI=" << source_ni.str(); + } + else + { + dsrc << " sourceNI=default"; + } #endif LOGC(kslog.Debug, @@ -809,15 +816,15 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka // Note that even if PKTINFO is desired, the first caller's packet will be sent // without ancillary info anyway because there's no "peer" yet to know where to send it. char mh_crtl_buf[sizeof(CMSGNodeIPv4) + sizeof(CMSGNodeIPv6)]; - if (m_bBindMasked && source_addr.family() != AF_UNSPEC && !source_addr.isany()) + if (m_bBindMasked && source_ni.address.family() != AF_UNSPEC && !source_ni.address.isany()) { - if (!setSourceAddress(mh, mh_crtl_buf, source_addr)) + if (!setSourceAddress(mh, mh_crtl_buf, source_ni)) { - LOGC(kslog.Error, log << "CChannel::setSourceAddress: source address invalid family #" << source_addr.family() << ", NOT setting."); + LOGC(kslog.Error, log << "CChannel::setSourceAddress: source address invalid family #" << source_ni.address.family() << ", NOT setting."); } else { - HLOGC(kslog.Debug, log << "CChannel::setSourceAddress: setting as " << source_addr.str()); + HLOGC(kslog.Debug, log << "CChannel::setSourceAddress: setting as " << source_ni.str()); have_set_src = true; } } diff --git a/srtcore/channel.h b/srtcore/channel.h index e12310001..573554cb6 100644 --- a/srtcore/channel.h +++ b/srtcore/channel.h @@ -117,7 +117,7 @@ class CChannel /// @param [in] src source address to sent on an outgoing packet (if not ANY) /// @return Actual size of data sent. - int sendto(const sockaddr_any& addr, srt::CPacket& packet, const sockaddr_any& src) const; + int sendto(const sockaddr_any& addr, srt::CPacket& packet, const CNetworkInterface& src) const; /// Receive a packet from the channel and record the source address. /// @param [in] addr pointer to the source address. @@ -205,7 +205,7 @@ class CChannel cmsghdr hdr; }; - sockaddr_any getTargetAddress(const msghdr& msg) const + CNetworkInterface getTargetAddress(const msghdr& msg) const { // Loop through IP header messages cmsghdr* cmsg; @@ -219,33 +219,33 @@ class CChannel { in_pktinfo dest_ip; memcpy(&dest_ip, CMSG_DATA(cmsg), sizeof(struct in_pktinfo)); - return sockaddr_any(dest_ip.ipi_addr, 0); + return CNetworkInterface(dest_ip.ipi_addr, dest_ip.ipi_ifindex); } if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { in6_pktinfo dest_ip; memcpy(&dest_ip, CMSG_DATA(cmsg), sizeof(struct in6_pktinfo)); - return sockaddr_any(dest_ip.ipi6_addr, 0); + return CNetworkInterface(dest_ip.ipi6_addr, dest_ip.ipi6_ifindex); } } // Fallback for an error - return sockaddr_any(m_BindAddr.family()); + return CNetworkInterface(); } // IMPORTANT!!! This function shall be called EXCLUSIVELY just before // calling ::sendmsg function. It uses a static buffer to supply data // for the call, and it's stated that only one thread is trying to // use a CChannel object in sending mode. - bool setSourceAddress(msghdr& mh, char *buf, const sockaddr_any& adr) const + bool setSourceAddress(msghdr& mh, char *buf, const CNetworkInterface& ni) const { // In contrast to an advice followed on the net, there's no case of putting // both IPv4 and IPv6 ancillary data, case we could have them. Only one // IP version is used and it's the version as found in @a adr, which should // be the version used for binding. - if (adr.family() == AF_INET) + if (ni.address.family() == AF_INET) { mh.msg_control = (void *) buf; mh.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); @@ -254,16 +254,16 @@ class CChannel cmsg_send->cmsg_level = IPPROTO_IP; cmsg_send->cmsg_type = IP_PKTINFO; cmsg_send->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - + in_pktinfo pktinfo; - pktinfo.ipi_ifindex = 0; - pktinfo.ipi_spec_dst = adr.sin.sin_addr; + pktinfo.ipi_ifindex = ni.interface_index; + pktinfo.ipi_spec_dst = ni.address.sin.sin_addr; memcpy(CMSG_DATA(cmsg_send), &pktinfo, sizeof(in_pktinfo)); return true; } - if (adr.family() == AF_INET6) + if (ni.address.family() == AF_INET6) { mh.msg_control = buf; mh.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); @@ -274,8 +274,8 @@ class CChannel cmsg_send->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); in6_pktinfo* pktinfo = (in6_pktinfo*) CMSG_DATA(cmsg_send); - pktinfo->ipi6_ifindex = 0; - pktinfo->ipi6_addr = adr.sin6.sin6_addr; + pktinfo->ipi6_ifindex = ni.interface_index; + pktinfo->ipi6_addr = ni.address.sin6.sin6_addr; return true; } diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 2e94668ae..9c4ddf18d 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -60,6 +60,15 @@ modified by #include #include #include + +#if _WIN32 + #if SRT_ENABLE_LOCALIF_WIN32 + #include + #endif +#else + #include +#endif + #include "udt.h" #include "md5.h" #include "common.h" @@ -511,6 +520,82 @@ ostream& PrintEpollEvent(ostream& os, int events, int et_events) return os; } + +vector GetLocalInterfaces() +{ + vector locals; +#ifdef _WIN32 + // If not enabled, simply an empty local vector will be returned + #if SRT_ENABLE_LOCALIF_WIN32 + ULONG flags = GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_ALL_INTERFACES; + ULONG outBufLen = 0; + + // This function doesn't allocate memory by itself, you have to do it + // yourself, worst case when it's too small, the size will be corrected + // and the function will do nothing. So, simply, call the function with + // always too little 0 size and make it show the correct one. + GetAdaptersAddresses(AF_UNSPEC, flags, NULL, NULL, &outBufLen); + // Ignore errors. Check errors on the real call. + // (Have doubts about this "max" here, as VC reports errors when + // using std::max, so it will likely resolve to a macro - hope this + // won't cause portability problems, this code is Windows only. + + // Good, now we can allocate memory + PIP_ADAPTER_ADDRESSES pAddresses = (PIP_ADAPTER_ADDRESSES)::operator new(outBufLen); + ULONG st = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, pAddresses, &outBufLen); + if (st == ERROR_SUCCESS) + { + for (PIP_ADAPTER_ADDRESSES i = pAddresses; i; i = pAddresses->Next) + { + std::string name = i->AdapterName; + PIP_ADAPTER_UNICAST_ADDRESS pUnicast = pAddresses->FirstUnicastAddress; + while (pUnicast) + { + LocalInterface a; + if (pUnicast->Address.lpSockaddr) + a.addr = pUnicast->Address.lpSockaddr; + if (a.addr.len > 0) + { + // DO NOT collect addresses that are not of + // AF_INET or AF_INET6 family. + a.name = name; + locals.push_back(a); + } + pUnicast = pUnicast->Next; + } + } + } + + ::operator delete(pAddresses); + #endif + +#else + // Use POSIX method: getifaddrs + struct ifaddrs* pif, * pifa; + int st = getifaddrs(&pifa); + if (st == 0) + { + for (pif = pifa; pif; pif = pif->ifa_next) + { + LocalInterface i; + if (pif->ifa_addr) + i.addr = pif->ifa_addr; + if (i.addr.len > 0) + { + // DO NOT collect addresses that are not of + // AF_INET or AF_INET6 family. + i.name = pif->ifa_name ? pif->ifa_name : ""; + locals.push_back(i); + } + } + } + + freeifaddrs(pifa); +#endif + return locals; +} + + } // namespace srt namespace srt_logging @@ -548,7 +633,6 @@ std::string SockStatusStr(SRT_SOCKSTATUS s) return names.names[int(s)-1]; } -#if ENABLE_BONDING std::string MemberStatusStr(SRT_MEMBERSTATUS s) { if (int(s) < int(SRT_GST_PENDING) || int(s) > int(SRT_GST_BROKEN)) @@ -571,7 +655,6 @@ std::string MemberStatusStr(SRT_MEMBERSTATUS s) return names.names[int(s)]; } -#endif } // (end namespace srt_logging) diff --git a/srtcore/common.h b/srtcore/common.h index ff84c3faf..92af89993 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -54,6 +54,7 @@ modified by #define INC_SRT_COMMON_H #include +#include #include #include #ifndef _WIN32 @@ -95,14 +96,41 @@ modified by #define SRT_STATIC_ASSERT(cond, msg) #endif -#include +namespace srt +{ + +struct CNetworkInterface +{ + sockaddr_any address; + int interface_index; + + template + CNetworkInterface(const InAddrType& sa, int index) + : address(sa, 0) + , interface_index(index) + { + } + + CNetworkInterface() // blank fallback + : address(AF_UNSPEC) + , interface_index(0) + { + } + + std::string str() const + { + std::ostringstream buf; + buf << address.str() << "/" << interface_index; + return buf.str(); + } +}; + +} namespace srt_logging { std::string SockStatusStr(SRT_SOCKSTATUS s); -#if ENABLE_BONDING std::string MemberStatusStr(SRT_MEMBERSTATUS s); -#endif } namespace srt @@ -580,6 +608,8 @@ class CSeqNo explicit CSeqNo(int32_t v): value(v) {} + int32_t val() const { return value; } + // Comparison bool operator == (const CSeqNo& other) const { return other.value == value; } bool operator < (const CSeqNo& other) const @@ -683,26 +713,34 @@ class CSeqNo inline static int32_t incseq(int32_t seq) {return (seq == m_iMaxSeqNo) ? 0 : seq + 1;} + SRT_ATR_NODISCARD CSeqNo inc() const { return CSeqNo(incseq(value)); } + inline static int32_t decseq(int32_t seq) {return (seq == 0) ? m_iMaxSeqNo : seq - 1;} + SRT_ATR_NODISCARD CSeqNo dec() const { return CSeqNo(decseq(value)); } + inline static int32_t incseq(int32_t seq, int32_t inc) {return (m_iMaxSeqNo - seq >= inc) ? seq + inc : seq - m_iMaxSeqNo + inc - 1;} // m_iMaxSeqNo >= inc + sec --- inc + sec <= m_iMaxSeqNo // if inc + sec > m_iMaxSeqNo then return seq + inc - (m_iMaxSeqNo+1) + SRT_ATR_NODISCARD CSeqNo inc(int32_t i) const { return CSeqNo(incseq(value, i)); } + inline static int32_t decseq(int32_t seq, int32_t dec) { // Check if seq - dec < 0, but before it would have happened if ( seq < dec ) { int32_t left = dec - seq; // This is so many that is left after dragging dec to 0 - // So now decrement the (m_iMaxSeqNo+1) by "left" + // So now decrement the (m_iMaxSeqNo+1) by "left" return m_iMaxSeqNo - left + 1; } return seq - dec; } + SRT_ATR_NODISCARD CSeqNo dec(int32_t i) const { return CSeqNo(decseq(value, i)); } + static int32_t maxseq(int32_t seq1, int32_t seq2) { if (seqcmp(seq1, seq2) < 0) @@ -1438,6 +1476,14 @@ inline bool checkMappedIPv4(const sockaddr_in6& sa) std::string FormatLossArray(const std::vector< std::pair >& lra); std::ostream& PrintEpollEvent(std::ostream& os, int events, int et_events = 0); +struct LocalInterface +{ + sockaddr_any addr; + std::string name; +}; + +std::vector GetLocalInterfaces(); + } // namespace srt #endif diff --git a/srtcore/core.cpp b/srtcore/core.cpp index aa5495dbf..ec85f06ff 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -50,6 +50,9 @@ modified by Haivision Systems Inc. *****************************************************************************/ +// Set this to 5 to fake "lost" handshake packets 5 times in a row +#define SRT_ENABLE_FAKE_LOSS_HS 0 + #include "platform_sys.h" // Linux specific @@ -89,8 +92,8 @@ using namespace srt; using namespace srt::sync; using namespace srt_logging; -const SRTSOCKET UDT::INVALID_SOCK = srt::CUDT::INVALID_SOCK; -const int UDT::ERROR = srt::CUDT::ERROR; +const SRTSOCKET UDT::INVALID_SOCK = SRT_INVALID_SOCK; +const SRTSTATUS UDT::ERROR = SRT_ERROR; //#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */ #define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */ @@ -273,13 +276,14 @@ void srt::CUDT::construct() m_pSndQueue = NULL; m_pRcvQueue = NULL; + m_TransferIPVersion = AF_UNSPEC; // Will be set after connection m_pSNode = NULL; m_pRNode = NULL; // Will be reset to 0 for HSv5, this value is important for HSv4. m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; - m_PeerID = 0; + m_PeerID = SRT_SOCKID_CONNREQ; m_bOpened = false; m_bListening = false; m_bConnecting = false; @@ -904,7 +908,7 @@ string srt::CUDT::getstreamid(SRTSOCKET u) // Initial sequence number, loss, acknowledgement, etc. void srt::CUDT::clearData() { - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE - CPacket::HDR_SIZE; + const size_t full_hdr_size = CPacket::udpHeaderSize(AF_INET) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); @@ -1341,7 +1345,7 @@ size_t srt::CUDT::fillHsExtGroup(uint32_t* pcmdspec) | SrtHSRequest::HS_GROUP_FLAGS::wrap(flags) | SrtHSRequest::HS_GROUP_WEIGHT::wrap(m_parent->m_GroupMemberData->weight); - const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(id), dataword }; + const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(int(id)), dataword }; memcpy((space), storedata, sizeof storedata); const size_t ra_size = Size(storedata); @@ -3108,7 +3112,7 @@ bool srt::CUDT::checkApplyFilterConfig(const std::string &confstr) // XXX Using less maximum payload size of IPv4 and IPv6; this is only about the payload size // for live. - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size; + size_t efc_max_payload_size = SRT_MAX_PLSIZE_AF_INET6 - cfg.extra_size; if (m_config.zExpPayloadSize > efc_max_payload_size) { LOGC(cnlog.Warn, @@ -3129,7 +3133,7 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A // for consistency and possibly changes in future. // We are granted these two fields do exist - SRTSOCKET grpid = groupdata[GRPD_GROUPID]; + SRTSOCKET grpid = SRTSOCKET(groupdata[GRPD_GROUPID]); uint32_t gd = groupdata[GRPD_GROUPDATA]; SRT_GROUP_TYPE gtp = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd)); @@ -3152,7 +3156,7 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A return false; } - if ((grpid & SRTGROUP_MASK) == 0) + if (!isgroup(grpid)) { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, log << CONID() << "HS/GROUP: socket ID passed as a group ID is not a group ID"); @@ -3222,7 +3226,7 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A } SRTSOCKET peer = pg->peerid(); - if (peer == -1) + if (peer == SRT_INVALID_SOCK) { // This is the first connection within this group, so this group // has just been informed about the peer membership. Accept it. @@ -3264,10 +3268,10 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A // ID to the peer. SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags); - if (!lgid) + if (lgid == SRT_SOCKID_CONNREQ) return true; // already done - if (lgid == -1) + if (lgid == SRT_INVALID_SOCK) { // NOTE: This error currently isn't reported by makeMePeerOf, // so this is left to handle a possible error introduced in future. @@ -3324,7 +3328,7 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 LOGC(gmlog.Error, log << CONID() << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp << " agent group=$" << gp->id() << " type" << gp->type()); - return -1; + return SRT_INVALID_SOCK; } HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id()); @@ -3341,14 +3345,14 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 catch (...) { // Expected exceptions are only those referring to system resources - return -1; + return SRT_INVALID_SOCK; } if (!gp->applyFlags(link_flags, m_SrtHsSide)) { // Wrong settings. Must reject. Delete group. uglobal().deleteGroup_LOCKED(gp); - return -1; + return SRT_INVALID_SOCK; } gp->set_peerid(peergroup); @@ -3357,7 +3361,8 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 // This can only happen on a listener (it's only called on a site that is // HSD_RESPONDER), so it was a response for a groupwise connection. // Therefore such a group shall always be considered opened. - gp->setOpen(); + // It's also set pending and it stays this way until accepted. + gp->setOpenPending(); HLOGC(gmlog.Debug, log << CONID() << "makeMePeerOf: no group has peer=$" << peergroup << " - creating new mirror group $" @@ -3392,7 +3397,7 @@ SRTSOCKET srt::CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint3 LOGC(gmlog.Error, log << CONID() << "IPE (non-fatal): the socket is in the group, but has no clue about it!"); s->m_GroupOf = gp; s->m_GroupMemberData = f; - return 0; + return SRT_SOCKID_CONNREQ; } s->m_GroupMemberData = gp->add(groups::prepareSocketData(s)); @@ -3614,7 +3619,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // control of methods.) // ID = 0, connection request - reqpkt.set_id(0); + reqpkt.set_id(SRT_SOCKID_CONNREQ); size_t hs_size = m_iMaxSRTPayloadSize; m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); @@ -3679,9 +3684,9 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) EConnectStatus cst = CONN_CONTINUE; // This is a temporary place to store the DESTINATION IP from the incoming packet. // We can't record this address yet until the cookie-confirmation is done, for safety reasons. - sockaddr_any use_source_adr(serv_addr.family()); + CNetworkInterface use_source_adr; - while (!m_bClosing) + while (!m_bClosing && !m_bBroken) { const steady_clock::time_point local_tnow = steady_clock::now(); const steady_clock::duration tdiff = local_tnow - m_tsLastReqTime.load(); @@ -3788,7 +3793,9 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) { HLOGC(cnlog.Debug, log << CONID() << "startConnect: REJECTED by processConnectResponse - sending SHUTDOWN"); - sendCtrl(UMSG_SHUTDOWN); + setAgentCloseReason(SRT_CLS_LATE); + uint32_t reason[1] = { SRT_CLS_LATE }; + sendCtrl(UMSG_SHUTDOWN, NULL, reason, sizeof reason); } if (cst != CONN_CONTINUE && cst != CONN_CONFUSED) @@ -3949,7 +3956,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, log << CONID() << "processAsyncConnectRequest: REQ-TIME: HIGH. Should prevent too quick responses."); m_tsLastReqTime = now; // ID = 0, connection request - reqpkt.set_id(!m_config.bRendezvous ? 0 : m_ConnRes.m_iID); + reqpkt.set_id(!m_config.bRendezvous ? SRT_SOCKID_CONNREQ : m_ConnRes.m_iID); bool status = true; @@ -4748,15 +4755,33 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT return false; } + m_TransferIPVersion = m_PeerAddr.family(); + if (m_PeerAddr.family() == AF_INET6) + { + // Check if the m_PeerAddr's address is a mapped IPv4. If so, + // define Transfer IP version as 4 because this one will be used. + if (checkMappedIPv4(m_PeerAddr.sin6)) + m_TransferIPVersion = AF_INET; + } + // Re-configure according to the negotiated values. m_config.iMSS = m_ConnRes.m_iMSS; - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; + const size_t full_hdr_size = CPacket::udpHeaderSize(m_TransferIPVersion) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; - HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + if (m_iMaxSRTPayloadSize < int(m_config.zExpPayloadSize)) + { + LOGC(cnlog.Error, log << CONID() << "applyResponseSettings: negotiated MSS=" << m_config.iMSS + << " leaves too little payload space " << m_iMaxSRTPayloadSize << " for configured payload size " + << m_config.zExpPayloadSize); + m_RejectReason = SRT_REJ_CONFIG; + return false; + } + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; - const int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; + const int udpsize = m_config.iMSS - CPacket::udpHeaderSize(m_TransferIPVersion); m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; m_iPeerISN = m_ConnRes.m_iISN; @@ -4782,6 +4807,16 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, if (m_ConnRes.m_iVersion < HS_VERSION_SRT1) m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly in SRT HS. + // This part fictionally "loses" incoming conclusion HS given number of times. +#if SRT_ENABLE_FAKE_LOSS_HS > 0 + static int fail_count = SRT_ENABLE_FAKE_LOSS_HS; + if (--fail_count) + { + LOGC(cnlog.Note, log << "postConnect: FAKE LOSS HS conclusion message"); + return CONN_CONTINUE; + } +#endif + // This procedure isn't being executed in rendezvous because // in rendezvous it's completed before calling this function. if (!rendezvous) @@ -5747,16 +5782,31 @@ bool srt::CUDT::prepareBuffers(CUDTException* eout) try { + // XXX SND buffer may allocate more memory, but must set the size of a single + // packet that fits the transmission for the overall connection. For any mixed 4-6 + // connection it should be the less size, that is, for IPv6 + // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. const int authtag = getAuthTagSize(); SRT_ASSERT(m_iMaxSRTPayloadSize != 0); - - HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << m_iMaxSRTPayloadSize - << " snd-bufsize=" << 32 + SRT_ASSERT(m_TransferIPVersion != AF_UNSPEC); + // IMPORTANT: + // The m_iMaxSRTPayloadSize is the size of the payload in the "SRT packet" that can be sent + // over the current connection - which means that if both parties are IPv6, then the maximum size + // is the one for IPv6 (1444). If any party is IPv4, this maximum size is 1456. + // The family as the first argument is something different - it's for the header size in order + // to calculate rate and statistics. + + int snd_payload_size = m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(m_TransferIPVersion); + SRT_ASSERT(m_iMaxSRTPayloadSize <= snd_payload_size); + + HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << snd_payload_size + << " snd-bufsize=" << 32 << " TF-IPv" + << (m_TransferIPVersion == AF_INET6 ? "6" : m_TransferIPVersion == AF_INET ? "4" : "???") << " authtag=" << authtag); - m_pSndBuffer = new CSndBuffer(AF_INET, 32, m_iMaxSRTPayloadSize, authtag); + m_pSndBuffer = new CSndBuffer(m_TransferIPVersion, 32, snd_payload_size, authtag); SRT_ASSERT(m_iPeerISN != -1); m_pRcvBuffer = new srt::CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pRcvQueue->m_pUnitQueue, m_config.bMessageAPI); // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space. @@ -5804,8 +5854,16 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // Uses the smaller MSS between the peers m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; + const size_t full_hdr_size = CPacket::udpHeaderSize(m_TransferIPVersion) + CPacket::HDR_SIZE; m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; + if (m_iMaxSRTPayloadSize < int(m_config.zExpPayloadSize)) + { + LOGC(cnlog.Error, log << CONID() << "acceptAndRespond: negotiated MSS=" << m_config.iMSS + << " leaves too little payload space " << m_iMaxSRTPayloadSize << " for configured payload size " + << m_config.zExpPayloadSize); + m_RejectReason = SRT_REJ_CONFIG; + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + } HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); @@ -5830,6 +5888,15 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& rewriteHandshakeData(peer, (w_hs)); + m_TransferIPVersion = peer.family(); + if (peer.family() == AF_INET6) + { + // Check if the peer's address is a mapped IPv4. If so, + // define Transfer IP version as 4 because this one will be used. + if (checkMappedIPv4(peer.sin6)) + m_TransferIPVersion = AF_INET; + } + // Prepare all structures if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) @@ -5953,6 +6020,21 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // Save the handshake in m_ConnRes in case when needs repeating. m_ConnRes = w_hs; + // NOTE: UNBLOCK THIS instruction in order to cause the final + // handshake to be missed and cause the problem solved in PR #417. + // When missed this message, the caller should not accept packets + // coming as connected, but continue repeated handshake until finally + // received the listener's handshake. + //return; + + if (!createSendHSResponse(kmdata, kmdatasize, hspkt.udpDestAddr(), (w_hs))) + { + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + } +} + +bool CUDT::createSendHSResponse(uint32_t* kmdata, size_t kmdatasize, const CNetworkInterface& hsaddr, CHandShake& w_hs) ATR_NOTHROW +{ // Send the response to the peer, see listen() for more discussions // about this. // TODO: Here create CONCLUSION RESPONSE with: @@ -5967,16 +6049,15 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // This will serialize the handshake according to its current form. HLOGC(cnlog.Debug, - log << CONID() - << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); + log << CONID() << "createSendHSResponse: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (rsppkt), (w_hs))) { - LOGC(cnlog.Error, log << CONID() << "acceptAndRespond: error creating handshake response"); - throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + LOGC(cnlog.Error, log << CONID() << "createSendHSResponse: error creating handshake response"); + return false; } // We can safely assign it here stating that this has passed the cookie test. - m_SourceAddr = hspkt.udpDestAddr(); + m_SourceAddr = hsaddr; #if ENABLE_HEAVY_LOGGING { @@ -5985,8 +6066,8 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& CHandShake debughs; debughs.load_from(rsppkt.m_pcData, rsppkt.getLength()); HLOGC(cnlog.Debug, - log << CONID() << "acceptAndRespond: sending HS from agent @" - << debughs.m_iID << " to peer @" << rsppkt.id() + log << CONID() << "createSendHSResponse: sending HS from agent @" + << debughs.m_iID << " to peer @" << m_PeerID // <-- will be used by addressAndSend << "HS:" << debughs.show() << " sourceIP=" << m_SourceAddr.str()); } @@ -5998,6 +6079,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // coming as connected, but continue repeated handshake until finally // received the listener's handshake. addressAndSend((rsppkt)); + return true; } bool srt::CUDT::frequentLogAllowed(size_t logid, const time_point& tnow, std::string& w_why) @@ -6258,7 +6340,7 @@ void srt::CUDT::addressAndSend(CPacket& w_pkt) // [[using maybe_locked(m_GlobControlLock, if called from breakSocket_LOCKED, usually from GC)]] // [[using maybe_locked(m_parent->m_ControlLock, if called from srt_close())]] -bool srt::CUDT::closeInternal() ATR_NOEXCEPT +bool srt::CUDT::closeInternal(int reason) ATR_NOEXCEPT { // NOTE: this function is called from within the garbage collector thread. @@ -6310,6 +6392,14 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT } } + // Some calls of closeInternal pass UNKNOWN here, which means + // that they don't want to change the code. It should have been + // set already somewhere else, however. + if (reason != SRT_CLS_UNKNOWN) + { + setAgentCloseReason(reason); + } + // remove this socket from the snd queue if (m_bConnected) m_pSndQueue->m_pSndUList->remove(this); @@ -6322,42 +6412,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT * it would remove the socket from the EPoll after close. */ - // Make a copy under a lock because other thread might access it - // at the same time. - enterCS(uglobal().m_EPoll.m_EPollLock); - set epollid = m_sPollID; - leaveCS(uglobal().m_EPoll.m_EPollLock); - - // trigger any pending IO events. - HLOGC(smlog.Debug, log << CONID() << "close: SETTING ERR readiness on E" << Printable(epollid)); - uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true); - // then remove itself from all epoll monitoring - int no_events = 0; - for (set::iterator i = epollid.begin(); i != epollid.end(); ++i) - { - HLOGC(smlog.Debug, log << CONID() << "close: CLEARING subscription on E" << (*i)); - try - { - uglobal().m_EPoll.update_usock(*i, m_SocketID, &no_events); - } - catch (...) - { - // The goal of this loop is to remove all subscriptions in - // the epoll system to this socket. If it's unsubscribed already, - // that's even better. - } - HLOGC(smlog.Debug, log << CONID() << "close: removing E" << (*i) << " from back-subscribers"); - } - - // Not deleting elements from m_sPollID inside the loop because it invalidates - // the control iterator of the loop. Instead, all will be removed at once. - - // IMPORTANT: there's theoretically little time between setting ERR readiness - // and unsubscribing, however if there's an application waiting on this event, - // it should be informed before this below instruction locks the epoll mutex. - enterCS(uglobal().m_EPoll.m_EPollLock); - m_sPollID.clear(); - leaveCS(uglobal().m_EPoll.m_EPollLock); + uglobal().m_EPoll.wipe_usock(m_SocketID, m_sPollID); // XXX What's this, could any of the above actions make it !m_bOpened? if (!m_bOpened) @@ -6392,7 +6447,8 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT if (!m_bShutdown) { HLOGC(smlog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer @" << m_PeerID); - sendCtrl(UMSG_SHUTDOWN); + int32_t shdata[1] = { reason }; + sendCtrl(UMSG_SHUTDOWN, NULL, shdata, sizeof shdata); } // Store current connection information. @@ -6902,7 +6958,8 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) << " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno << " STAMP: " << BufferStamp(data, size)); - if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(m_stats.tsStartTime.time_since_epoch())) + time_point start_time = m_stats.tsStartTime; + if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(start_time.time_since_epoch())) { LOGC(aslog.Error, log << CONID() << "Wrong source time was provided. Sending is rejected."); @@ -7091,7 +7148,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ return 0; // Forced to return error instead of throwing exception. if (!by_exception) - return APIError(MJ_CONNECTION, MN_CONNLOST, 0); + return APIError(MJ_CONNECTION, MN_CONNLOST, 0), int(SRT_ERROR); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } else @@ -7230,7 +7287,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ { // Forced to return 0 instead of throwing exception. if (!by_exception) - return APIError(MJ_CONNECTION, MN_CONNLOST, 0); + return APIError(MJ_CONNECTION, MN_CONNLOST, 0).as(); if (!m_config.bMessageAPI && m_bShutdown) return 0; throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -7239,7 +7296,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ { // Forced to return -1 instead of throwing exception. if (!by_exception) - return APIError(MJ_CONNECTION, MN_NOCONN, 0); + return APIError(MJ_CONNECTION, MN_NOCONN, 0).as(); throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } } while ((res == 0) && !timeout); @@ -7271,7 +7328,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ { // Forced to return -1 instead of throwing exception. if (!by_exception) - return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0); + return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0).as(); throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); } @@ -7531,15 +7588,16 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) if (m_bBroken || m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + const int pktHdrSize = CPacket::HDR_SIZE + CPacket::udpHeaderSize(m_TransferIPVersion == AF_UNSPEC ? AF_INET : m_TransferIPVersion); { int32_t flight_span = getFlightSpan(); ScopedLock statsguard(m_StatsLock); - const steady_clock::time_point currtime = steady_clock::now(); + const time_point currtime = steady_clock::now(), + start_time = m_stats.tsStartTime; - perf->msTimeStamp = count_milliseconds(currtime - m_stats.tsStartTime); + perf->msTimeStamp = count_milliseconds(currtime - start_time); perf->pktSent = m_stats.sndr.sent.trace.count(); perf->pktSentUnique = m_stats.sndr.sentUnique.trace.count(); perf->pktRecv = m_stats.rcvr.recvd.trace.count(); @@ -7611,10 +7669,15 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) perf->pktRcvUndecryptTotal = m_stats.rcvr.undecrypted.total.count(); perf->byteRcvUndecryptTotal = m_stats.rcvr.undecrypted.total.bytes(); + + // Average values management + // We are updating rate with 0 Byte 0 packet to ensure an up to date compute in case we are not sending packet for a while. + m_stats.sndr.updateRate(0, 0); + m_stats.rcvr.updateRate(0, 0); + perf->mbpsSendRate = Bps2Mbps(m_stats.sndr.getAverageValue()); + perf->mbpsRecvRate = Bps2Mbps(m_stats.rcvr.getAverageValue()); + // TODO: The following class members must be protected with a different mutex, not the m_StatsLock. - const double interval = (double) count_microseconds(currtime - m_stats.tsLastSampleTime); - perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; - perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; perf->usPktSndPeriod = (double) count_microseconds(m_tdSendInterval.load()); perf->pktFlowWindow = m_iFlowWindowSize.load(); perf->pktCongestionWindow = m_iCongestionWindow; @@ -7946,7 +8009,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.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); break; @@ -8033,9 +8096,9 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp break; case UMSG_SHUTDOWN: // 101 - Shutdown - if (m_PeerID == 0) // Dont't send SHUTDOWN if we don't know peer ID. + if (m_PeerID == SRT_SOCKID_CONNREQ) // Dont't send SHUTDOWN if we don't know peer ID. break; - ctrlpkt.pack(pkttype); + ctrlpkt.pack(pkttype, NULL, rparam, size); ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); @@ -8067,12 +8130,19 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp m_tsLastSndTime.store(steady_clock::now()); } -// [[using locked(m_RcvBufferLock)]] bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) { + if (!m_pRcvBuffer) + { + LOGP(cnlog.Error, "IPE: ack can't be sent, buffer doesn't exist and no group membership"); + return false; + } if (m_config.bTSBPD || !m_config.bMessageAPI) { - // The getFirstNonreadSeqNo() function retuens the sequence number of the first packet + // NOTE: it's not only about protecting the buffer itself, it's also protecting + // the section where the m_iRcvCurrSeqNo is updated. + ScopedLock buflock (m_RcvBufferLock); + // The getFirstNonreadSeqNo() function returns the sequence number of the first packet // that cannot be read. In cases when a message can consist of several data packets, // an existing packet of partially available message also cannot be read. // If TSBPD mode is enabled, a message must consist of a single data packet only. @@ -8090,28 +8160,17 @@ bool srt::CUDT::getFirstNoncontSequence(int32_t& w_seq, string& w_log_reason) return true; } - - { - ScopedLock losslock (m_RcvLossLock); - const int32_t seq = m_pRcvLossList->getFirstLostSeq(); - if (seq != SRT_SEQNO_NONE) - { - HLOGC(xtlog.Debug, log << "NONCONT-SEQUENCE: first loss %" << seq << " (loss len=" << - m_pRcvLossList->getLossLength() << ")"); - w_seq = seq; - w_log_reason = "first lost"; - return true; - } - } - - w_seq = CSeqNo::incseq(m_iRcvCurrSeqNo); - HLOGC(xtlog.Debug, log << "NONCONT-SEQUENCE: past-recv %" << w_seq); - w_log_reason = "expected next"; + + ScopedLock buflock (m_RcvBufferLock); + bool has_followers = m_pRcvBuffer->getContiguousEnd((w_seq)); + if (has_followers) + w_log_reason = "first lost"; + else + w_log_reason = "expected next"; return true; } - int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { int nbsent = 0; @@ -8129,16 +8188,14 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) local_prevack = m_iDebugPrevLastAck; #endif - string reason; // just for "a reason" of giving particular % for ACK - // The TSBPD thread may change the first lost sequence record (TLPKTDROP). - // To avoid it the m_RcvBufferLock has to be acquired. - UniqueLock bufflock(m_RcvBufferLock); + // NOTE: the below calls do locking on m_RcvBufferLock. + // Hence up to the handling of lite ACK, the scoped lock is not applied. // The full ACK should be sent to indicate there is now available space in the RCV buffer // since the last full ACK. It should unblock the sender to proceed further. - const bool bNeedFullAck = (m_bBufferWasFull && getAvailRcvBufferSizeNoLock() > 0); + const bool bNeedFullAck = (m_bBufferWasFull && !isRcvBufferFull()); int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). - + string reason; // just for "a reason" of giving particular % for ACK if (!getFirstNoncontSequence((ack), (reason))) return nbsent; @@ -8152,7 +8209,6 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number if (size == SEND_LITE_ACK && !bNeedFullAck) { - bufflock.unlock(); ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); ctrlpkt.set_id(m_PeerID); nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); @@ -8160,6 +8216,16 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) return nbsent; } + // Lock the group existence until this function ends. This will be useful + // also on other places. +#if ENABLE_BONDING + CUDTUnited::GroupKeeper gkeeper (uglobal(), m_parent); +#endif + + // There are new received packets to acknowledge, update related information. + /* tsbpd thread may also call ackData when skipping packet so protect code */ + UniqueLock bufflock(m_RcvBufferLock); + // IF ack %> m_iRcvLastAck // There are new received packets to acknowledge, update related information. if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) @@ -8226,23 +8292,22 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) } } #endif - // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, - // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which - // will signal m_RecvDataCond when there's time to play for particular - // data packet. + // Signalling m_RecvDataCond is not done when TSBPD is on. + // This signalling is done in file mode in order to keep the + // API reader thread sleeping until there is a "bigger portion" + // of data to read. In TSBPD mode this isn't done because every + // packet has its individual delivery time and its readiness is signed + // off by the TSBPD thread. HLOGC(xtlog.Debug, log << CONID() << "ACK: clip %" << m_iRcvLastAck << "-%" << ack << ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer"); - if (m_bTsbPd) - { - /* Newly acknowledged data, signal TsbPD thread */ - CUniqueSync tslcc (m_RecvLock, m_RcvTsbPdCond); - // m_bTsbPdAckWakeup is protected by m_RecvLock in the tsbpd() thread - if (m_bTsbPdNeedsWakeup) - tslcc.notify_one(); - } - else + // There's no need to update TSBPD in the wake-on-recv state + // from ACK because it is being done already in the receiver thread + // when a newly inserted packet caused provision of a new candidate + // that could be delivered soon. Also, this flag is only used in TSBPD + // mode and can be only set to true in the TSBPD thread. + if (!m_bTsbPd) { { CUniqueSync rdcc (m_RecvLock, m_RecvDataCond); @@ -8542,6 +8607,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); m_bBroken = true; m_iBrokenCounter = 0; + setAgentCloseReason(SRT_CLS_IPE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! @@ -8975,6 +9041,7 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // this should not happen: attack or bug m_bBroken = true; m_iBrokenCounter = 0; + setAgentCloseReason(SRT_CLS_ROGUE); updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! @@ -9104,6 +9171,27 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { + typedef int32_t expected_t[2]; + if (ctrlpkt.getLength() < sizeof (expected_t)) + { + // We ALLOW packets that are bigger than this to allow + // future extensions, this just interprets the part that + // is expected, and reject only those that don't carry + // even the required data. + LOGC(brlog.Error, log << CONID() << "EPE: Wrong size of the DROPREQ message: " << ctrlpkt.getLength() + << " - expected >=" << sizeof(expected_t)); + return; + } + + int32_t msgno = ctrlpkt.getMsgSeq(m_bPeerRexmitFlag); + + // Check for rogue message + if (msgno == SRT_MSGNO_NONE) + { + LOGC(brlog.Warn, log << CONID() << "ROGUE DROPREQ detected with #NONE - fallback: fixing to #CONTROL"); + msgno = SRT_MSGNO_CONTROL; + } + const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; { @@ -9114,9 +9202,8 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) // Still remove the record from the loss list to cease further retransmission requests. if (!m_bTLPktDrop || !m_bTsbPd) { - const bool using_rexmit_flag = m_bPeerRexmitFlag; ScopedLock rblock(m_RcvBufferLock); - const int iDropCnt = m_pRcvBuffer->dropMessage(dropdata[0], dropdata[1], ctrlpkt.getMsgSeq(using_rexmit_flag), CRcvBuffer::KEEP_EXISTING); + const int iDropCnt = m_pRcvBuffer->dropMessage(dropdata[0], dropdata[1], msgno, CRcvBuffer::KEEP_EXISTING); if (iDropCnt > 0) { @@ -9126,7 +9213,7 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) if (frequentLogAllowed(FREQLOGFA_RCV_DROPPED, tnow, (why))) { LOGC(brlog.Warn, log << CONID() << "RCV-DROPPED " << iDropCnt << " packet(s), seqno range %" - << dropdata[0] << "-%" << dropdata[1] << ", msgno " << ctrlpkt.getMsgSeq(using_rexmit_flag) + << dropdata[0] << "-%" << dropdata[1] << ", #" << ctrlpkt.getMsgSeq(m_bPeerRexmitFlag) << " (SND DROP REQUEST). " << why); } #if SRT_ENABLE_FREQUENT_LOG_TRACE @@ -9141,14 +9228,27 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt * avgpayloadsz, (uint32_t) iDropCnt)); } } - // When the drop request was received, it means that there are - // packets for which there will never be ACK sent; if the TSBPD thread - // is currently in the ACK-waiting state, it may never exit. - if (m_bTsbPd) - { - HLOGP(inlog.Debug, "DROPREQ: signal TSBPD"); - rcvtscc.notify_one(); - } + + // NOTE: + // PREVIOUSLY done: notify on rcvtscc if m_bTsbPd + // OLD COMMENT: + // // When the drop request was received, it means that there are + // // packets for which there will never be ACK sent; if the TSBPD thread + // // is currently in the ACK-waiting state, it may never exit. + // + // Likely this is no longer necessary because: + // + // 1. If there's a play-ready packet, either in cell 0 or + // after a drop, TSBPD is sleeping timely, up to the play-time + // of the next ready packet (and the drop region concerned here + // is still a gap to be skipped for this). + // 2. TSBPD sleeps forever when the buffer is empty, in which case + // it will be always woken up on packet reception (see m_bTsbPdNeedsWakeup). + // And dropping won't happen in that case anyway. Note that the drop + // request will not drop packets that are already received. + // 3. TSBPD sleeps forever when the API call didn't extract the + // data that are ready to play. This isn't a problem if nothing + // except the API call would wake it up. } dropFromLossLists(dropdata[0], dropdata[1]); @@ -9169,8 +9269,31 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) } } -void srt::CUDT::processCtrlShutdown() +void srt::CUDT::processCtrlShutdown(const CPacket& ctrlpkt) { + const uint32_t* data = (const uint32_t*) ctrlpkt.m_pcData; + const size_t data_len = ctrlpkt.getLength() / 4; + + int reason = 0; + + // This condition should be ALWAYS satisfied, it's only + // a sanity check before reading the data. Versions that + // do not support close reason will simply send 0 here because + // it's the padding 0 that is provided in every command + // that is not expected to carry any "body". It is acceptable + // that the old versions simply send 0 here, but then you + // can't have the UNKNOWN value in any of close reason + // fields because it means that it wasn't set. + if (data_len > 0) + { + reason = data[0]; + } + + if (reason == 0) + { + setPeerCloseReason(SRT_CLS_FALLBACK); + } + m_bShutdown = true; m_bClosing = true; m_bBroken = true; @@ -9221,7 +9344,7 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.id()); + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=@" << ctrlpkt.id()); switch (ctrlpkt.getType()) { @@ -9256,7 +9379,7 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) break; case UMSG_SHUTDOWN: // 101 - Shutdown - processCtrlShutdown(); + processCtrlShutdown(ctrlpkt); break; case UMSG_DROPREQ: // 111 - Msg drop request @@ -9646,9 +9769,10 @@ bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) // then consider sending original packets. const int threshold_ms_min = (2 * m_iSRTT + 4 * m_iRTTVar + COMM_SYN_INTERVAL_US) / 1000; const int msNextUniqueToSend = count_milliseconds(tnow - tsNextPacket) + m_iPeerTsbPdDelay_ms; + const time_point start_time = m_stats.tsStartTime; g_snd_logger.state.tsNow = tnow; - g_snd_logger.state.usElapsed = count_microseconds(tnow - m_stats.tsStartTime); + g_snd_logger.state.usElapsed = count_microseconds(tnow - start_time); g_snd_logger.state.usSRTT = m_iSRTT; g_snd_logger.state.usRTTVar = m_iRTTVar; g_snd_logger.state.msSndBuffSpan = buffdelay_ms; @@ -9682,7 +9806,7 @@ bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) return true; } -bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, sockaddr_any& w_src_addr) +bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, CNetworkInterface& w_src_addr) { int payload = 0; bool probe = false; @@ -9781,6 +9905,7 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime m_stats.sndr.sent.count(payload); if (new_packet_packed) m_stats.sndr.sentUnique.count(payload); + m_stats.sndr.updateRate(1, payload); leaveCS(m_StatsLock); const duration sendint = m_tdSendInterval; @@ -9975,10 +10100,12 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) return true; } -// This is a close request, but called from the +// This is a close request, but called from the handler of the +// buffer overflow in live mode. void srt::CUDT::processClose() { - sendCtrl(UMSG_SHUTDOWN); + uint32_t res[1] = { SRT_CLS_OVERFLOW }; + sendCtrl(UMSG_SHUTDOWN, NULL, res, sizeof res); m_bShutdown = true; m_bClosing = true; @@ -10062,7 +10189,15 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) return false; } - // + int dbytes; + const int dpkts SRT_ATR_UNUSED = m_pSndBuffer->dropAll((dbytes)); + + enterCS(m_StatsLock); + m_stats.sndr.dropped.count(dbytes);; + leaveCS(m_StatsLock); + + m_pSndLossList->removeUpTo(CSeqNo::decseq(seq)); + // The peer will have to do the same, as a reaction on perceived // packet loss. When it recognizes that this initial screwing up // has happened, it should simply ignore the loss and go on. @@ -10076,7 +10211,7 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo - << " (unchanged)"); + << " (unchanged) dropped-pkts=" << dpkts); return true; } @@ -10118,7 +10253,7 @@ CUDT::time_point srt::CUDT::getPktTsbPdTime(void*, const CPacket& packet) SRT_ATR_UNUSED static const char *const s_rexmitstat_str[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; // [[using locked(m_RcvBufferLock)]] -int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) +int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& w_new_inserted, time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs) { bool excessive SRT_ATR_UNUSED = true; // stays true unless it was successfully added @@ -10128,7 +10263,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& // Loop over all incoming packets that were filtered out. // In case when there is no filter, there's just one packet in 'incoming', // the one that came in the input of this function. - for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end(); ++unitIt) + for (vector::const_iterator unitIt = incoming.begin(); unitIt != incoming.end() && !m_bBroken; ++unitIt) { CUnit * u = *unitIt; CPacket &rpkt = u->m_Packet; @@ -10217,7 +10352,26 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } } - const int buffer_add_result = m_pRcvBuffer->insert(u); + CRcvBuffer::InsertInfo info = m_pRcvBuffer->insert(u); + + // Remember this value in order to CHECK if there's a need + // to request triggering TSBPD in case when TSBPD is in the + // state of waiting forever and wants to know if there's any + // possible time to wake up known earlier than that. + + // Note that in case of the "builtin group reader" (its own + // buffer), there's no need to do it here because it has also + // its own TSBPD thread. + + if (info.result == CRcvBuffer::InsertInfo::INSERTED) + { + // This may happen multiple times in the loop, so update only if earlier. + if (w_next_tsbpd == time_point() || w_next_tsbpd > info.first_time) + w_next_tsbpd = info.first_time; + w_new_inserted = true; + } + const int buffer_add_result = int(info.result); + if (buffer_add_result < 0) { // The insert() result is -1 if at the position evaluated from this packet's @@ -10228,8 +10382,6 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } else { - w_new_inserted = true; - IF_HEAVY_LOGGING(exc_type = "ACCEPTED"); excessive = false; if (u->m_Packet.getMsgCryptoFlags() != EK_NOENC) @@ -10457,6 +10609,7 @@ int srt::CUDT::processData(CUnit* in_unit) enterCS(m_StatsLock); m_stats.rcvr.recvd.count(pktsz); + m_stats.rcvr.updateRate(1, pktsz); leaveCS(m_StatsLock); loss_seqs_t filter_loss_seqs; @@ -10538,6 +10691,8 @@ int srt::CUDT::processData(CUnit* in_unit) } #endif + // NULL time by default + time_point next_tsbpd_avail; bool new_inserted = false; if (m_PacketFilter) @@ -10566,6 +10721,7 @@ int srt::CUDT::processData(CUnit* in_unit) const int res = handleSocketPacketReception(incoming, (new_inserted), + (next_tsbpd_avail), (was_sent_in_order), (srt_loss_seqs)); @@ -10640,6 +10796,42 @@ int srt::CUDT::processData(CUnit* in_unit) return -1; } + // 1. This is set to true in case when TSBPD during the last check + // has seen no packet candidate to ever deliver, hence it needs + // an update on that. Note that this is also false if TSBPD thread + // isn't running. + // 2. If next_tsbpd_avail is set, it means that in the buffer there is + // a new packet that precedes the previously earliest available packet. + // This means that if TSBPD was sleeping up to the time of this earliest + // delivery (after drop), this time we have received a packet to be delivered + // earlier than that, so we need to notify TSBPD immediately so that it + // updates this itself, not sleep until the previously set time. + + // The meaning of m_bTsbPdNeedsWakeup: + // - m_bTsbPdNeedsWakeup is set by TSBPD thread and means that it wishes to be woken up + // on every received packet. Hence we signal always if a new packet was inserted. + // - even if TSBPD doesn't wish to be woken up on every reception (because it sleeps + // until the play time of the next deliverable packet), it will be woken up when + // next_tsbpd_avail is set because it means this time is earlier than the time until + // which TSBPD sleeps, so it must be woken up prematurely. It might be more performant + // to simply update the sleeping end time of TSBPD, but there's no way to do it, so + // we simply wake TSBPD up and count on that it will update its sleeping settings. + + // XXX Consider: as CUniqueSync locks m_RecvLock, it means that the next instruction + // gets run only when TSBPD falls asleep again. Might be a good idea to record the + // TSBPD end sleeping time - as an alternative to m_bTsbPdNeedsWakeup - and after locking + // a mutex check this time again and compare it against next_tsbpd_avail; might be + // that if this difference is smaller than "dirac" (could be hard to reliably compare + // this time, unless it's set from this very value), there's no need to wake the TSBPD + // thread because it will wake up on time requirement at the right time anyway. + if (m_bTsbPd && ((m_bTsbPdNeedsWakeup && new_inserted) || next_tsbpd_avail != time_point())) + { + HLOGC(qrlog.Debug, log << "processData: will SIGNAL TSBPD for socket. WakeOnRecv=" << m_bTsbPdNeedsWakeup + << " new_inserted=" << new_inserted << " next_tsbpd_avail=" << FormatTime(next_tsbpd_avail)); + CUniqueSync tsbpd_cc(m_RecvLock, m_RcvTsbPdCond); + tsbpd_cc.notify_all(); + } + if (incoming.empty()) { // Treat as excessive. This is when a filter cumulates packets @@ -10655,16 +10847,6 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs)); sendLossReport(srt_loss_seqs); } - - if (m_bTsbPd) - { - HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); - CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); - } - else - { - HLOGC(qrlog.Debug, log << CONID() << "loss: socket is not TSBPD, not signaling"); - } } // Separately report loss records of those reported by a filter. @@ -10676,12 +10858,6 @@ int srt::CUDT::processData(CUnit* in_unit) { HLOGC(qrlog.Debug, log << CONID() << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs)); sendLossReport(filter_loss_seqs); - - if (m_bTsbPd) - { - HLOGC(qrlog.Debug, log << CONID() << "loss: signaling TSBPD cond"); - CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); - } } // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. @@ -11055,7 +11231,8 @@ int32_t srt::CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int co clientport, sizeof(clientport), NI_NUMERICHOST | NI_NUMERICSERV); - int64_t timestamp = (count_microseconds(steady_clock::now() - m_stats.tsStartTime) / 60000000) + distractor + + time_point start_time = m_stats.tsStartTime; + int64_t timestamp = (count_microseconds(steady_clock::now() - start_time) / 60000000) + distractor + correction; // secret changes every one minute stringstream cookiestr; cookiestr << clienthost << ":" << clientport << ":" << timestamp; @@ -11104,6 +11281,11 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) HLOGC(cnlog.Debug, log << CONID() << "processConnectRequest: received a connection request"); + // NOTE (IMPORTANT!!!) + // + // The current CUDT object represents a LISTENER SOCKET to which + // the request was redirected from the receiver queue. + if (m_bClosing) { m_RejectReason = SRT_REJ_CLOSE; @@ -11172,7 +11354,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) // because this happens still in the frames of the listener socket. Only // when processing switches to the newly spawned accepted socket can the // address be recorded in its m_SourceAddr field. - sockaddr_any use_source_addr = packet.udpDestAddr(); + CNetworkInterface use_source_addr = packet.udpDestAddr(); // REQUEST:INDUCTION. // Set a cookie, a target ID, and send back the same as @@ -11388,21 +11570,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) } } - if (result == 1) - { - // BUG! There is no need to update write-readiness on the listener socket once new connection is accepted. - // Only read-readiness has to be updated, but it is done so in the newConnection(..) function. - // See PR #1831 and issue #1667. - HLOGC(cnlog.Debug, - log << CONID() << "processConnectRequest: accepted connection, updating epoll to write-ready"); - - // New connection has been accepted or an existing one has been found. Update epoll write-readiness. - // a new connection has been created, enable epoll for write - // Note: not using SRT_EPOLL_CONNECT symbol because this is a procedure - // executed for the accepted socket. - uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true); - } - else if (result == -1) + if (result == -1) { // The new connection failed // or the connection already existed, but manually sending the HS response above has failed. @@ -11593,6 +11761,7 @@ bool srt::CUDT::checkExpTimer(const steady_clock::time_point& currtime, int chec if (m_bBreakAsUnstable || ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) && (currtime - last_rsp_time > microseconds_from(PEER_IDLE_TMO_US)))) { + setAgentCloseReason(SRT_CLS_PEERIDLE); // // Connection is broken. // UDT does not signal any information about this instead of to stop quietly. @@ -11843,6 +12012,24 @@ void srt::CUDT::addEPoll(const int eid) m_sPollID.insert(eid); leaveCS(uglobal().m_EPoll.m_EPollLock); + if (m_bListening) + { + // A listener socket can only get readiness on SRT_EPOLL_ACCEPT + // (which has the same value as SRT_EPOLL_IN), or sometimes + // also SRT_EPOLL_UPDATE. All interesting fields for that purpose + // are contained in the CUDTSocket class, so redirect there. + SRT_EPOLL_T events = m_parent->getListenerEvents(); + + // Only light up the events that were returned, do nothing if none is ready, + // the "no event" state is the default. + if (events) + uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, events, true); + + // You don't check anything else here - a listener socket can be only + // used for listening and nothing else. + return; + } + if (!stillConnected()) return; @@ -11931,7 +12118,7 @@ int srt::CUDT::rejectReason(SRTSOCKET u) return s->core().m_RejectReason; } -int srt::CUDT::rejectReason(SRTSOCKET u, int value) +SRTSTATUS srt::CUDT::rejectReason(SRTSOCKET u, int value) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) @@ -11941,16 +12128,17 @@ int srt::CUDT::rejectReason(SRTSOCKET u, int value) return APIError(MJ_NOTSUP, MN_INVAL); s->core().m_RejectReason = value; - return 0; + return SRT_STATUS_OK; } int64_t srt::CUDT::socketStartTime(SRTSOCKET u) { CUDTSocket* s = uglobal().locateSocket(u); if (!s) - return APIError(MJ_NOTSUP, MN_SIDINVAL); + return APIError(MJ_NOTSUP, MN_SIDINVAL).as(); - return count_microseconds(s->core().m_stats.tsStartTime.time_since_epoch()); + const time_point& start_time = s->core().m_stats.tsStartTime; + return count_microseconds(start_time.time_since_epoch()); } bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) @@ -12088,3 +12276,34 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr if (m_config.bDriftTracer) m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); } + +// This function should be called when closing the socket internally. +void srt::CUDT::setAgentCloseReason(int reason) +{ + m_AgentCloseReason.compare_exchange(SRT_CLS_UNKNOWN, reason); + + // Do not touch m_PeerCloseReason, it should remain SRT_CLS_UNKNOWN. + // If this reason is already set to some value, then m_AgentCloseReason + // should have been already set to SRT_CLS_PEER. + + m_CloseTimeStamp.compare_exchange(time_point(), steady_clock::now()); +} + +// This function should be called in a handler of UMSG_SHUTDOWN. +void srt::CUDT::setPeerCloseReason(int reason) +{ + m_AgentCloseReason.compare_exchange(SRT_CLS_UNKNOWN, SRT_CLS_PEER); + if (m_AgentCloseReason == SRT_CLS_PEER) + { + m_PeerCloseReason.compare_exchange(SRT_CLS_UNKNOWN, reason); + + m_CloseTimeStamp.compare_exchange(time_point(), steady_clock::now()); + } +} + +void srt::CUDT::copyCloseInfo(SRT_CLOSE_INFO& info) +{ + info.agent = SRT_CLOSE_REASON(m_AgentCloseReason.load()); + info.peer = SRT_CLOSE_REASON(m_PeerCloseReason.load()); + info.time = m_CloseTimeStamp.load().time_since_epoch().count(); +} diff --git a/srtcore/core.h b/srtcore/core.h index ed250c641..0c59bc62a 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -195,30 +195,31 @@ class CUDT ~CUDT(); public: //API - static int startup(); - static int cleanup(); + static SRTRUNSTATUS startup(); + static SRTSTATUS cleanup(); static SRTSOCKET socket(); #if ENABLE_BONDING static SRTSOCKET createGroup(SRT_GROUP_TYPE); static SRTSOCKET getGroupOfSocket(SRTSOCKET socket); - static int getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize); - static bool isgroup(SRTSOCKET sock) { return (sock & SRTGROUP_MASK) != 0; } + static SRTSTATUS getGroupData(SRTSOCKET groupid, SRT_SOCKGROUPDATA* pdata, size_t* psize); + static bool isgroup(SRTSOCKET sock) { return (int32_t(sock) & SRTGROUP_MASK) != 0; } #endif - static int bind(SRTSOCKET u, const sockaddr* name, int namelen); - static int bind(SRTSOCKET u, UDPSOCKET udpsock); - static int listen(SRTSOCKET u, int backlog); + static SRTSTATUS bind(SRTSOCKET u, const sockaddr* name, int namelen); + static SRTSTATUS bind(SRTSOCKET u, UDPSOCKET udpsock); + static SRTSTATUS listen(SRTSOCKET u, int backlog); static SRTSOCKET accept(SRTSOCKET u, sockaddr* addr, int* addrlen); static SRTSOCKET accept_bond(const SRTSOCKET listeners [], int lsize, int64_t msTimeOut); - static int connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); - static int connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen); + static SRTSOCKET connect(SRTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + static SRTSOCKET connect(SRTSOCKET u, const sockaddr* name, const sockaddr* tname, int namelen); #if ENABLE_BONDING - static int connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG links [], int arraysize); + static SRTSOCKET connectLinks(SRTSOCKET grp, SRT_SOCKGROUPCONFIG links [], int arraysize); #endif - static int close(SRTSOCKET u); - static int getpeername(SRTSOCKET u, sockaddr* name, int* namelen); - static int getsockname(SRTSOCKET u, sockaddr* name, int* namelen); - static int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); - static int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); + static SRTSTATUS close(SRTSOCKET u, int reason); + static SRTSTATUS getpeername(SRTSOCKET u, sockaddr* name, int* namelen); + static SRTSTATUS getsockname(SRTSOCKET u, sockaddr* name, int* namelen); + static SRTSTATUS getsockdevname(SRTSOCKET u, char* name, size_t* namelen); + static SRTSTATUS getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); + static SRTSTATUS setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); static int send(SRTSOCKET u, const char* buf, int len, int flags); static int recv(SRTSOCKET u, char* buf, int len, int flags); static int sendmsg(SRTSOCKET u, const char* buf, int len, int ttl = SRT_MSGTTL_INF, bool inorder = false, int64_t srctime = 0); @@ -230,30 +231,31 @@ class CUDT static int select(int nfds, UDT::UDSET* readfds, UDT::UDSET* writefds, UDT::UDSET* exceptfds, const timeval* timeout); static int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); static int epoll_create(); - static int epoll_clear_usocks(int eid); - static int epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); - static int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - static int epoll_remove_usock(const int eid, const SRTSOCKET u); - static int epoll_remove_ssock(const int eid, const SYSSOCKET s); - static int epoll_update_usock(const int eid, const SRTSOCKET u, const int* events = NULL); - static int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + static SRTSTATUS epoll_clear_usocks(int eid); + static SRTSTATUS epoll_add_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + static SRTSTATUS epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + static SRTSTATUS epoll_remove_usock(const int eid, const SRTSOCKET u); + static SRTSTATUS epoll_remove_ssock(const int eid, const SYSSOCKET s); + static SRTSTATUS epoll_update_usock(const int eid, const SRTSOCKET u, const int* events = NULL); + static SRTSTATUS epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); static int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); static int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); static int32_t epoll_set(const int eid, int32_t flags); - static int epoll_release(const int eid); + static SRTSTATUS epoll_release(const int eid); static CUDTException& getlasterror(); - static int bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true, bool instantaneous = false); + static SRTSTATUS bstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true, bool instantaneous = false); #if ENABLE_BONDING - static int groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true); + static SRTSTATUS groupsockbstats(SRTSOCKET u, CBytePerfMon* perf, bool clear = true); #endif static SRT_SOCKSTATUS getsockstate(SRTSOCKET u); static bool setstreamid(SRTSOCKET u, const std::string& sid); static std::string getstreamid(SRTSOCKET u); - static int getsndbuffer(SRTSOCKET u, size_t* blocks, size_t* bytes); + static int getsndbuffer(SRTSOCKET u, size_t* blocks, size_t* bytes); // returns buffer span in [ms] static int rejectReason(SRTSOCKET s); - static int rejectReason(SRTSOCKET s, int value); + static SRTSTATUS rejectReason(SRTSOCKET s, int value); static int64_t socketStartTime(SRTSOCKET s); + static int getMaxPayloadSize(SRTSOCKET u); public: // internal API // This is public so that it can be used directly in API implementation functions. @@ -261,15 +263,20 @@ class CUDT { APIError(const CUDTException&); APIError(CodeMajor, CodeMinor, int = 0); + APIError(int error_code); - operator int() const + // This represents both SRT_ERROR and SRT_INVALID_SOCK. + operator SRTSTATUS() const { return SRT_ERROR; } - }; - static const SRTSOCKET INVALID_SOCK = -1; // Invalid socket descriptor - static const int ERROR = -1; // Socket api error returned value + template + Retval as() const + { + return Retval((int)SRT_ERROR); + } + }; static const int HS_VERSION_UDT4 = 4; static const int HS_VERSION_SRT1 = 5; @@ -297,7 +304,7 @@ class CUDT { #if ENABLE_LOGGING std::ostringstream os; - os << "@" << m_SocketID << ": "; + os << "@" << int(m_SocketID) << ": "; return os.str(); #else return ""; @@ -305,6 +312,7 @@ class CUDT } SRTSOCKET socketID() const { return m_SocketID; } + SRTSOCKET peerID() const { return m_PeerID; } static CUDT* getUDTHandle(SRTSOCKET u); static std::vector existingSockets(); @@ -462,7 +470,11 @@ class CUDT SRTU_PROPERTY_RR(sync::Condition*, recvTsbPdCond, &m_RcvTsbPdCond); /// @brief Request a socket to be broken due to too long instability (normally by a group). - void breakAsUnstable() { m_bBreakAsUnstable = true; } + void breakAsUnstable() + { + m_bBreakAsUnstable = true; + setAgentCloseReason(SRT_CLS_UNSTABLE); + } void ConnectSignal(ETransmissionEvent tev, EventSlot sl); void DisconnectSignal(ETransmissionEvent tev); @@ -596,6 +608,7 @@ class CUDT /// @param hspkt [in] The original packet that brought the handshake. /// @param hs [in/out] The handshake information sent by the peer side (in), negotiated value (out). void acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& hs); + bool createSendHSResponse(uint32_t* kmdata, size_t kmdatasize, const CNetworkInterface& hsaddr, CHandShake& w_hs) ATR_NOTHROW; /// Write back to the hs structure the data after they have been /// negotiated by acceptAndRespond. @@ -604,10 +617,13 @@ class CUDT /// Close the opened UDT entity. - bool closeInternal() ATR_NOEXCEPT; + bool closeInternal(int reason) ATR_NOEXCEPT; void updateBrokenConnection(); void completeBrokenConnectionDependencies(int errorcode); + void setAgentCloseReason(int reason); + void setPeerCloseReason(int reason); + /// Request UDT to send out a data block "data" with size of "len". /// @param data [in] The address of the application data to be sent. /// @param len [in] The size of the data block. @@ -700,6 +716,7 @@ class CUDT void unlose(const CPacket& oldpacket); void dropFromLossLists(int32_t from, int32_t to); + SRT_ATTR_EXCLUDES(m_RcvBufferLock) SRT_ATTR_REQUIRES(m_RecvAckLock) bool getFirstNoncontSequence(int32_t& w_seq, std::string& w_log_reason); @@ -833,6 +850,15 @@ class CUDT sync::atomic m_bBreakAsUnstable; // A flag indicating that the socket should become broken because it has been unstable for too long. sync::atomic m_bPeerHealth; // If the peer status is normal sync::atomic m_RejectReason; + + // If the socket was closed by some reason locally, the reason is + // in m_AgentCloseReason and the m_PeerCloseReason is then SRT_CLS_UNKNOWN. + // If the socket was closed due to reception of UMSG_SHUTDOWN, the reason + // exctracted from the message is written to m_PeerCloseReason and the + // m_AgentCloseReason == SRT_CLS_PEER. + sync::atomic m_AgentCloseReason; + sync::atomic m_PeerCloseReason; + atomic_time_point m_CloseTimeStamp; // Time when the close reason was first set bool m_bOpened; // If the UDT entity has been opened // A counter (number of GC checks happening every 1s) to let the GC tag this socket as closed. sync::atomic m_iBrokenCounter; // If a broken socket still has data in the receiver buffer, it is not marked closed until the counter is 0. @@ -999,15 +1025,15 @@ class CUDT 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_bTsbPdNeedsWakeup; // Signal TsbPd thread to wake up on RCV buffer state change. + sync::atomic m_bTsbPdNeedsWakeup; // Expected to wake up TSBPD when a read-ready data packet is received. sync::Mutex m_RcvTsbPdStartupLock; // Protects TSBPD thread creation and joining. CallbackHolder m_cbAcceptHook; CallbackHolder m_cbConnectHook; // FORWARDER public: - static int installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); - static int installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); + static SRTSTATUS installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); + static SRTSTATUS installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); private: void installAcceptHook(srt_listen_callback_fn* hook, void* opaq) { @@ -1101,7 +1127,7 @@ class CUDT void processCtrlDropReq(const CPacket& ctrlpkt); /// @brief Process incoming shutdown control packet - void processCtrlShutdown(); + void processCtrlShutdown(const CPacket& ctrlpkt); /// @brief Process incoming user defined control packet /// @param ctrlpkt incoming user defined packet void processCtrlUserDefined(const CPacket& ctrlpkt); @@ -1129,7 +1155,7 @@ class CUDT /// /// @retval true A packet was extracted for sending, the socket should be rechecked at @a nexttime /// @retval false Nothing was extracted for sending, @a nexttime should be ignored - bool packData(CPacket& packet, time_point& nexttime, sockaddr_any& src_addr); + bool packData(CPacket& packet, time_point& nexttime, CNetworkInterface& src_addr); /// Also excludes srt::CUDTUnited::m_GlobControlLock. SRT_ATTR_EXCLUDES(m_RcvTsbPdStartupLock, m_StatsLock, m_RecvLock, m_RcvLossLock, m_RcvBufferLock) @@ -1142,16 +1168,17 @@ class CUDT /// /// @param incoming [in] The packet coming from the network medium /// @param w_new_inserted [out] Set false, if the packet already exists, otherwise true (packet added) + /// @param w_next_tsbpd [out] Get the TSBPD time of the earliest playable packet after insertion /// @param w_was_sent_in_order [out] Set false, if the packet was belated, but had no R flag set. /// @param w_srt_loss_seqs [out] Gets inserted a loss, if this function has detected it. /// /// @return 0 The call was successful (regardless if the packet was accepted or not). /// @return -1 The call has failed: no space left in the buffer. /// @return -2 The incoming packet exceeds the expected sequence by more than a length of the buffer (irrepairable discrepancy). - int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); + int handleSocketPacketReception(const std::vector& incoming, bool& w_new_inserted, sync::steady_clock::time_point& w_next_tsbpd, bool& w_was_sent_in_order, CUDT::loss_seqs_t& w_srt_loss_seqs); - /// Get the packet's TSBPD time - - /// the time when it is passed to the reading application. + // This function is to return the packet's play time (time when + // it is submitted to the reading application) of the given packet. /// 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); @@ -1184,7 +1211,7 @@ class CUDT private: // Trace struct CoreStats { - time_point tsStartTime; // timestamp when the UDT entity is started + atomic_time_point tsStartTime; // timestamp when the UDT entity is started stats::Sender sndr; // sender statistics stats::Receiver rcvr; // receiver statistics @@ -1204,6 +1231,8 @@ class CUDT static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq static const int PACKETPAIR_MASK = 0xF; + void copyCloseInfo(SRT_CLOSE_INFO&); + private: // Timers functions #if ENABLE_BONDING time_point m_tsFreshActivation; // GROUPS: time of fresh activation of the link, or 0 if past the activation phase or idle @@ -1229,8 +1258,9 @@ class CUDT CSndQueue* m_pSndQueue; // packet sending queue CRcvQueue* m_pRcvQueue; // packet receiving queue sockaddr_any m_PeerAddr; // peer address - sockaddr_any m_SourceAddr; // override UDP source address with this one when sending + CNetworkInterface m_SourceAddr; // override UDP source address with this one when sending uint32_t m_piSelfIP[4]; // local UDP IP address + int m_TransferIPVersion; // AF_INET/6 that should be used to determine common payload size CSNode* m_pSNode; // node information for UDT list used in snd queue CRNode* m_pRNode; // node information for UDT list used in rcv queue diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index e7fe0be89..d61e7dc9a 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -723,7 +723,7 @@ void srt::CCryptoControl::close() std::string srt::CCryptoControl::CONID() const { - if (m_SocketID == 0) + if (int32_t(m_SocketID) <= 0) return ""; std::ostringstream os; diff --git a/srtcore/epoll.cpp b/srtcore/epoll.cpp index 8cd8440c7..6f03f5adc 100644 --- a/srtcore/epoll.cpp +++ b/srtcore/epoll.cpp @@ -159,7 +159,7 @@ ENOMEM: There was insufficient memory to create the kernel object. return m_iIDSeed; } -int srt::CEPoll::clear_usocks(int eid) +void srt::CEPoll::clear_usocks(int eid) { // This should remove all SRT sockets from given eid. ScopedLock pg (m_EPollLock); @@ -171,8 +171,6 @@ int srt::CEPoll::clear_usocks(int eid) CEPollDesc& d = p->second; d.clearAll(); - - return 0; } @@ -213,7 +211,7 @@ void srt::CEPoll::clear_ready_usocks(CEPollDesc& d, int direction) d.removeSubscription(cleared[j]); } -int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) +void srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) { ScopedLock pg(m_EPollLock); @@ -281,11 +279,9 @@ int srt::CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) #endif p->second.m_sLocals.insert(s); - - return 0; } -int srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) +void srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) { ScopedLock pg(m_EPollLock); @@ -311,12 +307,10 @@ int srt::CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) #endif p->second.m_sLocals.erase(s); - - return 0; } // Need this to atomically modify polled events (ex: remove write/keep read) -int srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) +void srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* events) { ScopedLock pg(m_EPollLock); IF_HEAVY_LOGGING(ostringstream evd); @@ -385,10 +379,9 @@ int srt::CEPoll::update_usock(const int eid, const SRTSOCKET& u, const int* even HLOGC(ealog.Debug, log << "srt_epoll_update_usock: REMOVED E" << eid << " socket @" << u); d.removeSubscription(u); } - return 0; } -int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) +void srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) { ScopedLock pg(m_EPollLock); @@ -456,10 +449,9 @@ int srt::CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* even // Assuming add is used if not inserted // p->second.m_sLocals.insert(s); - return 0; } -int srt::CEPoll::setflags(const int eid, int32_t flags) +int32_t srt::CEPoll::setflags(const int eid, int32_t flags) { ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); @@ -500,18 +492,23 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 ScopedLock pg(m_EPollLock); map::iterator p = m_mPolls.find(eid); if (p == m_mPolls.end()) + { + LOGC(ealog.Error, log << "epoll_uwait: E" << eid << " doesn't exist"); throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + } CEPollDesc& ed = p->second; if (!ed.flags(SRT_EPOLL_ENABLE_EMPTY) && ed.watch_empty()) { // Empty EID is not allowed, report error. + LOGC(ealog.Error, log << "epoll_uwait: E" << eid << " is empty (use SRT_EPOLL_ENABLE_EMPTY to allow)"); throw CUDTException(MJ_NOTSUP, MN_EEMPTY); } if (ed.flags(SRT_EPOLL_ENABLE_OUTPUTCHECK) && (fdsSet == NULL || fdsSize == 0)) { - // Empty EID is not allowed, report error. + // Empty container is not allowed, report error. + LOGC(ealog.Error, log << "epoll_uwait: empty output container with E" << eid << " (use SRT_EPOLL_ENABLE_OUTPUTCHECK to allow)"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } @@ -519,6 +516,7 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 { // XXX Add error log // uwait should not be used with EIDs subscribed to system sockets + LOGC(ealog.Error, log << "epoll_uwait: E" << eid << " is subscribed to system sckets (not allowed for uwait)"); throw CUDTException(MJ_NOTSUP, MN_INVAL); } @@ -530,11 +528,20 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 ++total; if (total > fdsSize) + { + HLOGC(ealog.Debug, log << "epoll_uwait: output container size=" << fdsSize << " insufficient to report all sockets"); break; + } fdsSet[pos] = *i; + IF_HEAVY_LOGGING(std::ostringstream out); + IF_HEAVY_LOGGING(out << "epoll_uwait: Notice: fd=" << i->fd << " events="); + IF_HEAVY_LOGGING(PrintEpollEvent(out, i->events, 0)); + + SRT_ATR_UNUSED const bool was_edge = ed.checkEdge(i++); // NOTE: potentially deletes `i` + IF_HEAVY_LOGGING(out << (was_edge ? "(^)" : "")); + HLOGP(ealog.Debug, out.str()); - ed.checkEdge(i++); // NOTE: potentially deletes `i` } if (total) return total; @@ -810,7 +817,7 @@ int srt::CEPoll::swait(CEPollDesc& d, map& st, int64_t msTimeOut // Logging into 'singles' because it notifies as to whether // the edge-triggered event has been cleared - HLOGC(ealog.Debug, log << "E" << d.m_iID << " rdy=" << total << ": " + HLOGC(ealog.Debug, log << "swait: E" << d.m_iID << " rdy=" << total << ": " << singles.str() << " TRACKED: " << d.DisplayEpollWatch()); return total; @@ -839,7 +846,7 @@ bool srt::CEPoll::empty(const CEPollDesc& d) const return d.watch_empty(); } -int srt::CEPoll::release(const int eid) +void srt::CEPoll::release(const int eid) { ScopedLock pg(m_EPollLock); @@ -855,8 +862,6 @@ int srt::CEPoll::release(const int eid) #endif m_mPolls.erase(i); - - return 0; } @@ -866,7 +871,13 @@ int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const if ((events & ~SRT_EPOLL_EVENTTYPES) != 0) { LOGC(eilog.Fatal, log << "epoll/update: IPE: 'events' parameter shall not contain special flags!"); - return -1; // still, ignored. + return int(SRT_ERROR); // still, ignored. + } + + if (uid == SRT_INVALID_SOCK || uid == SRT_SOCKID_CONNREQ) + { + LOGC(eilog.Fatal, log << "epoll/update: IPE: invalid 'uid' submitted for update!"); + return int(SRT_ERROR); } int nupdated = 0; @@ -945,6 +956,33 @@ int srt::CEPoll::update_events(const SRTSOCKET& uid, std::set& eids, const return nupdated; } +/// This is a simple function which removes the socket from epoll system. +/// The subscription list should be provided in the @a eids container and +/// the socket is removed from each of them, then this is cleared. This +/// should be the socket's private EID container that keeps EIDs that it +/// should update when an appropriate event comes. +/// +/// @param uid Socket ID that has to be removed from the epoll system +/// @param eids EIDs that the given socket believes being subscribed in +void srt::CEPoll::wipe_usock(const SRTSOCKET uid, std::set& eids) +{ + ScopedLock pg (m_EPollLock); + for (set::iterator i = eids.begin(); i != eids.end(); ++ i) + { + map::iterator p = m_mPolls.find(*i); + if (p == m_mPolls.end()) + { + HLOGC(eilog.Note, log << "epoll/wipe: E" << *i << " was deleted in the meantime"); + continue; + } + + CEPollDesc& ed = p->second; + ed.removeSubscription(uid); + } + + eids.clear(); +} + // Debug use only. #if ENABLE_HEAVY_LOGGING namespace srt diff --git a/srtcore/epoll.h b/srtcore/epoll.h index 00d46ceb4..e82fef38a 100644 --- a/srtcore/epoll.h +++ b/srtcore/epoll.h @@ -377,38 +377,29 @@ friend class srt::CRendezvousQueue; /// delete all user sockets (SRT sockets) from an EPoll /// @param [in] eid EPoll ID. - /// @return 0 - int clear_usocks(int eid); + void clear_usocks(int eid); /// add a system socket to an EPoll. /// @param [in] eid EPoll ID. /// @param [in] s system Socket ID. /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. - - int add_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); + void add_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); /// remove a system socket event from an EPoll; socket will be removed if no events to watch. /// @param [in] eid EPoll ID. /// @param [in] s system socket ID. - /// @return 0 if success, otherwise an error number. - - int remove_ssock(const int eid, const SYSSOCKET& s); + void remove_ssock(const int eid, const SYSSOCKET& s); /// update a UDT socket events from an EPoll. /// @param [in] eid EPoll ID. /// @param [in] u UDT socket ID. /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. - - int update_usock(const int eid, const SRTSOCKET& u, const int* events); + void update_usock(const int eid, const SRTSOCKET& u, const int* events); /// update a system socket events from an EPoll. /// @param [in] eid EPoll ID. /// @param [in] u UDT socket ID. /// @param [in] events events to watch. - /// @return 0 if success, otherwise an error number. - - int update_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); + void update_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); /// wait for EPoll events or timeout. /// @param [in] eid EPoll ID. @@ -475,7 +466,7 @@ friend class srt::CRendezvousQueue; /// @param [in] eid EPoll ID. /// @return 0 if success, otherwise an error number. - int release(const int eid); + void release(const int eid); public: // for CUDT to acknowledge IO status @@ -489,7 +480,9 @@ friend class srt::CRendezvousQueue; int update_events(const SRTSOCKET& uid, std::set& eids, int events, bool enable); - int setflags(const int eid, int32_t flags); + void wipe_usock(const SRTSOCKET uid, std::set& eids); + + int32_t setflags(const int eid, int32_t flags); private: int m_iIDSeed; // seed to generate a new ID diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index fd762b88b..6ec11e13e 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -1447,7 +1447,7 @@ void FECFilterBuiltin::RcvRebuild(Group& g, int32_t seqno, Group::Type tp) ; p.hdr[SRT_PH_TIMESTAMP] = g.timestamp_clip; - p.hdr[SRT_PH_ID] = rcv.id; + p.hdr[SRT_PH_ID] = int32_t(rcv.id); // Header ready, now we rebuild the contents // First, rebuild the length. diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 4c292cb89..f6a696349 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -236,7 +236,7 @@ CUDTGroup::SocketData* CUDTGroup::add(SocketData data) log << "CUDTGroup::add: taking MAX payload size from socket @" << data.ps->m_SocketID << ": " << plsize << " " << (plsize ? "(explicit)" : "(unspecified = fallback to 1456)")); if (plsize == 0) - plsize = SRT_LIVE_MAX_PLSIZE; + plsize = CPacket::srtPayloadSize(data.agent.family()); // It is stated that the payload size // is taken from first, and every next one // will get the same. @@ -249,10 +249,9 @@ CUDTGroup::SocketData* CUDTGroup::add(SocketData data) CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) : m_Global(CUDT::uglobal()) - , m_GroupID(-1) - , m_PeerGroupID(-1) + , m_GroupID(SRT_INVALID_SOCK) + , m_PeerGroupID(SRT_INVALID_SOCK) , m_type(gtype) - , m_listener() , m_iBusy() , m_iSndOldestMsgNo(SRT_MSGNO_NONE) , m_iSndAckedMsgNo(SRT_MSGNO_NONE) @@ -274,6 +273,7 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) , m_RcvBaseSeqNo(SRT_SEQNO_NONE) , m_bOpened(false) , m_bConnected(false) + , m_bPending(false) , m_bClosing(false) , m_iLastSchedSeqNo(SRT_SEQNO_NONE) , m_iLastSchedMsgNo(SRT_MSGNO_NONE) @@ -284,6 +284,8 @@ CUDTGroup::CUDTGroup(SRT_GROUP_TYPE gtype) m_RcvEID = m_Global.m_EPoll.create(&m_RcvEpolld); m_SndEID = m_Global.m_EPoll.create(&m_SndEpolld); + HLOGC(gmlog.Debug, log << "Group internal EID: R:E" << m_RcvEID << " W:E" << m_SndEID); + m_stats.init(); // Set this data immediately during creation before @@ -569,8 +571,8 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_FC, iFlightFlagSize); // Nonstandard - importTrivialOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); - importTrivialOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); + importTrivialOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::udpHeaderSize(AF_INET))); + importTrivialOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::udpHeaderSize(AF_INET))); IM(SRTO_LINGER, Linger); @@ -712,7 +714,7 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) case SRTO_SNDBUF: case SRTO_RCVBUF: - w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::UDP_HDR_SIZE)); + w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::udpHeaderSize(AF_INET))); break; case SRTO_LINGER: @@ -1073,7 +1075,7 @@ void CUDTGroup::close() // removing themselves from the group when closing because they // are unaware of being group members. m_Group.clear(); - m_PeerGroupID = -1; + m_PeerGroupID = SRT_INVALID_SOCK; set epollid; { @@ -1110,7 +1112,7 @@ void CUDTGroup::close() { try { - CUDT::uglobal().close(*i); + CUDT::uglobal().close(*i, SRT_CLS_INTERNAL); } catch (CUDTException&) { @@ -1913,7 +1915,7 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) return rstat; } -int CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) { if (!psize) return CUDT::APIError(MJ_NOTSUP, MN_INVAL); @@ -1924,7 +1926,7 @@ int CUDTGroup::getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize) } // [[using locked(this->m_GroupLock)]] -int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) +SRTSTATUS CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) { SRT_ASSERT(psize != NULL); const size_t size = *psize; @@ -1933,7 +1935,9 @@ int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) if (!pdata) { - return 0; + // The request was only to get the number of group members, + // already filled. + return SRT_STATUS_OK; } if (m_Group.size() > size) @@ -1948,7 +1952,7 @@ int CUDTGroup::getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize) copyGroupData(*d, (pdata[i])); } - return (int)m_Group.size(); + return SRT_STATUS_OK; } // [[using locked(this->m_GroupLock)]] @@ -1969,20 +1973,20 @@ void CUDTGroup::copyGroupData(const CUDTGroup::SocketData& source, SRT_SOCKGROUP if (source.sndstate == SRT_GST_RUNNING || source.rcvstate == SRT_GST_RUNNING) { - w_target.result = 0; + w_target.result = SRT_STATUS_OK; w_target.memberstate = SRT_GST_RUNNING; } // Stats can differ per direction only // when at least in one direction it's ACTIVE. else if (source.sndstate == SRT_GST_BROKEN || source.rcvstate == SRT_GST_BROKEN) { - w_target.result = -1; + w_target.result = SRT_ERROR; w_target.memberstate = SRT_GST_BROKEN; } else { // IDLE or PENDING - w_target.result = 0; + w_target.result = SRT_STATUS_OK; w_target.memberstate = source.sndstate; } @@ -2038,7 +2042,7 @@ void CUDTGroup::fillGroupData(SRT_MSGCTRL& w_out, // MSGCTRL to be written return; } - int st = getGroupData_LOCKED((grpdata), (&grpdata_size)); + SRTSTATUS st = getGroupData_LOCKED((grpdata), (&grpdata_size)); // Always write back the size, no matter if the data were filled. w_out.grpdata_size = grpdata_size; @@ -2196,6 +2200,7 @@ vector CUDTGroup::recv_WaitForReadReady(const vector& // This call may wait indefinite time, so GroupLock must be unlocked. InvertedLock ung (m_GroupLock); THREAD_PAUSED(); + HLOGC(grlog.Debug, log << "group/recv: e-polling E" << m_RcvEID << " timeout=" << timeout << "ms"); nready = m_Global.m_EPoll.swait(*m_RcvEpolld, sready, timeout, false /*report by retval*/); THREAD_RESUMED(); @@ -2466,6 +2471,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) LOGC(grlog.Error, log << "grp/recv: $" << id() << ": @" << ps->m_SocketID << ": SEQUENCE DISCREPANCY: base=%" << m_RcvBaseSeqNo << " vs pkt=%" << info.seqno << ", setting ESECFAIL"); + ps->core().setAgentCloseReason(SRT_CLS_ROGUE); ps->core().m_bBroken = true; broken.insert(ps); continue; @@ -2518,7 +2524,7 @@ int CUDTGroup::recv(char* buf, int len, SRT_MSGCTRL& w_mc) // This socket will not be socketToRead in the next turn because receiveMessage() return 0 here. continue; } - if (res == SRT_ERROR) + if (res == int(SRT_ERROR)) { LOGC(grlog.Warn, log << "grp/recv: $" << id() << ": @" << socketToRead->m_SocketID << ": " << srt_getlasterror_str() @@ -2640,7 +2646,7 @@ void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) // links and sending a single packet over these two links could be different. // These stats then don't make much sense in this form, this has to be // redesigned. We use the header size as per IPv4, as it was everywhere. - const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + const int pktHdrSize = CPacket::HDR_SIZE + CPacket::udpHeaderSize(AF_INET); memset(perf, 0, sizeof *perf); @@ -3578,7 +3584,7 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx HLOGC(gslog.Debug, log << "grp/sendBackup: swait/ex on @" << (id) << " while waiting for any writable socket - CLOSING"); - CUDT::uglobal().close(s); // << LOCKS m_GlobControlLock, then GroupLock! + CUDT::uglobal().close(s, SRT_CLS_INTERNAL); // << LOCKS m_GlobControlLock, then GroupLock! } else { @@ -3762,7 +3768,9 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) } // Only live streaming is supported - if (len > SRT_LIVE_MAX_PLSIZE) + // Also - as the group may use potentially IPv4 and IPv6 connections + // in the same group, use the size that fits both + if (len > SRT_MAX_PLSIZE_AF_INET6) { LOGC(gslog.Error, log << "grp/send(backup): buffer size=" << len << " exceeds maximum allowed in live mode"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -3962,7 +3970,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& SRT_ASSERT(w_nsuccessful == 0); SRT_ASSERT(w_maxActiveWeight == 0); - int group_send_result = SRT_ERROR; + int group_send_result = int(SRT_ERROR); // TODO: implement iterator over active links typedef vector::const_iterator const_iter_t; @@ -3976,7 +3984,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& // Remaining sndstate is SRT_GST_RUNNING. Send a payload through it. CUDT& u = d->ps->core(); const int32_t lastseq = u.schedSeqNo(); - int sndresult = SRT_ERROR; + int sndresult = int(SRT_ERROR); try { // This must be wrapped in try-catch because on error it throws an exception. @@ -3989,7 +3997,7 @@ int CUDTGroup::sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& { w_cx = e; erc = e.getErrorCode(); - sndresult = SRT_ERROR; + sndresult = int(SRT_ERROR); } const bool send_succeeded = sendBackup_CheckSendStatus( @@ -4028,7 +4036,7 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) // This should resend all packets if (m_SenderBuffer.empty()) { - LOGC(gslog.Fatal, log << "IPE: sendBackupRexmit: sender buffer empty"); + LOGC(gslog.Fatal, log << core.CONID() << "IPE: sendBackupRexmit: sender buffer empty"); // Although act as if it was successful, otherwise you'll get connection break return 0; @@ -4060,8 +4068,9 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) // packets that are in the past towards the scheduling sequence. skip_initial = -distance; LOGC(gslog.Warn, - log << "sendBackupRexmit: OVERRIDE attempt. Link seqno %" << core.schedSeqNo() << ", trying to send from seqno %" << curseq - << " - DENIED; skip " << skip_initial << " pkts, " << m_SenderBuffer.size() << " pkts in buffer"); + log << core.CONID() << "sendBackupRexmit: OVERRIDE attempt. Link seqno %" << core.schedSeqNo() + << ", trying to send from seqno %" << curseq << " - DENIED; skip " << skip_initial << " pkts, " + << m_SenderBuffer.size() << " pkts in buffer"); } else { @@ -4070,11 +4079,11 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) // sequence with it first so that they go hand-in-hand with // sequences already used by the link from which packets were // copied to the backup buffer. - IF_HEAVY_LOGGING(int32_t old = core.schedSeqNo()); - const bool su SRT_ATR_UNUSED = core.overrideSndSeqNo(curseq); - HLOGC(gslog.Debug, - log << "sendBackupRexmit: OVERRIDING seq %" << old << " with %" << curseq - << (su ? " - succeeded" : " - FAILED!")); + const int32_t old SRT_ATR_UNUSED = core.schedSeqNo(); + const bool success SRT_ATR_UNUSED = core.overrideSndSeqNo(curseq); + LOGC(gslog.Debug, + log << core.CONID() << "sendBackupRexmit: OVERRIDING seq %" << old << " with %" << curseq + << (success ? " - succeeded" : " - FAILED!")); } } @@ -4082,8 +4091,8 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) if (skip_initial >= m_SenderBuffer.size()) { LOGC(gslog.Warn, - log << "sendBackupRexmit: All packets were skipped. Nothing to send %" << core.schedSeqNo() << ", trying to send from seqno %" << curseq - << " - DENIED; skip " << skip_initial << " packets"); + log << core.CONID() << "sendBackupRexmit: All packets were skipped. Nothing to send %" << core.schedSeqNo() + << ", trying to send from seqno %" << curseq << " - DENIED; skip " << skip_initial << " packets"); return 0; // can't return any other state, nothing was sent } @@ -4099,14 +4108,16 @@ int CUDTGroup::sendBackupRexmit(CUDT& core, SRT_MSGCTRL& w_mc) { // Stop sending if one sending ended up with error LOGC(gslog.Warn, - log << "sendBackupRexmit: sending from buffer stopped at %" << core.schedSeqNo() << " and FAILED"); + log << core.CONID() << "sendBackupRexmit: sending from buffer stopped at %" << core.schedSeqNo() + << " and FAILED"); return -1; } } // Copy the contents of the last item being updated. w_mc = m_SenderBuffer.back().mc; - HLOGC(gslog.Debug, log << "sendBackupRexmit: pre-sent collected %" << curseq << " - %" << w_mc.pktseq); + HLOGC(gslog.Debug, + log << core.CONID() << "sendBackupRexmit: pre-sent collected %" << curseq << " - %" << w_mc.pktseq); return stat; } @@ -4198,7 +4209,8 @@ void CUDTGroup::internalKeepalive(SocketData* gli) } } -CUDTGroup::BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_LIVE_MAX_PLSIZE /*, 1000*/); +// Use the bigger size of SRT_MAX_PLSIZE to potentially fit both IPv4/6 +CUDTGroup::BufferedMessageStorage CUDTGroup::BufferedMessage::storage(SRT_MAX_PLSIZE_AF_INET /*, 1000*/); // Forwarder needed due to class definition order int32_t CUDTGroup::generateISN() @@ -4210,6 +4222,7 @@ void CUDTGroup::setGroupConnected() { if (!m_bConnected) { + HLOGC(cnlog.Debug, log << "GROUP: First socket connected, SETTING GROUP CONNECTED"); // Switch to connected state and give appropriate signal m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_CONNECT, true); m_bConnected = true; @@ -4282,6 +4295,16 @@ void CUDTGroup::updateLatestRcv(CUDTSocket* s) } } +void CUDTGroup::getMemberSockets(std::list& w_ids) const +{ + ScopedLock gl (m_GroupLock); + + for (cgli_t gi = m_Group.begin(); gi != m_Group.end(); ++gi) + { + w_ids.push_back(gi->id); + } +} + void CUDTGroup::activateUpdateEvent(bool still_have_items) { // This function actually reacts on the fact that a socket diff --git a/srtcore/group.h b/srtcore/group.h index 56f1456f8..6794d9d49 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -101,6 +101,7 @@ class CUDTGroup typedef std::list group_t; typedef group_t::iterator gli_t; + typedef group_t::const_iterator cgli_t; typedef std::vector< std::pair > sendable_t; struct Sendstate @@ -203,6 +204,11 @@ class CUDTGroup return m_Group.empty(); } + bool groupPending() + { + return m_bPending; + } + void setGroupConnected(); int send(const char* buf, int len, SRT_MSGCTRL& w_mc); @@ -372,8 +378,8 @@ class CUDTGroup void readyPackets(srt::CUDT* core, int32_t ack); void syncWithSocket(const srt::CUDT& core, const HandshakeSide side); - int getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); - int getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); + SRTSTATUS getGroupData(SRT_SOCKGROUPDATA* pdata, size_t* psize); + SRTSTATUS getGroupData_LOCKED(SRT_SOCKGROUPDATA* pdata, size_t* psize); /// Predicted to be called from the reading function to fill /// the group data array as requested. @@ -399,7 +405,7 @@ class CUDTGroup void getGroupCount(size_t& w_size, bool& w_still_alive); srt::CUDTUnited& m_Global; - srt::sync::Mutex m_GroupLock; + mutable srt::sync::Mutex m_GroupLock; SRTSOCKET m_GroupID; SRTSOCKET m_PeerGroupID; @@ -428,6 +434,8 @@ class CUDTGroup gli_t begin() { return m_List.begin(); } gli_t end() { return m_List.end(); } + cgli_t begin() const { return m_List.begin(); } + cgli_t end() const { return m_List.end(); } bool empty() { return m_List.empty(); } void push_back(const SocketData& data) { m_List.push_back(data); ++m_SizeCache; } void clear() @@ -442,7 +450,6 @@ class CUDTGroup }; GroupContainer m_Group; SRT_GROUP_TYPE m_type; - CUDTSocket* m_listener; // A "group" can only have one listener. srt::sync::atomic m_iBusy; CallbackHolder m_cbConnectHook; void installConnectHook(srt_connect_callback_fn* hook, void* opaq) @@ -661,8 +668,21 @@ class CUDTGroup // from the first delivering socket will be taken as a good deal. sync::atomic m_RcvBaseSeqNo; - bool m_bOpened; // Set to true when at least one link is at least pending - bool m_bConnected; // Set to true on first link confirmed connected + /// True: at least one socket has joined the group in at least pending state + bool m_bOpened; + + /// True: at least one socket is connected, even if pending from the listener + bool m_bConnected; + + /// True: this group was created on the listner side for the first socket + /// that is pending connection, so the group is about to be reported for the + /// srt_accept() call, but the application hasn't retrieved the group yet. + /// Not in use in case of caller-side groups. + // NOTE: using atomic in otder to allow this variable to be changed independently + // on any mutex locks. + sync::atomic m_bPending; + + /// True: the group was requested to close and it should not allow any operations. bool m_bClosing; // There's no simple way of transforming config @@ -736,13 +756,20 @@ class CUDTGroup // Required after the call on newGroup on the listener side. // On the listener side the group is lazily created just before // accepting a new socket and therefore always open. - void setOpen() { m_bOpened = true; } + // However, after creation it will be still waiting for being + // extracted by the application in `srt_accept`, and until then + // it stays as pending. + void setOpenPending() + { + m_bOpened = true; + m_bPending = true; + } std::string CONID() const { #if ENABLE_LOGGING std::ostringstream os; - os << "@" << m_GroupID << ":"; + os << "$" << int(m_GroupID) << ":"; return os.str(); #else return ""; @@ -796,6 +823,8 @@ class CUDTGroup void updateLatestRcv(srt::CUDTSocket*); + void getMemberSockets(std::list&) const; + // Property accessors SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, id, m_GroupID); SRTU_PROPERTY_RW_CHAIN(CUDTGroup, SRTSOCKET, peerid, m_PeerGroupID); diff --git a/srtcore/handshake.cpp b/srtcore/handshake.cpp index c97b4e2a3..079417d69 100644 --- a/srtcore/handshake.cpp +++ b/srtcore/handshake.cpp @@ -88,7 +88,7 @@ int srt::CHandShake::store_to(char* buf, size_t& w_size) *p++ = m_iMSS; *p++ = m_iFlightFlagSize; *p++ = int32_t(m_iReqType); - *p++ = m_iID; + *p++ = int32_t(m_iID); *p++ = m_iCookie; for (int i = 0; i < 4; ++ i) *p++ = m_piPeerIP[i]; @@ -111,7 +111,7 @@ int srt::CHandShake::load_from(const char* buf, size_t size) m_iMSS = *p++; m_iFlightFlagSize = *p++; m_iReqType = UDTRequestType(*p++); - m_iID = *p++; + m_iID = SRTSOCKET(*p++); m_iCookie = *p++; for (int i = 0; i < 4; ++ i) m_piPeerIP[i] = *p++; diff --git a/srtcore/handshake.h b/srtcore/handshake.h index c6a5731e4..9b4dc1c91 100644 --- a/srtcore/handshake.h +++ b/srtcore/handshake.h @@ -320,7 +320,7 @@ class CHandShake int32_t m_iMSS; // maximum segment size int32_t m_iFlightFlagSize; // flow control window size UDTRequestType m_iReqType; // handshake stage - int32_t m_iID; // SRT socket ID of HS sender + SRTSOCKET m_iID; // SRT socket ID of HS sender int32_t m_iCookie; // cookie uint32_t m_piPeerIP[4]; // The IP address that the peer's UDP port is bound to diff --git a/srtcore/logging.cpp b/srtcore/logging.cpp index d0ba3fd4a..cc9d14e1d 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,6 +23,69 @@ 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->write(msg.data(), msg.size()); + src_config->log_stream->flush(); + } + src_config->unlock(); +} + #if ENABLE_LOGGING diff --git a/srtcore/logging.h b/srtcore/logging.h index 7782245a2..4f6a76005 100644 --- a/srtcore/logging.h +++ b/srtcore/logging.h @@ -20,6 +20,7 @@ written by #include #include #include +#include #include #include #ifdef _WIN32 @@ -33,6 +34,7 @@ written by #include "utilities.h" #include "threadname.h" #include "logging_api.h" +#include "srt_compat.h" #include "sync.h" #ifdef __GNUC__ @@ -114,6 +116,7 @@ struct LogConfig void* loghandler_opaque; mutable srt::sync::Mutex mutex; int flags; + std::vector loggers; LogConfig(const fa_bitset_t& efa, LogLevel::type l = LogLevel::warning, @@ -136,6 +139,10 @@ struct LogConfig SRT_ATTR_RELEASE(mutex) void unlock() const { mutex.unlock(); } + + void subscribe(LogDispatcher*); + void unsubscribe(LogDispatcher*); + void updateLoggersState(); }; // The LogDispatcher class represents the object that is responsible for @@ -148,6 +155,7 @@ struct SRT_API LogDispatcher static const size_t MAX_PREFIX_SIZE = 32; char prefix[MAX_PREFIX_SIZE+1]; size_t prefix_len; + srt::sync::atomic enabled; LogConfig* src_config; bool isset(int flg) { return (src_config->flags & flg) != 0; } @@ -158,6 +166,7 @@ struct SRT_API LogDispatcher const char* logger_pfx /*[[nullable]]*/, LogConfig& config): fa(functional_area), level(log_level), + enabled(false), src_config(&config) { const size_t your_pfx_len = your_pfx ? strlen(your_pfx) : 0; @@ -185,13 +194,18 @@ struct SRT_API LogDispatcher prefix[0] = '\0'; prefix_len = 0; } + config.subscribe(this); + Update(); } ~LogDispatcher() { + src_config->unsubscribe(this); } - bool CheckEnabled(); + void Update(); + + bool CheckEnabled() { return enabled; } void CreateLogLinePrefix(std::ostringstream&); void SendLogLine(const char* file, int line, const std::string& area, const std::string& sl); @@ -415,24 +429,6 @@ 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 @@ -483,24 +479,6 @@ 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->write(msg.data(), msg.size()); - (*src_config->log_stream).flush(); - } - src_config->unlock(); -} - } #endif // INC_SRT_LOGGING_H diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index 180623039..4d3cc4b33 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -387,7 +387,7 @@ void CPacket::pack(UDTMessageType pkttype, const int32_t* lparam, void* rparam, case UMSG_SHUTDOWN: // 0101 - Shutdown // control info field should be none // but "writev" does not allow this - m_PacketVector[PV_DATA].set((void*)&m_extra_pad, 4); + m_PacketVector[PV_DATA].set(rparam, size); break; diff --git a/srtcore/packet.h b/srtcore/packet.h index 5a6d6eb15..2feaab1f9 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -309,7 +309,7 @@ class CPacket /// @return packet header field [2] (bit 0~31, bit 0-26 if SRT_DEBUG_TSBPD_WRAP). uint32_t getMsgTimeStamp() const; - sockaddr_any udpDestAddr() const { return m_DestAddr; } + CNetworkInterface udpDestAddr() const { return m_DestAddr; } #ifdef SRT_DEBUG_TSBPD_WRAP // Receiver static const uint32_t MAX_TIMESTAMP = 0x07FFFFFF; // 27 bit fast wraparound for tests (~2m15s) @@ -348,7 +348,7 @@ class CPacket int32_t m_extra_pad; bool m_data_owned; - sockaddr_any m_DestAddr; + CNetworkInterface m_DestAddr; size_t m_zCapacity; protected: @@ -371,16 +371,24 @@ class CPacket static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size = SRT_PH_E_SIZE * sizeof(uint32_t) - // Can also be calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr). - static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. - - static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; +private: // Do not disclose ingredients to the public + static const size_t UDP_HDR_SIZE = 8; // 8 bytes of UDP { u16 sport, dport, len, csum }. + static const size_t IPv4_HDR_SIZE = 20; // 20 bytes IPv4 + static const size_t IPv6_HDR_SIZE = 32; // 32 bytes IPv6 +public: + static inline size_t udpHeaderSize(int family) + { + return UDP_HDR_SIZE + (family == AF_INET ? IPv4_HDR_SIZE : IPv6_HDR_SIZE); + } + static inline size_t srtPayloadSize(int family) + { + return ETH_MAX_MTU_SIZE - (family == AF_INET ? IPv4_HDR_SIZE : IPv6_HDR_SIZE) - UDP_HDR_SIZE - HDR_SIZE; + } // Maximum transmission unit size. 1500 in case of Ethernet II (RFC 1191). static const size_t ETH_MAX_MTU_SIZE = 1500; // Maximum payload size of an SRT packet. - static const size_t SRT_MAX_PAYLOAD_SIZE = ETH_MAX_MTU_SIZE - SRT_DATA_HDR_SIZE; // Packet interface char* data() { return m_pcData; } diff --git a/srtcore/packetfilter_api.h b/srtcore/packetfilter_api.h index 3bfba7c76..ef0d8867f 100644 --- a/srtcore/packetfilter_api.h +++ b/srtcore/packetfilter_api.h @@ -66,7 +66,7 @@ struct SrtFilterInitializer struct SrtPacket { uint32_t hdr[SRT_PH_E_SIZE]; - char buffer[SRT_LIVE_MAX_PLSIZE]; + char buffer[SRT_MAX_PLSIZE_AF_INET]; // Using this as the bigger one (this for AF_INET6 is smaller) size_t length; SrtPacket(size_t size): length(size) diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 6cb4faeb1..89f501ac3 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -584,7 +584,7 @@ void* srt::CSndQueue::worker(void* param) // pack a packet from the socket CPacket pkt; steady_clock::time_point next_send_time; - sockaddr_any source_addr; + CNetworkInterface source_addr; const bool res = u->packData((pkt), (next_send_time), (source_addr)); // Check if extracted anything to send @@ -608,7 +608,7 @@ void* srt::CSndQueue::worker(void* param) return NULL; } -int srt::CSndQueue::sendto(const sockaddr_any& addr, CPacket& w_packet, const sockaddr_any& src) +int srt::CSndQueue::sendto(const sockaddr_any& addr, CPacket& w_packet, const CNetworkInterface& src) { // send out the packet immediately (high priority), this is a control packet // NOTE: w_packet is passed by mutable reference because this function will do @@ -742,10 +742,10 @@ void srt::CHash::init(int size) m_iHashSize = size; } -srt::CUDT* srt::CHash::lookup(int32_t id) +srt::CUDT* srt::CHash::lookup(SRTSOCKET id) { // simple hash function (% hash table size); suitable for socket descriptors - CBucket* b = m_pBucket[id % m_iHashSize]; + CBucket* b = bucketAt(id); while (NULL != b) { @@ -757,21 +757,32 @@ srt::CUDT* srt::CHash::lookup(int32_t id) return NULL; } -void srt::CHash::insert(int32_t id, CUDT* u) +srt::CUDT* srt::CHash::lookupPeer(SRTSOCKET peerid) { - CBucket* b = m_pBucket[id % m_iHashSize]; + // Decode back the socket ID if it has that peer + SRTSOCKET id = map_get(m_RevPeerMap, peerid, SRT_INVALID_SOCK); + if (id == SRT_INVALID_SOCK) + return NULL; // no such peer id + return lookup(id); +} + +void srt::CHash::insert(SRTSOCKET id, CUDT* u) +{ + CBucket* b = bucketAt(id); CBucket* n = new CBucket; n->m_iID = id; + n->m_iPeerID = u->peerID(); n->m_pUDT = u; n->m_pNext = b; - m_pBucket[id % m_iHashSize] = n; + bucketAt(id) = n; + m_RevPeerMap[u->peerID()] = id; } -void srt::CHash::remove(int32_t id) +void srt::CHash::remove(SRTSOCKET id) { - CBucket* b = m_pBucket[id % m_iHashSize]; + CBucket* b = bucketAt(id); CBucket* p = NULL; while (NULL != b) @@ -779,10 +790,11 @@ void srt::CHash::remove(int32_t id) if (id == b->m_iID) { if (NULL == p) - m_pBucket[id % m_iHashSize] = b->m_pNext; + bucketAt(id) = b->m_pNext; else p->m_pNext = b->m_pNext; + m_RevPeerMap.erase(b->m_iPeerID); delete b; return; @@ -842,12 +854,12 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& { ScopedLock vg(m_RIDListLock); - IF_HEAVY_LOGGING(const char* const id_type = w_id ? "THIS ID" : "A NEW CONNECTION"); + IF_HEAVY_LOGGING(const char* const id_type = w_id == SRT_SOCKID_CONNREQ ? "A NEW CONNECTION" : "THIS ID" ); // TODO: optimize search for (list::const_iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++i) { - if (i->m_PeerAddr == addr && ((w_id == 0) || (w_id == i->m_iID))) + if (i->m_PeerAddr == addr && ((w_id == SRT_SOCKID_CONNREQ) || (w_id == i->m_iID))) { // This procedure doesn't exactly respond to the original UDT idea. // As the "rendezvous queue" is used for both handling rendezvous and @@ -866,7 +878,7 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& // This means: if an incoming ID is 0, then this search should succeed ONLY // IF THE FOUND SOCKET WAS RENDEZVOUS. - if (!w_id && !i->m_pUDT->m_config.bRendezvous) + if (w_id == SRT_SOCKID_CONNREQ && !i->m_pUDT->m_config.bRendezvous) { HLOGC(cnlog.Debug, log << "RID: found id @" << i->m_iID << " while looking for " @@ -885,7 +897,7 @@ srt::CUDT* srt::CRendezvousQueue::retrieve(const sockaddr_any& addr, SRTSOCKET& #if ENABLE_HEAVY_LOGGING std::ostringstream spec; - if (w_id == 0) + if (w_id == SRT_SOCKID_CONNREQ) spec << "A NEW CONNECTION REQUEST"; else spec << " AGENT @" << w_id; @@ -905,7 +917,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->id() : 0; + const SRTSOCKET dest_id = pkt ? pkt->id() : SRT_SOCKID_CONNREQ; // If no socket were qualified for further handling, finish here. // Otherwise toRemove and toProcess contain items to handle. @@ -946,7 +958,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst } - if (cst != CONN_RENDEZVOUS && dest_id != 0) + if (cst != CONN_RENDEZVOUS && dest_id != SRT_SOCKID_CONNREQ) { if (i->id != dest_id) { @@ -978,7 +990,8 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst LinkStatusInfo fi = *i; fi.errorcode = SRT_ECONNREJ; toRemove.push_back(fi); - i->u->sendCtrl(UMSG_SHUTDOWN); + uint32_t res[1] = {SRT_CLS_DEADLSN}; + i->u->sendCtrl(UMSG_SHUTDOWN, NULL, res, sizeof res); } } @@ -1045,7 +1058,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, EConnectStatus cst SRT_ATR_UNUSED, - int iDstSockID, + SRTSOCKET iDstSockID, vector& toRemove, vector& toProcess) { @@ -1183,7 +1196,7 @@ srt::CRcvQueue::~CRcvQueue() delete m_pRendezvousQueue; // remove all queued messages - for (map >::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++i) + for (qmap_t::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++i) { while (!i->second.empty()) { @@ -1232,7 +1245,7 @@ void* srt::CRcvQueue::worker(void* param) { CRcvQueue* self = (CRcvQueue*)param; sockaddr_any sa(self->getIPversion()); - int32_t id = 0; + SRTSOCKET id = SRT_SOCKID_CONNREQ; std::string thname; ThreadName::get(thname); @@ -1248,7 +1261,7 @@ void* srt::CRcvQueue::worker(void* param) INCREMENT_THREAD_ITERATIONS(); if (rst == RST_OK) { - if (id < 0) + if (int(id) < 0) // Any negative (illegal range) and SRT_INVALID_SOCKET { // User error on peer. May log something, but generally can only ignore it. // XXX Think maybe about sending some "connection rejection response". @@ -1265,7 +1278,7 @@ void* srt::CRcvQueue::worker(void* param) // Note to rendezvous connection. This can accept: // - ID == 0 - take the first waiting rendezvous socket // - ID > 0 - find the rendezvous socket that has this ID. - if (id == 0) + if (id == SRT_SOCKID_CONNREQ) { // ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets cst = self->worker_ProcessConnectionRequest(unit, sa); @@ -1361,7 +1374,7 @@ void* srt::CRcvQueue::worker(void* param) return NULL; } -srt::EReadStatus srt::CRcvQueue::worker_RetrieveUnit(int32_t& w_id, CUnit*& w_unit, sockaddr_any& w_addr) +srt::EReadStatus srt::CRcvQueue::worker_RetrieveUnit(SRTSOCKET& w_id, CUnit*& w_unit, sockaddr_any& w_addr) { #if !USE_BUSY_WAITING // This might be not really necessary, and probably @@ -1457,12 +1470,76 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, << " result:" << RequestTypeStr(UDTRequestType(listener_ret))); return listener_ret == SRT_REJ_UNKNOWN ? CONN_CONTINUE : CONN_REJECT; } + else + { + if (worker_TryAcceptedSocket(unit, addr)) + { + HLOGC(cnlog.Debug, log << "connection request to an accepted socket succeeded"); + return CONN_CONTINUE; + } + else + { + HLOGC(cnlog.Debug, log << "connection request to an accepted socket failed. Will retry RDV or store"); + } + } // If there's no listener waiting for the packet, just store it into the queue. - return worker_TryAsyncRend_OrStore(0, unit, addr); // 0 id because the packet came in with that very ID. + // Passing SRT_SOCKID_CONNREQ explicitly because it's a handler for a HS packet + // that came for this very ID. + return worker_TryAsyncRend_OrStore(SRT_SOCKID_CONNREQ, unit, addr); } -srt::EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& addr) +bool srt::CRcvQueue::worker_TryAcceptedSocket(CUnit* unit, const sockaddr_any& addr) +{ + // We are working with a possibly HS packet... check that. + CPacket& pkt = unit->m_Packet; + + if (pkt.getLength() < CHandShake::m_iContentSize || !pkt.isControl(UMSG_HANDSHAKE)) + return false; + + CHandShake hs; + if (0 != hs.load_from(pkt.data(), pkt.size())) + return false; + + if (hs.m_iReqType != URQ_CONCLUSION) + return false; + + if (hs.m_iVersion >= CUDT::HS_VERSION_SRT1) + hs.m_extension = true; + + // Ok, at last we have a peer ID info + SRTSOCKET peerid = hs.m_iID; + + // Now search for a socket that has this peer ID + CUDT* u = m_pHash->lookupPeer(peerid); + if (!u) + return false; // no socket has that peer in this multiplexer + + HLOGC(cnlog.Debug, log << "FOUND accepted socket @" << u->m_SocketID << " that is a peer for -@" + << peerid << " - DISPATCHING to it to resend HS response"); + + uint32_t kmdata[SRTDATA_MAXSIZE]; + size_t kmdatasize = SRTDATA_MAXSIZE; + if (u->craftKmResponse((kmdata), (kmdatasize)) != CONN_ACCEPT) + { + HLOGC(cnlog.Debug, log << "craftKmResponse: failed"); + return false; + } + + // addr should be unnecessary because the accepted socket should have + // it in its data. However, if it happened that this is a different + // address, do not send (could be some kind of attack). + if (addr != u->m_PeerAddr) + { + HLOGC(cnlog.Debug, log << "worker_TryAcceptedSocket: accepted socket has a different address: " + << u->m_PeerAddr.str() << " than the incoming HS request: " << addr.str() << " - POSSIBLE ATTACK"); + return false; + } + + return u->createSendHSResponse(kmdata, kmdatasize, pkt.udpDestAddr(), (hs)); +} + +srt::EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(SRTSOCKET id, CUnit* unit, const sockaddr_any& addr) { CUDT* u = m_pHash->lookup(id); if (!u) @@ -1520,7 +1597,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CU // This function then tries to manage the packet as a rendezvous connection // request in ASYNC mode; when this is not applicable, it stores the packet // in the "receiving queue" so that it will be picked up in the "main" thread. -srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& addr) +srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(SRTSOCKET id, CUnit* unit, const sockaddr_any& addr) { // This 'retrieve' requires that 'id' be either one of those // stored in the rendezvous queue (see CRcvQueue::registerConnector) @@ -1541,7 +1618,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUni // not belonging to the connection and not registered as rendezvous) as "possible // attack" and ignore it. This also should better protect the rendezvous socket // against a rogue connector. - if (id == 0) + if (id == SRT_SOCKID_CONNREQ) { HLOGC(cnlog.Debug, log << CONID() << "AsyncOrRND: no sockets expect connection from " << addr.str() @@ -1667,11 +1744,11 @@ void srt::CRcvQueue::stopWorker() m_WorkerThread.join(); } -int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet) +int srt::CRcvQueue::recvfrom(SRTSOCKET id, CPacket& w_packet) { CUniqueSync buffercond(m_BufferLock, m_BufferCond); - map >::iterator i = m_mBuffer.find(id); + qmap_t::iterator i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { @@ -1750,7 +1827,7 @@ void srt::CRcvQueue::removeConnector(const SRTSOCKET& id) ScopedLock bufferlock(m_BufferLock); - map >::iterator i = m_mBuffer.find(id); + qmap_t::iterator i = m_mBuffer.find(id); if (i != m_mBuffer.end()) { HLOGC(cnlog.Debug, @@ -1790,11 +1867,16 @@ srt::CUDT* srt::CRcvQueue::getNewEntry() return u; } -void srt::CRcvQueue::storePktClone(int32_t id, const CPacket& pkt) +void srt::CRcvQueue::kick() +{ + CSync::lock_notify_all(m_BufferCond, m_BufferLock); +} + +void srt::CRcvQueue::storePktClone(SRTSOCKET id, const CPacket& pkt) { CUniqueSync passcond(m_BufferLock, m_BufferCond); - map >::iterator i = m_mBuffer.find(id); + qmap_t::iterator i = m_mBuffer.find(id); if (i == m_mBuffer.end()) { diff --git a/srtcore/queue.h b/srtcore/queue.h index 48bedd9af..0e2680e57 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -279,23 +279,30 @@ class CHash /// @param [in] id socket ID /// @return Pointer to a UDT instance, or NULL if not found. - CUDT* lookup(int32_t id); + CUDT* lookup(SRTSOCKET id); + + /// Look for a UDT instance from the hash table by source ID + /// @param [in] peerid socket ID of the peer reported as source ID + /// @return Pointer to a UDT instance where m_PeerID == peerid, or NULL if not found + + CUDT* lookupPeer(SRTSOCKET peerid); /// Insert an entry to the hash table. /// @param [in] id socket ID /// @param [in] u pointer to the UDT instance - void insert(int32_t id, CUDT* u); + void insert(SRTSOCKET id, CUDT* u); /// Remove an entry from the hash table. /// @param [in] id socket ID - void remove(int32_t id); + void remove(SRTSOCKET id); private: struct CBucket { - int32_t m_iID; // Socket ID + SRTSOCKET m_iID; // Socket ID + SRTSOCKET m_iPeerID; // Peer ID CUDT* m_pUDT; // Socket instance CBucket* m_pNext; // next bucket @@ -303,6 +310,13 @@ class CHash int m_iHashSize; // size of hash table + std::map m_RevPeerMap; + + CBucket*& bucketAt(SRTSOCKET id) + { + return m_pBucket[int32_t(id) % m_iHashSize]; + } + private: CHash(const CHash&); CHash& operator=(const CHash&); @@ -374,7 +388,7 @@ class CRendezvousQueue /// @param[in,out] toProcess stores sockets which should repeat (resend) HS connection request. bool qualifyToHandle(EReadStatus rst, EConnectStatus cst, - int iDstSockID, + SRTSOCKET iDstSockID, std::vector& toRemove, std::vector& toProcess); @@ -420,7 +434,7 @@ class CSndQueue /// @param [in,ref] packet packet to be sent out /// @param [in] src The source IP address (details above) /// @return Size of data sent out. - int sendto(const sockaddr_any& addr, CPacket& packet, const sockaddr_any& src); + int sendto(const sockaddr_any& addr, CPacket& packet, const CNetworkInterface& src); /// Get the IP TTL. /// @param [in] ttl IP Time To Live. @@ -505,7 +519,7 @@ class CRcvQueue /// @param [in] id Socket ID /// @param [out] packet received packet /// @return Data size of the packet - int recvfrom(int32_t id, CPacket& to_packet); + int recvfrom(SRTSOCKET id, CPacket& to_packet); void stopWorker(); @@ -517,10 +531,11 @@ class CRcvQueue static void* worker(void* param); sync::CThread m_WorkerThread; // Subroutines of worker - EReadStatus worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr_any& sa); + EReadStatus worker_RetrieveUnit(SRTSOCKET& id, CUnit*& unit, sockaddr_any& sa); EConnectStatus worker_ProcessConnectionRequest(CUnit* unit, const sockaddr_any& sa); - EConnectStatus worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr_any& sa); - EConnectStatus worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr_any& sa); + EConnectStatus worker_TryAsyncRend_OrStore(SRTSOCKET id, CUnit* unit, const sockaddr_any& sa); + EConnectStatus worker_ProcessAddressedPacket(SRTSOCKET id, CUnit* unit, const sockaddr_any& sa); + bool worker_TryAcceptedSocket(CUnit* unit, const sockaddr_any& addr); private: CUnitQueue* m_pUnitQueue; // The received packet queue @@ -551,7 +566,9 @@ class CRcvQueue bool ifNewEntry(); CUDT* getNewEntry(); - void storePktClone(int32_t id, const CPacket& pkt); + void storePktClone(SRTSOCKET id, const CPacket& pkt); + + void kick(); private: sync::CSharedObjectPtr m_pListener; // pointer to the (unique, if any) listening UDT entity @@ -560,9 +577,10 @@ class CRcvQueue std::vector m_vNewEntry; // newly added entries, to be inserted sync::Mutex m_IDLock; - std::map > m_mBuffer; // temporary buffer for rendezvous connection request - sync::Mutex m_BufferLock; - sync::Condition m_BufferCond; + typedef std::map > qmap_t; + qmap_t m_mBuffer; // temporary buffer for rendezvous connection request + sync::Mutex m_BufferLock; + sync::Condition m_BufferCond; private: CRcvQueue(const CRcvQueue&); @@ -592,7 +610,7 @@ struct CMultiplexer , m_pChannel(NULL) , m_pTimer(NULL) , m_iPort(0) - , m_iIPversion(0) + , m_iIPversion(AF_UNSPEC) , m_iRefCount(1) , m_iID(-1) { diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index a0c8a6a47..b4909495c 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -57,7 +57,10 @@ namespace srt int RcvBufferSizeOptionToValue(int val, int flightflag, int mss) { // Mimimum recv buffer size is 32 packets - const int mssin_size = mss - CPacket::UDP_HDR_SIZE; + // We take the size per packet as maximum allowed for AF_INET, + // as we don't know which one is used, and this requires more + // space than AF_INET6. + const int mssin_size = mss - CPacket::udpHeaderSize(AF_INET); int bufsize; if (val > mssin_size * CSrtConfig::DEF_MIN_FLIGHT_PKT) @@ -90,9 +93,15 @@ struct CSrtConfigSetter { static void set(CSrtConfig& co, const void* optval, int optlen) { + using namespace srt_logging; const int ival = cast_optval(optval, optlen); - if (ival < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) + const int handshake_size = CHandShake::m_iContentSize + (sizeof(uint32_t) * SRT_HS_E_SIZE); + const int minval = int(CPacket::udpHeaderSize(AF_INET6) + CPacket::HDR_SIZE + handshake_size); + if (ival < minval) + { + LOGC(kmlog.Error, log << "SRTO_MSS: minimum value allowed is " << minval << " = [IPv6][UDP][SRT] headers + minimum SRT handshake"); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } co.iMSS = ival; @@ -130,7 +139,7 @@ struct CSrtConfigSetter if (bs <= 0) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - co.iSndBufSize = bs / (co.iMSS - CPacket::UDP_HDR_SIZE); + co.iSndBufSize = bs / co.bytesPerPkt(); } }; @@ -144,6 +153,16 @@ struct CSrtConfigSetter throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); co.iRcvBufSize = srt::RcvBufferSizeOptionToValue(val, co.iFlightFlagSize, co.iMSS); + const int mssin_size = co.bytesPerPkt(); + + if (val > mssin_size * co.DEF_MIN_FLIGHT_PKT) + co.iRcvBufSize = val / mssin_size; + else + co.iRcvBufSize = co.DEF_MIN_FLIGHT_PKT; + + // recv buffer MUST not be greater than FC size + if (co.iRcvBufSize > co.iFlightFlagSize) + co.iRcvBufSize = co.iFlightFlagSize; } }; @@ -621,9 +640,13 @@ struct CSrtConfigSetter throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - if (val > SRT_LIVE_MAX_PLSIZE) + // We don't know at this point, how bit the payloadsize can be set, + // so we limit it to the biggest value of the two. + // When this payloadsize would be then too big to be used with given MSS and IPv6, + // this problem should be reported appropriately from srt_connect and srt_bind calls. + if (val > SRT_MAX_PLSIZE_AF_INET) { - LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE << ", maximum payload per MTU."); + LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_MAX_PLSIZE_AF_INET << ", maximum payload per MTU."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } @@ -826,7 +849,7 @@ struct CSrtConfigSetter throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; + size_t efc_max_payload_size = SRT_MAX_PLSIZE_AF_INET - fc.extra_size; if (co.zExpPayloadSize > efc_max_payload_size) { LOGC(aclog.Warn, @@ -889,7 +912,7 @@ struct CSrtConfigSetter } }; -#ifdef ENABLE_AEAD_API_PREVIEW +#if defined(ENABLE_AEAD_API_PREVIEW) && defined(SRT_ENABLE_ENCRYPTION) template<> struct CSrtConfigSetter { @@ -897,7 +920,6 @@ struct CSrtConfigSetter { using namespace srt_logging; const int val = cast_optval(optval, optlen); -#ifdef SRT_ENABLE_ENCRYPTION if (val < CSrtConfig::CIPHER_MODE_AUTO || val > CSrtConfig::CIPHER_MODE_AES_GCM) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -914,11 +936,22 @@ struct CSrtConfigSetter } co.iCryptoMode = val; + + } +}; +#else +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& , const void* , int ) + { + using namespace srt_logging; +#ifdef SRT_ENABLE_ENCRYPTION + LOGC(aclog.Error, log << "SRT was built without AEAD enabled."); #else LOGC(aclog.Error, log << "SRT was built without crypto module."); - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); #endif - + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } }; #endif @@ -979,9 +1012,7 @@ int dispatchSet(SRT_SOCKOPT optName, CSrtConfig& co, const void* optval, int opt DISPATCH(SRTO_IPV6ONLY); DISPATCH(SRTO_PACKETFILTER); DISPATCH(SRTO_RETRANSMITALGO); -#ifdef ENABLE_AEAD_API_PREVIEW - DISPATCH(SRTO_CRYPTOMODE); -#endif + DISPATCH(SRTO_CRYPTOMODE); // STUB if not supported #ifdef ENABLE_MAXREXMITBW DISPATCH(SRTO_MAXREXMITBW); #endif @@ -999,40 +1030,54 @@ int CSrtConfig::set(SRT_SOCKOPT optName, const void* optval, int optlen) return dispatchSet(optName, *this, optval, optlen); } -bool CSrtConfig::payloadSizeFits(size_t val, int /*ip_family*/, std::string& w_errmsg) ATR_NOTHROW +int CSrtConfig::extraPayloadReserve(std::string& w_info) ATR_NOTHROW { + int resv = 0; + if (!this->sPacketFilterConfig.empty()) { // This means that the filter might have been installed before, // and the fix to the maximum payload size was already applied. // This needs to be checked now. SrtFilterConfig fc; - if (!ParseFilterConfig(this->sPacketFilterConfig.str(), fc)) + if (!ParseFilterConfig(this->sPacketFilterConfig.str(), (fc))) { // Break silently. This should not happen - w_errmsg = "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"; - return false; + w_info = "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed"; + return -1; } - const size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size; - if (size_t(val) > efc_max_payload_size) - { - std::ostringstream log; - log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE << " bytes decreased by " << fc.extra_size - << " required for packet filter header"; - w_errmsg = log.str(); - return false; - } + resv += fc.extra_size; + w_info = "Packet Filter"; + } + + if (this->iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) + { + resv += HAICRYPT_AUTHTAG_MAX; + if (!w_info.empty()) + w_info += " and "; + w_info += "AES_GCM mode"; } - // Not checking AUTO to allow defaul 1456 bytes. - if ((this->iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) - && (val > (SRT_LIVE_MAX_PLSIZE - HAICRYPT_AUTHTAG_MAX))) + return resv; +} + +bool CSrtConfig::payloadSizeFits(size_t val, int ip_family, std::string& w_errmsg) ATR_NOTHROW +{ + int resv = extraPayloadReserve((w_errmsg)); + if (resv == -1) + return false; + + size_t valmax = CPacket::srtPayloadSize(ip_family) - resv; + + if (val > valmax) { std::ostringstream log; - log << "SRTO_PAYLOADSIZE: value exceeds " << SRT_LIVE_MAX_PLSIZE - << " bytes decreased by " << HAICRYPT_AUTHTAG_MAX - << " required for AES-GCM."; + log << "SRTO_PAYLOADSIZE: value " << val << "exceeds " << valmax + << " bytes"; + if (!w_errmsg.empty()) + log << " as limited by " << w_errmsg; + w_errmsg = log.str(); return false; } diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index 0f6c79887..1d4666489 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -56,6 +56,7 @@ written by #include #endif #include +#include "srt.h" #include "haicrypt.h" #include "congctl.h" #include "packet.h" @@ -214,8 +215,8 @@ struct CSrtConfig: CSrtMuxerConfig // Mimimum recv flight flag size is 32 packets static const int DEF_MIN_FLIGHT_PKT = 32; - static const size_t MAX_SID_LENGTH = 512; - static const size_t MAX_PFILTER_LENGTH = 64; + static const size_t MAX_SID_LENGTH = SRT_STREAMID_MAX; + static const size_t MAX_PFILTER_LENGTH = SRT_PACKETFILTER_MAX; static const size_t MAX_CONG_LENGTH = 16; int iMSS; // Maximum Segment Size, in bytes @@ -355,7 +356,9 @@ struct CSrtConfig: CSrtMuxerConfig // This function returns the number of bytes that are allocated // for a single packet in the sender and receiver buffer. - int bytesPerPkt() const { return iMSS - int(CPacket::UDP_HDR_SIZE); } + int bytesPerPkt() const { return iMSS - int(CPacket::udpHeaderSize(AF_INET)); } + + int extraPayloadReserve(std::string& w_errmsg) ATR_NOTHROW; }; template diff --git a/srtcore/srt.h b/srtcore/srt.h index 71ac2c3af..7eb161f5b 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -16,6 +16,12 @@ written by #ifndef INC_SRTC_H #define INC_SRTC_H +// Behavior-controlling macros that can be set in the command line: +// +// * SRT_DYNAMIC: Define when creating a dynamic library on Windows +// * SRT_EXPORTS: Define when compiling SRT as dynamic on Windows +// * SRT_NO_DEPRECATED: Disable warnings for deprecated API + #ifndef SRT_API #ifdef _WIN32 #ifdef SRT_DYNAMIC @@ -53,10 +59,19 @@ written by // You can use these constants with SRTO_MINVERSION option. #define SRT_VERSION_FEAT_HSv5 0x010300 -#if defined(__cplusplus) && __cplusplus > 201406 +#ifdef __cplusplus + +#if __cplusplus > 201406L #define SRT_HAVE_CXX17 1 +#define SRT_HAVE_CXX11 1 +#elif __cplusplus > 199711L +#define SRT_HAVE_CXX17 0 +#define SRT_HAVE_CXX11 1 #else #define SRT_HAVE_CXX17 0 +#define SRT_HAVE_CXX11 0 +#endif + #endif @@ -109,25 +124,45 @@ written by #define SRT_ATR_DEPRECATED #define SRT_ATR_DEPRECATED_PX [[deprecated]] +#define SRT_ATR_NODISCARD [[nodiscard]] // GNUG is GNU C/C++; this syntax is also supported by Clang #elif defined(__GNUC__) #define SRT_ATR_DEPRECATED_PX #define SRT_ATR_DEPRECATED __attribute__((deprecated)) +#define SRT_ATR_NODISCARD __attribute__((warn_unused_result)) #elif defined(_MSC_VER) #define SRT_ATR_DEPRECATED_PX __declspec(deprecated) #define SRT_ATR_DEPRECATED // no postfix-type modifier +#define SRT_ATR_NODISCARD _Check_return_ #else #define SRT_ATR_DEPRECATED_PX #define SRT_ATR_DEPRECATED +#define SRT_ATR_NODISCARD +#endif + +// The SRT_TEST_FORCED_CONSTANT macro enables the strict +// version of SRTSOCKET and SRTSTATUS types. This allows you +// to detect constant type violations by compiling the code +// with the C++20 compliant compiler and the following cmake +// variables set: +// * USE_CXX_STD=c++20 +// * ENFORCE_SRT_TEST_FORCED_CONSTANT=1 +#ifndef SRT_TEST_FORCED_CONSTANT +// This is normal and should be normally used. +typedef int32_t SRTSOCKET; +typedef int SRTSTATUS; +typedef int SRTRUNSTATUS; +#else +// Used for development only. +#include "../common/devel_util.h" #endif + #ifdef __cplusplus extern "C" { #endif -typedef int32_t SRTSOCKET; - // The most significant bit 31 (sign bit actually) is left unused, // so that all people who check the value for < 0 instead of -1 // still get what they want. The bit 30 is reserved for marking @@ -142,10 +177,6 @@ static const int32_t SRTGROUP_MASK = (1 << 30); typedef int SYSSOCKET; #endif -#ifndef ENABLE_BONDING -#define ENABLE_BONDING 0 -#endif - typedef SYSSOCKET UDPSOCKET; @@ -227,12 +258,8 @@ typedef enum SRT_SOCKOPT { SRTO_GROUPTYPE, // Group type to which an accepted socket is about to be added, available in the handshake (ENABLE_BONDING) SRTO_PACKETFILTER = 60, // Add and configure a packet filter SRTO_RETRANSMITALGO = 61, // An option to select packet retransmission algorithm -#ifdef ENABLE_AEAD_API_PREVIEW SRTO_CRYPTOMODE = 62, // Encryption cipher mode (AES-CTR, AES-GCM, ...). -#endif -#ifdef ENABLE_MAXREXMITBW SRTO_MAXREXMITBW = 63, // Maximum bandwidth limit for retransmision (Bytes/s) -#endif SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; @@ -240,33 +267,29 @@ typedef enum SRT_SOCKOPT { #ifdef __cplusplus - -#if __cplusplus > 199711L // C++11 +#if SRT_HAVE_CXX11 // Newer compilers report error when [[deprecated]] is applied to types, // and C++11 and higher uses this. // Note that this doesn't exactly use the 'deprecated' attribute, // as it's introduced in C++14. What is actually used here is the // fact that unknown attributes are ignored, but still warned about. // This should only catch an eye - and that's what it does. -#define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT [[deprecated]])value) + #define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT [[deprecated]])value) #else // Older (pre-C++11) compilers use gcc deprecated applied to a typedef typedef SRT_ATR_DEPRECATED_PX SRT_SOCKOPT SRT_SOCKOPT_DEPRECATED SRT_ATR_DEPRECATED; -#define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT_DEPRECATED)value) + #define SRT_DEPRECATED_OPTION(value) ((SRT_SOCKOPT_DEPRECATED)value) #endif - -#else +#else // C version // deprecated enum labels are supported only since gcc 6, so in C there // will be a whole deprecated enum type, as it's not an error in C to mix // enum types enum SRT_ATR_DEPRECATED SRT_SOCKOPT_DEPRECATED { - // Dummy last option, as every entry ends with a comma SRTO_DEPRECATED_END = 0 - }; #define SRT_DEPRECATED_OPTION(value) ((enum SRT_SOCKOPT_DEPRECATED)value) #endif @@ -275,6 +298,7 @@ enum SRT_ATR_DEPRECATED SRT_SOCKOPT_DEPRECATED // stays so that it can be used in future. Example: // #define SRTO_STRICTENC SRT_DEPRECATED_OPTION(53) +// Values used for SRTO_TRANSTYPE option typedef enum SRT_TRANSTYPE { SRTT_LIVE, @@ -288,13 +312,27 @@ typedef enum SRT_TRANSTYPE // This is for MPEG TS and it's a default SRTO_PAYLOADSIZE for SRTT_LIVE. static const int SRT_LIVE_DEF_PLSIZE = 1316; // = 188*7, recommended for MPEG TS -// This is the maximum payload size for Live mode, should you have a different -// payload type than MPEG TS. -static const int SRT_LIVE_MAX_PLSIZE = 1456; // MTU(1500) - UDP.hdr(28) - SRT.hdr(16) +// DEPRECATED. Use one of these below instead. +SRT_ATR_DEPRECATED_PX static const int SRT_LIVE_MAX_PLSIZE SRT_ATR_DEPRECATED = 1456; // MTU(1500) - UDP.hdr(28) - SRT.hdr(16) + +// These constants define the maximum size of the payload +// in a single UDP packet, depending on the IP version, and +// with the default socket options, that is: +// * default 1500 bytes of MTU (see SRTO_MSS) +// * without FEC packet filter (see SRTO_PACKETFILTER) +// * without AEAD through AES-GCM (see SRTO_CRYPTOMODE) +static const int SRT_MAX_PLSIZE_AF_INET = 1456; // MTU(1500) - IPv4.hdr(20) - UDP.hdr(8) - SRT.hdr(16) +static const int SRT_MAX_PLSIZE_AF_INET6 = 1444; // MTU(1500) - IPv6.hdr(32) - UDP.hdr(8) - SRT.hdr(16) // Latency for Live transmission: default is 120 static const int SRT_LIVE_DEF_LATENCY_MS = 120; +// Maximum number of characters for SRTO_STREAMID +static const size_t SRT_STREAMID_MAX = 512; + +// Maximum number of characters for packet filter configuration, SRTO_PACKETFILTER +static const size_t SRT_PACKETFILTER_MAX = 64; + // Importrant note: please add new fields to this structure to the end and don't remove any existing fields struct CBytePerfMon { @@ -546,18 +584,13 @@ enum SRT_REJECT_REASON SRT_REJ_CONGESTION, // incompatible congestion-controller type SRT_REJ_FILTER, // incompatible packet filter SRT_REJ_GROUP, // incompatible group - SRT_REJ_TIMEOUT, // connection timeout -#ifdef ENABLE_AEAD_API_PREVIEW + SRT_REJ_TIMEOUT = 16,// connection timeout SRT_REJ_CRYPTO, // conflicting cryptographic configurations -#endif + SRT_REJ_CONFIG = 18, // socket settings on both sides collide and can't be negotiated SRT_REJ_E_SIZE, }; -// XXX This value remains for some time, but it's deprecated -// Planned deprecation removal: rel1.6.0. -#define SRT_REJ__SIZE SRT_REJ_E_SIZE - // Reject category codes: #define SRT_REJC_VALUE(code) (1000 * (code/1000)) @@ -566,6 +599,37 @@ enum SRT_REJECT_REASON #define SRT_REJC_USERDEFINED 2000 // User defined error codes +enum SRT_CLOSE_REASON +{ + SRT_CLS_UNKNOWN, // Unset + SRT_CLS_INTERNAL, // Closed by internal reasons during connection attempt + SRT_CLS_PEER, // Received SHUTDOWN message from the peer + SRT_CLS_RESOURCE, // Problem with resource allocation + SRT_CLS_ROGUE, // Received wrong data in the packet + SRT_CLS_OVERFLOW, // Emergency close due to receiver buffer overflow + SRT_CLS_IPE, // Internal program error + SRT_CLS_API, // The application called srt_close() + SRT_CLS_FALLBACK, // Used for peer that do not support close reason feature + SRT_CLS_LATE, // Accepted-socket late-rejection or in-handshake rollback + SRT_CLS_CLEANUP, // All sockets are being closed due to srt_cleanup() call + SRT_CLS_DEADLSN, // This is an accepted socket off a dead listener + SRT_CLS_PEERIDLE, // Peer didn't send any packet for a time of SRTO_PEERIDLETIMEO + SRT_CLS_UNSTABLE, // Requested to be broken as unstable in Backup group + + SRT_CLS_E_SIZE +}; + +typedef struct SRT_CLOSE_INFO +{ + enum SRT_CLOSE_REASON agent; + enum SRT_CLOSE_REASON peer; + int64_t time; +} SRT_CLOSE_INFO; + +#define SRT_CLSC_INTERNAL 0 +#define SRT_CLSC_USER 100 + + // Logging API - specialization for SRT. // WARNING: This part is generated. @@ -635,10 +699,8 @@ enum SRT_KM_STATE SRT_KM_S_SECURING = 1, // Stream encrypted, exchanging Keying Material SRT_KM_S_SECURED = 2, // Stream encrypted, keying Material exchanged, decrypting ok. SRT_KM_S_NOSECRET = 3, // Stream encrypted and no secret to decrypt Keying Material - SRT_KM_S_BADSECRET = 4 // Stream encrypted and wrong secret is used, cannot decrypt Keying Material -#ifdef ENABLE_AEAD_API_PREVIEW - ,SRT_KM_S_BADCRYPTOMODE = 5 // Stream encrypted but wrong cryptographic mode is used, cannot decrypt. Since v1.5.2. -#endif + SRT_KM_S_BADSECRET = 4, // Stream encrypted and wrong secret is used, cannot decrypt Keying Material + SRT_KM_S_BADCRYPTOMODE = 5 // Stream encrypted but wrong cryptographic mode is used, cannot decrypt. Since v1.5.2. }; enum SRT_EPOLL_OPT @@ -733,16 +795,33 @@ inline SRT_EPOLL_OPT operator|(SRT_EPOLL_OPT a1, SRT_EPOLL_OPT a2) return SRT_EPOLL_OPT( (int)a1 | (int)a2 ); } +static const SRTSOCKET SRT_INVALID_SOCK (-1); +static const SRTSOCKET SRT_SOCKID_CONNREQ (0); +static const SRTSTATUS SRT_ERROR (-1); +static const SRTSTATUS SRT_STATUS_OK (0); +static const SRTRUNSTATUS SRT_RUN_ERROR (-1); +static const SRTRUNSTATUS SRT_RUN_OK (0); +static const SRTRUNSTATUS SRT_RUN_ALREADY (1); + + +#else // C version + +static const SRTSOCKET SRT_INVALID_SOCK = -1; +static const SRTSOCKET SRT_SOCKID_CONNREQ = 0; +static const SRTSTATUS SRT_ERROR = -1; +static const SRTSTATUS SRT_STATUS_OK = 0; +static const SRTRUNSTATUS SRT_RUN_ERROR = -1; +static const SRTRUNSTATUS SRT_RUN_OK = 0; +static const SRTRUNSTATUS SRT_RUN_ALREADY = 1; + #endif typedef struct CBytePerfMon SRT_TRACEBSTATS; -static const SRTSOCKET SRT_INVALID_SOCK = -1; -static const int SRT_ERROR = -1; // library initialization -SRT_API int srt_startup(void); -SRT_API int srt_cleanup(void); +SRT_API SRTRUNSTATUS srt_startup(void); +SRT_API SRTSTATUS srt_cleanup(void); // // Socket operations @@ -753,33 +832,39 @@ SRT_API int srt_cleanup(void); SRT_ATR_DEPRECATED_PX SRT_API SRTSOCKET srt_socket(int, int, int) SRT_ATR_DEPRECATED; SRT_API SRTSOCKET srt_create_socket(void); -SRT_API int srt_bind (SRTSOCKET u, const struct sockaddr* name, int namelen); -SRT_API int srt_bind_acquire (SRTSOCKET u, UDPSOCKET sys_udp_sock); +SRT_API SRTSTATUS srt_bind (SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSTATUS srt_bind_acquire (SRTSOCKET u, UDPSOCKET sys_udp_sock); // Old name of srt_bind_acquire(), please don't use // Planned deprecation removal: rel1.6.0 -SRT_ATR_DEPRECATED_PX static inline int srt_bind_peerof(SRTSOCKET u, UDPSOCKET sys_udp_sock) SRT_ATR_DEPRECATED; -static inline int srt_bind_peerof (SRTSOCKET u, UDPSOCKET sys_udp_sock) { return srt_bind_acquire(u, sys_udp_sock); } -SRT_API int srt_listen (SRTSOCKET u, int backlog); +SRT_ATR_DEPRECATED_PX static inline SRTSTATUS srt_bind_peerof(SRTSOCKET u, UDPSOCKET sys_udp_sock) SRT_ATR_DEPRECATED; +static inline SRTSTATUS srt_bind_peerof (SRTSOCKET u, UDPSOCKET sys_udp_sock) { return srt_bind_acquire(u, sys_udp_sock); } +SRT_API SRTSTATUS srt_listen (SRTSOCKET u, int backlog); SRT_API SRTSOCKET srt_accept (SRTSOCKET u, struct sockaddr* addr, int* addrlen); SRT_API SRTSOCKET srt_accept_bond (const SRTSOCKET listeners[], int lsize, int64_t msTimeOut); typedef int srt_listen_callback_fn (void* opaq, SRTSOCKET ns, int hsversion, const struct sockaddr* peeraddr, const char* streamid); -SRT_API int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); +SRT_API SRTSTATUS srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook_fn, void* hook_opaque); typedef void srt_connect_callback_fn (void* opaq, SRTSOCKET ns, int errorcode, const struct sockaddr* peeraddr, int token); -SRT_API int srt_connect_callback(SRTSOCKET clr, srt_connect_callback_fn* hook_fn, void* hook_opaque); -SRT_API int srt_connect (SRTSOCKET u, const struct sockaddr* name, int namelen); -SRT_API int srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); -SRT_API int srt_connect_bind (SRTSOCKET u, const struct sockaddr* source, +SRT_API SRTSTATUS srt_connect_callback(SRTSOCKET clr, srt_connect_callback_fn* hook_fn, void* hook_opaque); +SRT_API SRTSOCKET srt_connect (SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSOCKET srt_connect_debug(SRTSOCKET u, const struct sockaddr* name, int namelen, int forced_isn); +SRT_API SRTSOCKET srt_connect_bind (SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int len); -SRT_API int srt_rendezvous (SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, +SRT_API SRTSTATUS srt_rendezvous (SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen); -SRT_API int srt_close (SRTSOCKET u); -SRT_API int srt_getpeername (SRTSOCKET u, struct sockaddr* name, int* namelen); -SRT_API int srt_getsockname (SRTSOCKET u, struct sockaddr* name, int* namelen); -SRT_API int srt_getsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, void* optval, int* optlen); -SRT_API int srt_setsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, const void* optval, int optlen); -SRT_API int srt_getsockflag (SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); -SRT_API int srt_setsockflag (SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); +SRT_API SRTSTATUS srt_close (SRTSOCKET u); +SRT_API SRTSTATUS srt_close_withreason(SRTSOCKET u, int reason); +SRT_API SRTSTATUS srt_close_getreason(SRTSOCKET u, SRT_CLOSE_INFO* info); +SRT_API SRTSTATUS srt_getpeername (SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS srt_getsockname (SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS srt_getsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, void* optval, int* optlen); +SRT_API SRTSTATUS srt_getsockdevname(SRTSOCKET u, char* name, size_t* namelen); +SRT_API SRTSTATUS srt_setsockopt (SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, const void* optval, int optlen); +SRT_API SRTSTATUS srt_getsockflag (SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); +SRT_API SRTSTATUS srt_setsockflag (SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); + +SRT_API int srt_getmaxpayloadsize(SRTSOCKET sock); + typedef struct SRT_SocketGroupData_ SRT_SOCKGROUPDATA; @@ -854,27 +939,27 @@ SRT_API int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int // last error detection SRT_API const char* srt_getlasterror_str(void); -SRT_API int srt_getlasterror(int* errno_loc); +SRT_API int srt_getlasterror(int* errno_loc); SRT_API const char* srt_strerror(int code, int errnoval); SRT_API void srt_clearlasterror(void); // Performance tracking // Performance monitor with Byte counters for better bitrate estimation. -SRT_API int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); +SRT_API SRTSTATUS srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // Performance monitor with Byte counters and instantaneous stats instead of moving averages for Snd/Rcvbuffer sizes. -SRT_API int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); +SRT_API SRTSTATUS srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous); // Socket Status (for problem tracking) SRT_API SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u); -SRT_API int srt_epoll_create(void); -SRT_API int srt_epoll_clear_usocks(int eid); -SRT_API int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); -SRT_API int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); -SRT_API int srt_epoll_remove_usock(int eid, SRTSOCKET u); -SRT_API int srt_epoll_remove_ssock(int eid, SYSSOCKET s); -SRT_API int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); -SRT_API int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); +SRT_API int srt_epoll_create(void); +SRT_API SRTSTATUS srt_epoll_clear_usocks(int eid); +SRT_API SRTSTATUS srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); +SRT_API SRTSTATUS srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); +SRT_API SRTSTATUS srt_epoll_remove_usock(int eid, SRTSOCKET u); +SRT_API SRTSTATUS srt_epoll_remove_ssock(int eid, SYSSOCKET s); +SRT_API SRTSTATUS srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); +SRT_API SRTSTATUS srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); SRT_API int srt_epoll_wait(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum); @@ -884,13 +969,13 @@ typedef struct SRT_EPOLL_EVENT_STR int events; // SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR #ifdef __cplusplus SRT_EPOLL_EVENT_STR(SRTSOCKET s, int ev): fd(s), events(ev) {} - SRT_EPOLL_EVENT_STR(): fd(-1), events(0) {} // NOTE: allows singular values, no init. + SRT_EPOLL_EVENT_STR(): fd(SRT_INVALID_SOCK), events(0) {} // NOTE: allows singular values, no init. #endif } SRT_EPOLL_EVENT; SRT_API int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); SRT_API int32_t srt_epoll_set(int eid, int32_t flags); -SRT_API int srt_epoll_release(int eid); +SRT_API SRTSTATUS srt_epoll_release(int eid); // Logging control @@ -908,11 +993,9 @@ SRT_API void srt_setlogflags(int flags); SRT_API int srt_getsndbuffer(SRTSOCKET sock, size_t* blocks, size_t* bytes); SRT_API int srt_getrejectreason(SRTSOCKET sock); -SRT_API int srt_setrejectreason(SRTSOCKET sock, int value); -// The srt_rejectreason_msg[] array is deprecated (as unsafe). -// Planned removal: v1.6.0. -SRT_API SRT_ATR_DEPRECATED extern const char* const srt_rejectreason_msg []; +SRT_API SRTSTATUS srt_setrejectreason(SRTSOCKET sock, int value); SRT_API const char* srt_rejectreason_str(int id); +SRT_API const char* srt_rejectreasonx_str(int id); SRT_API uint32_t srt_getversion(void); @@ -962,7 +1045,7 @@ struct SRT_SocketGroupData_ SRT_SOCKSTATUS sockstate; uint16_t weight; SRT_MEMBERSTATUS memberstate; - int result; + SRTSTATUS result; int token; }; @@ -981,14 +1064,14 @@ typedef struct SRT_GroupMemberConfig_ SRT_API SRTSOCKET srt_create_group(SRT_GROUP_TYPE); SRT_API SRTSOCKET srt_groupof(SRTSOCKET socket); -SRT_API int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen); +SRT_API SRTSTATUS srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen); SRT_API SRT_SOCKOPT_CONFIG* srt_create_config(void); SRT_API void srt_delete_config(SRT_SOCKOPT_CONFIG* config /*nullable*/); -SRT_API int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len); +SRT_API SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len); SRT_API SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src /*nullable*/, const struct sockaddr* adr, int namelen); -SRT_API int srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name[], int arraysize); +SRT_API SRTSOCKET srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name[], int arraysize); #ifdef __cplusplus } diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index 726c4a03b..2c71d80d5 100644 --- a/srtcore/srt_attr_defs.h +++ b/srtcore/srt_attr_defs.h @@ -17,26 +17,17 @@ used by SRT library internally. // ATTRIBUTES: // -// SRT_ATR_UNUSED: declare an entity ALLOWED to be unused (prevents warnings) -// ATR_DEPRECATED: declare an entity deprecated (compiler should warn when used) // ATR_NOEXCEPT: The true `noexcept` from C++11, or nothing if compiling in pre-C++11 mode // ATR_NOTHROW: In C++11: `noexcept`. In pre-C++11: `throw()`. Required for GNU libstdc++. // ATR_CONSTEXPR: In C++11: `constexpr`. Otherwise empty. // ATR_OVERRIDE: In C++11: `override`. Otherwise empty. // ATR_FINAL: In C++11: `final`. Otherwise empty. -#ifdef __GNUG__ -#define ATR_DEPRECATED __attribute__((deprecated)) -#else -#define ATR_DEPRECATED -#endif -#if HAVE_CXX11 -#define SRT_ATR_ALIGNAS(n) alignas(n) -#elif HAVE_GCC -#define SRT_ATR_ALIGNAS(n) __attribute__((aligned(n))) +#ifdef __GNUG__ +#define HAVE_GCC 1 #else -#define SRT_ATR_ALIGNAS(n) +#define HAVE_GCC 0 #endif #if defined(__cplusplus) && __cplusplus > 199711L @@ -44,18 +35,15 @@ used by SRT library internally. // For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on, // however it's only the "most required C++11 support". #if defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 // 4.7 only! -#define ATR_NOEXCEPT -#define ATR_NOTHROW throw() -#define ATR_CONSTEXPR -#define ATR_OVERRIDE -#define ATR_FINAL #else #define HAVE_FULL_CXX11 1 -#define ATR_NOEXCEPT noexcept -#define ATR_NOTHROW noexcept -#define ATR_CONSTEXPR constexpr -#define ATR_OVERRIDE override -#define ATR_FINAL final + +#if __cplusplus > 201406 +#define HAVE_CXX17 1 +#else +#define HAVE_CXX17 0 +#endif + #endif #elif defined(_MSC_VER) && _MSC_VER >= 1800 // Microsoft Visual Studio supports C++11, but not fully, @@ -65,26 +53,41 @@ used by SRT library internally. #define HAVE_CXX11 1 #if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026 #define HAVE_FULL_CXX11 1 + +#if __cplusplus > 201406 +#define HAVE_CXX17 1 +#else +#define HAVE_CXX17 0 +#endif + +#endif +#else +#define HAVE_CXX11 0 +#define HAVE_CXX17 0 +#endif // __cplusplus + +#if HAVE_FULL_CXX11 #define ATR_NOEXCEPT noexcept #define ATR_NOTHROW noexcept #define ATR_CONSTEXPR constexpr #define ATR_OVERRIDE override #define ATR_FINAL final #else +// These are both for HAVE_CXX11 == 1 and 0. #define ATR_NOEXCEPT #define ATR_NOTHROW throw() #define ATR_CONSTEXPR #define ATR_OVERRIDE #define ATR_FINAL #endif + +#if HAVE_CXX11 +#define SRT_ATR_ALIGNAS(n) alignas(n) +#elif HAVE_GCC +#define SRT_ATR_ALIGNAS(n) __attribute__((aligned(n))) #else -#define HAVE_CXX11 0 -#define ATR_NOEXCEPT -#define ATR_NOTHROW throw() -#define ATR_CONSTEXPR -#define ATR_OVERRIDE -#define ATR_FINAL -#endif // __cplusplus +#define SRT_ATR_ALIGNAS(n) +#endif #if !HAVE_CXX11 && defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1 #error "The currently compiled application required C++11, but your compiler doesn't support it." diff --git a/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp index 031030cd7..3b3b35c34 100644 --- a/srtcore/srt_c_api.cpp +++ b/srtcore/srt_c_api.cpp @@ -17,10 +17,14 @@ written by #include #include +#include +#include #include "srt.h" +#include "access_control.h" #include "common.h" #include "packet.h" #include "core.h" +#include "api.h" #include "utilities.h" using namespace std; @@ -29,18 +33,18 @@ using namespace srt; extern "C" { -int srt_startup() { return CUDT::startup(); } -int srt_cleanup() { return CUDT::cleanup(); } +SRTRUNSTATUS srt_startup() { return CUDT::startup(); } +SRTSTATUS srt_cleanup() { return CUDT::cleanup(); } // Socket creation. SRTSOCKET srt_socket(int , int , int ) { return CUDT::socket(); } SRTSOCKET srt_create_socket() { return CUDT::socket(); } -#if ENABLE_BONDING +#if defined(ENABLE_BONDING) && ENABLE_BONDING == 1 // Group management. SRTSOCKET srt_create_group(SRT_GROUP_TYPE gt) { return CUDT::createGroup(gt); } SRTSOCKET srt_groupof(SRTSOCKET socket) { return CUDT::getGroupOfSocket(socket); } -int srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen) +SRTSTATUS srt_group_data(SRTSOCKET socketgroup, SRT_SOCKGROUPDATA* output, size_t* inoutlen) { return CUDT::getGroupData(socketgroup, output, inoutlen); } @@ -50,32 +54,39 @@ SRT_SOCKOPT_CONFIG* srt_create_config() return new SRT_SocketOptionObject; } -int srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) +SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG* config, SRT_SOCKOPT option, const void* contents, int len) { if (!config) - return -1; + return SRT_ERROR; if (!config->add(option, contents, len)) - return -1; + return SRT_ERROR; - return 0; + return SRT_STATUS_OK; } -int srt_connect_group(SRTSOCKET group, +SRTSOCKET srt_connect_group(SRTSOCKET group, SRT_SOCKGROUPCONFIG name[], int arraysize) { return CUDT::connectLinks(group, name, arraysize); } +void srt_delete_config(SRT_SOCKOPT_CONFIG* in) +{ + delete in; +} + #else -SRTSOCKET srt_create_group(SRT_GROUP_TYPE) { return SRT_INVALID_SOCK; } -SRTSOCKET srt_groupof(SRTSOCKET) { return SRT_INVALID_SOCK; } -int srt_group_data(SRTSOCKET, SRT_SOCKGROUPDATA*, size_t*) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } +SRTSOCKET srt_create_group(SRT_GROUP_TYPE) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL), SRT_INVALID_SOCK; } +SRTSOCKET srt_groupof(SRTSOCKET) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL), SRT_INVALID_SOCK; } +SRTSTATUS srt_group_data(SRTSOCKET, SRT_SOCKGROUPDATA*, size_t*) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } SRT_SOCKOPT_CONFIG* srt_create_config() { return NULL; } -int srt_config_add(SRT_SOCKOPT_CONFIG*, SRT_SOCKOPT, const void*, int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } +SRTSTATUS srt_config_add(SRT_SOCKOPT_CONFIG*, SRT_SOCKOPT, const void*, int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } + +SRTSOCKET srt_connect_group(SRTSOCKET, SRT_SOCKGROUPCONFIG[], int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0), SRT_INVALID_SOCK; } -int srt_connect_group(SRTSOCKET, SRT_SOCKGROUPCONFIG[], int) { return srt::CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); } +void srt_delete_config(SRT_SOCKOPT_CONFIG*) { } #endif @@ -87,7 +98,7 @@ SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src, const struc #else data.errorcode = SRT_EINVOP; #endif - data.id = -1; + data.id = SRT_INVALID_SOCK; data.token = -1; data.weight = 0; data.config = NULL; @@ -103,29 +114,29 @@ SRT_SOCKGROUPCONFIG srt_prepare_endpoint(const struct sockaddr* src, const struc return data; } -void srt_delete_config(SRT_SOCKOPT_CONFIG* in) -{ - delete in; -} - // Binding and connection management -int srt_bind(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::bind(u, name, namelen); } -int srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } -int srt_listen(SRTSOCKET u, int backlog) { return CUDT::listen(u, backlog); } +SRTSTATUS srt_bind(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::bind(u, name, namelen); } +SRTSTATUS srt_bind_acquire(SRTSOCKET u, UDPSOCKET udpsock) { return CUDT::bind(u, udpsock); } +SRTSTATUS srt_listen(SRTSOCKET u, int backlog) { return CUDT::listen(u, backlog); } SRTSOCKET srt_accept(SRTSOCKET u, struct sockaddr * addr, int * addrlen) { return CUDT::accept(u, addr, addrlen); } SRTSOCKET srt_accept_bond(const SRTSOCKET lsns[], int lsize, int64_t msTimeOut) { return CUDT::accept_bond(lsns, lsize, msTimeOut); } -int srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } -int srt_connect_debug(SRTSOCKET u, const struct sockaddr * name, int namelen, int forced_isn) { return CUDT::connect(u, name, namelen, forced_isn); } -int srt_connect_bind(SRTSOCKET u, +SRTSOCKET srt_connect(SRTSOCKET u, const struct sockaddr * name, int namelen) { return CUDT::connect(u, name, namelen, SRT_SEQNO_NONE); } +SRTSOCKET srt_connect_debug(SRTSOCKET u, const struct sockaddr * name, int namelen, int forced_isn) { return CUDT::connect(u, name, namelen, forced_isn); } +SRTSOCKET srt_connect_bind(SRTSOCKET u, const struct sockaddr* source, const struct sockaddr* target, int target_len) { return CUDT::connect(u, source, target, target_len); } -int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, +SRTSTATUS srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_namelen, const struct sockaddr* remote_name, int remote_namelen) { +#if ENABLE_BONDING + if (CUDT::isgroup(u)) + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); +#endif + bool yes = 1; CUDT::setsockopt(u, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); @@ -135,14 +146,20 @@ int srt_rendezvous(SRTSOCKET u, const struct sockaddr* local_name, int local_nam || local_name->sa_family != remote_name->sa_family) return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); - const int st = srt_bind(u, local_name, local_namelen); - if (st != 0) + const SRTSTATUS st = srt_bind(u, local_name, local_namelen); + if (st != SRT_STATUS_OK) return st; - return srt_connect(u, remote_name, remote_namelen); + // Note: srt_connect may potentially return a socket value if it is used + // to connect a group. But rendezvous is not supported for groups. + const SRTSOCKET sst = srt_connect(u, remote_name, remote_namelen); + if (sst == SRT_INVALID_SOCK) + return SRT_ERROR; + + return SRT_STATUS_OK; } -int srt_close(SRTSOCKET u) +SRTSTATUS srt_close(SRTSOCKET u) { SRT_SOCKSTATUS st = srt_getsockstate(u); @@ -151,24 +168,54 @@ int srt_close(SRTSOCKET u) (st == SRTS_CLOSING) ) { // It's closed already. Do nothing. - return 0; + return SRT_STATUS_OK; } - return CUDT::close(u); + return CUDT::close(u, SRT_CLS_API); } -int srt_getpeername(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getpeername(u, name, namelen); } -int srt_getsockname(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getsockname(u, name, namelen); } -int srt_getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen) +SRTSTATUS srt_close_withreason(SRTSOCKET u, int reason) +{ + SRT_SOCKSTATUS st = srt_getsockstate(u); + + if ((st == SRTS_NONEXIST) || + (st == SRTS_CLOSED) || + (st == SRTS_CLOSING) ) + { + // It's closed already. Do nothing. + return SRT_STATUS_OK; + } + + if (reason < SRT_CLSC_USER) + reason = SRT_CLS_API; + + return CUDT::close(u, reason); +} + +SRTSTATUS srt_close_getreason(SRTSOCKET u, SRT_CLOSE_INFO* info) +{ + if (!info || u == SRT_INVALID_SOCK) + return SRT_ERROR; + + return CUDT::uglobal().getCloseReason(u, *info); +} + +SRTSTATUS srt_getpeername(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getpeername(u, name, namelen); } +SRTSTATUS srt_getsockname(SRTSOCKET u, struct sockaddr * name, int * namelen) { return CUDT::getsockname(u, name, namelen); } +SRTSTATUS srt_getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen) { return CUDT::getsockopt(u, level, optname, optval, optlen); } -int srt_setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen) +SRTSTATUS srt_setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen) { return CUDT::setsockopt(u, level, optname, optval, optlen); } - -int srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen) +SRTSTATUS srt_getsockflag(SRTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen) { return CUDT::getsockopt(u, 0, opt, optval, optlen); } -int srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen) +SRTSTATUS srt_setsockflag(SRTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen) { return CUDT::setsockopt(u, 0, opt, optval, optlen); } +SRTSTATUS srt_getsockdevname(SRTSOCKET u, char* devname, size_t * devnamelen) +{ return CUDT::getsockdevname(u, devname, devnamelen); } + +int srt_getmaxpayloadsize(SRTSOCKET u) { return CUDT::getMaxPayloadSize(u); } + int srt_send(SRTSOCKET u, const char * buf, int len) { return CUDT::send(u, buf, len, 0); } int srt_recv(SRTSOCKET u, char * buf, int len) { return CUDT::recv(u, buf, len, 0); } int srt_sendmsg(SRTSOCKET u, const char * buf, int len, int ttl, int inorder) { return CUDT::sendmsg(u, buf, len, ttl, 0!= inorder); } @@ -177,12 +224,12 @@ int64_t srt_sendfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t siz { if (!path || !offset ) { - return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } fstream ifs(path, ios::binary | ios::in); if (!ifs) { - return CUDT::APIError(MJ_FILESYSTEM, MN_READFAIL, 0); + return CUDT::APIError(MJ_FILESYSTEM, MN_READFAIL, 0).as(); } int64_t ret = CUDT::sendfile(u, ifs, *offset, size, block); ifs.close(); @@ -193,12 +240,12 @@ int64_t srt_recvfile(SRTSOCKET u, const char* path, int64_t* offset, int64_t siz { if (!path || !offset ) { - return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0); + return CUDT::APIError(MJ_NOTSUP, MN_INVAL, 0).as(); } fstream ofs(path, ios::binary | ios::out); if (!ofs) { - return CUDT::APIError(MJ_FILESYSTEM, MN_WRAVAIL, 0); + return CUDT::APIError(MJ_FILESYSTEM, MN_WRAVAIL, 0).as(); } int64_t ret = CUDT::recvfile(u, ofs, *offset, size, block); ofs.close(); @@ -261,21 +308,21 @@ void srt_clearlasterror() UDT::getlasterror().clear(); } -int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear) { return CUDT::bstats(u, perf, 0!= clear); } -int srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous) { return CUDT::bstats(u, perf, 0!= clear, 0!= instantaneous); } +SRTSTATUS srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear) { return CUDT::bstats(u, perf, 0!= clear); } +SRTSTATUS srt_bistats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear, int instantaneous) { return CUDT::bstats(u, perf, 0!= clear, 0!= instantaneous); } SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u) { return SRT_SOCKSTATUS((int)CUDT::getsockstate(u)); } // event mechanism int srt_epoll_create() { return CUDT::epoll_create(); } -int srt_epoll_clear_usocks(int eit) { return CUDT::epoll_clear_usocks(eit); } +SRTSTATUS srt_epoll_clear_usocks(int eit) { return CUDT::epoll_clear_usocks(eit); } // You can use either SRT_EPOLL_* flags or EPOLL* flags from , both are the same. IN/OUT/ERR only. // events == NULL accepted, in which case all flags are set. -int srt_epoll_add_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_add_usock(eid, u, events); } +SRTSTATUS srt_epoll_add_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_add_usock(eid, u, events); } -int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) +SRTSTATUS srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) { int flag = 0; @@ -289,15 +336,15 @@ int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) return CUDT::epoll_add_ssock(eid, s, &flag); } -int srt_epoll_remove_usock(int eid, SRTSOCKET u) { return CUDT::epoll_remove_usock(eid, u); } -int srt_epoll_remove_ssock(int eid, SYSSOCKET s) { return CUDT::epoll_remove_ssock(eid, s); } +SRTSTATUS srt_epoll_remove_usock(int eid, SRTSOCKET u) { return CUDT::epoll_remove_usock(eid, u); } +SRTSTATUS srt_epoll_remove_ssock(int eid, SYSSOCKET s) { return CUDT::epoll_remove_ssock(eid, s); } -int srt_epoll_update_usock(int eid, SRTSOCKET u, const int * events) +SRTSTATUS srt_epoll_update_usock(int eid, SRTSOCKET u, const int * events) { return CUDT::epoll_update_usock(eid, u, events); } -int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int * events) +SRTSTATUS srt_epoll_update_ssock(int eid, SYSSOCKET s, const int * events) { int flag = 0; @@ -338,7 +385,7 @@ int srt_epoll_uwait(int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTim // Pass -1 to not change anything (but still get the current flag value). int32_t srt_epoll_set(int eid, int32_t flags) { return CUDT::epoll_set(eid, flags); } -int srt_epoll_release(int eid) { return CUDT::epoll_release(eid); } +SRTSTATUS srt_epoll_release(int eid) { return CUDT::epoll_release(eid); } void srt_setloglevel(int ll) { @@ -380,17 +427,17 @@ int srt_getrejectreason(SRTSOCKET sock) return CUDT::rejectReason(sock); } -int srt_setrejectreason(SRTSOCKET sock, int value) +SRTSTATUS srt_setrejectreason(SRTSOCKET sock, int value) { return CUDT::rejectReason(sock, value); } -int srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) +SRTSTATUS srt_listen_callback(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq) { return CUDT::installAcceptHook(lsn, hook, opaq); } -int srt_connect_callback(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) +SRTSTATUS srt_connect_callback(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq) { return CUDT::installConnectHook(lsn, hook, opaq); } @@ -415,6 +462,9 @@ int srt_clock_type() return SRT_SYNC_CLOCK; } +// NOTE: crypto mode is defined regardless of the setting of +// ENABLE_AEAD_API_PREVIEW symbol. This can only block the symbol, +// but it doesn't change the symbol layout. const char* const srt_rejection_reason_msg [] = { "Unknown or erroneous", "Error in system calls", @@ -432,38 +482,19 @@ const char* const srt_rejection_reason_msg [] = { "Congestion controller type collision", "Packet Filter settings error", "Group settings collision", - "Connection timeout" -#ifdef ENABLE_AEAD_API_PREVIEW - ,"Crypto mode" -#endif -}; - -// Deprecated, available in SRT API. -extern const char* const srt_rejectreason_msg[] = { - srt_rejection_reason_msg[0], - srt_rejection_reason_msg[1], - srt_rejection_reason_msg[2], - srt_rejection_reason_msg[3], - srt_rejection_reason_msg[4], - srt_rejection_reason_msg[5], - srt_rejection_reason_msg[6], - srt_rejection_reason_msg[7], - srt_rejection_reason_msg[8], - srt_rejection_reason_msg[9], - srt_rejection_reason_msg[10], - srt_rejection_reason_msg[11], - srt_rejection_reason_msg[12], - srt_rejection_reason_msg[13], - srt_rejection_reason_msg[14], - srt_rejection_reason_msg[15], - srt_rejection_reason_msg[16] -#ifdef ENABLE_AEAD_API_PREVIEW - , srt_rejection_reason_msg[17] -#endif + "Connection timeout", + "Crypto mode", + "Invalid configuration" }; const char* srt_rejectreason_str(int id) { + using namespace srt_logging; + if (id == SRT_REJX_FALLBACK) + { + return "Application fallback (default) rejection reason"; + } + if (id >= SRT_REJC_PREDEFINED) { return "Application-defined rejection reason"; @@ -471,8 +502,65 @@ const char* srt_rejectreason_str(int id) static const size_t ra_size = Size(srt_rejection_reason_msg); if (size_t(id) >= ra_size) + { + HLOGC(gglog.Error, log << "Invalid rejection code #" << id); return srt_rejection_reason_msg[0]; + } return srt_rejection_reason_msg[id]; } +// NOTE: values in the first field must be sorted by numbers. +static pair srt_rejectionx_reason_msg [] = { + + // Internal + make_pair(SRT_REJX_FALLBACK, "Default fallback reason"), + make_pair(SRT_REJX_KEY_NOTSUP, "Unsupported streamid key"), + make_pair(SRT_REJX_FILEPATH, "Incorrect resource path"), + make_pair(SRT_REJX_HOSTNOTFOUND, "Unrecognized host under h key"), + + // HTTP adopted codes + make_pair(SRT_REJX_BAD_REQUEST, "Bad request"), + make_pair(SRT_REJX_UNAUTHORIZED, "Unauthorized"), + make_pair(SRT_REJX_OVERLOAD, "Server overloaded or underpaid"), + make_pair(SRT_REJX_FORBIDDEN, "Resource access forbidden"), + make_pair(SRT_REJX_BAD_MODE, "Bad mode specified with m key"), + make_pair(SRT_REJX_UNACCEPTABLE, "Unacceptable parameters for specified resource"), + make_pair(SRT_REJX_CONFLICT, "Access conflict for a locked resource"), + make_pair(SRT_REJX_NOTSUP_MEDIA, "Unsupported media type specified with t key"), + make_pair(SRT_REJX_LOCKED, "Resource locked for any access"), + make_pair(SRT_REJX_FAILED_DEPEND, "Dependent session id expired"), + make_pair(SRT_REJX_ISE, "Internal server error"), + make_pair(SRT_REJX_GW, "Gateway target rejected connection"), + make_pair(SRT_REJX_DOWN, "Service is down for maintenance"), + make_pair(SRT_REJX_VERSION, "Unsupported version for the request"), + make_pair(SRT_REJX_NOROOM, "Storage capacity exceeded"), +}; + +struct FCompareItems +{ + bool operator()(const pair& a, int b) + { + return a.first < b; + } +}; + +const char* srt_rejectreasonx_str(int id) +{ + if (id < SRT_REJX_FALLBACK) + { + return "System-defined rejection reason (not extended)"; + } + + pair* begin = srt_rejectionx_reason_msg; + pair* end = begin + Size(srt_rejectionx_reason_msg); + pair* found = lower_bound(begin, end, id, FCompareItems()); + + if (found == end || found->first != id) + { + return "Undefined extended rejection code"; + } + + return found->second; +} + } diff --git a/srtcore/stats.h b/srtcore/stats.h index 947489eb1..4b67378fd 100644 --- a/srtcore/stats.h +++ b/srtcore/stats.h @@ -13,6 +13,7 @@ #include "platform_sys.h" #include "packet.h" +#include "buffer_tools.h" namespace srt { @@ -144,6 +145,8 @@ struct Sender Metric recvdAck; // The number of ACK packets received by the sender. Metric recvdNak; // The number of ACK packets received by the sender. + CMovingRateEstimator mavgRateEstimator; // The average Mbps over last second + void reset() { sent.reset(); @@ -167,6 +170,12 @@ struct Sender recvdNak.resetTrace(); sentFilterExtra.resetTrace(); } + + void updateRate(int pkts, double bytes) { mavgRateEstimator.addSample(pkts, bytes); } + + void resetRate() { mavgRateEstimator.resetRate(); } + + int getAverageValue() { return mavgRateEstimator.getRate(); } }; /// Receiver-side statistics. @@ -187,6 +196,8 @@ struct Receiver Metric sentAck; // The number of ACK packets sent by the receiver. Metric sentNak; // The number of NACK packets sent by the receiver. + CMovingRateEstimator mavgRateEstimator; // The average Mbps over last second + void reset() { recvd.reset(); @@ -218,6 +229,12 @@ struct Receiver sentAck.resetTrace(); sentNak.resetTrace(); } + + void updateRate(int pkts, double bytes) { mavgRateEstimator.addSample(pkts, bytes); } + + void resetRate() { mavgRateEstimator.resetRate(); } + + int getAverageValue() { return mavgRateEstimator.getRate(); } }; } // namespace stats diff --git a/srtcore/tsbpd_time.cpp b/srtcore/tsbpd_time.cpp index d45215a01..5a71caed0 100644 --- a/srtcore/tsbpd_time.cpp +++ b/srtcore/tsbpd_time.cpp @@ -163,7 +163,7 @@ void CTsbpdTime::setTsbPdMode(const steady_clock::time_point& timebase, bool wra m_bTsbPdWrapCheck = wrap; // Timebase passed here comes is calculated as: - // Tnow - hspkt.m_iTimeStamp + // Tnow - hspkt.timestamp() // where hspkt is the packet with SRT_CMD_HSREQ message. // // This function is called in the HSREQ reception handler only. diff --git a/srtcore/udt.h b/srtcore/udt.h index ee4c02f4d..5cc63aa41 100644 --- a/srtcore/udt.h +++ b/srtcore/udt.h @@ -166,22 +166,22 @@ typedef std::set UDSET; SRT_API extern const SRTSOCKET INVALID_SOCK; #undef ERROR -SRT_API extern const int ERROR; +SRT_API extern const SRTSTATUS ERROR; -SRT_API int startup(); -SRT_API int cleanup(); +SRT_API SRTRUNSTATUS startup(); +SRT_API SRTSTATUS cleanup(); SRT_API SRTSOCKET socket(); inline SRTSOCKET socket(int , int , int ) { return socket(); } -SRT_API int bind(SRTSOCKET u, const struct sockaddr* name, int namelen); -SRT_API int bind2(SRTSOCKET u, UDPSOCKET udpsock); -SRT_API int listen(SRTSOCKET u, int backlog); +SRT_API SRTSTATUS bind(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSTATUS bind2(SRTSOCKET u, UDPSOCKET udpsock); +SRT_API SRTSTATUS listen(SRTSOCKET u, int backlog); SRT_API SRTSOCKET accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen); -SRT_API int connect(SRTSOCKET u, const struct sockaddr* name, int namelen); -SRT_API int close(SRTSOCKET u); -SRT_API int getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); -SRT_API int getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); -SRT_API int getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); -SRT_API int setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); +SRT_API SRTSOCKET connect(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API SRTSTATUS close(SRTSOCKET u); +SRT_API SRTSTATUS getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); +SRT_API SRTSTATUS getsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, void* optval, int* optlen); +SRT_API SRTSTATUS setsockopt(SRTSOCKET u, int level, SRT_SOCKOPT optname, const void* optval, int optlen); SRT_API int send(SRTSOCKET u, const char* buf, int len, int flags); SRT_API int recv(SRTSOCKET u, char* buf, int len, int flags); @@ -200,22 +200,22 @@ SRT_API int selectEx(const std::vector& fds, std::vector* std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); SRT_API int epoll_create(); -SRT_API int epoll_add_usock(int eid, SRTSOCKET u, const int* events = NULL); -SRT_API int epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); -SRT_API int epoll_remove_usock(int eid, SRTSOCKET u); -SRT_API int epoll_remove_ssock(int eid, SYSSOCKET s); -SRT_API int epoll_update_usock(int eid, SRTSOCKET u, const int* events = NULL); -SRT_API int epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); +SRT_API SRTSTATUS epoll_add_usock(int eid, SRTSOCKET u, const int* events = NULL); +SRT_API SRTSTATUS epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); +SRT_API SRTSTATUS epoll_remove_usock(int eid, SRTSOCKET u); +SRT_API SRTSTATUS epoll_remove_ssock(int eid, SYSSOCKET s); +SRT_API SRTSTATUS epoll_update_usock(int eid, SRTSOCKET u, const int* events = NULL); +SRT_API SRTSTATUS epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); SRT_API int epoll_wait(int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); SRT_API int epoll_wait2(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, SYSSOCKET* lrfds = NULL, int* lrnum = NULL, SYSSOCKET* lwfds = NULL, int* lwnum = NULL); SRT_API int epoll_uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut); -SRT_API int epoll_release(int eid); +SRT_API SRTSTATUS epoll_release(int eid); SRT_API ERRORINFO& getlasterror(); SRT_API int getlasterror_code(); SRT_API const char* getlasterror_desc(); -SRT_API int bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear = true); +SRT_API SRTSTATUS bstats(SRTSOCKET u, SRT_TRACEBSTATS* perf, bool clear = true); SRT_API SRT_SOCKSTATUS getsockstate(SRTSOCKET u); } // namespace UDT diff --git a/srtcore/utilities.h b/srtcore/utilities.h index ca8c365a3..c2e23e35a 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -417,7 +417,7 @@ struct DynamicStruct /// Fixed-size array template class. namespace srt { -template +template class FixedArray { public: @@ -433,37 +433,22 @@ class FixedArray } public: - const T& operator[](size_t index) const - { - if (index >= m_size) - throw_invalid_index(index); - - return m_entries[index]; - } - - T& operator[](size_t index) + const T& operator[](Indexer index) const { - if (index >= m_size) - throw_invalid_index(index); + if (int(index) >= int(m_size)) + throw_invalid_index(int(index)); - return m_entries[index]; + return m_entries[int(index)]; } - const T& operator[](int index) const + T& operator[](Indexer index) { - if (index < 0 || static_cast(index) >= m_size) - throw_invalid_index(index); + if (int(index) >= int(m_size)) + throw_invalid_index(int(index)); - return m_entries[index]; + return m_entries[int(index)]; } - T& operator[](int index) - { - if (index < 0 || static_cast(index) >= m_size) - throw_invalid_index(index); - - return m_entries[index]; - } size_t size() const { return m_size; } diff --git a/submodules/abi-compliance-checker b/submodules/abi-compliance-checker new file mode 160000 index 000000000..7c175c45a --- /dev/null +++ b/submodules/abi-compliance-checker @@ -0,0 +1 @@ +Subproject commit 7c175c45a8ba9ac41b8e47d8ebbab557b623b18e diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index e077ea1e0..107456c43 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -1,13 +1,15 @@ #include #include #include +#include #include #include #include "gtest/gtest.h" #include "test_env.h" +#include "apputil.hpp" // Note: declares CreateAddr, but not srt::CreateAddr #include "srt.h" -#include "udt.h" +#include "logging_api.h" #include "common.h" #include "netinet_any.h" #include "socketconfig.h" @@ -62,6 +64,8 @@ TEST(Bonding, SRTConnectGroup) #define EXPECT_SRT_SUCCESS(callform) EXPECT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str() +static std::mutex g_listening_stopped; + void listening_thread(bool should_read) { const SRTSOCKET server_sock = srt_create_socket(); @@ -118,6 +122,9 @@ void listening_thread(bool should_read) } } + std::cout << "Listen: wait for green light from the caller...\n"; + std::unique_lock listen_lock (g_listening_stopped); + srt_close(acp); srt_close(server_sock); @@ -135,8 +142,8 @@ int g_nfailed = 0; void ConnectCallback(void* , SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) { std::cout << "Connect callback. Socket: " << sock - << ", error: " << error - << ", token: " << token << '\n'; + << ", error: " << error << " (" << srt_strerror(error, 0) + << "), token: " << token << '\n'; if (error == SRT_SUCCESS) ++g_nconnected; @@ -171,6 +178,10 @@ TEST(Bonding, NonBlockingGroupConnect) sockaddr_in safail = sa; safail.sin_port = htons(4201); // port where we have no listener + // We need to keep the listener with the socket without closing it + // until we are done. + std::unique_lock listen_lock (g_listening_stopped); + std::future listen_promise = std::async(std::launch::async, std::bind(&listening_thread, false)); std::cout << "Connecting two sockets " << std::endl; @@ -203,7 +214,7 @@ TEST(Bonding, NonBlockingGroupConnect) write, &wlen, 5000, /* timeout */ 0, 0, 0, 0); - + std::cout << "Epoll result: " << epoll_res << '\n'; std::cout << "Epoll rlen: " << rlen << ", wlen: " << wlen << '\n'; for (int i = 0; i < rlen; ++i) @@ -212,10 +223,14 @@ TEST(Bonding, NonBlockingGroupConnect) } for (int i = 0; i < wlen; ++i) { - std::cout << "Epoll write[" << i << "]: " << write[i] << " (removed from epoll)\n"; + SRT_SOCKSTATUS st = srt_getsockstate(write[i]); + std::cout << "Epoll write[" << i << "]: " << write[i] + << " ST:" << srt_logging::SockStatusStr(st) + << " (removing from epoll)\n"; EXPECT_EQ(srt_epoll_remove_usock(poll_id, write[i]), 0); } } + listen_lock.unlock(); // give green light to the listener so that it closes sockets. listen_promise.wait(); @@ -637,6 +652,171 @@ TEST(Bonding, InitialFailure) srt_close(lsn); } +void SetLongSilenceTolerant(const SRTSOCKET s) +{ + int longtime = 100000; + + srt_setsockflag(s, SRTO_CONNTIMEO, &longtime, sizeof longtime); + srt_setsockflag(s, SRTO_PEERIDLETIMEO, &longtime, sizeof longtime); +} + +TEST(Bonding, DeadLinkUpdate) +{ + using namespace std; + using namespace std::chrono; + + srt::TestInit srtinit; + + SRTSOCKET listener = srt_create_socket(); + const SRTSOCKET group = srt_create_group(SRT_GTYPE_BACKUP); + + SetLongSilenceTolerant(listener); + SetLongSilenceTolerant(group); + + srt::sockaddr_any sa(AF_INET); + + inet_pton(AF_INET, "127.0.0.1", sa.get_addr()); + + sa.hport(5555); + + srt_bind(listener, sa.get(), sa.size()); + srt::setopt(listener)[SRTO_GROUPCONNECT] = 1; + srt_listen(listener, 1); + char srcbuf [] = "1234ABCD"; + + thread td = thread([&]() { + srt::ThreadName::set("TEST-conn"); + + cout << "[T] Connecting 1...\n"; + const SRTSOCKET member1 = srt_connect(group, sa.get(), sa.size()); + EXPECT_NE(member1, SRT_INVALID_SOCK); + + int nsent = srt_send(group, srcbuf, sizeof srcbuf); + // Now wait 3s + cout << "[T] Link 1 established. Wait 3s...\n"; + this_thread::sleep_for(seconds(3)); + + cout << "[T] Connecting 2...\n"; + // Make a second connection + + SRT_SOCKGROUPCONFIG sco [] = { + srt_prepare_endpoint(NULL, sa.get(), sa.size()) + }; + sco[0].weight = 10; + + const SRTSOCKET member2 = srt_connect_group(group, sco, 1); + EXPECT_NE(member2, SRT_INVALID_SOCK); + + if (member2 == SRT_INVALID_SOCK || member1 == SRT_INVALID_SOCK) + { + srt_close(member1); + srt_close(member2); + cout << "[T] Test already failed, exitting\n"; + return; + } + + cout << "[T] Link 2 established. Wait 3s...\n"; + // Again wait 3s + this_thread::sleep_for(seconds(3)); + + // DO NOT kill link 1 because this will send the shutdown + // signal and this way force update on the old socket. We + // need a silence in order to check if forever waiting on + // swait for group reading will be updated with the newly + // connected socket. Leaving the code for a case when some + // manual development testing is required. + //cout << "[T] Killing link 1...\n"; + // Now close the first connection + //srt_close(member1); + + // Now send the data and see if they are received + cout << "[T] Sending: size=" << (sizeof srcbuf) << " Content: '" << srcbuf << "'...\n"; + nsent = srt_send(group, srcbuf, sizeof srcbuf); + EXPECT_NE(nsent, -1) << "srt_send:" << srt_getlasterror_str(); + + cout << "[T] Wait 3s...\n"; + // Again wait 3s + this_thread::sleep_for(seconds(3)); + + cout << "[T] Killing the group and exitting.\n"; + // And close + srt_close(group); + cout << "[T] exit\n"; + }); + + struct AtReturnJoin + { + thread& thr; + ~AtReturnJoin() + { + thr.join(); + } + } atreturn_join { td }; + + cout << "Accepting (10s timeout)...\n"; + // Using srt_accept_bond to apply accept timeout + SRTSOCKET lsnra [] = { listener }; + const SRTSOCKET acp = srt_accept_bond(lsnra, 1, 10*1000); + + EXPECT_NE(acp, -1) << "srt_accept:" << srt_getlasterror_str(); + EXPECT_EQ(acp & SRTGROUP_MASK, SRTGROUP_MASK); + + cout << "Accepted, closing listener...\n"; + + // Close and set up the listener again. + srt_close(listener); + if (acp == SRT_INVALID_SOCK) + return; + + cout << "Creating new listener...\n"; + listener = srt_create_socket(); + srt_bind(listener, sa.get(), sa.size()); + srt::setopt(listener)[SRTO_GROUPCONNECT] = 1; + srt_listen(listener, 1); + + cout << "Group accepted. Receiving...\n"; + char buf[1316] = ""; + const int nrecv = srt_recv(acp, buf, 1316); + int syserr, err; + err = srt_getlasterror(&syserr); + EXPECT_NE(nrecv, -1) << "srt_recv:" << srt_getlasterror_str(); + + cout << "Received: val=" << nrecv << " Content: '" << buf << "'\n"; + if (nrecv == -1) + { + cout << "ERROR: " << srt_strerror(err, syserr) << endl; + cout << "STATUS: " << srt_logging::SockStatusStr(srt_getsockstate(acp)) << endl; + } + else + { + EXPECT_EQ(strcmp(srcbuf, buf), 0); + } + + cout << "Receiving again...\n"; + // Now receive again, the second portion. + const int nrecv2 = srt_recv(acp, buf, 1316); + err = srt_getlasterror(&syserr); + EXPECT_NE(nrecv, -1) << "srt_recv:" << srt_getlasterror_str(); + + cout << "Received: val=" << nrecv2 << " Content: '" << buf << "'\n"; + if (nrecv2 == -1) + { + cout << "ERROR: " << srt_strerror(err, syserr) << endl; + cout << "STATUS: " << srt_logging::SockStatusStr(srt_getsockstate(acp)) << endl; + } + else + { + EXPECT_EQ(strcmp(srcbuf, buf), 0); + } + + cout << "Closing.\n"; + srt_close(acp); + srt_close(listener); + + ASSERT_NE(nrecv, -1); + + EXPECT_EQ(strcmp(srcbuf, buf), 0); +} // General idea: diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 511c2dcb1..cd00f371c 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -76,7 +76,10 @@ class CRcvBufferReadMsg EXPECT_TRUE(packet.getMsgOrderFlag()); } - return m_rcv_buffer->insert(unit); + auto info = m_rcv_buffer->insert(unit); + // XXX extra checks? + + return int(info.result); } /// @returns 0 on success, the result of rcv_buffer::insert(..) once it failed @@ -643,6 +646,77 @@ TEST_F(CRcvBufferReadMsg, MsgOutOfOrderDrop) EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); } +TEST_F(CRcvBufferReadMsg, MsgOrderScraps) +{ + // Ok, in this test we're filling the message this way: + // 1. We have an empty packet in the first cell. + // 2. This is followed by a 5-packet message that is valid. + // 3. This is followed by empty, valid, empty, valid, valid packet, + // where all valid packets belong to the same message. + // 4. After that there should be 3-packet valid messsage. + // 5. We deploy drop request to that second scrapped message. + // 6. We read one message. Should be the first message. + // 7. We read one message. Should be the last message. + + auto& rcv_buffer = *m_rcv_buffer.get(); + + // 1, 2 + addMessage(5,// packets + 2, // msgno + m_init_seqno + 1, + true); + + // LAYOUT: 10 11 12 13 + // [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [A] [B] [C] [D] [E] [F] + // * (2 2 2 2 2) * 3 * 3 3) (4 4 4) + + // 3 + addPacket( + m_init_seqno + 7, + 3, + false, false, // subsequent + true); + + addPacket( + m_init_seqno + 9, + 3, + false, false, // subsequent + true); + + addPacket( + m_init_seqno + 10, + 3, + false, true, // last + true); + + // 4 + addMessage(3, // packets + 4, // msgno + m_init_seqno + 11, + true); + + // 5 + EXPECT_GT(rcv_buffer.dropMessage(m_init_seqno+8, m_init_seqno+8, 3, CRcvBuffer::KEEP_EXISTING), 0); + + // 6 + array buff; + SRT_MSGCTRL mc; + pair seqrange; + EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz*5); + EXPECT_EQ(mc.msgno, 2); + EXPECT_EQ(seqrange, make_pair(m_init_seqno+1, m_init_seqno+5)); + + CRcvBuffer::InsertInfo ii; + rcv_buffer.getAvailInfo((ii)); + EXPECT_EQ(ii.first_seq.val(), m_init_seqno+11); + + // 7 + EXPECT_TRUE(rcv_buffer.readMessage(buff.data(), buff.size(), (&mc), (&seqrange)) == m_payload_sz*3); + EXPECT_EQ(mc.msgno, 4); + EXPECT_EQ(seqrange, make_pair(m_init_seqno+11, m_init_seqno+13)); + +} + // One message (4 packets) is added to the buffer after a message with "in order" flag. // Read in order TEST_F(CRcvBufferReadMsg, MsgOutOfOrderAfterInOrder) diff --git a/test/test_common.cpp b/test/test_common.cpp index 1a8cca061..0c06ae68a 100644 --- a/test/test_common.cpp +++ b/test/test_common.cpp @@ -1,6 +1,12 @@ #include #include +#include + +#include +#include +#include + #include "gtest/gtest.h" #include "test_env.h" #include "utilities.h" @@ -71,3 +77,87 @@ TEST(CIPAddress, IPv4_in_IPv6_pton) test_cipaddress_pton(peer_ip, AF_INET6, ip); } + +TEST(SRTAPI, SyncRendezvousHangs) +{ + srt::TestInit srtinit; + int yes = 1; + + SRTSOCKET m_bindsock = srt_create_socket(); + ASSERT_NE(m_bindsock, SRT_ERROR); + + ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(m_bindsock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); + ASSERT_EQ(srt_setsockopt(m_bindsock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes), 0); + + const int connection_timeout_ms = 1000; // rendezvous timeout is x10 hence 10seconds + ASSERT_EQ(srt_setsockopt(m_bindsock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), 0); + + sockaddr_in local_sa={}; + local_sa.sin_family = AF_INET; + local_sa.sin_port = htons(9999); + local_sa.sin_addr.s_addr = INADDR_ANY; + + sockaddr_in peer_sa= {}; + peer_sa.sin_family = AF_INET; + peer_sa.sin_port = htons(9998); + ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &peer_sa.sin_addr), 1); + + uint64_t duration = 0; + + std::thread close_thread([&m_bindsock, &duration] { + std::this_thread::sleep_for(std::chrono::seconds(1)); // wait till srt_rendezvous is called + auto start = std::chrono::steady_clock::now(); + srt_close(m_bindsock); + auto end = std::chrono::steady_clock::now(); + + duration = std::chrono::duration_cast(end - start).count(); + }); + + EXPECT_EQ(srt_rendezvous(m_bindsock, (sockaddr*)&local_sa, sizeof local_sa, + (sockaddr*)&peer_sa, sizeof peer_sa), SRT_ERROR); + + close_thread.join(); + ASSERT_LE(duration, 1lu); // Worst case it will compare uint64_t against uint32_t on 32-bit systems. +} + +TEST(SRTAPI, RapidClose) +{ + srt::TestInit srtinit; + using namespace std; + + + SRTSOCKET sock = srt_create_socket(); + std::condition_variable cv_start; + std::mutex cvm; + sync::atomic started(false), ended(false); + + std::thread connect_thread([&sock, &cv_start, &started, &ended] { + + // Nonexistent address + sockaddr_any sa = CreateAddr("localhost", 5555, AF_INET); + cerr << "[T] Start connect\n"; + started = true; + cv_start.notify_one(); + srt_connect(sock, sa.get(), sa.size()); + // It doesn't matter if it succeeds. Important is that it exits. + ended = true; + cerr << "[T] exit\n"; + }); + + std::unique_lock lk(cvm); + + // Wait until the thread surely starts + cerr << "Waiting for thread start...\n"; + while (!started) + cv_start.wait(lk); + + cerr << "Closing socket\n"; + srt_close(sock); + cerr << "Waiting 250ms\n"; + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + EXPECT_TRUE(ended); + + cerr << "Joining [T]\n"; + connect_thread.join(); +} diff --git a/test/test_connection_timeout.cpp b/test/test_connection_timeout.cpp index c8cbc8722..cc0a13a31 100644 --- a/test/test_connection_timeout.cpp +++ b/test/test_connection_timeout.cpp @@ -90,25 +90,25 @@ class TestConnectionTimeout TEST_F(TestConnectionTimeout, Nonblocking) { const SRTSOCKET client_sock = srt_create_socket(); - ASSERT_GT(client_sock, 0); // socket_id should be > 0 + ASSERT_GT((int)client_sock, 0); // socket_id should be > 0 // First let's check the default connection timeout value. // It should be 3 seconds (3000 ms) int conn_timeout = 0; int conn_timeout_len = sizeof conn_timeout; - EXPECT_EQ(srt_getsockopt(client_sock, 0, SRTO_CONNTIMEO, &conn_timeout, &conn_timeout_len), SRT_SUCCESS); + EXPECT_EQ(srt_getsockopt(client_sock, 0, SRTO_CONNTIMEO, &conn_timeout, &conn_timeout_len), SRT_STATUS_OK); EXPECT_EQ(conn_timeout, 3000); // Set connection timeout to 500 ms to reduce the test execution time const int connection_timeout_ms = 300; - EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); + EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_STATUS_OK); const int yes = 1; const int no = 0; - ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_SUCCESS); // for async connect - ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_SUCCESS); // for async connect - ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_SUCCESS); - ASSERT_EQ(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_SUCCESS); + ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_STATUS_OK); // for async connect + ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_STATUS_OK); // for async connect + ASSERT_EQ(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_STATUS_OK); + ASSERT_EQ(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_STATUS_OK); const int pollid = srt_epoll_create(); ASSERT_GE(pollid, 0); @@ -116,7 +116,7 @@ TEST_F(TestConnectionTimeout, Nonblocking) { ASSERT_NE(srt_epoll_add_usock(pollid, client_sock, &epoll_out), SRT_ERROR); const sockaddr* psa = reinterpret_cast(&m_sa); - ASSERT_NE(srt_connect(client_sock, psa, sizeof m_sa), SRT_ERROR); + ASSERT_NE(srt_connect(client_sock, psa, sizeof m_sa), SRT_INVALID_SOCK); // Socket readiness for connection is checked by polling on WRITE allowed sockets. { @@ -151,8 +151,8 @@ TEST_F(TestConnectionTimeout, Nonblocking) { EXPECT_EQ(write[0], client_sock); } - EXPECT_EQ(srt_epoll_remove_usock(pollid, client_sock), SRT_SUCCESS); - EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); + EXPECT_EQ(srt_epoll_remove_usock(pollid, client_sock), SRT_STATUS_OK); + EXPECT_EQ(srt_close(client_sock), SRT_STATUS_OK); (void)srt_epoll_release(pollid); } @@ -175,8 +175,8 @@ TEST_F(TestConnectionTimeout, BlockingLoop) for (int i = 0; i < 10; ++i) { const SRTSOCKET client_sock = srt_create_socket(); - EXPECT_GT(client_sock, 0); // socket_id should be > 0 - if (client_sock <= 0) + EXPECT_GT((int)client_sock, 0); // socket_id should be > 0 + if (int(client_sock) <= 0) break; // Set connection timeout to 999 ms to reduce the test execution time. @@ -184,10 +184,10 @@ TEST_F(TestConnectionTimeout, BlockingLoop) // srt_connect will check TTL every second, // CRcvQueue::worker will wait on a socket for 10 ms. // Need to have a condition, when srt_connect will process the timeout. - EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); + EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_STATUS_OK); const chrono::steady_clock::time_point chrono_ts_start = chrono::steady_clock::now(); - EXPECT_EQ(srt_connect(client_sock, psa, sizeof m_sa), SRT_ERROR); + EXPECT_EQ(srt_connect(client_sock, psa, sizeof m_sa), SRT_INVALID_SOCK); const auto delta_ms = chrono::duration_cast(chrono::steady_clock::now() - chrono_ts_start).count(); // Confidence interval border : +/-200 ms @@ -203,7 +203,7 @@ TEST_F(TestConnectionTimeout, BlockingLoop) break; } - EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); + EXPECT_EQ(srt_close(client_sock), SRT_STATUS_OK); } } diff --git a/test/test_enforced_encryption.cpp b/test/test_enforced_encryption.cpp index 717b4549c..3a0fe173c 100644 --- a/test/test_enforced_encryption.cpp +++ b/test/test_enforced_encryption.cpp @@ -64,21 +64,21 @@ enum TEST_CASE struct TestResultNonBlocking { - int connect_ret; - int accept_ret; + SRTSOCKET connect_ret; + SRTSOCKET accept_ret; int epoll_wait_ret; int epoll_event; - int socket_state[CHECK_SOCKET_COUNT]; - int km_state [CHECK_SOCKET_COUNT]; + SRT_SOCKSTATUS socket_state[CHECK_SOCKET_COUNT]; + SRT_KM_STATE km_state [CHECK_SOCKET_COUNT]; }; struct TestResultBlocking { - int connect_ret; - int accept_ret; - int socket_state[CHECK_SOCKET_COUNT]; - int km_state[CHECK_SOCKET_COUNT]; + SRTSOCKET connect_ret; + SRTSOCKET accept_ret; + SRT_SOCKSTATUS socket_state[CHECK_SOCKET_COUNT]; + SRT_KM_STATE km_state[CHECK_SOCKET_COUNT]; }; @@ -137,35 +137,37 @@ static const std::string s_pwd_no(""); */ const int IGNORE_EPOLL = -2; -const int IGNORE_SRTS = -1; +const SRT_SOCKSTATUS IGNORE_SRTS = (SRT_SOCKSTATUS)-1; +const SRT_KM_STATE IGNORE_KMSTATE = (SRT_KM_STATE)-1; +const SRTSOCKET IGNORE_ACCEPT = (SRTSOCKET)-2; const TestCaseNonBlocking g_test_matrix_non_blocking[] = { - // ENFORCEDENC | Password | | EPoll wait | socket_state | KM State - // caller | listener | caller | listener | connect_ret accept_ret | ret | event | caller accepted | caller listener -/*A.1 */ { {true, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*A.2 */ { {true, true }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, -/*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, -/*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, -/*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, - -/*B.1 */ { {true, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*B.2 */ { {true, false }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, -/*B.3 */ { {true, false }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, -/*B.4 */ { {true, false }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_NOSECRET}}}, -/*B.5 */ { {true, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, - -/*C.1 */ { {false, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*C.2 */ { {false, true }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, -/*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, -/*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_SRTS}}}, -/*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, - -/*D.1 */ { {false, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*D.2 */ { {false, false }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, -/*D.3 */ { {false, false }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, -/*D.4 */ { {false, false }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_NOSECRET, SRT_KM_S_NOSECRET}}}, -/*D.5 */ { {false, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + // ENFORCEDENC | Password | | EPoll wait | socket_state | KM State + // caller | listener | caller | listener | connect_ret accept_ret | ret | event | caller accepted | caller listener +/*A.1 */ { {true, true }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*A.2 */ { {true, true }, {s_pwd_a, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + +/*B.1 */ { {true, false }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*B.2 */ { {true, false }, {s_pwd_a, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, +/*B.3 */ { {true, false }, {s_pwd_a, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, +/*B.4 */ { {true, false }, {s_pwd_no, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, IGNORE_EPOLL, 0, {SRTS_CONNECTING, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_NOSECRET}}}, +/*B.5 */ { {true, false }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + +/*C.1 */ { {false, true }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*C.2 */ { {false, true }, {s_pwd_a, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_INVALID_SOCK, 0, 0, {SRTS_BROKEN, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + +/*D.1 */ { {false, false }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*D.2 */ { {false, false }, {s_pwd_a, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, +/*D.3 */ { {false, false }, {s_pwd_a, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, +/*D.4 */ { {false, false }, {s_pwd_no, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_NOSECRET, SRT_KM_S_NOSECRET}}}, +/*D.5 */ { {false, false }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, 1, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, }; @@ -185,31 +187,31 @@ const TestCaseNonBlocking g_test_matrix_non_blocking[] = */ const TestCaseBlocking g_test_matrix_blocking[] = { - // ENFORCEDENC | Password | | socket_state | KM State - // caller | listener | caller | listener | connect_ret accept_ret | caller accepted | caller listener -/*A.1 */ { {true, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*A.2 */ { {true, true }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, -/*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, -/*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, -/*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, - -/*B.1 */ { {true, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*B.2 */ { {true, false }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, -2, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, -/*B.3 */ { {true, false }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, -2, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, -/*B.4 */ { {true, false }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, -2, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_NOSECRET}}}, -/*B.5 */ { {true, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, - -/*C.1 */ { {false, true }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*C.2 */ { {false, true }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, -/*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, -/*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, -/*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, - -/*D.1 */ { {false, false }, {s_pwd_a, s_pwd_a}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, -/*D.2 */ { {false, false }, {s_pwd_a, s_pwd_b}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, -/*D.3 */ { {false, false }, {s_pwd_a, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, -/*D.4 */ { {false, false }, {s_pwd_no, s_pwd_b}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_NOSECRET, SRT_KM_S_NOSECRET}}}, -/*D.5 */ { {false, false }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + // ENFORCEDENC | Password | | socket_state | KM State + // caller | listener | caller | listener | connect_ret accept_ret | caller accepted | caller listener +/*A.1 */ { {true, true }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*A.2 */ { {true, true }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + +/*B.1 */ { {true, false }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*B.2 */ { {true, false }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, IGNORE_ACCEPT, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, +/*B.3 */ { {true, false }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, IGNORE_ACCEPT, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, +/*B.4 */ { {true, false }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, IGNORE_ACCEPT, {SRTS_OPENED, SRTS_BROKEN}, {SRT_KM_S_UNSECURED, SRT_KM_S_NOSECRET}}}, +/*B.5 */ { {true, false }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + +/*C.1 */ { {false, true }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*C.2 */ { {false, true }, {s_pwd_a, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, {SRTS_OPENED, IGNORE_SRTS}, {SRT_KM_S_UNSECURED, IGNORE_KMSTATE}}}, +/*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, + +/*D.1 */ { {false, false }, {s_pwd_a, s_pwd_a}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_SECURED, SRT_KM_S_SECURED}}}, +/*D.2 */ { {false, false }, {s_pwd_a, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET}}}, +/*D.3 */ { {false, false }, {s_pwd_a, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, +/*D.4 */ { {false, false }, {s_pwd_no, s_pwd_b}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_NOSECRET, SRT_KM_S_NOSECRET}}}, +/*D.5 */ { {false, false }, {s_pwd_no, s_pwd_no}, { SRT_SOCKID_CONNREQ, SRT_SOCKID_CONNREQ, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, }; @@ -240,13 +242,13 @@ class TestEnforcedEncryption ASSERT_NE(m_caller_socket, SRT_INVALID_SOCK); ASSERT_NE(srt_setsockflag(m_caller_socket, SRTO_SENDER, &s_yes, sizeof s_yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt (m_caller_socket, 0, SRTO_TSBPDMODE, &s_yes, sizeof s_yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(m_caller_socket, SRTO_TSBPDMODE, &s_yes, sizeof s_yes), SRT_ERROR); m_listener_socket = srt_create_socket(); ASSERT_NE(m_listener_socket, SRT_INVALID_SOCK); ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_SENDER, &s_no, sizeof s_no), SRT_ERROR); - ASSERT_NE(srt_setsockopt (m_listener_socket, 0, SRTO_TSBPDMODE, &s_yes, sizeof s_yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_TSBPDMODE, &s_yes, sizeof s_yes), SRT_ERROR); // Will use this epoll to wait for srt_accept(...) const int epoll_out = SRT_EPOLL_IN | SRT_EPOLL_ERR; @@ -273,10 +275,10 @@ class TestEnforcedEncryption public: - int SetEnforcedEncryption(PEER_TYPE peer, bool value) + SRTSTATUS SetEnforcedEncryption(PEER_TYPE peer, bool value) { const SRTSOCKET &socket = peer == PEER_CALLER ? m_caller_socket : m_listener_socket; - return srt_setsockopt(socket, 0, SRTO_ENFORCEDENCRYPTION, value ? &s_yes : &s_no, sizeof s_yes); + return srt_setsockflag(socket, SRTO_ENFORCEDENCRYPTION, value ? &s_yes : &s_no, sizeof s_yes); } @@ -285,15 +287,15 @@ class TestEnforcedEncryption const SRTSOCKET socket = peer_type == PEER_CALLER ? m_caller_socket : m_listener_socket; bool optval; int optlen = sizeof optval; - EXPECT_EQ(srt_getsockopt(socket, 0, SRTO_ENFORCEDENCRYPTION, (void*)&optval, &optlen), SRT_SUCCESS); + EXPECT_EQ(srt_getsockopt(socket, 0, SRTO_ENFORCEDENCRYPTION, (void*)&optval, &optlen), SRT_STATUS_OK); return optval ? true : false; } - int SetPassword(PEER_TYPE peer_type, const std::basic_string &pwd) + SRTSTATUS SetPassword(PEER_TYPE peer_type, const std::basic_string &pwd) { const SRTSOCKET socket = peer_type == PEER_CALLER ? m_caller_socket : m_listener_socket; - return srt_setsockopt(socket, 0, SRTO_PASSPHRASE, pwd.c_str(), (int) pwd.size()); + return srt_setsockflag(socket, SRTO_PASSPHRASE, pwd.c_str(), (int) pwd.size()); } @@ -301,8 +303,8 @@ class TestEnforcedEncryption { int km_state = 0; int opt_size = sizeof km_state; - EXPECT_EQ(srt_getsockopt(socket, 0, SRTO_KMSTATE, reinterpret_cast(&km_state), &opt_size), SRT_SUCCESS); - + EXPECT_EQ(srt_getsockopt(socket, 0, SRTO_KMSTATE, reinterpret_cast(&km_state), &opt_size), SRT_STATUS_OK); + return km_state; } @@ -311,7 +313,7 @@ class TestEnforcedEncryption { int val = 0; int size = sizeof val; - EXPECT_EQ(srt_getsockopt(socket, 0, opt, reinterpret_cast(&val), &size), SRT_SUCCESS); + EXPECT_EQ(srt_getsockopt(socket, 0, opt, reinterpret_cast(&val), &size), SRT_STATUS_OK); return val; } @@ -330,26 +332,26 @@ class TestEnforcedEncryption const bool is_blocking = std::is_same::value; if (is_blocking) { - ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_RCVSYN, &s_yes, sizeof s_yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_SNDSYN, &s_yes, sizeof s_yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_RCVSYN, &s_yes, sizeof s_yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_SNDSYN, &s_yes, sizeof s_yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag( m_caller_socket, SRTO_RCVSYN, &s_yes, sizeof s_yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag( m_caller_socket, SRTO_SNDSYN, &s_yes, sizeof s_yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_RCVSYN, &s_yes, sizeof s_yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_SNDSYN, &s_yes, sizeof s_yes), SRT_ERROR); } else { - ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_RCVSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode - ASSERT_NE(srt_setsockopt( m_caller_socket, 0, SRTO_SNDSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode - ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_RCVSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode - ASSERT_NE(srt_setsockopt(m_listener_socket, 0, SRTO_SNDSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode + ASSERT_NE(srt_setsockflag( m_caller_socket, SRTO_RCVSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode + ASSERT_NE(srt_setsockflag( m_caller_socket, SRTO_SNDSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode + ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_RCVSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode + ASSERT_NE(srt_setsockflag(m_listener_socket, SRTO_SNDSYN, &s_no, sizeof s_no), SRT_ERROR); // non-blocking mode } // Prepare input state const TestCase &test = GetTestMatrix(test_case); - ASSERT_EQ(SetEnforcedEncryption(PEER_CALLER, test.enforcedenc[PEER_CALLER]), SRT_SUCCESS); - ASSERT_EQ(SetEnforcedEncryption(PEER_LISTENER, test.enforcedenc[PEER_LISTENER]), SRT_SUCCESS); + ASSERT_EQ(SetEnforcedEncryption(PEER_CALLER, test.enforcedenc[PEER_CALLER]), SRT_STATUS_OK); + ASSERT_EQ(SetEnforcedEncryption(PEER_LISTENER, test.enforcedenc[PEER_LISTENER]), SRT_STATUS_OK); - ASSERT_EQ(SetPassword(PEER_CALLER, test.password[PEER_CALLER]), SRT_SUCCESS); - ASSERT_EQ(SetPassword(PEER_LISTENER, test.password[PEER_LISTENER]), SRT_SUCCESS); + ASSERT_EQ(SetPassword(PEER_CALLER, test.password[PEER_CALLER]), SRT_STATUS_OK); + ASSERT_EQ(SetPassword(PEER_LISTENER, test.password[PEER_LISTENER]), SRT_STATUS_OK); // Determine the subcase for the KLUDGE (check the behavior of the decryption failure) const bool case_pw_failure = test.password[PEER_CALLER] != test.password[PEER_LISTENER]; @@ -369,7 +371,7 @@ class TestEnforcedEncryption ASSERT_NE(srt_bind(m_listener_socket, psa, sizeof sa), SRT_ERROR); ASSERT_NE(srt_listen(m_listener_socket, 4), SRT_ERROR); - SRTSOCKET accepted_socket = -1; + SRTSOCKET accepted_socket = SRT_INVALID_SOCK; auto accepting_thread = std::thread([&] { const int epoll_event = WaitOnEpoll(expect); @@ -398,12 +400,12 @@ class TestEnforcedEncryption std::cerr << "[T] ACCEPT SUCCEEDED: @" << accepted_socket << "\n"; } - EXPECT_NE(accepted_socket, 0); + EXPECT_NE(accepted_socket, SRT_SOCKID_CONNREQ); if (expect.accept_ret == SRT_INVALID_SOCK) { EXPECT_EQ(accepted_socket, SRT_INVALID_SOCK); } - else if (expect.accept_ret != -2) + else if (expect.accept_ret != IGNORE_ACCEPT) { EXPECT_NE(accepted_socket, SRT_INVALID_SOCK); } @@ -449,10 +451,10 @@ class TestEnforcedEncryption } }); - const int connect_ret = srt_connect(m_caller_socket, psa, sizeof sa); + const SRTSOCKET connect_ret = srt_connect(m_caller_socket, psa, sizeof sa); EXPECT_EQ(connect_ret, expect.connect_ret); - if (connect_ret == SRT_ERROR && connect_ret != expect.connect_ret) + if (connect_ret == SRT_INVALID_SOCK && connect_ret != expect.connect_ret) { std::cerr << "UNEXPECTED! srt_connect returned error: " << srt_getlasterror_str() << " (code " << srt_getlasterror(NULL) << ")\n"; @@ -524,7 +526,7 @@ class TestEnforcedEncryption std::cout << "W: " << epoll_res_w << std::endl; char buffer[1316] = {1, 2, 3, 4}; - ASSERT_NE(srt_sendmsg2(m_caller_socket, buffer, sizeof buffer, nullptr), SRT_ERROR); + ASSERT_NE(srt_sendmsg2(m_caller_socket, buffer, sizeof buffer, nullptr), int(SRT_ERROR)); std::this_thread::sleep_for(std::chrono::seconds(1)); } @@ -624,13 +626,13 @@ int TestEnforcedEncryption::WaitOnEpoll(const TestResultN } std::cerr << std::endl; - // Expect: -2 means that + // Expect: IGNORE_EPOLL means that you should not check the result. if (expect.epoll_wait_ret != IGNORE_EPOLL) { EXPECT_EQ(epoll_res, expect.epoll_wait_ret); } - if (epoll_res == SRT_ERROR) + if (epoll_res == int(SRT_ERROR)) { std::cerr << "Epoll returned error: " << srt_getlasterror_str() << " (code " << srt_getlasterror(NULL) << ")\n"; return 0; @@ -696,8 +698,8 @@ TEST_F(TestEnforcedEncryption, PasswordLength) { #ifdef SRT_ENABLE_ENCRYPTION // Empty string sets password to none - EXPECT_EQ(SetPassword(PEER_CALLER, std::string("")), SRT_SUCCESS); - EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("")), SRT_SUCCESS); + EXPECT_EQ(SetPassword(PEER_CALLER, std::string("")), SRT_STATUS_OK); + EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("")), SRT_STATUS_OK); EXPECT_EQ(SetPassword(PEER_CALLER, std::string("too_short")), SRT_ERROR); EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("too_short")), SRT_ERROR); @@ -715,8 +717,8 @@ TEST_F(TestEnforcedEncryption, PasswordLength) EXPECT_EQ(SetPassword(PEER_CALLER, long_pwd), SRT_ERROR); EXPECT_EQ(SetPassword(PEER_LISTENER, long_pwd), SRT_ERROR); - EXPECT_EQ(SetPassword(PEER_CALLER, std::string("proper_len")), SRT_SUCCESS); - EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("proper_length")), SRT_SUCCESS); + EXPECT_EQ(SetPassword(PEER_CALLER, std::string("proper_len")), SRT_STATUS_OK); + EXPECT_EQ(SetPassword(PEER_LISTENER, std::string("proper_length")), SRT_STATUS_OK); #else EXPECT_EQ(SetPassword(PEER_CALLER, "whateverpassword"), SRT_ERROR); #endif @@ -732,8 +734,8 @@ TEST_F(TestEnforcedEncryption, SetGetDefault) EXPECT_EQ(GetEnforcedEncryption(PEER_CALLER), true); EXPECT_EQ(GetEnforcedEncryption(PEER_LISTENER), true); - EXPECT_EQ(SetEnforcedEncryption(PEER_CALLER, false), SRT_SUCCESS); - EXPECT_EQ(SetEnforcedEncryption(PEER_LISTENER, false), SRT_SUCCESS); + EXPECT_EQ(SetEnforcedEncryption(PEER_CALLER, false), SRT_STATUS_OK); + EXPECT_EQ(SetEnforcedEncryption(PEER_LISTENER, false), SRT_STATUS_OK); EXPECT_EQ(GetEnforcedEncryption(PEER_CALLER), false); EXPECT_EQ(GetEnforcedEncryption(PEER_LISTENER), false); diff --git a/test/test_epoll.cpp b/test/test_epoll.cpp index f9d838030..78e60f0f0 100644 --- a/test/test_epoll.cpp +++ b/test/test_epoll.cpp @@ -7,7 +7,7 @@ #include "test_env.h" #include "api.h" #include "epoll.h" - +#include "apputil.hpp" using namespace std; using namespace srt; @@ -23,9 +23,9 @@ TEST(CEPoll, InfiniteWait) ASSERT_EQ(srt_epoll_wait(epoll_id, nullptr, nullptr, nullptr, nullptr, -1, - 0, 0, 0, 0), SRT_ERROR); + 0, 0, 0, 0), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } TEST(CEPoll, WaitNoSocketsInEpoll) @@ -42,9 +42,9 @@ TEST(CEPoll, WaitNoSocketsInEpoll) SRTSOCKET write[2]; ASSERT_EQ(srt_epoll_wait(epoll_id, read, &rlen, write, &wlen, - -1, 0, 0, 0, 0), SRT_ERROR); + -1, 0, 0, 0, 0), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } @@ -57,9 +57,9 @@ TEST(CEPoll, WaitNoSocketsInEpoll2) SRT_EPOLL_EVENT events[2]; - ASSERT_EQ(srt_epoll_uwait(epoll_id, events, 2, -1), SRT_ERROR); + ASSERT_EQ(srt_epoll_uwait(epoll_id, events, 2, -1), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } @@ -68,11 +68,11 @@ TEST(CEPoll, WaitEmptyCall) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - ASSERT_NE(client_sock, SRT_ERROR); + ASSERT_NE(client_sock, SRT_INVALID_SOCK); const int no = 0; - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -81,9 +81,9 @@ TEST(CEPoll, WaitEmptyCall) ASSERT_NE(srt_epoll_add_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); ASSERT_EQ(srt_epoll_wait(epoll_id, 0, NULL, 0, NULL, - -1, 0, 0, 0, 0), SRT_ERROR); + -1, 0, 0, 0, 0), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } TEST(CEPoll, UWaitEmptyCall) @@ -91,11 +91,11 @@ TEST(CEPoll, UWaitEmptyCall) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - ASSERT_NE(client_sock, SRT_ERROR); + ASSERT_NE(client_sock, SRT_INVALID_SOCK); const int no = 0; - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -103,9 +103,9 @@ TEST(CEPoll, UWaitEmptyCall) const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; ASSERT_NE(srt_epoll_add_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); - ASSERT_EQ(srt_epoll_uwait(epoll_id, NULL, 10, -1), SRT_ERROR); + ASSERT_EQ(srt_epoll_uwait(epoll_id, NULL, 10, -1), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } @@ -114,14 +114,14 @@ TEST(CEPoll, WaitAllSocketsInEpollReleased) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - ASSERT_NE(client_sock, SRT_ERROR); + ASSERT_NE(client_sock, SRT_INVALID_SOCK); const int yes = 1; const int no = 0; - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(client_sock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -137,9 +137,9 @@ TEST(CEPoll, WaitAllSocketsInEpollReleased) SRTSOCKET write[2]; ASSERT_EQ(srt_epoll_wait(epoll_id, read, &rlen, write, &wlen, - -1, 0, 0, 0, 0), SRT_ERROR); + -1, 0, 0, 0, 0), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } @@ -148,14 +148,14 @@ TEST(CEPoll, WaitAllSocketsInEpollReleased2) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - ASSERT_NE(client_sock, SRT_ERROR); + ASSERT_NE(client_sock, SRT_INVALID_SOCK); const int yes = 1; const int no = 0; - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(client_sock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -166,9 +166,9 @@ TEST(CEPoll, WaitAllSocketsInEpollReleased2) SRT_EPOLL_EVENT events[2]; - ASSERT_EQ(srt_epoll_uwait(epoll_id, events, 2, -1), SRT_ERROR); + ASSERT_EQ(srt_epoll_uwait(epoll_id, events, 2, -1), int(SRT_ERROR)); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } TEST(CEPoll, WrongEpoll_idOnAddUSock) @@ -176,11 +176,11 @@ TEST(CEPoll, WrongEpoll_idOnAddUSock) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - ASSERT_NE(client_sock, SRT_ERROR); + ASSERT_NE(client_sock, SRT_INVALID_SOCK); const int no = 0; - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -189,7 +189,7 @@ TEST(CEPoll, WrongEpoll_idOnAddUSock) /* We intentionally pass the wrong socket ID. The error should be returned.*/ ASSERT_EQ(srt_epoll_add_usock(epoll_id + 1, client_sock, &epoll_out), SRT_ERROR); - EXPECT_EQ(srt_epoll_release(epoll_id), 0); + EXPECT_EQ(srt_epoll_release(epoll_id), SRT_STATUS_OK); } @@ -199,21 +199,21 @@ TEST(CEPoll, HandleEpollEvent) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - EXPECT_NE(client_sock, SRT_ERROR); + EXPECT_NE(client_sock, SRT_INVALID_SOCK); const int yes = 1; const int no = 0; - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - EXPECT_NE(srt_setsockopt (client_sock, 0, 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_setsockopt (client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + EXPECT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + 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); CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); + epoll.update_usock(epoll_id, client_sock, &epoll_out); set epoll_ids = { epoll_id }; @@ -224,12 +224,12 @@ TEST(CEPoll, HandleEpollEvent) set* rval = &readset; set* wval = &writeset; - ASSERT_NE(epoll.wait(epoll_id, rval, wval, -1, nullptr, nullptr), SRT_ERROR); + ASSERT_NE(epoll.wait(epoll_id, rval, wval, -1, nullptr, nullptr), int(SRT_ERROR)); try { int no_events = 0; - EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); + epoll.update_usock(epoll_id, client_sock, &no_events); } catch (CUDTException &ex) { @@ -239,7 +239,7 @@ TEST(CEPoll, HandleEpollEvent) try { - EXPECT_EQ(epoll.release(epoll_id), 0); + epoll.release(epoll_id); } catch (CUDTException &ex) { @@ -260,19 +260,19 @@ TEST(CEPoll, NotifyConnectionBreak) // 1. Prepare client MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - ASSERT_NE(client_sock, SRT_ERROR); + ASSERT_NE(client_sock, SRT_INVALID_SOCK); const int yes SRT_ATR_UNUSED = 1; const int no SRT_ATR_UNUSED = 0; - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int client_epoll_id = srt_epoll_create(); ASSERT_GE(client_epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; /* We intentionally pass the wrong socket ID. The error should be returned.*/ - EXPECT_EQ(srt_epoll_add_usock(client_epoll_id, client_sock, &epoll_out), SRT_SUCCESS); + EXPECT_EQ(srt_epoll_add_usock(client_epoll_id, client_sock, &epoll_out), SRT_STATUS_OK); sockaddr_in sa_client; memset(&sa_client, 0, sizeof sa_client); @@ -282,10 +282,10 @@ TEST(CEPoll, NotifyConnectionBreak) // 2. Prepare server MAKE_UNIQUE_SOCK(server_sock, "server_sock", srt_create_socket()); - ASSERT_NE(server_sock, SRT_ERROR); + ASSERT_NE(server_sock, SRT_INVALID_SOCK); - ASSERT_NE(srt_setsockopt(server_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(server_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(server_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(server_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect const int server_epoll_id = srt_epoll_create(); ASSERT_GE(server_epoll_id, 0); @@ -320,13 +320,13 @@ TEST(CEPoll, NotifyConnectionBreak) 0, 0, 0, 0); EXPECT_EQ(epoll_res, 1); - if (epoll_res == SRT_ERROR) + if (epoll_res == int(SRT_ERROR)) { std::cerr << "Epoll returned error: " << srt_getlasterror_str() << " (code " << srt_getlasterror(NULL) << ")\n"; } // Wait for the caller connection thread to return connection result - EXPECT_EQ(connect_res.get(), SRT_SUCCESS); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); sockaddr_in scl; int sclen = sizeof scl; @@ -343,15 +343,15 @@ TEST(CEPoll, NotifyConnectionBreak) this_thread::sleep_for(chrono::seconds(1)); cout << "(async call): Closing client connection\n"; return srt_close(client_sock); - }); + }); int timeout_ms = -1; - int ready[2] = { SRT_INVALID_SOCK, SRT_INVALID_SOCK }; + SRTSOCKET ready[2] = { SRT_INVALID_SOCK, SRT_INVALID_SOCK }; int len = 2; cout << "TEST: entering INFINITE WAIT\n"; const int epoll_wait_res = srt_epoll_wait(epoll_io, ready, &len, nullptr, nullptr, timeout_ms, 0, 0, 0, 0); cout << "TEST: return from INFINITE WAIT\n"; - if (epoll_wait_res == SRT_ERROR) + if (epoll_wait_res == int(SRT_ERROR)) cerr << "socket::read::epoll " << to_string(srt_getlasterror(nullptr)); EXPECT_EQ(epoll_wait_res, 1); EXPECT_EQ(len, 1); @@ -359,7 +359,7 @@ TEST(CEPoll, NotifyConnectionBreak) // Wait for the caller to close connection // There should be no wait, as epoll should wait until connection is closed. - EXPECT_EQ(close_res.get(), SRT_SUCCESS); + EXPECT_EQ(close_res.get(), SRT_STATUS_OK); const SRT_SOCKSTATUS state = srt_getsockstate(sock); const bool state_valid = state == SRTS_BROKEN || state == SRTS_CLOSING || state == SRTS_CLOSED; EXPECT_TRUE(state_valid); @@ -374,21 +374,21 @@ TEST(CEPoll, HandleEpollEvent2) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - EXPECT_NE(client_sock, SRT_ERROR); + EXPECT_NE(client_sock, SRT_INVALID_SOCK); const int yes = 1; const int no = 0; - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + 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_setsockopt (client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + EXPECT_NE(srt_setsockflag(client_sock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR | SRT_EPOLL_ET; - ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); + epoll.update_usock(epoll_id, client_sock, &epoll_out); set epoll_ids = { epoll_id }; @@ -409,7 +409,7 @@ TEST(CEPoll, HandleEpollEvent2) try { int no_events = 0; - EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); + epoll.update_usock(epoll_id, client_sock, &no_events); } catch (CUDTException &ex) { @@ -419,7 +419,7 @@ TEST(CEPoll, HandleEpollEvent2) try { - EXPECT_EQ(epoll.release(epoll_id), 0); + epoll.release(epoll_id); } catch (CUDTException &ex) { @@ -435,21 +435,21 @@ TEST(CEPoll, HandleEpollNoEvent) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - EXPECT_NE(client_sock, SRT_ERROR); + EXPECT_NE(client_sock, SRT_INVALID_SOCK); const int yes = 1; const int no = 0; - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + 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_setsockopt (client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + EXPECT_NE(srt_setsockflag(client_sock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); CEPoll epoll; const int epoll_id = epoll.create(); ASSERT_GE(epoll_id, 0); const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); + epoll.update_usock(epoll_id, client_sock, &epoll_out); SRT_EPOLL_EVENT fds[1024]; @@ -460,7 +460,7 @@ TEST(CEPoll, HandleEpollNoEvent) try { int no_events = 0; - EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); + epoll.update_usock(epoll_id, client_sock, &no_events); } catch (CUDTException &ex) { @@ -470,7 +470,7 @@ TEST(CEPoll, HandleEpollNoEvent) try { - EXPECT_EQ(epoll.release(epoll_id), 0); + epoll.release(epoll_id); } catch (CUDTException &ex) { @@ -485,11 +485,11 @@ TEST(CEPoll, ThreadedUpdate) srt::TestInit srtinit; MAKE_UNIQUE_SOCK(client_sock, "client", srt_create_socket()); - EXPECT_NE(client_sock, SRT_ERROR); + EXPECT_NE(client_sock, SRT_INVALID_SOCK); const int no = 0; - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - EXPECT_NE(srt_setsockopt (client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockflag(client_sock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockflag(client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect CEPoll epoll; const int epoll_id = epoll.create(); @@ -502,7 +502,7 @@ TEST(CEPoll, ThreadedUpdate) this_thread::sleep_for(chrono::seconds(1)); // Make sure that uwait will be called as first cerr << "ADDING sockets to eid\n"; const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - ASSERT_NE(epoll.update_usock(epoll_id, client_sock, &epoll_out), SRT_ERROR); + epoll.update_usock(epoll_id, client_sock, &epoll_out); set epoll_ids = { epoll_id }; @@ -526,7 +526,7 @@ TEST(CEPoll, ThreadedUpdate) try { int no_events = 0; - EXPECT_EQ(epoll.update_usock(epoll_id, client_sock, &no_events), 0); + epoll.update_usock(epoll_id, client_sock, &no_events); } catch (CUDTException &ex) { @@ -536,7 +536,7 @@ TEST(CEPoll, ThreadedUpdate) try { - EXPECT_EQ(epoll.release(epoll_id), 0); + epoll.release(epoll_id); } catch (CUDTException &ex) { @@ -545,13 +545,416 @@ TEST(CEPoll, ThreadedUpdate) } } +void testListenerReady(const bool LATE_CALL, size_t nmembers) +{ + bool is_single = true; + bool want_sleep = !TestEnv::me->OptionPresent("nosleep"); + + sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(5555); + ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + + TestInit init; + + SRTSOCKET server_sock, caller_sock; + server_sock = srt_create_socket(); + + if (nmembers > 0) + { + caller_sock = srt_create_group(SRT_GTYPE_BROADCAST); + int on = 1; + EXPECT_NE(srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &on, sizeof on), SRT_ERROR); + is_single = false; + } + else + { + caller_sock = srt_create_socket(); + nmembers = 1; // Set to 1 so that caller starts at least once. + } + + srt_bind(server_sock, (sockaddr*)& sa, sizeof(sa)); + srt_listen(server_sock, nmembers+1); + + srt::setopt(server_sock)[SRTO_RCVSYN] = false; + + // Ok, the listener socket is ready; now make a call, but + // do not do anything on the listener socket yet. + + std::cout << "Using " << (LATE_CALL ? "LATE" : "EARLY") << " call\n"; + + std::vector> connect_res; + + if (LATE_CALL) + { + // We don't need the caller to be async, it can hang up here. + for (size_t i = 0; i < nmembers; ++i) + { + connect_res.push_back(std::async(std::launch::async, [&caller_sock, &sa, i]() { + std::cout << "[T:" << i << "] CALLING\n"; + return srt_connect(caller_sock, (sockaddr*)& sa, sizeof(sa)); + })); + } + + std::cout << "STARTED connecting...\n"; + } + + if (want_sleep) + { + std::cout << "Sleeping 1s...\n"; + this_thread::sleep_for(chrono::milliseconds(1000)); + } + + // What is important is that the accepted socket is now reporting in + // on the listener socket. So let's create an epoll. + + int eid = srt_epoll_create(); + int eid_postcheck = srt_epoll_create(); + + // and add this listener to it + int modes = SRT_EPOLL_IN; + int modes_postcheck = SRT_EPOLL_IN | SRT_EPOLL_UPDATE; + EXPECT_NE(srt_epoll_add_usock(eid, server_sock, &modes), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(eid_postcheck, server_sock, &modes_postcheck), SRT_ERROR); + + if (!LATE_CALL) + { + // We don't need the caller to be async, it can hang up here. + for (size_t i = 0; i < nmembers; ++i) + { + connect_res.push_back(std::async(std::launch::async, [&caller_sock, &sa, i]() { + std::cout << "[T:" << i << "] CALLING\n"; + return srt_connect(caller_sock, (sockaddr*)& sa, sizeof(sa)); + })); + } + + std::cout << "STARTED connecting...\n"; + } + + std::cout << "Waiting for readiness...\n"; + // And see now if the waiting accepted socket reports it. + SRT_EPOLL_EVENT fdset[1]; + EXPECT_EQ(srt_epoll_uwait(eid, fdset, 1, 5000), 1); + + std::cout << "Accepting...\n"; + sockaddr_in scl; + int sclen = sizeof scl; + SRTSOCKET sock = srt_accept(server_sock, (sockaddr*)& scl, &sclen); + EXPECT_NE(sock, SRT_INVALID_SOCK); + + if (nmembers > 1) + { + std::cout << "With >1 members, check if there's still UPDATE pending\n"; + // Spawn yet another connection within the group, just to get the update + auto extra_call = std::async(std::launch::async, [&caller_sock, &sa]() { + std::cout << "[T:X] CALLING (expected failure)\n"; + return srt_connect(caller_sock, (sockaddr*)& sa, sizeof(sa)); + }); + // For 2+ members, additionally check if there AREN'T any + // further acceptance members, but there are UPDATEs. + EXPECT_EQ(srt_epoll_uwait(eid_postcheck, fdset, 1, 5000), 1); + + // SUBSCRIBED EVENTS: IN, UPDATE. + // expected: UPDATE only. + EXPECT_EQ(SRT_EPOLL_OPT(fdset[0].events), SRT_EPOLL_UPDATE); + SRTSOCKET joined = extra_call.get(); + EXPECT_NE(joined, SRT_INVALID_SOCK); + std::cout << Sprint("Extra joined: @", joined, "\n"); + } + + std::vector gdata; + + if (!is_single) + { + EXPECT_EQ(sock & SRTGROUP_MASK, SRTGROUP_MASK); + // +1 because we have added one more caller to check UPDATE event. + size_t inoutlen = nmembers+1; + gdata.resize(inoutlen); + int groupndata = srt_group_data(sock, gdata.data(), (&inoutlen)); + EXPECT_NE(groupndata, SRT_ERROR); + + std::ostringstream sout; + if (groupndata == SRT_ERROR) + sout << "ERROR: " << srt_getlasterror_str() << " OUTLEN: " << inoutlen << std::endl; + else + { + // Just to display the members + sout << "(Listener) Members: "; + + for (int i = 0; i < groupndata; ++i) + sout << "@" << gdata[i].id << " "; + sout << std::endl; + } + + std::cout << sout.str(); + } + + std::cout << "Joining connector thread(s)\n"; + for (size_t i = 0; i < nmembers; ++i) + { + std::cout << "Join: #" << i << ":\n"; + SRTSOCKET called_socket = connect_res[i].get(); + std::cout << "... " << called_socket << std::endl; + EXPECT_NE(called_socket, SRT_INVALID_SOCK); + } + + if (!is_single) + { + EXPECT_EQ(caller_sock & SRTGROUP_MASK, SRTGROUP_MASK); + // +1 because we have added one more caller to check UPDATE event. + size_t inoutlen = nmembers+1; + gdata.resize(inoutlen); + int groupndata = srt_group_data(caller_sock, gdata.data(), (&inoutlen)); + EXPECT_NE(groupndata, SRT_ERROR); + + std::ostringstream sout; + if (groupndata == SRT_ERROR) + sout << "ERROR: " << srt_getlasterror_str() << " OUTLEN: " << inoutlen << std::endl; + else + { + // Just to display the members + sout << "(Caller) Members: "; + + for (int i = 0; i < groupndata; ++i) + sout << "@" << gdata[i].id << " "; + sout << std::endl; + } + + std::cout << sout.str(); + + if (want_sleep) + { + std::cout << "Sleep for 3 seconds to avoid closing-in-between\n"; + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + } + + std::cout << "Releasing EID resources and all sockets\n"; + + srt_epoll_release(eid); + srt_epoll_release(eid_postcheck); + + srt_close(server_sock); + srt_close(caller_sock); + srt_close(sock); +} + +TEST(CEPoll, EarlyListenerReady) +{ + testListenerReady(false, 0); +} + +TEST(CEPoll, LateListenerReady) +{ + testListenerReady(true, 0); +} + +#if ENABLE_BONDING + +TEST(CEPoll, EarlyGroupListenerReady_1) +{ + testListenerReady(false, 1); +} + +TEST(CEPoll, LateGroupListenerReady_1) +{ + testListenerReady(true, 1); +} + +TEST(CEPoll, EarlyGroupListenerReady_3) +{ + testListenerReady(false, 3); +} + +TEST(CEPoll, LateGroupListenerReady_3) +{ + testListenerReady(true, 3); +} + + +void testMultipleListenerReady(const bool LATE_CALL) +{ + sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(5555); + ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + + sockaddr_in sa2; + memset(&sa2, 0, sizeof sa2); + sa2.sin_family = AF_INET; + sa2.sin_port = htons(5556); + ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa2.sin_addr), 1); + + TestInit init; + + SRTSOCKET server_sock, server_sock2, caller_sock; + server_sock = srt_create_socket(); + server_sock2 = srt_create_socket(); + + caller_sock = srt_create_group(SRT_GTYPE_BROADCAST); + int on = 1; + EXPECT_NE(srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &on, sizeof on), SRT_ERROR); + EXPECT_NE(srt_setsockflag(server_sock2, SRTO_GROUPCONNECT, &on, sizeof on), SRT_ERROR); + + srt_bind(server_sock, (sockaddr*)& sa, sizeof(sa)); + srt_listen(server_sock, 3); + srt::setopt(server_sock)[SRTO_RCVSYN] = false; + + srt_bind(server_sock2, (sockaddr*)& sa2, sizeof(sa2)); + srt_listen(server_sock2, 3); + srt::setopt(server_sock2)[SRTO_RCVSYN] = false; + + // Ok, the listener socket is ready; now make a call, but + // do not do anything on the listener socket yet. + + std::cout << "Using " << (LATE_CALL ? "LATE" : "EARLY") << " call\n"; + + std::vector> connect_res; + + if (LATE_CALL) + { + connect_res.push_back(std::async(std::launch::async, [&caller_sock, &sa]() { + this_thread::sleep_for(chrono::milliseconds(1)); + return srt_connect(caller_sock, (sockaddr*)& sa, sizeof(sa)); + })); + + connect_res.push_back(std::async(std::launch::async, [&caller_sock, &sa2]() { + this_thread::sleep_for(chrono::milliseconds(1)); + return srt_connect(caller_sock, (sockaddr*)& sa2, sizeof(sa2)); + })); + + + std::cout << "STARTED connecting...\n"; + } + + std::cout << "Sleeping 1s...\n"; + this_thread::sleep_for(chrono::milliseconds(1000)); + + // What is important is that the accepted socket is now reporting in + // on the listener socket. So let's create an epoll. + + int eid = srt_epoll_create(); + int eid_postcheck = srt_epoll_create(); + + // and add this listener to it + int modes = SRT_EPOLL_IN; + int modes_postcheck = SRT_EPOLL_IN | SRT_EPOLL_UPDATE; + EXPECT_NE(srt_epoll_add_usock(eid, server_sock, &modes), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(eid, server_sock2, &modes), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(eid_postcheck, server_sock, &modes_postcheck), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(eid_postcheck, server_sock2, &modes_postcheck), SRT_ERROR); + + if (!LATE_CALL) + { + connect_res.push_back(std::async(std::launch::async, [&caller_sock, &sa]() { + this_thread::sleep_for(chrono::milliseconds(1)); + return srt_connect(caller_sock, (sockaddr*)& sa, sizeof(sa)); + })); + + connect_res.push_back(std::async(std::launch::async, [&caller_sock, &sa2]() { + this_thread::sleep_for(chrono::milliseconds(1)); + return srt_connect(caller_sock, (sockaddr*)& sa2, sizeof(sa2)); + })); + + std::cout << "STARTED connecting...\n"; + } + + // Sleep to make sure that the connection process has started. + this_thread::sleep_for(chrono::milliseconds(100)); + + std::cout << "Waiting for readiness on @" << server_sock << " and @" << server_sock2 << "\n"; + // And see now if the waiting accepted socket reports it. + + // This time we should expect that the connection reports in + // on two listener sockets + SRT_EPOLL_EVENT fdset[2] = {}; + std::ostringstream out; + + int nready = srt_epoll_uwait(eid, fdset, 2, 5000); + EXPECT_EQ(nready, 2); + out << "Ready socks:"; + for (int i = 0; i < nready; ++i) + { + out << " @" << fdset[i].fd; + PrintEpollEvent(out, fdset[i].events); + } + out << std::endl; + std::cout << out.str(); + + std::cout << "Accepting...\n"; + sockaddr_in scl; + int sclen = sizeof scl; + + // We choose the SECOND one to extract the group connection. + SRTSOCKET sock = srt_accept(server_sock2, (sockaddr*)& scl, &sclen); + EXPECT_NE(sock, SRT_INVALID_SOCK); + + // Make sure this time that the accepted connection is a group. + EXPECT_EQ(sock & SRTGROUP_MASK, SRTGROUP_MASK); + + std::cout << "Check if there's still UPDATE pending\n"; + // Spawn yet another connection within the group, just to get the update + auto extra_call = std::async(std::launch::async, [&caller_sock, &sa]() { + return srt_connect(caller_sock, (sockaddr*)& sa, sizeof(sa)); + }); + // For 2+ members, additionally check if there AREN'T any + // further acceptance members, but there are UPDATEs. + // Note that if this was done AFTER accepting, the UPDATE would + // be only set one one socket. + nready = srt_epoll_uwait(eid_postcheck, fdset, 1, 5000); + EXPECT_EQ(nready, 1); + + std::cout << "Ready socks:"; + for (int i = 0; i < nready; ++i) + { + std::cout << " @" << fdset[i].fd; + PrintEpollEvent(std::cout, fdset[i].events); + } + std::cout << std::endl; + + // SUBSCRIBED EVENTS: IN, UPDATE. + // expected: UPDATE only. + EXPECT_EQ(SRT_EPOLL_OPT(fdset[0].events), SRT_EPOLL_UPDATE); + EXPECT_NE(extra_call.get(), SRT_INVALID_SOCK); + + std::cout << "Joining connector thread(s)\n"; + for (size_t i = 0; i < connect_res.size(); ++i) + { + EXPECT_NE(connect_res[i].get(), SRT_INVALID_SOCK); + } + + srt_epoll_release(eid); + srt_epoll_release(eid_postcheck); + + srt_close(server_sock); + srt_close(server_sock2); + srt_close(caller_sock); + srt_close(sock); +} + +TEST(CEPoll, EarlyGroupMultiListenerReady) +{ + testMultipleListenerReady(false); +} + +TEST(CEPoll, LateGroupMultiListenerReady) +{ + testMultipleListenerReady(true); +} + + + +#endif + class TestEPoll: public srt::Test { protected: - int m_client_pollid = SRT_ERROR; - SRTSOCKET m_client_sock = SRT_ERROR; + int m_client_pollid = int(SRT_ERROR); + SRTSOCKET m_client_sock = SRT_INVALID_SOCK; void clientSocket() { @@ -559,12 +962,12 @@ class TestEPoll: public srt::Test int no = 0; m_client_sock = srt_create_socket(); - ASSERT_NE(m_client_sock, SRT_ERROR); + ASSERT_NE(m_client_sock, SRT_INVALID_SOCK); - ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_out = SRT_EPOLL_OUT; srt_epoll_add_usock(m_client_pollid, m_client_sock, &epoll_out); @@ -578,7 +981,7 @@ class TestEPoll: public srt::Test sockaddr* psa = (sockaddr*)&sa; - ASSERT_NE(srt_connect(m_client_sock, psa, sizeof sa), SRT_ERROR); + ASSERT_NE(srt_connect(m_client_sock, psa, sizeof sa), SRT_INVALID_SOCK); // Socket readiness for connection is checked by polling on WRITE allowed sockets. @@ -594,7 +997,7 @@ class TestEPoll: public srt::Test 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); + 0, 0, 0, 0), int(SRT_ERROR)); ASSERT_EQ(rlen, 0); // get exactly one write event without reads ASSERT_EQ(wlen, 1); // get exactly one write event without reads @@ -606,11 +1009,11 @@ class TestEPoll: public srt::Test -1, // infinit ttl true // in order must be set to true ), - SRT_ERROR); + int(SRT_ERROR)); // disable receiving OUT events int epoll_err = SRT_EPOLL_ERR; - ASSERT_EQ(0, srt_epoll_update_usock(m_client_pollid, m_client_sock, &epoll_err)); + ASSERT_EQ(srt_epoll_update_usock(m_client_pollid, m_client_sock, &epoll_err), SRT_STATUS_OK); { int rlen = 2; SRTSOCKET read[2]; @@ -623,11 +1026,11 @@ class TestEPoll: public srt::Test 1000, 0, 0, 0, 0)); const int last_error = srt_getlasterror(NULL); - EXPECT_EQ(SRT_ETIMEOUT, last_error) << last_error; + EXPECT_EQ(last_error, (int)SRT_ETIMEOUT) << last_error; } } - int m_server_pollid = SRT_ERROR; + int m_server_pollid = int(SRT_ERROR); void createServerSocket(SRTSOCKET& w_servsock) { @@ -635,10 +1038,10 @@ class TestEPoll: public srt::Test int no = 0; SRTSOCKET servsock = srt_create_socket(); - ASSERT_NE(servsock, SRT_ERROR); + ASSERT_NE(servsock, SRT_INVALID_SOCK); - ASSERT_NE(srt_setsockopt(servsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(servsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockflag(servsock, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(servsock, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_in = SRT_EPOLL_IN; srt_epoll_add_usock(m_server_pollid, servsock, &epoll_in); @@ -672,7 +1075,7 @@ class TestEPoll: public srt::Test 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 ); + 0, 0, 0, 0), int(SRT_ERROR)); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes @@ -699,7 +1102,7 @@ class TestEPoll: public srt::Test 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 ); + 0, 0, 0, 0), int(SRT_ERROR)); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes @@ -737,10 +1140,10 @@ class TestEPoll: public srt::Test void setup() override { m_client_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, m_client_pollid); + ASSERT_NE(m_client_pollid, int(SRT_ERROR)); m_server_pollid = srt_epoll_create(); - ASSERT_NE(SRT_ERROR, m_server_pollid); + ASSERT_NE(m_server_pollid, int(SRT_ERROR)); } diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index 3a7cb0791..97d7d0228 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -58,7 +58,7 @@ class TestFECRebuilding: public srt::Test source.emplace_back(new CPacket); CPacket& p = *source.back(); - p.allocate(SRT_LIVE_MAX_PLSIZE); + p.allocate(SRT_MAX_PLSIZE_AF_INET); uint32_t* hdr = p.getHeader(); @@ -313,8 +313,8 @@ TEST(TestFEC, Connection) // 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); + ASSERT_NE(a, SRT_INVALID_SOCK); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); // Now that the connection is established, check negotiated config @@ -372,7 +372,7 @@ TEST(TestFEC, ConnectionReorder) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); - EXPECT_EQ(connect_res.get(), SRT_SUCCESS); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); // Now that the connection is established, check negotiated config @@ -430,7 +430,7 @@ TEST(TestFEC, ConnectionFull1) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); - EXPECT_EQ(connect_res.get(), SRT_SUCCESS); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); // Now that the connection is established, check negotiated config @@ -488,7 +488,7 @@ TEST(TestFEC, ConnectionFull2) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); - EXPECT_EQ(connect_res.get(), SRT_SUCCESS); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); // Now that the connection is established, check negotiated config @@ -546,7 +546,7 @@ TEST(TestFEC, ConnectionMess) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR) << srt_getlasterror_str(); - EXPECT_EQ(connect_res.get(), SRT_SUCCESS); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); // Now that the connection is established, check negotiated config @@ -602,7 +602,7 @@ TEST(TestFEC, ConnectionForced) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 2000); ASSERT_NE(a, SRT_ERROR); - EXPECT_EQ(connect_res.get(), SRT_SUCCESS); + EXPECT_EQ(connect_res.get(), SRT_STATUS_OK); // Now that the connection is established, check negotiated config @@ -774,7 +774,7 @@ TEST_F(TestFECRebuilding, Prepare) seq = p.getSeqNo(); } - SrtPacket fec_ctl(SRT_LIVE_MAX_PLSIZE); + SrtPacket fec_ctl(SRT_MAX_PLSIZE_AF_INET); // Use the sequence number of the last packet, as usual. bool have_fec_ctl = fec->packControlPacket(fec_ctl, seq); @@ -795,7 +795,7 @@ TEST_F(TestFECRebuilding, NoRebuild) seq = p.getSeqNo(); } - SrtPacket fec_ctl(SRT_LIVE_MAX_PLSIZE); + SrtPacket fec_ctl(SRT_MAX_PLSIZE_AF_INET); // Use the sequence number of the last packet, as usual. const bool have_fec_ctl = fec->packControlPacket(fec_ctl, seq); @@ -872,7 +872,7 @@ TEST_F(TestFECRebuilding, Rebuild) seq = p.getSeqNo(); } - SrtPacket fec_ctl(SRT_LIVE_MAX_PLSIZE); + SrtPacket fec_ctl(SRT_MAX_PLSIZE_AF_INET); // Use the sequence number of the last packet, as usual. const bool have_fec_ctl = fec->packControlPacket(fec_ctl, seq); diff --git a/test/test_file_transmission.cpp b/test/test_file_transmission.cpp index bfd668ac7..6ad97072a 100644 --- a/test/test_file_transmission.cpp +++ b/test/test_file_transmission.cpp @@ -18,6 +18,7 @@ #endif #include "srt.h" +#include "netinet_any.h" #include #include @@ -29,7 +30,7 @@ //#pragma comment (lib, "ws2_32.lib") -TEST(Transmission, FileUpload) +TEST(FileTransmission, Upload) { srt::TestInit srtinit; srtinit.HandlePerTestOptions(); @@ -52,21 +53,22 @@ TEST(Transmission, FileUpload) // Find unused a port not used by any other service. // Otherwise srt_connect may actually connect. - int bind_res = -1; + SRTSTATUS bind_res = SRT_ERROR; for (int port = 5000; port <= 5555; ++port) { sa_lsn.sin_port = htons(port); bind_res = srt_bind(sock_lsn, (sockaddr*)&sa_lsn, sizeof sa_lsn); - if (bind_res == 0) + int bind_err = srt_getlasterror(NULL); + if (bind_res == SRT_STATUS_OK) { std::cout << "Running test on port " << port << "\n"; break; } - ASSERT_TRUE(bind_res == SRT_EINVOP) << "Bind failed not due to an occupied port. Result " << bind_res; + ASSERT_TRUE(bind_err == SRT_EINVOP) << "Bind failed not due to an occupied port. Result " << bind_err; } - ASSERT_GE(bind_res, 0); + ASSERT_GE((int)bind_res, 0); srt_bind(sock_lsn, (sockaddr*)&sa_lsn, sizeof sa_lsn); @@ -210,3 +212,200 @@ TEST(Transmission, FileUpload) remove("file.target"); } + +TEST(FileTransmission, Setup46) +{ + using namespace srt; + SRTST_REQUIRES(IPv6); + TestInit srtinit; + + SRTSOCKET sock_lsn = srt_create_socket(), sock_clr = srt_create_socket(); + + const int tt = SRTT_FILE; + srt_setsockflag(sock_lsn, SRTO_TRANSTYPE, &tt, sizeof tt); + srt_setsockflag(sock_clr, SRTO_TRANSTYPE, &tt, sizeof tt); + + // Setup a connection with IPv6 caller and IPv4 listener, + // then send data of 1456 size and make sure two packets were used. + + // So first configure a caller for IPv6 socket, capable of + // using IPv4. As the IP version isn't specified now when + // creating a socket, force binding explicitly. + + // This creates the "any" spec for IPv6 with port = 0 + sockaddr_any sa(AF_INET6); + + int ipv4_and_ipv6 = 0; + ASSERT_NE(srt_setsockflag(sock_clr, SRTO_IPV6ONLY, &ipv4_and_ipv6, sizeof ipv4_and_ipv6), -1); + + ASSERT_NE(srt_bind(sock_clr, sa.get(), sa.size()), -1); + + int connect_port = 5555; + + // Configure listener + sockaddr_in sa_lsn = sockaddr_in(); + sa_lsn.sin_family = AF_INET; + sa_lsn.sin_addr.s_addr = INADDR_ANY; + sa_lsn.sin_port = htons(connect_port); + + // Find unused a port not used by any other service. + // Otherwise srt_connect may actually connect. + int bind_res = -1; + for (connect_port = 5000; connect_port <= 5555; ++connect_port) + { + sa_lsn.sin_port = htons(connect_port); + bind_res = srt_bind(sock_lsn, (sockaddr*)&sa_lsn, sizeof sa_lsn); + if (bind_res == 0) + { + std::cout << "Running test on port " << connect_port << "\n"; + break; + } + + ASSERT_TRUE(bind_res == SRT_EINVOP) << "Bind failed not due to an occupied port. Result " << bind_res; + } + + ASSERT_GE(bind_res, 0); + + srt_listen(sock_lsn, 1); + + ASSERT_EQ(inet_pton(AF_INET6, "::FFFF:127.0.0.1", &sa.sin6.sin6_addr), 1); + + sa.hport(connect_port); + + ASSERT_EQ(srt_connect(sock_clr, sa.get(), sa.size()), 0); + + int sock_acp = -1; + ASSERT_NE(sock_acp = srt_accept(sock_lsn, sa.get(), &sa.len), -1); + + const size_t SIZE = 1454; // Max payload for IPv4 minus 2 - still more than 1444 for IPv6 + char buffer[SIZE]; + + std::random_device rd; + std::mt19937 mtrd(rd()); + std::uniform_int_distribution dis(0, UINT8_MAX); + + for (size_t i = 0; i < SIZE; ++i) + { + buffer[i] = dis(mtrd); + } + + EXPECT_EQ(srt_send(sock_acp, buffer, SIZE), int(SIZE)) << srt_getlasterror_str(); + + char resultbuf[SIZE]; + EXPECT_EQ(srt_recv(sock_clr, resultbuf, SIZE), int(SIZE)) << srt_getlasterror_str(); + + // It should use the maximum payload size per packet reported from the option. + int payloadsize_back = 0; + int payloadsize_back_size = sizeof (payloadsize_back); + EXPECT_EQ(srt_getsockflag(sock_clr, SRTO_PAYLOADSIZE, &payloadsize_back, &payloadsize_back_size), 0); + EXPECT_EQ(payloadsize_back, SRT_MAX_PLSIZE_AF_INET); + + SRT_TRACEBSTATS snd_stats, rcv_stats; + srt_bstats(sock_acp, &snd_stats, 0); + srt_bstats(sock_clr, &rcv_stats, 0); + + EXPECT_EQ(snd_stats.pktSentUniqueTotal, 1); + EXPECT_EQ(rcv_stats.pktRecvUniqueTotal, 1); + +} + +TEST(FileTransmission, Setup66) +{ + using namespace srt; + SRTST_REQUIRES(IPv6); + TestInit srtinit; + + SRTSOCKET sock_lsn = srt_create_socket(), sock_clr = srt_create_socket(); + + const int tt = SRTT_FILE; + srt_setsockflag(sock_lsn, SRTO_TRANSTYPE, &tt, sizeof tt); + srt_setsockflag(sock_clr, SRTO_TRANSTYPE, &tt, sizeof tt); + + // Setup a connection with IPv6 caller and IPv4 listener, + // then send data of 1456 size and make sure two packets were used. + + // So first configure a caller for IPv6 socket, capable of + // using IPv4. As the IP version isn't specified now when + // creating a socket, force binding explicitly. + + // This creates the "any" spec for IPv6 with port = 0 + sockaddr_any sa(AF_INET6); + + // Require that the connection allows both IP versions. + int ipv4_and_ipv6 = 0; + ASSERT_NE(srt_setsockflag(sock_clr, SRTO_IPV6ONLY, &ipv4_and_ipv6, sizeof ipv4_and_ipv6), -1); + ASSERT_NE(srt_setsockflag(sock_lsn, SRTO_IPV6ONLY, &ipv4_and_ipv6, sizeof ipv4_and_ipv6), -1); + + ASSERT_NE(srt_bind(sock_clr, sa.get(), sa.size()), -1); + + int connect_port = 5555; + + // Configure listener + sockaddr_any sa_lsn(AF_INET6); + + // Find unused a port not used by any other service. + // Otherwise srt_connect may actually connect. + int bind_res = -1; + for (connect_port = 5000; connect_port <= 5555; ++connect_port) + { + sa_lsn.hport(connect_port); + bind_res = srt_bind(sock_lsn, sa_lsn.get(), sa_lsn.size()); + if (bind_res == 0) + { + std::cout << "Running test on port " << connect_port << "\n"; + break; + } + + ASSERT_TRUE(bind_res == SRT_EINVOP) << "Bind failed not due to an occupied port. Result " << bind_res; + } + + ASSERT_GE(bind_res, 0); + + srt_listen(sock_lsn, 1); + + ASSERT_EQ(inet_pton(AF_INET6, "::1", &sa.sin6.sin6_addr), 1); + + sa.hport(connect_port); + + std::cout << "Connecting to: " << sa.str() << std::endl; + + int connect_result = srt_connect(sock_clr, sa.get(), sa.size()); + ASSERT_EQ(connect_result, 0) << srt_getlasterror_str(); + + int sock_acp = -1; + ASSERT_NE(sock_acp = srt_accept(sock_lsn, sa.get(), &sa.len), SRT_ERROR); + + const size_t SIZE = 1454; // Max payload for IPv4 minus 2 - still more than 1444 for IPv6 + char buffer[SIZE]; + + std::random_device rd; + std::mt19937 mtrd(rd()); + std::uniform_int_distribution dis(0, UINT8_MAX); + + for (size_t i = 0; i < SIZE; ++i) + { + buffer[i] = dis(mtrd); + } + + EXPECT_EQ(srt_send(sock_acp, buffer, SIZE), int(SIZE)) << srt_getlasterror_str(); + + char resultbuf[SIZE]; + EXPECT_EQ(srt_recv(sock_clr, resultbuf, SIZE), int(SIZE)) << srt_getlasterror_str(); + + // It should use the maximum payload size per packet reported from the option. + int payloadsize_back = 0; + int payloadsize_back_size = sizeof (payloadsize_back); + EXPECT_EQ(srt_getsockflag(sock_clr, SRTO_PAYLOADSIZE, &payloadsize_back, &payloadsize_back_size), 0); + EXPECT_EQ(payloadsize_back, SRT_MAX_PLSIZE_AF_INET6); + std::cout << "Payload size: " << payloadsize_back << std::endl; + + SRT_TRACEBSTATS snd_stats, rcv_stats; + srt_bstats(sock_acp, &snd_stats, 0); + srt_bstats(sock_clr, &rcv_stats, 0); + + // We use the same data size that fit in 1 payload IPv4, but not IPv6. + // Therefore sending should be here split into two packets. + EXPECT_EQ(snd_stats.pktSentUniqueTotal, 2); + EXPECT_EQ(rcv_stats.pktRecvUniqueTotal, 2); + +} diff --git a/test/test_ipv6.cpp b/test/test_ipv6.cpp index ee11292b0..1f1c2027c 100644 --- a/test/test_ipv6.cpp +++ b/test/test_ipv6.cpp @@ -1,12 +1,16 @@ #include +#include #include +#include #include "gtest/gtest.h" #include "test_env.h" #include "srt.h" +#include "sync.h" #include "netinet_any.h" using srt::sockaddr_any; +using namespace srt::sync; class TestIPv6 : public srt::Test @@ -30,12 +34,17 @@ class TestIPv6 void setup() override { m_caller_sock = srt_create_socket(); - ASSERT_NE(m_caller_sock, SRT_ERROR); + ASSERT_NE(m_caller_sock, SRT_INVALID_SOCK); // IPv6 calling IPv4 would otherwise fail if the system-default net.ipv6.bindv6only=1. ASSERT_NE(srt_setsockflag(m_caller_sock, SRTO_IPV6ONLY, &no, sizeof no), SRT_ERROR); m_listener_sock = srt_create_socket(); - ASSERT_NE(m_listener_sock, SRT_ERROR); + ASSERT_NE(m_listener_sock, SRT_INVALID_SOCK); + + m_CallerStarted.reset(new std::promise); + m_ReadyCaller.reset(new std::promise); + m_ReadyAccept.reset(new std::promise); + } void teardown() override @@ -47,20 +56,71 @@ class TestIPv6 } public: + + void SetupFileMode() + { + int val = SRTT_FILE; + ASSERT_NE(srt_setsockflag(m_caller_sock, SRTO_TRANSTYPE, &val, sizeof val), -1); + ASSERT_NE(srt_setsockflag(m_listener_sock, SRTO_TRANSTYPE, &val, sizeof val), -1); + } + + int m_CallerPayloadSize = 0; + int m_AcceptedPayloadSize = 0; + + std::unique_ptr> m_CallerStarted, m_ReadyCaller, m_ReadyAccept; + + // "default parameter" version. Can't use default parameters because this goes + // against binding parameters. Nor overloading. void ClientThread(int family, const std::string& address) { + return ClientThreadFlex(family, address, true); + } + + void ClientThreadFlex(int family, const std::string& address, bool shouldwork) + { + std::future ready_accepter = m_ReadyAccept->get_future(); + sockaddr_any sa (family); sa.hport(m_listen_port); EXPECT_EQ(inet_pton(family, address.c_str(), sa.get_addr()), 1); - std::cout << "Calling: " << address << "(" << fam[family] << ")\n"; + std::cout << "Calling: " << address << "(" << fam[family] << ") [LOCK...]\n"; + + m_CallerStarted->set_value(); const int connect_res = srt_connect(m_caller_sock, (sockaddr*)&sa, sizeof sa); - EXPECT_NE(connect_res, SRT_ERROR) << "srt_connect() failed with: " << srt_getlasterror_str(); - if (connect_res == SRT_ERROR) - srt_close(m_listener_sock); - PrintAddresses(m_caller_sock, "CALLER"); + if (shouldwork) + { + // Version with expected success + EXPECT_NE(connect_res, SRT_ERROR) << "srt_connect() failed with: " << srt_getlasterror_str(); + + int size = sizeof (int); + EXPECT_NE(srt_getsockflag(m_caller_sock, SRTO_PAYLOADSIZE, &m_CallerPayloadSize, &size), -1); + + m_ReadyCaller->set_value(); + + PrintAddresses(m_caller_sock, "CALLER"); + + if (connect_res == SRT_ERROR) + { + std::cout << "Connect failed - [UNLOCK]\n"; + srt_close(m_listener_sock); + } + else + { + std::cout << "Connect succeeded, [FUTURE-WAIT...]\n"; + ready_accepter.wait(); + } + } + else + { + // Version with expected failure + EXPECT_EQ(connect_res, SRT_ERROR); + EXPECT_EQ(srt_getrejectreason(m_caller_sock), SRT_REJ_CONFIG); + srt_close(m_listener_sock); + } + std::cout << "Connect: exit\n"; } std::map fam = { {AF_INET, "IPv4"}, {AF_INET6, "IPv6"} }; @@ -68,24 +128,34 @@ class TestIPv6 void ShowAddress(std::string src, const sockaddr_any& w) { EXPECT_NE(fam.count(w.family()), 0U) << "INVALID FAMILY"; - std::cout << src << ": " << w.str() << " (" << fam[w.family()] << ")" << std::endl; + // Printing may happen from different threads, avoid intelining. + std::ostringstream sout; + sout << src << ": " << w.str() << " (" << fam[w.family()] << ")" << std::endl; + std::cout << sout.str(); } sockaddr_any DoAccept() { sockaddr_any sc1; + // Make sure the caller started + m_CallerStarted->get_future().wait(); + std::cout << "DoAccept: caller started, proceeding to accept\n"; + SRTSOCKET accepted_sock = srt_accept(m_listener_sock, sc1.get(), &sc1.len); EXPECT_NE(accepted_sock, SRT_INVALID_SOCK) << "accept() failed with: " << srt_getlasterror_str(); if (accepted_sock == SRT_INVALID_SOCK) { return sockaddr_any(); } - PrintAddresses(accepted_sock, "ACCEPTED"); sockaddr_any sn; EXPECT_NE(srt_getsockname(accepted_sock, sn.get(), &sn.len), SRT_ERROR); EXPECT_NE(sn.get_addr(), nullptr); + int size = sizeof (int); + EXPECT_NE(srt_getsockflag(m_caller_sock, SRTO_PAYLOADSIZE, &m_AcceptedPayloadSize, &size), -1); + + m_ReadyCaller->get_future().wait(); if (sn.get_addr() != nullptr) { @@ -94,6 +164,9 @@ class TestIPv6 << "EMPTY address in srt_getsockname"; } + std::cout << "DoAccept: ready accept - promise SET\n"; + m_ReadyAccept->set_value(); + srt_close(accepted_sock); return sn; } @@ -197,3 +270,86 @@ TEST_F(TestIPv6, v6_calls_v4) client.join(); } +TEST_F(TestIPv6, plsize_v6) +{ + SRTST_REQUIRES(IPv6); + + SetupFileMode(); + + sockaddr_any sa (AF_INET6); + sa.hport(m_listen_port); + + // This time bind the socket exclusively to IPv6. + ASSERT_EQ(srt_setsockflag(m_listener_sock, SRTO_IPV6ONLY, &yes, sizeof yes), 0); + ASSERT_EQ(inet_pton(AF_INET6, "::1", sa.get_addr()), 1); + + ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); + ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); + + std::thread client(&TestIPv6::ClientThread, this, AF_INET6, "::1"); + + DoAccept(); + + EXPECT_EQ(m_CallerPayloadSize, 1444); // == 1500 - 32[IPv6] - 8[UDP] - 16[SRT] + EXPECT_EQ(m_AcceptedPayloadSize, 1444); + + client.join(); +} + +TEST_F(TestIPv6, plsize_v4) +{ + SetupFileMode(); + + sockaddr_any sa (AF_INET); + sa.hport(m_listen_port); + + // This time bind the socket exclusively to IPv4. + ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", sa.get_addr()), 1); + + ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); + ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); + + std::thread client(&TestIPv6::ClientThread, this, AF_INET6, "0::FFFF:127.0.0.1"); + + DoAccept(); + + EXPECT_EQ(m_CallerPayloadSize, 1456); // == 1500 - 20[IPv4] - 8[UDP] - 16[SRT] + EXPECT_EQ(m_AcceptedPayloadSize, 1456); + + client.join(); +} + +TEST_F(TestIPv6, plsize_faux_v6) +{ + SRTST_REQUIRES(IPv6); + + using namespace std::chrono; + SetupFileMode(); + + sockaddr_any sa (AF_INET6); + sa.hport(m_listen_port); + + // This time bind the socket exclusively to IPv6. + ASSERT_EQ(srt_setsockflag(m_listener_sock, SRTO_IPV6ONLY, &yes, sizeof yes), 0); + ASSERT_EQ(inet_pton(AF_INET6, "::1", sa.get_addr()), 1); + + ASSERT_NE(srt_bind(m_listener_sock, sa.get(), sa.size()), SRT_ERROR); + ASSERT_NE(srt_listen(m_listener_sock, SOMAXCONN), SRT_ERROR); + + int oversize = 1450; + ASSERT_NE(srt_setsockflag(m_caller_sock, SRTO_PAYLOADSIZE, &oversize, sizeof (int)), -1); + + std::thread client(&TestIPv6::ClientThreadFlex, this, AF_INET6, "::1", false); + + // Set on sleeping to make sure that the thread started. + // Sleeping isn't reliable so do a dampened spinlock here. + // This flag also confirms that the caller acquired the mutex and will + // unlock it for CV waiting - so we can proceed to notifying it. + m_CallerStarted->get_future().wait(); + + // Just in case of a test failure, kick CV to avoid deadlock + std::cout << "TEST: [PROMISE-SET]\n"; + m_ReadyAccept->set_value(); + + client.join(); +} diff --git a/test/test_main.cpp b/test/test_main.cpp index e2243a306..2079e2c36 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -94,12 +94,12 @@ bool TestEnv::Allowed_IPv6() void TestInit::start(int& w_retstatus) { - ASSERT_GE(w_retstatus = srt_startup(), 0); + ASSERT_GE(w_retstatus = (int)srt_startup(), 0); } void TestInit::stop() { - EXPECT_NE(srt_cleanup(), -1); + EXPECT_NE((int)srt_cleanup(), -1); } // This function finds some interesting options among command diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index fe27bb387..3a5e68b88 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -342,7 +342,7 @@ const OptionTestEntry g_test_matrix_options[] = { SRTO_MESSAGEAPI, "SRTO_MESSAGEAPI", RestrictionType::PRE, sizeof(bool), false, true, true, false, {}, O | W | G | S | D | O | O }, { SRTO_MININPUTBW, "SRTO_MININPUTBW", RestrictionType::POST, sizeof(int64_t), int64_t(0), INT64_MAX, int64_t(0), int64_t(200000), {int64_t(-1)}, R | W | G | S | D | O | O }, { SRTO_MINVERSION, "SRTO_MINVERSION", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0x010000, 0x010300, {}, R | W | G | S | D | O | O }, - { SRTO_MSS, "SRTO_MSS", RestrictionType::PREBIND, sizeof(int), 76, 65536, 1500, 1400, {-1, 0, 75}, R | W | G | S | D | O | O }, + { SRTO_MSS, "SRTO_MSS", RestrictionType::PREBIND, sizeof(int), 116, 65536, 1500, 1400, {-1, 0, 75}, R | W | G | S | D | O | O }, { SRTO_NAKREPORT, "SRTO_NAKREPORT", RestrictionType::PRE, sizeof(bool), false, true, true, false, {}, R | W | G | S | D | O | M }, { SRTO_OHEADBW, "SRTO_OHEADBW", RestrictionType::POST, sizeof(int), 5, 100, 25, 20, {-1, 0, 4, 101}, R | W | G | S | D | O | O }, //SRTO_PACKETFILTER diff --git a/testing/srt-test-file.cpp b/testing/srt-test-file.cpp index 3dd8fe998..baacc66d1 100644 --- a/testing/srt-test-file.cpp +++ b/testing/srt-test-file.cpp @@ -261,7 +261,7 @@ bool DoUpload(UriParser& ut, string path, string filename) { int st = srt_send(ss, buf.data()+shift, int(n)); Verb() << "Upload: " << n << " --> " << st << (!shift ? string() : "+" + Sprint(shift)); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { cerr << "Upload: SRT error: " << srt_getlasterror_str() << endl; return false; @@ -290,7 +290,7 @@ bool DoUpload(UriParser& ut, string path, string filename) size_t bytes; size_t blocks; int st = srt_getsndbuffer(ss, &blocks, &bytes); - if (st == SRT_ERROR) + if (st == int(SRT_ERROR)) { cerr << "Error in srt_getsndbuffer: " << srt_getlasterror_str() << endl; return false; @@ -358,7 +358,7 @@ bool DoDownload(UriParser& us, string directory, string filename) for (;;) { int n = srt_recv(ss, buf.data(), int(::g_buffer_size)); - if (n == SRT_ERROR) + if (n == int(SRT_ERROR)) { cerr << "Download: SRT error: " << srt_getlasterror_str() << endl; return false; diff --git a/testing/srt-test-live.cpp b/testing/srt-test-live.cpp index 12478e461..82656fc69 100644 --- a/testing/srt-test-live.cpp +++ b/testing/srt-test-live.cpp @@ -72,13 +72,18 @@ #include "testmedia.hpp" // requires access to SRT-dependent globals #include "verbose.hpp" -// NOTE: This is without "haisrt/" because it uses an internal path +// NOTE: This is without "srt/" because it uses an internal path // to the library. Application using the "installed" library should // use #include -#include // This TEMPORARILY contains extra C++-only SRT API. +#include #include +// Define as 1 to test how the stubbed non-bonding version is working. +#ifndef ENABLE_BONDING +#define ENABLE_BONDING 0 +#endif + using namespace std; srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-live"); @@ -280,7 +285,6 @@ namespace srt_logging extern Logger glog; } -#if ENABLE_BONDING extern "C" int SrtCheckGroupHook(void* , SRTSOCKET acpsock, int , const sockaddr*, const char* ) { static string gtypes[] = { @@ -299,7 +303,7 @@ extern "C" int SrtCheckGroupHook(void* , SRTSOCKET acpsock, int , const sockaddr { SRT_GROUP_TYPE gt; size = sizeof gt; - if (-1 != srt_getsockflag(acpsock, SRTO_GROUPTYPE, >, &size)) + if (SRT_ERROR != srt_getsockflag(acpsock, SRTO_GROUPTYPE, >, &size)) { if (size_t(gt) < Size(gtypes)) Verb(" type=", gtypes[gt], VerbNoEOL); @@ -311,7 +315,6 @@ extern "C" int SrtCheckGroupHook(void* , SRTSOCKET acpsock, int , const sockaddr return 0; } -#endif extern "C" int SrtUserPasswordHook(void* , SRTSOCKET acpsock, int hsv, const sockaddr*, const char* streamid) { @@ -355,6 +358,7 @@ extern "C" int SrtUserPasswordHook(void* , SRTSOCKET acpsock, int hsv, const soc // This hook sets the password to the just accepted socket // depending on the user + srt_setrejectreason(acpsock, SRT_REJX_UNAUTHORIZED); string exp_pw = passwd.at(username); srt_setsockflag(acpsock, SRTO_PASSPHRASE, exp_pw.c_str(), int(exp_pw.size())); @@ -418,6 +422,8 @@ int main( int argc, char** argv ) o_hook ((optargs), " Use listener callback of given specification (internally coded)", "hook"), #if ENABLE_BONDING o_group ((optargs), " Using multiple SRT connections as redundancy group", "g"), +#else + o_group ((optargs), " NOT SUPPORTED (Bonding not enabled at compile time)", "g"), #endif o_stime ((optargs), " Pass source time explicitly to SRT output", "st", "srctime", "sourcetime"), o_retry ((optargs), " Retry connection N times if failed on timeout", "rc", "retry"), @@ -431,25 +437,23 @@ int main( int argc, char** argv ) vector args = params[""]; string source_spec, target_spec; -#if ENABLE_BONDING vector groupspec = Option(params, vector{}, o_group); -#endif vector source_items, target_items; if (!need_help) { // You may still need help. -#if ENABLE_BONDING - if ( !groupspec.empty() ) + if (!groupspec.empty()) { +#if ENABLE_BONDING // Check if you have something before -g and after -g. if (args.empty()) { // Then all items are sources, but the last one is a single target. if (groupspec.size() < 3) { - cerr << "ERROR: Redundancy group: with nothing preceding -g, use -g ... (at least 3 args)\n"; + cerr << "ERROR: Bonding group: with nothing preceding -g, use -g ... (at least 3 args)\n"; need_help = true; } else @@ -464,9 +468,12 @@ int main( int argc, char** argv ) copy(args.begin(), args.end(), back_inserter(source_items)); copy(groupspec.begin(), groupspec.end(), back_inserter(target_items)); } +#else + cerr << "ERROR: Option: -g: Bonding feature not enabled at compile time\n"; + return 1; +#endif } else -#endif { if (args.size() < 2) { @@ -488,7 +495,7 @@ int main( int argc, char** argv ) unique_ptr pout_verb; int verbch = 1; // default cerr - if (verbose_val != "no") + if (!need_help && verbose_val != "no") { Verbose::on = true; if (verbose_val == "") @@ -529,10 +536,9 @@ int main( int argc, char** argv ) } } - if (!need_help) { - // Redundancy is then simply recognized by the fact that there are + // Bonding is then simply recognized by the fact that there are // multiple specified inputs or outputs, for SRT caller only. Check // every URI in advance. if (!CheckMediaSpec("INPUT", source_items, (source_spec))) @@ -581,16 +587,24 @@ int main( int argc, char** argv ) // Unrecognized helpspec is same as no helpspec, that is, general help. cerr << "Usage:\n"; +#if ENABLE_BONDING cerr << " (1) " << argv[0] << " [options] \n"; cerr << " (2) " << argv[0] << " -g [options]\n"; +#else + cerr << " " << argv[0] << " [options] \n"; +#endif cerr << "*** (Position of [options] is unrestricted.)\n"; cerr << "*** ( option parameters can be only terminated by a next option.)\n"; +#if ENABLE_BONDING cerr << "where:\n"; cerr << " (1) Exactly one input and one output URI spec is required,\n"; cerr << " (2) Multiple SRT inputs or output as redundant links are allowed.\n"; cerr << " `URI1 URI2 -g URI3` uses 1, 2 input and 3 output\n"; cerr << " `-g URI1 URI2 URI3` like above\n"; cerr << " `URI1 -g URI2 URI3` uses 1 input and 2, 3 output\n"; +#else + cerr << "*** (Exactly one input and one output should be specified)\n"; +#endif cerr << "SUPPORTED URI SCHEMES:\n"; cerr << " srt: use SRT connection\n"; cerr << " udp: read from bound UDP socket or send to given address as UDP\n"; @@ -647,13 +661,11 @@ int main( int argc, char** argv ) transmit_accept_hook_op = (void*)&g_reject_data; transmit_accept_hook_fn = &SrtRejectByCodeHook; } -#if ENABLE_BONDING else if (hargs[0] == "groupcheck") { transmit_accept_hook_fn = &SrtCheckGroupHook; transmit_accept_hook_op = nullptr; } -#endif } string pfextra; diff --git a/testing/srt-test-mpbond.cpp b/testing/srt-test-mpbond.cpp index 157fa9728..8a5143ddf 100644 --- a/testing/srt-test-mpbond.cpp +++ b/testing/srt-test-mpbond.cpp @@ -238,7 +238,7 @@ int main( int argc, char** argv ) return 2; } - size_t chunk = SRT_LIVE_MAX_PLSIZE; + size_t chunk = SRT_MAX_PLSIZE_AF_INET; // state the bigger size // Now run the loop try diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index 2c36a4d12..7b7a9a9b3 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -45,9 +45,7 @@ using namespace srt; using srt_logging::KmStateStr; using srt_logging::SockStatusStr; -#if ENABLE_BONDING using srt_logging::MemberStatusStr; -#endif srt::sync::atomic transmit_throw_on_interrupt {false}; srt::sync::atomic transmit_int_state {false}; @@ -61,6 +59,50 @@ bool transmit_use_sourcetime = false; int transmit_retry_connect = 0; bool transmit_retry_always = false; +struct CloseReasonMap +{ + map at; + + CloseReasonMap() + { + at[SRT_CLS_UNKNOWN] = "Unset"; + at[SRT_CLS_INTERNAL] = "Closed by internal reasons during connection attempt"; + at[SRT_CLS_PEER] = "Received SHUTDOWN message from the peer"; + at[SRT_CLS_RESOURCE] = "Problem with resource allocation"; + at[SRT_CLS_ROGUE] = "Received wrong data in the packet"; + at[SRT_CLS_OVERFLOW] = "Emergency close due to receiver buffer overflow"; + at[SRT_CLS_IPE] = "Internal program error"; + at[SRT_CLS_API] = "The application called srt_close()"; + at[SRT_CLS_FALLBACK] = "The peer doesn't support close reason feature"; + at[SRT_CLS_LATE] = "Accepted-socket late-rejection or in-handshake rollback"; + at[SRT_CLS_CLEANUP] = "All sockets are being closed due to srt_cleanup() call"; + at[SRT_CLS_DEADLSN] = "This was an accepted socket off a dead listener"; + at[SRT_CLS_PEERIDLE] = "Peer didn't send any packet for a time of SRTO_PEERIDLETIMEO"; + at[SRT_CLS_UNSTABLE] = "Requested to be broken as unstable in Backup group"; + } + + string operator[](SRT_CLOSE_REASON reason) + { + if (int(reason) >= SRT_CLSC_USER) + { + string extra; + if (reason == SRT_CLSC_USER) + extra = " - Application exit due to interrupted transmission"; + + if (reason == SRT_CLSC_USER + 1) + extra = " - Error during configuration, transmission not started"; + + return Sprint("User-defined reason #", reason - SRT_CLSC_USER, extra); + } + + auto p = at.find(reason); + if (p == at.end()) + return "UNDEFINED"; + return p->second; + } + +} g_close_reason; + // Do not unblock. Copy this to an app that uses applog and set appropriate name. //srt_logging::Logger applog(SRT_LOGFA_APP, srt_logger_config, "srt-test"); @@ -98,6 +140,19 @@ string DirectionName(SRT_EPOLL_T direction) return dir_name; } +static string RejectReasonStr(int id) +{ + if (id < SRT_REJC_PREDEFINED) + return srt_rejectreason_str(id); + + if (id < SRT_REJC_USERDEFINED) + return srt_rejectreasonx_str(id); + + ostringstream sout; + sout << "User-defined reason code " << id; + return sout.str(); +} + template inline bytevector FileRead(FileBase& ifile, size_t chunk, const string& filename) { @@ -219,7 +274,6 @@ void SrtCommon::InitParameters(string host, string path, map par) path = path.substr(2); -#if ENABLE_BONDING if (path == "group") { // Group specified, check type. @@ -359,7 +413,6 @@ void SrtCommon::InitParameters(string host, string path, map par) // possible in future. par["mode"] = "caller"; } -#endif } if (par.count("bind")) @@ -391,6 +444,41 @@ void SrtCommon::InitParameters(string host, string path, map par) { m_mode = par.at("mode"); } + + size_t max_payload_size = 0; + + // Try to interpret host and adapter first + sockaddr_any host_sa, adapter_sa; + + if (host != "") + { + host_sa = CreateAddr(host); + if (host_sa.family() == AF_UNSPEC) + Error("Failed to interpret 'host' spec: " + host); + + if (host_sa.family() == AF_INET) + max_payload_size = SRT_MAX_PLSIZE_AF_INET; + } + + if (adapter != "") + { + adapter_sa = CreateAddr(adapter); + + if (adapter_sa.family() == AF_UNSPEC) + Error("Failed to interpret 'adapter' spec: " + adapter); + + if (host_sa.family() != AF_UNSPEC && host_sa.family() != adapter_sa.family()) + { + Error("Both host and adapter specified and they use different IP versions"); + } + + if (max_payload_size == 0 && host_sa.family() == AF_INET) + max_payload_size = SRT_MAX_PLSIZE_AF_INET; + } + + if (!max_payload_size) + max_payload_size = SRT_MAX_PLSIZE_AF_INET6; + SocketOption::Mode mode = SrtInterpretMode(m_mode, host, adapter); if (mode == SocketOption::FAILURE) { @@ -444,16 +532,19 @@ void SrtCommon::InitParameters(string host, string path, map par) // That's kinda clumsy, but it must rely on the defaults. // Default mode is live, so check if the file mode was enforced - if (par.count("transtype") == 0 || par["transtype"] != "file") + if ((par.count("transtype") == 0 || par["transtype"] != "file") + && transmit_chunk_size > SRT_LIVE_DEF_PLSIZE) { - // If the Live chunk size was nondefault, enforce the size. - if (transmit_chunk_size != SRT_LIVE_DEF_PLSIZE) - { - if (transmit_chunk_size > SRT_LIVE_MAX_PLSIZE) - throw std::runtime_error("Chunk size in live mode exceeds 1456 bytes; this is not supported"); + if (transmit_chunk_size > max_payload_size) + throw std::runtime_error(Sprint("Chunk size in live mode exceeds ", max_payload_size, " bytes; this is not supported")); - par["payloadsize"] = Sprint(transmit_chunk_size); - } + par["payloadsize"] = Sprint(transmit_chunk_size); + } + else + { + // set it so without making sure that it was set to "file". + // worst case it will be rejected in settings + m_transtype = SRTT_FILE; } // Assigning group configuration from a special "groupconfig" attribute. @@ -488,16 +579,16 @@ void SrtCommon::InitParameters(string host, string path, map par) void SrtCommon::PrepareListener(string host, int port, int backlog) { m_bindsock = srt_create_socket(); - if (m_bindsock == SRT_ERROR) + if (m_bindsock == SRT_INVALID_SOCK) Error("srt_create_socket"); - int stat = ConfigurePre(m_bindsock); + SRTSTATUS stat = ConfigurePre(m_bindsock); if (stat == SRT_ERROR) Error("ConfigurePre"); if (!m_blocking_mode) { - srt_conn_epoll = AddPoller(m_bindsock, SRT_EPOLL_OUT); + srt_conn_epoll = AddPoller(m_bindsock, SRT_EPOLL_IN); } auto sa = CreateAddr(host, port); @@ -546,7 +637,7 @@ void SrtCommon::AcceptNewClient() int len = 2; SRTSOCKET ready[2]; - while (srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, 1000, 0, 0, 0, 0) == -1) + while (srt_epoll_wait(srt_conn_epoll, ready, &len, 0, 0, 1000, 0, 0, 0, 0) == int(SRT_ERROR)) { if (::transmit_int_state) Error("srt_epoll_wait for srt_accept: interrupt"); @@ -568,8 +659,22 @@ void SrtCommon::AcceptNewClient() Error("srt_accept"); } -#if ENABLE_BONDING - if (m_sock & SRTGROUP_MASK) + int maxsize = srt_getmaxpayloadsize(m_sock); + if (maxsize == int(SRT_ERROR)) + { + srt_close(m_bindsock); + srt_close(m_sock); + Error("srt_getmaxpayloadsize"); + } + + if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) + { + srt_close(m_bindsock); + srt_close(m_sock); + Error(Sprint("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + } + + if (int32_t(m_sock) & SRTGROUP_MASK) { m_listener_group = true; if (m_group_config != "") @@ -580,9 +685,8 @@ void SrtCommon::AcceptNewClient() // There might be added a poller, remove it. // We need it work different way. -#ifndef SRT_OLD_APP_READER - if (srt_epoll != -1) + if (srt_epoll != int(SRT_ERROR)) { Verb() << "(Group: erasing epoll " << srt_epoll << ") " << VerbNoEOL; srt_epoll_release(srt_epoll); @@ -591,7 +695,6 @@ void SrtCommon::AcceptNewClient() // Don't add any sockets, they will have to be added // anew every time again. srt_epoll = srt_epoll_create(); -#endif // Group data must have a size of at least 1 // otherwise the srt_group_data() call will fail @@ -601,29 +704,32 @@ void SrtCommon::AcceptNewClient() Verb() << " connected(group epoll " << srt_epoll <<")."; } else -#endif { sockaddr_any peeraddr(AF_INET6); string peer = ""; - if (-1 != srt_getpeername(m_sock, (peeraddr.get()), (&peeraddr.len))) + if (SRT_ERROR != srt_getpeername(m_sock, (peeraddr.get()), (&peeraddr.len))) { peer = peeraddr.str(); } sockaddr_any agentaddr(AF_INET6); - string agent = ""; - if (-1 != srt_getsockname(m_sock, (agentaddr.get()), (&agentaddr.len))) + string agent = "", dev = ""; + if (SRT_ERROR != srt_getsockname(m_sock, (agentaddr.get()), (&agentaddr.len))) { agent = agentaddr.str(); + char name[256]; + size_t len = 255; + if (srt_getsockdevname(m_sock, name, &len) == SRT_STATUS_OK) + dev.assign(name, len); } - Verb() << " connected [" << agent << "] <-- " << peer; + Verb() << " connected [" << agent << "] <-- " << peer << " [" << dev << "]"; } ::transmit_throw_on_interrupt = false; // ConfigurePre is done on bindsock, so any possible Pre flags // are DERIVED by sock. ConfigurePost is done exclusively on sock. - int stat = ConfigurePost(m_sock); + SRTSTATUS stat = ConfigurePost(m_sock); if (stat == SRT_ERROR) Error("ConfigurePost"); } @@ -679,12 +785,10 @@ void SrtCommon::Init(string host, int port, string path, map par, { OpenClient(host, port); } -#if ENABLE_BONDING else { OpenGroupClient(); // Source data are in the fields already. } -#endif } else if (m_mode == "listener") OpenServer(m_adapter, port, backlog); @@ -705,7 +809,7 @@ void SrtCommon::Init(string host, int port, string path, map par, if (m_bindsock != SRT_INVALID_SOCK) srt_close(m_bindsock); if (m_sock != SRT_INVALID_SOCK) - srt_close(m_sock); + srt_close_withreason(m_sock, SRT_CLSC_USER+1); m_sock = m_bindsock = SRT_INVALID_SOCK; throw; } @@ -748,7 +852,7 @@ void SrtCommon::Init(string host, int port, string path, map par, if (!m_blocking_mode) { // Don't add new epoll if already created as a part - // of group management: if (srt_epoll == -1)... + // of group management: if (srt_epoll == SRT_ERROR)... if (m_mode == "caller") dir = (dir | SRT_EPOLL_UPDATE); @@ -761,7 +865,7 @@ void SrtCommon::Init(string host, int port, string path, map par, int SrtCommon::AddPoller(SRTSOCKET socket, int modes) { int pollid = srt_epoll_create(); - if (pollid == -1) + if (pollid == int(SRT_ERROR)) throw std::runtime_error("Can't create epoll in nonblocking mode"); Verb() << "EPOLL: creating eid=" << pollid << " and adding @" << socket << " in " << DirectionName(SRT_EPOLL_OPT(modes)) << " mode"; @@ -769,15 +873,15 @@ int SrtCommon::AddPoller(SRTSOCKET socket, int modes) return pollid; } -int SrtCommon::ConfigurePost(SRTSOCKET sock) +SRTSTATUS SrtCommon::ConfigurePost(SRTSOCKET sock) { bool yes = m_blocking_mode; - int result = 0; + SRTSTATUS result = SRT_STATUS_OK; if (m_direction & SRT_EPOLL_OUT) { Verb() << "Setting SND blocking mode: " << boolalpha << yes << " timeout=" << m_timeout; result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &yes, sizeof yes); - if (result == -1) + if (result == SRT_ERROR) { #ifdef PLEASE_LOG extern srt_logging::Logger applog; @@ -788,7 +892,7 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) if (m_timeout) result = srt_setsockopt(sock, 0, SRTO_SNDTIMEO, &m_timeout, sizeof m_timeout); - if (result == -1) + if (result == SRT_ERROR) { #ifdef PLEASE_LOG extern srt_logging::Logger applog; @@ -802,7 +906,7 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) { Verb() << "Setting RCV blocking mode: " << boolalpha << yes << " timeout=" << m_timeout; result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &yes, sizeof yes); - if (result == -1) + if (result == SRT_ERROR) return result; if (m_timeout) @@ -812,7 +916,7 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) int timeout = 1000; result = srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &timeout, sizeof timeout); } - if (result == -1) + if (result == SRT_ERROR) return result; } @@ -833,18 +937,18 @@ int SrtCommon::ConfigurePost(SRTSOCKET sock) } } - return 0; + return SRT_STATUS_OK; } -int SrtCommon::ConfigurePre(SRTSOCKET sock) +SRTSTATUS SrtCommon::ConfigurePre(SRTSOCKET sock) { - int result = 0; + SRTSTATUS result = SRT_STATUS_OK; int no = 0; if (!m_tsbpdmode) { result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); - if (result == -1) + if (result == SRT_ERROR) return result; } @@ -852,7 +956,7 @@ int SrtCommon::ConfigurePre(SRTSOCKET sock) // This is for asynchronous connect. int maybe = m_blocking_mode; result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &maybe, sizeof maybe); - if (result == -1) + if (result == SRT_ERROR) return result; // host is only checked for emptiness and depending on that the connection mode is selected. @@ -876,14 +980,14 @@ int SrtCommon::ConfigurePre(SRTSOCKET sock) return SRT_ERROR; } - return 0; + return SRT_STATUS_OK; } void SrtCommon::SetupAdapter(const string& host, int port) { Verb() << "Binding the caller socket to " << host << ":" << port << " ..."; auto lsa = CreateAddr(host, port); - int stat = srt_bind(m_sock, lsa.get(), sizeof lsa); + SRTSTATUS stat = srt_bind(m_sock, lsa.get(), sizeof lsa); if (stat == SRT_ERROR) Error("srt_bind"); } @@ -903,10 +1007,10 @@ void SrtCommon::OpenClient(string host, int port) void SrtCommon::PrepareClient() { m_sock = srt_create_socket(); - if (m_sock == SRT_ERROR) + if (m_sock == SRT_INVALID_SOCK) Error("srt_create_socket"); - int stat = ConfigurePre(m_sock); + SRTSTATUS stat = ConfigurePre(m_sock); if (stat == SRT_ERROR) Error("ConfigurePre"); @@ -917,7 +1021,6 @@ void SrtCommon::PrepareClient() } -#if ENABLE_BONDING void TransmitGroupSocketConnect(void* srtcommon, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) { SrtCommon* that = (SrtCommon*)srtcommon; @@ -997,12 +1100,12 @@ void SrtCommon::OpenGroupClient() } m_sock = srt_create_group(type); - if (m_sock == -1) + if (m_sock == SRT_INVALID_SOCK) Error("srt_create_group"); srt_connect_callback(m_sock, &TransmitGroupSocketConnect, this); - int stat = -1; + SRTSTATUS stat = SRT_ERROR; if (m_group_config != "") { Verb() << "Ignoring setting group config: '" << m_group_config; @@ -1010,7 +1113,7 @@ void SrtCommon::OpenGroupClient() stat = ConfigurePre(m_sock); - if ( stat == SRT_ERROR ) + if (stat == SRT_ERROR) Error("ConfigurePre"); if (!m_blocking_mode) @@ -1078,9 +1181,9 @@ void SrtCommon::OpenGroupClient() Connect_Again: Verb() << "Waiting for group connection... " << VerbNoEOL; - int fisock = srt_connect_group(m_sock, targets.data(), int(targets.size())); + SRTSOCKET fisock = srt_connect_group(m_sock, targets.data(), int(targets.size())); - if (fisock == SRT_ERROR) + if (fisock == SRT_INVALID_SOCK) { // Complete the error information for every member ostringstream out; @@ -1092,7 +1195,7 @@ void SrtCommon::OpenGroupClient() out << "[" << c.token << "] " << c.host << ":" << c.port; if (!c.source.empty()) out << "[[" << c.source.str() << "]]"; - out << ": " << srt_strerror(c.error, 0) << ": " << srt_rejectreason_str(c.reason) << endl; + out << ": " << srt_strerror(c.error, 0) << ": " << RejectReasonStr(c.reason) << endl; } reasons.insert(c.reason); } @@ -1135,7 +1238,7 @@ void SrtCommon::OpenGroupClient() // 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[j].id != -1 && targets[j].errorcode == SRT_SUCCESS) + if (targets[j].id != SRT_INVALID_SOCK && targets[j].errorcode == SRT_SUCCESS) { m_group_nodes[j].socket = targets[j].id; } @@ -1145,14 +1248,14 @@ void SrtCommon::OpenGroupClient() // should be added to epoll. size_t size = m_group_data.size(); stat = srt_group_data(m_sock, m_group_data.data(), &size); - if (stat == -1 && size > m_group_data.size()) + if (stat == SRT_ERROR && size > m_group_data.size()) { // Just too small buffer. Resize and continue. m_group_data.resize(size); stat = srt_group_data(m_sock, m_group_data.data(), &size); } - if (stat == -1) + if (stat == SRT_ERROR) { Error("srt_group_data"); } @@ -1161,7 +1264,7 @@ void SrtCommon::OpenGroupClient() for (size_t j = 0; j < m_group_nodes.size(); ++j) { SRTSOCKET insock = m_group_nodes[j].socket; - if (insock == -1) + if (insock == SRT_INVALID_SOCK) { Verb() << "TARGET '" << sockaddr_any(targets[i].peeraddr).str() << "' connection failed."; continue; @@ -1190,7 +1293,7 @@ void SrtCommon::OpenGroupClient() ready_conn, &len1, -1, // Wait infinitely NULL, NULL, - NULL, NULL) != -1) + NULL, NULL) != int(SRT_ERROR)) { Verb() << "[C]" << VerbNoEOL; for (int ii = 0; ii < len1; ++ii) @@ -1216,7 +1319,7 @@ void SrtCommon::OpenGroupClient() out << "[" << c.token << "] " << c.host << ":" << c.port; if (!c.source.empty()) out << "[[" << c.source.str() << "]]"; - out << ": " << srt_strerror(c.error, 0) << ": " << srt_rejectreason_str(c.reason) << endl; + out << ": " << srt_strerror(c.error, 0) << ": " << RejectReasonStr(c.reason) << endl; } reasons.insert(c.reason); } @@ -1249,7 +1352,7 @@ void SrtCommon::OpenGroupClient() } stat = ConfigurePost(m_sock); - if (stat == -1) + if (stat == SRT_ERROR) { // This kind of error must reject the whole operation. // Usually you'll get this error on the first socket, @@ -1270,35 +1373,6 @@ void SrtCommon::OpenGroupClient() // Prepare group data for monitoring the group status. m_group_data.resize(m_group_nodes.size()); } -#endif - -/* - This may be used sometimes for testing, but it's nonportable. - void SrtCommon::SpinWaitAsync() - { - static string udt_status_names [] = { - "INIT" , "OPENED", "LISTENING", "CONNECTING", "CONNECTED", "BROKEN", "CLOSING", "CLOSED", "NONEXIST" - }; - - for (;;) - { - SRT_SOCKSTATUS state = srt_getsockstate(m_sock); - if (int(state) < SRTS_CONNECTED) - { - if (Verbose::on) - Verb() << state; - usleep(250000); - continue; - } - else if (int(state) > SRTS_CONNECTED) - { - Error("UDT::connect status=" + udt_status_names[state]); - } - - return; - } - } - */ struct TransmitErrorReason { @@ -1318,6 +1392,18 @@ static void TransmitConnectCallback(void*, SRTSOCKET socket, int errorcode, cons void SrtCommon::ConnectClient(string host, int port) { auto sa = CreateAddr(host, port); + { + // Check if trying to connect to self. + sockaddr_any lsa; + srt_getsockname(m_sock, lsa.get(), &lsa.len); + + if (lsa.hport() == port && IsTargetAddrSelf(lsa.get(), sa.get())) + { + Verb() << "ERROR: Trying to connect to SELF address " << sa.str() + << " with socket bound to " << lsa.str(); + Error("srt_connect", 0, SRT_EINVPARAM); + } + } Verb() << "Connecting to " << host << ":" << port << " ... " << VerbNoEOL; if (!m_blocking_mode) @@ -1325,13 +1411,13 @@ void SrtCommon::ConnectClient(string host, int port) srt_connect_callback(m_sock, &TransmitConnectCallback, 0); } - int stat = -1; + SRTSTATUS stat = SRT_ERROR; for (;;) { ::transmit_throw_on_interrupt = true; - stat = srt_connect(m_sock, sa.get(), sizeof sa); + SRTSOCKET stats = srt_connect(m_sock, sa.get(), sizeof sa); ::transmit_throw_on_interrupt = false; - if (stat == SRT_ERROR) + if (stats == SRT_INVALID_SOCK) { int reason = srt_getrejectreason(m_sock); #if PLEASE_LOG @@ -1347,7 +1433,7 @@ void SrtCommon::ConnectClient(string host, int port) continue; } - srt_close(m_sock); + srt_close_withreason(m_sock, SRT_CLSC_USER+1); Error("srt_connect", reason); } break; @@ -1364,7 +1450,7 @@ void SrtCommon::ConnectClient(string host, int port) // Socket readiness for connection is checked by polling on WRITE allowed sockets. int lenc = 2, lene = 2; SRTSOCKET ready_connect[2], ready_error[2]; - if (srt_epoll_wait(srt_conn_epoll, ready_error, &lene, ready_connect, &lenc, -1, 0, 0, 0, 0) != -1) + if (srt_epoll_wait(srt_conn_epoll, ready_error, &lene, ready_connect, &lenc, -1, 0, 0, 0, 0) != (int)SRT_ERROR) { // We should have just one socket, so check whatever socket // is in the transmit_error_storage. @@ -1405,7 +1491,32 @@ void SrtCommon::ConnectClient(string host, int port) transmit_error_storage.clear(); } + int maxsize = srt_getmaxpayloadsize(m_sock); + if (maxsize == int(SRT_ERROR)) + { + srt_close(m_sock); + Error("srt_getmaxpayloadsize"); + } + + if (m_transtype == SRTT_LIVE && transmit_chunk_size > size_t(maxsize)) + { + srt_close(m_sock); + Error(Sprint("accepted connection's payload size ", maxsize, " is too small for required ", transmit_chunk_size, " chunk size")); + } + Verb() << " connected."; + + sockaddr_any agent; + string dev; + if (Verbose::on) + { + srt_getsockname(m_sock, agent.get(), &agent.len); + char name[256]; + size_t len = 255; + if (srt_getsockdevname(m_sock, name, &len) == SRT_STATUS_OK) + dev.assign(name, len); + } + Verb("Connected AGENT:", agent.str(), "[", dev, "] PEER:", sa.str()); stat = ConfigurePost(m_sock); if (stat == SRT_ERROR) Error("ConfigurePost"); @@ -1413,6 +1524,9 @@ void SrtCommon::ConnectClient(string host, int port) void SrtCommon::Error(string src, int reason, int force_result) { + SRT_CLOSE_INFO cli; + SRTSTATUS cls = srt_close_getreason(m_sock, &cli); + int errnov = 0; const int result = force_result == 0 ? srt_getlasterror(&errnov) : force_result; if (result == SRT_SUCCESS) @@ -1426,18 +1540,32 @@ void SrtCommon::Error(string src, int reason, int force_result) if ( Verbose::on ) Verb() << "FAILURE\n" << src << ": [" << result << "] " << "Connection rejected: [" << int(reason) << "]: " - << srt_rejectreason_str(reason); + << RejectReasonStr(reason); else cerr << "\nERROR #" << result << ": Connection rejected: [" << int(reason) << "]: " - << srt_rejectreason_str(reason); + << RejectReasonStr(reason); } else { if ( Verbose::on ) - Verb() << "FAILURE\n" << src << ": [" << result << "." << errnov << "] " << message; + Verb() << "FAILURE\n" << src << ": [" << result << "." << errnov << "] " << message; else - cerr << "\nERROR #" << result << "." << errnov << ": " << message << endl; + cerr << "\nERROR #" << result << "." << errnov << ": " << message << endl; + } + + if (cls == SRT_STATUS_OK) + { + int64_t srt_now = srt_time_now(); + int64_t ago = srt_now - cli.time; + cerr << "CLOSE REASON: ->\n\tagent=" << cli.agent << " [" << g_close_reason[cli.agent] + << "]\n\tpeer=" << cli.peer << " [" << g_close_reason[cli.peer] + << "]\n\ttime=" << cli.time << " (" << fixed << (ago/1000000.0) << "s ago)" + << endl; + } + else + { + cerr << "(CLOSE REASON not found)\n"; } throw TransmissionError("error: " + src + ": " + message); @@ -1464,10 +1592,10 @@ void SrtCommon::SetupRendezvous(string adapter, string host, int port) if (target.family() == AF_INET6) showhost = "[" + showhost + "]"; Verb() << "Binding rendezvous: " << showhost << ":" << outport << " ..."; - int stat = srt_bind(m_sock, localsa.get(), localsa.size()); + SRTSTATUS stat = srt_bind(m_sock, localsa.get(), localsa.size()); if (stat == SRT_ERROR) { - srt_close(m_sock); + srt_close_withreason(m_sock, SRT_CLSC_USER+1); Error("srt_bind"); } } @@ -1484,8 +1612,21 @@ void SrtCommon::Close() { Verb() << "SrtCommon: DESTROYING CONNECTION, closing socket (rt%" << m_sock << ")..."; srt_setsockflag(m_sock, SRTO_SNDSYN, &yes, sizeof yes); - srt_close(m_sock); + srt_close_withreason(m_sock, SRT_CLSC_USER); any = true; + + SRT_CLOSE_INFO cli; + SRTSTATUS cls = srt_close_getreason(m_sock, &cli); + + if (cls == SRT_STATUS_OK) + { + int64_t srt_now = srt_time_now(); + int64_t ago = srt_now - cli.time; + cerr << "POST-FACTUM CLOSE REASON: ->\n\tagent=" << cli.agent << " [" << g_close_reason[cli.agent] + << "]\n\tpeer=" << cli.peer << " [" << g_close_reason[cli.peer] + << "]\n\ttime=" << cli.time << " (" << fixed << (ago/1000000.0) << "s ago)" + << endl; + } } if (m_bindsock != SRT_INVALID_SOCK) @@ -1493,7 +1634,7 @@ void SrtCommon::Close() Verb() << "SrtCommon: DESTROYING SERVER, closing socket (ls%" << m_bindsock << ")..."; // Set sndsynchro to the socket to synch-close it. srt_setsockflag(m_bindsock, SRTO_SNDSYN, &yes, sizeof yes); - srt_close(m_bindsock); + srt_close_withreason(m_bindsock, SRT_CLSC_USER); any = true; } @@ -1506,7 +1647,6 @@ SrtCommon::~SrtCommon() Close(); } -#if ENABLE_BONDING void SrtCommon::UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpdata_size) { if (!grpdata) @@ -1538,10 +1678,10 @@ void SrtCommon::UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpda SRTSOCKET id = d.id; SRT_SOCKSTATUS status = d.sockstate; - int result = d.result; + SRTSTATUS result = d.result; SRT_MEMBERSTATUS mstatus = d.memberstate; - if (result != -1 && status == SRTS_CONNECTED) + if (result != SRT_ERROR && status == SRTS_CONNECTED) { // Short report with the state. Verb() << "G@" << id << "<" << MemberStatusStr(mstatus) << "> " << VerbNoEOL; @@ -1586,8 +1726,8 @@ void SrtCommon::UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpda gd.config = n.options; gd.token = n.token; - int fisock = srt_connect_group(m_sock, &gd, 1); - if (fisock == SRT_ERROR) + SRTSOCKET fisock = srt_connect_group(m_sock, &gd, 1); + if (fisock == SRT_INVALID_SOCK) { // Whatever. Skip the node. Verb() << "FAILED: "; @@ -1599,7 +1739,6 @@ void SrtCommon::UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpda } } } -#endif SrtSource::SrtSource(string host, int port, std::string path, const map& par) { @@ -1621,669 +1760,6 @@ static void PrintSrtStats(SRTSOCKET sock, bool clr, bool bw, bool stats) cout << transmit_stats_writer->WriteStats(sock, perf); } - -#ifdef SRT_OLD_APP_READER - -// NOTE: 'output' is expected to be EMPTY here. -bool SrtSource::GroupCheckPacketAhead(bytevector& output) -{ - bool status = false; - vector past_ahead; - - // This map no longer maps only ahead links. - // Here are all links, and whether ahead, it's defined by the sequence. - for (auto i = m_group_positions.begin(); i != m_group_positions.end(); ++i) - { - // i->first: socket ID - // i->second: ReadPos { sequence, packet } - // We are not interested with the socket ID because we - // aren't going to read from it - we have the packet already. - ReadPos& a = i->second; - - int seqdiff = CSeqNo::seqcmp(a.sequence, m_group_seqno); - if ( seqdiff == 1) - { - // The very next packet. Return it. - m_group_seqno = a.sequence; - Verb() << " (SRT group: ahead delivery %" << a.sequence << " from @" << i->first << ")"; - swap(output, a.packet); - status = true; - } - else if (seqdiff < 1 && !a.packet.empty()) - { - Verb() << " (@" << i->first << " dropping collected ahead %" << a.sequence << ")"; - a.packet.clear(); - } - // In case when it's >1, keep it in ahead - } - - return status; -} - -static string DisplayEpollResults(const std::set& sockset, std::string prefix) -{ - typedef set fset_t; - ostringstream os; - os << prefix << " "; - for (fset_t::const_iterator i = sockset.begin(); i != sockset.end(); ++i) - { - os << "@" << *i << " "; - } - - return os.str(); -} - -bytevector SrtSource::GroupRead(size_t chunk) -{ - // Read the current group status. m_sock is here the group id. - bytevector output; - - // Later iteration over it might be less efficient than - // by vector, but we'll also often try to check a single id - // if it was ever seen broken, so that it's skipped. - set broken; - -RETRY_READING: - - size_t size = m_group_data.size(); - int stat = srt_group_data(m_sock, m_group_data.data(), &size); - if (stat == -1 && size > m_group_data.size()) - { - // Just too small buffer. Resize and continue. - m_group_data.resize(size); - stat = srt_group_data(m_sock, m_group_data.data(), &size); - } - else - { - // Downsize if needed. - m_group_data.resize(size); - } - - if (stat == -1) // Also after the above fix - { - Error(UDT::getlasterror(), "FAILURE when reading group data"); - } - - if (size == 0) - { - Error("No sockets in the group - disconnected"); - } - - bool connected = false; - for (auto& d: m_group_data) - { - if (d.status == SRTS_CONNECTED) - { - connected = true; - break; - } - } - if (!connected) - { - Error("All sockets in the group disconnected"); - } - - if (Verbose::on) - { - for (auto& d: m_group_data) - { - if (d.status != SRTS_CONNECTED) - // id, status, result, peeraddr - Verb() << "@" << d.id << " <" << SockStatusStr(d.status) << "> (=" << d.result << ") PEER:" - << sockaddr_any((sockaddr*)&d.peeraddr, sizeof d.peeraddr).str(); - } - } - - // Check first the ahead packets if you have any to deliver. - if (m_group_seqno != -1 && !m_group_positions.empty()) - { - bytevector ahead_packet; - - // This function also updates the group sequence pointer. - if (GroupCheckPacketAhead(ahead_packet)) - return move(ahead_packet); - } - - // LINK QUALIFICATION NAMES: - // - // HORSE: Correct link, which delivers the very next sequence. - // Not necessarily this link is currently active. - // - // KANGAROO: Got some packets dropped and the sequence number - // of the packet jumps over the very next sequence and delivers - // an ahead packet. - // - // ELEPHANT: Is not ready to read, while others are, or reading - // up to the current latest delivery sequence number does not - // reach this sequence and the link becomes non-readable earlier. - - // The above condition has ruled out one kangaroo and turned it - // into a horse. - - // Below there's a loop that will try to extract packets. Kangaroos - // will be among the polled ones because skipping them risks that - // the elephants will take over the reading. Links already known as - // elephants will be also polled in an attempt to revitalize the - // connection that experienced just a short living choking. - // - // After polling we attempt to read from every link that reported - // read-readiness and read at most up to the sequence equal to the - // current delivery sequence. - - // Links that deliver a packet below that sequence will be retried - // until they deliver no more packets or deliver the packet of - // expected sequence. Links that don't have a record in m_group_positions - // and report readiness will be always read, at least to know what - // sequence they currently stand on. - // - // Links that are already known as kangaroos will be polled, but - // no reading attempt will be done. If after the reading series - // it will turn out that we have no more horses, the slowest kangaroo - // will be "advanced to a horse" (the ahead link with a sequence - // closest to the current delivery sequence will get its sequence - // set as current delivered and its recorded ahead packet returned - // as the read packet). - - // If we find at least one horse, the packet read from that link - // will be delivered. All other link will be just ensured update - // up to this sequence number, or at worst all available packets - // will be read. In this case all kangaroos remain kangaroos, - // until the current delivery sequence m_group_seqno will be lifted - // to the sequence recorded for these links in m_group_positions, - // during the next time ahead check, after which they will become - // horses. - - Verb() << "E(" << srt_epoll << ") " << VerbNoEOL; - - for (size_t i = 0; i < size; ++i) - { - SRT_SOCKGROUPDATA& d = m_group_data[i]; - if (d.status == SRTS_CONNECTING) - { - Verb() << "@" << d.id << " " << VerbNoEOL; - int modes = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - srt_epoll_add_usock(srt_epoll, d.id, &modes); - continue; // don't read over a failed or pending socket - } - - if (d.status >= SRTS_BROKEN) - { - broken.insert(d.id); - } - - if (broken.count(d.id)) - { - Verb() << "@" << d.id << " " << VerbNoEOL; - continue; - } - - if (d.status != SRTS_CONNECTED) - { - Verb() << "@" << d.id << " " << VerbNoEOL; - // Sockets in this state are ignored. We are waiting until it - // achieves CONNECTING state, then it's added to write. - continue; - } - - // Don't skip packets that are ahead because if we have a situation - // that all links are either "elephants" (do not report read readiness) - // and "kangaroos" (have already delivered an ahead packet) then - // omitting kangaroos will result in only elephants to be polled for - // reading. Elephants, due to the strict timing requirements and - // ensurance that TSBPD on every link will result in exactly the same - // delivery time for a packet of given sequence, having an elephant - // and kangaroo in one cage means that the elephant is simply a broken - // or half-broken link (the data are not delivered, but it will get - // repaired soon, enough for SRT to maintain the connection, but it - // will still drop packets that didn't arrive in time), in both cases - // it may potentially block the reading for an indefinite time, while - // simultaneously a kangaroo might be a link that got some packets - // dropped, but then it's still capable to deliver packets on time. - - // Note also that about the fact that some links turn out to be - // elephants we'll learn only after we try to poll and read them. - - // Note that d.id might be a socket that was previously being polled - // on write, when it's attempting to connect, but now it's connected. - // This will update the socket with the new event set. - - int modes = SRT_EPOLL_IN | SRT_EPOLL_ERR; - srt_epoll_add_usock(srt_epoll, d.id, &modes); - Verb() << "@" << d.id << "[READ] " << VerbNoEOL; - } - - Verb() << ""; - - // Here we need to make an additional check. - // There might be a possibility that all sockets that - // were added to the reader group, are ahead. At least - // surely we don't have a situation that any link contains - // an ahead-read subsequent packet, because GroupCheckPacketAhead - // already handled that case. - // - // What we can have is that every link has: - // - no known seq position yet (is not registered in the position map yet) - // - the position equal to the latest delivered sequence - // - the ahead position - - // Now the situation is that we don't have any packets - // waiting for delivery so we need to wait for any to report one. - // XXX We support blocking mode only at the moment. - // The non-blocking mode would need to simply check the readiness - // with only immediate report, and read-readiness would have to - // be done in background. - - SrtPollState sready; - - // Poll on this descriptor until reading is available, indefinitely. - if (UDT::epoll_swait(srt_epoll, sready, -1) == SRT_ERROR) - { - Error(UDT::getlasterror(), "UDT::epoll_swait(srt_epoll, group)"); - } - if (Verbose::on) - { - Verb() << "RDY: {" - << DisplayEpollResults(sready.rd(), "[R]") - << DisplayEpollResults(sready.wr(), "[W]") - << DisplayEpollResults(sready.ex(), "[E]") - << "} " << VerbNoEOL; - - } - - LOGC(applog.Debug, log << "epoll_swait: " - << DisplayEpollResults(sready.rd(), "[R]") - << DisplayEpollResults(sready.wr(), "[W]") - << DisplayEpollResults(sready.ex(), "[E]")); - - typedef set fset_t; - - // Handle sockets of pending connection and with errors. - broken = sready.ex(); - - // We don't do anything about sockets that have been configured to - // poll on writing (that is, pending for connection). What we need - // is that the epoll_swait call exit on that fact. Probably if this - // was the only socket reported, no broken and no read-ready, this - // will later check on output if still empty, if so, repeat the whole - // function. This write-ready socket will be there already in the - // connected state and will be added to read-polling. - - // Ok, now we need to have some extra qualifications: - // 1. If a socket has no registry yet, we read anyway, just - // to notify the current position. We read ONLY ONE PACKET this time, - // we'll worry later about adjusting it to the current group sequence - // position. - // 2. If a socket is already position ahead, DO NOT read from it, even - // if it is ready. - - // The state of things whether we were able to extract the very next - // sequence will be simply defined by the fact that `output` is nonempty. - - int32_t next_seq = m_group_seqno; - - // If this set is empty, it won't roll even once, therefore output - // will be surely empty. This will be checked then same way as when - // reading from every socket resulted in error. - for (fset_t::const_iterator i = sready.rd().begin(); i != sready.rd().end(); ++i) - { - // Check if this socket is in aheads - // If so, don't read from it, wait until the ahead is flushed. - - SRTSOCKET id = *i; - ReadPos* p = nullptr; - auto pe = m_group_positions.find(id); - if (pe != m_group_positions.end()) - { - p = &pe->second; - // Possible results of comparison: - // x < 0: the sequence is in the past, the socket should be adjusted FIRST - // x = 0: the socket should be ready to get the exactly next packet - // x = 1: the case is already handled by GroupCheckPacketAhead. - // x > 1: AHEAD. DO NOT READ. - int seqdiff = CSeqNo::seqcmp(p->sequence, m_group_seqno); - if (seqdiff > 1) - { - Verb() << "EPOLL: @" << id << " %" << p->sequence << " AHEAD, not reading."; - continue; - } - } - - - // Read from this socket stubbornly, until: - // - reading is no longer possible (AGAIN) - // - the sequence difference is >= 1 - - int fi = 1; // marker for Verb to display flushing - for (;;) - { - bytevector data(chunk); - SRT_MSGCTRL mctrl = srt_msgctrl_default; - stat = srt_recvmsg2(id, data.data(), chunk, &mctrl); - if (stat == SRT_ERROR) - { - if (fi == 0) - { - if (Verbose::on) - { - if (p) - { - int32_t pktseq = p->sequence; - int seqdiff = CSeqNo::seqcmp(p->sequence, m_group_seqno); - Verb() << ". %" << pktseq << " " << seqdiff << ")"; - } - else - { - Verb() << ".)"; - } - } - fi = 1; - } - int err = srt_getlasterror(0); - if (err == SRT_EASYNCRCV) - { - // Do not treat this as spurious, just stop reading. - break; - } - Verb() << "Error @" << id << ": " << srt_getlasterror_str(); - broken.insert(id); - break; - } - - // NOTE: checks against m_group_seqno and decisions based on it - // must NOT be done if m_group_seqno is -1, which means that we - // are about to deliver the very first packet and we take its - // sequence number as a good deal. - - // The order must be: - // - check discrepancy - // - record the sequence - // - check ordering. - // The second one must be done always, but failed discrepancy - // check should exclude the socket from any further checks. - // That's why the common check for m_group_seqno != -1 can't - // embrace everything below. - - // We need to first qualify the sequence, just for a case - if (m_group_seqno != -1 && abs(m_group_seqno - mctrl.pktseq) > CSeqNo::m_iSeqNoTH) - { - // This error should be returned if the link turns out - // to be the only one, or set to the group data. - // err = SRT_ESECFAIL; - if (fi == 0) - { - Verb() << ".)"; - fi = 1; - } - Verb() << "Error @" << id << ": SEQUENCE DISCREPANCY: base=%" << m_group_seqno << " vs pkt=%" << mctrl.pktseq << ", setting ESECFAIL"; - broken.insert(id); - break; - } - - // Rewrite it to the state for a case when next reading - // would not succeed. Do not insert the buffer here because - // this is only required when the sequence is ahead; for that - // it will be fixed later. - if (!p) - { - p = &(m_group_positions[id] = ReadPos { mctrl.pktseq, {} }); - } - else - { - p->sequence = mctrl.pktseq; - } - - if (m_group_seqno != -1) - { - // Now we can safely check it. - int seqdiff = CSeqNo::seqcmp(mctrl.pktseq, m_group_seqno); - - if (seqdiff <= 0) - { - if (fi == 1) - { - Verb() << "(@" << id << " FLUSH:" << VerbNoEOL; - fi = 0; - } - - Verb() << "." << VerbNoEOL; - - // The sequence is recorded, the packet has to be discarded. - // That's all. - continue; - } - - // Finish flush reporting if fallen into here - if (fi == 0) - { - Verb() << ". %" << mctrl.pktseq << " " << (-seqdiff) << ")"; - fi = 1; - } - - // Now we have only two possibilities: - // seqdiff == 1: The very next sequence, we want to read and return the packet. - // seqdiff > 1: The packet is ahead - record the ahead packet, but continue with the others. - - if (seqdiff > 1) - { - Verb() << "@" << id << " %" << mctrl.pktseq << " AHEAD"; - p->packet = move(data); - break; // Don't read from that socket anymore. - } - } - - // We have seqdiff = 1, or we simply have the very first packet - // which's sequence is taken as a good deal. Update the sequence - // and record output. - - if (!output.empty()) - { - Verb() << "@" << id << " %" << mctrl.pktseq << " REDUNDANT"; - break; - } - - - Verb() << "@" << id << " %" << mctrl.pktseq << " DELIVERING"; - output = move(data); - - // Record, but do not update yet, until all sockets are handled. - next_seq = mctrl.pktseq; - break; - } - } - - // ready_len is only the length of currently reported - // ready sockets, NOT NECESSARILY containing all sockets from the group. - if (broken.size() == size) - { - // All broken - Error("All sockets broken"); - } - - if (Verbose::on && !broken.empty()) - { - Verb() << "BROKEN: " << Printable(broken) << " - removing"; - } - - // Now remove all broken sockets from aheads, if any. - // Even if they have already delivered a packet. - for (SRTSOCKET d: broken) - { - m_group_positions.erase(d); - srt_close(d); - } - - // May be required to be re-read. - broken.clear(); - - if (!output.empty()) - { - // We have extracted something, meaning that we have the sequence shift. - // Update it now and don't do anything else with the sockets. - - // Sanity check - if (next_seq == -1) - { - Error("IPE: next_seq not set after output extracted!"); - } - m_group_seqno = next_seq; - return output; - } - - // Check if we have any sockets left :D - - // Here we surely don't have any more HORSES, - // only ELEPHANTS and KANGAROOS. Qualify them and - // attempt to at least take advantage of KANGAROOS. - - // In this position all links are either: - // - updated to the current position - // - updated to the newest possible position available - // - not yet ready for extraction (not present in the group) - - // If we haven't extracted the very next sequence position, - // it means that we might only have the ahead packets read, - // that is, the next sequence has been dropped by all links. - - if (!m_group_positions.empty()) - { - // This might notify both lingering links, which didn't - // deliver the required sequence yet, and links that have - // the sequence ahead. Review them, and if you find at - // least one packet behind, just wait for it to be ready. - // Use again the waiting function because we don't want - // the general waiting procedure to skip others. - set elephants; - - // const because it's `typename decltype(m_group_positions)::value_type` - pair* slowest_kangaroo = nullptr; - - for (auto& sock_rp: m_group_positions) - { - // NOTE that m_group_seqno in this place wasn't updated - // because we haven't successfully extracted anything. - int seqdiff = CSeqNo::seqcmp(sock_rp.second.sequence, m_group_seqno); - if (seqdiff < 0) - { - elephants.insert(sock_rp.first); - } - // If seqdiff == 0, we have a socket ON TRACK. - else if (seqdiff > 0) - { - if (!slowest_kangaroo) - { - slowest_kangaroo = &sock_rp; - } - else - { - // Update to find the slowest kangaroo. - int seqdiff = CSeqNo::seqcmp(slowest_kangaroo->second.sequence, sock_rp.second.sequence); - if (seqdiff > 0) - { - slowest_kangaroo = &sock_rp; - } - } - } - } - - // Note that if no "slowest_kangaroo" was found, it means - // that we don't have kangaroos. - if (slowest_kangaroo) - { - // We have a slowest kangaroo. Elephants must be ignored. - // Best case, they will get revived, worst case they will be - // soon broken. - // - // As we already have the packet delivered by the slowest - // kangaroo, we can simply return it. - - m_group_seqno = slowest_kangaroo->second.sequence; - Verb() << "@" << slowest_kangaroo->first << " %" << m_group_seqno << " KANGAROO->HORSE"; - swap(output, slowest_kangaroo->second.packet); - return output; - } - - // Here ALL LINKS ARE ELEPHANTS, stating that we still have any. - if (Verbose::on) - { - if (!elephants.empty()) - { - // If we don't have kangaroos, then simply reattempt to - // poll all elephants again anyway (at worst they are all - // broken and we'll learn about it soon). - Verb() << "ALL LINKS ELEPHANTS. Re-polling."; - } - else - { - Verb() << "ONLY BROKEN WERE REPORTED. Re-polling."; - } - } - goto RETRY_READING; - } - - // We have checked so far only links that were ready to poll. - // Links that are not ready should be re-checked. - // Links that were not ready at the entrance should be checked - // separately, and probably here is the best moment to do it. - // After we make sure that at least one link is ready, we can - // reattempt to read a packet from it. - - // Ok, so first collect all sockets that are in - // connecting state, make a poll for connection. - srt_epoll_clear_usocks(srt_epoll); - bool have_connectors = false, have_ready = false; - for (auto& d: m_group_data) - { - if (d.status < SRTS_CONNECTED) - { - // Not sure anymore if IN or OUT signals the connect-readiness, - // but no matter. The signal will be cleared once it is used, - // while it will be always on when there's anything ready to read. - int modes = SRT_EPOLL_IN | SRT_EPOLL_OUT; - srt_epoll_add_usock(srt_epoll, d.id, &modes); - have_connectors = true; - } - else if (d.status == SRTS_CONNECTED) - { - have_ready = true; - } - } - - if (have_ready || have_connectors) - { - Verb() << "(still have: " << (have_ready ? "+" : "-") << "ready, " - << (have_connectors ? "+" : "-") << "conenctors)."; - goto RETRY_READING; - } - - if (have_ready) - { - Verb() << "(connected in the meantime)"; - // Some have connected in the meantime, don't - // waste time on the pending ones. - goto RETRY_READING; - } - - if (have_connectors) - { - Verb() << "(waiting for pending connectors to connect)"; - // Wait here for them to be connected. - vector sready; - sready.resize(m_group_data.size()); - int ready_len = m_group_data.size(); - if (srt_epoll_wait(srt_epoll, sready.data(), &ready_len, 0, 0, -1, 0, 0, 0, 0) == SRT_ERROR) - { - Error("All sockets in the group disconnected"); - } - - goto RETRY_READING; - } - - Error("No data extracted"); - return output; // Just a marker - this above function throws an exception -} - -#endif - MediaPacket SrtSource::Read(size_t chunk) { static size_t counter = 1; @@ -2291,19 +1767,6 @@ MediaPacket SrtSource::Read(size_t chunk) bool have_group SRT_ATR_UNUSED = !m_group_nodes.empty(); bytevector data(chunk); - // EXPERIMENTAL -#ifdef SRT_OLD_APP_READER - if (have_group || m_listener_group) - { - data = GroupRead(chunk); - } - - if (have_group) - { - // This is to be done for caller mode only - UpdateGroupStatus(m_group_data.data(), m_group_data.size()); - } -#else SRT_MSGCTRL mctrl = srt_msgctrl_default; bool ready = true; @@ -2311,13 +1774,11 @@ MediaPacket SrtSource::Read(size_t chunk) do { -#if ENABLE_BONDING if (have_group || m_listener_group) { mctrl.grpdata = m_group_data.data(); mctrl.grpdata_size = m_group_data.size(); } -#endif if (::transmit_int_state) Error("srt_recvmsg2: interrupted"); @@ -2325,7 +1786,7 @@ MediaPacket SrtSource::Read(size_t chunk) ::transmit_throw_on_interrupt = true; stat = srt_recvmsg2(m_sock, data.data(), int(chunk), &mctrl); ::transmit_throw_on_interrupt = false; - if (stat != SRT_ERROR) + if (stat != int(SRT_ERROR)) { ready = true; } @@ -2345,13 +1806,13 @@ MediaPacket SrtSource::Read(size_t chunk) int len = 2; SRT_EPOLL_EVENT sready[2]; len = srt_epoll_uwait(srt_epoll, sready, len, -1); - if (len != -1) + if (len != int(SRT_ERROR)) { Verb() << "... epoll reported ready " << len << " sockets"; // If the event was SRT_EPOLL_UPDATE, report it, and still wait. bool any_read_ready = false; - vector errored; + vector errored; for (int i = 0; i < len; ++i) { if (sready[i].events & SRT_EPOLL_UPDATE) @@ -2413,7 +1874,6 @@ MediaPacket SrtSource::Read(size_t chunk) const bool need_bw_report = transmit_bw_report && int(counter % transmit_bw_report) == transmit_bw_report - 1; const bool need_stats_report = transmit_stats_report && counter % transmit_stats_report == transmit_stats_report - 1; -#if ENABLE_BONDING if (have_group) // Means, group with caller mode { UpdateGroupStatus(mctrl.grpdata, mctrl.grpdata_size); @@ -2425,14 +1885,12 @@ MediaPacket SrtSource::Read(size_t chunk) } } else -#endif { if (transmit_stats_writer && (need_stats_report || need_bw_report)) { PrintSrtStats(m_sock, need_stats_report, need_bw_report, need_stats_report); } } -#endif ++counter; @@ -2445,22 +1903,25 @@ SrtTarget::SrtTarget(std::string host, int port, std::string path, const std::ma } -int SrtTarget::ConfigurePre(SRTSOCKET sock) +SRTSTATUS SrtTarget::ConfigurePre(SRTSOCKET sock) { - int result = SrtCommon::ConfigurePre(sock); - if (result == -1) + SRTSTATUS result = SrtCommon::ConfigurePre(sock); + if (result == SRT_ERROR) return result; + if (int(sock) & SRTGROUP_MASK) + return SRT_STATUS_OK; + int yes = 1; // This is for the HSv4 compatibility; if both parties are HSv5 // (min. version 1.2.1), then this setting simply does nothing. // In HSv4 this setting is obligatory; otherwise the SRT handshake // extension will not be done at all. result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); - if (result == -1) + if (result == SRT_ERROR) return result; - return 0; + return SRT_STATUS_OK; } void SrtTarget::Write(const MediaPacket& data) @@ -2476,7 +1937,7 @@ void SrtTarget::Write(const MediaPacket& data) int len = 2; SRT_EPOLL_EVENT sready[2]; len = srt_epoll_uwait(srt_epoll, sready, len, -1); - if (len != -1) + if (len != int(SRT_ERROR)) { bool any_write_ready = false; for (int i = 0; i < len; ++i) @@ -2505,14 +1966,12 @@ void SrtTarget::Write(const MediaPacket& data) } SRT_MSGCTRL mctrl = srt_msgctrl_default; -#if ENABLE_BONDING bool have_group = !m_group_nodes.empty(); if (have_group || m_listener_group) { mctrl.grpdata = m_group_data.data(); mctrl.grpdata_size = m_group_data.size(); } -#endif if (transmit_use_sourcetime) { @@ -2525,14 +1984,13 @@ void SrtTarget::Write(const MediaPacket& data) // if ALL links from the group have failed to perform // the operation. If only one did, the result will be // visible in the status array. - if (stat == SRT_ERROR) + if (stat == int(SRT_ERROR)) Error("srt_sendmsg"); ::transmit_throw_on_interrupt = false; const bool need_bw_report = transmit_bw_report && int(counter % transmit_bw_report) == transmit_bw_report - 1; const bool need_stats_report = transmit_stats_report && counter % transmit_stats_report == transmit_stats_report - 1; -#if ENABLE_BONDING if (have_group) { // For listener group this is not necessary. The group information @@ -2546,7 +2004,6 @@ void SrtTarget::Write(const MediaPacket& data) } } else -#endif { if (transmit_stats_writer && (need_stats_report || need_bw_report)) { diff --git a/testing/testmedia.hpp b/testing/testmedia.hpp index 36a649130..9bb71f2d0 100644 --- a/testing/testmedia.hpp +++ b/testing/testmedia.hpp @@ -69,28 +69,22 @@ class SrtCommon struct Connection: ConnectionBase { -#if ENABLE_BONDING SRT_SOCKOPT_CONFIG* options = nullptr; -#endif int error = SRT_SUCCESS; int reason = SRT_REJ_UNKNOWN; Connection(std::string h, int p): ConnectionBase(h, p) {} Connection(Connection&& old): ConnectionBase(old) { -#if ENABLE_BONDING if (old.options) { options = old.options; old.options = nullptr; } -#endif } ~Connection() { -#if ENABLE_BONDING srt_delete_config(options); -#endif } }; @@ -103,23 +97,11 @@ class SrtCommon std::string m_mode; std::string m_adapter; std::map m_options; // All other options, as provided in the URI + SRT_TRANSTYPE m_transtype = SRTT_LIVE; std::vector m_group_nodes; std::string m_group_type; std::string m_group_config; -#if ENABLE_BONDING std::vector m_group_data; -#ifdef SRT_OLD_APP_READER - int32_t m_group_seqno = -1; - - struct ReadPos - { - int32_t sequence; - bytevector packet; - }; - std::map m_group_positions; - SRTSOCKET m_group_active; // The link from which the last packet was delivered -#endif -#endif SRTSOCKET m_sock = SRT_INVALID_SOCK; SRTSOCKET m_bindsock = SRT_INVALID_SOCK; @@ -141,7 +123,7 @@ class SrtCommon void Acquire(SRTSOCKET s) { m_sock = s; - if (s & SRTGROUP_MASK) + if (int32_t(s) & SRTGROUP_MASK) m_listener_group = true; } @@ -152,13 +134,11 @@ class SrtCommon 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); + virtual SRTSTATUS ConfigurePost(SRTSOCKET sock); + virtual SRTSTATUS ConfigurePre(SRTSOCKET sock); 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(std::string host, int port); @@ -198,8 +178,6 @@ class SrtSource: public virtual Source, public virtual SrtCommon MediaPacket Read(size_t chunk) override; bytevector GroupRead(size_t chunk); - bool GroupCheckPacketAhead(bytevector& output); - /* In this form this isn't needed. @@ -225,7 +203,7 @@ class SrtTarget: public virtual Target, public virtual SrtCommon SrtTarget(std::string host, int port, std::string path, const std::map& par); SrtTarget() {} - int ConfigurePre(SRTSOCKET sock) override; + SRTSTATUS ConfigurePre(SRTSOCKET sock) override; void Write(const MediaPacket& data) override; bool IsOpen() override { return IsUsable(); } bool Broken() override { return IsBroken(); } @@ -248,7 +226,7 @@ class SrtRelay: public Relay, public SrtSource, public SrtTarget SrtRelay(std::string host, int port, std::string path, const std::map& par); SrtRelay() {} - int ConfigurePre(SRTSOCKET sock) override + SRTSTATUS ConfigurePre(SRTSOCKET sock) override { // This overrides the change introduced in SrtTarget, // which sets the SRTO_SENDER flag. For a bidirectional transmission