From eb5b73f531588cf229d44aacda5f0cd7433148e6 Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Thu, 14 Nov 2024 11:35:55 +0100 Subject: [PATCH 01/92] [core] Fix CUDTGroup::getOpt(..): check value length in group config storage. The storage could have empty strings, and dereferencing the first element must not happen. --- srtcore/group.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 6b0b16484b..dbd8094352 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -806,11 +806,9 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) // Check if the option is in the storage, which means that // it was modified on the group. + vector::const_iterator i = find_if(m_config.begin(), m_config.end(), FOptionValue(optname)); - vector::const_iterator i = find_if(m_config.begin(), m_config.end(), - FOptionValue(optname)); - - if (i == m_config.end()) + if (i == m_config.end() || i->value.empty()) { // Already written to the target variable. if (is_set_on_socket) @@ -822,15 +820,14 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) return; } - // NOTE: even if is_set_on_socket, if it was also found in the group - // settings, overwrite with the value from the group. - // Found, return the value from the storage. + // Found a value set on or derived by a group. Prefer returing it over the one taken from a member socket. // Check the size first. if (w_optlen < int(i->value.size())) throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); - w_optlen = (int) i->value.size(); + SRT_ASSERT(!i->value.empty()); + w_optlen = (int)i->value.size(); memcpy((pw_optval), &i->value[0], i->value.size()); } From 833880b4fb2111fe7d17ee4c8377cbb0cb7c330e Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Thu, 14 Nov 2024 11:39:29 +0100 Subject: [PATCH 02/92] [core] Fixed deriving string options by a group. SRTO_STREAMID, SRTO_PACKETFILTER are directly affected. SRTO_PASSPHRASE was derived correctly. --- srtcore/group.cpp | 69 ++++++++++++++++++++++++++++-------------- srtcore/socketconfig.h | 6 ++++ 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index dbd8094352..3a9fc3f1d5 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -444,23 +444,45 @@ static bool operator!=(const struct linger& l1, const struct linger& l2) return l1.l_onoff != l2.l_onoff || l1.l_linger != l2.l_linger; } +/// A function template to import socket option of a trivial type. +/// This includes linger, bool, int8_t, int32_t, int64_t, double, etc. +/// Potentially can be extended to trivially_copyable if needed. template -static void importOption(vector& storage, SRT_SOCKOPT optname, const ValueType& field) +static void importTrivialOption(vector& storage, SRT_SOCKOPT optname, const ValueType& optval, const int optsize = sizeof(ValueType)) { - ValueType default_opt = ValueType(); - static const int default_opt_size = sizeof(ValueType); - ValueType opt = field; - int opt_size = default_opt_size; - if (!getOptDefault(optname, (&default_opt), (opt_size)) || default_opt != opt) + SRT_STATIC_ASSERT(std::is_trivial::value, "ValueType must be a trivial type."); + ValueType optval_dflt = ValueType(); + int optsize_dflt = sizeof(ValueType); + if (!getOptDefault(optname, (&optval_dflt), (optsize_dflt)) || optval_dflt != optval) { - SRT_ASSERT(opt_size == default_opt_size); + SRT_ASSERT(optsize == sizeof(ValueType)); // Store the option when: // - no default for this option is found - // - the option value retrieved from the field is different than default - storage.push_back(CUDTGroup::ConfigItem(optname, &opt, default_opt_size)); + // - the option value retrieved from the field is different from the default one +#if HAVE_FULL_CXX11 + storage.emplace_back(optname, &optval, optsize); +#else + storage.push_back(CUDTGroup::ConfigItem(optname, &optval, optsize)); +#endif } } +/// A function template to import a StringStorage option. +template +static void importStringOption(vector& storage, SRT_SOCKOPT optname, const StringStorage& optval) +{ + if (optval.empty()) + return; + + // Store the option when: + // - option has a value (default is empty). +#if HAVE_FULL_CXX11 + storage.emplace_back(optname, optval.c_str(),(int) optval.size()); +#else + storage.push_back(CUDTGroup::ConfigItem(optname, optval.c_str(), (int) optval.size())); +#endif +} + // This function is called by the same premises as the CUDT::CUDT(const CUDT&) (copy constructor). // The intention is to rewrite the part that comprises settings from the socket // into the group. Note that some of the settings concern group, some others concern @@ -502,17 +524,18 @@ void CUDTGroup::deriveSettings(CUDT* u) // to be potentially replicated on the socket. So both pre // and post options apply. -#define IM(option, field) importOption(m_config, option, u->m_config.field) -#define IMF(option, field) importOption(m_config, option, u->field) +#define IM(option, field) importTrivialOption(m_config, option, u->m_config.field) +#define IMF(option, field) importTrivialOption(m_config, option, u->field) IM(SRTO_MSS, iMSS); IM(SRTO_FC, iFlightFlagSize); // Nonstandard - importOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); - importOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE)); + 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)); IM(SRTO_LINGER, Linger); + IM(SRTO_UDP_SNDBUF, iUDPSndBufSize); IM(SRTO_UDP_RCVBUF, iUDPRcvBufSize); // SRTO_RENDEZVOUS: impossible to have it set on a listener socket. @@ -520,7 +543,7 @@ void CUDTGroup::deriveSettings(CUDT* u) // SRTO_CONNTIMEO requires a special handling, because API stores the value in integer milliseconds, // but the type of the variable is srt::sync::duration. - importOption(m_config, SRTO_CONNTIMEO, (int) count_milliseconds(u->m_config.tdConnTimeOut)); + importTrivialOption(m_config, SRTO_CONNTIMEO, (int) count_milliseconds(u->m_config.tdConnTimeOut)); IM(SRTO_DRIFTTRACER, bDriftTracer); // Reuseaddr: true by default and should only be true. @@ -536,10 +559,10 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_SNDDROPDELAY, iSndDropDelay); // Special handling of SRTO_PAYLOADSIZE becuase API stores the value as int32_t, // while the config structure stores it as size_t. - importOption(m_config, SRTO_PAYLOADSIZE, (int)u->m_config.zExpPayloadSize); + importTrivialOption(m_config, SRTO_PAYLOADSIZE, (int)u->m_config.zExpPayloadSize); IMF(SRTO_TLPKTDROP, m_bTLPktDrop); - importOption(m_config, SRTO_STREAMID, u->m_config.sStreamName.str()); + importStringOption(m_config, SRTO_STREAMID, u->m_config.sStreamName); IM(SRTO_MESSAGEAPI, bMessageAPI); IM(SRTO_NAKREPORT, bRcvNakReport); @@ -548,22 +571,22 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_IPV6ONLY, iIpV6Only); IM(SRTO_PEERIDLETIMEO, iPeerIdleTimeout_ms); - importOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig.str()); + importStringOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig); - importOption(m_config, SRTO_PBKEYLEN, (int) u->m_pCryptoControl->KeyLen()); + importTrivialOption(m_config, SRTO_PBKEYLEN, (int) u->m_pCryptoControl->KeyLen()); // Passphrase is empty by default. Decipher the passphrase and // store as passphrase option if (u->m_config.CryptoSecret.len) { - string password((const char*)u->m_config.CryptoSecret.str, u->m_config.CryptoSecret.len); - m_config.push_back(ConfigItem(SRTO_PASSPHRASE, password.c_str(), (int)password.size())); + const StringStorage password((const char*)u->m_config.CryptoSecret.str, u->m_config.CryptoSecret.len); + importStringOption(m_config, SRTO_PASSPHRASE, password); } IM(SRTO_KMREFRESHRATE, uKmRefreshRatePkt); IM(SRTO_KMPREANNOUNCE, uKmPreAnnouncePkt); - string cc = u->m_CongCtl.selected_name(); + const string cc = u->m_CongCtl.selected_name(); if (cc != "live") { m_config.push_back(ConfigItem(SRTO_CONGESTION, cc.c_str(), (int)cc.size())); @@ -592,7 +615,7 @@ bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide) template struct Value { - static int fill(void* optval, int, Type value) + static int fill(void* optval, int, const Type& value) { // XXX assert size >= sizeof(Type) ? *(Type*)optval = value; @@ -601,7 +624,7 @@ struct Value }; template <> -inline int Value::fill(void* optval, int len, std::string value) +inline int Value::fill(void* optval, int len, const std::string& value) { if (size_t(len) < value.size()) return 0; diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index 8a78ea21ea..a6dd06caaf 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -142,6 +142,12 @@ class StringStorage memset(stor, 0, sizeof stor); } + StringStorage(const char* s, size_t length) + : len(0) + { + set(s, length); + } + bool set(const char* s, size_t length) { if (length > SIZE) From 6ab86d8acb85849859ac9aa020f95b2da44674d8 Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Thu, 14 Nov 2024 12:23:23 +0100 Subject: [PATCH 03/92] [tests] Extended Bonding.Options test with Stream ID and linger set/get check. --- test/test_bonding.cpp | 55 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 0e48c8a048..54199b1d65 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -9,6 +10,7 @@ #include "srt.h" #include "netinet_any.h" +#include "socketconfig.h" TEST(Bonding, SRTConnectGroup) { @@ -330,6 +332,18 @@ TEST(Bonding, CloseGroupAndSocket) listen_promise.wait(); } +// (void* opaq, SRTSOCKET ns, int hsversion, const struct sockaddr* peeraddr, const char* streamid); +int ListenCallbackFn(void* expected_sid, SRTSOCKET, int /*hsversion*/, const sockaddr* /*peer*/, const char* streamid) +{ + const auto* p = (std::pair*) expected_sid; + // Note: It is not safe to access the streamid pointer by the expected size, + // but there is no way to know the real size apart from finding the first null terminator. + // See FR #3073. + EXPECT_EQ(std::memcmp(streamid, p->first, p->second), 0); + + return 0; +} + TEST(Bonding, Options) { using namespace std; @@ -346,12 +360,12 @@ TEST(Bonding, Options) //EXPECT_EQ(srt_setsockflag(grp, SRTO_RENDEZVOUS, &yes, sizeof yes), SRT_ERROR); #ifdef SRT_ENABLE_ENCRYPTION - string pass = "longenoughpassword"; + const string pass = "longenoughpassword"; // passphrase should be ok. - EXPECT_NE(srt_setsockflag(grp, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR); + EXPECT_NE(srt_setsockflag(grp, SRTO_PASSPHRASE, pass.c_str(), (int) pass.size()), SRT_ERROR); uint32_t val = 16; - EXPECT_NE(srt_setsockflag(grp, SRTO_PBKEYLEN, &val, sizeof val), SRT_ERROR); + EXPECT_NE(srt_setsockflag(grp, SRTO_PBKEYLEN, &val, (int) sizeof val), SRT_ERROR); #ifdef ENABLE_AEAD_API_PREVIEW val = 1; @@ -359,6 +373,35 @@ TEST(Bonding, Options) #endif #endif + // ================ + // Linger is an option of a trivial type, but differes from other integer-typed options. + // Therefore checking it specifically. + const linger l = {1, 10}; + srt_setsockflag(grp, SRTO_LINGER, &l, sizeof l); + + { + linger l2; + int optsize = sizeof l2; + EXPECT_NE(srt_getsockflag(grp, SRTO_LINGER, &l2, &optsize), SRT_ERROR); + EXPECT_EQ(optsize, (int)sizeof l2); + EXPECT_EQ(l2.l_onoff, l.l_onoff); + EXPECT_EQ(l2.l_linger, l.l_linger); + } + // ================ + + const std::array streamid = { 's', 't', 'r', 'e', 0, 'm', 'i', 'd', '%', '&'}; + EXPECT_NE(srt_setsockflag(grp, SRTO_STREAMID, &streamid, streamid.size()), SRT_ERROR); + + auto check_streamid = [&streamid](SRTSOCKET sock) { + std::array tmpbuf; + auto opt_len = (int)tmpbuf.size(); + EXPECT_EQ(srt_getsockflag(sock, SRTO_STREAMID, tmpbuf.data(), &opt_len), SRT_SUCCESS); + EXPECT_EQ(size_t(opt_len), streamid.size()); + EXPECT_EQ(std::memcmp(tmpbuf.data(), streamid.data(), opt_len), 0); + }; + + check_streamid(grp); + int lat = 500; EXPECT_NE(srt_setsockflag(grp, SRTO_RCVLATENCY, &lat, sizeof lat), SRT_ERROR); @@ -371,6 +414,10 @@ TEST(Bonding, Options) unique_lock ux(mx); SRTSOCKET lsn = srt_create_socket(); + + auto expected_sid = std::make_pair(streamid.data(), streamid.size()); + srt_listen_callback(lsn, &ListenCallbackFn, (void*) &expected_sid); + #ifdef SRT_ENABLE_ENCRYPTION EXPECT_NE(srt_setsockflag(lsn, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR); #endif @@ -388,6 +435,8 @@ TEST(Bonding, Options) SRTSOCKET gs = srt_accept(lsn, revsa.get(), &revsa.len); ASSERT_NE(gs, SRT_ERROR); + check_streamid(gs); + // Connected, wait to close latch.wait(ux); From 3931af64fbe87543d8fc65a9ff067a7e07c9eed7 Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Tue, 19 Nov 2024 16:12:16 +0100 Subject: [PATCH 04/92] [tests] Minor clean up of socket options tests (#3076). Using std::array where possible. --- test/test_socket_options.cpp | 218 +++++++++++++++-------------------- 1 file changed, 95 insertions(+), 123 deletions(-) diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index 84a93030bb..58e60e7610 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -30,34 +30,28 @@ class TestSocketOptions : public ::srt::Test { protected: - TestSocketOptions() - { - // initialization code here - } + TestSocketOptions() = default; - ~TestSocketOptions() - { - // cleanup any pending stuff, but no exceptions allowed - } + ~TestSocketOptions() override = default; public: - void BindListener() + void BindListener() const { // Specify address of the listener - sockaddr* psa = (sockaddr*)&m_sa; + const auto* psa = (const sockaddr*)&m_sa; ASSERT_NE(srt_bind(m_listen_sock, psa, sizeof m_sa), SRT_ERROR); } - void StartListener() + void StartListener() const { BindListener(); srt_listen(m_listen_sock, 1); } - int Connect() + int Connect() const { - sockaddr* psa = (sockaddr*)&m_sa; + const auto* psa = (const sockaddr*)&m_sa; return srt_connect(m_caller_sock, psa, sizeof m_sa); } @@ -85,7 +79,7 @@ class TestSocketOptions protected: // setup() is run immediately before a test starts. - void setup() + void setup() override { const int yes = 1; @@ -105,7 +99,7 @@ class TestSocketOptions ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect } - void teardown() + void teardown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. @@ -113,7 +107,6 @@ class TestSocketOptions EXPECT_NE(srt_close(m_listen_sock), SRT_ERROR); } -protected: sockaddr_in m_sa; SRTSOCKET m_caller_sock = SRT_INVALID_SOCK; SRTSOCKET m_listen_sock = SRT_INVALID_SOCK; @@ -121,7 +114,6 @@ class TestSocketOptions int m_pollid = 0; }; - enum class RestrictionType { PREBIND = 0, @@ -131,21 +123,13 @@ enum class RestrictionType const char* RestrictionTypeStr(RestrictionType val) { - switch (val) - { - case RestrictionType::PREBIND: - return "PREBIND"; - break; - case RestrictionType::PRE: - return "PRE"; - break; - case RestrictionType::POST: - return "POST"; - break; - default: - break; - } - return "INVALID"; + const std::map type_to_str = { + { RestrictionType::PREBIND, "PREBIND" }, + { RestrictionType::PRE, "PRE" }, + { RestrictionType::POST, "POST" } + }; + + return type_to_str.find(val) != type_to_str.end() ? type_to_str.at(val) : "INVALID"; } struct OptionTestEntry @@ -249,16 +233,16 @@ void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const ValueTy EXPECT_EQ(opt_len, (int) entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; } -typedef char const* strptr; +using strptr = const char *; template<> void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const strptr& value, const char* desc) { - char opt_val[16]; + std::array opt_val; int opt_len = 0; - EXPECT_EQ(srt_getsockopt(sock, 0, entry.optid, &opt_val, &opt_len), SRT_SUCCESS) + EXPECT_EQ(srt_getsockopt(sock, 0, entry.optid, opt_val.data(), &opt_len), SRT_SUCCESS) << "Getting " << entry.optname << " returned error: " << srt_getlasterror_str(); - EXPECT_EQ(strncmp(opt_val, value, min(opt_len, (int)entry.opt_len)), 0) << desc << ": Wrong " << entry.optname << " value " << opt_val; + EXPECT_EQ(strncmp(opt_val.data(), value, min(opt_len, (int)entry.opt_len)), 0) << desc << ": Wrong " << entry.optname << " value " << opt_val.data(); EXPECT_EQ(opt_len, (int) entry.opt_len) << desc << "Wrong " << entry.optname << " value length"; } @@ -484,25 +468,22 @@ TEST_F(TestSocketOptions, InvalidVals) } } +const char* StateToStr(SRT_SOCKSTATUS st) +{ + std::map st_to_str = { + { SRTS_INIT, "SRTS_INIT" }, + { SRTS_OPENED, "SRTS_OPENED" }, + { SRTS_LISTENING, "SRTS_LISTENING" }, + { SRTS_CONNECTING, "SRTS_CONNECTING" }, + { SRTS_CONNECTED, "SRTS_CONNECTED" }, + { SRTS_BROKEN, "SRTS_BROKEN" }, + { SRTS_CLOSING, "SRTS_CLOSING" }, + { SRTS_CLOSED, "SRTS_CLOSED" }, + { SRTS_NONEXIST, "SRTS_NONEXIST" } + }; - -// TODO: taken from test_enforced_encryption -static const char* const socket_state_array[] = { - "IGNORE_SRTS", - "SRTS_INVALID", - "SRTS_INIT", - "SRTS_OPENED", - "SRTS_LISTENING", - "SRTS_CONNECTING", - "SRTS_CONNECTED", - "SRTS_BROKEN", - "SRTS_CLOSING", - "SRTS_CLOSED", - "SRTS_NONEXIST" -}; - -// A trick that allows the array to be indexed by -1 -const char* const* g_socket_state = socket_state_array + 1; + return st_to_str.find(st) != st_to_str.end() ? st_to_str.at(st) : "INVALID"; +} #if 0 // No socket option can be set in blocking mode because m_ConnectionLock is required by both srt_setsockopt and srt_connect @@ -551,17 +532,17 @@ TEST_F(TestSocketOptions, RestrictionBind) if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) - << "Sock state : " << g_socket_state[srt_getsockstate(m_listen_sock)]; + << "Sock state : " << StateToStr(srt_getsockstate(m_listen_sock)); } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) - << "Sock state : " << g_socket_state[srt_getsockstate(m_listen_sock)]; + << "Sock state : " << StateToStr(srt_getsockstate(m_listen_sock)); } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) - << "Sock state : " << g_socket_state[srt_getsockstate(m_listen_sock)]; + << "Sock state : " << StateToStr(srt_getsockstate(m_listen_sock)); } else { @@ -585,17 +566,17 @@ TEST_F(TestSocketOptions, RestrictionListening) if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) - << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(m_listen_sock)]; + << test_desc << entry.optname << " Sock state: " << StateToStr(srt_getsockstate(m_listen_sock)); } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) - << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(m_listen_sock)]; + << test_desc << entry.optname << " Sock state: " << StateToStr(srt_getsockstate(m_listen_sock)); } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, m_listen_sock, expected_res, test_desc)) - << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(m_listen_sock)]; + << test_desc << entry.optname << " Sock state: " << StateToStr(srt_getsockstate(m_listen_sock)); } else { @@ -622,17 +603,17 @@ TEST_F(TestSocketOptions, RestrictionConnected) if (entry.dflt_val.type() == typeid(bool)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, sock, expected_res, test_desc)) - << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(sock)]; + << test_desc << entry.optname << " Sock state: " << StateToStr(srt_getsockstate(sock)); } else if (entry.dflt_val.type() == typeid(int)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, sock, expected_res, test_desc)) - << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(sock)]; + << test_desc << entry.optname << " Sock state: " << StateToStr(srt_getsockstate(sock)); } else if (entry.dflt_val.type() == typeid(int64_t)) { EXPECT_TRUE(CheckSetNonDefaultValue(entry, sock, expected_res, test_desc)) - << test_desc << entry.optname << " Sock state: " << g_socket_state[srt_getsockstate(sock)]; + << test_desc << entry.optname << " Sock state: " << StateToStr(srt_getsockstate(sock)); } else { @@ -783,7 +764,7 @@ TEST_F(TestSocketOptions, MinInputBWSet) { const int64_t mininputbw_dflt = 0; const int64_t mininputbw = 50000000; - int optlen = (int)(sizeof mininputbw); + auto optlen = (int)(sizeof mininputbw); int64_t bw = -100; EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_MININPUTBW, &bw, sizeof bw), SRT_ERROR) << "Has to be a non-negative number"; @@ -849,15 +830,15 @@ TEST_F(TestSocketOptions, MinInputBWRuntime) TEST_F(TestSocketOptions, StreamIDWrongLen) { - char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; - for (size_t i = 0; i < sizeof buffer; ++i) + std::array buffer; + for (size_t i = 0; i < buffer.size(); ++i) buffer[i] = 'a' + i % 25; - EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer, CSrtConfig::MAX_SID_LENGTH+1), SRT_ERROR); - EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer.data(), CSrtConfig::MAX_SID_LENGTH + 1), SRT_ERROR); + EXPECT_EQ(srt_getlasterror(nullptr), SRT_EINVPARAM); } -//Check if setting -1 as optlen returns an error +// Check if setting -1 as optlen returns an error TEST_F(TestSocketOptions, StringOptLenInvalid) { const string test_string = "test1234567"; @@ -865,19 +846,19 @@ TEST_F(TestSocketOptions, StringOptLenInvalid) const string fec_config = "fec,cols:10,rows:10"; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, test_string.c_str(), -1), SRT_ERROR); - EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + EXPECT_EQ(srt_getlasterror(nullptr), SRT_EINVPARAM); EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_BINDTODEVICE, test_string.c_str(), -1), SRT_ERROR); - EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + EXPECT_EQ(srt_getlasterror(nullptr), SRT_EINVPARAM); EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_CONGESTION, srto_congestion_string.c_str(), -1), SRT_ERROR); - EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + EXPECT_EQ(srt_getlasterror(nullptr), SRT_EINVPARAM); EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_PACKETFILTER, fec_config.c_str(), -1), SRT_ERROR); - EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + EXPECT_EQ(srt_getlasterror(nullptr), SRT_EINVPARAM); EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_PASSPHRASE, test_string.c_str(), -1), SRT_ERROR); - EXPECT_EQ(srt_getlasterror(NULL), SRT_EINVPARAM); + EXPECT_EQ(srt_getlasterror(nullptr), SRT_EINVPARAM); } // Try to set/get a 13-character string in SRTO_STREAMID. @@ -890,23 +871,22 @@ TEST_F(TestSocketOptions, StreamIDOdd) EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_odd.c_str(), (int)sid_odd.size()), SRT_SUCCESS); - char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; - int buffer_len = sizeof buffer; - EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); - EXPECT_EQ(std::string(buffer), sid_odd); + array buffer; + auto buffer_len = (int) buffer.size(); + EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); + EXPECT_EQ(std::string(buffer.data()), sid_odd); EXPECT_EQ(size_t(buffer_len), sid_odd.size()); - EXPECT_EQ(strlen(buffer), sid_odd.size()); + EXPECT_EQ(strlen(buffer.data()), sid_odd.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values - for (size_t i = 0; i < sizeof buffer; ++i) - buffer[i] = 'a'; - buffer_len = (int)(sizeof buffer); + fill(buffer.begin(), buffer.end(), 'a'); + buffer_len = (int) buffer.size(); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_odd.size()); - EXPECT_EQ(strlen(buffer), sid_odd.size()); + EXPECT_EQ(strlen(buffer.data()), sid_odd.size()); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } @@ -919,23 +899,22 @@ TEST_F(TestSocketOptions, StreamIDEven) EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_even.c_str(), (int)sid_even.size()), SRT_SUCCESS); - char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; - int buffer_len = sizeof buffer; - EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); - EXPECT_EQ(std::string(buffer), sid_even); + array buffer; + auto buffer_len = (int) buffer.size(); + EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); + EXPECT_EQ(std::string(buffer.data()), sid_even); EXPECT_EQ(size_t(buffer_len), sid_even.size()); - EXPECT_EQ(strlen(buffer), sid_even.size()); + EXPECT_EQ(strlen(buffer.data()), sid_even.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values - for (size_t i = 0; i < sizeof buffer; ++i) - buffer[i] = 'a'; - buffer_len = (int)(sizeof buffer); + fill(buffer.begin(), buffer.end(), 'a'); + buffer_len = (int) buffer.size(); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_even.size()); - EXPECT_EQ(strlen(buffer), sid_even.size()); + EXPECT_EQ(strlen(buffer.data()), sid_even.size()); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } @@ -945,20 +924,18 @@ TEST_F(TestSocketOptions, StreamIDEven) TEST_F(TestSocketOptions, StreamIDAlmostFull) { // 12 characters = 4*3, that is, aligned to 4 - std::array sid_almost_full; + array sid_almost_full; const size_t size = sid_almost_full.size(); - for (size_t i = 0; i < size; ++i) - sid_almost_full[i] += 'x'; - // Just to manipulate the last ones. + sid_almost_full.fill('x'); sid_almost_full[size-2] = '\0'; sid_almost_full[size-1] = 'z'; EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_almost_full.data(), (int)size), SRT_SUCCESS); std::array buffer; - int buffer_len = (int) buffer.size(); - EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); + auto buffer_len = (int) buffer.size(); + EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_almost_full.size()); EXPECT_EQ(std::memcmp(buffer.data(), sid_almost_full.data(), buffer_len), 0); @@ -967,8 +944,7 @@ TEST_F(TestSocketOptions, StreamIDAlmostFull) // Check accepted socket inherits values buffer_len = (int) buffer.size(); - for (int i = 0; i < buffer_len; ++i) - buffer[i] = 'a'; + fill(buffer.begin(), buffer.end(), 'a'); EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_almost_full.size()); EXPECT_EQ(std::memcmp(buffer.data(), sid_almost_full.data(), buffer_len), 0); @@ -981,35 +957,31 @@ TEST_F(TestSocketOptions, StreamIDAlmostFull) TEST_F(TestSocketOptions, StreamIDFull) { // 12 characters = 4*3, that is, aligned to 4 - string sid_full; - for (size_t i = 0; i < CSrtConfig::MAX_SID_LENGTH; ++i) - sid_full += 'x'; + array sid_full; + sid_full.fill('x'); // Just to manipulate the last ones. size_t size = sid_full.size(); - sid_full[size-2] = 'y'; + sid_full[size-2] = '\0'; sid_full[size-1] = 'z'; - EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_full.c_str(), (int)sid_full.size()), SRT_SUCCESS); + EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_full.data(), (int)sid_full.size()), SRT_SUCCESS); - char buffer[CSrtConfig::MAX_SID_LENGTH + 135]; - int buffer_len = sizeof buffer; - EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); - EXPECT_EQ(std::string(buffer), sid_full); + array buffer; + auto buffer_len = (int) buffer.size(); + EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); + EXPECT_EQ(memcmp(buffer.data(), sid_full.data(), sid_full.size()), 0); EXPECT_EQ(size_t(buffer_len), sid_full.size()); - EXPECT_EQ(strlen(buffer), sid_full.size()); StartListener(); const SRTSOCKET accepted_sock = EstablishConnection(); // Check accepted socket inherits values - for (size_t i = 0; i < sizeof buffer; ++i) - buffer[i] = 'a'; - buffer_len = (int)(sizeof buffer); - EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); + fill(buffer.begin(), buffer.end(), 'a'); + buffer_len = (int) buffer.size(); + EXPECT_EQ(srt_getsockopt(accepted_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); EXPECT_EQ(size_t(buffer_len), sid_full.size()); - EXPECT_EQ(strlen(buffer), sid_full.size()); - EXPECT_EQ(buffer[sid_full.size()-1], 'z'); + EXPECT_EQ(std::memcmp(buffer.data(), sid_full.data(), buffer_len), 0); ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); } @@ -1022,10 +994,10 @@ TEST_F(TestSocketOptions, StreamIDLenListener) EXPECT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_STREAMID, stream_id_13.c_str(), (int)stream_id_13.size()), SRT_SUCCESS); - char buffer[648]; - int buffer_len = sizeof buffer; - EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); - EXPECT_EQ(string(buffer), stream_id_13); + array buffer; + auto buffer_len = (int) buffer.size(); + EXPECT_EQ(srt_getsockopt(m_listen_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); + EXPECT_EQ(string(buffer.data()), stream_id_13); EXPECT_EQ(size_t(buffer_len), stream_id_13.size()); StartListener(); @@ -1034,9 +1006,9 @@ TEST_F(TestSocketOptions, StreamIDLenListener) // Check accepted and caller sockets do not inherit StreamID. for (SRTSOCKET sock : { m_caller_sock, accepted_sock }) { - buffer_len = (int)(sizeof buffer); - fill_n(buffer, buffer_len, 'a'); - EXPECT_EQ(srt_getsockopt(sock, 0, SRTO_STREAMID, &buffer, &buffer_len), SRT_SUCCESS); + buffer_len = (int) buffer.size(); + fill_n(buffer.data(), buffer_len, 'a'); + EXPECT_EQ(srt_getsockopt(sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); EXPECT_EQ(buffer_len, 0) << (sock == accepted_sock ? "ACCEPTED" : "CALLER"); } From 5f16494f584e52b08a5dcdc55fe891d01d02f3a4 Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Tue, 19 Nov 2024 16:25:53 +0100 Subject: [PATCH 05/92] [core] Fixed getting SRTO_RCVBUF and SRTO_SNDBUF on a group. The documented length is 4 bytes, the code was setting 8 bytes without checking `optlen`. --- srtcore/group.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 3a9fc3f1d5..c0df17ac82 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -472,7 +472,7 @@ template static void importStringOption(vector& storage, SRT_SOCKOPT optname, const StringStorage& optval) { if (optval.empty()) - return; + return; // Store the option when: // - option has a value (default is empty). @@ -615,9 +615,11 @@ bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide) template struct Value { - static int fill(void* optval, int, const Type& value) + static int fill(void* optval, int len, const Type& value) { - // XXX assert size >= sizeof(Type) ? + if (size_t(len) < sizeof(Type)) + return 0; + *(Type*)optval = value; return sizeof(Type); } @@ -671,7 +673,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::UDP_HDR_SIZE)); break; case SRTO_LINGER: From 87850b7926860889e2525853f8411b711dee4e6f Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Mon, 25 Nov 2024 11:17:44 +0100 Subject: [PATCH 06/92] [docs] Fixed link to rejection codes document. --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 8c07ee515d..83baf258bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,7 +7,7 @@ | [SRT API](API/API.md) | [API](API/) | [API.md](API/API.md) | Detailed description of the SRT C API. | | [SRT API Functions](API/API-functions.md) | [API](API/) | [API-functions.md](API/API-functions.md) | Reference document for SRT API functions. | | [SRT API Socket Options](API/API-socket-options.md) | [API](API/) | [API-socket-options.md](API/API-socket-options.md) | Instructions and list of socket options for SRT API. | -| [SRT Rejection Codes](API/rejections-codes.md) | [API](API/) | [rejection-codes.md](API/rejection-codes.md) | The list of SRT rejections codes. | +| [SRT Rejection Codes](API/rejection-codes.md) | [API](API/) | [rejection-codes.md](API/rejection-codes.md) | The list of SRT rejections codes. | | [SRT Statistics](API/statistics.md) | [API](API/) | [statistics.md](API/statistics.md) | How to use SRT socket and socket group statistics. | | [Configuration Guidelines](API/configuration-guidelines.md) | [API](API/) | [configuration-guidelines.md](API/configuration-guidelines.md) | How to configure SRT buffers. | | | | | | From cdbe5f515281e7d1050c6046bf372743e0bbcd5b Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Wed, 27 Nov 2024 12:52:54 +0100 Subject: [PATCH 07/92] [docs] Updated conan badge links. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0bf8101441..b6465a347e 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,8 @@ By contributing code to the SRT project, you agree to license your contribution [Vcpkg-package]: https://repology.org/project/srt/versions [Vcpkg-badge]: https://repology.org/badge/version-for-repo/vcpkg/srt.svg -[ConanCenter-package]: https://repology.org/project/srt/versions -[ConanCenter-badge]: https://repology.org/badge/version-for-repo/conancenter/srt.svg +[ConanCenter-package]: https://conan.io/center/recipes/srt +[ConanCenter-badge]: https://img.shields.io/conan/v/srt [sonarcloud-project]: https://sonarcloud.io/project/overview?id=srt [sonarcloud-badge]: https://sonarcloud.io/api/project_badges/measure?project=srt&metric=alert_status From f109fb1354d6b31ba7cc6b6e72d75bc761a9cd31 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Wed, 11 Dec 2024 13:46:49 +0100 Subject: [PATCH 08/92] [core] Fixed: attempt to send to a group in connection-pending state (nonblocking) reported wrong 'connection lost' error --- srtcore/group.cpp | 71 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/srtcore/group.cpp b/srtcore/group.cpp index c0df17ac82..fd4e0ba0dd 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -600,6 +600,7 @@ void CUDTGroup::deriveSettings(CUDT* u) #undef IMF } +// XXX This function is likely of no use now. bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide) { const bool synconmsg = IsSet(flags, SRT_GFLAG_SYNCONMSG); @@ -1418,9 +1419,28 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) // { send_CheckBrokenSockets() - if (!pendingSockets.empty()) + // Make an extra loop check to see if we could be + // in a condition of "all sockets either blocked or pending" + + int nsuccessful = 0; // number of successfully connected sockets + int nblocked = 0; // number of sockets blocked in connection + bool is_pending_blocked = false; + for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is) + { + if (is->stat != -1) + { + nsuccessful++; + } + // is->stat == -1 + else if (is->code == SRT_EASYNCSND) + { + ++nblocked; + } + } + + if (!pendingSockets.empty() || nblocked) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets, polling them."); + HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets (blocked: " << nblocked << "), polling them."); // These sockets if they are in pending state, they should be added to m_SndEID // at the connecting stage. @@ -1435,12 +1455,24 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) } else { + int swait_timeout = 0; + + // There's also a hidden condition here that is the upper if condition. + is_pending_blocked = (nsuccessful == 0); + + // If this is the case when + if (m_bSynSending && is_pending_blocked) + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: will block for " << m_iSndTimeOut << " - waiting for any writable in blocking mode"); + swait_timeout = m_iSndTimeOut; + } + { InvertedLock ug(m_GroupLock); THREAD_PAUSED(); m_Global.m_EPoll.swait( - *m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything happened + *m_SndEpolld, (sready), swait_timeout, false /*report by retval*/); // Just check if anything happened THREAD_RESUMED(); } @@ -1453,6 +1485,10 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) HLOGC(gslog.Debug, log << "grp/sendBroadcast: RDY: " << DisplayEpollResults(sready)); // sockets in EX: should be moved to wipeme. + // IMPORTANT: we check only PENDING sockets (not blocked) because only + // pending sockets might report ERR epoll without being explicitly broken. + // Sockets that did connect and just have buffer full will be always broken, + // if they're going to report ERR in epoll. for (vector::iterator i = pendingSockets.begin(); i != pendingSockets.end(); ++i) { if (CEPoll::isready(sready, *i, SRT_EPOLL_ERR)) @@ -1464,6 +1500,9 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) int no_events = 0; m_Global.m_EPoll.update_usock(m_SndEID, *i, &no_events); } + + if (CEPoll::isready(sready, *i, SRT_EPOLL_OUT)) + is_pending_blocked = false; } // After that, all sockets that have been reported @@ -1480,7 +1519,10 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (m_bClosing) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - send_CloseBrokenSockets(wipeme); + // Just for a case, when a socket that was blocked or pending + // had switched to write-enabled, + + send_CloseBrokenSockets((wipeme)); // wipeme will be cleared by this function // Re-check after the waiting lock has been reacquired if (m_bClosing) @@ -1742,9 +1784,18 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc) if (none_succeeded) { - HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)"); m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false); - m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + if (!m_bSynSending && (is_pending_blocked || was_blocked)) + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: no links are ready for sending"); + ercode = SRT_EASYNCSND; + } + else + { + HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)"); + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true); + } + // Reparse error code, if set. // It might be set, if the last operation was failed. // If any operation succeeded, this will not be executed anyway. @@ -3398,11 +3449,13 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx // Note: A link is added in unstableLinks if sending has failed with SRT_ESYNCSND. const unsigned num_unstable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE); const unsigned num_wary = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE_WARY); - if ((num_unstable + num_wary == 0) || !w_none_succeeded) + const unsigned num_pending = w_sendBackupCtx.countMembersByState(BKUPST_PENDING); + if ((num_unstable + num_wary + num_pending == 0) || !w_none_succeeded) return; HLOGC(gslog.Debug, log << "grp/sendBackup: no successfull sending: " - << (num_unstable + num_wary) << " unstable links - waiting to retry sending..."); + << (num_unstable + num_wary) << " unstable links, " + << num_pending << " pending - waiting to retry sending..."); // Note: GroupLock is set already, skip locks and checks getGroupData_LOCKED((w_mc.grpdata), (&w_mc.grpdata_size)); @@ -3658,6 +3711,7 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) { if (len <= 0) { + LOGC(gslog.Error, log << "grp/send(backup): negative length: " << len); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } @@ -3677,6 +3731,7 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc) if (m_bClosing) { leaveCS(m_Global.m_GlobControlLock); + LOGC(gslog.Error, log << "grp/send(backup): Cannot send, connection lost!"); throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } From 59e2c9c538a297ee7357fd49fe2149dd034d5cb1 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 17 Dec 2024 21:36:06 +0100 Subject: [PATCH 09/92] [tests] Added more tests for bonding (#3085). --- test/test_bonding.cpp | 1002 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 970 insertions(+), 32 deletions(-) diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 54199b1d65..4ec4a84f52 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -3,15 +3,17 @@ #include #include #include -#include - #include "gtest/gtest.h" #include "test_env.h" #include "srt.h" +#include "udt.h" +#include "common.h" #include "netinet_any.h" #include "socketconfig.h" +#include "apputil.hpp" + TEST(Bonding, SRTConnectGroup) { srt::TestInit srtinit; @@ -25,7 +27,7 @@ TEST(Bonding, SRTConnectGroup) { sa.sin_family = AF_INET; sa.sin_port = htons(4200 + i); - ASSERT_EQ(inet_pton(AF_INET, "192.168.1.237", &sa.sin_addr), 1); + EXPECT_EQ(inet_pton(AF_INET, "192.168.1.237", &sa.sin_addr), 1); const SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(NULL, (struct sockaddr*)&sa, sizeof sa); targets.push_back(gd); @@ -58,7 +60,7 @@ TEST(Bonding, SRTConnectGroup) } } -#define ASSERT_SRT_SUCCESS(callform) ASSERT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str() +#define EXPECT_SRT_SUCCESS(callform) EXPECT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str() void listening_thread(bool should_read) { @@ -66,37 +68,37 @@ void listening_thread(bool should_read) sockaddr_in bind_sa; memset(&bind_sa, 0, sizeof bind_sa); bind_sa.sin_family = AF_INET; - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); bind_sa.sin_port = htons(4200); - ASSERT_SRT_SUCCESS(srt_bind(server_sock, (sockaddr*)&bind_sa, sizeof bind_sa)); + EXPECT_SRT_SUCCESS(srt_bind(server_sock, (sockaddr*)&bind_sa, sizeof bind_sa)); const int yes = 1; - ASSERT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &yes, sizeof yes)); + EXPECT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &yes, sizeof yes)); const int no = 1; - ASSERT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_RCVSYN, &no, sizeof no)); + EXPECT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_RCVSYN, &no, sizeof no)); const int eid = srt_epoll_create(); const int listen_event = SRT_EPOLL_IN | SRT_EPOLL_ERR; - ASSERT_SRT_SUCCESS(srt_epoll_add_usock(eid, server_sock, &listen_event)); + EXPECT_SRT_SUCCESS(srt_epoll_add_usock(eid, server_sock, &listen_event)); - ASSERT_SRT_SUCCESS(srt_listen(server_sock, 5)); + EXPECT_SRT_SUCCESS(srt_listen(server_sock, 5)); std::cout << "Listen: wait for acceptability\n"; int fds[2]; int fds_len = 2; int ers[2]; int ers_len = 2; - ASSERT_SRT_SUCCESS(srt_epoll_wait(eid, fds, &fds_len, ers, &ers_len, 5000, + EXPECT_SRT_SUCCESS(srt_epoll_wait(eid, fds, &fds_len, ers, &ers_len, 5000, 0, 0, 0, 0)); std::cout << "Listen: reported " << fds_len << " acceptable and " << ers_len << " errors\n"; - ASSERT_GT(fds_len, 0); - ASSERT_EQ(fds[0], server_sock); + EXPECT_GT(fds_len, 0); + EXPECT_EQ(fds[0], server_sock); srt::sockaddr_any scl; int acp = srt_accept(server_sock, (scl.get()), (&scl.len)); - ASSERT_SRT_SUCCESS(acp); - ASSERT_NE(acp & SRTGROUP_MASK, 0); + EXPECT_SRT_SUCCESS(acp); + EXPECT_NE(acp & SRTGROUP_MASK, 0); if (should_read) { @@ -124,11 +126,22 @@ void listening_thread(bool should_read) // srt_accept.. } -void ConnectCallback(void* /*opaq*/, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) +SRTSOCKET g_listen_socket = -1; +int g_nconnected = 0; +int g_nfailed = 0; + +// This ConnectCallback is mainly informative, but it also collects the +// number of succeeded and failed links. +void ConnectCallback(void* , SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) { std::cout << "Connect callback. Socket: " << sock << ", error: " << error << ", token: " << token << '\n'; + + if (error == SRT_SUCCESS) + ++g_nconnected; + else + ++g_nfailed; } TEST(Bonding, NonBlockingGroupConnect) @@ -140,20 +153,20 @@ TEST(Bonding, NonBlockingGroupConnect) std::cout << "Created group socket: " << ss << '\n'; int no = 0; - ASSERT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode - ASSERT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode + EXPECT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode + EXPECT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode const int poll_id = srt_epoll_create(); // Will use this epoll to wait for srt_accept(...) const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; - ASSERT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); srt_connect_callback(ss, &ConnectCallback, this); sockaddr_in sa; sa.sin_family = AF_INET; sa.sin_port = htons(4200); - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); sockaddr_in safail = sa; safail.sin_port = htons(4201); // port where we have no listener @@ -166,14 +179,14 @@ TEST(Bonding, NonBlockingGroupConnect) EXPECT_GT(sockid, 0) << "Socket " << 1; sa.sin_port = htons(4201); // Changing port so that second connect fails std::cout << "Socket created: " << sockid << '\n'; - ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); } { const int sockid = srt_connect(ss, (sockaddr*) &safail, sizeof safail); EXPECT_GT(sockid, 0) << "Socket " << 2; safail.sin_port = htons(4201); // Changing port so that second connect fails std::cout << "Socket created: " << sockid << '\n'; - ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); + EXPECT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR); } std::cout << "Returned from connecting two sockets " << std::endl; @@ -226,8 +239,8 @@ TEST(Bonding, CloseGroupAndSocket) { srt::TestInit srtinit; - const int ss = srt_create_group(SRT_GTYPE_BROADCAST); - ASSERT_NE(ss, SRT_ERROR); + MAKE_UNIQUE_SOCK(ss, "broadcast group", srt_create_group(SRT_GTYPE_BROADCAST)); + std::cout << "Created group socket: " << ss << '\n'; int no = 0; @@ -325,7 +338,7 @@ TEST(Bonding, CloseGroupAndSocket) std::this_thread::sleep_for(std::chrono::milliseconds(300)); - EXPECT_EQ(srt_close(ss), 0) << "srt_close: %s\n" << srt_getlasterror_str(); + ss.close(); std::cout << "CLOSED GROUP. Now waiting for sender to exit...\n"; sender.join(); @@ -422,10 +435,10 @@ TEST(Bonding, Options) EXPECT_NE(srt_setsockflag(lsn, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR); #endif int allow = 1; - ASSERT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR); - sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET); - ASSERT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR); - ASSERT_NE(srt_listen(lsn, 1), SRT_ERROR); + EXPECT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR); + sockaddr_any sa = srt::CreateAddr("127.0.0.1", 5555, AF_INET); + EXPECT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR); + EXPECT_NE(srt_listen(lsn, 1), SRT_ERROR); started = true; // First wait - until it's let go with accepting @@ -433,7 +446,7 @@ TEST(Bonding, Options) sockaddr_any revsa; SRTSOCKET gs = srt_accept(lsn, revsa.get(), &revsa.len); - ASSERT_NE(gs, SRT_ERROR); + EXPECT_NE(gs, SRT_ERROR); check_streamid(gs); @@ -462,7 +475,7 @@ TEST(Bonding, Options) } // Now the thread is accepting, so we call the connect. - sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET); + sockaddr_any sa = srt::CreateAddr("127.0.0.1", 5555, AF_INET); SRTSOCKET member = srt_connect(grp, sa.get(), sa.size()); // We've released the mutex and signaled the CV, so accept should proceed now. @@ -556,7 +569,7 @@ TEST(Bonding, InitialFailure) int allow = 1; ASSERT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR); - sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET); + sockaddr_any sa = srt::CreateAddr("127.0.0.1", 5555, AF_INET); ASSERT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR); ASSERT_NE(srt_listen(lsn, 5), SRT_ERROR); @@ -613,3 +626,928 @@ TEST(Bonding, InitialFailure) srt_close(lsn); } + + +// General idea: +// This should try to connect to two nonexistent links, +// the connecting function (working in blocking mode) +// should exit with error, after the group has been closed +// in a separate thread. +// +// Steps: +// 1. Create group +// 2. Use a nonexistent endpoints 192.168.1.237:4200 and *:4201 +// 3. Close the group in a thread +// 4. Wait for error +TEST(Bonding, ConnectBlind) +{ + using namespace srt; + + TestInit srtinit; + MAKE_UNIQUE_SOCK(ss, "GrpCaller", srt_create_group(SRT_GTYPE_BROADCAST)); + + std::vector targets; + for (int i = 0; i < 2; ++i) + { + sockaddr_any sa = srt::CreateAddr("192.168.1.237", 4200 + i, AF_INET); + const SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(NULL, sa.get(), sa.size()); + targets.push_back(gd); + } + + std::future closing_promise = std::async(std::launch::async, [](int s) { + std::this_thread::sleep_for(std::chrono::seconds(2)); + std::cerr << "Closing group" << std::endl; + srt_close(s); + }, ss); + + std::cout << "srt_connect_group calling " << std::endl; + const int st = srt_connect_group(ss, targets.data(), targets.size()); + std::cout << "srt_connect_group returned " << st << std::endl; + + closing_promise.wait(); + EXPECT_EQ(st, -1); + + // Delete config objects before prospective exception + for (auto& gd: targets) + srt_delete_config(gd.config); + + int res = srt_close(ss); + if (res == SRT_ERROR) + { + std::cerr << "srt_close: " << srt_getlasterror_str() << std::endl; + } +} + +// TEST IDEA: +// This uses srt_connect_group in non-blocking mode. The listener +// is also created to respond to the connection. Expected is to +// continue the connecting in background and report a success, +// and report the epoll IN on listener for the first connection, +// and UPDATE For the second one. +// +// TEST STEPS: +// 1. Create a listener socket and a group. +// 2. Set the group and the listener socket non-blocking mode +// 3. Start the accepting thread +// - wait for IN event ready on the listener socket +// - accept a connection +// - wait for UPDATE event ready on the listener socket +// - wait for any event up to 5s (possibly ERR) +// - close the listener socket +// 4. Prepare two connections and start connecting +// 5. Wait for the OUT readiness event on the group +// 6. Close the group. +// 7. Join the thread +TEST(Bonding, ConnectNonBlocking) +{ + using namespace std; + using namespace std::chrono; + using namespace srt; + + TestInit srtinit; + + const string ADDR = "127.0.0.1"; + const int PORT = 4209; + + // NOTE: Add more group types, if implemented! + vector types { SRT_GTYPE_BROADCAST, SRT_GTYPE_BACKUP }; + + for (const auto GTYPE: types) + { + g_listen_socket = srt_create_socket(); + sockaddr_in bind_sa; + memset(&bind_sa, 0, sizeof bind_sa); + bind_sa.sin_family = AF_INET; + EXPECT_EQ(inet_pton(AF_INET, ADDR.c_str(), &bind_sa.sin_addr), 1); + bind_sa.sin_port = htons(PORT); + + EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1); + const int yes = 1; + srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes); + EXPECT_NE(srt_listen(g_listen_socket, 5), -1); + + int lsn_eid = srt_epoll_create(); + int lsn_events = SRT_EPOLL_IN | SRT_EPOLL_ERR | SRT_EPOLL_UPDATE; + srt_epoll_add_usock(lsn_eid, g_listen_socket, &lsn_events); + + // Caller part + + const int ss = srt_create_group(GTYPE); + EXPECT_NE(ss, SRT_ERROR); + std::cout << "Created group socket: " << ss << '\n'; + + int no = 0; + EXPECT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode + EXPECT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode + + const int poll_id = srt_epoll_create(); + // Will use this epoll to wait for srt_accept(...) + const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR; + EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); + + srt_connect_callback(ss, &ConnectCallback, this); + + sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port = htons(PORT); + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + + //srt_setloglevel(LOG_DEBUG); + + auto acthr = std::thread([&lsn_eid]() { + SRT_EPOLL_EVENT ev[3]; + + ThreadName::set("TEST_A"); + + cout << "[A] Waiting for accept\n"; + + // This can wait in infinity; worst case it will be killed in process. + int uwait_res = srt_epoll_uwait(lsn_eid, ev, 3, -1); + EXPECT_EQ(uwait_res, 1); + EXPECT_EQ(ev[0].fd, g_listen_socket); + + // Check if the IN event is set, even if it's not the only event + const int ev_in_bit = SRT_EPOLL_IN; + EXPECT_NE(ev[0].events & ev_in_bit, 0); + bool have_also_update = ev[0].events & SRT_EPOLL_UPDATE; + + sockaddr_any adr; + int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len); + + // Expected: group reporting + EXPECT_NE(accept_id & SRTGROUP_MASK, 0); + + if (have_also_update) + { + cout << "[A] NOT waiting for update - already reported previously\n"; + } + else + { + cout << "[A] Waiting for update\n"; + // Now another waiting is required and expected the update event + // Wait up to 5s to avoid hangup in case of error + uwait_res = srt_epoll_uwait(lsn_eid, ev, 3, 5000); + EXPECT_EQ(uwait_res, 1); + EXPECT_EQ(ev[0].fd, g_listen_socket); + EXPECT_EQ(ev[0].events, (int)SRT_EPOLL_UPDATE); + } + + cout << "[A] Waitig for close (up to 5s)\n"; + // Wait up to 5s for an error + srt_epoll_uwait(lsn_eid, ev, 3, 5000); + + srt_close(accept_id); + cout << "[A] thread finished\n"; + }); + + cout << "Connecting two sockets\n"; + + SRT_SOCKGROUPCONFIG cc[2]; + cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[1] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + + EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); + + int result = srt_connect_group(ss, cc, 2); + EXPECT_NE(result, -1); + char data[4] = { 1, 2, 3, 4}; + cout << "Sending...\n"; + int wrong_send = srt_send(ss, data, sizeof data); + cout << "Getting error...\n"; + int errorcode = srt_getlasterror(NULL); + EXPECT_EQ(wrong_send, -1); + EXPECT_EQ(errorcode, SRT_EASYNCSND) << "REAL ERROR: " << srt_getlasterror_str(); + + // Wait up to 2s + SRT_EPOLL_EVENT ev[3]; + const int uwait_result = srt_epoll_uwait(poll_id, ev, 3, 2000); + std::cout << "Returned from connecting two sockets " << std::endl; + + EXPECT_EQ(uwait_result, 1); // Expect the group reported + EXPECT_EQ(ev[0].fd, ss); + + // One second to make sure that both links are connected. + this_thread::sleep_for(seconds(1)); + + EXPECT_EQ(srt_close(ss), 0); + acthr.join(); + + srt_epoll_release(lsn_eid); + srt_epoll_release(poll_id); + + srt_close(g_listen_socket); + } + +} + +// TEST IDEA: +// In this test there is created a working listener socket to +// accept the connection, and we use a Backup-type group with +// two links, but different weights. We connect them both and +// make sure that both are ready for use. Then we send a packet +// over the group and see, which link got activated and which +// remained idle. Expected is to have the link with higher +// priority (greater weight) to be activated. +// +// TEST STEPS: +// 1. Create a listener socket and a group. +// 3. Start the accepting thread +// - accept a connection +// - read one packet from the accepted entity +// - close the listener socket +// 4. Prepare two connections (one with weight=1) and connect the group +// 5. Wait for having all links connected +// 6. Send one packet and check which link was activated +// 6. Close the group. +// 7. Join the thread +TEST(Bonding, BackupPriorityBegin) +{ + using namespace std; + using namespace std::chrono; + using namespace srt; + + TestInit srtinit; + + g_nconnected = 0; + g_nfailed = 0; + + g_listen_socket = srt_create_socket(); + ASSERT_NE(g_listen_socket, SRT_INVALID_SOCK); + sockaddr_in bind_sa; + memset(&bind_sa, 0, sizeof bind_sa); + bind_sa.sin_family = AF_INET; + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); + bind_sa.sin_port = htons(4200); + + EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1); + const int yes = 1; + srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes); + EXPECT_NE(srt_listen(g_listen_socket, 5), -1); + + // Caller part + + const int ss = srt_create_group(SRT_GTYPE_BACKUP); + EXPECT_NE(ss, SRT_ERROR); + + srt_connect_callback(ss, &ConnectCallback, this); + + sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port = htons(4200); + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + + auto acthr = std::thread([]() { + sockaddr_any adr; + cout << "[A] Accepting a connection...\n"; + int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len); + + // Expected: group reporting + EXPECT_NE(accept_id & SRTGROUP_MASK, 0); + + SRT_SOCKGROUPDATA gdata[2]; + SRT_MSGCTRL mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + long long data[1320/8]; + + cout << "[A] Receiving...\n"; + int ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc)); + EXPECT_EQ(ds, 8); + + cout << "[A] Closing\n"; + srt_close(accept_id); + cout << "[A] thread finished\n"; + }); + + cout << "Connecting two sockets\n"; + + SRT_SOCKGROUPCONFIG cc[2]; + cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[0].token = 0; + cc[1] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[1].token = 1; + cc[1].weight = 1; // higher than the default 0 + + int result = srt_connect_group(ss, cc, 2); + EXPECT_GT(result, 0); // blocking mode, first connection = returns Socket ID + + // Make sure both links are connected + SRT_SOCKGROUPDATA gdata[2]; + size_t psize = 2; + size_t nwait = 10; + cout << "Waiting for getting 2 links:\n"; + while (--nwait) + { + srt_group_data(ss, gdata, &psize); + if (psize == 2) + { + int l1, l2; + l1 = gdata[0].memberstate; + l2 = gdata[1].memberstate; + + if (l1 > SRT_GST_PENDING && l2 > SRT_GST_PENDING) + { + cout << "Both up: [0]=" << l1 << " [1]=" << l2 << "\n"; + break; + } + else + { + cout << "Still link states [0]=" << l1 << " [1]=" << l2 << "\n"; + } + } + else + { + cout << "Still " << psize << endl; + } + this_thread::sleep_for(milliseconds(500)); + } + EXPECT_NE(nwait, size_t()); + + // Now send one packet + long long data = 0x1234123412341234; + + SRT_MSGCTRL mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + + // This call should retrieve the group information + // AFTER the transition has happened + int sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc)); + EXPECT_EQ(sendret, int(sizeof data)); + + // So, let's check which link is in RUNNING state + // TOKEN value is the index in cc array, and we should + // also have the weight value there. + + SRT_SOCKGROUPDATA* mane, * backup; + if (gdata[0].weight == 0) + { + backup = &gdata[0]; + mane = &gdata[1]; + } + else + { + mane = &gdata[0]; + backup = &gdata[1]; + } + + cout << "MAIN:[" << mane->token << "] weight=" << mane->weight << endl; + cout << "BACKUP:[" << backup->token << "] weight=" << backup->weight << endl; + + // Ok, now mane link should be active, backup idle + EXPECT_EQ(mane->memberstate, SRT_GST_RUNNING); + EXPECT_EQ(backup->memberstate, SRT_GST_IDLE); + + acthr.join(); +} + + +// TEST IDEA: +// In this test there is created a working listener socket to +// accept the connection, and we use a Backup-type group with +// two links, but different weights. We connect the first link +// with less weight and send one packet to make sure this only +// link was activated. Then we connect a second link with weight=1. +// Then we send the packet again and see if the new link was +// immediately activated. The first link should be silenced after +// time, but there's no possibility to check this in such a +// simple test. +// +// TEST STEPS: +// 1. Create a listener socket and a group. +// 3. Start the accepting thread +// - accept a connection +// - read one packet from the accepted entity +// - read the second packet from the accepted entity +// - close the listener socket +// 4. Prepare one connection with weight=0 and connect the group +// 5. Send a packet to enforce activation of one link +// 6. Prepare another connection with weight=1 and connect the group +// 7. Send a packet +// 8. Check member status - both links should be running. +// 9. Close the group. +// 10. Join the thread +TEST(Bonding, BackupPriorityTakeover) +{ + using namespace std; + using namespace std::chrono; + using namespace srt; + + TestInit srtinit; + + g_nconnected = 0; + g_nfailed = 0; + + g_listen_socket = srt_create_socket(); + ASSERT_NE(g_listen_socket, SRT_INVALID_SOCK); + sockaddr_in bind_sa; + memset(&bind_sa, 0, sizeof bind_sa); + bind_sa.sin_family = AF_INET; + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); + bind_sa.sin_port = htons(4200); + + EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1); + const int yes = 1; + srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes); + EXPECT_NE(srt_listen(g_listen_socket, 5), -1); + + // Caller part + + const int ss = srt_create_group(SRT_GTYPE_BACKUP); + EXPECT_NE(ss, SRT_ERROR); + + srt_connect_callback(ss, &ConnectCallback, this); + + sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port = htons(4200); + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + + auto acthr = std::thread([]() { + sockaddr_any adr; + cout << "[A] Accepting a connection...\n"; + int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len); + + // Expected: group reporting + EXPECT_NE(accept_id & SRTGROUP_MASK, 0); + + SRT_SOCKGROUPDATA gdata[2]; + SRT_MSGCTRL mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + long long data[1320/8]; + + cout << "[A] Receiving 1...\n"; + int ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc)); + EXPECT_EQ(ds, 8); + + cout << "[A] Receiving 2...\n"; + ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc)); + EXPECT_EQ(ds, 8); + + // To make it possible that the state is checked before it is closed. + this_thread::sleep_for(seconds(1)); + + cout << "[A] Closing\n"; + srt_close(accept_id); + cout << "[A] thread finished\n"; + }); + + cout << "Connecting first link weight=0:\n"; + + SRT_SOCKGROUPCONFIG cc[2]; + cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[0].token = 0; + + int result = srt_connect_group(ss, cc, 1); + EXPECT_GT(result, 0); // connect with only one element returns socket ID + + // As we have one link, after `srt_connect_group` returns, we have + // this link now connected. Send one data portion. + + SRT_SOCKGROUPDATA gdata[2]; + + long long data = 0x1234123412341234; + SRT_MSGCTRL mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + + cout << "Sending (1)\n"; + // This call should retrieve the group information + // AFTER the transition has happened + int sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc)); + EXPECT_EQ(sendret, int(sizeof data)); + EXPECT_EQ(mc.grpdata_size, size_t(1)); + EXPECT_EQ(gdata[0].memberstate, SRT_GST_RUNNING); + + cout << "Connecting second link weight=1:\n"; + // Now prepare the second connection + cc[0].token = 1; + cc[0].weight = 1; // higher than the default 0 + result = srt_connect_group(ss, cc, 1); + EXPECT_GT(result, 0); // connect with only one element returns socket ID + + // Make sure both links are connected + size_t psize = 2; + size_t nwait = 10; + cout << "Waiting for getting 2 links:\n"; + while (--nwait) + { + srt_group_data(ss, gdata, &psize); + if (psize == 2) + { + int l1, l2; + l1 = gdata[0].memberstate; + l2 = gdata[1].memberstate; + + if (l1 > SRT_GST_PENDING && l2 > SRT_GST_PENDING) + { + cout << "Both up: [0]=" << l1 << " [1]=" << l2 << "\n"; + break; + } + else + { + cout << "Still link states [0]=" << l1 << " [1]=" << l2 << "\n"; + } + } + else + { + cout << "Still " << psize << endl; + } + this_thread::sleep_for(milliseconds(500)); + } + EXPECT_NE(nwait, size_t(0)); + + // Now send one packet (again) + mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + + cout << "Sending (2)\n"; + // This call should retrieve the group information + // AFTER the transition has happened + sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc)); + EXPECT_EQ(sendret, int(sizeof data)); + + // So, let's check which link is in RUNNING state + // TOKEN value is the index in cc array, and we should + // also have the weight value there. + + SRT_SOCKGROUPDATA* mane, * backup; + if (gdata[0].weight == 0) + { + backup = &gdata[0]; + mane = &gdata[1]; + } + else + { + mane = &gdata[0]; + backup = &gdata[1]; + } + + cout << "MAIN:[" << mane->token << "] weight=" << mane->weight << endl; + cout << "BACKUP:[" << backup->token << "] weight=" << backup->weight << endl; + + // Ok, now both links should be running (this state lasts + // for the "temporary activation" period. + EXPECT_EQ(mane->memberstate, SRT_GST_RUNNING); + EXPECT_EQ(backup->memberstate, SRT_GST_RUNNING); + + acthr.join(); +} + + +// TEST IDEA: +// In this test there is created a working listener socket to +// accept the connection, and we use a Backup-type group with +// two links, but different weights. We connect then two links +// both with weight=1. Then we send a packet to make sure that +// exactly one of them got activated. Then we connect another +// link with weight=0. Then we send a packet again, which should +// not change the link usage. Then we check which link was +// active so far, and we close the socket for that link to make +// it broken, then we wait for having only two links connected. +// Then a packet is sent to activate a link. We expect the link +// with higher weight is activated. +// +// TEST STEPS: +// 1. Create a listener socket. +// 2. Create and setup a group. +// 3. Start the accepting thread +// A1. accept a connection +// A2. read one packet from the accepted entity +// A3. read the second packet from the accepted entity +// A4. read the third packet from the accepted entity +// A5. close the listener socket +// 4. Prepare two connections with weight=1 and connect the group +// 5. Send a packet to enforce activation of one link +// 6. Prepare another connection with weight=0 and connect the group +// 7. Wait for having all 3 links connected. +// 8. Send a packet +// 9. Find which link is currently active and close it +// 10. Wait for having only two links. +// 11. Send a packet. +// 12. Find one link active and one idle +// 13. Check if the link with weight=1 is active and the one with weight=0 is idle. +// 14. Close the group. +// 15. Join the thread +TEST(Bonding, BackupPrioritySelection) +{ + using namespace std; + using namespace std::chrono; + using namespace srt; + + TestInit srtinit; + + g_nconnected = 0; + g_nfailed = 0; + volatile bool recvd = false; + + // 1. + sockaddr_in bind_sa; + memset(&bind_sa, 0, sizeof bind_sa); + bind_sa.sin_family = AF_INET; + ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1); + bind_sa.sin_port = htons(4200); + + g_listen_socket = srt_create_socket(); + ASSERT_NE(g_listen_socket, SRT_INVALID_SOCK); + EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1); + const int yes = 1; + srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes); + EXPECT_NE(srt_listen(g_listen_socket, 5), -1); + + // Caller part + // 2. + const int ss = srt_create_group(SRT_GTYPE_BACKUP); + EXPECT_NE(ss, SRT_ERROR); + + srt_connect_callback(ss, &ConnectCallback, this); + + // Set the group's stability timeout to 1s, otherwise it will + // declare the links unstable by not receiving ACKs. + int stabtimeo = 1000; + srt_setsockflag(ss, SRTO_GROUPMINSTABLETIMEO, &stabtimeo, sizeof stabtimeo); + + //srt_setloglevel(LOG_DEBUG); + srt::resetlogfa( std::set { + SRT_LOGFA_GRP_SEND, + SRT_LOGFA_GRP_MGMT, + SRT_LOGFA_CONN + }); + + sockaddr_in sa; + sa.sin_family = AF_INET; + sa.sin_port = htons(4200); + EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + + // 3. + auto acthr = std::thread([&recvd]() { + sockaddr_any adr; + cout << "[A1] Accepting a connection...\n"; + + // A1 + int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len); + + // Expected: group reporting + EXPECT_NE(accept_id & SRTGROUP_MASK, 0); + + SRT_SOCKGROUPDATA gdata[2]; + SRT_MSGCTRL mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + long long data[1320/8]; + + // A2 + cout << "[A2] Receiving 1...\n"; + int ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc)); + if (ds == -1) { cout << "[A2] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; } + EXPECT_EQ(ds, 8); + + // A3 + cout << "[A3] Receiving 2...\n"; + ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc)); + if (ds == -1) { cout << "[A3] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; } + EXPECT_EQ(ds, 8); + recvd = true; + + // A4 + cout << "[A4] Receiving 3...\n"; + ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc)); + if (ds == -1) { cout << "[A4] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; } + EXPECT_EQ(ds, 8); + + cout << "[A] Waiting 5s...\n"; + // To make it possible that the state is checked before it is closed. + this_thread::sleep_for(seconds(5)); + + // A5 + cout << "[A5] Closing\n"; + srt_close(accept_id); + cout << "[A] thread finished\n"; + }); + + + cout << "(4) Connecting first 2 links weight=1:\n"; + + SRT_SOCKGROUPCONFIG cc[2]; + cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[0].token = 0; + cc[0].weight = 1; + cc[1] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[1].token = 1; + cc[1].weight = 1; + + // 4. + int result = srt_connect_group(ss, cc, 2); + EXPECT_GT(result, 0); // BLOCKING MODE, always returns the socket value + + // As we have one link, after `srt_connect_group` returns, we have + // this link now connected. Send one data portion. + + SRT_SOCKGROUPDATA gdata[3]; + memset(gdata, 0, sizeof(gdata)); + + long long data = 0x1234123412341234; + SRT_MSGCTRL mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 3; + + // We can send now. We know that we have at least one + // link connected and it already has the same priority + // as the other. + + //srt_setloglevel(LOG_DEBUG); + // 5. + cout << "(5) Sending (1)\n"; + // This call should retrieve the group information + // AFTER the transition has happened + int sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc)); + // In case when this was an error, display the code + if (sendret == -1) { cout << "[A4] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; } + + EXPECT_EQ(sendret, int(sizeof data)); + + EXPECT_EQ(mc.grpdata_size, size_t(2)); + + int state0 = gdata[0].memberstate; + int state1 = gdata[1].memberstate; + + cout << "States: [0]=" << state0 << " [1]=" << state1 << endl; + EXPECT_TRUE(state0 == SRT_GST_RUNNING || state1 == SRT_GST_RUNNING); + + // 6. + cout << "(6) Connecting third link weight=0:\n"; + // Now prepare the third connection + cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[0].token = 2; + cc[0].weight = 0; // higher than the default 0 + result = srt_connect_group(ss, cc, 1); + EXPECT_GE(result, 0); // ONE connection only - will return socket id + + // Make sure all 3 links are connected + size_t psize = 3; + size_t nwait = 10; + set states; + + // 7. + cout << "(7) Waiting for getting 3 links:\n"; + while (--nwait) + { + srt_group_data(ss, gdata, &psize); + if (psize == 3) + { + states.clear(); + for (int i = 0; i < 3; ++i) + states.insert(gdata[i].memberstate); + + if (states.count(SRT_GST_PENDING)) + { + cout << "Still not all links...\n"; + } + else + { + cout << "All links up\n"; + break; + } + } + else + { + cout << "Still " << psize << endl; + } + this_thread::sleep_for(milliseconds(500)); + } + EXPECT_NE(nwait, size_t(0)); + + // Now send one packet (again) + mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 3; + + // 8. + cout << "(8) Sending (2)\n"; + // This call should retrieve the group information + // AFTER the transition has happened + sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc)); + EXPECT_EQ(sendret, int(sizeof data)); + EXPECT_EQ(mc.grpdata_size, size_t(3)); + + // So, let's check which link is in RUNNING state + // TOKEN value is the index in cc array, and we should + // also have the weight value there. + + SRT_SOCKGROUPDATA* mane = nullptr; + + for (size_t i = 0; i < mc.grpdata_size; ++i) + { + if (gdata[i].memberstate == SRT_GST_RUNNING) + { + mane = &gdata[i]; + break; + } + } + + EXPECT_NE(mane, nullptr); + EXPECT_EQ(mane->weight, 1); + + // Spin-wait for making sure the reception succeeded before + // closing. This shouldn't be a problem in general, but + int ntry = 100; + while (!recvd && --ntry) + this_thread::sleep_for(milliseconds(200)); + EXPECT_NE(ntry, 0); + + cout << "(9) Found activated link: [" << mane->token << "] - closing after 0.5s...\n"; + + // Waiting is to make sure that the listener thread has received packet 3. + this_thread::sleep_for(milliseconds(500)); + EXPECT_NE(srt_close(mane->id), -1); + + // Now expect to have only 2 links, wait for it if needed. + psize = 2; + nwait = 10; + + cout << "(10) Waiting for ONLY 2 links:\n"; + while (--nwait) + { + srt_group_data(ss, gdata, &psize); + if (psize == 2) + { + break; + } + else + { + cout << "Still " << psize << endl; + } + this_thread::sleep_for(milliseconds(500)); + } + EXPECT_NE(nwait, size_t(0)); + + // Now send one packet (again) + mc = srt_msgctrl_default; + mc.grpdata = gdata; + mc.grpdata_size = 2; + + cout << "(11) Sending (3)\n"; + // This call should retrieve the group information + // AFTER the transition has happened + sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc)); + EXPECT_EQ(sendret, int(sizeof data)); + + cout << "(sleep)\n"; + this_thread::sleep_for(seconds(1)); + + mane = nullptr; + SRT_SOCKGROUPDATA* backup = nullptr; + cout << "(12) Checking main/backup:"; + int repeat_check = 1; // 50; +CheckLinksAgain: + for (size_t i = 0; i < mc.grpdata_size; ++i) + { + cout << "[" << i << "]" << srt_logging::MemberStatusStr(gdata[i].memberstate) + << " weight=" << gdata[i].weight; + if (gdata[i].memberstate == SRT_GST_RUNNING) + { + cout << " (main) "; + mane = &gdata[i]; + } + else + { + cout << " (backup) "; + backup = &gdata[i]; + } + } + if (backup == nullptr) + { + if (--repeat_check) + { + cout << "BACKUP STILL RUNNING. AGAIN\n"; + this_thread::sleep_for(milliseconds(250)); + goto CheckLinksAgain; + } + } + cout << endl; + + EXPECT_NE(mane, nullptr); + EXPECT_NE(backup, nullptr); + EXPECT_EQ(mane->weight, 1); + EXPECT_EQ(backup->weight, 0); + + cout << "MAIN (expected active):[" << mane->token << "] weight=" << mane->weight << endl; + cout << "BACKUP (expected idle):[" << backup->token << "] weight=" << backup->weight << endl; + + // Ok, now both links should be running (this state lasts + // for the "temporary activation" period. + EXPECT_EQ(mane->memberstate, SRT_GST_RUNNING); + EXPECT_EQ(backup->memberstate, SRT_GST_IDLE); + + this_thread::sleep_for(seconds(1)); + + cout << "Closing receiver thread [A]\n"; + + acthr.join(); + + srt_close(ss); +} + + From b25263783d2abd266be71a31b2ed15e10e4795f0 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 18 Dec 2024 17:02:07 +0100 Subject: [PATCH 10/92] [core] Make field for bandwidth atomic to avoid thread clash (#3093). --- srtcore/congctl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srtcore/congctl.cpp b/srtcore/congctl.cpp index 85d4cda97d..9bc43db8b7 100644 --- a/srtcore/congctl.cpp +++ b/srtcore/congctl.cpp @@ -60,7 +60,7 @@ void SrtCongestion::Check() class LiveCC: public SrtCongestionControlBase { - int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec) + srt::sync::atomic m_llSndMaxBW; //Max bandwidth (bytes/sec) srt::sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit size_t m_zMaxPayloadSize; size_t m_zHeaderSize; From 1ec61fc04db843ead4bebcb9db90cef8fbdd96d1 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 18 Dec 2024 17:04:23 +0100 Subject: [PATCH 11/92] [core] Apply conditional to setting a macro for IPv6 on Mac (#3092). Define __APPLE_USE_RFC_3542 if not defined. --- srtcore/platform_sys.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/srtcore/platform_sys.h b/srtcore/platform_sys.h index 83763e5ea5..5dd2b928d4 100644 --- a/srtcore/platform_sys.h +++ b/srtcore/platform_sys.h @@ -68,7 +68,9 @@ // also other macros, like TARGET_OS_IOS etc. #include "TargetConditionals.h" +#ifndef __APPLE_USE_RFC_3542 #define __APPLE_USE_RFC_3542 /* IPV6_PKTINFO */ +#endif #ifdef SRT_IMPORT_TIME #include From 8a89a3abbf4d3a2f7869d535349a474607ea0214 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 24 Dec 2024 10:41:40 +0100 Subject: [PATCH 12/92] [core][tests] Clarified conditions for setting IPTOS/TTL options. Added UT (#3096). * Added requirement for IPv6 for UDP Options test. * Updated documentation. --- CMakeLists.txt | 1 + docs/API/API-socket-options.md | 4 + srtcore/channel.cpp | 143 +++++++++++++++++++++++---------- test/test_env.h | 10 +++ test/test_reuseaddr.cpp | 23 ++++++ 5 files changed, 137 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ed11ef8ec..981440189b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1526,6 +1526,7 @@ if (ENABLE_UNITTESTS AND ENABLE_CXX11) srt_add_program_dont_install(test-srt ${SOURCES_unittests}) srt_make_application(test-srt) target_include_directories(test-srt PRIVATE ${SSL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS}) + target_compile_definitions(test-srt PRIVATE "-DSRT_TEST_SYSTEM_NAME=\"${CMAKE_SYSTEM_NAME}\"") target_link_libraries( test-srt diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index 3291f6e278..fed2a5b5a1 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -614,6 +614,8 @@ See [`SRTO_INPUTBW`](#SRTO_INPUTBW). IPv4 Type of Service (see `IP_TOS` option for IP) or IPv6 Traffic Class (see `IPV6_TCLASS` of IPv6) depending on socket address family. Applies to sender only. +NOTE: This option has been tested to work correctly on Linux only. + When *getting*, the returned value is the user preset for non-connected sockets and the actual value for connected sockets. @@ -632,6 +634,8 @@ and the actual value for connected sockets. IPv4 Time To Live (see `IP_TTL` option for IP) or IPv6 unicast hops (see `IPV6_UNICAST_HOPS` for IPv6) depending on socket address family. Applies to sender only. +NOTE: This option has been tested to work correctly on Linux only. + When *getting*, the returned value is the user preset for non-connected sockets and the actual value for connected sockets. diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 9967d7daf4..0ec8f090f3 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -289,6 +289,20 @@ void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr) setUDPSockOpt(); } +static inline string fmt_opt(bool value, const string& label) +{ + string out; + out.reserve(label.size() + 2); + out = value ? "+" : "-"; + out += label; + return out; +} + +static inline string fmt_alt(bool value, const string& label, const string& unlabel) +{ + return value ? label : unlabel; +} + void srt::CChannel::setUDPSockOpt() { #if defined(SUNOS) @@ -297,13 +311,13 @@ void srt::CChannel::setUDPSockOpt() // Retrieve starting SND/RCV Buffer sizes. int startRCVBUF = 0; optSize = sizeof(startRCVBUF); - if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize)) { startRCVBUF = -1; } int startSNDBUF = 0; optSize = sizeof(startSNDBUF); - if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize)) { startSNDBUF = -1; } @@ -312,13 +326,12 @@ void srt::CChannel::setUDPSockOpt() // maximum value. // However, do not reduce the buffer size. const int maxsize = 64000; - if (0 != - ::setsockopt( - m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) + if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, + (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) { int currentRCVBUF = 0; optSize = sizeof(currentRCVBUF); - if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tRCVBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tRCVBUF, &optSize)) { currentRCVBUF = -1; } @@ -327,13 +340,12 @@ void srt::CChannel::setUDPSockOpt() ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize); } } - if (0 != - ::setsockopt( - m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) + if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, + (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) { int currentSNDBUF = 0; optSize = sizeof(currentSNDBUF); - if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tSNDBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tSNDBUF, &optSize)) { currentSNDBUF = -1; } @@ -346,13 +358,13 @@ void srt::CChannel::setUDPSockOpt() // Retrieve ending SND/RCV Buffer sizes. int endRCVBUF = 0; optSize = sizeof(endRCVBUF); - if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize)) { endRCVBUF = -1; } int endSNDBUF = 0; optSize = sizeof(endSNDBUF); - if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize)) + if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize)) { endSNDBUF = -1; } @@ -368,90 +380,137 @@ void srt::CChannel::setUDPSockOpt() #elif defined(BSD) || TARGET_OS_MAC // BSD system will fail setsockopt if the requested buffer size exceeds system maximum value int maxsize = 64000; - if (0 != ::setsockopt( + if (-1 == ::setsockopt( m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize); - if (0 != ::setsockopt( + if (-1 == ::setsockopt( m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize); #else // for other systems, if requested is greated than maximum, the maximum value will be automactally used - if ((0 != - ::setsockopt( - m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) || - (0 != ::setsockopt( - m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))) + if ((-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, + (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) + || + (-1 == ::setsockopt( m_iSocket, SOL_SOCKET, SO_SNDBUF, + (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #endif + bool is_set = false; + bool adr_unspec = false, adr_mapped = false, adr_v6 = false; + if (m_BindAddr.family() == AF_INET) + { + adr_unspec = m_BindAddr.isany(); + } + else + { + adr_unspec = IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr); + adr_mapped = IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr); + adr_v6 = true; + } + if (m_mcfg.iIpTTL != -1) { - if (m_BindAddr.family() == AF_INET) + if (!adr_v6) { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + { + LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + is_set = true; } else { // If IPv6 address is unspecified, set BOTH IP_TTL and IPV6_UNICAST_HOPS. // For specified IPv6 address, set IPV6_UNICAST_HOPS ONLY UNLESS it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || - !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + if (adr_unspec || !adr_mapped) { - if (0 != - ::setsockopt( - m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + if (-1 == ::setsockopt( m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { + LOGC(kmlog.Error, log << "setsockopt(IPV6_UNICAST_HOPS): " << SysStrError(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } + is_set = true; } // For specified IPv6 address, set IP_TTL ONLY WHEN it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + if (!is_set) // adr_mapped (because adr_unspec was handled above) { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) + if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL)) { + LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR) + << fmt_alt(adr_unspec, " (v6 unspec)", " (v6 mapped v4)")); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } + is_set = true; } } + + if (!is_set) + { + LOGC(kmlog.Error, log << "srt_setsockflag(SRTO_IPTTL): No suitable condition for adr=" << m_BindAddr.str() + << " : " << fmt_opt(adr_v6, "v6 ") << fmt_opt(adr_unspec, "unspec ") << fmt_opt(adr_mapped, "mapped")); + throw CUDTException(MJ_SETUP, MN_INVAL, 0); + } } + is_set = false; if (m_mcfg.iIpToS != -1) { if (m_BindAddr.family() == AF_INET) { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + { + LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + is_set = true; } else { // If IPv6 address is unspecified, set BOTH IP_TOS and IPV6_TCLASS. + SRT_ATR_UNUSED bool using_tclass = false; #ifdef IPV6_TCLASS + using_tclass = true; // For specified IPv6 address, set IPV6_TCLASS ONLY UNLESS it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || - !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + if (adr_unspec || !adr_mapped) { - if (0 != ::setsockopt( - m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + if (-1 == ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, + (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { + LOGC(kmlog.Error, log << "setsockopt(IPV6_TCLASS): " << SysStrError(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } + is_set = true; } #endif // For specified IPv6 address, set IP_TOS ONLY WHEN it's an IPv4-mapped-IPv6 - if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr)) + if (!is_set && (adr_unspec || adr_mapped)) { - if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) + if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS)) { + LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR) + << (adr_unspec ? " (v6 unspecified)" : " (v6 mapped v4)") + << (using_tclass ? "(fallback to IP_TOS)" : "")); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } + is_set = true; } } + + if (!is_set) + { + LOGC(kmlog.Error, log << "srt_setsockflag(SRTO_IPTOS): No suitable condition for adr=" << m_BindAddr.str() + << " : " << fmt_opt(adr_v6, "v6 ") << fmt_opt(adr_unspec, "unspec ") << fmt_opt(adr_mapped, "mapped")); + throw CUDTException(MJ_SETUP, MN_INVAL, 0); + } } + #ifdef SRT_ENABLE_BINDTODEVICE if (!m_mcfg.sBindToDevice.empty()) { @@ -461,14 +520,10 @@ void srt::CChannel::setUDPSockOpt() throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } - if (0 != ::setsockopt( - m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size())) + if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, + m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size())) { -#if ENABLE_LOGGING - char buf[255]; - const char* err = SysStrError(NET_ERROR, buf, 255); - LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << err); -#endif // ENABLE_LOGGING + LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << SysStrError(NET_ERROR)); throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); } } @@ -482,7 +537,7 @@ void srt::CChannel::setUDPSockOpt() throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #elif defined(_WIN32) u_long nonBlocking = 1; - if (0 != ioctlsocket(m_iSocket, FIONBIO, &nonBlocking)) + if (-1 == ioctlsocket(m_iSocket, FIONBIO, &nonBlocking)) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #else timeval tv; @@ -495,7 +550,7 @@ void srt::CChannel::setUDPSockOpt() tv.tv_usec = 100; #endif // Set receiving time-out value - if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval))) + if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval))) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #endif diff --git a/test/test_env.h b/test/test_env.h index b425fa8088..5ca1a536e4 100644 --- a/test/test_env.h +++ b/test/test_env.h @@ -40,6 +40,16 @@ class TestEnv: public testing::Environment // All must be static, return bool. Arguments allowed. // The name must start with Allowed_. static bool Allowed_IPv6(); + + template + static bool Allowed_Platform(const std::string& first, const Args&... follow) + { + if (first == SRT_TEST_SYSTEM_NAME) + return true; + return Allowed_Platform(follow...); + } + + static bool Allowed_Platform() { return false; } }; #define SRTST_REQUIRES(feature,...) if (!srt::TestEnv::Allowed_##feature(__VA_ARGS__)) { return; } diff --git a/test/test_reuseaddr.cpp b/test/test_reuseaddr.cpp index df4aec4879..14789f897f 100644 --- a/test/test_reuseaddr.cpp +++ b/test/test_reuseaddr.cpp @@ -531,6 +531,29 @@ TEST_F(ReuseAddr, DiffAddr) shutdownListener(bindsock_2); } +TEST_F(ReuseAddr, UDPOptions) +{ + // IP_TOS and IP_TTL don't work on Windows and Mac + SRTST_REQUIRES(Platform, "Linux", "GNU"); + + // Travis doesn't work with IPv6 + SRTST_REQUIRES(IPv6); + + MAKE_UNIQUE_SOCK(bs1, "general ipv6", prepareServerSocket()); + MAKE_UNIQUE_SOCK(bs2, "mapped ipv4", prepareServerSocket()); + + int val_TOS = 4; // IPTOS_RELIABILITY per , but not available on Windows + int val_TTL = 10; + + EXPECT_NE(srt_setsockflag(bs1, SRTO_IPTOS, &val_TOS, sizeof val_TOS), SRT_ERROR); + EXPECT_NE(srt_setsockflag(bs1, SRTO_IPTTL, &val_TTL, sizeof val_TTL), SRT_ERROR); + EXPECT_NE(srt_setsockflag(bs2, SRTO_IPTOS, &val_TOS, sizeof val_TOS), SRT_ERROR); + EXPECT_NE(srt_setsockflag(bs2, SRTO_IPTTL, &val_TTL, sizeof val_TTL), SRT_ERROR); + + bindSocket(bs1, "::1", 5000, true); + bindSocket(bs2, "::FFFF:127.0.0.1", 5001, true); +} + TEST_F(ReuseAddr, Wildcard) { #if defined(_WIN32) || defined(CYGWIN) From a6b995939ab31a314b0f4156f4c55ee9e28116fe Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 20 Jan 2025 10:30:53 +0100 Subject: [PATCH 13/92] [core] Fixed misleading error message for listening socket (#3102). --- scripts/generate-error-types.tcl | 10 +++++----- srtcore/strerror_defs.cpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/generate-error-types.tcl b/scripts/generate-error-types.tcl index e4d29c6233..a24fd62168 100755 --- a/scripts/generate-error-types.tcl +++ b/scripts/generate-error-types.tcl @@ -109,17 +109,17 @@ set errortypes { NOTSUP "Operation not supported" { NONE "" ISBOUND "Cannot do this operation on a BOUND socket" - ISCONNECTED "Cannot do this operation on a CONNECTED socket" + ISCONNECTED "Cannot do this operation on a CONNECTED or LISTENING socket" INVAL "Bad parameters" SIDINVAL "Invalid socket ID" ISUNBOUND "Cannot do this operation on an UNBOUND socket" NOLISTEN "Socket is not in listening state" ISRENDEZVOUS "Listen/accept is not supported in rendezvous connection setup" ISRENDUNBOUND "Cannot call connect on UNBOUND socket in rendezvous connection setup" - INVALMSGAPI "Incorrect use of Message API (sendmsg/recvmsg)." - INVALBUFFERAPI "Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)." + INVALMSGAPI "Incorrect use of Message API (sendmsg/recvmsg)" + INVALBUFFERAPI "Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)" BUSY "Another socket is already listening on the same port" - XSIZE "Message is too large to send (it must be less than the SRT send buffer size)" + XSIZE "Message too large to send (exceeds send buffer size)" EIDINVAL "Invalid epoll ID" EEMPTY "All sockets removed from epoll, waiting would deadlock" BUSYPORT "Another socket is bound to that port and is not reusable for requested settings" @@ -169,7 +169,7 @@ const char* strerror_get_message(size_t major, size_t minor) } const char** array = strerror_array_major[major]; - size_t size = strerror_array_sizes[major]; + const size_t size = strerror_array_sizes[major]; if (minor >= size) { diff --git a/srtcore/strerror_defs.cpp b/srtcore/strerror_defs.cpp index e99c8e2778..4a93d6a2fe 100644 --- a/srtcore/strerror_defs.cpp +++ b/srtcore/strerror_defs.cpp @@ -66,7 +66,7 @@ const char* strerror_msgs_filesystem [] = { const char* strerror_msgs_notsup [] = { "Operation not supported", // MN_NONE = 0 "Operation not supported: Cannot do this operation on a BOUND socket", // MN_ISBOUND = 1 - "Operation not supported: Cannot do this operation on a CONNECTED socket", // MN_ISCONNECTED = 2 + "Operation not supported: Cannot do this operation on a CONNECTED or LISTENING socket", // MN_ISCONNECTED = 2 "Operation not supported: Bad parameters", // MN_INVAL = 3 "Operation not supported: Invalid socket ID", // MN_SIDINVAL = 4 "Operation not supported: Cannot do this operation on an UNBOUND socket", // MN_ISUNBOUND = 5 @@ -76,7 +76,7 @@ const char* strerror_msgs_notsup [] = { "Operation not supported: Incorrect use of Message API (sendmsg/recvmsg)", // MN_INVALMSGAPI = 9 "Operation not supported: Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)", // MN_INVALBUFFERAPI = 10 "Operation not supported: Another socket is already listening on the same port", // MN_BUSY = 11 - "Operation not supported: Message is too large to send", // MN_XSIZE = 12 + "Operation not supported: Message too large to send (exceeds send buffer size)", // MN_XSIZE = 12 "Operation not supported: Invalid epoll ID", // MN_EIDINVAL = 13 "Operation not supported: All sockets removed from epoll, waiting would deadlock", // MN_EEMPTY = 14 "Operation not supported: Another socket is bound to that port and is not reusable for requested settings", // MN_BUSYPORT = 15 From 5cd2414fee01f6dbca167e0c63bbf28ad9b4eccd Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Tue, 14 Jan 2025 10:41:14 +0100 Subject: [PATCH 14/92] [core] Fix stalled connection that should break after rogue NAK/ACK reception --- scripts/srt-proxy.py | 168 +++++++++++++++++++++++++++++++++++++++++++ srtcore/core.cpp | 44 +++++++----- 2 files changed, 194 insertions(+), 18 deletions(-) create mode 100755 scripts/srt-proxy.py diff --git a/scripts/srt-proxy.py b/scripts/srt-proxy.py new file mode 100755 index 0000000000..ad2e8ac347 --- /dev/null +++ b/scripts/srt-proxy.py @@ -0,0 +1,168 @@ +#!/usr/bin/python3 + +# This script is a testing tool that inserts a packet interceptor for +# a bond between two local ports and modify a packet that is sent over +# the UDP link used by the SRT connection. + +# Provided by: +# https://github.com/FelixSodermanNeti + +import socket, select, argparse + +class UDPProxy: + def __init__(self, caller_host, caller_port, listener_host, listener_port, break_at_pkt_NAK, break_at_pkt_ACK): + + # Listening socket configuration + self.caller_host = caller_host + self.caller_port = caller_port + self.caller_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.caller_socket.bind((self.caller_host, self.caller_port)) + + # Forwarding destination configuration + self.listener_host = listener_host + self.listener_port = listener_port + + # Client address tracking + self.client_address = None + + # Proxy state + self.running = False + + # Break SRT + self.sentPacketCounter = 0 + self.recivedAckCounter = 0 + self.PACKETBREAK_NAK = break_at_pkt_NAK + self.PACKETBREAK_ACK = break_at_pkt_ACK + + def start(self): + try: + self.running = True + print( + f"UDP Proxy started. Listening on {self.caller_host}:{self.caller_port}. " + f"Forwarding to {self.listener_host}:{self.listener_port}" + ) + + # Create forward socket + listener_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + # Use select for non-blocking I/O + while self.running: + readable, _, _ = select.select([self.caller_socket, listener_socket], [], [], 1) + + for sock in readable: + if sock == self.caller_socket: + # Receive from client + data, addr = sock.recvfrom(65535) + self.client_address = addr + + #Modify the NAK + processedData = data + # NAK + if(data[1] == 0x03 and len(data) == 24 and self.PACKETBREAK_NAK != 0): # IS A NAK, This should check for a 1 in the first byte aswell. + print("Modifying NAK.") + newBytes = b"\xff\xff\xff\xff" + processedData = data[:20] + newBytes + data[20 + len(newBytes):] + """ + print("NAK BEFORE:") + print(string_bytearray_in_rows(data=data)) + print("\n\nModifying NAK.\n\n") + print("ŃAK AFTER:") + print(string_bytearray_in_rows(data=processedData)) + """ + + # ACK + if(data[1] == 0x02 and len(data) == 44): # IS A ACK + self.recivedAckCounter += 1 + if (self.PACKETBREAK_ACK != 0 and self.recivedAckCounter == self.PACKETBREAK_ACK): + print("Modifying ACK.") + newBytes = b"\x7f\xff\xff\xff" + offset = 16 + processedData = data[:offset] + newBytes + data[offset + len(newBytes):] + """ + print("ACK BEFORE:") + print(string_bytearray_in_rows(data=data)) + print("\n\nModifying ACK.\n\n") + print("ACK AFTER:") + print(string_bytearray_in_rows(data=processedData)) + """ + listener_socket.sendto( + processedData, + (self.listener_host, self.listener_port) + ) + + elif sock == listener_socket: + # Receive from server + data, addr = sock.recvfrom(65535) + + # Drop a sequence of data packets (as to atleast 3 to trigger an NAK with a range) + if len(data) == 1332: # Packet with data + self.sentPacketCounter += 1 + if (self.PACKETBREAK_NAK != 0 and self.sentPacketCounter in range(self.PACKETBREAK_NAK, self.PACKETBREAK_NAK+5)): + print("Discarding packet.") + continue + + if not self.client_address: + print("No client to send data to!") + continue + + self.caller_socket.sendto( + data, + self.client_address + ) + + except Exception as e: + print(f"Proxy error: {e}") + + finally: + self.stop() + + def stop(self): + self.running = False + self.caller_socket.close() + print("UDP Proxy stopped.") + + +def string_bytearray_in_rows(data, bytes_per_row=4): + tmpString = "\n" + for i in range(0, len(data), bytes_per_row): + row = data[i:i+bytes_per_row] + tmpString += f"{i}: " + tmpString += (" ".join(f"{byte:02x}" for byte in row)) + tmpString += "\n" + tmpString += "\n" + return tmpString + +def main(): + # Parse command-line arguments + parser = argparse.ArgumentParser(description='UDP Proxy') + parser.add_argument('--caller-host', default='127.0.0.1', + help='Host to listen on (srt caller) (default: 127.0.0.1)') + parser.add_argument('--caller-port', type=int, required=True, + help='Port to listen on (srt caller)') + parser.add_argument('--listener-host', default='127.0.0.1', + help='Destination host to forward packets (srt listener)') + parser.add_argument('--listener-port', type=int, required=True, + help='Destination port to forward packets (srt listener)') + parser.add_argument('--break-at-pkt-NAK', type=int, default=0, + help='At what datapacket should the NAK -1 be sent (0=never)') + parser.add_argument('--break-at-pkt-ACK', type=int, default=0, + help='At what datapacket should the ACK -1 be sent (0=never)') + args = parser.parse_args() + + # Create and start proxy + proxy = UDPProxy( + args.caller_host, + args.caller_port, + args.listener_host, + args.listener_port, + args.break_at_pkt_NAK, + args.break_at_pkt_ACK + ) + + try: + proxy.start() + except KeyboardInterrupt: + proxy.stop() + +if __name__ == '__main__': + main() diff --git a/srtcore/core.cpp b/srtcore/core.cpp index eca2b20699..dc1e2d118b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -8483,39 +8483,44 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ // Protect packet retransmission { - ScopedLock ack_lock(m_RecvAckLock); + UniqueLock ack_lock(m_RecvAckLock); // Check the validation of the ack if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) { + ack_lock.unlock(); + // this should not happen: attack or bug LOGC(gglog.Error, log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current " << m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!"); m_bBroken = true; m_iBrokenCounter = 0; + + updateBrokenConnection(); + completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! return; } - if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) - { - const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow); - const bool bWasStuck = cwnd1<= getFlightSpan(); - // Update Flow Window Size, must update before and together with m_iSndLastAck - m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; - m_iSndLastAck = ackdata_seqno; - m_tsLastRspAckTime = currtime; - m_iReXmitCount = 1; // Reset re-transmit count since last ACK - - const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); - if (bWasStuck && cwnd > getFlightSpan()) + if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) { - m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); - HLOGC(gglog.Debug, - log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize - << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); + const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow); + const bool bWasStuck = cwnd1<= getFlightSpan(); + // Update Flow Window Size, must update before and together with m_iSndLastAck + m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ackdata_seqno; + m_tsLastRspAckTime = currtime; + m_iReXmitCount = 1; // Reset re-transmit count since last ACK + + const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow); + if (bWasStuck && cwnd > getFlightSpan()) + { + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + HLOGC(gglog.Debug, + log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize + << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); + } } - } /* * We must not ignore full ack received by peer @@ -8924,6 +8929,9 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // this should not happen: attack or bug m_bBroken = true; m_iBrokenCounter = 0; + + updateBrokenConnection(); + completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! return; } From bc8a084d484c84204f9d7bb378777fb794208385 Mon Sep 17 00:00:00 2001 From: Thierry Lelegard Date: Thu, 13 Feb 2025 11:07:06 +0100 Subject: [PATCH 15/92] [build] Added support for Windows on Arm64 (#3110). When building the libsrt binary installer for Windows, also build the libraries for Arm64 target, in addition to Win32 and Win64. The build can be performed anywhere, Visual Studio can build for distinct targets. Before the build on Intel, make sure that your VS installation includes the build tools for Arm64. See scripts/win-installer/README.md for more details. On existing build systems, be sure to run install-openssl.ps1 once to make sure that the Arm64 libraries for OpenSSL are installed. Also fixed a few harmless warnings. --- scripts/win-installer/README.md | 13 +++-- scripts/win-installer/build-win-installer.ps1 | 49 ++++++++++++------- scripts/win-installer/install-openssl.ps1 | 8 +-- scripts/win-installer/libsrt.nsi | 46 +++++++++++------ srtcore/window.cpp | 6 +-- 5 files changed, 78 insertions(+), 44 deletions(-) diff --git a/scripts/win-installer/README.md b/scripts/win-installer/README.md index ff54df2588..006b8a6bce 100644 --- a/scripts/win-installer/README.md +++ b/scripts/win-installer/README.md @@ -7,12 +7,19 @@ libsrt on Windows systems for Visual Studio applications using SRT. ### Prerequisites -These first two steps need to be executed once only. +These initial steps need to be executed once only. -- Prerequisite 1: Install OpenSSL for Windows, both 64 and 32 bits. +- Prerequisite 1: Install Visual Studio. The free Community Edition is recommended + for open-source software. See https://visualstudio.microsoft.com/. Be sure to + install the compilation tools for the all target architectures, including 64-bit Arm. + In the Visual Studio Installer program, select "Modify" -> "Individual Components". + In section "Compilers, build tools, and runtimes", select "MSVC v143 - VS2022 C++ + ARM64/ARM64EC build tools (latest)" (the exact version may vary). + +- Prerequisite 2: Install OpenSSL for Windows, 64-bit Intel, 32-bit Intel, 64-bit Arm. This can be done automatically by running the PowerShell script `install-openssl.ps1`. -- Prerequisite 2: Install NSIS, the NullSoft Installation Scripting system. +- Prerequisite 3: Install NSIS, the NullSoft Installation Scripting system. This can be done automatically by running the PowerShell script `install-nsis.ps1`. ### Building the libsrt installer diff --git a/scripts/win-installer/build-win-installer.ps1 b/scripts/win-installer/build-win-installer.ps1 index d08ee186c2..9e9ae7940c 100644 --- a/scripts/win-installer/build-win-installer.ps1 +++ b/scripts/win-installer/build-win-installer.ps1 @@ -102,12 +102,17 @@ $SSL = @{ "bits" = 32; "root" = "C:\Program Files (x86)\OpenSSL-Win32" } + "ARM64" = @{ + "alt" = "arm64"; + "bits" = 64; + "root" = "C:\Program Files\OpenSSL-Win64-ARM" + }; } # Verify OpenSSL directories and static libraries. Write-Output "Searching OpenSSL libraries ..." $Missing = 0 -foreach ($arch in @("x64", "Win32")) { +foreach ($arch in $SSL.Keys) { $root = $SSL[$arch]["root"] $bits = $SSL[$arch]["bits"] $alt = $SSL[$arch]["alt"] @@ -125,7 +130,6 @@ foreach ($arch in @("x64", "Win32")) { "$root\lib\VC\${alt}\${conf}\lib${lib}_static.lib")) { if (Test-Path $try) { $SSL[$arch][$name] = $try - New-Variable "lib${lib}${bits}${conf}" "$try" break } } @@ -180,10 +184,10 @@ Write-Output "NSIS: $NSIS" #----------------------------------------------------------------------------- -# Configure and build SRT library using CMake on two architectures. +# Configure and build SRT library using CMake on all architectures. #----------------------------------------------------------------------------- -foreach ($Platform in @("x64", "Win32")) { +foreach ($Platform in $SSL.Keys) { # Build directory. Cleanup to force a fresh cmake config. $BuildDir = "$TmpDir\build.$Platform" @@ -191,13 +195,18 @@ foreach ($Platform in @("x64", "Win32")) { [void](New-Item -Path $BuildDir -ItemType Directory -Force) # Run CMake. + # Note: In previous versions of CMake, it was necessary to specify where + # OpenSSL was located using OPENSSL_ROOT_DIR, OPENSSL_LIBRARIES, and + # OPENSSL_INCLUDE_DIR. Starting with CMake 3.29.0, this is no longer + # necessary because CMake knows where to find the OpenSSL binaries from + # slproweb. Additionally, for some unkown reason, defining the OPENSSL_xxx + # variables no longer works with Arm64 libraries, even though the path to + # that version is correct. So, it's better to let CMake find OpenSSL by itself. Write-Output "Configuring build for platform $Platform ..." $SRoot = $SSL[$Platform]["root"] - & $CMake -S $RepoDir -B $BuildDir -A $Platform ` - -DENABLE_STDCXX_SYNC=ON ` - -DOPENSSL_ROOT_DIR="$SRoot" ` - -DOPENSSL_LIBRARIES="$SRoot\lib\libssl_static.lib;$SRoot\lib\libcrypto_static.lib" ` - -DOPENSSL_INCLUDE_DIR="$SRoot\include" + $LibSSL = $SSL[$Platform]["libsslMD"] + $LibCrypto = $SSL[$Platform]["libcryptoMD"] + & $CMake -S $RepoDir -B $BuildDir -A $Platform -DENABLE_STDCXX_SYNC=ON # Patch version string in version.h Get-Content "$BuildDir\version.h" | @@ -218,7 +227,7 @@ foreach ($Platform in @("x64", "Win32")) { Write-Output "Checking compiled libraries ..." $Missing = 0 foreach ($Conf in @("Release", "Debug")) { - foreach ($Platform in @("x64", "Win32")) { + foreach ($Platform in $SSL.Keys) { $Path = "$TmpDir\build.$Platform\$Conf\srt_static.lib" if (-not (Test-Path $Path)) { Write-Output "**** Missing $Path" @@ -245,14 +254,18 @@ Write-Output "Building installer ..." /DOutDir="$OutDir" ` /DBuildRoot="$TmpDir" ` /DRepoDir="$RepoDir" ` - /Dlibssl32MD="$libssl32MD" ` - /Dlibssl32MDd="$libssl32MDd" ` - /Dlibcrypto32MD="$libcrypto32MD" ` - /Dlibcrypto32MDd="$libcrypto32MDd" ` - /Dlibssl64MD="$libssl64MD" ` - /Dlibssl64MDd="$libssl64MDd" ` - /Dlibcrypto64MD="$libcrypto64MD" ` - /Dlibcrypto64MDd="$libcrypto64MDd" ` + /DlibsslWin32MD="$($SSL.Win32.libsslMD)" ` + /DlibsslWin32MDd="$($SSL.Win32.libsslMDd)" ` + /DlibcryptoWin32MD="$($SSL.Win32.libcryptoMD)" ` + /DlibcryptoWin32MDd="$($SSL.Win32.libcryptoMDd)" ` + /DlibsslWin64MD="$($SSL.x64.libsslMD)" ` + /DlibsslWin64MDd="$($SSL.x64.libsslMDd)" ` + /DlibcryptoWin64MD="$($SSL.x64.libcryptoMD)" ` + /DlibcryptoWin64MDd="$($SSL.x64.libcryptoMDd)" ` + /DlibsslArm64MD="$($SSL.ARM64.libsslMD)" ` + /DlibsslArm64MDd="$($SSL.ARM64.libsslMDd)" ` + /DlibcryptoArm64MD="$($SSL.ARM64.libcryptoMD)" ` + /DlibcryptoArm64MDd="$($SSL.ARM64.libcryptoMDd)" ` "$ScriptDir\libsrt.nsi" if (-not (Test-Path $InstallExe)) { diff --git a/scripts/win-installer/install-openssl.ps1 b/scripts/win-installer/install-openssl.ps1 index e24cecc570..3352584b89 100644 --- a/scripts/win-installer/install-openssl.ps1 +++ b/scripts/win-installer/install-openssl.ps1 @@ -101,19 +101,19 @@ if ($status -ne 1 -and $status -ne 2) { } $config = ConvertFrom-Json $Response.Content -# Download and install MSI packages for 32 and 64 bit. -foreach ($bits in @(32, 64)) { +# Download and install MSI packages for 32 and 64-bit Intel, 64-bit Arm. +foreach ($conf in @(@{arch="intel"; bits=32}, @{arch="intel"; bits=64}, @{arch="arm"; bits=64})) { # Get the URL of the MSI installer from the JSON config. $Url = $config.files | Get-Member | ForEach-Object { $name = $_.name $info = $config.files.$($_.name) - if (-not $info.light -and $info.installer -like "msi" -and $info.bits -eq $bits -and $info.arch -like "intel") { + if (-not $info.light -and $info.installer -like "msi" -and $info.bits -eq $conf.bits -and $info.arch -like $conf.arch) { $info.url } } | Select-Object -Last 1 if (-not $Url) { - Exit-Script "#### No MSI installer found for Win${bits}" + Exit-Script "#### No MSI installer found for $($conf.bits)-bit $($conf.arch)" } $MsiName = (Split-Path -Leaf $Url) diff --git a/scripts/win-installer/libsrt.nsi b/scripts/win-installer/libsrt.nsi index 022ceb617f..a3fff4451e 100644 --- a/scripts/win-installer/libsrt.nsi +++ b/scripts/win-installer/libsrt.nsi @@ -28,9 +28,10 @@ Caption "SRT Libraries Installer" !include "x64.nsh" !verbose pop -!define ProductName "libsrt" -!define Build32Dir "${BuildRoot}\build.Win32" -!define Build64Dir "${BuildRoot}\build.x64" +!define ProductName "libsrt" +!define BuildWin32Dir "${BuildRoot}\build.Win32" +!define BuildWin64Dir "${BuildRoot}\build.x64" +!define BuildArm64Dir "${BuildRoot}\build.ARM64" ; Installer file information. VIProductVersion ${VersionInfo} @@ -93,6 +94,7 @@ functionEnd function un.onInit ; In 64-bit installers, don't use registry redirection. ${If} ${RunningX64} + ${OrIf} ${IsNativeARM64} SetRegView 64 ${EndIf} functionEnd @@ -128,7 +130,7 @@ Section "Install" File "${RepoDir}\srtcore\platform_sys.h" File "${RepoDir}\srtcore\srt.h" File "${RepoDir}\srtcore\udt.h" - File "${Build64Dir}\version.h" + File "${BuildWin64Dir}\version.h" CreateDirectory "$INSTDIR\include\win" SetOutPath "$INSTDIR\include\win" @@ -139,27 +141,39 @@ Section "Install" CreateDirectory "$INSTDIR\lib\Release-x64" SetOutPath "$INSTDIR\lib\Release-x64" - File /oname=srt.lib "${Build64Dir}\Release\srt_static.lib" - File /oname=libcrypto.lib "${libcrypto64MD}" - File /oname=libssl.lib "${libssl64MD}" + File /oname=srt.lib "${BuildWin64Dir}\Release\srt_static.lib" + File /oname=libcrypto.lib "${libcryptoWin64MD}" + File /oname=libssl.lib "${libsslWin64MD}" CreateDirectory "$INSTDIR\lib\Debug-x64" SetOutPath "$INSTDIR\lib\Debug-x64" - File /oname=srt.lib "${Build64Dir}\Debug\srt_static.lib" - File /oname=libcrypto.lib "${libcrypto64MDd}" - File /oname=libssl.lib "${libssl64MDd}" + File /oname=srt.lib "${BuildWin64Dir}\Debug\srt_static.lib" + File /oname=libcrypto.lib "${libcryptoWin64MDd}" + File /oname=libssl.lib "${libsslWin64MDd}" CreateDirectory "$INSTDIR\lib\Release-Win32" SetOutPath "$INSTDIR\lib\Release-Win32" - File /oname=srt.lib "${Build32Dir}\Release\srt_static.lib" - File /oname=libcrypto.lib "${libcrypto32MD}" - File /oname=libssl.lib "${libssl32MD}" + File /oname=srt.lib "${BuildWin32Dir}\Release\srt_static.lib" + File /oname=libcrypto.lib "${libcryptoWin32MD}" + File /oname=libssl.lib "${libsslWin32MD}" CreateDirectory "$INSTDIR\lib\Debug-Win32" SetOutPath "$INSTDIR\lib\Debug-Win32" - File /oname=srt.lib "${Build32Dir}\Debug\srt_static.lib" - File /oname=libcrypto.lib "${libcrypto32MDd}" - File /oname=libssl.lib "${libssl32MDd}" + File /oname=srt.lib "${BuildWin32Dir}\Debug\srt_static.lib" + File /oname=libcrypto.lib "${libcryptoWin32MDd}" + File /oname=libssl.lib "${libsslWin32MDd}" + + CreateDirectory "$INSTDIR\lib\Release-Arm64" + SetOutPath "$INSTDIR\lib\Release-Arm64" + File /oname=srt.lib "${BuildArm64Dir}\Release\srt_static.lib" + File /oname=libcrypto.lib "${libcryptoArm64MD}" + File /oname=libssl.lib "${libsslArm64MD}" + + CreateDirectory "$INSTDIR\lib\Debug-Arm64" + SetOutPath "$INSTDIR\lib\Debug-Arm64" + File /oname=srt.lib "${BuildArm64Dir}\Debug\srt_static.lib" + File /oname=libcrypto.lib "${libcryptoArm64MDd}" + File /oname=libssl.lib "${libsslArm64MDd}" ; Add an environment variable to installation root. WriteRegStr HKLM ${EnvironmentKey} "LIBSRT" "$INSTDIR" diff --git a/srtcore/window.cpp b/srtcore/window.cpp index 1950f7e166..5deb1d097b 100644 --- a/srtcore/window.cpp +++ b/srtcore/window.cpp @@ -154,13 +154,13 @@ void srt::CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_p r_probeWindow[k] = 1000; //1 msec -> 1000 pkts/sec for (size_t i = 0; i < asize; ++ i) - r_bytesWindow[i] = max_payload_size; //based on 1 pkt/sec set in r_pktWindow[i] + r_bytesWindow[i] = int(max_payload_size); //based on 1 pkt/sec set in r_pktWindow[i] } int srt::CPktTimeWindowTools::ceilPerMega(double value, double count) { static const double MEGA = 1000.0 * 1000.0; - return ::ceil(MEGA / (value / count)); + return int(::ceil(MEGA / (value / count))); } int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, size_t asize, size_t hdr_size, int& w_bytesps) @@ -183,7 +183,7 @@ int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, return 0; } - bytes += (hdr_size * count); //Add protocol headers to bytes received + bytes += (unsigned long)(hdr_size * count); //Add protocol headers to bytes received w_bytesps = ceilPerMega(sum, bytes); return ceilPerMega(sum, count); } From ce0a888a005e2ec515b18ef09bddac5bd5ca9c3e Mon Sep 17 00:00:00 2001 From: cl-ment Date: Thu, 13 Feb 2025 12:05:50 +0100 Subject: [PATCH 16/92] [apps] FIX srt-live-transmit stops listening after SRT disconnect #2997... (#3108) * [apps] Fix srt-live-transmit stops listening after SRT disconnects during no active input on console #2997 (#3108) --------- Co-authored-by: Clement Gerouville Co-authored-by: Maxim Sharabayko --- apps/srt-live-transmit.cpp | 45 +++++++++++++++++++++++++++++++------- apps/transmitbase.hpp | 1 + apps/transmitmedia.cpp | 20 +++++++++++------ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index 0a918e6b06..932bcd1423 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -514,6 +514,7 @@ int main(int argc, char** argv) return 1; } int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; + switch (src->uri.type()) { case UriParser::SRT: @@ -537,14 +538,18 @@ int main(int argc, char** argv) } break; case UriParser::FILE: - if (srt_epoll_add_ssock(pollid, - src->GetSysSocket(), &events)) { - cerr << "Failed to add FILE source to poll, " - << src->GetSysSocket() << endl; - return 1; + const int con = src->GetSysSocket(); + // try to make the standard input non blocking + if (srt_epoll_add_ssock(pollid, con, &events)) + { + cerr << "Failed to add FILE source to poll, " + << src->GetSysSocket() << endl; + return 1; + } + break; + } - break; default: break; } @@ -589,6 +594,7 @@ int main(int argc, char** argv) SRTSOCKET srtrwfds[4] = {SRT_INVALID_SOCK, SRT_INVALID_SOCK , SRT_INVALID_SOCK , SRT_INVALID_SOCK }; int sysrfdslen = 2; SYSSOCKET sysrfds[2]; + if (srt_epoll_wait(pollid, &srtrwfds[0], &srtrfdslen, &srtrwfds[2], &srtwfdslen, 100, @@ -771,12 +777,34 @@ int main(int argc, char** argv) break; } + + bool srcReady = false; + + if (src.get() && src->IsOpen() && !src->End()) + { + if (srtrfdslen > 0) + { + SRTSOCKET sock = src->GetSRTSocket(); + if (sock != SRT_INVALID_SOCK) + { + for (int n = 0; n < srtrfdslen && !(srcReady = (sock == srtrwfds[n])); n ++); + } + } + if (!srcReady && sysrfdslen > 0) + { + int sock = src->GetSysSocket(); + if (sock != -1) + { + for (int n = 0; n < sysrfdslen && !(srcReady = (sock == sysrfds[n])); n++); + } + } + } // read a few chunks at a time in attempt to deplete // read buffers as much as possible on each read event // note that this implies live streams and does not // work for cached/file sources std::list> dataqueue; - if (src.get() && src->IsOpen() && (srtrfdslen || sysrfdslen)) + if (srcReady) { while (dataqueue.size() < cfg.buffering) { @@ -800,9 +828,10 @@ int main(int argc, char** argv) dataqueue.push_back(pkt); receivedBytes += pkt->payload.size(); + if (src->MayBlock()) + break; } } - // if there is no target, let the received data be lost while (!dataqueue.empty()) { diff --git a/apps/transmitbase.hpp b/apps/transmitbase.hpp index bd0af916db..7451c59b78 100644 --- a/apps/transmitbase.hpp +++ b/apps/transmitbase.hpp @@ -71,6 +71,7 @@ class Source: public Location virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; } virtual int GetSysSocket() const { return -1; } + virtual bool MayBlock() const { return false; } virtual bool AcceptNewClient() { return false; } }; diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 2752951734..8e535cf07a 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -704,9 +704,15 @@ Iface* CreateSrt(const string& host, int port, const map& par) { class ConsoleSource: public Source { + const bool may_block = true; public: ConsoleSource() +#ifdef _WIN32 + : may_block(true) +#else + : may_block(fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL) | O_NONBLOCK) < 0) +#endif { #ifdef _WIN32 // The default stdin mode on windows is text. @@ -720,9 +726,8 @@ class ConsoleSource: public Source if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); - bool st = cin.read(pkt.payload.data(), chunk).good(); - chunk = cin.gcount(); - if (chunk == 0 || !st) + const int ret = ::read(GetSysSocket(), pkt.payload.data(), chunk); + if (ret <= 0) { pkt.payload.clear(); return 0; @@ -731,14 +736,15 @@ class ConsoleSource: public Source // Save this time to potentially use it for SRT target. pkt.time = srt_time_now(); if (chunk < pkt.payload.size()) - pkt.payload.resize(chunk); + pkt.payload.resize(ret); - return (int) chunk; + return ret; } bool IsOpen() override { return cin.good(); } + bool MayBlock() const final { return may_block; } bool End() override { return cin.eof(); } - int GetSysSocket() const override { return 0; }; + int GetSysSocket() const override { return fileno(stdin); }; }; class ConsoleTarget: public Target @@ -767,7 +773,7 @@ class ConsoleTarget: public Target bool IsOpen() override { return cout.good(); } bool Broken() override { return cout.eof(); } - int GetSysSocket() const override { return 0; }; + int GetSysSocket() const override { return fileno(stdout); }; }; template struct Console; From d69b065c6bc27e9b23714fc497dbfe03486d19ac Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Tue, 18 Feb 2025 11:01:37 +0100 Subject: [PATCH 17/92] [core] Fixed socket options that are not allowed to be set on a group (#3089). Co-authored-by: Sektor van Skijlen --- docs/API/API-socket-options.md | 6 +++--- srtcore/group.cpp | 34 ++++++++++++++++++++++++++++++++-- srtcore/srt.h | 2 +- test/test_bonding.cpp | 10 +++++----- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index fed2a5b5a1..d74907df11 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -201,10 +201,10 @@ The following table lists SRT API socket options in alphabetical order. Option d | Option Name | Since | Restrict | Type | Units | Default | Range | Dir |Entity | | :------------------------------------------------------ | :---: | :------: | :-------: | :-----: | :---------------: | :------: |:---:|:-----:| -| [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | | | RW | GSD+ | +| [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | | | RW | S | | [`SRTO_CONGESTION`](#SRTO_CONGESTION) | 1.3.0 | pre | `string` | | "live" | \* | W | S | | [`SRTO_CONNTIMEO`](#SRTO_CONNTIMEO) | 1.1.2 | pre | `int32_t` | ms | 3000 | 0.. | W | GSD+ | -| [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE) | 1.5.2 | pre | `int32_t` | | 0 (Auto) | [0, 2] | W | GSD | +| [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE) | 1.5.2 | pre | `int32_t` | | 0 (Auto) | [0, 2] | W | GSD | | [`SRTO_DRIFTTRACER`](#SRTO_DRIFTTRACER) | 1.4.2 | post | `bool` | | true | | RW | GSD | | [`SRTO_ENFORCEDENCRYPTION`](#SRTO_ENFORCEDENCRYPTION) | 1.3.2 | pre | `bool` | | true | | W | GSD | | [`SRTO_EVENT`](#SRTO_EVENT) | | | `int32_t` | flags | | | R | S | @@ -269,7 +269,7 @@ The following table lists SRT API socket options in alphabetical order. Option d | OptName | Since | Restrict | Type | Units | Default | Range | Dir |Entity| | --------------------- | ----- | -------- | -------- | ------ | -------- | ------ |-----|------| -| `SRTO_BINDTODEVICE` | 1.4.2 | pre-bind | `string` | | | | RW | GSD+ | +| `SRTO_BINDTODEVICE` | 1.4.2 | pre-bind | `string` | | | | RW | S | Refers to the `SO_BINDTODEVICE` system socket option for `SOL_SOCKET` level. This effectively limits the packets received by this socket to only those diff --git a/srtcore/group.cpp b/srtcore/group.cpp index fd4e0ba0dd..2c758396e1 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -347,6 +347,36 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) switch (optName) { + // First go options that are NOT ALLOWED to be modified on the group. + // (socket-only), or are read-only. + + case SRTO_ISN: // read-only + case SRTO_STATE: // read-only + case SRTO_EVENT: // read-only + case SRTO_SNDDATA: // read-only + case SRTO_RCVDATA: // read-only + case SRTO_KMSTATE: // read-only + case SRTO_VERSION: // read-only + case SRTO_PEERVERSION: // read-only + case SRTO_SNDKMSTATE: // read-only + case SRTO_RCVKMSTATE: // read-only + case SRTO_GROUPTYPE: // read-only + LOGC(gmlog.Error, log << "group option setter: this option ("<< int(optName) << ") is read-only"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + case SRTO_SENDER: // deprecated (1.2.0 version legacy) + case SRTO_IPV6ONLY: // link-type specific + case SRTO_RENDEZVOUS: // socket-only + case SRTO_BINDTODEVICE: // socket-specific + case SRTO_GROUPCONNECT: // listener-specific + LOGC(gmlog.Error, log << "group option setter: this option ("<< int(optName) << ") is socket- or link-specific"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + case SRTO_TRANSTYPE: + case SRTO_TSBPDMODE: + case SRTO_CONGESTION: + LOGC(gmlog.Error, log << "group option setter: this option (" << int(optName) << ") removes live mode, which is the only supported for groups"); + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); case SRTO_RCVSYN: m_bSynRecving = cast_optval(optval, optlen); return; @@ -369,7 +399,7 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) const int min_timeo_ms = (int) CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS; if (val_ms < min_timeo_ms) { - LOGC(qmlog.Error, + LOGC(gmlog.Error, log << "group option: SRTO_GROUPMINSTABLETIMEO min allowed value is " << min_timeo_ms << " ms."); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } @@ -385,7 +415,7 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) if (val_ms > idletmo) { - LOGC(qmlog.Error, + LOGC(gmlog.Error, log << "group option: SRTO_GROUPMINSTABLETIMEO=" << val_ms << " exceeds SRTO_PEERIDLETIMEO=" << idletmo); throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); } diff --git a/srtcore/srt.h b/srtcore/srt.h index 614a85aeac..71ac2c3af7 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -182,7 +182,7 @@ typedef enum SRT_SOCKOPT { SRTO_RCVTIMEO = 14, // recv() timeout SRTO_REUSEADDR = 15, // reuse an existing port or create a new one SRTO_MAXBW = 16, // maximum bandwidth (bytes per second) that the connection can use - SRTO_STATE = 17, // current socket state, see UDTSTATUS, read only + SRTO_STATE = 17, // current socket state, see SRT_SOCKSTATUS, read only SRTO_EVENT = 18, // current available events associated with the socket SRTO_SNDDATA = 19, // size of data in the sending buffer SRTO_RCVDATA = 20, // size of data available for recv diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 4ec4a84f52..4205b46657 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -365,7 +365,7 @@ TEST(Bonding, Options) TestInit srtinit; // Create a group - const SRTSOCKET grp = srt_create_group(SRT_GTYPE_BROADCAST); + MAKE_UNIQUE_SOCK(grp, "broadcast group", srt_create_group(SRT_GTYPE_BROADCAST)); // rendezvous shall not be allowed to be set on the group // XXX actually it is possible, but no one tested it. POSTPONE. @@ -444,9 +444,10 @@ TEST(Bonding, Options) // First wait - until it's let go with accepting latch.wait(ux); - sockaddr_any revsa; - SRTSOCKET gs = srt_accept(lsn, revsa.get(), &revsa.len); - EXPECT_NE(gs, SRT_ERROR); + //sockaddr_any revsa; + SRTSOCKET lsna [1] = { lsn }; + SRTSOCKET gs = srt_accept_bond(lsna, 1, 1000); + ASSERT_NE(gs, SRT_INVALID_SOCK); check_streamid(gs); @@ -543,7 +544,6 @@ TEST(Bonding, Options) } accept_and_close.join(); - srt_close(grp); } inline SRT_SOCKGROUPCONFIG PrepareEndpoint(const std::string& host, int port) From 63bf9620964fab46a1f235937d8e92e5b0bc405b Mon Sep 17 00:00:00 2001 From: Thierry Lelegard Date: Fri, 21 Feb 2025 11:36:48 +0100 Subject: [PATCH 18/92] [build] Fix Windows installers (#3113). Use the OpenSSL Universal installer on Windows to build on x86/x64 host targeting Arm64 systems. --- scripts/win-installer/README.md | 79 +++++++++ scripts/win-installer/build-win-installer.ps1 | 155 ++++++++---------- scripts/win-installer/install-openssl.ps1 | 73 +++------ scripts/win-installer/libsrt.nsi | 24 +-- 4 files changed, 184 insertions(+), 147 deletions(-) diff --git a/scripts/win-installer/README.md b/scripts/win-installer/README.md index 006b8a6bce..cab9d76619 100644 --- a/scripts/win-installer/README.md +++ b/scripts/win-installer/README.md @@ -18,6 +18,8 @@ These initial steps need to be executed once only. - Prerequisite 2: Install OpenSSL for Windows, 64-bit Intel, 32-bit Intel, 64-bit Arm. This can be done automatically by running the PowerShell script `install-openssl.ps1`. + If you are interested in more information about OpenSSL for Windows, scroll to the + appendix at the end of this page. - Prerequisite 3: Install NSIS, the NullSoft Installation Scripting system. This can be done automatically by running the PowerShell script `install-nsis.ps1`. @@ -128,3 +130,80 @@ This directory contains the following files: | libsrt.nsi | NSIS installation script (used to build the installer). | libsrt.props | Visual Studio property files to use libsrt (embedded in the installer). | README.md | This text file. + +## Appendix: Using OpenSSL on Windows + +The SRT protocol uses encryption. Internally, `libsrt` can use multiple cryptographic +libraries but OpenSSL is the default choice on all platforms, including Windows. + +OpenSSL is not part of the base installation of a Windows platform and should be +separately installed. OpenSSL is neither "Windows-friendly" and the OpenSSL team +does not provide Windows binaries. + +The production of OpenSSL binaries for Windows is delegated to Shining Light +Productions (http://slproweb.com/). Binaries for all architectures are available +here: http://slproweb.com/products/Win32OpenSSL.html + +### How to grab OpenSSL installers for Windows + +This is automatically done by the PowerShell script `install-openssl.ps1` in +this directory. Let's see what's behind the scene. + +First, the script determines which installers should be downloaded and where +to get them from. + +Initially, the HTML for the [slproweb](http://slproweb.com/products/Win32OpenSSL.html) +page was built on server-side. Our script grabbed the URL content, parsed the HTML +and extracted the URL of the binaries for the latest OpenSSL packages from the +"href" of the links. + +At some point in 2024, the site changed policy. The Web page was no longer +built on server-side, but on client-side. The downloaded HTML contains some +JavaScript which dynamically builds the URL of the binaries for the latest +OpenSSL binaries. The previous method now finds no valid package URL from +the downloaded page (a full browser is needed to execute the JavaScript). +The JavaScript downloads a JSON file which contains all references to +the OpenSSL binaries. + +Therefore, the script `install-openssl.ps1` now uses the same method: +download that JSON file and parse it. + +### Multi-architecture builds + +The `libsrt` installer contains static libraries for all three architectures +which are supported by Windows: x86, x64, Arm64. The corresponding static +libraries for OpenSSL are also needed and included in the installer. + +Because Visual Studio can build libraries and executables for all three +architectures, on any build system, we need to install the OpenSSL headers +and libraries for all three architectures, on the build system. + +This is what the script `install-openssl.ps1` does. However, the way to +do this has changed over time. + +As a starting point, there are three installers, one per architecture. +All installers are x86 executables which can be run on any Windows system +(Arm64 Windows emulates x86 and x64 when necessary). Additionally, the +three sets of files are installed side by side, without overlap. + +- x86: `C:\Program Files (x86)\OpenSSL-Win32` +- x64: `C:\Program Files\OpenSSL-Win64` +- Arm64: `C:\Program Files\OpenSSL-Win64-ARM` + +So, the script `install-openssl.ps1` installed them all and `libsrt` could +be built for all architecture. + +However, it works only on Arm64 systems. On x86 and x64 system, the OpenSSL +installer for Arm64 fails. The installer executable can run but there is an +explicit check in the installation process to abort when running on Intel +systems. + +Since most Windows build servers are Intel systems, this approach was no +longer appropriate. + +At some point, the producers of the OpenSSL binaries acknowledged the problem +and they created an additional form of installer: the "universal" one. This +package can be installed on any system and the binaries and headers are installed +for all architectures, in one single location. + +- Universal: `C:\Program Files (x86)\OpenSSL-WinUniversal` diff --git a/scripts/win-installer/build-win-installer.ps1 b/scripts/win-installer/build-win-installer.ps1 index 9e9ae7940c..b6621550c5 100644 --- a/scripts/win-installer/build-win-installer.ps1 +++ b/scripts/win-installer/build-win-installer.ps1 @@ -21,6 +21,11 @@ the defaut version is a detailed version number (most recent version, number of commits since then, short commit SHA). + .PARAMETER NoBuild + + Do not rebuild the SRT libraries. Assume that they are already built. + Only build the installer. + .PARAMETER NoPause Do not wait for the user to press at end of execution. By default, @@ -31,6 +36,7 @@ [CmdletBinding()] param( [string]$Version = "", + [switch]$NoBuild = $false, [switch]$NoPause = $false ) Write-Output "Building the SRT static libraries installer for Windows" @@ -89,54 +95,33 @@ Write-Output "Windows version info: $VersionInfo" #----------------------------------------------------------------------------- # Locate OpenSSL root from local installation. -# After version 3.something, OpenSSL has changed its installation layout. -# We have to look for OpenSSL libraries in various locations. -$SSL = @{ - "x64" = @{ - "alt" = "Win64"; - "bits" = 64; - "root" = "C:\Program Files\OpenSSL-Win64" - }; - "Win32" = @{ - "alt" = "x86"; - "bits" = 32; - "root" = "C:\Program Files (x86)\OpenSSL-Win32" - } - "ARM64" = @{ - "alt" = "arm64"; - "bits" = 64; - "root" = "C:\Program Files\OpenSSL-Win64-ARM" - }; -} +# We now requires the "universal" OpenSSL installer (see README.md). +# The universal OpenSSL installer is installed in $SSL. +# $ARCH translates between VC "platform" names to subdirectory names inside $SSL. +# $LIBDIR translates between VC "configuration" names to library subdirectory names. + +$SSL = "C:\Program Files (x86)\OpenSSL-WinUniversal" +$ARCH = @{x64 = "x64"; Win32 = "x86"; ARM64 = "arm64"} +$LIBDIR = @{Release = "MD"; Debug = "MDd"} +$LIBFILE = @{crypto = "libcrypto_static.lib"; ssl = "libssl_static.lib"} + +function ssl-include($pf) { return "$SSL\include\$($ARCH.$pf)" } +function ssl-libdir($pf, $conf) { return "$SSL\lib\VC\$($ARCH.$pf)\$($LIBDIR.$conf)" } +function ssl-lib($pf, $conf, $lib) { return "$(ssl-libdir $pf $conf)\$($LIBFILE.$lib)" } # Verify OpenSSL directories and static libraries. Write-Output "Searching OpenSSL libraries ..." $Missing = 0 -foreach ($arch in $SSL.Keys) { - $root = $SSL[$arch]["root"] - $bits = $SSL[$arch]["bits"] - $alt = $SSL[$arch]["alt"] - if (-not (Test-Path $root)) { - Write-Output "**** Missing $root" +foreach ($Platform in $SSL.Keys) { + if (-not (Test-Path -PathType Container $(ssl-include $Platform))) { + Write-Output "**** Missing $(ssl-include $Platform)" $Missing = $Missing + 1 } - else { - foreach ($lib in @("ssl", "crypto")) { - foreach ($conf in @("MD", "MDd")) { - $name = "lib${lib}${conf}" - $SSL[$arch][$name] = "" - foreach ($try in @("$root\lib\VC\static\lib${lib}${bits}${conf}.lib", - "$root\lib\VC\${arch}\${conf}\lib${lib}_static.lib", - "$root\lib\VC\${alt}\${conf}\lib${lib}_static.lib")) { - if (Test-Path $try) { - $SSL[$arch][$name] = $try - break - } - } - if (-not $SSL[$arch][$name]) { - Write-Output "**** OpenSSL static library for $name not found" - $Missing = $Missing + 1 - } + foreach ($lib in $LIBFILE.Keys) { + foreach ($conf in $LIBDIR.Keys) { + if (-not (Test-Path $(ssl-lib $Platform $conf $lib))) { + Write-Output "**** Missing $(ssl-lib $Platform $conf $lib)" + $Missing = $Missing + 1 } } } @@ -187,46 +172,40 @@ Write-Output "NSIS: $NSIS" # Configure and build SRT library using CMake on all architectures. #----------------------------------------------------------------------------- -foreach ($Platform in $SSL.Keys) { - - # Build directory. Cleanup to force a fresh cmake config. - $BuildDir = "$TmpDir\build.$Platform" - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir - [void](New-Item -Path $BuildDir -ItemType Directory -Force) - - # Run CMake. - # Note: In previous versions of CMake, it was necessary to specify where - # OpenSSL was located using OPENSSL_ROOT_DIR, OPENSSL_LIBRARIES, and - # OPENSSL_INCLUDE_DIR. Starting with CMake 3.29.0, this is no longer - # necessary because CMake knows where to find the OpenSSL binaries from - # slproweb. Additionally, for some unkown reason, defining the OPENSSL_xxx - # variables no longer works with Arm64 libraries, even though the path to - # that version is correct. So, it's better to let CMake find OpenSSL by itself. - Write-Output "Configuring build for platform $Platform ..." - $SRoot = $SSL[$Platform]["root"] - $LibSSL = $SSL[$Platform]["libsslMD"] - $LibCrypto = $SSL[$Platform]["libcryptoMD"] - & $CMake -S $RepoDir -B $BuildDir -A $Platform -DENABLE_STDCXX_SYNC=ON - - # Patch version string in version.h - Get-Content "$BuildDir\version.h" | - ForEach-Object { - $_ -replace "#define *SRT_VERSION_STRING .*","#define SRT_VERSION_STRING `"$Version`"" - } | - Out-File "$BuildDir\version.new" -Encoding ascii - Move-Item "$BuildDir\version.new" "$BuildDir\version.h" -Force - - # Compile SRT. - Write-Output "Building for platform $Platform ..." - foreach ($Conf in @("Release", "Debug")) { - & $MSBuild "$BuildDir\SRT.sln" /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static +if (-not $NoBuild) { + foreach ($Platform in $ARCH.Keys) { + # Build directory. Cleanup to force a fresh cmake config. + $BuildDir = "$TmpDir\build.$Platform" + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir + [void](New-Item -Path $BuildDir -ItemType Directory -Force) + + # Run CMake. + Write-Output "Configuring build for platform $Platform ..." + & $CMake -S $RepoDir -B $BuildDir -A $Platform ` + -DENABLE_STDCXX_SYNC=ON ` + -DOPENSSL_INCLUDE_DIR="$(ssl-include $Platform)" ` + -DOPENSSL_ROOT_DIR="$(ssl-libdir $Platform Release)" + + # Patch version string in version.h + Get-Content "$BuildDir\version.h" | + ForEach-Object { + $_ -replace "#define *SRT_VERSION_STRING .*","#define SRT_VERSION_STRING `"$Version`"" + } | + Out-File "$BuildDir\version.new" -Encoding ascii + Move-Item "$BuildDir\version.new" "$BuildDir\version.h" -Force + + # Compile SRT. + Write-Output "Building for platform $Platform ..." + foreach ($Conf in $LIBDIR.Keys) { + & $MSBuild "$BuildDir\SRT.sln" /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static + } } } # Verify the presence of compiled libraries. Write-Output "Checking compiled libraries ..." $Missing = 0 -foreach ($Conf in @("Release", "Debug")) { +foreach ($Conf in $LIBDIR.Keys) { foreach ($Platform in $SSL.Keys) { $Path = "$TmpDir\build.$Platform\$Conf\srt_static.lib" if (-not (Test-Path $Path)) { @@ -254,18 +233,18 @@ Write-Output "Building installer ..." /DOutDir="$OutDir" ` /DBuildRoot="$TmpDir" ` /DRepoDir="$RepoDir" ` - /DlibsslWin32MD="$($SSL.Win32.libsslMD)" ` - /DlibsslWin32MDd="$($SSL.Win32.libsslMDd)" ` - /DlibcryptoWin32MD="$($SSL.Win32.libcryptoMD)" ` - /DlibcryptoWin32MDd="$($SSL.Win32.libcryptoMDd)" ` - /DlibsslWin64MD="$($SSL.x64.libsslMD)" ` - /DlibsslWin64MDd="$($SSL.x64.libsslMDd)" ` - /DlibcryptoWin64MD="$($SSL.x64.libcryptoMD)" ` - /DlibcryptoWin64MDd="$($SSL.x64.libcryptoMDd)" ` - /DlibsslArm64MD="$($SSL.ARM64.libsslMD)" ` - /DlibsslArm64MDd="$($SSL.ARM64.libsslMDd)" ` - /DlibcryptoArm64MD="$($SSL.ARM64.libcryptoMD)" ` - /DlibcryptoArm64MDd="$($SSL.ARM64.libcryptoMDd)" ` + /DlibsslWin32Release="$(ssl-lib Win32 Release ssl)" ` + /DlibsslWin32Debug="$(ssl-lib Win32 Debug ssl)" ` + /DlibcryptoWin32Release="$(ssl-lib Win32 Release crypto)" ` + /DlibcryptoWin32Debug="$(ssl-lib Win32 Debug crypto)" ` + /DlibsslWin64Release="$(ssl-lib x64 Release ssl)" ` + /DlibsslWin64Debug="$(ssl-lib x64 Debug ssl)" ` + /DlibcryptoWin64Release="$(ssl-lib x64 Release crypto)" ` + /DlibcryptoWin64Debug="$(ssl-lib x64 Debug crypto)" ` + /DlibsslArm64Release="$(ssl-lib Arm64 Release ssl)" ` + /DlibsslArm64Debug="$(ssl-lib Arm64 Debug ssl)" ` + /DlibcryptoArm64Release="$(ssl-lib Arm64 Release crypto)" ` + /DlibcryptoArm64Debug="$(ssl-lib Arm64 Debug crypto)" ` "$ScriptDir\libsrt.nsi" if (-not (Test-Path $InstallExe)) { diff --git a/scripts/win-installer/install-openssl.ps1 b/scripts/win-installer/install-openssl.ps1 index 3352584b89..c9ec580683 100644 --- a/scripts/win-installer/install-openssl.ps1 +++ b/scripts/win-installer/install-openssl.ps1 @@ -37,24 +37,7 @@ param( Write-Output "OpenSSL download and installation procedure" -# A bit of history: Where to load OpenSSL binaries from? -# -# The packages are built by "slproweb". The binaries are downloadable from -# their Web page at: http://slproweb.com/products/Win32OpenSSL.html -# -# Initially, the HTML for this page was built on server-side. This script -# grabbed the URL content, parse the HTML and extracted the URL of the -# binaries for the latest OpenSSL packages from the "href" of the links. -# -# At some point in 2024, the site changed policy. The Web page is no longer -# built on server-side, but on client-side. The downloaded HTML contains some -# JavaScript which dynamically builds the URL of the binaries for the latest -# OpenSSL binaries. The previous method now finds no valid package URL from -# the downloaded page (a full browser is needed to execute the JavaScript). -# The JavaScript downloads a JSON file which contains all references to -# the OpenSSL binaries. This script now uses the same method: download that -# JSON file and parse it. - +# The list of OpenSSL packages is available in a JSON file. See more details in README.md. $PackageList = "https://github.com/slproweb/opensslhashes/raw/master/win32_openssl_hashes.json" # A function to exit this script. @@ -101,40 +84,36 @@ if ($status -ne 1 -and $status -ne 2) { } $config = ConvertFrom-Json $Response.Content -# Download and install MSI packages for 32 and 64-bit Intel, 64-bit Arm. -foreach ($conf in @(@{arch="intel"; bits=32}, @{arch="intel"; bits=64}, @{arch="arm"; bits=64})) { - - # Get the URL of the MSI installer from the JSON config. - $Url = $config.files | Get-Member | ForEach-Object { - $name = $_.name - $info = $config.files.$($_.name) - if (-not $info.light -and $info.installer -like "msi" -and $info.bits -eq $conf.bits -and $info.arch -like $conf.arch) { - $info.url - } - } | Select-Object -Last 1 - if (-not $Url) { - Exit-Script "#### No MSI installer found for $($conf.bits)-bit $($conf.arch)" +# Find the URL of the latest "universal" installer in the JSON config file. +$Url = $config.files | Get-Member | ForEach-Object { + $name = $_.name + $info = $config.files.$($_.name) + if (-not $info.light -and $info.installer -like "exe" -and $info.arch -like "universal") { + $info.url } +} | Select-Object -Last 1 +if (-not $Url) { + Exit-Script "#### No universal installer found" +} - $MsiName = (Split-Path -Leaf $Url) - $MsiPath = "$TmpDir\$MsiName" +$ExeName = (Split-Path -Leaf $Url) +$ExePath = "$TmpDir\$ExeName" - if (-not $ForceDownload -and (Test-Path $MsiPath)) { - Write-Output "$MsiName already downloaded, use -ForceDownload to download again" - } - else { - Write-Output "Downloading $Url ..." - Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $MsiPath - } +if (-not $ForceDownload -and (Test-Path $ExePath)) { + Write-Output "$ExeName already downloaded, use -ForceDownload to download again" +} +else { + Write-Output "Downloading $Url ..." + Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $ExePath +} - if (-not (Test-Path $MsiPath)) { - Exit-Script "$Url download failed" - } +if (-not (Test-Path $ExePath)) { + Exit-Script "$Url download failed" +} - if (-not $NoInstall) { - Write-Output "Installing $MsiName" - Start-Process msiexec.exe -ArgumentList @("/i", $MsiPath, "/qn", "/norestart") -Wait - } +if (-not $NoInstall) { + Write-Output "Installing $ExeName" + Start-Process -FilePath $ExePath -ArgumentList @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/ALLUSERS") -Wait } Exit-Script diff --git a/scripts/win-installer/libsrt.nsi b/scripts/win-installer/libsrt.nsi index a3fff4451e..66ff147450 100644 --- a/scripts/win-installer/libsrt.nsi +++ b/scripts/win-installer/libsrt.nsi @@ -142,38 +142,38 @@ Section "Install" CreateDirectory "$INSTDIR\lib\Release-x64" SetOutPath "$INSTDIR\lib\Release-x64" File /oname=srt.lib "${BuildWin64Dir}\Release\srt_static.lib" - File /oname=libcrypto.lib "${libcryptoWin64MD}" - File /oname=libssl.lib "${libsslWin64MD}" + File /oname=libcrypto.lib "${libcryptoWin64Release}" + File /oname=libssl.lib "${libsslWin64Release}" CreateDirectory "$INSTDIR\lib\Debug-x64" SetOutPath "$INSTDIR\lib\Debug-x64" File /oname=srt.lib "${BuildWin64Dir}\Debug\srt_static.lib" - File /oname=libcrypto.lib "${libcryptoWin64MDd}" - File /oname=libssl.lib "${libsslWin64MDd}" + File /oname=libcrypto.lib "${libcryptoWin64Debug}" + File /oname=libssl.lib "${libsslWin64Debug}" CreateDirectory "$INSTDIR\lib\Release-Win32" SetOutPath "$INSTDIR\lib\Release-Win32" File /oname=srt.lib "${BuildWin32Dir}\Release\srt_static.lib" - File /oname=libcrypto.lib "${libcryptoWin32MD}" - File /oname=libssl.lib "${libsslWin32MD}" + File /oname=libcrypto.lib "${libcryptoWin32Release}" + File /oname=libssl.lib "${libsslWin32Release}" CreateDirectory "$INSTDIR\lib\Debug-Win32" SetOutPath "$INSTDIR\lib\Debug-Win32" File /oname=srt.lib "${BuildWin32Dir}\Debug\srt_static.lib" - File /oname=libcrypto.lib "${libcryptoWin32MDd}" - File /oname=libssl.lib "${libsslWin32MDd}" + File /oname=libcrypto.lib "${libcryptoWin32Debug}" + File /oname=libssl.lib "${libsslWin32Debug}" CreateDirectory "$INSTDIR\lib\Release-Arm64" SetOutPath "$INSTDIR\lib\Release-Arm64" File /oname=srt.lib "${BuildArm64Dir}\Release\srt_static.lib" - File /oname=libcrypto.lib "${libcryptoArm64MD}" - File /oname=libssl.lib "${libsslArm64MD}" + File /oname=libcrypto.lib "${libcryptoArm64Release}" + File /oname=libssl.lib "${libsslArm64Release}" CreateDirectory "$INSTDIR\lib\Debug-Arm64" SetOutPath "$INSTDIR\lib\Debug-Arm64" File /oname=srt.lib "${BuildArm64Dir}\Debug\srt_static.lib" - File /oname=libcrypto.lib "${libcryptoArm64MDd}" - File /oname=libssl.lib "${libsslArm64MDd}" + File /oname=libcrypto.lib "${libcryptoArm64Debug}" + File /oname=libssl.lib "${libsslArm64Debug}" ; Add an environment variable to installation root. WriteRegStr HKLM ${EnvironmentKey} "LIBSRT" "$INSTDIR" From fd5638590939b0eb1e72836e8a2b2411555d6759 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 21 Feb 2025 12:35:47 +0100 Subject: [PATCH 19/92] [build] Added deprecation warning for Windows+PThreads configuration (#3112). --- CMakeLists.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 981440189b..c65cbd592f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,11 +43,11 @@ set_if(DARWIN (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set_if(LINUX ${CMAKE_SYSTEM_NAME} MATCHES "Linux") set_if(BSD ${SYSNAME_LC} MATCHES "bsd$") set_if(MICROSOFT WIN32 AND (NOT MINGW AND NOT CYGWIN)) -set_if(GNU ${CMAKE_SYSTEM_NAME} MATCHES "GNU") +set_if(GNU_OS ${CMAKE_SYSTEM_NAME} MATCHES "GNU") # Use GNU_OS to not confuse with gcc set_if(ANDROID ${SYSNAME_LC} MATCHES "android") set_if(SUNOS "${SYSNAME_LC}" MATCHES "sunos") -set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR (CYGWIN AND CYGWIN_USE_POSIX)) -set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU) +set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR (CYGWIN AND CYGWIN_USE_POSIX) OR GNU_OS) +set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU_OS) set_if(NEED_DESTINATION ${CMAKE_VERSION} VERSION_LESS "3.14.0") include(GNUInstallDirs) @@ -881,6 +881,9 @@ elseif (PTHREAD_LIBRARY AND PTHREAD_INCLUDE_DIR) message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") message(STATUS "Pthread include dir: ${PTHREAD_INCLUDE_DIR}") elseif (MICROSOFT) + + message(WARNING "DEPRECATION WARNING!\nUsing POSIX thread API with Windows is deprecated and will be removed") + find_package(pthreads QUIET) if (NOT PTHREAD_INCLUDE_DIR OR NOT PTHREAD_LIBRARY) From 7b77d4c93a7bc2e53e1cfd6dc83a1dcb0612d198 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 24 Feb 2025 10:34:31 +0100 Subject: [PATCH 20/92] [build] Fix use of the OPENSSL_USE_STATIC_LIBS CMake option (#3117). Now SRT_USE_OPENSSL_STATIC_LIBS is the CMake option of SRT. The OPENSSL_USE_STATIC_LIBS is CMake's option, still has to be used if SRT_USE_OPENSSL_STATIC_LIBS is set. --------- Co-authored-by: lilinxiong Co-authored-by: Mikolaj Malecki --- CMakeLists.txt | 32 +++++++++--- docs/build/build-options.md | 6 +-- scripts/ShowProjectConfig.cmake | 2 +- scripts/build-windows.ps1 | 2 +- srtcore/srt_shared.rc | 90 ++++++++++++++++----------------- 5 files changed, 76 insertions(+), 56 deletions(-) mode change 100644 => 100755 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index c65cbd592f..d718792d17 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,7 +172,7 @@ option(ENABLE_CODE_COVERAGE "Enable code coverage reporting" OFF) option(ENABLE_MONOTONIC_CLOCK "Enforced clock_gettime with monotonic clock on GC CV" ${ENABLE_MONOTONIC_CLOCK_DEFAULT}) option(ENABLE_STDCXX_SYNC "Use C++11 chrono and threads for timing instead of pthreads" ${ENABLE_STDCXX_SYNC_DEFAULT}) option(USE_OPENSSL_PC "Use pkg-config to find OpenSSL libraries" ON) -option(OPENSSL_USE_STATIC_LIBS "Link OpenSSL libraries statically." OFF) +option(SRT_USE_OPENSSL_STATIC_LIBS "Link OpenSSL libraries statically." OFF) option(USE_BUSY_WAITING "Enable more accurate sending times at a cost of potentially higher CPU load" OFF) option(USE_GNUSTL "Get c++ library/headers from the gnustl.pc" OFF) option(ENABLE_SOCK_CLOEXEC "Enable setting SOCK_CLOEXEC on a socket" ON) @@ -180,6 +180,12 @@ option(ENABLE_SHOW_PROJECT_CONFIG "Enable show Project Configuration" OFF) option(ENABLE_CLANG_TSA "Enable Clang Thread Safety Analysis" OFF) +if (DEFINED OPENSSL_USE_STATIC_LIBS AND "${OPENSSL_USE_STATIC_LIBS}" STREQUAL "ON") + message(WARNING "Use of OPENSSL_USE_STATIC_LIBS as SRT build option here is deprecated. +Please use SRT_USE_OPENSSL_STATIC_LIBS instead.") + set(SRT_USE_OPENSSL_STATIC_LIBS ${OPENSSL_USE_STATIC_LIBS}) +endif() + # NOTE: Use ATOMIC_USE_SRT_SYNC_MUTEX and will override the auto-detection of the # Atomic implemetation in srtcore/atomic.h. option(ATOMIC_USE_SRT_SYNC_MUTEX "Use srt::sync::Mutex to Implement Atomics" OFF) @@ -389,7 +395,7 @@ if (ENABLE_ENCRYPTION) # fall back to find_package method otherwise if (USE_OPENSSL_PC) pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) - if (OPENSSL_USE_STATIC_LIBS) + if (SRT_USE_OPENSSL_STATIC_LIBS) # use `pkg-config --static xxx` found libs set(SSL_LIBRARIES ${SSL_STATIC_LIBRARIES}) endif() @@ -413,6 +419,11 @@ if (ENABLE_ENCRYPTION) ) message(STATUS "SSL via pkg-config: -L ${SSL_LIBRARY_DIRS} -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") else() + if (SRT_USE_OPENSSL_STATIC_LIBS) + # use `pkg-config --static xxx` found libs + set(OPENSSL_USE_STATIC_LIBS True) + set(OPENSSL_MSVC_STATIC_RT True) + endif() find_package(OpenSSL REQUIRED) set (SSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) @@ -466,6 +477,10 @@ if (ENABLE_ENCRYPTION) # fall back to find_package method otherwise if (USE_OPENSSL_PC) pkg_check_modules(SSL ${SSL_REQUIRED_MODULES}) + if (SRT_USE_OPENSSL_STATIC_LIBS) + # use `pkg-config --static xxx` found libs + set(SSL_LIBRARIES ${SSL_STATIC_LIBRARIES}) + endif() endif() if (SSL_FOUND) # We have some cases when pkg-config is improperly configured @@ -486,6 +501,11 @@ if (ENABLE_ENCRYPTION) ) message(STATUS "SSL via pkg-config: -L ${SSL_LIBRARY_DIRS} -I ${SSL_INCLUDE_DIRS} -l;${SSL_LIBRARIES}") else() + if (SRT_USE_OPENSSL_STATIC_LIBS) + # use `pkg-config --static xxx` found libs + set(OPENSSL_USE_STATIC_LIBS True) + set(OPENSSL_MSVC_STATIC_RT True) + endif() find_package(OpenSSL REQUIRED) set (SSL_INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR}) set (SSL_LIBRARIES ${OPENSSL_LIBRARIES}) @@ -1082,7 +1102,7 @@ if (srt_libspec_shared) if (MICROSOFT) target_link_libraries(${TARGET_srt}_shared PRIVATE ws2_32.lib) if (NOT (ENABLE_ENCRYPTION AND "${USE_ENCLIB}" STREQUAL "botan")) - if (OPENSSL_USE_STATIC_LIBS) + if (SRT_USE_OPENSSL_STATIC_LIBS) target_link_libraries(${TARGET_srt}_shared PRIVATE crypt32.lib) else() set_target_properties(${TARGET_srt}_shared PROPERTIES LINK_FLAGS "/DELAYLOAD:libeay32.dll") @@ -1121,7 +1141,7 @@ if (srt_libspec_static) endif() if (MICROSOFT) target_link_libraries(${TARGET_srt}_static PRIVATE ws2_32.lib) - if (OPENSSL_USE_STATIC_LIBS) + if (SRT_USE_OPENSSL_STATIC_LIBS) target_link_libraries(${TARGET_srt}_static PRIVATE crypt32.lib) endif() elseif (MINGW) @@ -1135,7 +1155,7 @@ endif() target_include_directories(srt_virtual PRIVATE ${SSL_INCLUDE_DIRS}) if (MICROSOFT) - if (OPENSSL_USE_STATIC_LIBS) + if (SRT_USE_OPENSSL_STATIC_LIBS) set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib crypt32.lib) else() set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ws2_32.lib) @@ -1177,7 +1197,7 @@ endif() if (srt_libspec_shared) if (MICROSOFT) target_link_libraries(${TARGET_srt}_shared PUBLIC Ws2_32.lib) - if (OPENSSL_USE_STATIC_LIBS) + if (SRT_USE_OPENSSL_STATIC_LIBS) target_link_libraries(${TARGET_srt}_shared PUBLIC crypt32.lib) endif() endif() diff --git a/docs/build/build-options.md b/docs/build/build-options.md index 529bd5cd7e..e09ea41bb7 100644 --- a/docs/build/build-options.md +++ b/docs/build/build-options.md @@ -64,7 +64,7 @@ Option details are given further below. | [`USE_ENCLIB`](#use_enclib) | 1.3.3 | `STRING` | openssl | Encryption library to be used (`openssl`, `openssl-evp` (since 1.5.1), `gnutls`, `mbedtls`, `botan` (since 1.6.0)). | | [`USE_GNUSTL`](#use_gnustl) | 1.3.4 | `BOOL` | OFF | Use `pkg-config` with the `gnustl` package name to extract the header and library path for the C++ standard library. | | [`USE_OPENSSL_PC`](#use_openssl_pc) | 1.3.0 | `BOOL` | ON | Use `pkg-config` to find OpenSSL libraries. | -| [`OPENSSL_USE_STATIC_LIBS`](#openssl_use_static_libs) | 1.5.0 | `BOOL` | OFF | Link OpenSSL statically. | +| [`SRT_USE_OPENSSL_STATIC_LIBS`](#srt_use_openssl_static_libs)| 1.5.0 | `BOOL` | OFF | Link OpenSSL statically. | | [`USE_STATIC_LIBSTDCXX`](#use_static_libstdcxx) | 1.2.0 | `BOOL` | OFF | Enforces linking the SRT library against the static `libstdc++` library. | | [`WITH_COMPILER_PREFIX`](#with_compiler_prefix) | 1.3.0 | `STRING` | OFF | Sets C/C++ toolchains as `` and ``, overriding the default compiler. | | [`WITH_COMPILER_TYPE`](#with_compiler_type) | 1.3.0 | `STRING` | OFF | Sets the compiler type to be used (values: gcc, cc, clang, etc.). | @@ -618,8 +618,8 @@ built-in one). When ON, uses `pkg-config` to find OpenSSL libraries. You can turn this OFF to force `cmake` to find OpenSSL by its own preferred method. -### OPENSSL_USE_STATIC_LIBS -**`--openssl-use-static-libs`** (default: OFF) +### SRT_USE_OPENSSL_STATIC_LIBS +**`--srt-use-openssl-static-libs`** (default: OFF) When ON, OpenSSL libraries are linked statically. When `pkg-config`(`-DUSE_OPENSSL_PC=ON`) is used, static OpenSSL libraries are listed in `SSL_STATIC_LIBRARIES`. See `_STATIC` in [CMake's FindPkgConfig](https://cmake.org/cmake/help/latest/module/FindPkgConfig.html). diff --git a/scripts/ShowProjectConfig.cmake b/scripts/ShowProjectConfig.cmake index 108dde5dbd..9fc275bc86 100644 --- a/scripts/ShowProjectConfig.cmake +++ b/scripts/ShowProjectConfig.cmake @@ -166,7 +166,7 @@ function(ShowProjectConfig) " ENABLE_MONOTONIC_CLOCK: ${ENABLE_MONOTONIC_CLOCK}\n" " ENABLE_STDCXX_SYNC: ${ENABLE_STDCXX_SYNC}\n" " USE_OPENSSL_PC: ${USE_OPENSSL_PC}\n" - " OPENSSL_USE_STATIC_LIBS: ${OPENSSL_USE_STATIC_LIBS}\n" + " SRT_USE_OPENSSL_STATIC_LIBS: ${SRT_USE_OPENSSL_STATIC_LIBS}\n" " USE_BUSY_WAITING: ${USE_BUSY_WAITING}\n" " USE_GNUSTL: ${USE_GNUSTL}\n" " ENABLE_SOCK_CLOEXEC: ${ENABLE_SOCK_CLOEXEC}\n" diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index 66586158ee..d2f07972d9 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -175,7 +175,7 @@ if ( $VCPKG_OPENSSL -eq 'ON' ) { $cmakeFlags += " -DCMAKE_TOOLCHAIN_FILE=$projectRoot\vcpkg\scripts\buildsystems\vcpkg.cmake" } else { - $cmakeFlags += " -DOPENSSL_USE_STATIC_LIBS=$STATIC_LINK_SSL " + $cmakeFlags += " -DSRT_USE_OPENSSL_STATIC_LIBS=$STATIC_LINK_SSL " } # cmake uses a flag for architecture from vs2019, so add that as a suffix diff --git a/srtcore/srt_shared.rc b/srtcore/srt_shared.rc index 4481928781..5639a02599 100644 --- a/srtcore/srt_shared.rc +++ b/srtcore/srt_shared.rc @@ -1,45 +1,45 @@ -// Microsoft Visual C++ generated resource script. -// -#include "version.h" -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO -#ifdef SRT_VERSION_BUILD - FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH, SRT_VERSION_BUILD -#else - FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH -#endif - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x2L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "080904b0" - BEGIN - VALUE "CompanyName", "SRT Alliance" - VALUE "FileDescription", "SRT Local Build" - VALUE "InternalName", "srt.dll" - VALUE "LegalCopyright", "" - VALUE "OriginalFilename", "srt.dll" - VALUE "ProductName", "SRT" - VALUE "ProductVersion", SRT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x809, 1200 - END -END - +// Microsoft Visual C++ generated resource script. +// +#include "version.h" +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO +#ifdef SRT_VERSION_BUILD + FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH, SRT_VERSION_BUILD +#else + FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH +#endif + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "CompanyName", "SRT Alliance" + VALUE "FileDescription", "SRT Local Build" + VALUE "InternalName", "srt.dll" + VALUE "LegalCopyright", "" + VALUE "OriginalFilename", "srt.dll" + VALUE "ProductName", "SRT" + VALUE "ProductVersion", SRT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + From 00e14f7bd9e15933a52005b33ad9c0f79c2d8d6d Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Tue, 25 Feb 2025 10:22:12 +0100 Subject: [PATCH 21/92] [tests] Extending socket options test to group options (#3086). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Małecki Co-authored-by: Sektor van Skijlen --- .travis.yml | 4 + srtcore/group.cpp | 26 ++- srtcore/socketconfig.h | 4 +- test/test_bonding.cpp | 11 + test/test_env.h | 6 + test/test_socket_options.cpp | 425 ++++++++++++++++++++++++++++------- 6 files changed, 394 insertions(+), 82 deletions(-) diff --git a/.travis.yml b/.travis.yml index 94d5dac3a1..0402fb51ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,6 +65,10 @@ matrix: - BUILD_TYPE=Release - BUILD_OPTS='-DENABLE_MONOTONIC_CLOCK=ON' script: + - echo COMPILER $TRAVIS_COMPILER + - echo SYSTEM $TRAVIS_OS_NAME + - echo BUILD_TYPE $BUILD_TYPE + - echo BUILD_OPTS $BUILD_OPTS - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then export CC="x86_64-w64-mingw32-gcc"; export CXX="x86_64-w64-mingw32-g++"; diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 2c758396e1..4c292cb894 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -461,6 +461,14 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen) } } + // Before possibly storing the option, check if it is settable on a socket. + CSrtConfig testconfig; + + // Note: this call throws CUDTException by itself. + int result = testconfig.set(optName, optval, optlen); + if (result == -1) // returned in case of unknown option + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + // Store the option regardless if pre or post. This will apply m_config.push_back(ConfigItem(optName, optval, optlen)); } @@ -724,8 +732,10 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(int64_t(-1)); case SRTO_INPUTBW: RD(int64_t(-1)); + case SRTO_MININPUTBW: + RD(int64_t(0)); case SRTO_OHEADBW: - RD(0); + RD(SRT_OHEAD_DEFAULT_P100); case SRTO_STATE: RD(SRTS_INIT); case SRTO_EVENT: @@ -746,8 +756,9 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(false); case SRTO_LATENCY: case SRTO_RCVLATENCY: - case SRTO_PEERLATENCY: RD(SRT_LIVE_DEF_LATENCY_MS); + case SRTO_PEERLATENCY: + RD(0); case SRTO_TLPKTDROP: RD(true); case SRTO_SNDDROPDELAY: @@ -758,14 +769,15 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(SRT_DEF_VERSION); case SRTO_PEERVERSION: RD(0); - + case SRTO_PEERIDLETIMEO: + RD(CSrtConfig::COMM_RESPONSE_TIMEOUT_MS); case SRTO_CONNTIMEO: - RD(-1); + RD(CSrtConfig::DEF_CONNTIMEO_S * 1000); // required milliseconds case SRTO_DRIFTTRACER: RD(true); case SRTO_MINVERSION: - RD(0); + RD(SRT_VERSION_MAJ1); case SRTO_STREAMID: RD(std::string()); case SRTO_CONGESTION: @@ -776,6 +788,10 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(0); case SRTO_GROUPMINSTABLETIMEO: RD(CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS); + case SRTO_LOSSMAXTTL: + RD(0); + case SRTO_RETRANSMITALGO: + RD(1); } #undef RD diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index a6dd06caaf..0f6c798878 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -70,6 +70,8 @@ written by #define SRT_VERSION_MIN(v) (0x00FF00 & (v)) #define SRT_VERSION_PCH(v) (0x0000FF & (v)) +static const int SRT_OHEAD_DEFAULT_P100 = 25; + // NOTE: SRT_VERSION is primarily defined in the build file. extern const int32_t SRT_DEF_VERSION; @@ -316,7 +318,7 @@ struct CSrtConfig: CSrtMuxerConfig , iCryptoMode(CIPHER_MODE_AUTO) , llInputBW(0) , llMinInputBW(0) - , iOverheadBW(25) + , iOverheadBW(SRT_OHEAD_DEFAULT_P100) , bRcvNakReport(true) , iMaxReorderTolerance(0) // Sensible optimal value is 10, 0 preserves old behavior , uKmRefreshRatePkt(0) diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 4205b46657..e9efe939a8 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -380,12 +380,18 @@ TEST(Bonding, Options) uint32_t val = 16; EXPECT_NE(srt_setsockflag(grp, SRTO_PBKEYLEN, &val, (int) sizeof val), SRT_ERROR); + const bool bfalse = true; + EXPECT_EQ(srt_setsockflag(grp, SRTO_RENDEZVOUS, &bfalse, (int)sizeof bfalse), SRT_ERROR); + #ifdef ENABLE_AEAD_API_PREVIEW val = 1; EXPECT_NE(srt_setsockflag(grp, SRTO_CRYPTOMODE, &val, sizeof val), SRT_ERROR); #endif #endif + const string packet_filter = "fec,cols:10,rows:5"; + EXPECT_NE(srt_setsockflag(grp, SRTO_PACKETFILTER, packet_filter.c_str(), (int)packet_filter.size()), SRT_ERROR); + // ================ // Linger is an option of a trivial type, but differes from other integer-typed options. // Therefore checking it specifically. @@ -451,6 +457,11 @@ TEST(Bonding, Options) check_streamid(gs); + std::array tmpbuf; + auto opt_len = (int)tmpbuf.size(); + EXPECT_EQ(srt_getsockflag(gs, SRTO_PACKETFILTER, tmpbuf.data(), &opt_len), SRT_SUCCESS); + std::cout << "Packet filter: " << std::string(tmpbuf.data(), opt_len) << '\n'; + // Connected, wait to close latch.wait(ux); diff --git a/test/test_env.h b/test/test_env.h index 5ca1a536e4..edceee6e65 100644 --- a/test/test_env.h +++ b/test/test_env.h @@ -50,6 +50,12 @@ class TestEnv: public testing::Environment } static bool Allowed_Platform() { return false; } + +#ifdef ENABLE_BONDING + static bool Allowed_Bonding() { return true; } +#else + static bool Allowed_Bonding() { return false; } +#endif }; #define SRTST_REQUIRES(feature,...) if (!srt::TestEnv::Allowed_##feature(__VA_ARGS__)) { return; } diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index 58e60e7610..fe27bb387a 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -25,16 +25,27 @@ using namespace std; using namespace srt; +#define PLEASE_LOG 0 -class TestSocketOptions - : public ::srt::Test +#if PLEASE_LOG +#define LOGD(args) args +#else +#define LOGD(args) (void)0 +#endif + +class TestOptionsCommon: public srt::Test { protected: - TestSocketOptions() = default; + TestOptionsCommon() = default; + ~TestOptionsCommon() override = default; - ~TestSocketOptions() override = default; + sockaddr_any m_sa; + SRTSOCKET m_caller_sock = SRT_INVALID_SOCK; + SRTSOCKET m_listen_sock = SRT_INVALID_SOCK; + int m_pollid = 0; public: + void BindListener() const { // Specify address of the listener @@ -77,42 +88,104 @@ class TestSocketOptions return accepted_sock; } + void teardown() override + { + // Code here will be called just after the test completes. + // OK to throw exceptions from here if needed. + EXPECT_NE(srt_close(m_caller_sock), SRT_ERROR); + EXPECT_NE(srt_close(m_listen_sock), SRT_ERROR); + } +}; + + +class TestSocketOptions: public TestOptionsCommon +{ protected: + TestSocketOptions() = default; + ~TestSocketOptions() override = default; + // setup() is run immediately before a test starts. void setup() override { const int yes = 1; - - memset(&m_sa, 0, sizeof m_sa); - m_sa.sin_family = AF_INET; - m_sa.sin_port = htons(5200); - ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &m_sa.sin_addr), 1); + m_sa = srt::CreateAddr("127.0.0.1", 5200, AF_INET); + ASSERT_FALSE(m_sa.empty()); m_caller_sock = srt_create_socket(); - ASSERT_NE(m_caller_sock, SRT_INVALID_SOCK); + ASSERT_NE(m_caller_sock, SRT_INVALID_SOCK) << srt_getlasterror_str(); + m_listen_sock = srt_create_socket(); + ASSERT_NE(m_listen_sock, SRT_INVALID_SOCK) << srt_getlasterror_str(); + ASSERT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect ASSERT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect + ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect + ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect + } +}; + +#if ENABLE_BONDING +// Test group options +class TestGroupOptions: public TestOptionsCommon +{ +protected: + TestGroupOptions() = default; + ~TestGroupOptions() override = default; + + // Is run immediately before a test starts. + void setup() override + { + const int yes = 1; + + m_sa = srt::CreateAddr("127.0.0.1", 5200, AF_INET); + ASSERT_FALSE(m_sa.empty()); + + m_caller_sock = srt_create_group(SRT_GTYPE_BROADCAST); + ASSERT_NE(m_caller_sock, SRT_INVALID_SOCK) << srt_getlasterror_str(); m_listen_sock = srt_create_socket(); - ASSERT_NE(m_listen_sock, SRT_INVALID_SOCK); + ASSERT_NE(m_listen_sock, SRT_INVALID_SOCK) << srt_getlasterror_str(); + + ASSERT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect + ASSERT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect + ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect + ASSERT_EQ(srt_setsockflag(m_listen_sock, SRTO_GROUPCONNECT, &yes, sizeof yes), SRT_SUCCESS); } +}; +#endif - void teardown() override +#if PLEASE_LOG +static std::string to_string(const linb::any& val) +{ + using namespace linb; + + if (val.type() == typeid(const char*)) { - // Code here will be called just after the test completes. - // OK to throw exceptions from here if needed. - EXPECT_NE(srt_close(m_caller_sock), SRT_ERROR); - EXPECT_NE(srt_close(m_listen_sock), SRT_ERROR); + return any_cast(val); } - sockaddr_in m_sa; - SRTSOCKET m_caller_sock = SRT_INVALID_SOCK; - SRTSOCKET m_listen_sock = SRT_INVALID_SOCK; + std::ostringstream out; + if (val.type() == typeid(bool)) + { + out << any_cast(val); + } + else if (val.type() == typeid(int)) + { + out << any_cast(val); + } + else if (val.type() == typeid(int64_t)) + { + out << any_cast(val); + } + else + { + return ""; + } - int m_pollid = 0; -}; + return out.str(); +} +#endif enum class RestrictionType { @@ -121,9 +194,70 @@ enum class RestrictionType POST = 2 }; +// FLAGS +// - READABLE (R) +// : You can call srt_getsockflag +// - WRITABLE (W) +// : You can call srt_setsockflag +// - SOCKETWISE (S) +// : Can be set on a socket +// - GROUPWISE (G) +// : Can be set on a group +// - DERIVED (D) +// : TRUE: If it's set on the group, it will be derived by members +// : FALSE: It cannot be set on the group to be derived by members +// - GROUPUNIQUE (I) +// : TRUE: If set on the group, it's assigned to a group (not members) +// : FALSE: If set on the group, it's derived by members +// - MODIFIABLE (M) +// : TRUE: Can be set on individual member socket differently. +// : FALSE: Cannot be altered on the individual member socket. + +namespace Flags +{ +enum type: char +{ + O = 0, // Marker for an unset flag + + R = 1 << 0, // readable + W = 1 << 1, // writable + S = 1 << 2, // can be set on single socket + G = 1 << 3, // can be set on group + D = 1 << 4, // when set on group, derived by the socket + I = 1 << 5, // when set on group, it concerns group only + M = 1 << 6 // can be modified on individual member +}; + +inline type operator|(type f1, type f2) +{ + char val = char(f1) | char(f2); + return type(val); +} + +inline bool operator&(type ff, type mask) +{ + char val = char(ff) & char(mask); + return val == mask; +} + +const std::string str(type t) +{ + static const char names [] = "RWSGDI+"; + std::string out; + + for (int i = 0; i < 7; ++i) + if (int(t) & (1 << i)) + out += names[i]; + + if (out.empty()) + return "O"; + return out; +} +} + const char* RestrictionTypeStr(RestrictionType val) { - const std::map type_to_str = { + static const std::map type_to_str = { { RestrictionType::PREBIND, "PREBIND" }, { RestrictionType::PRE, "PRE" }, { RestrictionType::POST, "POST" } @@ -143,26 +277,51 @@ struct OptionTestEntry linb::any dflt_val; linb::any ndflt_val; vector invalid_vals; + Flags::type flags; + + bool allof() const { return true; } + + template + bool allof(Flags::type flg, Args... args) const + { + return flags & flg && allof(args...); + } + + bool anyof() const { return false; } + + template + bool anyof(Flags::type flg, Args... args) const + { + return flags & flg || anyof(args...); + } }; static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. static const size_t DFT_MTU_SIZE = 1500; // Default MTU size static const size_t SRT_PKT_SIZE = DFT_MTU_SIZE - UDP_HDR_SIZE; // MTU without UDP header +namespace Table +{ + // A trick to localize 1-letter flags without exposing + // them for the rest of the file. +using namespace Flags; + const OptionTestEntry g_test_matrix_options[] = { - // Option ID, Option Name | Restriction | optlen | min | max | default | nondefault | invalid vals | - //SRTO_BINDTODEVICE - //{ SRTO_CONGESTION, "SRTO_CONGESTION", RestrictionType::PRE, 4, "live", "file", "live", "file", {"liv", ""} }, - { SRTO_CONNTIMEO, "SRTO_CONNTIMEO", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 3000, 250, {-1} }, - { SRTO_DRIFTTRACER, "SRTO_DRIFTTRACER", RestrictionType::POST, sizeof(bool), false, true, true, false, {} }, - { SRTO_ENFORCEDENCRYPTION, "SRTO_ENFORCEDENCRYPTION", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, - //SRTO_EVENT - { SRTO_FC, "SRTO_FC", RestrictionType::PRE, sizeof(int), 32, INT32_MAX, 25600, 10000, {-1, 31} }, - //SRTO_GROUPCONNECT + // Place 'O' if not set. + // Option ID, Option Name | Restriction | optlen | min | max | default | nondefault | invalid vals | flags: R | W | G | S | D | I | M + + //SRTO_BINDTODEVICE R | W | G | S | D | I | M + //{ SRTO_CONGESTION, "SRTO_CONGESTION", RestrictionType::PRE, 4, "live", "file", "live", "file", {"liv", ""}, O | W | O | S | O | O | O }, + { SRTO_CONNTIMEO, "SRTO_CONNTIMEO", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 3000, 250, {-1}, O | W | G | S | D | O | M }, + { SRTO_DRIFTTRACER, "SRTO_DRIFTTRACER", RestrictionType::POST, sizeof(bool), false, true, true, false, {}, R | W | G | S | D | O | O }, + { SRTO_ENFORCEDENCRYPTION, "SRTO_ENFORCEDENCRYPTION", RestrictionType::PRE, sizeof(bool), false, true, true, false, {}, O | W | G | S | D | O | O }, + //SRTO_EVENT R | O | O | S | O | O | O + { SRTO_FC, "SRTO_FC", RestrictionType::PRE, sizeof(int), 32, INT32_MAX, 25600, 10000, {-1, 31}, R | W | G | S | D | O | O }, + //SRTO_GROUPCONNECT O | W | O | S | O | O | O #if ENABLE_BONDING // Max value can't exceed SRTO_PEERIDLETIMEO - { SRTO_GROUPMINSTABLETIMEO, "SRTO_GROUPMINSTABLETIMEO", RestrictionType::PRE, sizeof(int), 60, 5000, 60, 70, {0, -1, 50, 5001} }, + { SRTO_GROUPMINSTABLETIMEO, "SRTO_GROUPMINSTABLETIMEO", RestrictionType::PRE, sizeof(int), 60, 5000, 60, 70, {0, -1, 50, 5001}, O | W | G | O | D | I | M }, #endif //SRTO_GROUPTYPE //SRTO_INPUTBW @@ -170,56 +329,66 @@ const OptionTestEntry g_test_matrix_options[] = //SRTO_IPTTL //SRTO_IPV6ONLY //SRTO_ISN - { SRTO_KMPREANNOUNCE, "SRTO_KMPREANNOUNCE", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 1024, {-1} }, - { SRTO_KMREFRESHRATE, "SRTO_KMREFRESHRATE", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 1024, {-1} }, + { SRTO_KMPREANNOUNCE, "SRTO_KMPREANNOUNCE", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 1024, {-1}, O | W | G | S | D | O | O }, + { SRTO_KMREFRESHRATE, "SRTO_KMREFRESHRATE", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 1024, {-1}, O | W | G | S | D | O | O }, //SRTO_KMSTATE - { SRTO_LATENCY, "SRTO_LATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 200, {-1} }, + { SRTO_LATENCY, "SRTO_LATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 200, {-1}, R | W | G | S | D | O | O }, //SRTO_LINGER - { SRTO_LOSSMAXTTL, "SRTO_LOSSMAXTTL", RestrictionType::POST, sizeof(int), 0, INT32_MAX, 0, 10, {} }, - { SRTO_MAXBW, "SRTO_MAXBW", RestrictionType::POST, sizeof(int64_t), int64_t(-1), INT64_MAX, int64_t(-1), int64_t(200000), {int64_t(-2)}}, + { SRTO_LOSSMAXTTL, "SRTO_LOSSMAXTTL", RestrictionType::POST, sizeof(int), 0, INT32_MAX, 0, 10, {}, R | W | G | S | D | O | M }, + { SRTO_MAXBW, "SRTO_MAXBW", RestrictionType::POST, sizeof(int64_t), int64_t(-1), INT64_MAX, int64_t(-1), int64_t(200000), {int64_t(-2)}, R | W | G | S | D | O | O }, #ifdef ENABLE_MAXREXMITBW - { SRTO_MAXREXMITBW, "SRTO_MAXREXMITBW", RestrictionType::POST, sizeof(int64_t), int64_t(-1), INT64_MAX, int64_t(-1), int64_t(200000), {int64_t(-2)}}, + { SRTO_MAXREXMITBW, "SRTO_MAXREXMITBW", RestrictionType::POST, sizeof(int64_t), int64_t(-1), INT64_MAX, int64_t(-1), int64_t(200000), {int64_t(-2)}, R | W | G | S | D | O | O }, #endif - { SRTO_MESSAGEAPI, "SRTO_MESSAGEAPI", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, - { SRTO_MININPUTBW, "SRTO_MININPUTBW", RestrictionType::POST, sizeof(int64_t), int64_t(0), INT64_MAX, int64_t(0), int64_t(200000), {int64_t(-1)}}, - { SRTO_MINVERSION, "SRTO_MINVERSION", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0x010000, 0x010300, {} }, - { SRTO_MSS, "SRTO_MSS", RestrictionType::PREBIND, sizeof(int), 76, 65536, 1500, 1400, {-1, 0, 75} }, - { SRTO_NAKREPORT, "SRTO_NAKREPORT", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, - { SRTO_OHEADBW, "SRTO_OHEADBW", RestrictionType::POST, sizeof(int), 5, 100, 25, 20, {-1, 0, 4, 101} }, + { 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_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 //SRTO_PASSPHRASE - { SRTO_PAYLOADSIZE, "SRTO_PAYLOADSIZE", RestrictionType::PRE, sizeof(int), 0, 1456, 1316, 1400, {-1, 1500} }, + { SRTO_PAYLOADSIZE, "SRTO_PAYLOADSIZE", RestrictionType::PRE, sizeof(int), 0, 1456, 1316, 1400, {-1, 1500}, O | W | G | S | D | O | O }, //SRTO_PBKEYLEN - //SRTO_PEERIDLETIMEO - { SRTO_PEERIDLETIMEO, "SRTO_PEERIDLETIMEO", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 5000, 4500, {-1} }, - { SRTO_PEERLATENCY, "SRTO_PEERLATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 180, {-1} }, + { SRTO_PEERIDLETIMEO, "SRTO_PEERIDLETIMEO", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 5000, 4500, {-1}, R | W | G | S | D | O | M }, + { SRTO_PEERLATENCY, "SRTO_PEERLATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 0, 180, {-1}, R | W | G | S | D | O | O }, //SRTO_PEERVERSION - { SRTO_RCVBUF, "SRTO_RCVBUF", RestrictionType::PREBIND, sizeof(int), (int)(32 * SRT_PKT_SIZE), 2147483256, (int)(8192 * SRT_PKT_SIZE), 1000000, {-1} }, + { SRTO_RCVBUF, "SRTO_RCVBUF", RestrictionType::PREBIND, sizeof(int), (int)(32 * SRT_PKT_SIZE), 2147483256, (int)(8192 * SRT_PKT_SIZE), 1000000, {-1},R | W | G | S | D | O | M }, //SRTO_RCVDATA //SRTO_RCVKMSTATE - { SRTO_RCVLATENCY, "SRTO_RCVLATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 1100, {-1} }, + { SRTO_RCVLATENCY, "SRTO_RCVLATENCY", RestrictionType::PRE, sizeof(int), 0, INT32_MAX, 120, 1100, {-1}, R | W | G | S | D | O | O }, //SRTO_RCVSYN - { SRTO_RCVTIMEO, "SRTO_RCVTIMEO", RestrictionType::POST, sizeof(int), -1, INT32_MAX, -1, 2000, {-2} }, + { SRTO_RCVTIMEO, "SRTO_RCVTIMEO", RestrictionType::POST, sizeof(int), -1, INT32_MAX, -1, 2000, {-2}, R | W | G | S | O | I | O }, //SRTO_RENDEZVOUS - { SRTO_RETRANSMITALGO, "SRTO_RETRANSMITALGO", RestrictionType::PRE, sizeof(int), 0, 1, 1, 0, {-1, 2} }, + { SRTO_RETRANSMITALGO, "SRTO_RETRANSMITALGO", RestrictionType::PRE, sizeof(int), 0, 1, 1, 0, {-1, 2}, R | W | G | S | D | O | O }, //SRTO_REUSEADDR //SRTO_SENDER - { SRTO_SNDBUF, "SRTO_SNDBUF", RestrictionType::PREBIND, sizeof(int), (int)(32 * SRT_PKT_SIZE), 2147483256, (int)(8192 * SRT_PKT_SIZE), 1000000, {-1} }, + { SRTO_SNDBUF, "SRTO_SNDBUF", RestrictionType::PREBIND, sizeof(int), (int)(32 * SRT_PKT_SIZE), 2147483256, (int)(8192 * SRT_PKT_SIZE), 1000000, {-1},R | W | G | S | D | O | M }, //SRTO_SNDDATA - { SRTO_SNDDROPDELAY, "SRTO_SNDDROPDELAY", RestrictionType::POST, sizeof(int), -1, INT32_MAX, 0, 1500, {-2} }, + { SRTO_SNDDROPDELAY, "SRTO_SNDDROPDELAY", RestrictionType::POST, sizeof(int), -1, INT32_MAX, 0, 1500, {-2}, O | W | G | S | D | O | M }, //SRTO_SNDKMSTATE //SRTO_SNDSYN - { SRTO_SNDTIMEO, "SRTO_SNDTIMEO", RestrictionType::POST, sizeof(int), -1, INT32_MAX, -1, 1400, {-2} }, + { SRTO_SNDTIMEO, "SRTO_SNDTIMEO", RestrictionType::POST, sizeof(int), -1, INT32_MAX, -1, 1400, {-2}, R | W | G | S | O | I | O }, //SRTO_STATE //SRTO_STREAMID - { SRTO_TLPKTDROP, "SRTO_TLPKTDROP", RestrictionType::PRE, sizeof(bool), false, true, true, false, {} }, + { SRTO_TLPKTDROP, "SRTO_TLPKTDROP", RestrictionType::PRE, sizeof(bool), false, true, true, false, {}, R | W | G | S | D | O | O }, //SRTO_TRANSTYPE //SRTO_TSBPDMODE //SRTO_UDP_RCVBUF //SRTO_UDP_SNDBUF //SRTO_VERSION }; +} // end namespace Table +using Table::g_test_matrix_options; + +template +void CheckGetSockOptMustFail(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) +{ + ValueType opt_val; + int opt_len = (int)entry.opt_len; + EXPECT_NE(srt_getsockopt(sock, 0, entry.optid, &opt_val, &opt_len), SRT_SUCCESS) + << desc << " Getting " << entry.optname << " must fail, but succeeded."; +} template void CheckGetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const ValueType& value, const char* desc) @@ -264,6 +433,7 @@ void CheckSetSockOpt(const OptionTestEntry& entry, SRTSOCKET sock, const ValueTy template bool CheckDefaultValue(const OptionTestEntry& entry, SRTSOCKET sock, const char* desc) { + LOGD(cerr << "Will check default value: " << entry.optname << " = " << to_string(entry.dflt_val) << ": " << desc << endl); try { const ValueType dflt_val = linb::any_cast(entry.dflt_val); CheckGetSockOpt(entry, sock, dflt_val, desc); @@ -339,6 +509,7 @@ bool CheckInvalidValues(const OptionTestEntry& entry, SRTSOCKET sock, const char { for (const auto& inval : entry.invalid_vals) { + LOGD(cerr << "Will check INVALID value: " << entry.optname << " : " << to_string(inval) << ": " << sock_name << endl); try { const ValueType val = linb::any_cast(inval); CheckSetSockOpt(entry, sock, val, SRT_ERROR, sock_name); @@ -353,26 +524,76 @@ bool CheckInvalidValues(const OptionTestEntry& entry, SRTSOCKET sock, const char return true; } -TEST_F(TestSocketOptions, DefaultVals) +void TestDefaultValues(SRTSOCKET s) { + const char* test_desc = "[Caller, default]"; for (const auto& entry : g_test_matrix_options) { - const char* test_desc = "[Caller, default]"; + // Check flags. An option must be RW to test default value + const bool is_group = (s & SRTGROUP_MASK) != 0; + + if (!(entry.flags & Flags::R)) + { + // TODO: Check reading retuns an error. + LOGD(cerr << "Skipping " << entry.optname << ": not readable.\n"); + continue; // The flag must be READABLE and WRITABLE for this. + } + + // Check that retrieving a value must fail if the option is not a group option read on a group and not a socket + // option read on a socket. + bool readable = true; + if (is_group) + { + readable = entry.allof(Flags::G) && entry.anyof(Flags::I, Flags::D); + LOGD(cerr << "Group option " << entry.optname << ": expected " << (readable? "" : "NOT ") << "readable\n"); + } + else + { + readable = entry.allof(Flags::S); + LOGD(cerr << "Socket option " << entry.optname << ": expected " << (readable? "" : "NOT ") << "readable\n"); + } + + if (!readable) + { + if (entry.dflt_val.type() == typeid(bool)) + { + CheckGetSockOptMustFail(entry, s, test_desc); + } + else if (entry.dflt_val.type() == typeid(int)) + { + CheckGetSockOptMustFail(entry, s, test_desc); + } + else if (entry.dflt_val.type() == typeid(int64_t)) + { + CheckGetSockOptMustFail(entry, s, test_desc); + } + else if (entry.dflt_val.type() == typeid(const char*)) + { + CheckGetSockOptMustFail(entry, s, test_desc); + } + else + { + FAIL() << entry.optname << ": Unexpected type " << entry.dflt_val.type().name(); + } + + continue; // s is group && The option is not groupwise-individual option + } + if (entry.dflt_val.type() == typeid(bool)) { - EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); + EXPECT_TRUE(CheckDefaultValue(entry, s, test_desc)); } else if (entry.dflt_val.type() == typeid(int)) { - EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); + EXPECT_TRUE(CheckDefaultValue(entry, s, test_desc)); } else if (entry.dflt_val.type() == typeid(int64_t)) { - EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); + EXPECT_TRUE(CheckDefaultValue(entry, s, test_desc)); } else if (entry.dflt_val.type() == typeid(const char*)) { - EXPECT_TRUE(CheckDefaultValue(entry, m_caller_sock, test_desc)); + EXPECT_TRUE(CheckDefaultValue(entry, s, test_desc)); } else { @@ -381,11 +602,34 @@ TEST_F(TestSocketOptions, DefaultVals) } } +TEST_F(TestSocketOptions, DefaultVals) +{ + TestDefaultValues(m_caller_sock); +} + +#if ENABLE_BONDING +TEST_F(TestGroupOptions, DefaultVals) +{ + SRTST_REQUIRES(Bonding); + TestDefaultValues(m_caller_sock); +} +#endif + TEST_F(TestSocketOptions, MaxVals) { // Note: Changing SRTO_FC changes SRTO_RCVBUF limitation for (const auto& entry : g_test_matrix_options) { + if (!(entry.flags & Flags::R)) + { + cerr << "Skipping " << entry.optname << ": option not readable\n"; + } + + if (!(entry.flags & Flags::W)) + { + cerr << "Skipping " << entry.optname << ": option not writable\n"; + } + if (entry.optid == SRTO_KMPREANNOUNCE || entry.optid == SRTO_KMREFRESHRATE) { cerr << "Skipping " << entry.optname << "\n"; @@ -419,6 +663,16 @@ TEST_F(TestSocketOptions, MinVals) // Note: Changing SRTO_FC changes SRTO_RCVBUF limitation for (const auto& entry : g_test_matrix_options) { + if (!(entry.flags & Flags::R)) + { + cerr << "Skipping " << entry.optname << ": option not readable\n"; + } + + if (!(entry.flags & Flags::W)) + { + cerr << "Skipping " << entry.optname << ": option not writable\n"; + } + const char* test_desc = "[Caller, min val]"; if (entry.min_val.type() == typeid(bool)) { @@ -441,23 +695,28 @@ TEST_F(TestSocketOptions, MinVals) } } -TEST_F(TestSocketOptions, InvalidVals) +void TestInvalidValues(SRTSOCKET s) { // Note: Changing SRTO_FC changes SRTO_RCVBUF limitation for (const auto& entry : g_test_matrix_options) { - const char* desc = "[Caller, invalid val]"; + if (!(entry.flags & Flags::W)) + { + cerr << "Skipping " << entry.optname << ": option not writable\n"; + } + + const char* desc = "[Group Caller, invalid val]"; if (entry.dflt_val.type() == typeid(bool)) { - EXPECT_TRUE(CheckInvalidValues(entry, m_caller_sock, desc)); + EXPECT_TRUE(CheckInvalidValues(entry, s, desc)); } else if (entry.dflt_val.type() == typeid(int)) { - EXPECT_TRUE(CheckInvalidValues(entry, m_caller_sock, desc)); + EXPECT_TRUE(CheckInvalidValues(entry, s, desc)); } else if (entry.dflt_val.type() == typeid(int64_t)) { - EXPECT_TRUE(CheckInvalidValues(entry, m_caller_sock, desc)); + EXPECT_TRUE(CheckInvalidValues(entry, s, desc)); } else { @@ -468,18 +727,32 @@ TEST_F(TestSocketOptions, InvalidVals) } } +TEST_F(TestSocketOptions, InvalidVals) +{ + TestInvalidValues(m_caller_sock); +} + + +#if ENABLE_BONDING +TEST_F(TestGroupOptions, InvalidVals) +{ + SRTST_REQUIRES(Bonding); + TestInvalidValues(m_caller_sock); +} +#endif + const char* StateToStr(SRT_SOCKSTATUS st) { std::map st_to_str = { - { SRTS_INIT, "SRTS_INIT" }, - { SRTS_OPENED, "SRTS_OPENED" }, - { SRTS_LISTENING, "SRTS_LISTENING" }, + { SRTS_INIT, "SRTS_INIT" }, + { SRTS_OPENED, "SRTS_OPENED" }, + { SRTS_LISTENING, "SRTS_LISTENING" }, { SRTS_CONNECTING, "SRTS_CONNECTING" }, - { SRTS_CONNECTED, "SRTS_CONNECTED" }, - { SRTS_BROKEN, "SRTS_BROKEN" }, - { SRTS_CLOSING, "SRTS_CLOSING" }, - { SRTS_CLOSED, "SRTS_CLOSED" }, - { SRTS_NONEXIST, "SRTS_NONEXIST" } + { SRTS_CONNECTED, "SRTS_CONNECTED" }, + { SRTS_BROKEN, "SRTS_BROKEN" }, + { SRTS_CLOSING, "SRTS_CLOSING" }, + { SRTS_CLOSED, "SRTS_CLOSED" }, + { SRTS_NONEXIST, "SRTS_NONEXIST" } }; return st_to_str.find(st) != st_to_str.end() ? st_to_str.at(st) : "INVALID"; @@ -871,7 +1144,7 @@ TEST_F(TestSocketOptions, StreamIDOdd) EXPECT_EQ(srt_setsockopt(m_caller_sock, 0, SRTO_STREAMID, sid_odd.c_str(), (int)sid_odd.size()), SRT_SUCCESS); - array buffer; + std::array buffer; auto buffer_len = (int) buffer.size(); EXPECT_EQ(srt_getsockopt(m_caller_sock, 0, SRTO_STREAMID, buffer.data(), &buffer_len), SRT_SUCCESS); EXPECT_EQ(std::string(buffer.data()), sid_odd); From dca5399b5257f9d5a5524e65031cc49f8d18c8b7 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Wed, 26 Feb 2025 14:23:02 +0100 Subject: [PATCH 22/92] Automatic call of startup() and cleanup() #3098 (#3109) This pull request fixes issue #3098 by ensuring that startup() and cleanup() are called automatically. --------- Co-authored-by: Sektor van Skijlen Co-authored-by: Clement Gerouville Co-authored-by: Sektor van Skijlen --- .gitignore | 1 + srtcore/api.cpp | 224 +++++++++++++++++------------------ srtcore/api.h | 4 + srtcore/packetfilter.cpp | 97 +++++++++------ srtcore/packetfilter.h | 81 +++++++------ srtcore/socketconfig.cpp | 2 +- test/test_epoll.cpp | 5 +- test/test_fec_rebuilding.cpp | 1 - 8 files changed, 225 insertions(+), 190 deletions(-) diff --git a/.gitignore b/.gitignore index b4e22ff8d0..5fc34a6a47 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ compile_commands.json # ignore files generated by clion and cmake .idea/ cmake-build-debug/ +tags diff --git a/srtcore/api.cpp b/srtcore/api.cpp index bb5dd64feb..1108c0a1d9 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -189,11 +189,23 @@ srt::CUDTUnited::CUDTUnited() // XXX An unlikely exception thrown from the below calls // might destroy the application before `main`. This shouldn't // be a problem in general. + setupMutex(m_GCStartLock, "GCStart"); setupMutex(m_GCStopLock, "GCStop"); setupCond(m_GCStopCond, "GCStop"); setupMutex(m_GlobControlLock, "GlobControl"); setupMutex(m_IDLock, "ID"); setupMutex(m_InitLock, "Init"); + // Global initialization code +#ifdef _WIN32 + WORD wVersionRequested; + WSADATA wsaData; + wVersionRequested = MAKEWORD(2, 2); + + if (0 != WSAStartup(wVersionRequested, &wsaData)) + throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); +#endif + CCryptoControl::globalInit(); + HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); } srt::CUDTUnited::~CUDTUnited() @@ -201,11 +213,10 @@ srt::CUDTUnited::~CUDTUnited() // Call it if it wasn't called already. // This will happen at the end of main() of the application, // when the user didn't call srt_cleanup(). - if (m_bGCStatus) - { - cleanup(); - } - + enterCS(m_InitLock); + stopGarbageCollector(); + leaveCS(m_InitLock); + closeAllSockets(); releaseMutex(m_GlobControlLock); releaseMutex(m_IDLock); releaseMutex(m_InitLock); @@ -219,8 +230,11 @@ srt::CUDTUnited::~CUDTUnited() releaseCond(m_GCStopCond); #endif releaseMutex(m_GCStopLock); - + releaseMutex(m_GCStartLock); delete m_pCache; +#ifdef _WIN32 + WSACleanup(); +#endif } string srt::CUDTUnited::CONID(SRTSOCKET sock) @@ -233,39 +247,107 @@ string srt::CUDTUnited::CONID(SRTSOCKET sock) return os.str(); } -int srt::CUDTUnited::startup() +bool srt::CUDTUnited::startGarbageCollector() { - ScopedLock gcinit(m_InitLock); + + ScopedLock guard(m_GCStartLock); + if (!m_bGCStatus) + { + m_bClosing = false; + m_bGCStatus = StartThread(m_GCThread, garbageCollect, this, "SRT:GC"); + } + return m_bGCStatus; +} + +void srt::CUDTUnited::stopGarbageCollector() +{ + + ScopedLock guard(m_GCStartLock); if (m_bGCStatus) - return 1; + { + m_bGCStatus = false; + { + CUniqueSync gclock (m_GCStopLock, m_GCStopCond); + m_bClosing = true; + gclock.notify_all(); + } + m_GCThread.join(); + } +} - if (m_iInstanceCount++ > 0) - return 1; +void srt::CUDTUnited::closeAllSockets() +{ + // remove all sockets and multiplexers + HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); - // Global initialization code -#ifdef _WIN32 - WORD wVersionRequested; - WSADATA wsaData; - wVersionRequested = MAKEWORD(2, 2); + { + ScopedLock glock(m_GlobControlLock); - if (0 != WSAStartup(wVersionRequested, &wsaData)) - throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + s->breakSocket_LOCKED(); + +#if ENABLE_BONDING + if (s->m_GroupOf) + { + HLOGC(smlog.Debug, + log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() + << " (IPE?) - REMOVING FROM GROUP"); + s->removeFromGroup(false); + } #endif + m_ClosedSockets[i->first] = s; - CCryptoControl::globalInit(); + // 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; + } - PacketFilter::globalInit(); + enterCS(ls->second->m_AcceptLock); + ls->second->m_QueuedSockets.erase(s->m_SocketID); + leaveCS(ls->second->m_AcceptLock); + } + m_Sockets.clear(); - m_bClosing = false; + for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j) + { + j->second->m_tsClosureTimeStamp = steady_clock::time_point(); + } + } - if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC")) - return -1; + HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); + while (true) + { + checkBrokenSockets(); - m_bGCStatus = true; + enterCS(m_GlobControlLock); + bool empty = m_ClosedSockets.empty(); + leaveCS(m_GlobControlLock); + + if (empty) + break; + + HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep"); + srt::sync::this_thread::sleep_for(milliseconds_from(1)); + } - HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR); - return 0; +} + + +int srt::CUDTUnited::startup() +{ + ScopedLock gcinit(m_InitLock); + m_iInstanceCount++; + if (m_bGCStatus) + return (m_iInstanceCount == 1) ? 1 : 0; + else + return startGarbageCollector() ? 0 : -1; } int srt::CUDTUnited::cleanup() @@ -283,28 +365,8 @@ int srt::CUDTUnited::cleanup() if (--m_iInstanceCount > 0) return 0; - if (!m_bGCStatus) - return 0; - - { - UniqueLock gclock(m_GCStopLock); - m_bClosing = true; - } - // NOTE: we can do relaxed signaling here because - // waiting on m_GCStopCond has a 1-second timeout, - // after which the m_bClosing flag is cheched, which - // is set here above. Worst case secenario, this - // pthread_join() call will block for 1 second. - CSync::notify_one_relaxed(m_GCStopCond); - m_GCThread.join(); - - m_bGCStatus = false; - - // Global destruction code -#ifdef _WIN32 - WSACleanup(); -#endif - + stopGarbageCollector(); + closeAllSockets(); return 0; } @@ -458,6 +520,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } + startGarbageCollector(); if (pps) *pps = ns; @@ -3391,66 +3454,6 @@ void* srt::CUDTUnited::garbageCollect(void* p) HLOGC(inlog.Debug, log << "GC: sleep 1 s"); self->m_GCStopCond.wait_for(gclock, seconds_from(1)); } - - // remove all sockets and multiplexers - HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); - - { - ScopedLock glock(self->m_GlobControlLock); - - for (sockets_t::iterator i = self->m_Sockets.begin(); i != self->m_Sockets.end(); ++i) - { - CUDTSocket* s = i->second; - s->breakSocket_LOCKED(); - -#if ENABLE_BONDING - if (s->m_GroupOf) - { - HLOGC(smlog.Debug, - log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id() - << " (IPE?) - REMOVING FROM GROUP"); - s->removeFromGroup(false); - } -#endif - self->m_ClosedSockets[i->first] = s; - - // remove from listener's queue - sockets_t::iterator ls = self->m_Sockets.find(s->m_ListenSocket); - if (ls == self->m_Sockets.end()) - { - ls = self->m_ClosedSockets.find(s->m_ListenSocket); - if (ls == self->m_ClosedSockets.end()) - continue; - } - - enterCS(ls->second->m_AcceptLock); - ls->second->m_QueuedSockets.erase(s->m_SocketID); - leaveCS(ls->second->m_AcceptLock); - } - self->m_Sockets.clear(); - - for (sockets_t::iterator j = self->m_ClosedSockets.begin(); j != self->m_ClosedSockets.end(); ++j) - { - j->second->m_tsClosureTimeStamp = steady_clock::time_point(); - } - } - - HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); - while (true) - { - self->checkBrokenSockets(); - - enterCS(self->m_GlobControlLock); - bool empty = self->m_ClosedSockets.empty(); - leaveCS(self->m_GlobControlLock); - - if (empty) - break; - - HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep"); - srt::sync::this_thread::sleep_for(milliseconds_from(1)); - } - THREAD_EXIT(); return NULL; } @@ -3469,8 +3472,6 @@ int srt::CUDT::cleanup() SRTSOCKET srt::CUDT::socket() { - uglobal().startup(); - try { return uglobal().newSocket(); @@ -3518,9 +3519,6 @@ srt::CUDTGroup& srt::CUDT::newGroup(const int type) SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) { - // Doing the same lazy-startup as with srt_create_socket() - uglobal().startup(); - try { srt::sync::ScopedLock globlock(uglobal().m_GlobControlLock); diff --git a/srtcore/api.h b/srtcore/api.h index 48e7827f84..d0249ca109 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -462,6 +462,9 @@ class CUDTUnited CUDTSocket* locateAcquireSocket(SRTSOCKET u, ErrorHandling erh = ERH_RETURN); bool acquireSocket(CUDTSocket* s); + bool startGarbageCollector(); + void stopGarbageCollector(); + void closeAllSockets(); public: struct SocketKeeper @@ -534,6 +537,7 @@ class CUDTUnited private: srt::sync::atomic m_bClosing; + sync::Mutex m_GCStartLock; sync::Mutex m_GCStopLock; sync::Condition m_GCStopCond; diff --git a/srtcore/packetfilter.cpp b/srtcore/packetfilter.cpp index dc7e5b422b..e8b2a6102a 100644 --- a/srtcore/packetfilter.cpp +++ b/srtcore/packetfilter.cpp @@ -26,12 +26,13 @@ using namespace std; using namespace srt_logging; using namespace srt::sync; -bool srt::ParseFilterConfig(const string& s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf) +namespace srt { +bool PacketFilter::Internal::ParseConfig(const string& s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf) { if (!SrtParseConfig(s, (w_config))) return false; - PacketFilter::Factory* fac = PacketFilter::find(w_config.type); + PacketFilter::Factory* fac = find(w_config.type); if (!fac) return false; @@ -43,24 +44,26 @@ bool srt::ParseFilterConfig(const string& s, SrtFilterConfig& w_config, PacketFi return true; } -bool srt::ParseFilterConfig(const string& s, SrtFilterConfig& w_config) +bool ParseFilterConfig(const std::string& s, SrtFilterConfig& w_config) { - return ParseFilterConfig(s, (w_config), NULL); + return PacketFilter::internal().ParseConfig(s, (w_config), NULL); } // Parameters are passed by value because they need to be potentially modicied inside. -bool srt::CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer) +bool PacketFilter::Internal::CheckFilterCompat(SrtFilterConfig& w_agent, const SrtFilterConfig& peer_in) { - PacketFilter::Factory* fac = PacketFilter::find(w_agent.type); + PacketFilter::Factory* fac = find(w_agent.type); if (!fac) return false; SrtFilterConfig defaults; - if (!ParseFilterConfig(fac->defaultConfig(), (defaults))) + if (!ParseConfig(fac->defaultConfig(), (defaults))) { return false; } + // Make a copy so that modifications can be done. This is only required for internal checks. + SrtFilterConfig peer = peer_in; set keys; // Extract all keys to identify also unspecified parameters on both sides // Note that theoretically for FEC it could simply check for the "cols" parameter @@ -109,20 +112,18 @@ bool srt::CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer) return true; } -namespace srt { - struct SortBySequence +struct SortBySequence +{ + bool operator()(const CUnit* u1, const CUnit* u2) { - bool operator()(const CUnit* u1, const CUnit* u2) - { - int32_t s1 = u1->m_Packet.getSeqNo(); - int32_t s2 = u2->m_Packet.getSeqNo(); + int32_t s1 = u1->m_Packet.getSeqNo(); + int32_t s2 = u2->m_Packet.getSeqNo(); - return CSeqNo::seqcmp(s1, s2) < 0; - } - }; -} // namespace srt + return CSeqNo::seqcmp(s1, s2) < 0; + } +}; -void srt::PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs) +void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs) { const CPacket& rpkt = unit->m_Packet; @@ -205,7 +206,7 @@ void srt::PacketFilter::receive(CUnit* unit, std::vector& w_incoming, lo } -bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) +bool PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet) { bool have = m_filter->packControlPacket(m_sndctlpkt, seq); if (!have) @@ -237,7 +238,7 @@ bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_pack } -void srt::PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) +void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) { if (m_provided.empty()) return; @@ -272,42 +273,61 @@ void srt::PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq) m_provided.clear(); } -bool srt::PacketFilter::IsBuiltin(const string& s) +// Placement here is necessary in order to mark the location to +// store the PacketFilter::Factory class characteristic object. +PacketFilter::Factory::~Factory() { - return builtin_filters.count(s); } -namespace srt { -std::set PacketFilter::builtin_filters; -PacketFilter::filters_map_t PacketFilter::filters; +#if HAVE_CXX11 + +PacketFilter::Internal& PacketFilter::internal() +{ + static PacketFilter::Internal instance; + return instance; +} + +#else // !HAVE_CXX11 + +static pthread_once_t s_PacketFactoryOnce = PTHREAD_ONCE_INIT; + +static PacketFilter::Internal *getInstance() +{ + static PacketFilter::Internal instance; + return &instance; } -srt::PacketFilter::Factory::~Factory() +PacketFilter::Internal& PacketFilter::internal() { + // We don't want lock each time, pthread_once can be faster than mutex. + pthread_once(&s_PacketFactoryOnce, reinterpret_cast(getInstance)); + return *getInstance(); } -void srt::PacketFilter::globalInit() +#endif + +PacketFilter::Internal::Internal() { // Add here builtin packet filters and mark them // as builtin. This will disallow users to register // external filters with the same name. - filters["fec"] = new Creator; - builtin_filters.insert("fec"); + m_filters["fec"] = new PacketFilter::Creator; + m_builtin_filters.insert("fec"); } -bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr) +bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr) { m_parent = parent; SrtFilterConfig cfg; - if (!ParseFilterConfig(confstr, (cfg))) + if (!internal().ParseConfig(confstr, (cfg))) return false; // Extract the "type" key from parameters, or use // builtin if lacking. - filters_map_t::iterator selector = filters.find(cfg.type); - if (selector == filters.end()) + PacketFilter::Factory *factory = internal().find(cfg.type); + if (factory == NULL) return false; SrtFilterInitializer init; @@ -324,7 +344,7 @@ bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::strin << init.payload_size << " rcvbuf size=" << init.rcvbuf_size); // Found a filter, so call the creation function - m_filter = selector->second->Create(init, m_provided, confstr); + m_filter = factory->Create(init, m_provided, confstr); if (!m_filter) return false; @@ -336,7 +356,7 @@ bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::strin return true; } -bool srt::PacketFilter::correctConfig(const SrtFilterConfig& conf) +bool PacketFilter::correctConfig(const SrtFilterConfig& conf) { const string* pname = map_getp(conf.parameters, "type"); @@ -346,15 +366,16 @@ bool srt::PacketFilter::correctConfig(const SrtFilterConfig& conf) if (*pname == "adaptive") return true; - filters_map_t::iterator x = filters.find(*pname); - if (x == filters.end()) + PacketFilter::Factory *factory = internal().find(*pname); + if (factory == NULL) return false; return true; } -srt::PacketFilter::~PacketFilter() +PacketFilter::~PacketFilter() { delete m_filter; } +} // namespace srt diff --git a/srtcore/packetfilter.h b/srtcore/packetfilter.h index 429e81e795..2d1105042c 100644 --- a/srtcore/packetfilter.h +++ b/srtcore/packetfilter.h @@ -51,7 +51,6 @@ class PacketFilter virtual ~Factory(); }; private: - friend bool ParseFilterConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf); template class Creator: public Factory @@ -74,7 +73,6 @@ class PacketFilter virtual ~Creator() {} }; - // We need a private wrapper for the auto-pointer, can't use // std::unique_ptr here due to no C++11. struct ManagedPtr @@ -114,14 +112,6 @@ class PacketFilter Factory* get() { return f; } }; - // The list of builtin names that are reserved. - static std::set builtin_filters; - - // Temporarily changed to linear searching, until this is exposed - // for a user-defined filter. - typedef std::map filters_map_t; - static filters_map_t filters; - // This is a filter container. SrtPacketFilterBase* m_filter; void Check() @@ -133,29 +123,52 @@ class PacketFilter // Don't do any check for now. } + friend class Internal; + public: + class Internal + { + public: - static void globalInit(); + // The list of builtin names that are reserved. + std::set m_builtin_filters; - static bool IsBuiltin(const std::string&); + // Temporarily changed to linear searching, until this is exposed + // for a user-defined filter. + typedef std::map filters_map_t; + filters_map_t m_filters; - template - static bool add(const std::string& name) - { - if (IsBuiltin(name)) - return false; - filters[name] = new Creator; - return true; - } + public: - static Factory* find(const std::string& type) - { - filters_map_t::iterator i = filters.find(type); - if (i == filters.end()) - return NULL; // No matter what to return - this is "undefined behavior" to be prevented - return i->second.get(); - } + bool IsBuiltin(const std::string& s) + { + return m_builtin_filters.count(s); + } + + template + bool add(const std::string& name) + { + if (IsBuiltin(name)) + return false; + + m_filters[name] = new Creator; + return true; + } + + Factory* find(const std::string& type) + { + filters_map_t::iterator i = m_filters.find(type); + if (i == m_filters.end()) + return NULL; // No matter what to return - this is "undefined behavior" to be prevented + return i->second.get(); + } + bool ParseConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf = NULL); + bool CheckFilterCompat(srt::SrtFilterConfig& w_agent, const srt::SrtFilterConfig& peer); + public: + Internal(); + }; + static Internal& internal(); // Singleton accessor // Filter is optional, so this check should be done always // manually. @@ -188,8 +201,8 @@ class PacketFilter ~PacketFilter(); // Simple wrappers - void feedSource(CPacket& w_packet); - SRT_ARQLevel arqLevel(); + void feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); } + SRT_ARQLevel arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); } bool packControlPacket(int32_t seq, int kflg, CPacket& w_packet); void receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs); @@ -207,12 +220,10 @@ class PacketFilter std::vector m_provided; }; -bool CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer); - -inline void PacketFilter::feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); } -inline SRT_ARQLevel PacketFilter::arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); } - -bool ParseFilterConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf); +inline bool CheckFilterCompat(SrtFilterConfig& w_agent, const SrtFilterConfig& peer) +{ + return PacketFilter::internal().CheckFilterCompat((w_agent), peer); +} } // namespace srt diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index 1c067d0595..a0c8a6a47b 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -811,7 +811,7 @@ struct CSrtConfigSetter // Parse the configuration string prematurely SrtFilterConfig fc; PacketFilter::Factory* fax = 0; - if (!ParseFilterConfig(arg, (fc), (&fax))) + if (!PacketFilter::internal().ParseConfig(arg, (fc), (&fax))) { LOGC(aclog.Error, log << "SRTO_PACKETFILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. " diff --git a/test/test_epoll.cpp b/test/test_epoll.cpp index a8a88aa7ad..f9d8380308 100644 --- a/test/test_epoll.cpp +++ b/test/test_epoll.cpp @@ -12,6 +12,7 @@ using namespace std; using namespace srt; +#define TEST_UDP_PORT 9990 TEST(CEPoll, InfiniteWait) { @@ -571,7 +572,7 @@ class TestEPoll: public srt::Test sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; - sa.sin_port = htons(9999); + sa.sin_port = htons(TEST_UDP_PORT); ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); @@ -645,7 +646,7 @@ class TestEPoll: public srt::Test sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; - sa.sin_port = htons(9999); + sa.sin_port = htons(TEST_UDP_PORT); sa.sin_addr.s_addr = INADDR_ANY; sockaddr* psa = (sockaddr*)&sa; diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index 7e14992125..3a7cb0791e 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -29,7 +29,6 @@ class TestFECRebuilding: public srt::Test TestFECRebuilding() { // Required to make ParseCorrectorConfig work - PacketFilter::globalInit(); } void setup() override From 8bc76ac04e36e1e3dcf4c2f81b6e51092d5dd660 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Thu, 27 Feb 2025 13:20:49 +0100 Subject: [PATCH 23/92] [docs] Documentation fixes for socket options (#3125). --- docs/API/API-socket-options.md | 56 ++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index d74907df11..c550845a71 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -201,7 +201,7 @@ The following table lists SRT API socket options in alphabetical order. Option d | Option Name | Since | Restrict | Type | Units | Default | Range | Dir |Entity | | :------------------------------------------------------ | :---: | :------: | :-------: | :-----: | :---------------: | :------: |:---:|:-----:| -| [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | | | RW | S | +| [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | "" | \* | RW | S | | [`SRTO_CONGESTION`](#SRTO_CONGESTION) | 1.3.0 | pre | `string` | | "live" | \* | W | S | | [`SRTO_CONNTIMEO`](#SRTO_CONNTIMEO) | 1.1.2 | pre | `int32_t` | ms | 3000 | 0.. | W | GSD+ | | [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE) | 1.5.2 | pre | `int32_t` | | 0 (Auto) | [0, 2] | W | GSD | @@ -210,7 +210,7 @@ The following table lists SRT API socket options in alphabetical order. Option d | [`SRTO_EVENT`](#SRTO_EVENT) | | | `int32_t` | flags | | | R | S | | [`SRTO_FC`](#SRTO_FC) | | pre | `int32_t` | pkts | 25600 | 32.. | RW | GSD | | [`SRTO_GROUPCONNECT`](#SRTO_GROUPCONNECT) | 1.5.0 | pre | `int32_t` | | 0 | 0...1 | W | S | -| [`SRTO_GROUPMINSTABLETIMEO`](#SRTO_GROUPMINSTABLETIMEO) | 1.5.0 | pre | `int32_t` | ms | 60 | 60-... | W | GDI+ | +| [`SRTO_GROUPMINSTABLETIMEO`](#SRTO_GROUPMINSTABLETIMEO) | 1.5.0 | pre | `int32_t` | ms | 60 | 60-... | W | GDI | | [`SRTO_GROUPTYPE`](#SRTO_GROUPTYPE) | 1.5.0 | | `int32_t` | enum | | | R | S | | [`SRTO_INPUTBW`](#SRTO_INPUTBW) | 1.0.5 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD | | [`SRTO_IPTOS`](#SRTO_IPTOS) | 1.0.5 | pre-bind | `int32_t` | | (system) | 0..255 | RW | GSD | @@ -263,6 +263,48 @@ The following table lists SRT API socket options in alphabetical order. Option d | [`SRTO_UDP_SNDBUF`](#SRTO_UDP_SNDBUF) | | pre-bind | `int32_t` | bytes | 65536 | \* | RW | GSD+ | | [`SRTO_VERSION`](#SRTO_VERSION) | 1.1.0 | | `int32_t` | | | | R | S | +### Short summary for some general options' characteristics + +The following options cannot be set on a group: + +* [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) - link-specific +* [`SRTO_CONGESTION`](#SRTO_CONGESTION) - "live" mode is the only supported for groups +* [`SRTO_GROUPCONNECT`](#SRTO_GROUPCONNECT) - to be set for a listener only +* [`SRTO_RENDEZVOUS`](#SRTO_RENDEZVOUS) - groups support only caller-listener mode +* [`SRTO_SENDER`](#SRTO_SENDER) - legacy option for <1.3.0, not available for bonding +* [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) - live mode (default) is the only supported for groups +* [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) + +The following options cannot be retrieved from a group: + +* [`SRTO_EVENT`](#SRTO_EVENT) - not supported +* [`SRTO_GROUPTYPE`](#SRTO_GROUPTYPE) - this is information for an incoming socket only +* [`SRTO_ISN`](#SRTO_ISN) - socket-specific +* [`SRTO_KMSTATE`](#SRTO_KMSTATE) - connection-specific +* [`SRTO_RCVDATA`](#SRTO_RCVDATA) - socket-specific +* [`SRTO_RCVKMSTATE`](#SRTO_RCVKMSTATE) - connection-specific +* [`SRTO_SNDDATA`](#SRTO_SNDDATA) - socket-specific (sender buffer is still one per socket, also in groups) +* [`SRTO_SNDKMSTATE`](#SRTO_SNDKMSTATE) - connection-specific +* [`SRTO_STATE`](#SRTO_STATE) - socket-specific state for the connection +* [`SRTO_VERSION`](#SRTO_VERSION) - not supported + +The following options can be set on a socket or on a group, but for +a group it has a different meaning than for a socket and they also +cannot be set on a group member socket. The reason for all of them +is such that the socket usually has this option set as required for +the group internals to work properly, while a member socket cannot +be used for reading and writing operations. + +* [`SRTO_RCVSYN`](#SRTO_RCVSYN) +* [`SRTO_RCVTIMEO`](#SRTO_RCVTIMEO) +* [`SRTO_SNDSYN`](#SRTO_SNDSYN) +* [`SRTO_SNDTIMEO`](#SRTO_SNDTIMEO) + +The following options can be set only on a group and not on a socket: + +* [`SRTO_GROUPMINSTABLETIMEO`](#SRTO_GROUPMINSTABLETIMEO) - specific for a group of type `SRT_GTYPE_BACKUP`. + + ### Option Descriptions #### SRTO_BINDTODEVICE @@ -304,7 +346,7 @@ if an appropriate instruction was given in the Stream ID. Currently supported congestion controllers are designated as "live" and "file", which correspond to the Live and File modes. -Note that it is not recommended to change this option manually, but you should +Note that it is not recommended to change this option directly, but you should rather change the whole set of options using the [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) option. [Return to list](#list-of-options) @@ -317,9 +359,11 @@ rather change the whole set of options using the [`SRTO_TRANSTYPE`](#SRTO_TRANST | ------------------ | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ | | `SRTO_CONNTIMEO` | 1.1.2 | pre | `int32_t` | msec | 3000 | 0.. | W | GSD+ | -Connect timeout. This option applies to the caller and rendezvous connection -modes. For the rendezvous mode (see `SRTO_RENDEZVOUS`) the effective connection timeout -will be 10 times the value set with `SRTO_CONNTIMEO`. +Connection timeout value in milliseconds. This is the time up to which the connecting +facility will attempt to connect and wait for the response from the remote endpoint +before giving up with error status. The value applies for both caller and +rendezvous modes. For the rendezvous mode (see `SRTO_RENDEZVOUS`) the effective +connection timeout will be 10 times the value set with `SRTO_CONNTIMEO`. [Return to list](#list-of-options) From df81ecdff828cc8c5d10afd8a66b60fd799b1939 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Thu, 27 Feb 2025 13:26:04 +0100 Subject: [PATCH 24/92] [build] Fixed outdated SonarQube configuration (#3124). --- .github/workflows/cxx11-ubuntu.yaml | 4 ++-- sonar-project.properties | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index 918bbcd226..5566ed8019 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -19,7 +19,7 @@ jobs: - name: configure run: | mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: build run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . - name: test @@ -35,4 +35,4 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options. - run: sonar-scanner --define sonar.cfamily.build-wrapper-output=_build/"${{ env.BUILD_WRAPPER_OUT_DIR }}" + run: sonar-scanner --define sonar.cfamily.compile-commands="_build/compile_commands.json" diff --git a/sonar-project.properties b/sonar-project.properties index d11323df85..4bdaccdb22 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -32,5 +32,9 @@ sonar.sources=srtcore/,apps/,common/,examples/,haicrypt/,scripts/,testing/ sonar.tests=test/ # Properties specific to the C/C++ analyzer: -sonar.cfamily.build-wrapper-output=_build/sonar-output +#sonar.cfamily.build-wrapper-output=_build/sonar-output +# This field is deprecated. compile-commands should be used +# instead, and this property is set in the action specification +# Should be something like: +#sonar.cfamily.compile-commands=_build/compile_commands.json sonar.cfamily.gcov.reportsPath=. From 697dce0978c9e8c2f8fff4d1443f2cb69941bdc2 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 28 Feb 2025 10:02:28 +0100 Subject: [PATCH 25/92] [apps] Fixed correct bind address selection for windows multicast (#3126). Co-authored-by: BX --- apps/transmitmedia.cpp | 107 ++++++++++++++++++++------------------ scripts/build-windows.ps1 | 2 + testing/testmedia.cpp | 86 ++++++++++++++++-------------- testing/testmedia.hpp | 4 +- 4 files changed, 108 insertions(+), 91 deletions(-) diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 8e535cf07a..862875fa2e 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -805,8 +805,10 @@ class UdpCommon { protected: int m_sock = -1; - sockaddr_any sadr; string adapter; + sockaddr_any interface_addr; + sockaddr_any target_addr; + bool is_multicast = false; map m_options; void Setup(string host, int port, map attr) @@ -821,33 +823,35 @@ class UdpCommon // set non-blocking mode #if defined(_WIN32) unsigned long ulyes = 1; - if (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR) + const bool ioctl_error = (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR); #else - if (ioctl(m_sock, FIONBIO, (const char *)&yes) < 0) + const bool ioctl_error = (ioctl(m_sock, FIONBIO, (const char *)&yes) == -1); #endif + if (ioctl_error) { Error(SysError(), "UdpCommon::Setup: ioctl FIONBIO"); } - sadr = CreateAddr(host, port); + interface_addr = CreateAddr("", port, AF_INET); + target_addr = CreateAddr(host, port); - bool is_multicast = false; + is_multicast = false; if (attr.count("multicast")) { // XXX: Here provide support for IPv6 multicast #1479 - if (sadr.family() != AF_INET) + if (target_addr.family() != AF_INET) { throw std::runtime_error("UdpCommon: Multicast on IPv6 is not yet supported"); } - if (!IsMulticast(sadr.sin.sin_addr)) + if (!IsMulticast(target_addr.sin.sin_addr)) { throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address"); } is_multicast = true; } - else if (sadr.family() == AF_INET && IsMulticast(sadr.sin.sin_addr)) + else if (target_addr.family() == AF_INET && IsMulticast(target_addr.sin.sin_addr)) { is_multicast = true; } @@ -855,23 +859,19 @@ class UdpCommon if (is_multicast) { ip_mreq mreq; - sockaddr_any maddr (AF_INET); int opt_name; - void* mreq_arg_ptr; + const char* mreq_arg_ptr; socklen_t mreq_arg_size; adapter = attr.count("adapter") ? attr.at("adapter") : string(); - if ( adapter == "" ) + if (adapter == "") { Verb() << "Multicast: home address: INADDR_ANY:" << port; - maddr.sin.sin_family = AF_INET; - maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - maddr.sin.sin_port = htons(port); // necessary for temporary use } else { Verb() << "Multicast: home address: " << adapter << ":" << port; - maddr = CreateAddr(adapter, port); + interface_addr = CreateAddr(adapter, port); } if (attr.count("source")) @@ -880,11 +880,11 @@ class UdpCommon ip_mreq_source mreq_ssm; /* this is an ssm. we need to use the right struct and opt */ opt_name = IP_ADD_SOURCE_MEMBERSHIP; - mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq_ssm.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq_ssm.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); - mreq_arg_ptr = &mreq_ssm; + mreq_arg_ptr = (const char*)&mreq_ssm; #else throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS"); #endif @@ -892,39 +892,20 @@ class UdpCommon else { opt_name = IP_ADD_MEMBERSHIP; - mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); - mreq_arg_ptr = &mreq; + mreq_arg_ptr = (const char*)&mreq; } #ifdef _WIN32 - const char* mreq_arg = (const char*)mreq_arg_ptr; const auto status_error = SOCKET_ERROR; #else - const void* mreq_arg = mreq_arg_ptr; const auto status_error = -1; #endif + auto res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg_ptr, mreq_arg_size); -#if defined(_WIN32) || defined(__CYGWIN__) - // On Windows it somehow doesn't work when bind() - // is called with multicast address. Write the address - // that designates the network device here. - // Also, sets port sharing when working with multicast - sadr = maddr; - int reuse = 1; - int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); - if (shareAddrRes == status_error) - { - throw runtime_error("marking socket for shared use failed"); - } - Verb() << "Multicast(Windows): will bind to home address"; -#else - Verb() << "Multicast(POSIX): will bind to IGMP address: " << host; -#endif - int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size); - - if ( res == status_error ) + if (res == status_error) { Error(errno, "adding to multicast membership failed"); } @@ -998,8 +979,35 @@ class UdpSource: public Source, public UdpCommon UdpSource(string host, int port, const map& attr) { Setup(host, port, attr); - int stat = ::bind(m_sock, sadr.get(), sadr.size()); - if ( stat == -1 ) + + // On Windows it somehow doesn't work when bind() + // is called with multicast address. Write the address + // that designates the network device here, always. + + // Hance we have two fields holding the address: + // * target_addr: address from which reading will be done + // * unicast: local address of the interface + // * multicast: the IGMP address + // * interface_addr: address of the local interface + // (same as target_addr for unicast) + + // In case of multicast, binding should be done: + // On most POSIX systems, to the multicast address (target_addr in this case) + // On Windows, to a local address (hence use interface_addr, as in this + // case it differs to target_addr). + +#if defined(_WIN32) || defined(__CYGWIN__) + sockaddr_any baddr = is_multicast ? interface_addr : target_addr; + static const char* const sysname = "Windows"; +#else + static const char* const sysname = "POSIX"; + sockaddr_any baddr = target_addr; +#endif + Verb("UDP:", is_multicast ? "Multicast" : "Unicast", "(", sysname, + "): will bind to: ", baddr.str()); + int stat = ::bind(m_sock, baddr.get(), baddr.size()); + + if (stat == -1) Error(SysError(), "Binding address for UDP"); eof = false; } @@ -1009,7 +1017,7 @@ class UdpSource: public Source, public UdpCommon if (pkt.payload.size() < chunk) pkt.payload.resize(chunk); - sockaddr_any sa(sadr.family()); + sockaddr_any sa(target_addr.family()); socklen_t si = sa.size(); int stat = recvfrom(m_sock, pkt.payload.data(), (int) chunk, 0, sa.get(), &si); if (stat < 1) @@ -1045,15 +1053,14 @@ class UdpTarget: public Target, public UdpCommon cerr << "\nWARN Host for UDP target is not provided. Will send to localhost:" << port << ".\n"; Setup(host, port, attr); - if (adapter != "") + if (is_multicast && interface_addr.isany() == false) { - sockaddr_any maddr = CreateAddr(adapter, 0); - if (maddr.family() != AF_INET) + if (interface_addr.family() != AF_INET) { Error(0, "UDP/target: IPv6 multicast not supported in the application"); } - in_addr addr = maddr.sin.sin_addr; + in_addr addr = interface_addr.sin.sin_addr; int res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&addr), sizeof(addr)); if (res == -1) @@ -1066,7 +1073,7 @@ class UdpTarget: public Target, public UdpCommon int Write(const char* data, size_t len, int64_t src_time SRT_ATR_UNUSED, ostream & ignored SRT_ATR_UNUSED = cout) override { - int stat = sendto(m_sock, data, (int) len, 0, sadr.get(), sadr.size()); + int stat = sendto(m_sock, data, (int)len, 0, target_addr.get(), target_addr.size()); if ( stat == -1 ) { if ((false)) diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1 index d2f07972d9..c4c7b5740b 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -17,6 +17,7 @@ param ( [Parameter()][String]$STATIC_LINK_SSL = "OFF", [Parameter()][String]$CXX11 = "ON", [Parameter()][String]$BUILD_APPS = "ON", + [Parameter()][String]$BUILD_TESTAPPS = "OFF", [Parameter()][String]$UNIT_TESTS = "OFF", [Parameter()][String]$BUILD_DIR = "_build", [Parameter()][String]$VCPKG_OPENSSL = "OFF", @@ -135,6 +136,7 @@ $cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " + "-DENABLE_APPS=$BUILD_APPS " + "-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " + "-DENABLE_BONDING=$BONDING " + + "-DENABLE_TESTING=$BUILD_TESTAPPS " + "-DENABLE_UNITTESTS=$UNIT_TESTS" # if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index a7eb0541c9..2c36a4d127 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -2753,20 +2753,22 @@ void UdpCommon::Setup(string host, int port, map attr) int yes = 1; ::setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&yes, sizeof yes); - sadr = CreateAddr(host, port); + interface_addr = CreateAddr("", port, AF_INET); + target_addr = CreateAddr(host, port); - bool is_multicast = false; - if (sadr.family() == AF_INET) + is_multicast = false; + + if (target_addr.family() == AF_INET) { if (attr.count("multicast")) { - if (!IsMulticast(sadr.sin.sin_addr)) + if (!IsMulticast(target_addr.sin.sin_addr)) { throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address"); } is_multicast = true; } - else if (IsMulticast(sadr.sin.sin_addr)) + else if (IsMulticast(target_addr.sin.sin_addr)) { is_multicast = true; } @@ -2774,23 +2776,19 @@ void UdpCommon::Setup(string host, int port, map attr) if (is_multicast) { ip_mreq mreq; - sockaddr_any maddr (AF_INET); int opt_name; - void* mreq_arg_ptr; + const char* mreq_arg_ptr; socklen_t mreq_arg_size; adapter = attr.count("adapter") ? attr.at("adapter") : string(); if (adapter == "") { Verb() << "Multicast: home address: INADDR_ANY:" << port; - maddr.sin.sin_family = AF_INET; - maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY); - maddr.sin.sin_port = htons(port); // necessary for temporary use } else { Verb() << "Multicast: home address: " << adapter << ":" << port; - maddr = CreateAddr(adapter, port); + interface_addr = CreateAddr(adapter, port); } if (attr.count("source")) @@ -2799,11 +2797,11 @@ void UdpCommon::Setup(string host, int port, map attr) ip_mreq_source mreq_ssm; /* this is an ssm. we need to use the right struct and opt */ opt_name = IP_ADD_SOURCE_MEMBERSHIP; - mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq_ssm.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq_ssm.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); - mreq_arg_ptr = &mreq_ssm; + mreq_arg_ptr = (const char*)&mreq_ssm; #else throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS"); #endif @@ -2811,37 +2809,18 @@ void UdpCommon::Setup(string host, int port, map attr) else { opt_name = IP_ADD_MEMBERSHIP; - mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr; - mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr; + mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr; + mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr; mreq_arg_size = sizeof(mreq); - mreq_arg_ptr = &mreq; + mreq_arg_ptr = (const char*)&mreq; } #ifdef _WIN32 - const char* mreq_arg = (const char*)mreq_arg_ptr; const auto status_error = SOCKET_ERROR; #else - const void* mreq_arg = mreq_arg_ptr; const auto status_error = -1; #endif - -#if defined(_WIN32) || defined(__CYGWIN__) - // On Windows it somehow doesn't work when bind() - // is called with multicast address. Write the address - // that designates the network device here. - // Also, sets port sharing when working with multicast - sadr = maddr; - int reuse = 1; - int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse)); - if (shareAddrRes == status_error) - { - throw runtime_error("marking socket for shared use failed"); - } - Verb() << "Multicast(Windows): will bind to home address"; -#else - Verb() << "Multicast(POSIX): will bind to IGMP address: " << host; -#endif - int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size); + auto res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg_ptr, mreq_arg_size); if (res == status_error) { @@ -2913,7 +2892,34 @@ UdpCommon::~UdpCommon() UdpSource::UdpSource(string host, int port, const map& attr) { Setup(host, port, attr); - int stat = ::bind(m_sock, sadr.get(), sadr.size()); + + // On Windows it somehow doesn't work when bind() + // is called with multicast address. Write the address + // that designates the network device here, always. + + // Hance we have two fields holding the address: + // * target_addr: address from which reading will be done + // * unicast: local address of the interface + // * multicast: the IGMP address + // * interface_addr: address of the local interface + // (same as target_addr for unicast) + + // In case of multicast, binding should be done: + // On most POSIX systems, to the multicast address (target_addr in this case) + // On Windows, to a local address (hence use interface_addr, as in this + // case it differs to target_addr). + +#if defined(_WIN32) || defined(__CYGWIN__) + sockaddr_any baddr = is_multicast ? interface_addr : target_addr; + static const char* const sysname = "Windows"; +#else + static const char* const sysname = "POSIX"; + sockaddr_any baddr = target_addr; +#endif + Verb("UDP:", is_multicast ? "Multicast" : "Unicast", "(", sysname, + "): will bind to: ", baddr.str()); + int stat = ::bind(m_sock, baddr.get(), baddr.size()); + if (stat == -1) Error(SysError(), "Binding address for UDP"); eof = false; @@ -2927,7 +2933,7 @@ UdpSource::UdpSource(string host, int port, const map& attr) MediaPacket UdpSource::Read(size_t chunk) { bytevector data(chunk); - sockaddr_any sa(sadr.family()); + sockaddr_any sa(target_addr.family()); int64_t srctime = 0; AGAIN: int stat = recvfrom(m_sock, data.data(), (int) chunk, 0, sa.get(), &sa.syslen()); @@ -2975,7 +2981,7 @@ UdpTarget::UdpTarget(string host, int port, const map& attr) void UdpTarget::Write(const MediaPacket& data) { - int stat = sendto(m_sock, data.payload.data(), int(data.payload.size()), 0, (sockaddr*)&sadr, int(sizeof sadr)); + int stat = sendto(m_sock, data.payload.data(), int(data.payload.size()), 0, target_addr.get(), (int)target_addr.size()); if (stat == -1) Error(SysError(), "UDP Write/sendto"); } diff --git a/testing/testmedia.hpp b/testing/testmedia.hpp index 470e825ef4..36a6491300 100644 --- a/testing/testmedia.hpp +++ b/testing/testmedia.hpp @@ -304,8 +304,10 @@ class UdpCommon { protected: int m_sock = -1; - srt::sockaddr_any sadr; std::string adapter; + srt::sockaddr_any interface_addr; + srt::sockaddr_any target_addr; + bool is_multicast = false; std::map m_options; void Setup(std::string host, int port, std::map attr); void Error(int err, std::string src); From f3a8fe3f2883b0dacbc8400b97d474d3997e898b Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 12 Mar 2025 11:14:23 +0100 Subject: [PATCH 26/92] [core] Fixed bug: tsbpd might miss m_bClosing flag setting, flag not always properly set. (#1709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [core] Fixed bug: tsbpd might miss m_bClosing flag setting, flag not always properly set. Added some sanity checks * Removed excessive variable * Added important comment from another PR * Localized the bWokeUpOnSignal variable better * Removed time-interrupted waiting on TsbPd thread as it caused more CPU usage --------- Co-authored-by: Mikołaj Małecki --- srtcore/core.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index dc1e2d118b..0a8cc012b6 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -4951,6 +4951,11 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, s->m_Status = SRTS_CONNECTED; // acknowledde any waiting epolls to write + // This must be done AFTER the group member status is upgraded to IDLE because + // this state change will trigger the waiting function in blocking-mode groupConnect + // and this may be immediately followed by exit from connect and start sending function, + // which must see this very link already as IDLE, not PENDING, which will make this + // link unable to be used and therefore the sending call would fail. uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_CONNECT, true); CGlobEvent::triggerEvent(); @@ -5541,6 +5546,12 @@ void * srt::CUDT::tsbpd(void* param) gkeeper.group->updateLatestRcv(self->m_parent); } } + + // After re-acquisition of the m_RecvLock, re-check the closing flag + if (self->m_bClosing) + { + break; + } #endif CGlobEvent::triggerEvent(); tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. @@ -5566,6 +5577,7 @@ void * srt::CUDT::tsbpd(void* param) THREAD_PAUSED(); bWokeUpOnSignal = tsbpd_cc.wait_until(tsNextDelivery); THREAD_RESUMED(); + HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP on " << (bWokeUpOnSignal? "SIGNAL" : "TIMEOUIT") << "!!!"); } else { @@ -6328,7 +6340,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT // Inform the threads handler to stop. m_bClosing = true; - HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock"); + HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE (closing=true). Acquiring connection lock"); ScopedLock connectguard(m_ConnectionLock); @@ -7824,6 +7836,11 @@ void srt::CUDT::destroySynch() void srt::CUDT::releaseSynch() { SRT_ASSERT(m_bClosing); + if (!m_bClosing) + { + LOGC(smlog.Error, log << "releaseSynch: IPE: m_bClosing not set to false, TSBPD might hangup!"); + m_bClosing = true; + } // wake up user calls CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock); @@ -7831,8 +7848,8 @@ void srt::CUDT::releaseSynch() leaveCS(m_SendLock); // Awake tsbpd() and srt_recv*(..) threads for them to check m_bClosing. - CSync::lock_notify_one(m_RecvDataCond, m_RecvLock); - CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock); + CSync::lock_notify_all(m_RecvDataCond, m_RecvLock); + CSync::lock_notify_all(m_RcvTsbPdCond, m_RecvLock); // Azquiring m_RcvTsbPdStartupLock protects race in starting // the tsbpd() thread in CUDT::processData(). @@ -9939,7 +9956,7 @@ void srt::CUDT::processClose() m_bBroken = true; m_iBrokenCounter = 60; - HLOGP(smlog.Debug, "processClose: sent message and set flags"); + HLOGP(smlog.Debug, "processClose: (closing=true) sent message and set flags"); if (m_bTsbPd) { @@ -11691,6 +11708,7 @@ void srt::CUDT::checkTimers() void srt::CUDT::updateBrokenConnection() { + HLOGC(smlog.Debug, log << "updateBrokenConnection: setting closing=true and taking out epoll events"); m_bClosing = true; releaseSynch(); // app can call any UDT API to learn the connection_broken error From f8e1bd6d3cc8f3abb1f4c60cb8b9a4359a2d7892 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Mon, 10 Mar 2025 12:41:49 +0100 Subject: [PATCH 27/92] [core] Late-rejection when listener responds with no packet filter --- srtcore/core.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 0a8cc012b6..fef0d0c2a8 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -2985,6 +2985,34 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, return false; } + // Check if the filter was configured on agent, + // but the peer responded with no filter. + if (!m_config.sPacketFilterConfig.empty() && !have_filter) + { + if (m_SrtHsSide == HSD_INITIATOR) + { + // IMPORTANT: + // If agent didn't set the packet filter in the configuration, the peer + // could have responded with its own configuration and enforce the packet filter + // setting here locally. In this case the sPacketFilterConfig will not be empty + // at the moment, even if it was empty initially. + + // The situation caught here is that the configuration for packet filter was set, + // but the peer responded as if it didn't understand it, or simply didn't accept it. + // In such a situation the configuration should be rejected because if the + // peer doesn't support packet filter, the caller side should not have set it. + m_RejectReason = SRT_REJ_FILTER; + LOGC(cnlog.Error, log << CONID() + << "HS EXT: Agent has configured packetfilter, but peer didn't respond."); + return false; + } + + // Responder: we are the listener that has configured packetfilter, + // but the caller didn't request packetfilter. + m_config.sPacketFilterConfig.set(""); + LOGC(cnlog.Warn, log << CONID() << "HS EXT: Agent has configured packetfilter, but peer didn't request it"); + } + #if ENABLE_BONDING // m_GroupOf and locking info: NULL check won't hurt here. If the group // was deleted in the meantime, it will be found out later anyway and result with error. From 0976f76a9db1c3144087a38c5c4df2200b600783 Mon Sep 17 00:00:00 2001 From: zhouliuya Date: Thu, 13 Mar 2025 10:22:27 +0000 Subject: [PATCH 28/92] [build] support OHOS(harmonyOS) system compile (#3139) * feat: support OHOS(harmonyOS) system compile * add support for OHOS platform detection. --- CMakeLists.txt | 6 +++++- srtcore/sync_posix.cpp | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d718792d17..847b6f23fb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,8 +45,9 @@ set_if(BSD ${SYSNAME_LC} MATCHES "bsd$") set_if(MICROSOFT WIN32 AND (NOT MINGW AND NOT CYGWIN)) set_if(GNU_OS ${CMAKE_SYSTEM_NAME} MATCHES "GNU") # Use GNU_OS to not confuse with gcc set_if(ANDROID ${SYSNAME_LC} MATCHES "android") +set_if(OHOS ${CMAKE_SYSTEM_NAME} MATCHES "OHOS") set_if(SUNOS "${SYSNAME_LC}" MATCHES "sunos") -set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR (CYGWIN AND CYGWIN_USE_POSIX) OR GNU_OS) +set_if(POSIX LINUX OR DARWIN OR BSD OR SUNOS OR ANDROID OR OHOS OR (CYGWIN AND CYGWIN_USE_POSIX) OR GNU_OS) set_if(SYMLINKABLE LINUX OR DARWIN OR BSD OR SUNOS OR CYGWIN OR GNU_OS) set_if(NEED_DESTINATION ${CMAKE_VERSION} VERSION_LESS "3.14.0") @@ -791,6 +792,9 @@ elseif(LINUX) elseif(ANDROID) add_definitions(-DLINUX=1) message(STATUS "DETECTED SYSTEM: ANDROID; LINUX=1" ) +elseif(OHOS) + add_definitions(-DLINUX=1) + message(STATUS "DETECTED SYSTEM: OHOS; LINUX=1" ) elseif(CYGWIN) add_definitions(-DCYGWIN=1) message(STATUS "DETECTED SYSTEM: CYGWIN (posix mode); CYGWIN=1") diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp index 8d7561a19d..1668a94796 100644 --- a/srtcore/sync_posix.cpp +++ b/srtcore/sync_posix.cpp @@ -381,13 +381,13 @@ srt::sync::CThread& srt::sync::CThread::operator=(CThread& other) LOGC(inlog.Error, log << "IPE: Assigning to a thread that is not terminated!"); #ifndef DEBUG -#ifndef __ANDROID__ +#if !defined(__ANDROID__) && !defined(__OHOS__) // In case of production build the hanging thread should be terminated // to avoid hang ups and align with C++11 implementation. // There is no pthread_cancel on Android. See #1476. This error should not normally // happen, but if it happen, then detaching the thread. pthread_cancel(m_thread); -#endif // __ANDROID__ +#endif // __ANDROID__ __OHOS__ #else join(); #endif From 4d0e984088644224eb275f3f81ee66d09cd8f77c Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Wed, 12 Mar 2025 11:17:23 +0100 Subject: [PATCH 29/92] [core] Fixed accidental blocking on sendmsg call --- srtcore/channel.cpp | 4 ++++ srtcore/core.cpp | 1 + 2 files changed, 5 insertions(+) diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 0ec8f090f3..81dbc4d35f 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -552,6 +552,10 @@ void srt::CChannel::setUDPSockOpt() // Set receiving time-out value if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval))) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + // Set sending time-out, too; O_NONBLOCK sets it in both directions, + // so both should be also set here for consistency. + if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(timeval))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); #endif #ifdef SRT_ENABLE_PKTINFO diff --git a/srtcore/core.cpp b/srtcore/core.cpp index fef0d0c2a8..f3812cc4a9 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -8358,6 +8358,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) { ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL); } + bufflock.unlock(); ctrlpkt.set_id(m_PeerID); setPacketTS(ctrlpkt, steady_clock::now()); From ee03ae90c4467d593067b5c6ee51ead2fcd1699d Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 14 Mar 2025 09:45:21 +0100 Subject: [PATCH 30/92] [tests] Fixed tests to avoid mistakes in CI (#3143) authored-by: Mikolaj Malecki --- test/test_bonding.cpp | 68 +++++++++++++++++++++++--------- test/test_connection_timeout.cpp | 28 +++++++------ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index e9efe939a8..a1df9ec2d0 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -726,13 +726,10 @@ TEST(Bonding, ConnectNonBlocking) for (const auto GTYPE: types) { g_listen_socket = srt_create_socket(); - sockaddr_in bind_sa; - memset(&bind_sa, 0, sizeof bind_sa); - bind_sa.sin_family = AF_INET; - EXPECT_EQ(inet_pton(AF_INET, ADDR.c_str(), &bind_sa.sin_addr), 1); - bind_sa.sin_port = htons(PORT); - EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1); + sockaddr_any sa = srt::CreateAddr(ADDR, PORT, AF_INET); + + EXPECT_NE(srt_bind(g_listen_socket, sa.get(), sa.size()), -1); const int yes = 1; srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes); EXPECT_NE(srt_listen(g_listen_socket, 5), -1); @@ -758,14 +755,17 @@ TEST(Bonding, ConnectNonBlocking) srt_connect_callback(ss, &ConnectCallback, this); - sockaddr_in sa; - sa.sin_family = AF_INET; - sa.sin_port = htons(PORT); - EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + cout << "TEST: Group type: " << GTYPE << endl; + + // synchronizers + std::promise + connect_passed, + accept_passed, + checks_done; //srt_setloglevel(LOG_DEBUG); - auto acthr = std::thread([&lsn_eid]() { + auto acthr = std::thread([&lsn_eid, &connect_passed, &accept_passed, &checks_done]() { SRT_EPOLL_EVENT ev[3]; ThreadName::set("TEST_A"); @@ -782,8 +782,15 @@ TEST(Bonding, ConnectNonBlocking) EXPECT_NE(ev[0].events & ev_in_bit, 0); bool have_also_update = ev[0].events & SRT_EPOLL_UPDATE; + cout << "[A] Accept delay until connect done...\n"; + // Delay with executing accept to keep the peer in "in progress" + // connection state. + connect_passed.get_future().get(); + + cout << "[A] Accept: go on\n"; + sockaddr_any adr; - int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len); + SRTSOCKET accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len); // Expected: group reporting EXPECT_NE(accept_id & SRTGROUP_MASK, 0); @@ -803,19 +810,35 @@ TEST(Bonding, ConnectNonBlocking) EXPECT_EQ(ev[0].events, (int)SRT_EPOLL_UPDATE); } - cout << "[A] Waitig for close (up to 5s)\n"; + // As accept is expected to be finished and two connections were + // established, make sure that both connections are established. + // Wait until you have at least two members + SRT_SOCKGROUPDATA members[3]; + size_t grouplen = 3; + cout << "[A] Waiting until having 2 members in the group\n"; + while (srt_group_data(accept_id, members, &grouplen) != SRT_ERROR + && grouplen < 2) + { + grouplen = 3; + this_thread::sleep_for(milliseconds(250)); + } + accept_passed.set_value(); + + + cout << "[A] Waitig on epoll for close (up to 5s)\n"; // Wait up to 5s for an error srt_epoll_uwait(lsn_eid, ev, 3, 5000); - srt_close(accept_id); + checks_done.set_value(); + cout << "[A] thread finished\n"; }); cout << "Connecting two sockets\n"; SRT_SOCKGROUPCONFIG cc[2]; - cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); - cc[1] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa); + cc[0] = srt_prepare_endpoint(NULL, sa.get(), sa.size()); + cc[1] = srt_prepare_endpoint(NULL, sa.get(), sa.size()); EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR); @@ -824,21 +847,28 @@ TEST(Bonding, ConnectNonBlocking) char data[4] = { 1, 2, 3, 4}; cout << "Sending...\n"; int wrong_send = srt_send(ss, data, sizeof data); + + // Only now we allow the other thread to go on with accept + connect_passed.set_value(); cout << "Getting error...\n"; int errorcode = srt_getlasterror(NULL); EXPECT_EQ(wrong_send, -1); EXPECT_EQ(errorcode, SRT_EASYNCSND) << "REAL ERROR: " << srt_getlasterror_str(); + // Wait to make sure that both links are connected. + accept_passed.get_future().get(); + // Wait up to 2s SRT_EPOLL_EVENT ev[3]; const int uwait_result = srt_epoll_uwait(poll_id, ev, 3, 2000); - std::cout << "Returned from connecting two sockets " << std::endl; + std::cout << "Returned from connecting two sockets, waiting for all checks from [A]" << std::endl; + + checks_done.get_future().get(); EXPECT_EQ(uwait_result, 1); // Expect the group reported EXPECT_EQ(ev[0].fd, ss); - // One second to make sure that both links are connected. - this_thread::sleep_for(seconds(1)); + std::cout << "Closing group and releasing resources\n"; EXPECT_EQ(srt_close(ss), 0); acthr.join(); diff --git a/test/test_connection_timeout.cpp b/test/test_connection_timeout.cpp index ec52d9dd84..c8cbc87220 100644 --- a/test/test_connection_timeout.cpp +++ b/test/test_connection_timeout.cpp @@ -170,20 +170,22 @@ TEST_F(TestConnectionTimeout, Nonblocking) { */ TEST_F(TestConnectionTimeout, BlockingLoop) { - const SRTSOCKET client_sock = srt_create_socket(); - ASSERT_GT(client_sock, 0); // socket_id should be > 0 - - // Set connection timeout to 999 ms to reduce the test execution time. - // Also need to hit a time point between two threads: - // 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. - const int connection_timeout_ms = 999; - EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); - const sockaddr* psa = reinterpret_cast(&m_sa); + const int connection_timeout_ms = 999; 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) + break; + + // Set connection timeout to 999 ms to reduce the test execution time. + // Also need to hit a time point between two threads: + // 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); + 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); @@ -200,9 +202,9 @@ TEST_F(TestConnectionTimeout, BlockingLoop) << error_code << " " << srt_getlasterror_str() << "\n"; break; } - } - EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); + EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); + } } From 952f9495246abc201bac55b8f9ad7409c0572423 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Fri, 21 Mar 2025 10:40:56 +0100 Subject: [PATCH 31/92] [test] Fixing ConnectNonBlocking: delay listen to prevent connection from being established too early --- test/test_bonding.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index a1df9ec2d0..e077ea1e07 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -732,7 +732,6 @@ TEST(Bonding, ConnectNonBlocking) EXPECT_NE(srt_bind(g_listen_socket, sa.get(), sa.size()), -1); const int yes = 1; srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes); - EXPECT_NE(srt_listen(g_listen_socket, 5), -1); int lsn_eid = srt_epoll_create(); int lsn_events = SRT_EPOLL_IN | SRT_EPOLL_ERR | SRT_EPOLL_UPDATE; @@ -770,8 +769,14 @@ TEST(Bonding, ConnectNonBlocking) ThreadName::set("TEST_A"); - cout << "[A] Waiting for accept\n"; + cout << "[A] Waiting for main thread to pass connect()\n"; + + // Delay with executing accept to keep the peer in "in progress" + // connection state. + connect_passed.get_future().get(); + EXPECT_NE(srt_listen(g_listen_socket, 5), SRT_ERROR); + cout << "[A] Waiting for accept\n"; // This can wait in infinity; worst case it will be killed in process. int uwait_res = srt_epoll_uwait(lsn_eid, ev, 3, -1); EXPECT_EQ(uwait_res, 1); @@ -783,9 +788,6 @@ TEST(Bonding, ConnectNonBlocking) bool have_also_update = ev[0].events & SRT_EPOLL_UPDATE; cout << "[A] Accept delay until connect done...\n"; - // Delay with executing accept to keep the peer in "in progress" - // connection state. - connect_passed.get_future().get(); cout << "[A] Accept: go on\n"; From 0def1b1a1094fc57752f241250e9a1aed71bbffd Mon Sep 17 00:00:00 2001 From: Sam Umbach Date: Mon, 19 May 2025 08:39:46 -0400 Subject: [PATCH 32/92] [build] Update for compatibility with CMake 4.x (#3167) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 847b6f23fb..847564d03d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # -cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) +cmake_minimum_required (VERSION 3.5 FATAL_ERROR) set (SRT_VERSION 1.5.4) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") From 19113e65d9c66ed32746a1d49c0a82a11ad92126 Mon Sep 17 00:00:00 2001 From: yaruno Date: Mon, 19 May 2025 14:41:09 +0200 Subject: [PATCH 33/92] [build] Update cxx11-macos.yaml (#3170) * Update for compatibility with CMake 4.x * Update cxx11-macos.yaml Downgrade googletest to 1.16.x as latest one requires C++ 17 * Update cxx11-macos.yaml try to download the exact version of google test formula from homebrew history * Update cxx11-macos.yaml downgrade googletest to 1.12.1 --------- Co-authored-by: Sam Umbach --- .github/workflows/cxx11-macos.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cxx11-macos.yaml b/.github/workflows/cxx11-macos.yaml index e3e6b1f8a6..4732c2abbd 100644 --- a/.github/workflows/cxx11-macos.yaml +++ b/.github/workflows/cxx11-macos.yaml @@ -13,7 +13,9 @@ jobs: steps: - name: GoogleTest - run: brew install googletest + run: | + curl -o googletest.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/23e7fb4dc0cc73facc3772815741e1deb87d6406/Formula/googletest.rb + brew install -s googletest.rb - uses: actions/checkout@v3 - name: configure run: | From 21370a7e8f63a4816ffabe768726b68662c27731 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 21 May 2025 15:36:37 +0200 Subject: [PATCH 34/92] [core] Fixed problems found by thread and memory sanitizers (#3166) * Fixed some thread-related problems * Fixed a leak in group objects * Fixed incorrect conditional for a group closure fix * Update srtcore/group.cpp - comment typo --------- Co-authored-by: Mikolaj Malecki --- srtcore/api.cpp | 15 ++++++++++++++- srtcore/core.cpp | 2 +- srtcore/core.h | 1 + srtcore/group.cpp | 22 +++++++++++++++------- test/test_bonding.cpp | 2 +- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 1108c0a1d9..14d7a46529 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -318,6 +318,15 @@ void srt::CUDTUnited::closeAllSockets() { j->second->m_tsClosureTimeStamp = steady_clock::time_point(); } + +#if ENABLE_BONDING + for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j) + { + SRTSOCKET id = j->second->m_GroupID; + m_ClosedGroups[id] = j->second; + } + m_Groups.clear(); +#endif } HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets."); @@ -591,7 +600,11 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, } // exceeding backlog, refuse the connection request - if (ls->m_QueuedSockets.size() >= ls->m_uiBackLog) + + enterCS(ls->m_AcceptLock); + size_t backlog = ls->m_QueuedSockets.size(); + leaveCS(ls->m_AcceptLock); + if (backlog >= ls->m_uiBackLog) { w_error = SRT_REJ_BACKLOG; LOGC(cnlog.Note, log << "newConnection: listen backlog=" << ls->m_uiBackLog << " EXCEEDED"); diff --git a/srtcore/core.cpp b/srtcore/core.cpp index f3812cc4a9..d2118c5552 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -11924,7 +11924,7 @@ int64_t srt::CUDT::socketStartTime(SRTSOCKET u) if (!s) return APIError(MJ_NOTSUP, MN_SIDINVAL); - return count_microseconds(s->core().m_stats.tsStartTime.time_since_epoch()); + return count_microseconds(s->core().socketStartTime().time_since_epoch()); } bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt) diff --git a/srtcore/core.h b/srtcore/core.h index ed250c641a..6f7bd6b19f 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -747,6 +747,7 @@ class CUDT time_point socketStartTime() { + sync::ScopedLock lk (m_StatsLock); return m_stats.tsStartTime; } diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 4c292cb894..dc3c1a9be7 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -862,17 +862,25 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) { // Can't have m_GroupLock locked while calling getOpt on a member socket // because the call will acquire m_ControlLock leading to a lock-order-inversion. + SRTSOCKET firstsocket = SRT_INVALID_SOCK; enterCS(m_GroupLock); gli_t gi = m_Group.begin(); - CUDTSocket* const ps = (gi != m_Group.end()) ? gi->ps : NULL; - CUDTUnited::SocketKeeper sk(CUDT::uglobal(), ps); + if (gi != m_Group.end()) + firstsocket = gi->ps->core().id(); leaveCS(m_GroupLock); - if (sk.socket) + // CUDTUnited::m_GlobControlLock can't be acquired with m_GroupLock either. + // We have also no guarantee that after leaving m_GroupLock the socket isn't + // going to be deleted. Hence use the safest method by extracting through the id. + if (firstsocket != SRT_INVALID_SOCK) { - // Return the value from the first member socket, if any is present - // Note: Will throw exception if the request is wrong. - sk.socket->core().getOpt(optname, (pw_optval), (w_optlen)); - is_set_on_socket = true; + CUDTUnited::SocketKeeper sk(CUDT::uglobal(), firstsocket); + if (sk.socket) + { + // Return the value from the first member socket, if any is present + // Note: Will throw exception if the request is wrong. + sk.socket->core().getOpt(optname, (pw_optval), (w_optlen)); + is_set_on_socket = true; + } } } diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index e077ea1e07..6307fd21f0 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -1284,7 +1284,7 @@ TEST(Bonding, BackupPrioritySelection) g_nconnected = 0; g_nfailed = 0; - volatile bool recvd = false; + sync::atomic recvd { false }; // 1. sockaddr_in bind_sa; From d6d0e5a964f6f9d5a52acf475d4016bc5c5bb9eb Mon Sep 17 00:00:00 2001 From: cl-ment Date: Fri, 6 Jun 2025 14:32:04 +0200 Subject: [PATCH 35/92] [build] + [core] compilation of SRT on platforms that do not support the pthread_condattr_setclock function. (#3152) * [core],[build] check phtread_condattr_setclock (Android < 21) * Update CMakelists.txt with #3154 to fix travis buildbreak. --------- Co-authored-by: Lilinxiong Zhouliuya --- CMakeLists.txt | 15 +++++++++++++++ srtcore/sync_posix.cpp | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 847564d03d..a5547454fd 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ cmake_minimum_required (VERSION 3.5 FATAL_ERROR) set (SRT_VERSION 1.5.4) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") +include(CheckSymbolExists) include(haiUtil) # needed for set_version_variables # CMake version 3.0 introduced the VERSION option of the project() command # to specify a project version as well as the name. @@ -961,8 +962,22 @@ elseif (MICROSOFT) else () find_package(Threads REQUIRED) set(PTHREAD_LIBRARY ${CMAKE_THREAD_LIBS_INIT}) + message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") endif() +# To avoid the need for other judgments when ENABLE_STDCXX_SYNC is OFF in the future, this is a separate conditional statement. +if (NOT ENABLE_STDCXX_SYNC AND ENABLE_MONOTONIC_CLOCK) + list(PREPEND CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY}") + unset(CMAKE_REQUIRED_QUIET) + check_symbol_exists(pthread_condattr_setclock "pthread.h" HAVE_PTHREAD_CONDATTR_SETCLOCK) + message(STATUS "Checking pthread_condattr_setclock: '${HAVE_PTHREAD_CONDATTR_SETCLOCK}'") + if ("${HAVE_PTHREAD_CONDATTR_SETCLOCK}" STREQUAL "1") + message(STATUS "FOUND pthread_condattr_setclock - pthread_cond_timedwait will use monotonic clock") + add_definitions(-DHAVE_PTHREAD_CONDATTR_SETCLOCK=1) + else () + message(WARNING "NOT FOUND pthread_cond_timedwait. pthread_cond_timedwait will use system clock. Recommended -DENABLE_STDCXX_SYNC=ON") + endif () +endif () # This is required in some projects that add some other sources # to the SRT library to be compiled together (aka "virtual library"). if (DEFINED SRT_EXTRA_LIB_INC) diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp index 1668a94796..1649130972 100644 --- a/srtcore/sync_posix.cpp +++ b/srtcore/sync_posix.cpp @@ -288,13 +288,23 @@ Condition::~Condition() {} void Condition::init() { pthread_condattr_t* attr = NULL; -#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK pthread_condattr_t CondAttribs; pthread_condattr_init(&CondAttribs); - pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC); + if (pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC) != 0) + { + pthread_condattr_destroy(&CondAttribs); + LOGC(inlog.Fatal, log << "IPE: pthread_condattr_setclock failed to set up a monotonic clock for a CV"); + } attr = &CondAttribs; #endif const int res = pthread_cond_init(&m_cv, attr); +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK + if (attr != NULL) + { + pthread_condattr_destroy(attr); + } +#endif if (res != 0) throw std::runtime_error("pthread_cond_init monotonic failed"); } @@ -312,7 +322,7 @@ void Condition::wait(UniqueLock& lock) bool Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time) { timespec timeout; -#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC +#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK clock_gettime(CLOCK_MONOTONIC, &timeout); const uint64_t now_us = timeout.tv_sec * uint64_t(1000000) + (timeout.tv_nsec / 1000); #else From 0c570a10e30686552be0f6b9b19ce6a64daed598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20G=C3=A9rouville?= Date: Wed, 21 May 2025 11:14:16 +0200 Subject: [PATCH 36/92] [build] Remove SonarQube --- .github/workflows/cxx11-ubuntu.yaml | 11 -------- README.md | 4 --- sonar-project.properties | 40 ----------------------------- 3 files changed, 55 deletions(-) delete mode 100644 sonar-project.properties diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index 5566ed8019..7d30f4a1af 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -10,12 +10,8 @@ jobs: build: name: ubuntu runs-on: ubuntu-20.04 - env: - BUILD_WRAPPER_OUT_DIR: sonar-output # Directory where build-wrapper output will be placed steps: - uses: actions/checkout@v3 - - name: Install sonar-scanner and build-wrapper - uses: sonarsource/sonarcloud-github-c-cpp@v2 - name: configure run: | mkdir _build && cd _build @@ -29,10 +25,3 @@ jobs: run: | source ./scripts/collect-gcov.sh bash <(curl -s https://codecov.io/bash) - - name: Run SonarCloud Scan for C and C++ - if: ${{ !github.event.pull_request.head.repo.fork }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - # Consult https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-cli/ for more information and options. - run: sonar-scanner --define sonar.cfamily.compile-commands="_build/compile_commands.json" diff --git a/README.md b/README.md index b6465a347e..0e18ff4a57 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ [![License: MPLv2.0][license-badge]](./LICENSE) [![Latest release][release-badge]][github releases] -[![Quality Gate Status][sonarcloud-badge]][sonarcloud-project] [![codecov][codecov-badge]][codecov-project] [![Build Status Linux and macOS][travis-badge]][travis] [![Build Status Windows][appveyor-badge]][appveyor] @@ -226,9 +225,6 @@ By contributing code to the SRT project, you agree to license your contribution [ConanCenter-package]: https://conan.io/center/recipes/srt [ConanCenter-badge]: https://img.shields.io/conan/v/srt -[sonarcloud-project]: https://sonarcloud.io/project/overview?id=srt -[sonarcloud-badge]: https://sonarcloud.io/api/project_badges/measure?project=srt&metric=alert_status - [codecov-project]: https://codecov.io/gh/haivision/srt [codecov-badge]: https://codecov.io/gh/haivision/srt/branch/master/graph/badge.svg diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 4bdaccdb22..0000000000 --- a/sonar-project.properties +++ /dev/null @@ -1,40 +0,0 @@ -# must be unique in a given SonarQube instance -sonar.projectKey=srt -sonar.organization=haivision - -# --- optional properties --- - -# This is the name and version displayed in the SonarCloud UI. -#sonar.projectName=srt -#sonar.projectVersion=1.0 - -# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. -#sonar.sources=. - -# Encoding of the source code. Default is default system encoding -#sonar.sourceEncoding=UTF-8 - -# ===================================================== -# Meta-data for the project -# ===================================================== - -sonar.links.homepage=https://github.com/Haivision/srt -sonar.links.scm=https://github.com/Haivision/srt -sonar.links.issue=https://github.com/Haivision/srt/issues - - -# ===================================================== -# Properties that will be shared amongst all modules -# ===================================================== - -# SQ standard properties -sonar.sources=srtcore/,apps/,common/,examples/,haicrypt/,scripts/,testing/ -sonar.tests=test/ - -# Properties specific to the C/C++ analyzer: -#sonar.cfamily.build-wrapper-output=_build/sonar-output -# This field is deprecated. compile-commands should be used -# instead, and this property is set in the action specification -# Should be something like: -#sonar.cfamily.compile-commands=_build/compile_commands.json -sonar.cfamily.gcov.reportsPath=. From 5d80411f0c2df2ebaa2177875309974b0233cc6c Mon Sep 17 00:00:00 2001 From: Sergei Ignatov Date: Thu, 17 Jul 2025 16:33:38 +1000 Subject: [PATCH 37/92] [build] Update iOS build scripts (#3182) * Update iOS build scripts ~ Generate Xcode project files ~ Support build for tvOS ~ Package all variants into XCFramework bundle * Fix iOS / iOS-cxxsyncON --- docs/build/build-android.md | 2 +- docs/build/build-iOS.md | 36 ++++++-------------- scripts/build-ios/README.md | 3 ++ scripts/build-ios/mksrt-xcf.sh | 60 ++++++++++++++++++++++++++++++++++ scripts/build-ios/mkssl-xcf.sh | 22 +++++++++++++ scripts/iOS.cmake | 58 +++++++++++++++++++++++--------- 6 files changed, 139 insertions(+), 42 deletions(-) create mode 100644 scripts/build-ios/README.md create mode 100755 scripts/build-ios/mksrt-xcf.sh create mode 100755 scripts/build-ios/mkssl-xcf.sh diff --git a/docs/build/build-android.md b/docs/build/build-android.md index 7c13c9404b..da35414070 100644 --- a/docs/build/build-android.md +++ b/docs/build/build-android.md @@ -1,6 +1,6 @@ # Building SRT on Android -**NOTE:** The scripts have been moved to [scripts/build-android](../../scripts/build-android/) folder. +**NOTE:** The scripts are located in [scripts/build-android](../../scripts/build-android/) folder. ## Install the NDK and CMake diff --git a/docs/build/build-iOS.md b/docs/build/build-iOS.md index a1fd48ba95..c6d58844f2 100644 --- a/docs/build/build-iOS.md +++ b/docs/build/build-iOS.md @@ -1,44 +1,28 @@ -# Building SRT on iOS +# Building SRT on iOS/tvOS + +**NOTE:** The scripts are located in [scripts/build-ios](../../scripts/build-ios/) folder. ## Prerequisites * Xcode should be installed. Check in terminal whether `xcode-select -p` points to **/Applications/Xcode.app/Contents/Developer** * Install Homebrew according to instructions on [https://brew.sh/](https://brew.sh/) -* Install CMake and pkg-config with Homebrew: +* Install CMake with Homebrew: ``` brew install cmake -brew install pkg-config ``` ## Building OpenSSL -There is [OpenSSL for iPhone](https://github.com/x2on/OpenSSL-for-iPhone) project which have all necessary to build OpenSSL for our needs. It fetches OpenSSL code by itself, so you don't need to download it separately. So simply clone it and build with command: +There is [OpenSSL-Universal](https://github.com/krzyzanowskim/OpenSSL) project which has all necessary to build OpenSSL for our needs. It fetches OpenSSL code by itself, so you don't need to download it separately. Build it with command: ``` -./build-libssl.sh --archs="arm64" +./mkssl-xcf.sh ``` -Results (both libraries and headers) will be placed in bin/<SDK_VERSION>-<ARCH>.sdk directory (for example, *bin/iPhoneOS11.2-arm64.sdk*). We assume you set **IOS_OPENSSL** variable to this path (e.g. `export IOS_OPENSSL="/Users/johndoe/sources/OpenSSL-for-iPhone/bin/iPhoneOS11.2-arm64.sdk"`). - ## Building SRT code -Now you can build SRT providing path to OpenSSL library and toolchain file for iOS - +Now you can build SRT with command: ``` -./configure --cmake-prefix-path=$IOS_OPENSSL --use-openssl-pc=OFF --cmake-toolchain-file=scripts/iOS.cmake -make +./mksrt-xcf.sh ``` -Optionally you may add following iOS-specifc settings to configure: - -* `--ios-disable-bitcode=1` - disable embedding bitcode to library. -* `--ios-arch=armv7|armv7s|arm64` - specify if you want to build for specific architecture (arm64 by default) -* `--ios-platform=OS|SIMULATOR|SIMULATOR64` - specify for build simulator code -* `--cmake-ios-developer-root=<path>`- specify path for platform directory; {XCODE_ROOT}/Platforms/iPhoneOS.platform/Developer by default -* `--cmake-ios-sdk-root=` - by default searches for latest SDK version within {CMAKE_IOS_DEVELOPER_ROOT}/SDKs, set if you want to use another SDK version - -Note that resulting .dylib file has install path @executable_path/Frameworks/libsrt.1.dylib, so if you need to place it in some other place with your application, you may change it with *install_name_tool* command: ``install_name_tool -id "" ``, for example ``install_name_tool -id "@executable_path/Frameworks/libsrt.1.3.0.dylib" libsrt.1.3.0.dylib`` - ## Adding to Xcode project -In Xcode project settings in General tab, add libsrt to **Linked Frameworks and Libraries** section - click Plus sign, then click "Add Other" and find libsrt.1.dylib - -Click plus sign in **Embedded binaries** section and choose Frameworks/libsrt.1.dylib +Results (libcrypto.xcframework and libsrt.xcframework) will be placed in [scripts/build-ios](../../scripts/build-ios/) folder. -In **Build settings** tab find **Header Search Paths** setting -and add paths to SRT library sources (you should add srt, srt/common and srt/common directories). +Follow [these steps](https://developer.apple.com/documentation/xcode/creating-a-static-framework#Embed-your-framework-in-an-app) to embed frameworks in your app. Choose the Do Not Embed option from the Embed value list. diff --git a/scripts/build-ios/README.md b/scripts/build-ios/README.md new file mode 100644 index 0000000000..63986286e3 --- /dev/null +++ b/scripts/build-ios/README.md @@ -0,0 +1,3 @@ +## Scripts for building SRT for iOS/tvOS + +See [Building SRT for iOS/tvOS](../../docs/build/build-iOS.md) for the instructions. diff --git a/scripts/build-ios/mksrt-xcf.sh b/scripts/build-ios/mksrt-xcf.sh new file mode 100755 index 0000000000..59022cdc1a --- /dev/null +++ b/scripts/build-ios/mksrt-xcf.sh @@ -0,0 +1,60 @@ +#!/bin/zsh + +# Determine the path of the executing script +BASE_DIR=$(readlink -f $0 | xargs dirname) + +srt="../.." +ssl=$BASE_DIR/OpenSSL +out=$BASE_DIR/libsrt +target=13.0 +tv_target=14.0 +tvos_sdk=`xcrun -sdk appletvos --show-sdk-version` +lib=libsrt.a + +ios_archs=('arm64' 'arm64' 'x86_64' 'arm64' 'arm64' 'x86_64') +ios_platforms=('OS' 'ARM_SIMULATOR' 'SIMULATOR64' 'TV' 'TV_ARM_SIMULATOR' 'TV_SIMULATOR' ) +ios_targets=('iOS-arm64' 'iOS-simArm' 'iOS-simIntel64' 'tvOS' 'tvOS_simArm' 'tvOS_simIntel64') +ssl_folders=('iphoneos' 'iphonesimulator' 'iphonesimulator' 'appletvos' 'appletvsimulator' 'appletvsimulator') +rm -rf $out + +cd $srt + +for idx in {1..$#ios_archs} +do + ios_arch=${ios_archs[$idx]} + ios_platform=${ios_platforms[$idx]} + dest=${ios_targets[$idx]} + ssl_path=${ssl}/${ssl_folders[$idx]} + + git clean -fd -e scripts + echo '********************************************************************************' + echo SSL_PATH $ssl_path ARCH $ios_arch PLATFORM $ios_platform + + CMAKE_GENERATOR=Xcode ./configure \ + --enable-shared=OFF --enable-apps=OFF \ + --cmake-toolchain-file=scripts/iOS.cmake --ios-arch=$ios_arch --ios-platform=$ios_platform \ + --cmake-xcode-attribute-iphoneos-deployment-target=$target \ + --cmake-xcode-attribute-tvos-deployment-target=$tv_target \ + --cmake-prefix-path=$ssl_path --use-openssl-pc=OFF \ + --cmake_install_prefix=$out/$dest + + cmake --build . --target install --config Release + echo '********************************************************************************' +done + +git clean -fd -e scripts + +lipo -create $out/iOS-simArm/lib/$lib $out/iOS-simIntel64/lib/$lib -output $out/libsrt-ios-sim.a +lipo -create $out/tvOS_simArm/lib/$lib $out/tvOS_simIntel64/lib/$lib -output $out/libsrt-tv-sim.a + +srt_headers=$out/iOS-arm64/include +rm -rf $BASE_DIR/libsrt.xcframework + +echo '####### create XCFramework bundle #######' + +xcodebuild -create-xcframework \ + -library $out/iOS-arm64/lib/$lib -headers $srt_headers \ + -library $out/libsrt-ios-sim.a -headers $srt_headers \ + -library $out/tvOS/lib/$lib -headers $srt_headers \ + -library $out/libsrt-tv-sim.a -headers $srt_headers \ + -output $BASE_DIR/libsrt.xcframework diff --git a/scripts/build-ios/mkssl-xcf.sh b/scripts/build-ios/mkssl-xcf.sh new file mode 100755 index 0000000000..c7283d75d9 --- /dev/null +++ b/scripts/build-ios/mkssl-xcf.sh @@ -0,0 +1,22 @@ +#!/bin/zsh + +# Determine the path of the executing script +BASE_DIR=$(readlink -f $0 | xargs dirname) + +REPO_DIR=OpenSSL +SSL_DIR=$BASE_DIR/$REPO_DIR + +if [ ! -d $REPO_DIR ] +then + git clone https://github.com/krzyzanowskim/OpenSSL +fi +cd $REPO_DIR +make build + +rm -rf $BASE_DIR/libcrypto.xcframework +xcodebuild -create-xcframework \ + -library $SSL_DIR/iphoneos/lib/libcrypto.a \ + -library $SSL_DIR/iphonesimulator/lib/libcrypto.a \ + -library $SSL_DIR/appletvos/lib/libcrypto.a \ + -library $SSL_DIR/appletvsimulator/lib/libcrypto.a \ + -output $BASE_DIR/libcrypto.xcframework diff --git a/scripts/iOS.cmake b/scripts/iOS.cmake index 1f2a04b500..ed3743dd64 100644 --- a/scripts/iOS.cmake +++ b/scripts/iOS.cmake @@ -8,8 +8,10 @@ # This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders # OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch. # SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. +# TV - used to build for tvOS devices +# TV_SIMULATOR - used to build for tvOS Simulator platforms, which have an x86 arch. # -# IOS_ARCH = arm64 (default for OS), armv7, armv7s, i386 (default for SIMULATOR), x86_64 (default for SIMULATOR64) +# IOS_ARCH = arm64 (default for OS and TV), armv7, armv7s, i386 (default for SIMULATOR), x86_64 (default for SIMULATOR64 and TV_SIMULATOR) # # CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder # By default this location is automatcially chosen based on the IOS_PLATFORM value above. @@ -19,8 +21,6 @@ # By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value. # In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path. # If set manually, this will force the use of a specific SDK version -# -# IOS_DISABLE_BITCODE - set to 1 if you want to disable bitcode generation # Standard settings set (CMAKE_SYSTEM_NAME Darwin) @@ -29,14 +29,16 @@ set (UNIX True) set (APPLE True) set (IOS True) +# With Xcode 16, Apple has deprecated bitcode for all platforms +set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO") + # Required as of cmake 2.8.10 set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE) # Determine the cmake host system version so we know where to find the iOS SDKs find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin) if (CMAKE_UNAME) - execute_process(COMMAND uname -r - OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) + execute_process(COMMAND uname -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION) string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}") endif (CMAKE_UNAME) @@ -49,10 +51,6 @@ set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ") set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}") set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}") -if (NOT DEFINED IOS_DISABLE_BITCODE) - set (EMBED_OPTIONS "-fembed-bitcode") -endif(NOT DEFINED IOS_DISABLE_BITCODE) - if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR ENABLE_DEBUG) set(IOS_DEBUG_OPTIONS "-glldb -gmodules") else() @@ -102,6 +100,12 @@ if (${IOS_PLATFORM} STREQUAL OS) # This causes the installers to properly locate the output libraries set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos") +elseif (${IOS_PLATFORM} STREQUAL TV) + set (IOS_PLATFORM_LOCATION "AppleTVOS.platform") + unset(CMAKE_IOS_DEVELOPER_ROOT CACHE) + unset(CMAKE_IOS_SDK_ROOT CACHE) + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvos") elseif (${IOS_PLATFORM} STREQUAL SIMULATOR) set (SIMULATOR true) set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") @@ -114,15 +118,35 @@ elseif (${IOS_PLATFORM} STREQUAL SIMULATOR64) # This causes the installers to properly locate the output libraries set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") +elseif (${IOS_PLATFORM} STREQUAL ARM_SIMULATOR) + set (SIMULATOR true) + set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform") + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator") +elseif (${IOS_PLATFORM} STREQUAL TV_SIMULATOR) + set (SIMULATOR true) + set (IOS_PLATFORM_LOCATION "AppleTVSimulator.platform") + unset(CMAKE_IOS_DEVELOPER_ROOT CACHE) + unset(CMAKE_IOS_SDK_ROOT CACHE) + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvsimulator") +elseif (${IOS_PLATFORM} STREQUAL TV_ARM_SIMULATOR) + set (SIMULATOR true) + set (IOS_PLATFORM_LOCATION "AppleTVSimulator.platform") + unset(CMAKE_IOS_DEVELOPER_ROOT CACHE) + unset(CMAKE_IOS_SDK_ROOT CACHE) + + # This causes the installers to properly locate the output libraries + set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvsimulator") else (${IOS_PLATFORM} STREQUAL OS) - message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR") + message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS, TV or SIMULATOR") endif (${IOS_PLATFORM} STREQUAL OS) # Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) - execute_process(COMMAND /usr/bin/xcode-select -print-path - OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR) - string(STRIP "${CMAKE_XCODE_DEVELOPER_DIR}" CMAKE_XCODE_DEVELOPER_DIR) # FIXED: remove new line character, otherwise it complain no iOS SDK's found in default search path + execute_process(COMMAND /usr/bin/xcode-select -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) set (CMAKE_IOS_DEVELOPER_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer") endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT) set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform") @@ -152,6 +176,10 @@ if (NOT DEFINED IOS_ARCH) set (IOS_ARCH i386) elseif (${IOS_PLATFORM} STREQUAL SIMULATOR64) set (IOS_ARCH x86_64) + elseif (${IOS_PLATFORM} STREQUAL ARM_SIMULATOR) + set (IOS_ARCH arm64) + elseif (${IOS_PLATFORM} STREQUAL TV_ARM_SIMULATOR) + set (IOS_ARCH arm64) endif (${IOS_PLATFORM} STREQUAL OS) endif(NOT DEFINED IOS_ARCH) set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS") @@ -169,8 +197,8 @@ set (CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks ) -# only search the iOS sdks, not the remainder of the host filesystem (except for programs, so that we can still find Python if needed) -set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +# only search the iOS sdks, not the remainder of the host filesystem +set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY) set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) From e9160f9228fdea4629e25a2f94b3a2ec2669b764 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 19 Aug 2025 11:45:07 +0200 Subject: [PATCH 38/92] [MAINT] Added optional test discovery. Added improvements for attributes. Enabled std shared mutex if compiling with C++17 (#3190) * [MAINT] Added optional test discovery. Added improvements for attributes * Enabled C++17 version of shared mutex when compiling in C++17 mode * Added UT discovery option to configure * Changed enabled std::shared_mutex only if compiling with stdc++-sync * Fixed problem with not updated sync.cpp SharedMutex * Fixed review findings --------- Co-authored-by: Mikolaj Malecki --- CMakeLists.txt | 5 +++- configure-data.tcl | 1 + srtcore/srt.h | 4 +++ srtcore/srt_attr_defs.h | 61 +++++++++++++++++++++-------------------- srtcore/sync.cpp | 9 +++++- srtcore/sync.h | 12 ++++++-- test/test_sync.cpp | 3 ++ 7 files changed, 62 insertions(+), 33 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a5547454fd..0acd39b413 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -164,6 +164,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 unit test discovery when unit tests 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) @@ -1585,7 +1586,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/configure-data.tcl b/configure-data.tcl index 3510a9645e..d7b9d49376 100644 --- a/configure-data.tcl +++ b/configure-data.tcl @@ -49,6 +49,7 @@ set cmake_options { enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)" enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)" enable-unittests "Enable Unit Tests (will download Google UT) (default: OFF)" + enable-unittests-discovery "Enable UT Discovery (will run when building) (default: ON)" enable-encryption "Should encryption features be enabled (default: ON)" enable-c++-deps "Extra library dependencies in srt.pc for C language (default: ON)" use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)" diff --git a/srtcore/srt.h b/srtcore/srt.h index 71ac2c3af7..2d8ba39978 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -109,17 +109,21 @@ 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 #ifdef __cplusplus diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h index 726c4a03b4..1c489658f6 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 >= 201703L +#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 >= 201703L +#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/sync.cpp b/srtcore/sync.cpp index bfe1536577..96cb1a9d9d 100644 --- a/srtcore/sync.cpp +++ b/srtcore/sync.cpp @@ -357,6 +357,11 @@ int srt::sync::genRandomInt(int minVal, int maxVal) #endif // HAVE_CXX11 } +#if defined(ENABLE_STDCXX_SYNC) && HAVE_CXX17 + +// Shared mutex imp not required - aliased from C++17 + +#else //////////////////////////////////////////////////////////////////////////////// // @@ -451,4 +456,6 @@ int srt::sync::SharedMutex::getReaderCount() const { ScopedLock lk(m_Mutex); return m_iCountRead; -} \ No newline at end of file +} +#endif // C++17 for shared_mutex + diff --git a/srtcore/sync.h b/srtcore/sync.h index 45285828cb..7e11c29cf2 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -12,6 +12,7 @@ #define INC_SRT_SYNC_H #include "platform_sys.h" +#include "srt_attr_defs.h" #include #include @@ -21,6 +22,9 @@ #include #include #include +#if HAVE_CXX17 +#include +#endif #define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_STDCXX_STEADY #define SRT_SYNC_CLOCK_STR "STDCXX_STEADY" #else @@ -54,7 +58,6 @@ #include "srt.h" #include "utilities.h" -#include "srt_attr_defs.h" namespace srt @@ -491,11 +494,15 @@ inline void releaseCond(Condition& cv) { cv.destroy(); } // /////////////////////////////////////////////////////////////////////////////// +#if defined(ENABLE_STDCXX_SYNC) && HAVE_CXX17 +using SharedMutex = std::shared_mutex; +#else + /// Implementation of a read-write mutex. /// This allows multiple readers at a time, or a single writer. /// TODO: The class can be improved if needed to give writer a preference /// by adding additional m_iWritersWaiting member variable (counter). -/// TODO: The m_iCountRead could be made atomic to make unlok_shared() faster and lock-free. +/// TODO: The m_iCountRead could be made atomic to make unlock_shared() faster and lock-free. class SharedMutex { public: @@ -526,6 +533,7 @@ class SharedMutex int m_iCountRead; bool m_bWriterLocked; }; +#endif /// A version of std::scoped_lock (or lock_guard for C++11). /// We could have used the srt::sync::ScopedLock making it a template-based class. diff --git a/test/test_sync.cpp b/test/test_sync.cpp index d06b5e5f52..9fb1b3db36 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -609,6 +609,8 @@ TEST(SyncThread, Joinable) EXPECT_FALSE(foo.joinable()); } +#if !HAVE_CXX17 + /*****************************************************************************/ /* * SharedMutex @@ -693,6 +695,7 @@ TEST(SharedMutex, LockedReadCount) EXPECT_TRUE(mut.try_lock()); } +#endif /*****************************************************************************/ /* From 7497397023cddd72a54ea9ed79b60e96f1085640 Mon Sep 17 00:00:00 2001 From: Mikolaj Malecki Date: Tue, 19 Aug 2025 18:39:37 +0200 Subject: [PATCH 39/92] [core] Fixed bug in srt_epoll_uwait: wrong number of sockets returned --- srtcore/epoll.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/srtcore/epoll.cpp b/srtcore/epoll.cpp index 8cd8440c7c..c8c3c74b71 100644 --- a/srtcore/epoll.cpp +++ b/srtcore/epoll.cpp @@ -522,22 +522,17 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6 throw CUDTException(MJ_NOTSUP, MN_INVAL); } - int total = 0; // This is a list, so count it during iteration - CEPollDesc::enotice_t::iterator i = ed.enotice_begin(); - while (i != ed.enotice_end()) + CEPollDesc::enotice_t::iterator i = ed.enotice_begin(), inext; + int pos = 0; // This is a list, so count it during iteration + for (inext = i ; i != ed.enotice_end() && pos < fdsSize ; ++pos, i = inext) { - int pos = total; // previous past-the-end position - ++total; - - if (total > fdsSize) - break; + ++inext; // deletion-safe list loop fdsSet[pos] = *i; - - ed.checkEdge(i++); // NOTE: potentially deletes `i` + ed.checkEdge(i); // NOTE: potentially deletes `i` } - if (total) - return total; + if (pos) // pos is increased by 1 towards the last used position + return pos; } if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000))) From d720b8c9a47c265f60eaf5a7bf6a1990423d039f Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 26 Aug 2025 09:36:12 +0200 Subject: [PATCH 40/92] [core] Rework m_bListening to lock-free and m_GlobControlLock to use shared mutex (#3189) * [MAINT] Added optional test discovery. Added improvements for attributes * Enabled C++17 version of shared mutex when compiling in C++17 mode * [core] Rework m_bListening to lock-free and m_GlobControlLock to use shared mutex * Added UT discovery option to configure * Changed enabled std::shared_mutex only if compiling with stdc++-sync * Fixed problem with not updated sync.cpp SharedMutex * Removed dead code * Fixed review findings * Demoted some locks on m_GlobControlLock to shared. Added false positive TSan info * Merged changes from #3200 * Added a fix that should repair the problem reported in 3196. Still needs rechecks * Fixed incorrect shared lock applied for a case when a new group is to be created (required exclusive) --------- Co-authored-by: Mikolaj Malecki --- srtcore/api.cpp | 65 +++++++++------ srtcore/api.h | 2 +- srtcore/core.cpp | 200 +++++++++++++++++++++++++++++----------------- srtcore/core.h | 1 - srtcore/group.cpp | 6 +- srtcore/queue.cpp | 22 +++-- srtcore/queue.h | 5 +- srtcore/sync.h | 26 +++--- 8 files changed, 206 insertions(+), 121 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 14d7a46529..da929ef5ab 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -281,7 +281,8 @@ void srt::CUDTUnited::closeAllSockets() HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); { - ScopedLock glock(m_GlobControlLock); + // Pre-closing: run over all open sockets and close them. + SharedLock glock(m_GlobControlLock); for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { @@ -297,6 +298,16 @@ void srt::CUDTUnited::closeAllSockets() s->removeFromGroup(false); } #endif + } + } + + { + ExclusiveLock glock(m_GlobControlLock); + + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + m_ClosedSockets[i->first] = s; // remove from listener's queue @@ -518,7 +529,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) HLOGC(smlog.Debug, log << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID); // protect the m_Sockets structure. - ScopedLock cs(m_GlobControlLock); + ExclusiveLock cs(m_GlobControlLock); m_Sockets[ns->m_SocketID] = ns; } catch (...) @@ -536,6 +547,12 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps) return ns->m_SocketID; } +// XXX NOTE: TSan reports here false positive against the call +// to CRcvQueue::removeListener. This here will apply shared +// lock on m_GlobControlLock in the call of locateSocket, while +// having applied a shared lock on CRcvQueue::m_pListener in +// CRcvQueue::worker_ProcessConnectionRequest. As this thread +// locks both mutexes as shared, it doesn't form a deadlock. int srt::CUDTUnited::newConnection(const SRTSOCKET listen, const sockaddr_any& peer, const CPacket& hspkt, @@ -675,7 +692,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, HLOGC(cnlog.Debug, log << "newConnection: incoming " << peer.str() << ", mapping socket " << ns->m_SocketID); { - ScopedLock cg(m_GlobControlLock); + ExclusiveLock cg(m_GlobControlLock); m_Sockets[ns->m_SocketID] = ns; } @@ -724,7 +741,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, { // protect the m_PeerRec structure (and group existence) - ScopedLock glock(m_GlobControlLock); + ExclusiveLock glock(m_GlobControlLock); try { HLOGC(cnlog.Debug, log << "newConnection: mapping peer " << ns->m_PeerID @@ -898,7 +915,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // connect() in UDT code) may fail, in which case this socket should not be // further processed and should be removed. { - ScopedLock cg(m_GlobControlLock); + ExclusiveLock cg(m_GlobControlLock); #if ENABLE_BONDING if (ns->m_GroupOf) @@ -973,7 +990,7 @@ int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_ SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u) { // protects the m_Sockets structure - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); sockets_t::const_iterator i = m_Sockets.find(u); @@ -1247,7 +1264,7 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int { // Put a lock to protect the group against accidental deletion // in the meantime. - ScopedLock glock(m_GlobControlLock); + SharedLock glock(m_GlobControlLock); // Check again; it's unlikely to happen, but // it's a theoretically possible scenario if (s->m_GroupOf) @@ -1527,7 +1544,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } { - ScopedLock cs(m_GlobControlLock); + ExclusiveLock cs(m_GlobControlLock); if (m_Sockets.count(sid) == 0) { HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " deleted in process"); @@ -1633,7 +1650,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i targets[tii].errorcode = e.getErrorCode(); targets[tii].id = CUDT::INVALID_SOCK; - ScopedLock cl(m_GlobControlLock); + ExclusiveLock cl(m_GlobControlLock); ns->removeFromGroup(false); m_Sockets.erase(ns->m_SocketID); // Intercept to delete the socket on failure. @@ -1645,7 +1662,7 @@ 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; - ScopedLock cl(m_GlobControlLock); + ExclusiveLock cl(m_GlobControlLock); ns->removeFromGroup(false); m_Sockets.erase(ns->m_SocketID); // Intercept to delete the socket on failure. @@ -2019,7 +2036,7 @@ void srt::CUDTUnited::deleteGroup(CUDTGroup* g) { using srt_logging::gmlog; - srt::sync::ScopedLock cg(m_GlobControlLock); + srt::sync::ExclusiveLock cg(m_GlobControlLock); return deleteGroup_LOCKED(g); } @@ -2110,7 +2127,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) HLOGC(smlog.Debug, log << "@" << u << "U::close done. GLOBAL CLOSE: " << s->core().CONID() << "Acquiring GLOBAL control lock"); - ScopedLock manager_cg(m_GlobControlLock); + ExclusiveLock manager_cg(m_GlobControlLock); // since "s" is located before m_GlobControlLock, locate it again in case // it became invalid // XXX This is very weird; if we state that the CUDTSocket object @@ -2181,7 +2198,7 @@ int srt::CUDTUnited::close(CUDTSocket* s) // Done the other way, but still done. You can stop waiting. bool isgone = false; { - ScopedLock manager_cg(m_GlobControlLock); + SharedLock manager_cg(m_GlobControlLock); isgone = m_ClosedSockets.count(u) == 0; } if (!isgone) @@ -2583,7 +2600,7 @@ int srt::CUDTUnited::epoll_release(const int eid) srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh) { - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); CUDTSocket* s = locateSocket_LOCKED(u); if (!s) { @@ -2611,7 +2628,7 @@ srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u) #if ENABLE_BONDING srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh) { - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); const groups_t::iterator i = m_Groups.find(u); if (i == m_Groups.end()) @@ -2628,7 +2645,7 @@ srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling e srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) { - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); CUDTGroup* g = s->m_GroupOf; if (!g) return NULL; @@ -2642,7 +2659,7 @@ srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s) srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh) { - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); CUDTSocket* s = locateSocket_LOCKED(u); if (!s) @@ -2665,7 +2682,7 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) // directly from m_Sockets, or even better, has been acquired // by some other functionality already, which is only about to // be released earlier than you need. - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); s->apiAcquire(); // Keep the lock so that no one changes anything in the meantime. // If the socket m_Status == SRTS_CLOSED (set by setClosed()), then @@ -2681,7 +2698,7 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s) srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn) { - ScopedLock cg(m_GlobControlLock); + SharedLock cg(m_GlobControlLock); map >::iterator i = m_PeerRec.find(CUDTSocket::getPeerSpec(id, isn)); if (i == m_PeerRec.end()) @@ -2705,7 +2722,7 @@ srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRT void srt::CUDTUnited::checkBrokenSockets() { - ScopedLock cg(m_GlobControlLock); + ExclusiveLock cg(m_GlobControlLock); #if ENABLE_BONDING vector delgids; @@ -3059,7 +3076,7 @@ bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, cons void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) { - ScopedLock cg(m_GlobControlLock); + ExclusiveLock cg(m_GlobControlLock); // If udpsock is provided, then this socket will be simply // taken for binding as a good deal. It would be nice to make @@ -3366,7 +3383,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons // multiplexer wasn't found by id, the search by port number continues. bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) { - ScopedLock cg(m_GlobControlLock); + ExclusiveLock cg(m_GlobControlLock); const int port = ls->m_SelfAddr.hport(); HLOGC(smlog.Debug, @@ -3534,7 +3551,7 @@ SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) { try { - srt::sync::ScopedLock globlock(uglobal().m_GlobControlLock); + srt::sync::ExclusiveLock globlock(uglobal().m_GlobControlLock); return newGroup(gt).id(); // Note: potentially, after this function exits, the group // could be deleted, immediately, from a separate thread (tho @@ -3590,7 +3607,7 @@ SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket) { // Lock this for the whole function as we need the group // to persist the call. - ScopedLock glock(uglobal().m_GlobControlLock); + SharedLock glock(uglobal().m_GlobControlLock); CUDTSocket* s = uglobal().locateSocket_LOCKED(socket); if (!s || !s->m_GroupOf) return APIError(MJ_NOTSUP, MN_INVAL, 0); diff --git a/srtcore/api.h b/srtcore/api.h index d0249ca109..d3f8427d99 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -407,7 +407,7 @@ class CUDTUnited groups_t m_Groups; #endif - sync::Mutex m_GlobControlLock; // used to synchronize UDT API + sync::SharedMutex m_GlobControlLock; // used to synchronize UDT API sync::Mutex m_IDLock; // used to synchronize ID generation diff --git a/srtcore/core.cpp b/srtcore/core.cpp index d2118c5552..344f21ac1b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -1012,8 +1012,6 @@ void srt::CUDT::open() void srt::CUDT::setListenState() { - ScopedLock cg(m_ConnectionLock); - if (!m_bOpened) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); @@ -1021,14 +1019,42 @@ void srt::CUDT::setListenState() throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // listen can be called more than once - if (m_bListening) - return; - - // if there is already another socket listening on the same port - if (m_pRcvQueue->setListener(this) < 0) - throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); - - m_bListening = true; + // If two threads call srt_listen at the same time, only + // one will pass this condition; others will be rejected. + // If it was called ever once, none will pass. + for (;;) + { + if (m_bListening.compare_exchange(false, true)) + { + // if there is already another socket listening on the same port + if (!m_pRcvQueue->setListener(this)) + { + // Failed here, so + m_bListening = false; + throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); + } + } + else + { + // Ok, this thread could have been blocked access, + // but still the other thread that attempted to set + // the listener could have failed. Therefore check + // again if the listener was set successfully, and + // if the listening point is still free, try again. + CUDT* current = m_pRcvQueue->getListener(); + if (current == NULL) + { + continue; + } + else if (current != this) + { + // Some other listener already set it + throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); + } + // If it was you who set this, just return with no exception. + } + break; + } } size_t srt::CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version) @@ -1734,7 +1760,7 @@ bool srt::CUDT::createSrtHandshake( if (have_group) { // NOTE: See information about mutex ordering in api.h - ScopedLock gdrg (uglobal().m_GlobControlLock); + SharedLock gdrg (uglobal().m_GlobControlLock); if (!m_parent->m_GroupOf) { // This may only happen if since last check of m_GroupOf pointer the socket was removed @@ -3188,10 +3214,19 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A return false; } - ScopedLock guard_group_existence (uglobal().m_GlobControlLock); - if (m_SrtHsSide == HSD_INITIATOR) { + // Here we'll be only using a pre-existing group, so shared is enough. + SharedLock guard_group_existence (uglobal().m_GlobControlLock); + + // Recheck broken flags after acquisition + if (m_bClosing || m_bBroken) + { + m_RejectReason = SRT_REJ_CLOSE; + LOGC(cnlog.Error, log << CONID() << "interpretGroup: closure during handshake, interrupting"); + return false; + } + // This is a connection initiator that has requested the peer to make a // mirror group and join it, then respond its mirror group id. The // `grpid` variable contains this group ID; map this as your peer @@ -3210,64 +3245,81 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A return false; } - // Group existence is guarded, so we can now lock the group as well. - ScopedLock gl(*pg->exp_groupLock()); - - // Now we know the group exists, but it might still be closed - if (pg->closing()) { - LOGC(cnlog.Error, log << CONID() << "HS/RSP: group was closed in the process, can't continue connecting"); - m_RejectReason = SRT_REJ_IPE; - return false; - } + // XXX Consider moving this to another function to avoid + // exposing group lock. - SRTSOCKET peer = pg->peerid(); - if (peer == -1) - { - // This is the first connection within this group, so this group - // has just been informed about the peer membership. Accept it. - pg->set_peerid(grpid); - HLOGC(cnlog.Debug, - log << CONID() << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() - << ", copying characteristic data"); + // Group existence is guarded, so we can now lock the group as well. + ScopedLock gl(*pg->exp_groupLock()); - // The call to syncWithSocket is copying - // some interesting data from the first connected - // socket. This should be only done for the first successful connection. - pg->syncWithSocket(*this, HSD_INITIATOR); - } - // Otherwise the peer id must be the same as existing, otherwise - // this group is considered already bound to another peer group. - // (Note that the peer group is peer-specific, and peer id numbers - // may repeat among sockets connected to groups established on - // different peers). - else if (peer != grpid) - { - LOGC(cnlog.Error, - log << CONID() << "IPE: HS/RSP: group membership responded for peer $" << grpid - << " but the current socket's group $" << pg->id() << " has already a peer $" << peer); - m_RejectReason = SRT_REJ_GROUP; - return false; - } - else - { - HLOGC(cnlog.Debug, - log << CONID() << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $" - << pg->peerid()); + // Now we know the group exists, but it might still be closed + if (pg->closing()) + { + LOGC(cnlog.Error, log << CONID() << "HS/RSP: group was closed in the process, can't continue connecting"); + m_RejectReason = SRT_REJ_IPE; + return false; + } + + SRTSOCKET peer = pg->peerid(); + if (peer == SRT_INVALID_SOCK) + { + // This is the first connection within this group, so this group + // has just been informed about the peer membership. Accept it. + pg->set_peerid(grpid); + HLOGC(cnlog.Debug, + log << CONID() << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid() + << ", copying characteristic data"); + + // The call to syncWithSocket is copying + // some interesting data from the first connected + // socket. This should be only done for the first successful connection. + pg->syncWithSocket(*this, HSD_INITIATOR); + } + // Otherwise the peer id must be the same as existing, otherwise + // this group is considered already bound to another peer group. + // (Note that the peer group is peer-specific, and peer id numbers + // may repeat among sockets connected to groups established on + // different peers). + else if (peer != grpid) + { + LOGC(cnlog.Error, + log << CONID() << "IPE: HS/RSP: group membership responded for peer $" << grpid + << " but the current socket's group $" << pg->id() << " has already a peer $" << peer); + m_RejectReason = SRT_REJ_GROUP; + return false; + } + else + { + HLOGC(cnlog.Debug, + log << CONID() << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $" + << pg->peerid()); + } } + m_parent->m_GroupOf->debugGroup(); } else { + // Here we'll be potentially creating a new group, hence exclusive is needed. + ExclusiveLock guard_group_existence (uglobal().m_GlobControlLock); + + // Recheck broken flags after acquisition + if (m_bClosing || m_bBroken) + { + m_RejectReason = SRT_REJ_CLOSE; + LOGC(cnlog.Error, log << CONID() << "interpretGroup: closure during handshake, interrupting"); + return false; + } + // This is a connection responder that has been requested to make a // mirror group and join it. Later on, the HS response will be sent // and its group ID will be added to the HS extensions as mirror group // ID to the peer. SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags); - if (!lgid) + if (lgid == 0) 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. @@ -3288,10 +3340,9 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A f->weight = link_weight; f->agent = m_parent->m_SelfAddr; f->peer = m_PeerAddr; + m_parent->m_GroupOf->debugGroup(); } - m_parent->m_GroupOf->debugGroup(); - // That's all. For specific things concerning group // types, this will be later. return true; @@ -4843,7 +4894,7 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, { #if ENABLE_BONDING - ScopedLock cl (uglobal().m_GlobControlLock); + SharedLock cl (uglobal().m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; if (g) { @@ -4950,10 +5001,11 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous, //int token = -1; #if ENABLE_BONDING { - ScopedLock cl (uglobal().m_GlobControlLock); + SharedLock cl (uglobal().m_GlobControlLock); CUDTGroup* g = m_parent->m_GroupOf; if (g) { + ScopedLock gl (*g->exp_groupLock()); // XXX this might require another check of group type. // For redundancy group, at least, update the status in the group. @@ -6380,7 +6432,11 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT if (m_bListening) { m_bListening = false; - m_pRcvQueue->removeListener(this); + bool removed SRT_ATR_UNUSED = m_pRcvQueue->removeListener(this); + if (!removed) + { + LOGC(smlog.Error, log << CONID() << "CLOSING: IPE: listening=true but listener removal failed!"); + } } else if (m_bConnecting) { @@ -8213,7 +8269,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) // can be either never set, already reset, or ever set // and possibly dangling. The re-check after lock eliminates // the dangling case. - ScopedLock glock (uglobal().m_GlobControlLock); + SharedLock glock (uglobal().m_GlobControlLock); // Note that updateLatestRcv will lock m_GroupOf->m_GroupLock, // but this is an intended order. @@ -8274,7 +8330,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf) { // See above explanation for double-checking - ScopedLock glock (uglobal().m_GlobControlLock); + SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { @@ -8431,7 +8487,7 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) { // m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only // be done now that m_RecvAckLock is unlocked. - ScopedLock glock (uglobal().m_GlobControlLock); + SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { HLOGC(inlog.Debug, log << CONID() << "ACK: acking group sender buffer for #" << msgno_at_last_acked_seq); @@ -8591,7 +8647,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ #if ENABLE_BONDING if (m_parent->m_GroupOf) { - ScopedLock glock (uglobal().m_GlobControlLock); + SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { // Will apply m_GroupLock, ordered after m_GlobControlLock. @@ -8812,7 +8868,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr if (m_config.bDriftTracer) { #if ENABLE_BONDING - ScopedLock glock(uglobal().m_GlobControlLock); // XXX not too excessive? + ExclusiveLock glock(uglobal().m_GlobControlLock); // XXX not too excessive? const bool drift_updated = #endif m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt); @@ -9358,7 +9414,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) if (m_parent->m_GroupOf) { - ScopedLock glock (uglobal().m_GlobControlLock); + SharedLock glock (uglobal().m_GlobControlLock); grpspec = m_parent->m_GroupOf ? " group=$" + Sprint(m_parent->m_GroupOf->id()) : string(); @@ -10512,7 +10568,7 @@ int srt::CUDT::processData(CUnit* in_unit) // reception sequence pointer stating that this link is not receiving. if (m_parent->m_GroupOf) { - ScopedLock protect_group_existence (uglobal().m_GlobControlLock); + ExclusiveLock protect_group_existence (uglobal().m_GlobControlLock); groups::SocketData* gi = m_parent->m_GroupMemberData; // This check is needed as after getting the lock the socket @@ -11720,7 +11776,7 @@ void srt::CUDT::checkTimers() #if ENABLE_BONDING if (m_parent->m_GroupOf) { - ScopedLock glock (uglobal().m_GlobControlLock); + SharedLock glock (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { // Pass socket ID because it's about changing group socket data @@ -11752,7 +11808,7 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) #if ENABLE_BONDING bool pending_broken = false; { - ScopedLock guard_group_existence (uglobal().m_GlobControlLock); + SharedLock guard_group_existence (uglobal().m_GlobControlLock); if (m_parent->m_GroupOf) { token = m_parent->m_GroupMemberData->token; @@ -11787,7 +11843,7 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode) // existence of the group will not be changed during // the operation. The attempt of group deletion will // have to wait until this operation completes. - ScopedLock lock(uglobal().m_GlobControlLock); + ExclusiveLock lock(uglobal().m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) { @@ -12045,7 +12101,7 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr // existence of the group will not be changed during // the operation. The attempt of group deletion will // have to wait until this operation completes. - ScopedLock lock(uglobal().m_GlobControlLock); + ExclusiveLock lock(uglobal().m_GlobControlLock); CUDTGroup* pg = m_parent->m_GroupOf; if (pg) { diff --git a/srtcore/core.h b/srtcore/core.h index 6f7bd6b19f..00fd44923c 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -436,7 +436,6 @@ class CUDT // immediately to free the socket void notListening() { - sync::ScopedLock cg(m_ConnectionLock); m_bListening = false; m_pRcvQueue->removeListener(this); } diff --git a/srtcore/group.cpp b/srtcore/group.cpp index dc3c1a9be7..339512573f 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -1044,7 +1044,7 @@ void CUDTGroup::close() vector ids; { - ScopedLock glob(CUDT::uglobal().m_GlobControlLock); + ExclusiveLock glob(CUDT::uglobal().m_GlobControlLock); ScopedLock g(m_GroupLock); m_bClosing = true; @@ -3409,7 +3409,7 @@ void CUDTGroup::send_CloseBrokenSockets(vector& w_wipeme) // With unlocked GroupLock, we can now lock GlobControlLock. // This is needed to prevent any of them deleted from the container // at the same time. - ScopedLock globlock(CUDT::uglobal().m_GlobControlLock); + SharedLock globlock(CUDT::uglobal().m_GlobControlLock); for (vector::iterator p = w_wipeme.begin(); p != w_wipeme.end(); ++p) { @@ -3446,7 +3446,7 @@ void CUDTGroup::sendBackup_CloseBrokenSockets(SendBackupCtx& w_sendBackupCtx) // With unlocked GroupLock, we can now lock GlobControlLock. // This is needed prevent any of them be deleted from the container // at the same time. - ScopedLock globlock(CUDT::uglobal().m_GlobControlLock); + SharedLock globlock(CUDT::uglobal().m_GlobControlLock); typedef vector::const_iterator const_iter_t; for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member) diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 6cb4faeb11..f8b7821742 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -1429,7 +1429,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, bool have_listener = false; { SharedLock shl(m_pListener); - CUDT* pListener = m_pListener.getPtrNoLock(); + CUDT* pListener = m_pListener.get_locked(shl); if (pListener) { @@ -1720,17 +1720,25 @@ int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet) return (int)w_packet.getLength(); } -int srt::CRcvQueue::setListener(CUDT* u) +bool srt::CRcvQueue::setListener(CUDT* u) { - if (!m_pListener.set(u)) - return -1; + return m_pListener.compare_exchange(NULL, u); +} - return 0; +srt::CUDT* srt::CRcvQueue::getListener() +{ + SharedLock lkl (m_pListener); + return m_pListener.get_locked(lkl); } -void srt::CRcvQueue::removeListener(const CUDT* u) +// XXX NOTE: TSan reports here false positive against the call +// to locateSocket in CUDTUnited::newConnection. This here will apply +// exclusive lock on m_pListener, while keeping shared lock on +// CUDTUnited::m_GlobControlLock in CUDTUnited::closeAllSockets. +// As the other thread locks both as shared, this is no deadlock risk. +bool srt::CRcvQueue::removeListener(CUDT* u) { - m_pListener.clearIf(u); + return m_pListener.compare_exchange(u, NULL); } void srt::CRcvQueue::registerConnector(const SRTSOCKET& id, diff --git a/srtcore/queue.h b/srtcore/queue.h index 48bedd9af0..0d23790e9d 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -538,8 +538,9 @@ class CRcvQueue #endif private: - int setListener(CUDT* u); - void removeListener(const CUDT* u); + bool setListener(CUDT* u); + CUDT* getListener(); + bool removeListener(CUDT* u); void registerConnector(const SRTSOCKET& id, CUDT* u, diff --git a/srtcore/sync.h b/srtcore/sync.h index 7e11c29cf2..100a11eedb 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -535,6 +535,15 @@ class SharedMutex }; #endif +inline void enterCS(SharedMutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_ACQUIRE(m) { m.lock(); } + +inline bool tryEnterCS(SharedMutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_TRY_ACQUIRE(true, m) { return m.try_lock(); } + +inline void leaveCS(SharedMutex& m) SRT_ATTR_REQUIRES(m) SRT_ATTR_RELEASE(m) { m.unlock(); } + +inline void setupMutex(SharedMutex&, const char*) {} +inline void releaseMutex(SharedMutex&) {} + /// A version of std::scoped_lock (or lock_guard for C++11). /// We could have used the srt::sync::ScopedLock making it a template-based class. /// But in that case all usages would have to be specificed like ScopedLock in C++03. @@ -585,26 +594,21 @@ class CSharedObjectPtr : public SharedMutex { } - bool set(T* pObj) + bool compare_exchange(T* expected, T* newobj) { ExclusiveLock lock(*this); - if (m_pObj) + if (m_pObj != expected) return false; - m_pObj = pObj; + m_pObj = newobj; return true; } - bool clearIf(const T* pObj) + T* get_locked(SharedLock& /*wholocked*/) { - ExclusiveLock lock(*this); - if (m_pObj != pObj) - return false; - m_pObj = NULL; - return true; + // XXX Here you can assert that `wholocked` locked *this. + return m_pObj; } - T* getPtrNoLock() const { return m_pObj; } - private: T* m_pObj; }; From 725e2d472115c4ba745822441b79125ec9efdd58 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Wed, 27 Aug 2025 10:56:20 +0200 Subject: [PATCH 41/92] [build] Update Ubuntu runners to Lastest. (#3180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [build] Update Ubuntu runners to 22.04. * Update to googletest-1.12.1 * Remove Unit Tests from the ABI workflow. * Use latest version of ubuntu * Remove build-wrapper. --------- Authored-by: Clément Gérouville --- .github/workflows/abi.yml | 6 +++--- .github/workflows/android.yaml | 2 +- .github/workflows/cxx11-ubuntu.yaml | 4 ++-- scripts/googletest-download.cmake | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml index 2c05cc06b9..51d22a5164 100644 --- a/.github/workflows/abi.yml +++ b/.github/workflows/abi.yml @@ -12,7 +12,7 @@ env: jobs: build: name: ABI checks - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -22,7 +22,7 @@ jobs: run: | cd pull_request mkdir _build && cd _build - cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_UNITTESTS=ON ../ + cmake -DCMAKE_BUILD_TYPE=Debug ../ - name: build run: | sudo apt install -y abi-dumper @@ -41,7 +41,7 @@ jobs: echo $SRT_TAG_VERSION cd tag mkdir _build && cd _build - cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_UNITTESTS=ON ../ + cmake -DCMAKE_BUILD_TYPE=Debug ../ - name: build_tag run: | cd tag diff --git a/.github/workflows/android.yaml b/.github/workflows/android.yaml index 0af85fda38..5e11c75df9 100644 --- a/.github/workflows/android.yaml +++ b/.github/workflows/android.yaml @@ -9,7 +9,7 @@ on: jobs: build: name: NDK-R23 - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - name: Setup Android NDK R23 diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index 7d30f4a1af..2f362d84fa 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -9,7 +9,7 @@ on: jobs: build: name: ubuntu - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: configure @@ -17,7 +17,7 @@ jobs: mkdir _build && cd _build cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: build - run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . + run: cd _build && cmake --build . - name: test run: | cd _build && ctest --extra-verbose diff --git a/scripts/googletest-download.cmake b/scripts/googletest-download.cmake index 7469f4f7b1..36554f316f 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.1 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From 265d0e495f5a44e762b65ceb60b9fa0e7a698b73 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 29 Aug 2025 13:10:58 +0200 Subject: [PATCH 42/92] [core] Fixed invalid cookie contest calculation (#3212) Co-authored-by: Mikolaj Malecki --- srtcore/core.cpp | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 344f21ac1b..050caaa177 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -4107,13 +4107,13 @@ void srt::CUDT::cookieContest() if (m_SrtHsSide != HSD_DRAW) return; - LOGC(cnlog.Debug, - log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie); - // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0) { + LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie + << " - ERROR: zero not allowed!"); + // Note that it's virtually impossible that Agent's cookie is not ready, this // shall be considered IPE. // Not all cookies are ready, don't start the contest. @@ -4134,11 +4134,34 @@ void srt::CUDT::cookieContest() // int32_t iBetterCookie = 2002790373 (7760 27E5); // // Now 64-bit arithmetic is used to calculate the actual result of subtraction. - // The 31-st bit is then checked to check if the resulting is negative in 32-bit aritmetics. - // This way the old contest behavior is preserved, and potential compiler optimisations are avoided. - const int64_t contest = int64_t(m_ConnReq.m_iCookie) - int64_t(m_ConnRes.m_iCookie); - - if ((contest & 0xFFFFFFFF) == 0) + // + // In SRT v1.5.4 there was a version, that checked: + // - if LOWER 32-bits are 0, this is a draw + // - if bit 31 is set (AND with 0x80000000), the result is considered negative. + // This was erroneous because for 1 and 0x80000001 cookie values the + // result was always the same, regardless of the order: + // + // 0x0000000000000001 - 0xFFFFFFFF80000001 = 0x0000000080000000 + // 0xFFFFFFFF80000001 - 0x0000000080000000 = 0xFFFFFFFF80000000 + // + // >> if (contest & 0x80000000) -> results in true in both comparisons. + // + // This version takes the bare result of the 64-bit arithmetics. + const int64_t xreq = int64_t(m_ConnReq.m_iCookie); + const int64_t xres = int64_t(m_ConnRes.m_iCookie); + const int64_t contest = xreq - xres; + + // NOTE: for 1.6.0 use: + // fmtc hex64 = fmtc().uhex().fillzero().width(16); then + // << fmt(xreq, hex64) + LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie + << " peer=" << m_ConnRes.m_iCookie + << hex << uppercase << setfill('0') // PERSISTENT flags + << " X64: "<< setw(16) << xreq + << " vs. " << setw(16) << xres + << " DIFF: " << setw(16) << contest); + + if (contest == 0) { // DRAW! The only way to continue would be to force the // cookies to be regenerated and to start over. But it's @@ -4154,7 +4177,7 @@ void srt::CUDT::cookieContest() return; } - if (contest & 0x80000000) + if (contest < 0) { m_SrtHsSide = HSD_RESPONDER; return; From 5138f37d45b5724e379e27760dc7e30bcfa110f1 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Fri, 29 Aug 2025 13:15:35 +0200 Subject: [PATCH 43/92] [core] Cleanup SRT state after a fork() (issue #3177) (#3179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [core] Cleanup SRT state after a fork() (issue #3177) * Free socket memory without calling the destructor. * Remove srt_cleanupAtFork() from the API. * Make it compile on systems that don't support pthread_atfork() * Remove a typo * Avoid to send shutdown packet when cleaning up after a fork. * Close the dangling UDP sockets, Free memory. * Add TODO for freeing the Send Queue after refacttoring it. * Ensure that CThread is joinable before join() * Try fix iOS-cxxsyncOFF * Replace the mutex pointer by a mutex reference. * Refactor the Multiplexer initialization. * Fix SIGSEGV * Fix Compilation error on a Debug Log * Rollback to cleaner code for the multiplexer initialization. * Add compatibility with C++11 Sync. * Apply code review changes * Replace the resetThread() macro by an inline function. Co-authored-by: Sektor van Skijlen * Reset m_CGStopCond in CUDTUnited * Rework of resetThread() * Ensure Garbage collector is in the right state after fork. * Protect ~CMultiplexer() against NULL pointers. * Protect resetAtFork() and stop() against NULL pointers. * Added fork example to the repository * Remove french comments. --------- Co-authored-by: Clément Gérouville Co-authored-by: Sektor van Skijlen Co-authored-by: Mikolaj Malecki --- CMakeLists.txt | 10 ++- examples/fork-test/Makefile | 9 +++ examples/fork-test/README.md | 7 ++ examples/fork-test/srt_client.c | 55 +++++++++++++++ examples/fork-test/srt_server.c | 118 ++++++++++++++++++++++++++++++++ srtcore/api.cpp | 90 ++++++++++++++++++++++-- srtcore/api.h | 3 + srtcore/core.cpp | 12 ++++ srtcore/core.h | 3 + srtcore/queue.cpp | 74 +++++++++++++++----- srtcore/queue.h | 18 +++++ srtcore/sync.h | 6 +- srtcore/sync_cxx11.cpp | 18 +++++ srtcore/sync_posix.cpp | 39 +++++++---- 14 files changed, 426 insertions(+), 36 deletions(-) create mode 100644 examples/fork-test/Makefile create mode 100644 examples/fork-test/README.md create mode 100644 examples/fork-test/srt_client.c create mode 100644 examples/fork-test/srt_server.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0acd39b413..2017139ede 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -966,10 +966,16 @@ else () message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") endif() +list(PREPEND CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY}") +unset(CMAKE_REQUIRED_QUIET) + +check_symbol_exists(pthread_atfork "pthread.h" HAVE_PTHREAD_ATFORK) +if ("${HAVE_PTHREAD_ATFORK}" STREQUAL "1") + add_definitions(-DHAVE_PTHREAD_ATFORK=1) +endif () + # To avoid the need for other judgments when ENABLE_STDCXX_SYNC is OFF in the future, this is a separate conditional statement. if (NOT ENABLE_STDCXX_SYNC AND ENABLE_MONOTONIC_CLOCK) - list(PREPEND CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY}") - unset(CMAKE_REQUIRED_QUIET) check_symbol_exists(pthread_condattr_setclock "pthread.h" HAVE_PTHREAD_CONDATTR_SETCLOCK) message(STATUS "Checking pthread_condattr_setclock: '${HAVE_PTHREAD_CONDATTR_SETCLOCK}'") if ("${HAVE_PTHREAD_CONDATTR_SETCLOCK}" STREQUAL "1") diff --git a/examples/fork-test/Makefile b/examples/fork-test/Makefile new file mode 100644 index 0000000000..57e9f3a946 --- /dev/null +++ b/examples/fork-test/Makefile @@ -0,0 +1,9 @@ +TARGETS=srt_server srt_client + +all: $(TARGETS) + +%: %.c + $(CC) $< `pkg-config --cflags --libs srt` -o `basename $< .c` + +clean: + rm -f $(TARGETS) diff --git a/examples/fork-test/README.md b/examples/fork-test/README.md new file mode 100644 index 0000000000..59752ad022 --- /dev/null +++ b/examples/fork-test/README.md @@ -0,0 +1,7 @@ +The `srt_server` and `srt_client` apps should be compiled using +external installation of SRT (e.g. in the local directory), each +one as a single program. This is not compiled as a part of SRT. + +If you want to use a local installation, simply set `PKG_CONFIG_PATH` +environment variable to point to the local installation directory with +"lib/pkgconfig" or "lib64/pkgconfig" suffix. diff --git a/examples/fork-test/srt_client.c b/examples/fork-test/srt_client.c new file mode 100644 index 0000000000..ab69d59569 --- /dev/null +++ b/examples/fork-test/srt_client.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#define SERVER_IP "127.0.0.1" +#define SERVER_PORT 9000 +int main() { + if (srt_startup() != 0) { + fprintf(stderr, "Error initializing SRT.\n"); + return 1; + } + SRTSOCKET client_sock = srt_create_socket(); + if (client_sock == SRT_INVALID_SOCK) { + fprintf(stderr, "Error creating a socket: %s\n", srt_getlasterror_str()); + return 1; + } + struct sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(SERVER_PORT); + inet_pton(AF_INET, SERVER_IP, &sa.sin_addr); + if (srt_connect(client_sock, (struct sockaddr*)&sa, sizeof(sa)) == SRT_ERROR) { + fprintf(stderr, "Error: srt_connect: %s\n", srt_getlasterror_str()); + return 1; + } + printf("Connected to SRT server %s:%d\n", SERVER_IP, SERVER_PORT); + const char* message = "Hello from SRT client!"; + int bytes = srt_send(client_sock, message, strlen(message)); + if (bytes == SRT_ERROR) { + fprintf(stderr, "Sending error: %s\n", srt_getlasterror_str()); + } else { + printf("Message sent: %s\n", message); + } + + while (1) + { + char buffer[1500]; + int nb = srt_recv(client_sock, buffer, sizeof(buffer)); + if (nb <= 0) + { + printf("Closed from the server !\n"); + srt_close(client_sock); + break; + } + buffer[nb] = 0; + printf("Server has sent: %s\n", buffer); + } + srt_cleanup(); + return 0; +} + diff --git a/examples/fork-test/srt_server.c b/examples/fork-test/srt_server.c new file mode 100644 index 0000000000..a680f1966b --- /dev/null +++ b/examples/fork-test/srt_server.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#define PORT 9000 + +int run(char *command) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork (intermediate)"); + exit(EXIT_FAILURE); + } + if (pid > 0) { + // Parent process + printf("[GRANDPARENT %d] waiting for grand-child process pid=%d to finish...\n", + getpid(), pid); + waitpid(pid, NULL, 0); // Wait for intermediate child + printf("[GRANDPARENT] returning\n"); + return 0; + } + // Intermediate process + //srt_cleanup(); + if (setsid() < 0) { + perror("setsid"); + exit(EXIT_FAILURE); + } + pid_t grandchild_pid = fork(); + if (grandchild_pid < 0) { + perror("fork (grandchild)"); + exit(EXIT_FAILURE); + } + if (grandchild_pid > 0) { + printf("[PARENT %d] waiting for 10s with child process pid=%d ...\n", + getpid(), grandchild_pid); + // Intermediate process exits immediately + sleep(10); + printf("[PARENT] exitting\n"); + exit(0); + } + // Grandchild process + // Redirect stdin to /dev/null + printf("[CHILD %d] Preparing descriptors...\n", getpid()); + int devnull = open("/dev/null", O_RDONLY); + if (devnull >= 0) { + dup2(devnull, STDIN_FILENO); + close(devnull); + } else { + perror("open /dev/null"); + } + // Redirect stdout to stderr + dup2(STDERR_FILENO, STDOUT_FILENO); + // Execute the command + printf("[CHILD] Executing process '%s'...\n", command); + execl("/bin/sh", "sh", "-c", command, (char *)NULL); + // If execl fails + perror("execl"); + exit(EXIT_FAILURE); +} + +int main() { + if (srt_startup() != 0) { + fprintf(stderr, "Error initializing SRT.\n"); + return 1; + } + + SRTSOCKET serv_sock = srt_create_socket(); + if (serv_sock == SRT_INVALID_SOCK) { + fprintf(stderr, "Error creating SRT socket: %s\n", srt_getlasterror_str()); + return 1; + } + struct sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(PORT); + sa.sin_addr.s_addr = INADDR_ANY; + if (srt_bind(serv_sock, (struct sockaddr*)&sa, sizeof sa) == SRT_ERROR) { + fprintf(stderr, "Error: srt_bind: %s\n", srt_getlasterror_str()); + return 1; + } + if (srt_listen(serv_sock, 5) == SRT_ERROR) { + fprintf(stderr, "Error: srt_listen: %s\n", srt_getlasterror_str()); + return 1; + } + printf("SRT server is listening on port %d...\n", PORT); + struct sockaddr_in client_addr; + int addr_len = sizeof(client_addr); + SRTSOCKET client_sock = srt_accept(serv_sock, (struct sockaddr*)&client_addr, &addr_len); + if (client_sock == SRT_INVALID_SOCK) { + fprintf(stderr, "Error: srt_accept: %s\n", srt_getlasterror_str()); + return 1; + } + printf("Client connected via SRT !\n"); + char buffer[1500]; + int bytes = srt_recv(client_sock, buffer, sizeof(buffer)); + if (bytes > 0) { + buffer[bytes] = '\0'; + printf("Message received: %s\n", buffer); + const char resp [] = "We read you!"; + srt_send(client_sock, resp, (sizeof resp)-1); + } else { + printf("Error: reading from srt_recv: %s.\n", srt_getlasterror_str()); + } + run("date > /tmp/res"); + printf("Server: sleep(1)\n"); + sleep(1); + // Nettoyage + printf("Server: closing SRT sockets\n"); + srt_close(client_sock); + srt_close(serv_sock); + printf("Server: cleanup\n"); + srt_cleanup(); + printf("Server: exit\n"); + return 0; +} diff --git a/srtcore/api.cpp b/srtcore/api.cpp index da929ef5ab..22b093dccf 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -99,6 +99,12 @@ srt::CUDTSocket::~CUDTSocket() releaseMutex(m_ControlLock); } +void srt::CUDTSocket::resetAtFork() +{ + m_UDT.resetAtFork(); + resetCond(m_AcceptCond); +} + SRT_SOCKSTATUS srt::CUDTSocket::getStatus() { // TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false. @@ -275,6 +281,51 @@ void srt::CUDTUnited::stopGarbageCollector() } } +void srt::CUDTUnited::cleanupAllSockets() +{ + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) + { + CUDTSocket* s = i->second; + +#if ENABLE_BONDING + if (s->m_GroupOf) + { + s->removeFromGroup(false); + } +#endif + + // 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()) + { + ls->second->m_QueuedSockets.erase(s->m_SocketID); + } + s->core().closeAtFork(); + s->resetAtFork(); + delete(s); + } + m_Sockets.clear(); + +#if ENABLE_BONDING + for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j) + { + delete j->second; + } + m_Groups.clear(); +#endif + for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) + { + CMultiplexer &multiplexer = i->second; + multiplexer.resetAtFork(); + } + m_mMultiplexer.clear(); +} + + void srt::CUDTUnited::closeAllSockets() { // remove all sockets and multiplexers @@ -370,6 +421,18 @@ int srt::CUDTUnited::startup() return startGarbageCollector() ? 0 : -1; } +int srt::CUDTUnited::cleanupAtFork() +{ + cleanupAllSockets(); + resetThread(&m_GCThread); + resetCond(m_GCStopCond); + m_GCStopLock.unlock(); + setupCond(m_GCStopCond, "GCStop"); + m_iInstanceCount=0; + m_bGCStatus = false; + return 0; +} + int srt::CUDTUnited::cleanup() { // IMPORTANT!!! @@ -3076,8 +3139,9 @@ bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, cons void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) { + const int port = reqaddr.hport(); ExclusiveLock cg(m_GlobControlLock); - + // If udpsock is provided, then this socket will be simply // taken for binding as a good deal. It would be nice to make // a sanity check to see if this UDP socket isn't already installed @@ -3087,7 +3151,6 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons { // If not, we need to see if there exist already a multiplexer bound // to the same endpoint. - const int port = reqaddr.hport(); const CSrtConfig& cfgSocket = s->core().m_config; // This loop is going to check the attempted binding of @@ -3305,6 +3368,8 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons } } + + // a new multiplexer is needed CMultiplexer m; configureMuxer((m), s, reqaddr.family()); @@ -3360,7 +3425,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons // Rewrite the port here, as it might be only known upon return // from CChannel::open. m.m_iPort = installMuxer((s), m); - m_mMultiplexer[m.m_iID] = m; + swap(m_mMultiplexer[m.m_iID],m); } catch (const CUDTException&) { @@ -3373,7 +3438,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << m.m_iPort); + HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << port); } // This function is going to find a multiplexer for the port contained @@ -3492,6 +3557,14 @@ void* srt::CUDTUnited::garbageCollect(void* p) int srt::CUDT::startup() { +#if HAVE_PTHREAD_ATFORK + static bool registered = false; + if (!registered) + { + pthread_atfork(NULL, NULL, (void (*)()) srt::CUDT::cleanupAtFork); + registered = true; + } +#endif return uglobal().startup(); } @@ -3500,6 +3573,15 @@ int srt::CUDT::cleanup() return uglobal().cleanup(); } +int srt::CUDT::cleanupAtFork() +{ + CUDTUnited &context = uglobal(); + context.cleanupAtFork(); + new (&context) CUDTUnited(); + + return context.startup(); +} + SRTSOCKET srt::CUDT::socket() { try diff --git a/srtcore/api.h b/srtcore/api.h index d3f8427d99..42b97ef32c 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -120,6 +120,7 @@ class CUDTSocket } ~CUDTSocket(); + void resetAtFork(); void construct(); @@ -263,6 +264,7 @@ class CUDTUnited /// release the UDT library. /// @return 0 if success, otherwise -1 is returned. int cleanup(); + int cleanupAtFork(); /// Create a new UDT socket. /// @param [out] pps Variable (optional) to which the new socket will be written, if succeeded @@ -464,6 +466,7 @@ class CUDTUnited bool acquireSocket(CUDTSocket* s); bool startGarbageCollector(); void stopGarbageCollector(); + void cleanupAllSockets(); void closeAllSockets(); public: diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 050caaa177..0acef17bbe 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -6513,6 +6513,12 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT return true; } +bool srt::CUDT::closeAtFork() ATR_NOEXCEPT +{ + m_bShutdown = true; + return closeInternal(); +} + int srt::CUDT::receiveBuffer(char *data, int len) { if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false)) @@ -7939,6 +7945,12 @@ void srt::CUDT::destroySynch() m_RcvTsbPdCond.notify_all(); releaseCond(m_RcvTsbPdCond); } +void srt::CUDT::resetAtFork() +{ + resetCond(m_SendBlockCond); + resetCond(m_RecvDataCond); + resetCond(m_RcvTsbPdCond); +} void srt::CUDT::releaseSynch() { diff --git a/srtcore/core.h b/srtcore/core.h index 00fd44923c..a07d172771 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -197,6 +197,7 @@ class CUDT public: //API static int startup(); static int cleanup(); + static int cleanupAtFork(); static SRTSOCKET socket(); #if ENABLE_BONDING static SRTSOCKET createGroup(SRT_GROUP_TYPE); @@ -604,6 +605,7 @@ class CUDT /// Close the opened UDT entity. bool closeInternal() ATR_NOEXCEPT; + bool closeAtFork() ATR_NOEXCEPT; void updateBrokenConnection(); void completeBrokenConnectionDependencies(int errorcode); @@ -1045,6 +1047,7 @@ class CUDT void initSynch(); void destroySynch(); + void resetAtFork(); void releaseSynch(); private: // Common connection Congestion Control setup diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index f8b7821742..88c0d30f63 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -220,6 +220,11 @@ srt::CSndUList::~CSndUList() delete[] m_pHeap; } +void srt::CSndUList::resetAtFork() +{ + resetCond(m_ListCond); +} + void srt::CSndUList::update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts) { ScopedLock listguard(m_ListLock); @@ -414,6 +419,17 @@ srt::CSndQueue::CSndQueue() } srt::CSndQueue::~CSndQueue() +{ + delete m_pSndUList; +} + +void srt::CSndQueue::resetAtFork() +{ + resetThread(&m_WorkerThread); + m_pSndUList->resetAtFork(); +} + +void srt::CSndQueue::stop() { m_bClosing = true; @@ -430,8 +446,6 @@ srt::CSndQueue::~CSndQueue() HLOGC(rslog.Debug, log << "SndQueue: EXIT"); m_WorkerThread.join(); } - - delete m_pSndUList; } int srt::CSndQueue::ioctlQuery(int type) const @@ -1168,15 +1182,6 @@ srt::CRcvQueue::CRcvQueue() srt::CRcvQueue::~CRcvQueue() { - m_bClosing = true; - - if (m_WorkerThread.joinable()) - { - HLOGC(rslog.Debug, log << "RcvQueue: EXIT"); - m_WorkerThread.join(); - } - releaseCond(m_BufferCond); - delete m_pUnitQueue; delete m_pRcvUList; delete m_pHash; @@ -1194,6 +1199,24 @@ srt::CRcvQueue::~CRcvQueue() } } +void srt::CRcvQueue::resetAtFork() +{ + resetThread(&m_WorkerThread); +} + +void srt::CRcvQueue::stop() +{ + m_bClosing = true; + + if (m_WorkerThread.joinable()) + { + HLOGC(rslog.Debug, log << "RcvQueue: EXIT"); + m_WorkerThread.join(); + } + releaseCond(m_BufferCond); +} + + #if ENABLE_LOGGING srt::sync::atomic srt::CRcvQueue::m_counter(0); #endif @@ -1819,16 +1842,35 @@ void srt::CRcvQueue::storePktClone(int32_t id, const CPacket& pkt) } } -void srt::CMultiplexer::destroy() +void srt::CMultiplexer::resetAtFork() { - // Reverse order of the assigned. - delete m_pRcvQueue; - delete m_pSndQueue; - delete m_pTimer; + if (m_pRcvQueue != NULL) + m_pRcvQueue->resetAtFork(); + if (m_pSndQueue != NULL) + m_pSndQueue->resetAtFork(); +} +void srt::CMultiplexer::close() +{ if (m_pChannel) { m_pChannel->close(); delete m_pChannel; + m_pChannel = NULL; } } + +void srt::CMultiplexer::stop() +{ + if (m_pRcvQueue != NULL) + m_pRcvQueue->stop(); + if (m_pSndQueue != NULL) + m_pSndQueue->stop(); +} + +void srt::CMultiplexer::destroy() +{ + // Reverse order of the assigned. + stop(); + close(); +} diff --git a/srtcore/queue.h b/srtcore/queue.h index 0d23790e9d..cf31097673 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -157,6 +157,7 @@ class CSndUList }; static EReschedule rescheduleIf(bool cond) { return cond ? DO_RESCHEDULE : DONT_RESCHEDULE; } + void resetAtFork(); /// Update the timestamp of the UDT instance on the list. /// @param [in] u pointer to the UDT instance @@ -401,6 +402,7 @@ class CSndQueue ~CSndQueue(); public: + void resetAtFork(); // XXX There's currently no way to access the socket ID set for // whatever the queue is currently working for. Required to find // some way to do this, possibly by having a "reverse pointer". @@ -439,6 +441,7 @@ class CSndQueue int sockoptQuery(int level, int type) const; void setClosing() { m_bClosing = true; } + void stop(); private: static void* worker(void* param); @@ -486,6 +489,7 @@ class CRcvQueue ~CRcvQueue(); public: + void resetAtFork(); // XXX There's currently no way to access the socket ID set for // whatever the queue is currently working. Required to find // some way to do this, possibly by having a "reverse pointer". @@ -513,6 +517,7 @@ class CRcvQueue int getIPversion() { return m_iIPversion; } + void stop(); private: static void* worker(void* param); sync::CThread m_WorkerThread; @@ -599,6 +604,19 @@ struct CMultiplexer { } + ~CMultiplexer() + { + if (m_pRcvQueue != NULL) + delete m_pRcvQueue; + if (m_pSndQueue != NULL) + delete m_pSndQueue; + if (m_pTimer != NULL) + delete m_pTimer; + close(); + } + void resetAtFork(); + void close(); + void stop(); void destroy(); }; diff --git a/srtcore/sync.h b/srtcore/sync.h index 100a11eedb..72da5869be 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -429,11 +429,11 @@ class Condition public: Condition(); ~Condition(); - public: /// These functions do not align with C++11 version. They are here hopefully as a temporal solution /// to avoud issues with static initialization of CV on windows. void init(); + void reset(); void destroy(); public: @@ -486,6 +486,7 @@ class Condition }; inline void setupCond(Condition& cv, const char*) { cv.init(); } +inline void resetCond(Condition& cv) { cv.reset(); } inline void releaseCond(Condition& cv) { cv.destroy(); } /////////////////////////////////////////////////////////////////////////////// @@ -1002,6 +1003,7 @@ class CThread private: pthread_t m_thread; + pid_t m_pid; }; template @@ -1033,6 +1035,8 @@ namespace this_thread #endif +inline void resetThread(CThread* th) { (void)new (th) CThread; } + /// StartThread function should be used to do CThread assignments: /// @code /// CThread a(); diff --git a/srtcore/sync_cxx11.cpp b/srtcore/sync_cxx11.cpp index bdfbf9a4e2..c376b0adfa 100644 --- a/srtcore/sync_cxx11.cpp +++ b/srtcore/sync_cxx11.cpp @@ -58,6 +58,24 @@ srt::sync::Condition::~Condition() {} void srt::sync::Condition::init() {} +void srt::sync::Condition::reset() +{ + // SRT attempts to safely handle `fork()` in multithreaded environments, + // even though using `fork()` in such contexts is strongly discouraged. + // This is because `fork()` only duplicates the calling thread, leaving + // synchronization primitives (like condition variables) in an + // undefined or inconsistent state in the child process. + // + // To mitigate this, SRT forcefully reinitializes these synchronization + // primitives post-fork. In POSIX, this is done by overwriting the object + // with its default-initialized state. In C++11, we achieve the same effect + // using *placement new* to reconstruct the object in place. This ensures + // the condition variable is returned to a fresh, "neutral" state, + // as if it was just created. + + new (&m_cv) std::condition_variable; +} + void srt::sync::Condition::destroy() {} void srt::sync::Condition::wait(UniqueLock& lock) diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp index 1649130972..09def5c334 100644 --- a/srtcore/sync_posix.cpp +++ b/srtcore/sync_posix.cpp @@ -309,6 +309,14 @@ void Condition::init() throw std::runtime_error("pthread_cond_init monotonic failed"); } +void Condition::reset() +{ + + pthread_cond_t temp = PTHREAD_COND_INITIALIZER; + memcpy(&m_cv, (void *) &temp, sizeof(m_cv)); + init(); +} + void Condition::destroy() { pthread_cond_destroy(&m_cv); @@ -370,6 +378,7 @@ void Condition::notify_all() srt::sync::CThread::CThread() { m_thread = pthread_t(); + m_pid = getpid(); } srt::sync::CThread::CThread(void *(*start_routine) (void *), void *arg) @@ -402,9 +411,9 @@ srt::sync::CThread& srt::sync::CThread::operator=(CThread& other) join(); #endif } - // Move thread handler from other m_thread = other.m_thread; + m_pid = other.m_pid; other.m_thread = pthread_t(); return *this; } @@ -419,24 +428,27 @@ void srt::sync::CThread::create_thread(void *(*start_routine) (void *), void *ar bool srt::sync::CThread::joinable() const { - return !pthread_equal(m_thread, pthread_t()); + return m_pid == getpid() && !pthread_equal(m_thread, pthread_t()); } void srt::sync::CThread::join() { - void *retval; - const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval); - if (ret != 0) - { - LOGC(inlog.Error, log << "pthread_join failed with " << ret); - } + if (joinable()) + { + void *retval; + const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval); + if (ret != 0) + { + LOGC(inlog.Error, log << "pthread_join failed with " << ret << " (" << m_thread << ")"); + } #ifdef HEAVY_LOGGING - else - { - HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED"); - } + else + { + HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED"); + } #endif - // After joining, joinable should be false + // After joining, joinable should be false + } m_thread = pthread_t(); return; } @@ -449,6 +461,7 @@ void srt::sync::CThread::create(void *(*start_routine) (void *), void *arg) LOGC(inlog.Error, log << "pthread_create failed with " << st); throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0); } + m_pid = getpid(); } From 9b97cfa235c1778262325e84d080aad8ac34de89 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 1 Sep 2025 14:28:01 +0200 Subject: [PATCH 44/92] [core] Fixed reentrancy problem of srt_strerror (#3214) * [core] Fixed reentrancy problem of srt_strerror * [docs] Updated documentation for srt_strerror --------- Co-authored-by: Mikolaj Malecki --- docs/API/API-functions.md | 12 ++++++------ srtcore/srt_c_api.cpp | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 2fdb8a6610..928fd6aaa3 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -2841,13 +2841,13 @@ associated with the last error. The system error is: const char* srt_strerror(int code, int errnoval); ``` -Returns a string message that represents a given SRT error code and possibly the -`errno` value, if not 0. +Returns a string message that represents a given SRT error code. -**NOTE:** *This function isn't thread safe. It uses a static variable to hold the -error description. There's no problem with using it in a multithreaded environment, -as long as only one thread in the whole application calls this function at the -moment* +**NOTE:** *The `errnoval` parameter is ignored. This function's old version +was intended to get both the SRT error description and system error description, +but this requires resolution of the reentrancy problem and dynamic strings. +For getting the error description for a system error, you need to use the +`strerror` function or some of its reentrant version.* [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) diff --git a/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp index 031030cd74..cf82467c27 100644 --- a/srtcore/srt_c_api.cpp +++ b/srtcore/srt_c_api.cpp @@ -26,6 +26,14 @@ written by using namespace std; using namespace srt; +namespace srt +{ +// This is provided in strerror_defs.cpp, which doesn't have +// its header file. +// XXX Consider adding some static function to CUDTException. +const char* strerror_get_message(size_t major, size_t minor); +} + extern "C" { @@ -248,11 +256,9 @@ int srt_getlasterror(int* loc_errno) return CUDT::getlasterror().getErrorCode(); } -const char* srt_strerror(int code, int err) +const char* srt_strerror(int code, int /*err ignored*/) { - static srt::CUDTException e; - e = srt::CUDTException(CodeMajor(code/1000), CodeMinor(code%1000), err); - return(e.getErrorMessage()); + return strerror_get_message(CodeMajor(code/1000), CodeMinor(code%1000)); } From 52ceecdf5190885914f0f94d01be32441ccb1f4c Mon Sep 17 00:00:00 2001 From: Aleksey S P <35438102+SPAleksey@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:14:14 +0400 Subject: [PATCH 45/92] Fix SIGABRT when bonding option is too long (#3210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix SIGABRT when bonding option is too long * Move the UT to test_bonding.cpp --------- Co-authored-by: Clément Gérouville --- srtcore/socketconfig.cpp | 2 +- test/test_bonding.cpp | 9 +++++++++ test/test_utilities.cpp | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index a0c8a6a47b..1b94c9197a 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -1092,7 +1092,7 @@ bool SRT_SocketOptionObject::add(SRT_SOCKOPT optname, const void* optval, size_t // needed - and it's better to not risk that alighment rules // will make these calculations result in less space than needed. const size_t headersize = sizeof(SingleOption); - const size_t payload = std::min(sizeof(uint32_t), optlen); + const size_t payload = std::max(sizeof(uint32_t), optlen); unsigned char* mem = new unsigned char[headersize + payload]; SingleOption* option = reinterpret_cast(mem); option->option = optname; diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 6307fd21f0..982145faef 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -1594,3 +1594,12 @@ TEST(Bonding, BackupPrioritySelection) } +TEST(Bonding, ApiConfig) +{ + using namespace std; + SRT_SOCKOPT_CONFIG config; + + string example = "example_long_excessively"; + + EXPECT_EQ(srt_config_add(&config, SRTO_BINDTODEVICE, (void*)example.data(), example.size()), 0); +} diff --git a/test/test_utilities.cpp b/test/test_utilities.cpp index 28c14044ed..e8494e744b 100644 --- a/test/test_utilities.cpp +++ b/test/test_utilities.cpp @@ -272,3 +272,4 @@ TEST(ConfigString, Setting) EXPECT_EQ(s.size(), 0U); EXPECT_TRUE(s.empty()); } + From bab403744b4d02005b03dac12a79988bc4590038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20G=C3=A9rouville?= Date: Wed, 3 Sep 2025 09:43:59 +0200 Subject: [PATCH 46/92] [API] raise libSRT version number to 1.5.5 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2017139ede..a7e9795139 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ # cmake_minimum_required (VERSION 3.5 FATAL_ERROR) -set (SRT_VERSION 1.5.4) +set (SRT_VERSION 1.5.5) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(CheckSymbolExists) From 5c5f5b5f3ac52c1d453c98bb945b6a39309a32cb Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 15 Oct 2025 13:23:41 +0200 Subject: [PATCH 47/92] [MAINT] Add fixes for CI configuration based on latest dev (#3232) * [MAINT] Add fixes for CI configuration based on latest dev * Added fixes from another PR * Added lacking scripts and repos * Fixed error reported in CI. Fixed ABI entry --------- Co-authored-by: Mikolaj Malecki --- .github/workflows/abi.yml | 139 ++++++++++++++++++++++------ .github/workflows/android.yaml | 4 +- .github/workflows/codeql.yml | 4 +- .github/workflows/cxx03-ubuntu.yaml | 29 ++++++ .github/workflows/cxx11-macos.yaml | 16 ++-- .github/workflows/cxx11-ubuntu.yaml | 6 +- .github/workflows/cxx11-win.yaml | 8 +- .github/workflows/iOS.yaml | 10 +- .github/workflows/s390x-focal.yaml | 4 +- .gitmodules | 3 + .travis.yml | 116 +++++++++++++++-------- scripts/abi-check.tcl | 76 +++++++++++++++ scripts/build-windows.ps1 | 24 ++++- scripts/get-build-version.tcl | 47 ++++++++++ scripts/googletest-download.cmake | 4 +- srtcore/utilities.h | 17 ++-- submodules/abi-compliance-checker | 1 + 17 files changed, 402 insertions(+), 106 deletions(-) create mode 100644 .github/workflows/cxx03-ubuntu.yaml create mode 100644 .gitmodules create mode 100644 scripts/abi-check.tcl create mode 100755 scripts/get-build-version.tcl create mode 160000 submodules/abi-compliance-checker diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml index 51d22a5164..4d4d6012c6 100644 --- a/.github/workflows/abi.yml +++ b/.github/workflows/abi.yml @@ -2,60 +2,141 @@ 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-latest + outputs: + base_version: ${{ steps.build.outputs.SRT_BASE }} + tag_version: ${{ steps.build.outputs.SRT_TAG_VERSION }} steps: - uses: actions/checkout@v3 with: - path: pull_request - - name: configure + path: gitview_pr + - id: configure + name: Configure (cmake) run: | - cd pull_request + cd gitview_pr mkdir _build && cd _build cmake -DCMAKE_BUILD_TYPE=Debug ../ - - name: build + - id: build + name: Build and dump 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) + echo "SRT_TAG_VERSION=$SRT_TAG_VERSION" >> "$GITHUB_OUTPUT" + SRT_TAG=${SRT_TAG_VERSION}dev-$(git rev-parse --short HEAD) + echo "TAGGING PR BUILD: $SRT_TAG" + abi-dumper libsrt.so -o libsrt-pr.dump -public-headers installdir/usr/local/include/srt/ -lver $SRT_TAG + ls -ld libsrt-pr.dump + sha256sum libsrt-pr.dump + 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 (not emitting SRT_BASE)" + #echo "SRT_BASE=''" >> "$GITHUB_OUTPUT" + else + echo "WILL CHECK ABI changes $SRT_BASE - $SRT_TAG_VERSION" + echo "SRT_BASE=$SRT_BASE" >> "$GITHUB_OUTPUT" + fi + - id: upload_pr_dump + uses: actions/upload-artifact@v4 + with: + name: abidump_pr + path: gitview_pr/_build/libsrt-pr.dump + + build_base: + name: Build base version + runs-on: ubuntu-latest + needs: build_pr + if: ${{ needs.build_pr.outputs.base_version != '' }} + + env: + SRT_BASE: ${{ needs.build_pr.outputs.base_version }} + SRT_TAG_VERSION: ${{ needs.build_pr.outputs.tag_version }} + steps: - uses: actions/checkout@v3 with: - path: tag + path: gitview_base ref: ${{ env.SRT_BASE }} - - name: configure_tag + - id: configure_tag + name: Configure (cmake) run: | - echo $SRT_TAG_VERSION - cd tag + echo "TAG:$SRT_TAG_VERSION BASE:$SRT_BASE" + + #This is currently a paranoid check - the if should do the job + if [[ -z $SRT_BASE ]]; then + echo "NO BASE DEFINED. NOT BUILDING" + exit 1 + fi + cd gitview_base mkdir _build && cd _build cmake -DCMAKE_BUILD_TYPE=Debug ../ - - name: build_tag + - id: build_tag + name: Build and dump + if: ${{ success() }} run: | - cd tag + sudo apt install -y abi-dumper + sudo apt install -y tcl + 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 + echo "TAGGING BASE BUILD: $SRT_BASE" + abi-dumper libsrt.so -o libsrt-base.dump -public-headers installdir/usr/local/include/srt/ -lver $SRT_BASE + ls -ld libsrt-base.dump + sha256sum libsrt-base.dump + - id: upload_base_dump + uses: actions/upload-artifact@v4 + with: + name: abidump_base + path: gitview_base/_build/libsrt-base.dump + + check_abi: + name: Compare ABI + runs-on: ubuntu-latest + needs: [build_pr, build_base] + env: + SRT_BASE: ${{ needs.build_pr.outputs.base_version }} + + steps: + - name: Download base dump + uses: actions/download-artifact@v4 + with: + name: abidump_base + path: . + - name: Download pr dump + uses: actions/download-artifact@v4 + with: + name: abidump_pr + path: . - 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 ../.. + echo "FILESYSTEM state before running abi-check at $PWD" + ls -l + sha256sum libsrt-base.dump + sha256sum libsrt-pr.dump + abi-compliance-checker -l libsrt -old libsrt-base.dump -new 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 5e11c75df9..c94c0bb3c4 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 4a337b6cad..f7f4172598 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/cxx03-ubuntu.yaml b/.github/workflows/cxx03-ubuntu.yaml new file mode 100644 index 0000000000..ce27436aa1 --- /dev/null +++ b/.github/workflows/cxx03-ubuntu.yaml @@ -0,0 +1,29 @@ +name: C++03 (old compat) + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + types: [opened, synchronize, reopened] +jobs: + build: + name: ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: configure + run: | + mkdir _build && cd _build + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=OFF -DUSE_CXX_STD=c++03 -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: build + # That below is likely SonarQube remains, which was removed earlier. + #run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . + run: cd _build && cmake --build . + - name: test + run: | + cd _build && ctest --extra-verbose + - name: codecov + run: | + source ./scripts/collect-gcov.sh + bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/cxx11-macos.yaml b/.github/workflows/cxx11-macos.yaml index 4732c2abbd..b03b4f75ff 100644 --- a/.github/workflows/cxx11-macos.yaml +++ b/.github/workflows/cxx11-macos.yaml @@ -1,10 +1,10 @@ -name: cxx11 +name: C++11 on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] jobs: build: @@ -14,13 +14,17 @@ jobs: steps: - name: GoogleTest run: | - curl -o googletest.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/23e7fb4dc0cc73facc3772815741e1deb87d6406/Formula/googletest.rb - brew install -s googletest.rb + brew tap-new Haivision/gt-local + brew extract --version=1.12.1 --force googletest Haivision/gt-local + brew install googletest@1.12.1 + # NOTE: 1.12.1 is the last version that requires C++11; might need update later + # curl -o googletest.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/23e7fb4dc0cc73facc3772815741e1deb87d6406/Formula/googletest.rb + # brew install -s googletest.rb - uses: actions/checkout@v3 - name: configure run: | mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DUSE_CXX_STD=14 + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DUSE_CXX_STD=17 - name: build run: cd _build && cmake --build ./ - name: test diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml index 2f362d84fa..eda7ecca3e 100644 --- a/.github/workflows/cxx11-ubuntu.yaml +++ b/.github/workflows/cxx11-ubuntu.yaml @@ -1,10 +1,10 @@ -name: cxx11 +name: C++11 on: push: - branches: [ master ] + branches: [ "master", "dev" ] pull_request: - branches: [ master ] + branches: [ "master", "dev" ] types: [opened, synchronize, reopened] jobs: build: diff --git a/.github/workflows/cxx11-win.yaml b/.github/workflows/cxx11-win.yaml index f1554053d8..2947809ab7 100644 --- a/.github/workflows/cxx11-win.yaml +++ b/.github/workflows/cxx11-win.yaml @@ -1,10 +1,10 @@ -name: cxx11 +name: C++11 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 0fb11542b5..9ae9e9124b 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: @@ -17,9 +17,13 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install dependency packages + run: | + brew install make llvm - name: configure run: | mkdir _build && cd _build - cmake ../ -DENABLE_ENCRYPTION=OFF -DENABLE_STDCXX_SYNC=${{matrix.cxxstdsync}} -DENABLE_UNITTESTS=OFF -DENABLE_BONDING=ON --toolchain scripts/iOS.cmake + export PATH="/opt/homebrew/opt/llvm/bin:$PATH" CC=clang CXX=clang++ + cmake .. -DCMAKE_MAKE_PROGRAM=gmake -DENABLE_ENCRYPTION=OFF -DENABLE_STDCXX_SYNC=${{matrix.cxxstdsync}} -DENABLE_MONOTONIC_CLOCK=OFF -DENABLE_UNITTESTS=OFF -DUSE_CXX_STD=c++11 -DENABLE_BONDING=ON --toolchain scripts/iOS.cmake - name: build run: cd _build && cmake --build ./ diff --git a/.github/workflows/s390x-focal.yaml b/.github/workflows/s390x-focal.yaml index f1b6c7508c..d91991456b 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 0000000000..0c81e9233a --- /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/.travis.yml b/.travis.yml index 0402fb51ec..5c54148ea3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,10 @@ +# TRAVIS Configuration file +# +# XXX MIND SYNTAX TRICKS: +# - Keep column consistency +# - TAB characters NOT ALLOWED anywhere +# - COLON characters (in freestanding strings) not allowed in the script + language: cpp dist: xenial @@ -16,27 +23,27 @@ addons: packages: - openssl -matrix: +jobs: include: - os: linux env: - - BUILD_TYPE=Debug - - BUILD_OPTS='-DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' + - BUILD_TYPE=Debug CFG="monotonic openssl werror" + - CMAKE_OPTS='-DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' - env: - - BUILD_TYPE=Debug - - BUILD_OPTS='-DENABLE_LOGGING=OFF -DUSE_ENCLIB=mbedtls -DENABLE_MONOTONIC_CLOCK=ON -DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' + - BUILD_TYPE=Debug CFG="nologging mbedtls monotonic werror" + - CMAKE_OPTS='-DENABLE_LOGGING=OFF -DUSE_ENCLIB=mbedtls -DENABLE_MONOTONIC_CLOCK=ON -DENABLE_BONDING=ON -DCMAKE_CXX_FLAGS="-Werror"' - os: linux - env: BUILD_TYPE=Release - - os: osx - osx_image: xcode11.1 - env: - - BUILD_TYPE=Debug - - BUILD_OPTS='-DCMAKE_CXX_FLAGS="-Werror"' - - os: osx - osx_image: xcode11.1 - env: - - BUILD_TYPE=Release - - BUILD_OPTS='-DCMAKE_CXX_FLAGS="-Werror"' + env: BUILD_TYPE=Release CFG=default +# - os: osx +# osx_image: xcode11.1 +# env: +# - BUILD_TYPE=Debug CFG=werror +# - CMAKE_OPTS='-DCMAKE_CXX_FLAGS="-Werror"' +# - os: osx +# osx_image: xcode11.1 +# env: +# - BUILD_TYPE=Release CFG=werror +# - CMAKE_OPTS='-DCMAKE_CXX_FLAGS="-Werror"' - os: linux compiler: x86_64-w64-mingw32-g++ addons: @@ -53,37 +60,70 @@ matrix: - ./Configure --cross-compile-prefix=x86_64-w64-mingw32- mingw64 - make - cd .. - env: BUILD_TYPE=Release + env: BUILD_TYPE=Release CFG=no-UT # Power jobs + # Forcing Focal distro because Xenial + # has somehow outdated CMake - os: linux arch: ppc64le + dist: focal env: - - BUILD_TYPE=Debug + - ARCH=PowerPC BUILD_TYPE=Debug - arch: ppc64le + dist: focal env: - - BUILD_TYPE=Release - - BUILD_OPTS='-DENABLE_MONOTONIC_CLOCK=ON' + - ARCH=PowerPC BUILD_TYPE=Release CFG=monotonic + - CMAKE_OPTS='-DENABLE_MONOTONIC_CLOCK=ON' script: + - CMAKE_VERSION=$(cmake --version | head -1 | awk '{print $3}') + - echo CMAKE version $CMAKE_VERSION + - CMAKE_VERSION_OK=$(echo "puts [package vcompare $CMAKE_VERSION 3.10]" | tclsh); + - if [ "$CMAKE_VERSION_OK" == "-1" ]; then + echo "ERROR - cmake version too old, >=3.10 required"; + exit 1; + fi; + - export REQUIRE_UNITTESTS=1 + - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then + CMAKE_OPTS+=" -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc"; + CMAKE_OPTS+=" -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++"; + CMAKE_OPTS+=" -DENABLE_STDCXX_SYNC=OFF -DENABLE_LOCALIF_WIN32=OFF -DENABLE_UNITTESTS=OFF -DUSE_OPENSSL_PC=OFF"; + CMAKE_OPTS+=" -DOPENSSL_ROOT_DIR=$PWD/openssl"; + CMAKE_OPTS+=" -DOPENSSL_CRYPTO_LIBRARY=$PWD/openssl/libcrypto-1_1-x64.dll"; + CMAKE_OPTS+=" -DCMAKE_SYSTEM_NAME=Windows"; + REQUIRE_UNITTESTS=0; + fi; + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig"; + fi; + if (( $REQUIRE_UNITTESTS )); then + CMAKE_OPTS+=" -DENABLE_UNITTESTS=ON"; + fi; - echo COMPILER $TRAVIS_COMPILER - echo SYSTEM $TRAVIS_OS_NAME - echo BUILD_TYPE $BUILD_TYPE - - echo BUILD_OPTS $BUILD_OPTS - - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then - export CC="x86_64-w64-mingw32-gcc"; - export CXX="x86_64-w64-mingw32-g++"; - cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="OFF" -DUSE_OPENSSL_PC="OFF" -DOPENSSL_ROOT_DIR="$PWD/openssl" -DCMAKE_SYSTEM_NAME="Windows"; - elif [ "$TRAVIS_OS_NAME" == "linux" ]; then - cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="ON"; - elif [ "$TRAVIS_OS_NAME" == "osx" ]; then - export PKG_CONFIG_PATH=$(brew --prefix openssl)"/lib/pkgconfig"; - cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE $BUILD_OPTS -DENABLE_UNITTESTS="ON"; - fi - - make -j$(nproc); - - if [ "$TRAVIS_COMPILER" != "x86_64-w64-mingw32-g++" ]; then + - echo CMAKE_OPTS $CMAKE_OPTS + - export SUCCESS=0 + - cmake . --debug-output -DCMAKE_MESSAGE_LOG_LEVEL=VERBOSE -DCMAKE_BUILD_TYPE=$BUILD_TYPE $CMAKE_OPTS 2>&1 || SUCCESS=$?; + - if (($SUCCESS == 0)); then + echo "Configure OK"; + else + echo "-- OUTPUT --"; + cat CMakeFiles/CMakeOutput.log || echo "NO OUTPUT"; + echo "-- ERRORS --"; + cat CMakeFiles/CMakeError.log || echo "NO LOGS"; + exit 1; + fi; + - make VERBOSE=1 -j$(nproc); + - if (( $REQUIRE_UNITTESTS )); then ulimit -c unlimited; - ./test-srt -disable-ipv6; - SUCCESS=$?; - if [ -f core ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; - test $SUCCESS == 0; - fi + if [ ! -f ./test-srt ]; then + echo "ERROR - UT application not found"; + false; + else + ./test-srt -disable-ipv6; + SUCCESS=$?; + if [ -f core ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + test $SUCCESS == 0; + fi; + fi; diff --git a/scripts/abi-check.tcl b/scripts/abi-check.tcl new file mode 100644 index 0000000000..7f9c70e790 --- /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/build-windows.ps1 b/scripts/build-windows.ps1 index c4c7b5740b..a65247ccdd 100644 --- a/scripts/build-windows.ps1 +++ b/scripts/build-windows.ps1 @@ -49,11 +49,25 @@ if ( $Env:APPVEYOR ) { $CONFIGURATION = $Env:CONFIGURATION - #appveyor has many openssl installations - place the latest one in the default location unless VS2013 - Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null - Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null - Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null + # See https://www.appveyor.com/docs/windows-images-software/#visual-studio-2022 + # According to AppVeyor environment definition: + # + # 2013 - 2017: C:\OpenSSL-Win*: v1.0.2p, C:\OpenSSL-v111-Win*: v1.1.1 (v1.1.1d for 2015+) + # 2019 - 2022: C:\OpenSSL-Win*: v1.1.1w and C:\OpenSSL-v3* with version 3+ + # SO: + # For 2013 - 2017: Delete C:\OpenSSL-Win* and replace it with contents of C:\OpenSSL-v111-Win* + # For 2019 - 2022: Do nothing. + # Note: No guarantee that C:\OpenSSL-Win* directories will always contain version v1.1.1+ in + # any future versions for AppVeyor Windows environment, but then probably the SRT project + # would have to be adjusted to this version anyway. + + if ($VS_VERSION -lt 2019) { + #appveyor has many openssl installations - place the latest one in the default location unless VS2013 + Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null + Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null + } } # persist VS_VERSION so it can be used in an artifact name later diff --git a/scripts/get-build-version.tcl b/scripts/get-build-version.tcl new file mode 100755 index 0000000000..fafda061a1 --- /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 36554f316f..f1f6d4902d 100644 --- a/scripts/googletest-download.cmake +++ b/scripts/googletest-download.cmake @@ -1,7 +1,7 @@ # code copied from https://crascit.com/2015/07/25/cmake-gtest/ -cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) -project(googletest-download NONE) +project(GOOGLETEST_DOWNLOAD) include(ExternalProject) diff --git a/srtcore/utilities.h b/srtcore/utilities.h index ca8c365a3c..8781b80e75 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -828,17 +828,14 @@ struct CallbackHolder // Test if the pointer is a pointer to function. Don't let // other type of pointers here. #if HAVE_CXX11 + // NOTE: No poor-man's replacement can be done for C++03 because it's + // not possible to fake calling a function without calling it and no + // other operation can be done without extensive transformations on + // the Signature type, still in C++03 possible only on functions up to + // 2 arguments (callbacks in SRT usually have more). static_assert(std::is_function::value, "CallbackHolder is for functions only!"); -#else - // This is a poor-man's replacement, which should in most compilers - // generate a warning, if `Signature` resolves to a value type. - // This would make an illegal pointer cast from a value to a function type. - // Casting function-to-function, however, should not. Unfortunately - // newer compilers disallow that, too (when a signature differs), but - // then they should better use the C++11 way, much more reliable and safer. - void* (*testfn)(void*) = (void*(*)(void*))f; - (void)(testfn); #endif + opaque = o; fn = f; } @@ -1111,7 +1108,7 @@ struct MapProxy { typename std::map::const_iterator p = find(); if (p == mp.end()) - return ""; + return ValueType(); return p->second; } diff --git a/submodules/abi-compliance-checker b/submodules/abi-compliance-checker new file mode 160000 index 0000000000..7c175c45a8 --- /dev/null +++ b/submodules/abi-compliance-checker @@ -0,0 +1 @@ +Subproject commit 7c175c45a8ba9ac41b8e47d8ebbab557b623b18e From 84676b50be5e14b043b781257ebd9faa14e7a540 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Tue, 21 Oct 2025 16:59:14 +0200 Subject: [PATCH 48/92] [build] Avoid using list(PREPEND). (#3230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Gérouville --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7e9795139..6e19caa2c5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -966,7 +966,7 @@ else () message(STATUS "Pthread library: ${PTHREAD_LIBRARY}") endif() -list(PREPEND CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY}") +set(CMAKE_REQUIRED_LIBRARIES "${PTHREAD_LIBRARY};${CMAKE_REQUIRED_LIBRARIES}") unset(CMAKE_REQUIRED_QUIET) check_symbol_exists(pthread_atfork "pthread.h" HAVE_PTHREAD_ATFORK) From 547688cce7db6949dd8236507331cbe2a14cd06b Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 22 Oct 2025 08:55:08 +0200 Subject: [PATCH 49/92] [core] Restored old cookie contest. Added development support for enforcing cookie values. (#3227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial testing version of the cookie contest fix * Setting cookie contest 1.5.4 as default * [core] Restored old cookie contest. Added ignored wrong peer response on cookie collision * Fixed security report * Removed the (unnecessary now) fix for the future version compatibility * Update test/test_common.cpp - fixed a typo in a comment * Fixed TestFEC tests to be resistant of a listener-readiness bug (fixed in dev) * Try to fix the first issue * Removed remains after the screw-in cookie procedures --------- Co-authored-by: Mikolaj Malecki Co-authored-by: Clément Gérouville --- srtcore/core.cpp | 120 +++++++++++++++++------------------ srtcore/core.h | 16 +++++ test/test_common.cpp | 45 +++++++++++++ test/test_fec_rebuilding.cpp | 65 +++++++++---------- 4 files changed, 150 insertions(+), 96 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 0acef17bbe..dd189f73c1 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -4102,88 +4102,69 @@ void srt::CUDT::sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket& m_pSndQueue->sendto(serv_addr, r_rsppkt, m_SourceAddr); } -void srt::CUDT::cookieContest() +enum HandshakeSide srt::CUDT::compareCookies(int32_t req, int32_t res) { - if (m_SrtHsSide != HSD_DRAW) - return; - - // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. - // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. - if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0) + if (req < res) { - LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie - << " - ERROR: zero not allowed!"); - - // Note that it's virtually impossible that Agent's cookie is not ready, this - // shall be considered IPE. - // Not all cookies are ready, don't start the contest. - return; + LOGC(gglog.Note, log << hex << setfill('0') << uppercase + << "REQ: " << setw(8) << req + << " RES: " << setw(8) << res << " - result: RESPONDER"); + return HSD_RESPONDER; } + else if (req > res) + { + LOGC(gglog.Note, log << hex << setfill('0') << uppercase + << "REQ: " << setw(8) << req + << " RES: " << setw(8) << res << " - result: INITIATOR"); + return HSD_INITIATOR; + } + return HSD_DRAW; +} - // INITIATOR/RESPONDER role is resolved by COOKIE CONTEST. - // - // The cookie contest must be repeated every time because it - // may change the state at some point. - // - // In SRT v1.4.3 and prior the below subtraction was performed in 32-bit arithmetic. - // The result of subtraction can overflow 32-bits. - // Example - // m_ConnReq.m_iCookie = -1480577720; - // m_ConnRes.m_iCookie = 811599203; - // int64_t llBetterCookie = -1480577720 - 811599203 = -2292176923 (FFFF FFFF 7760 27E5); - // int32_t iBetterCookie = 2002790373 (7760 27E5); - // - // Now 64-bit arithmetic is used to calculate the actual result of subtraction. - // - // In SRT v1.5.4 there was a version, that checked: - // - if LOWER 32-bits are 0, this is a draw - // - if bit 31 is set (AND with 0x80000000), the result is considered negative. - // This was erroneous because for 1 and 0x80000001 cookie values the - // result was always the same, regardless of the order: - // - // 0x0000000000000001 - 0xFFFFFFFF80000001 = 0x0000000080000000 - // 0xFFFFFFFF80000001 - 0x0000000080000000 = 0xFFFFFFFF80000000 - // - // >> if (contest & 0x80000000) -> results in true in both comparisons. - // - // This version takes the bare result of the 64-bit arithmetics. - const int64_t xreq = int64_t(m_ConnReq.m_iCookie); - const int64_t xres = int64_t(m_ConnRes.m_iCookie); +enum HandshakeSide srt::CUDT::backwardCompatibleCookieContest(int32_t req, int32_t res) +{ + const int64_t xreq = int64_t(req); + const int64_t xres = int64_t(res); const int64_t contest = xreq - xres; - // NOTE: for 1.6.0 use: - // fmtc hex64 = fmtc().uhex().fillzero().width(16); then - // << fmt(xreq, hex64) - LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie - << " peer=" << m_ConnRes.m_iCookie + LOGC(cnlog.Debug, log << "cookieContest: agent=" << req + << " peer=" << res << hex << uppercase << setfill('0') // PERSISTENT flags << " X64: "<< setw(16) << xreq << " vs. " << setw(16) << xres << " DIFF: " << setw(16) << contest); - if (contest == 0) + if ((contest & 0xFFFFFFFF) == 0) { - // DRAW! The only way to continue would be to force the - // cookies to be regenerated and to start over. But it's - // not worth a shot - this is an extremely rare case. - // This can simply do reject so that it can be started again. + return HSD_DRAW; + } + if (contest & 0x80000000) + { + const int64_t revert = xres - xreq; + if (revert & 0x80000000 && req > res) + return HSD_INITIATOR; + return HSD_RESPONDER; + } - // Pretend then that the cookie contest wasn't done so that - // it's done again. Cookies are baked every time anew, however - // the successful initial contest remains valid no matter how - // cookies will change. + return HSD_INITIATOR; +} - m_SrtHsSide = HSD_DRAW; +void srt::CUDT::cookieContest() +{ + if (m_SrtHsSide != HSD_DRAW) return; - } - if (contest < 0) + // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer. + // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request. + if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0) { - m_SrtHsSide = HSD_RESPONDER; + LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie + << " - ERROR: zero not allowed!"); + return; } + m_SrtHsSide = backwardCompatibleCookieContest(m_ConnReq.m_iCookie, m_ConnRes.m_iCookie); - m_SrtHsSide = HSD_INITIATOR; } // This function should complete the data for KMX needed for an out-of-band @@ -12153,3 +12134,18 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr if (m_config.bDriftTracer) m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); } + +namespace srt { +HandshakeSide getHandshakeSide(SRTSOCKET u) +{ + return CUDT::handshakeSide(u); +} + +HandshakeSide CUDT::handshakeSide(SRTSOCKET u) +{ + CUDTSocket *s = uglobal().locateSocket(u); + return s ? s->core().handshakeSide() : HSD_DRAW; +} +} + + diff --git a/srtcore/core.h b/srtcore/core.h index a07d172771..6bf75466ed 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -294,6 +294,17 @@ class CUDT return m_ConnRes.m_iVersion; } + int32_t handshakeCookie() + { + return m_ConnReq.m_iCookie; + } + + static HandshakeSide handshakeSide(SRTSOCKET u); + HandshakeSide handshakeSide() + { + return m_SrtHsSide; + } + std::string CONID() const { #if ENABLE_LOGGING @@ -1010,6 +1021,8 @@ class CUDT public: static int installAcceptHook(SRTSOCKET lsn, srt_listen_callback_fn* hook, void* opaq); static int installConnectHook(SRTSOCKET lsn, srt_connect_callback_fn* hook, void* opaq); + static enum HandshakeSide compareCookies(int32_t req, int32_t res); + static enum HandshakeSide backwardCompatibleCookieContest(int32_t req, int32_t res); private: void installAcceptHook(srt_listen_callback_fn* hook, void* opaq) { @@ -1248,6 +1261,9 @@ class CUDT void removeEPollID(const int eid); }; +// DEBUG SUPPORT +HandshakeSide getHandshakeSide(SRTSOCKET s); + } // namespace srt #endif diff --git a/test/test_common.cpp b/test/test_common.cpp index 1a8cca061a..b4e150f1be 100644 --- a/test/test_common.cpp +++ b/test/test_common.cpp @@ -5,6 +5,7 @@ #include "test_env.h" #include "utilities.h" #include "common.h" +#include "core.h" using namespace srt; @@ -71,3 +72,47 @@ TEST(CIPAddress, IPv4_in_IPv6_pton) test_cipaddress_pton(peer_ip, AF_INET6, ip); } + +void testCookieContest(int32_t agent_cookie, int32_t peer_cookie) +{ + using namespace srt; + using namespace std; + + cout << "TEST: Cookies: agent=" << hex << agent_cookie + << " peer=" << peer_cookie << endl << dec; + HandshakeSide agent_side = CUDT::backwardCompatibleCookieContest(agent_cookie, peer_cookie); + EXPECT_EQ(agent_side, HSD_INITIATOR); + HandshakeSide peer_side = CUDT::backwardCompatibleCookieContest(peer_cookie, agent_cookie); + EXPECT_EQ(peer_side, HSD_RESPONDER); +} + +TEST(Common, CookieContest) +{ + srt::TestInit srtinit; + using namespace std; + + srt_setloglevel(LOG_NOTICE); + + // In this function you should pass cookies always in the order: INITIATOR, RESPONDER. + cout << "TEST 1: two easy comparable values\n"; + testCookieContest(100, 50); + testCookieContest(-1, -1000); + testCookieContest(10055, -10000); + testCookieContest(-1480577720, 811599203); + testCookieContest( -2147483648, 2147483647); + testCookieContest(0x00000001, 0x80000001); + + /* XXX Blocked temporarily because in 1.5.5 they would fail, + and it's decided to not provide the fix, which is too + dangerous. The real fix will be provided in 1.6.0. + + // Values from PR 1517 + cout << "TEST 2: Values from PR 1517\n"; + testCookieContest(811599203, -1480577720); + testCookieContest(2147483647, -2147483648); + + cout << "TEST 3: wrong post-fix\n"; + // NOTE: 0x80000001 is a negative number in hex + testCookieContest(0x00000001, 0x80000001); + */ +} diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index 3a7cb0791e..b39345c401 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "gtest/gtest.h" #include "test_env.h" @@ -92,6 +93,21 @@ class TestFECRebuilding: public srt::Test } }; +static std::future spawn_connect(SRTSOCKET s, sockaddr_in& sa, int timeout_ms = 1000) +{ + std::cout << "[M] SPAWNING srt_connect()\n"; + return std::async(std::launch::async, [s, &sa, timeout_ms]() + { + // Add a delay for starting connection to give a chance + // for the main thread to establish an EID with the listener + // BEFORE the handshake packet is received, otherwise the + // epoll will miss the signal (a bug fixed in 1.6.0). + std::this_thread::sleep_for(chrono::milliseconds(timeout_ms)); + std::cout << "[T] RUNNING srt_connect()\n"; + return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); + }); +} + namespace srt { class TestMockCUDT { @@ -302,9 +318,7 @@ TEST(TestFEC, Connection) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa, 1); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); @@ -312,7 +326,7 @@ TEST(TestFEC, Connection) // Given 2s timeout for accepting as it has occasionally happened with Travis // that 1s might not be enough. SRTSOCKET la[] = { l }; - SRTSOCKET a = srt_accept_bond(la, 1, 2000); + SRTSOCKET a = srt_accept_bond(la, 1, 5000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -362,15 +376,13 @@ TEST(TestFEC, ConnectionReorder) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); SRTSOCKET la[] = { l }; - SRTSOCKET a = srt_accept_bond(la, 1, 2000); + SRTSOCKET a = srt_accept_bond(la, 1, 5000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -420,15 +432,12 @@ TEST(TestFEC, ConnectionFull1) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); - + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); SRTSOCKET la[] = { l }; - SRTSOCKET a = srt_accept_bond(la, 1, 2000); + SRTSOCKET a = srt_accept_bond(la, 1, 5000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -478,15 +487,13 @@ TEST(TestFEC, ConnectionFull2) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); SRTSOCKET la[] = { l }; - SRTSOCKET a = srt_accept_bond(la, 1, 2000); + SRTSOCKET a = srt_accept_bond(la, 1, 5000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -536,15 +543,13 @@ TEST(TestFEC, ConnectionMess) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); SRTSOCKET la[] = { l }; - SRTSOCKET a = srt_accept_bond(la, 1, 2000); + SRTSOCKET a = srt_accept_bond(la, 1, 5000); ASSERT_NE(a, SRT_ERROR) << srt_getlasterror_str(); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -592,15 +597,13 @@ TEST(TestFEC, ConnectionForced) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); SRTSOCKET la[] = { l }; - SRTSOCKET a = srt_accept_bond(la, 1, 2000); + SRTSOCKET a = srt_accept_bond(la, 1, 5000); ASSERT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); @@ -645,9 +648,7 @@ TEST(TestFEC, RejectionConflict) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); @@ -689,9 +690,7 @@ TEST(TestFEC, RejectionIncompleteEmpty) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); @@ -737,9 +736,7 @@ TEST(TestFEC, RejectionIncomplete) srt_listen(l, 1); - auto connect_res = std::async(std::launch::async, [&s, &sa]() { - return srt_connect(s, (sockaddr*)& sa, sizeof(sa)); - }); + auto connect_res = spawn_connect(s, sa); // Make sure that the async call to srt_connect() is already kicked. std::this_thread::yield(); From a26a7021a501e8c4bc17c75f3a109cca529cf262 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 22 Oct 2025 10:47:34 +0200 Subject: [PATCH 50/92] [core] Reimplemented MAXREXMITBW controller basing on Token Bucket algorithm (#3220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added more logs around rexmit bandwidth * [API] raise libSRT version number to 1.5.5 * Maintenance changes * Rewrite the Bandwidth estimation system. * Merged changes from Clement's implementation * Introduce CShaper to control the retransmission BW. * Fixed with average packet size measurement * Added self-adjusting of burst period * Fixed wrong formulas and added some logs * Removed old changes for CSndRateEstimator. Blocked the use of CSndRateEstimator. * Applied most direct review fixes * Refactoring: split packLostData into smaller parts * Some cosmetic fixes * Cosmetics * Removed Tie function that was using non-C++03-compatible assignment * Stretched connection timeout for accept in FEC tests * Trying to track the problem in FEC tests on Mac * Adjusted TestFEC tests to a bug in listener missing signal --------- Co-authored-by: Mikolaj Malecki Co-authored-by: Clément Gérouville --- srtcore/buffer_tools.h | 127 ++++++++ srtcore/common.cpp | 16 +- srtcore/common.h | 1 + srtcore/core.cpp | 576 ++++++++++++++++++++++++++++++----- srtcore/core.h | 156 +++++++++- srtcore/group.cpp | 4 + srtcore/utilities.h | 16 +- test/test_fec_rebuilding.cpp | 4 + 8 files changed, 801 insertions(+), 99 deletions(-) diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h index 88479827df..1c90a56a1e 100644 --- a/srtcore/buffer_tools.h +++ b/srtcore/buffer_tools.h @@ -198,6 +198,133 @@ class CSndRateEstimator int m_iRateBps; //< Rate in Bytes/sec. }; +// Utility class for bandwidth limitation +class CShaper +{ +public: + static double SHAPER_RESOLUTION_US() { return 1000000.; } // micro seconds + static double BURSTPERIOD_DEFAULT() { return 100; } + + typedef sync::steady_clock::time_point time_point; + typedef sync::steady_clock::duration duration; + CShaper (double min_tokens_capacity): + m_BurstPeriod(sync::milliseconds_from(BURSTPERIOD_DEFAULT())), + m_rate_Bps(0), + m_tokens(0), + m_tokensCapacity(0), + m_minTokensCapacity(min_tokens_capacity) + { + + } + + // Token capacity should be settable later + void setMinimumTokenCapacity(double cap) + { + m_minTokensCapacity = cap; + updateLimits(); + } + +private: + duration m_BurstPeriod; + double m_rate_Bps; // current_bitrate in Bytes per second + double m_tokens; // in bytes + double m_tokensCapacity; // in bytes + double m_minTokensCapacity; + time_point m_UpdateTime; + void setTokenCapacity(double tokens) + { + m_tokensCapacity = std::max(0., tokens); + } + + void setTokens(double tokens) + { + // It is allowed to set the negative number of tokens, + // just not less than the negative maximum + m_tokens = Bounds(1 - m_tokensCapacity, tokens, m_tokensCapacity); + } + + static double periodToTokens(double rate_Bps, duration period) + { + double seconds = double(sync::count_microseconds(period)) / SHAPER_RESOLUTION_US(); + return rate_Bps * seconds; + } + + static duration tokensToPeriod(double rate_Bps, double tokens) + { + double seconds = tokens / rate_Bps; + return sync::microseconds_from(seconds * SHAPER_RESOLUTION_US()); + } + + void updateLimits() + { + double token_cap = periodToTokens(m_rate_Bps, m_BurstPeriod); + if (token_cap < m_minTokensCapacity) + { + // We have a too small value; recalculate the burst period to reach the minimum. + duration minperiod = tokensToPeriod(m_rate_Bps, m_minTokensCapacity); + token_cap = m_minTokensCapacity; + m_BurstPeriod = minperiod; + } + setTokenCapacity(token_cap); + } + +public: + void setBitrate(double bw_Bps) + { + // NOTE: comparison on double to check the PREVIOUSLY + // SET value (m_rate_Bps), not recalculated value. + if (bw_Bps != m_rate_Bps) + { + m_rate_Bps = bw_Bps; + updateLimits(); + } + } + + void setMinimumBurstPeriod(duration hp) + { + if (m_BurstPeriod < hp) + { + setBurstPeriod(hp); + } + } + + void setBurstPeriod(duration bp) + { + if (bp != m_BurstPeriod) + { + m_BurstPeriod = bp; + updateLimits(); + } + } + + // Note that tick() must be always called after setBitrate. + void tick(const time_point& now) + { + duration delta = now - m_UpdateTime; + m_UpdateTime = now; + + double update_tokens = periodToTokens(m_rate_Bps, delta); + setTokens(update_tokens + m_tokens); + } + + bool enoughTokens(double len) const { return len <= m_tokens; } + void consumeTokens(double len) { setTokens(m_tokens - len); } + + + // For debug purposes + int ntokens() const { return m_tokens; } + duration burstPeriod() const { return m_BurstPeriod; } + int maxTokens() const { return m_tokensCapacity; } + // TOKENS = BITRATE * BURST_PERIOD / (SHAPER_UNIT * SHAPER_RESOLUTION_US) + // TOKENS * SHAPER_UNIT * SHAPER_RESOLUTION_US = BITRATE * BURST_PERIOD + // BITRATE = (TOKENS * SHAPER_UNIT * SHAPER_RESOLUTION_US) / (BURST_PERIOD) + double tokenRate_Bps(double tokens) const + { + return (tokens * SHAPER_RESOLUTION_US()) / (sync::count_microseconds(m_BurstPeriod)); + } + double availRate_Bps() const { return tokenRate_Bps(m_tokens); } + double usedRate_Bps() const { return tokenRate_Bps(m_tokensCapacity - m_tokens); } +}; } // namespace srt #endif diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 2e94668ae1..9d0b8e338c 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -464,12 +464,12 @@ bool SrtParseConfig(const string& s, SrtConfig& w_config) return true; } -std::string FormatLossArray(const std::vector< std::pair >& lra) +string FormatLossArray(const vector< pair >& lra) { - std::ostringstream os; + ostringstream os; os << "[ "; - for (std::vector< std::pair >::const_iterator i = lra.begin(); i != lra.end(); ++i) + for (vector< pair >::const_iterator i = lra.begin(); i != lra.end(); ++i) { int len = CSeqNo::seqoff(i->first, i->second); os << "%" << i->first; @@ -482,6 +482,16 @@ std::string FormatLossArray(const std::vector< std::pair >& lr return os.str(); } +string FormatValue(int value, int factor, const char* unit) +{ + ostringstream out; + double showval = value; + showval /= factor; + out << std::fixed << showval << unit; + return out.str(); +} + + ostream& PrintEpollEvent(ostream& os, int events, int et_events) { static pair const namemap [] = { diff --git a/srtcore/common.h b/srtcore/common.h index ff84c3fafc..15fa39e376 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -1437,6 +1437,7 @@ 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); +std::string FormatValue(int value, int factor, const char* unit); } // namespace srt diff --git a/srtcore/core.cpp b/srtcore/core.cpp index dd189f73c1..9e8d6b11e0 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -259,6 +259,134 @@ CUDTUnited& srt::CUDT::uglobal() #endif +#ifdef ENABLE_RATE_MEASUREMENT +void srt::RateMeasurement::pickup(const clock_time& time) +{ + // Check if the time has passed the period. + clock_interval diff; + if (!passedInterval(time, (diff))) + { + HLOGC(bslog.Debug, log << "RateMeasurement: too early, " << FormatDuration(diff) + << " with begin TS=" << FormatTime(m_beginTime) << " now=" << FormatTime(time)); + // Still collect data for the current period. + return; + } + + // Doesn't matter what `diff` really is. Enough that we have + // this time aligned with 10ms resolution calls 10 times. + + Slice slice; + { + // Lock for the moment of taking the data + sync::ScopedLock lk (m_lock); + + slice.packets = m_nInstaPackets; + slice.bytes = m_nInstaBytes; + slice.begin_time = m_beginTime; + + m_nInstaPackets = 0; + m_nInstaBytes = 0; + m_beginTime = time; + } + + HLOGC(bslog.Debug, log << "RateMeasurement: passed " << FormatDuration(diff) + << " extracted packets=" << slice.packets << " bytes=" << slice.bytes + << " now beginTS=" << FormatTime(m_beginTime)); + + // Ok, now stickpile the slice. + // If the container is empty, do not stockpile empty slices. + if (m_slices.empty() && slice.bytes == 0) + { + HLOGC(bslog.Debug, log << "... NOT COLLECING packet with " << slice.bytes << " bytes to " << m_slices.size() << " total"); + // Empty-to-empty: DO NOT stockpile. + // NOTE: the container is never cleared when it has + // any older data, so this situation is only possible: + // - in the beginning + // - in the long run if it happened that 10 slices in + // a row were empty. + // So in this case we simply wait for having the first + // packets with something notified. We also know that + // the rate was initially 0 and it remains so now. + return; + } + + // We either have something and have pushed an empty slice + // or we have pushed filled slice onto the empty container. + // Still, we need at least 5 samples to start measuring. + m_slices.push_back(slice); + + // NOTE: this is also a situation that might have happened + // in the beginning, or after a long period without sending. + // If that period happens, the sending rate is also cleared, + // in any case we need to wait for the minimum samples. + if (m_slices.size() < MIN_SLICES) + { + HLOGC(bslog.Debug, log << "... NOT MEASURING: still " << m_slices.size() << " < 5 slices"); + return; + } + + // Keep track of only 10 last slices + if (m_slices.size() > MAX_SLICES) + { + m_slices.pop_front(); + + // After a slice was removed, there's already a chance + // that all other slices are also 0. Check the minimum + // number of slices + 1 and drop all that are empty + size_t end_range = 0; // default: nothing to delete + for (size_t i = 0; i < m_slices.size(); ++i) + { + if (i > MIN_SLICES + 1) + break; + + if (m_slices[i].bytes > 0) + break; + + // This can be skipped and check the next one. + end_range = i; + } + + // PARANOID + if (end_range >= m_slices.size()) + end_range = m_slices.size() - 1; // at least 1 element must stay + + m_slices.erase(m_slices.begin(), m_slices.begin() + end_range); + + m_beginTime = m_slices[0].begin_time; + HLOGC(bslog.Debug, log << "... Dropping 1 oldest + " << end_range << " empty slices, TS=" << FormatTime(m_beginTime)); + } + + // Check also if the begin time is older than 2* period; if so, + // reset it to that value + if (m_slices.size() > 1) // with 1 always m_beginTime = m_slices[0].begin_time + { + clock_time earliest = m_slices[1].begin_time - milliseconds_from(SLICE_INTERVAL_MS*2); + if (m_beginTime < earliest) + { + HLOGC(bslog.Debug, log << "... BEGIN TIME too old TS=" << FormatTime(m_beginTime) + << " - fixed to " << FormatTime(earliest)); + m_beginTime = earliest; + m_slices[0].begin_time = earliest; + } + } + + // Ok, so we have enough samples to record the measurement. + // (so we have at least one) + Summary sum (m_slices[0].begin_time); + + for (size_t i = 0; i < m_slices.size(); ++i) + { + sum.consume(m_slices[i]); + } + + m_currentPktRate = sum.ratePackets(time); + m_currentByteRate = sum.rateBytes(time, m_SysHeaderSize + CPacket::HDR_SIZE); + + HLOGC(bslog.Debug, log << "... CALCULATED from " << m_slices.size() << " slices: total TD=" << FormatDuration(time - m_slices[0].begin_time) + << " packets=" << sum.packets << " bytes=" << sum.bytes << " pkt-rate=" << m_currentPktRate << " byte-rate=" << m_currentByteRate); +} +#endif + void srt::CUDT::construct() { m_pSndBuffer = NULL; @@ -303,6 +431,12 @@ void srt::CUDT::construct() m_bPeerTLPktDrop = false; m_bBufferWasFull = false; + // Will be updated on first send +#ifdef ENABLE_MAXREXMITBW + m_zSndAveragePacketSize = 0; + m_zSndMaxPacketSize = 0; +#endif + // Initilize mutex and condition variables. initSynch(); @@ -313,7 +447,8 @@ void srt::CUDT::construct() srt::CUDT::CUDT(CUDTSocket* parent) : m_parent(parent) #ifdef ENABLE_MAXREXMITBW - , m_SndRexmitRate(sync::steady_clock::now()) + // , m_SndRexmitRate(sync::steady_clock::now()) + , m_SndRexmitShaper(CSrtConfig::DEF_MSS) #endif , m_iISN(-1) , m_iPeerISN(-1) @@ -342,7 +477,8 @@ srt::CUDT::CUDT(CUDTSocket* parent) srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) : m_parent(parent) #ifdef ENABLE_MAXREXMITBW - , m_SndRexmitRate(sync::steady_clock::now()) + // , m_SndRexmitRate(sync::steady_clock::now()) + , m_SndRexmitShaper(CSrtConfig::DEF_MSS) #endif , m_iISN(-1) , m_iPeerISN(-1) @@ -6202,6 +6338,13 @@ SRT_REJECT_REASON srt::CUDT::setupCC() m_tsLastRspAckTime = currtime; m_tsLastSndTime.store(currtime); +#ifdef ENABLE_RATE_MEASUREMENT + // XXX NOTE: use IPv4 or IPv6 as applicable! + HLOGC(bslog.Debug, log << CONID() << "RATE-MEASUREMENT: initializing time TS=" << FormatTime(currtime)); + m_SndRegularMeasurement.init(currtime, CPacket::UDP_HDR_SIZE); + m_SndRexmitMeasurement.init(currtime, CPacket::UDP_HDR_SIZE); +#endif + HLOGC(rslog.Debug, log << CONID() << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize << " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)" @@ -9461,80 +9604,157 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) } } -int srt::CUDT::packLostData(CPacket& w_packet) +// XXX During sender buffer refax turn this into either returning +// a sequence number or move it to the sender buffer facility. +// [[using locked (m_RecvAckLock)]] +std::pair srt::CUDT::getCleanRexmitOffset() { - // protect m_iSndLastDataAck from updating by ACK processing - UniqueLock ackguard(m_RecvAckLock); - const steady_clock::time_point time_now = steady_clock::now(); - const steady_clock::time_point time_nak = time_now - microseconds_from(m_iSRTT - 4 * m_iRTTVar); + // This function is required to look into the loss list and + // drop all sequences that are alrady revoked from the sender + // buffer, send the drop request if needed, and return the sender + // buffer offset for the next packet to retransmit, or -1 if + // there is no retransmission candidate at the moment. for (;;) { - w_packet.set_seqno(m_pSndLossList->popLostSeq()); - if (w_packet.seqno() == SRT_SEQNO_NONE) - break; + // REPEATABLE because we might need to drop this scheduled seq. - // XXX See the note above the m_iSndLastDataAck declaration in core.h - // This is the place where the important sequence numbers for - // sender buffer are actually managed by this field here. - const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.seqno()); + // Pick up the first sequence. + int32_t seq = m_pSndLossList->popLostSeq(); + if (seq == SRT_SEQNO_NONE) + { + // No loss reports, we are clear here. + return std::make_pair(SRT_SEQNO_NONE, -1); + } + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); if (offset < 0) { // XXX Likely that this will never be executed because if the upper // sequence is not in the sender buffer, then most likely the loss // was completely ignored. - LOGC(qrlog.Error, - log << CONID() << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() " - << w_packet.seqno() << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset - << ". Continue, request DROP"); + LOGC(qslog.Error, log << CONID() + << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() " + << seq << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset + << ". Continue, request DROP"); + + int32_t oldest_outdated_seq = seq; + // We have received the first outdated sequence. + // Loop on to find out more. + // Interrupt on the first non-outdated sequence + // or when the loss list gets empty. + + for (;;) + { + seq = m_pSndLossList->popLostSeq(); + if (seq == SRT_SEQNO_NONE) + { + offset = -1; // set it because this will be returned. + break; + } + + offset = CSeqNo::seqoff(m_iSndLastDataAck, seq); + if (offset >= 0) + break; + } + + // Now we have 'seq' that is either non-sequence + // or a valid sequence. We can safely exit with this + // value from this branch. We just need to manage + // the drop report. // No matter whether this is right or not (maybe the attack case should be // considered, and some LOSSREPORT flood prevention), send the drop request // to the peer. int32_t seqpair[2] = { - w_packet.seqno(), + oldest_outdated_seq, CSeqNo::decseq(m_iSndLastDataAck) }; - HLOGC(qrlog.Debug, - log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: #" - << MSGNO_SEQ::unwrap(w_packet.msgflags()) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "(" - << (-offset) << " packets)"); + HLOGC(qslog.Debug, + log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %(" + << seqpair[0] << " - " << seqpair[1] << ") (" << (-offset) << " packets)"); // See interpretation in processCtrlDropReq(). We don't know the message number, // so we request that the drop be exclusively sequence number based. int32_t msgno = SRT_MSGNO_CONTROL; - sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair)); + } + + // We have exit anyway with the right sequence or no sequence. + if (seq == SRT_SEQNO_NONE) + return std::make_pair(SRT_SEQNO_NONE, -1); + + // For the right sequence though check also the rigt time. + const steady_clock::time_point time_now = steady_clock::now(); + if (!checkRexmitRightTime(offset, time_now)) + { + // Ignore this, even though valid, sequence, and pick up + // the next from the list. continue; } - if (m_bPeerNakReport && m_config.iRetransmitAlgo != 0) + // Ok, we have what we needed one way or another. + return std::make_pair(seq, offset); + } +} + +// [[using locked (m_RecvAckLock)]] +bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time) +{ + // If not configured, the time is always right + if (!m_bPeerNakReport || m_config.iRetransmitAlgo == 0) + return true; + + const steady_clock::time_point time_nak = current_time - microseconds_from(m_iSRTT - 4 * m_iRTTVar); + const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); + + if (tsLastRexmit >= time_nak) + { +#if ENABLE_HEAVY_LOGGING + ostringstream last_rextime; + if (is_zero(tsLastRexmit)) { - const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset); - if (tsLastRexmit >= time_nak) - { - HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno " - << w_packet.seqno() << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit)) - << " RTT=" << m_iSRTT << " RTTVar=" << m_iRTTVar - << " now=" << FormatTime(time_now)); - continue; - } + last_rextime << "REXMIT FIRST TIME"; } + else + { + last_rextime << "REXMITTED " << FormatDuration(current_time - tsLastRexmit) + << " ago at " << FormatTime(tsLastRexmit); + } + HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << CSeqNo::incseq(m_iSndLastDataAck, offset) + << " " << last_rextime.str() + << ", RTT: ~" << FormatValue(m_iSRTT, 1000, "ms") + << " <>" << FormatValue(m_iRTTVar, 1000, "") + << " now=" << FormatTime(current_time)); +#endif + return false; + } + + return true; +} - typedef CSndBuffer::DropRange DropRange; +// [[using locked (m_RecvAckLock)]] +int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, + srt::sync::steady_clock::time_point& w_tsOrigin) +{ + // REPEATABLE BLOCK (not a real loop) + for (;;) + { + typedef CSndBuffer::DropRange DropRange; DropRange buffer_drop; - steady_clock::time_point tsOrigin; - const int payload = m_pSndBuffer->readData(offset, (w_packet), (tsOrigin), (buffer_drop)); + w_packet.set_seqno(seqno); + + const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_tsOrigin), (buffer_drop)); if (payload == CSndBuffer::READ_DROP) { SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0); - HLOGC(qrlog.Debug, - log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #" - << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " - << buffer_drop.seqno[DropRange::END] << ")"); + HLOGC(qslog.Debug, + log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #" + << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - " + << buffer_drop.seqno[DropRange::END] << ")"); sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno)); // skip all dropped packets @@ -9542,43 +9762,133 @@ int srt::CUDT::packLostData(CPacket& w_packet) m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]); continue; } - else if (payload == CSndBuffer::READ_NONE) - continue; - // The packet has been ecrypted, thus the authentication tag is expected to be stored - // in the SND buffer as well right after the payload. - if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + if (payload == CSndBuffer::READ_NONE) { - w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX); + LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << w_packet.seqno() << " NOT FOUND in the sender buffer"); + return 0; } - // At this point we no longer need the ACK lock, - // because we are going to return from the function. - // Therefore unlocking in order not to block other threads. - ackguard.unlock(); + return payload; + } + +} - enterCS(m_StatsLock); - m_stats.sndr.sentRetrans.count(payload); - leaveCS(m_StatsLock); +int srt::CUDT::packLostData(CPacket& w_packet) +{ +#if ENABLE_MAXREXMITBW + +#ifdef ENABLE_RATE_MEASUREMENT +#define IF_RATE_MEA(expr) expr +#else +#define IF_RATE_MEA(expr) +#endif - // Despite the contextual interpretation of packet.m_iMsgNo around - // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular - // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. - // So, set here the rexmit flag if the peer understands it. - if (m_bPeerRexmitFlag) + // XXX NOTE: If you refactor the sender buffer so that the random access + // is possible, you might be able to extract the exact packet size to + // check for enough tokens and consume them in one step. + + bool maxrexmitbw_enabled = m_config.llMaxRexmitBW >= 0; + if (maxrexmitbw_enabled) + { + IF_RATE_MEA( IF_HEAVY_LOGGING(const int64_t iRexmitRateMeasured = m_SndRexmitMeasurement.rateBytes()); ) + size_t granted_size = m_zSndAveragePacketSize; + size_t len = granted_size + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + + if (!m_SndRexmitShaper.enoughTokens(len)) { - w_packet.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT); + HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED pkt est/len=" << len << " exceeds " << m_SndRexmitShaper.ntokens() + << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() + IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) + ); + return 0; } - setDataPacketTS(w_packet, tsOrigin); -#ifdef ENABLE_MAXREXMITBW - m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength()); + HLOGC(qslog.Debug, log << "REXMIT-SH: ALLOWED pkt est/len=" << len << " allowed, budget " << m_SndRexmitShaper.ntokens() + << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps() + IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" ) + ); + } + +#undef IF_RATE_MEA #endif - return payload; + // Original sending time of the packet, to be stamped after filling. + steady_clock::time_point tsOrigin; + + { + // protect m_iSndLastDataAck from updating by ACK processing + ScopedLock ackguard(m_RecvAckLock); + int32_t seqno; + int offset; + + // Get the first sequence for retransmission, bypassing and taking care of + // those that are in the forgotten region, as well as required to be rejected. + Tie2(seqno, offset) = getCleanRexmitOffset(); + + if (seqno == SRT_SEQNO_NONE) + return 0; + + // Extract the packet from the sender buffer that is mapped to the expected sequence + // number, bypassing and taking care of those that are decided to be dropped. + const int payload = extractCleanRexmitPacket(seqno, offset, (w_packet), (tsOrigin)); + if (payload <= 0) + return 0; } - return 0; + HLOGC(qslog.Debug, log << CONID() << "packed REXMIT packet %" << w_packet.seqno() << " size=" << w_packet.getLength() + << " - still " << m_pSndLossList->getLossLength() << " LOSS ENTRIES left"); + + // POST-EXTRACTION length fixes. + + // The packet has been ecrypted, thus the authentication tag is expected to be stored + // in the SND buffer as well right after the payload. + if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + { + w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX); + } + +#if ENABLE_MAXREXMITBW + if (maxrexmitbw_enabled) + { + // Token consumption will only happen when the retransmission + // effectively happens. + // XXX NOTE: In version 1.6.0 use the IP-version dependent value for UDP_HDR_SIZE + size_t network_size = w_packet.getLength() + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + m_SndRexmitShaper.consumeTokens(network_size); + HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens()); + } +#endif + + enterCS(m_StatsLock); + m_stats.sndr.sentRetrans.count(w_packet.getLength()); + leaveCS(m_StatsLock); + + // Despite the contextual interpretation of packet.m_iMsgNo around + // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular + // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. + // So, set here the rexmit flag if the peer understands it. + if (m_bPeerRexmitFlag) + { + w_packet.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT); + } + setDataPacketTS(w_packet, tsOrigin); + +#ifdef ENABLE_MAXREXMITBW + // XXX OLD rexmit measurement + // m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength()); +#endif + + // XXX Consider calculating the total packet length once you have a possibility + // to get the in-connection used IP version. +#ifdef ENABLE_RATE_MEASUREMENT + m_SndRexmitMeasurement.dataUpdate(1, w_packet.getLength()); + HLOGC(bslog.Debug, log << "RateMeasurement: REXMIT, pkt-size=" << w_packet.getLength() + << " - COLLECTED pkts=" << m_SndRexmitMeasurement.m_nInstaPackets + << " bytes=" << m_SndRexmitMeasurement.m_nInstaBytes); +#endif + + return w_packet.getLength(); } #if SRT_DEBUG_TRACE_SND @@ -9703,15 +10013,69 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) p.set_timestamp(makeTS(ts, tsStart)); } -bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) +bool srt::CUDT::isRegularSendingPriority() { - // Prioritization of original packets only applies to Live CC. + // In order to have regular packets take precedense over retransmitted: + // - SRTO_TLPKTDROP = true + // - SRTO_MESSAGEAPI = true + // NOTE: + // - tlpktdrop is ignored in stream mode + // - messageapi without tlpktdrop is possible in non-live message mode + // - Live mode without tlpktdrop is possible and in this mode also the + // retransmitted packets should have priority. if (!m_bPeerTLPktDrop || !m_config.bMessageAPI) - return true; + return false; - // TODO: lock sender buffer? + // XXX NOTE: the current solution is simple - the regular packet takes precedence + // over a retransmitted packet, if there is at least one such packet already + // scheduled. + // + // This probably isn't the most wanted solution, some more elaborate condition + // might be better in some situations. For example, it should be acceptable + // that a packet that has very little time to be recovered is sent before a + // regular packet that has still STT + Latency time to deliver. The regular + // packets should still be favorized, but not necessarily at the expence of + // dismissing a recovery chance that wouldn't endanger the delivery of a regular + // packet. Criteria might be various, for example, the number of scheduled + // packets and their late delivery time might be taken into account. const time_point tsNextPacket = m_pSndBuffer->peekNextOriginal(); + if (tsNextPacket != time_point()) + { + // Have regular packet and we decided they have a priority. + HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED because regular packets have priority"); + return true; + } + return false; +} + +namespace srt { +#ifdef ENABLE_MAXREXMITBW +static sync::steady_clock::duration optimisticSingleTripDuration(int rttval, int rttvar) +{ + int lowrtt = rttval - rttvar; + if (lowrtt < 0) // bullshit? + lowrtt = rttval; + + int stt = lowrtt/2; + + // Make sure that burst period has at least this value + sync::steady_clock::duration stt_td = sync::microseconds_from(stt); + + // Still, prevent from setting enormous STT values + if (stt_td < sync::seconds_from(4)) + { + return stt_td; + } + return sync::steady_clock::duration(); +} +#endif +} + +void srt::CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED) +{ + const time_point tnow SRT_ATR_UNUSED = steady_clock::now(); + #if SRT_DEBUG_TRACE_SND const int buffdelay_ms = count_milliseconds(m_pSndBuffer->getBufferingDelay(tnow)); // If there is a small loss, still better to retransmit. If timespan is already big, @@ -9727,31 +10091,37 @@ bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED) g_snd_logger.state.msTimespanTh = threshold_ms_min; g_snd_logger.state.msNextUniqueToSend = msNextUniqueToSend; g_snd_logger.state.usElapsedLastDrop = count_microseconds(tnow - m_tsLastTLDrop); - g_snd_logger.state.canRexmit = false; + g_snd_logger.state.canRexmit = can_rexmit; #endif - if (tsNextPacket != time_point()) - { - // Can send original packet, so just send it - return false; - } - #ifdef ENABLE_MAXREXMITBW + /* OLD rate estimator; CODE for packLostData()! m_SndRexmitRate.addSample(tnow, 0, 0); // Update the estimation. const int64_t iRexmitRateBps = m_SndRexmitRate.getRate(); - const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW; if (iRexmitRateLimitBps >= 0 && iRexmitRateBps > iRexmitRateLimitBps) { // Too many retransmissions, so don't send anything. // TODO: When to wake up next time? return false; } -#endif + */ + const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW; + if (iRexmitRateLimitBps >= 0) + { + int b4_tokens SRT_ATR_UNUSED = m_SndRexmitShaper.ntokens(); + m_SndRexmitShaper.setBitrate(iRexmitRateLimitBps); + duration stt_td = optimisticSingleTripDuration(m_iSRTT, m_iRTTVar); + if (stt_td != duration()) + m_SndRexmitShaper.setMinimumBurstPeriod(stt_td); + m_SndRexmitShaper.tick(tnow); + int a4_tokens SRT_ATR_UNUSED = m_SndRexmitShaper.ntokens(); -#if SRT_DEBUG_TRACE_SND - g_snd_logger.state.canRexmit = true; + HLOGC(qslog.Debug, log << "REXMIT-SW: tick tokens updated: " << b4_tokens << " -> " << a4_tokens + << "/" << m_SndRexmitShaper.maxTokens() << " period=" + << FormatDuration(m_SndRexmitShaper.burstPeriod())); + } #endif - return true; + } bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, sockaddr_any& w_src_addr) @@ -9780,9 +10150,25 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime if (!m_bOpened) return false; - payload = isRetransmissionAllowed(enter_time) - ? packLostData((w_packet)) - : 0; + // This function returns true if there is a regular packet candidate waiting + // for being sent and sending this packet has a prioriry over retransmission candidate. + if (!isRegularSendingPriority()) + { + payload = packLostData((w_packet)); +#if ENABLE_MAXREXMITBW + if (payload == 0) + { + HLOGC(qslog.Debug, log << "REXMIT-SH: no rexmit required, remain " << m_SndRexmitShaper.ntokens() << " tokens"); + } +#endif + } + else + { + HLOGC(qslog.Debug, log << "REXMIT: retransmission SUPERSEDED, proceed with regular candidate"); + } + + // Updates the data that will be next used in packLostData() in next calls. + updateSenderMeasurements(payload != 0); IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter) if (payload > 0) @@ -9808,6 +10194,18 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime m_tdSendTimeDiff = steady_clock::duration(); return false; } + +#if ENABLE_MAXREXMITBW + if (m_zSndAveragePacketSize > 0) + { + m_zSndAveragePacketSize = avg_iir<16>(m_zSndAveragePacketSize, w_packet.getLength()); + } + else + { + m_zSndAveragePacketSize = w_packet.getLength(); + } + m_zSndMaxPacketSize = std::max(m_zSndMaxPacketSize, w_packet.getLength()); +#endif new_packet_packed = true; // every 16 (0xF) packets, a packet pair is sent @@ -10044,6 +10442,11 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet) g_snd_logger.trace(); #endif +#ifdef ENABLE_RATE_MEASUREMENT + HLOGC(bslog.Debug, log << "RATE-MEASUREMENT: REGULAR, pkt-size=" << w_packet.getLength()); + m_SndRegularMeasurement.dataUpdate(1, w_packet.getLength()); +#endif + return true; } @@ -11773,6 +12176,11 @@ void srt::CUDT::checkTimers() << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount); #endif +#ifdef ENABLE_RATE_MEASUREMENT + m_SndRegularMeasurement.pickup(currtime); + m_SndRexmitMeasurement.pickup(currtime); +#endif + // Check if it is time to send ACK int debug_decision = checkACKTimer(currtime); diff --git a/srtcore/core.h b/srtcore/core.h index 6bf75466ed..0e4b4b5d1d 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -159,6 +159,133 @@ class CUDTSocket; class CUDTGroup; #endif +#ifdef ENABLE_RATE_MEASUREMENT +struct RateMeasurement +{ + typedef sync::steady_clock clock_type; + typedef clock_type::time_point clock_time; + typedef clock_type::duration clock_interval; + + static const int SLICE_INTERVAL_MS = 20; + static const size_t MIN_SLICES = 5; // min + static const size_t MAX_SLICES = 10; + + sync::Mutex m_lock; + + size_t m_SysHeaderSize; + + // Instantaneous data updated by sending packets + // AFFINITY: Snd:worker thread + // MODE: write + sync::atomic m_nInstaPackets; + sync::atomic m_nInstaBytes; + + void dataUpdate(int packets, int bytes) + { + sync::ScopedLock lk (m_lock); + m_nInstaPackets = m_nInstaPackets + packets; + m_nInstaBytes = m_nInstaBytes + bytes; + } + + // Cached last measurement + // AFFINITY: Snd:worker thread MODE: read + // AFFINITY: Rcv:worker thread MODE: write + // (not locking because one data is updated at a time). + sync::atomic m_currentPktRate; + sync::atomic m_currentByteRate; + int64_t rateBytes() const + { + return m_currentByteRate; + } + int64_t ratePackets() const + { + return m_currentPktRate; + } + + // Time of the last checkpoint or initialization + clock_time m_beginTime; + + struct Slice + { + int packets; + int bytes; + clock_time begin_time; + + Slice(): packets(0), bytes(0) {} // clock_time is non-POD + Slice(const clock_time& t): packets(0), bytes(0), begin_time(t) {} + + }; + + struct Summary: Slice + { + Summary(const clock_time& earliest_time): Slice(earliest_time), nsamples(0) {} + + unsigned char nsamples; + + void consume(const Slice& another) + { + if (is_zero(begin_time)) + begin_time = another.begin_time; + packets += another.packets; + bytes += another.bytes; + nsamples++; + } + + int64_t ratePackets(const clock_time& end_time) const + { + double packets_per_micro = double(packets) * 1000 * 1000; + double rate = packets_per_micro / count_microseconds(end_time - begin_time); + return rate; + } + + int64_t rateBytes(const clock_time& end_time, size_t hdr_size) const + { + double data_bytes = bytes + (packets * hdr_size); + double bytes_per_micro = data_bytes * 1000 * 1000; + double rate = bytes_per_micro / count_microseconds(end_time - begin_time); + return rate; + } + }; + + std::deque m_slices; + + RateMeasurement(): + m_SysHeaderSize(CPacket::UDP_HDR_SIZE), // XXX NOTE: IPv4 ! + m_nInstaPackets(0), + m_nInstaBytes(0), + m_currentPktRate(0), + m_currentByteRate(0) + { + } + + bool passedInterval(const clock_time& this_time, clock_interval& w_interval) + { + if (is_zero(m_beginTime)) + { + sync::ScopedLock lk (m_lock); + m_beginTime = this_time; + w_interval = clock_interval(0); + return false; + } + w_interval = this_time - m_beginTime; + return (sync::count_milliseconds(w_interval) > SLICE_INTERVAL_MS); + } + + // This is to be called in constructor, only once. + void init(const clock_time& time, size_t sys_hdr_size) + { + // Just formally. + sync::ScopedLock lk (m_lock); + m_beginTime = time; + m_SysHeaderSize = sys_hdr_size; + } + + // AFFINITY: Rcv:worker thread (update thread) + // This function should be called in regular time periods. + void pickup(const clock_time& time); +}; +#endif + // XXX REFACTOR: The 'CUDT' class is to be merged with 'CUDTSocket'. // There's no reason for separating them, there's no case of having them // anyhow managed separately. After this is done, with a small help with @@ -593,11 +720,14 @@ class CUDT SRT_ATTR_REQUIRES2(m_RecvAckLock, m_StatsLock) int sndDropTooLate(); - /// @bried Allow packet retransmission. - /// Depending on the configuration mode (live / file), retransmission - /// can be blocked if e.g. there are original packets pending to be sent. - /// @return true if retransmission is allowed; false otherwise. - bool isRetransmissionAllowed(const time_point& tnow); + // Returns true if there is a regular packet waiting for sending + // and sending regular packets has priority over retransmitted ones. + bool isRegularSendingPriority(); + + // Performs updates in the measurement variables after possible + // extraction of a retransmission packet. Some are for debug purposes, + // others for retransmission rate measurement. + void updateSenderMeasurements(bool can_rexmit); /// Connect to a UDT entity as per hs request. This will update /// required data in the entity, then update them also in the hs structure, @@ -873,7 +1003,16 @@ class CUDT CSndLossList* m_pSndLossList; // Sender loss list CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window #ifdef ENABLE_MAXREXMITBW - CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation. + size_t m_zSndAveragePacketSize; + size_t m_zSndMaxPacketSize; + // XXX Old rate estimator for rexmit + // CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation. + CShaper m_SndRexmitShaper; + +#ifdef ENABLE_RATE_MEASUREMENT + RateMeasurement m_SndRegularMeasurement; // Regular rate measurement + RateMeasurement m_SndRexmitMeasurement; // Retransmission rate measurement +#endif #endif atomic_duration m_tdSendInterval; // Inter-packet time, in CPU clock cycles @@ -1131,6 +1270,11 @@ class CUDT /// @return payload size on success, <=0 on failure int packLostData(CPacket &packet); + std::pair getCleanRexmitOffset(); + bool checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time); + int extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet, + srt::sync::steady_clock::time_point& w_tsOrigin); + /// Pack a unique data packet (never sent so far) in CPacket for sending. /// @param packet [in, out] a CPacket structure to fill. /// diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 339512573f..ed18a72d6a 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -730,6 +730,10 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) RD(true); case SRTO_MAXBW: RD(int64_t(-1)); +#ifdef ENABLE_MAXREXMITBW + case SRTO_MAXREXMITBW: + RD(int64_t(-1)); +#endif case SRTO_INPUTBW: RD(int64_t(-1)); case SRTO_MININPUTBW: diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 8781b80e75..cf3add810c 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -789,12 +789,6 @@ inline void insert_uniq(std::vector& v, const ArgValue& val) v.push_back(val); } -template -inline std::pair Tie(Type1& var1, Type2& var2) -{ - return std::pair(var1, var2); -} - // This can be used in conjunction with Tie to simplify the code // in loops around a whole container: // list::const_iterator it, end; @@ -972,6 +966,16 @@ inline void AccumulatePassFilterParallel(const int* p, size_t size, PassFilter +inline Type Bounds(Type lower, Type value, Type upper) +{ + if (value < lower) + return lower; + if (value > upper) + return upper; + return value; +} + inline std::string FormatBinaryString(const uint8_t* bytes, size_t size) { diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index b39345c401..3910c959b9 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -91,6 +91,7 @@ class TestFECRebuilding: public srt::Test { delete fec; } + }; static std::future spawn_connect(SRTSOCKET s, sockaddr_in& sa, int timeout_ms = 1000) @@ -374,6 +375,9 @@ TEST(TestFEC, ConnectionReorder) ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); + int conntimeo = 10000; + ASSERT_NE(srt_setsockflag(s, SRTO_CONNTIMEO, &conntimeo, sizeof (conntimeo)), SRT_ERROR); + srt_listen(l, 1); auto connect_res = spawn_connect(s, sa); From c09532f8edf28de91854f975bb16e643e8085ed9 Mon Sep 17 00:00:00 2001 From: Maria Sharabayko Date: Mon, 27 Oct 2025 11:06:48 +0100 Subject: [PATCH 51/92] [docs] Added link to the blogpost with - Live Streaming using SRT with QUIC Datagrams - research --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0e18ff4a57..e6e7f79051 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ In live streaming configurations, the SRT protocol maintains a constant end-to-e - [RTMP vs. SRT: Comparing Latency and Maximum Bandwidth](https://www.haivision.com/resources/white-paper/srt-versus-rtmp/) White Paper. - [Documentation on GitHub](./docs#documentation-overview) with SRT API documents, features decsriptions, etc. - The SRT Protocol Internet Draft: [Datatracker](https://datatracker.ietf.org/doc/draft-sharabayko-srt/) | [Latest Version](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01) | [Latest Working Copy](https://haivision.github.io/srt-rfc/draft-sharabayko-srt.html) | [GitHub Repo](https://github.com/Haivision/srt-rfc) +- If you are curious about live streaming using SRT with QUIC datagrams as an alternative to UDP transport, take a look at the following [blog post](https://medium.com/innovation-labs-blog/live-streaming-using-srt-with-quic-datagrams-7896f7ce7bf3?source=friends_link&sk=d0a00e79861d89673e27a04260f279b5) on Medium. ## Build Instructions From f0368c6998fbf6f789f8e484bdc6ea6f4a48a750 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 25 Nov 2025 08:50:17 +0100 Subject: [PATCH 52/92] [build] Added handling of LIBSRT_ prefix when setting cmake options for SRT build (#3219) * [BUILD] Added handling of LIBSRT_ prefix when setting cmake options for SRT build * Added documentation * Apply suggestions from code review Co-authored-by: stevomatthews * Fixed text alignment after doc review --------- Co-authored-by: Mikolaj Malecki Co-authored-by: stevomatthews --- CMakeLists.txt | 10 ++++++++++ docs/build/build-options.md | 33 +++++++++++++++++++++++++++++++++ scripts/haiUtil.cmake | 13 +++++++++++++ 3 files changed, 56 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e19caa2c5..cf9b360ed5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,16 @@ endforeach() # SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ # SRT_ENABLE_FREQUENT_LOG_TRACE 0 : set to 1 to enable printing reason for suppressed freq logs +# CMake offers sometimes OLD behavior, where names that are defined below with +# option() function, will be deleted if they are not in cache or do not have set +# type - so effectively options provided through LIBSRT_* prefix would be rejected. +# We do want these variables, obviously. +cmake_policy(SET CMP0077 NEW) + +# Import all options preceded with LIBSRT_ likely from a parent scope. +# Names are not being checked, at worst it will set an unused variable. +srt_import_parent_options() + # option defaults set(ENABLE_HEAVY_LOGGING_DEFAULT OFF) diff --git a/docs/build/build-options.md b/docs/build/build-options.md index e09ea41bb7..05fcf8be4e 100644 --- a/docs/build/build-options.md +++ b/docs/build/build-options.md @@ -17,6 +17,39 @@ Additional information on building for Windows is available in the document and in the [SRT CookBook](https://srtlab.github.io/srt-cookbook/getting-started/build-on-windows/). +## Building as a subproject + +The CMake tool offers the ability to add a complete project as a subdirectory. +Variables used by the SRT project in this case remain in their own scope, but +all variables from the parent scope are reflected. In order to prevent name +clashes for option-designating variables, SRT provides a namespace-like +prefixing for the optional variables it uses. If you want to configure optional +variables from the level of `CMakeLists.txt` of the parent project, use the +`LIBSRT_` prefix for the option names. + +This will not prevent the variables from being seen as derived in SRT project +scope, but if you explicitly set a variable this way, it will be set to the +desired value inside the SRT project. It will not set the same variable in the +parent project, and it will also override (locally in SRT project only) any +value of a variable with the same name in the parent project. + +For example, if you want to set `ENABLE_SHARED=OFF` in the parent project, +simply do: + +``` +set (LIBSRT_ENABLE_SHARED OFF) +``` + +If you already have a variable named `ENABLE_SHARED` in your project (existing +before the call to `add_subdirectory` with SRT), its value will be derived in +the SRT project, unless you override it by setting `LIBSRT_ENABLE_SHARED` to a +different value. + +Note that the trick works simply by getting the actual variable name through +cutting off the `LIBSRT_` prefix; the check whether this variable is of any use +will be done after that. + + ## List of Build Options The following table lists available build options in alphabetical order. diff --git a/scripts/haiUtil.cmake b/scripts/haiUtil.cmake index 2226619893..312480e1e4 100644 --- a/scripts/haiUtil.cmake +++ b/scripts/haiUtil.cmake @@ -288,3 +288,16 @@ function (parse_compiler_type wct _type _suffix) endif() endif() endfunction() + +macro (srt_import_parent_options) + getVarsWith(LIBSRT_ extoptions) + foreach(ef ${extoptions}) + set (val ${${ef}}) + string(LENGTH LIBSRT_ pflen) + string(LENGTH ${ef} eflen) + math(EXPR alen ${eflen}-${pflen}) + string(SUBSTRING ${ef} ${pflen} ${alen} ef) + message(STATUS "IMPORTED OPTION: ${ef}='${val}'") + set (${ef} "${val}") + endforeach() +endmacro() From f8a53dadfb4d023405c6b49699f2f8bdf0b7fdd6 Mon Sep 17 00:00:00 2001 From: Tomoya Fujita Date: Tue, 16 Dec 2025 17:37:02 +0900 Subject: [PATCH 53/92] [build] Ubuntu 23.04 reached end-of-life in January 2024. (#3261) Signed-off-by: Tomoya.Fujita --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e6e7f79051..4255c06634 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![Build Status Linux and macOS][travis-badge]][travis] [![Build Status Windows][appveyor-badge]][appveyor] -[![Ubuntu 23.04][Ubuntu-badge]][Ubuntu-package] +[![Ubuntu 24.04][Ubuntu-badge]][Ubuntu-package] [![Fedora 37][fedora-badge]][fedora-package] [![Debian][debian-badge]][debian-package] [![Homebrew][Homebrew-badge]][Homebrew-package] @@ -242,4 +242,4 @@ By contributing code to the SRT project, you agree to license your contribution [homebrew-badge]: https://repology.org/badge/version-for-repo/homebrew/srt.svg [Ubuntu-package]: https://repology.org/project/srt/versions -[Ubuntu-badge]: https://repology.org/badge/version-for-repo/ubuntu_23_04/srt.svg +[Ubuntu-badge]: https://repology.org/badge/version-for-repo/ubuntu_24_04/srt.svg From 72f0c6e0e9a186adec428a145f4e085412d96796 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 16 Dec 2025 09:37:59 +0100 Subject: [PATCH 54/92] [BUG] [core] Prevent blocking srt_close call from interrupting connection (#3259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BUG] Prevent blocking srt_close call from interrupting connection * Test: added check that srt_close call is not blocked. Ensured that waiting is interrupt ed on close. Ensured that the socket is closed right after wait quit * Fixed incorrect C++17 literals * Increased wait time tolerance for srt_close (failing on some slower CI machines) --------- Co-authored-by: Mikołaj Małecki --- srtcore/api.cpp | 5 +- srtcore/api.h | 5 ++ srtcore/core.cpp | 3 +- srtcore/core.h | 1 + test/test_connection_timeout.cpp | 122 ++++++++++++++++++++++++++++++- test/test_file_transmission.cpp | 16 +++- 6 files changed, 146 insertions(+), 6 deletions(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 22b093dccf..0b96a33bcd 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -2144,6 +2144,9 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g) int srt::CUDTUnited::close(CUDTSocket* s) { + // Set the closing flag BEFORE you attempt to acquire + s->setBreaking(); + HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSE. Acquiring control lock"); ScopedLock socket_cg(s->m_ControlLock); HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSING (removing from listening, closing CUDT)"); @@ -2973,7 +2976,7 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u) return; } - LOGC(smlog.Note, log << "@" << s->m_SocketID << " busy=" << s->isStillBusy()); + HLOGC(smlog.Note, log << "@" << s->m_SocketID << " busy=" << s->isStillBusy()); #if ENABLE_BONDING if (s->m_GroupOf) diff --git a/srtcore/api.h b/srtcore/api.h index 42b97ef32c..789d68d0cf 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -216,6 +216,11 @@ class CUDTSocket core().m_bClosing = true; } + void setBreaking() + { + core().m_bBreaking = true; + } + /// This does the same as setClosed, plus sets the m_bBroken to true. /// Such a socket can still be read from so that remaining data from /// the receiver buffer can be read, but no longer sends anything. diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 9e8d6b11e0..d13221f1cd 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -414,6 +414,7 @@ void srt::CUDT::construct() m_bConnected = false; m_bClosing = false; m_bShutdown = false; + m_bBreaking = false; m_bBroken = false; m_bBreakAsUnstable = false; // TODO: m_iBrokenCounter should be still set to some default. @@ -3868,7 +3869,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // We can't record this address yet until the cookie-confirmation is done, for safety reasons. sockaddr_any use_source_adr(serv_addr.family()); - while (!m_bClosing) + while (!m_bClosing && !m_bBreaking) { const steady_clock::time_point local_tnow = steady_clock::now(); const steady_clock::duration tdiff = local_tnow - m_tsLastReqTime.load(); diff --git a/srtcore/core.h b/srtcore/core.h index 0e4b4b5d1d..81a5142a50 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -972,6 +972,7 @@ class CUDT sync::atomic m_bConnected; // Whether the connection is on or off sync::atomic m_bClosing; // If the UDT entity is closing sync::atomic m_bShutdown; // If the peer side has shutdown the connection + sync::atomic m_bBreaking; // The flag that declares interrupt of the connecting process sync::atomic m_bBroken; // If the connection has been broken sync::atomic m_bBreakAsUnstable; // A flag indicating that the socket should become broken because it has been unstable for too long. sync::atomic m_bPeerHealth; // If the peer status is normal diff --git a/test/test_connection_timeout.cpp b/test/test_connection_timeout.cpp index c8cbc87220..281376ce1f 100644 --- a/test/test_connection_timeout.cpp +++ b/test/test_connection_timeout.cpp @@ -14,9 +14,10 @@ typedef int SOCKET; #include"platform_sys.h" #include "srt.h" #include "netinet_any.h" +#include "common.h" using namespace std; - +using namespace srt_logging; class TestConnectionTimeout : public ::srt::Test @@ -172,12 +173,19 @@ TEST_F(TestConnectionTimeout, BlockingLoop) { const sockaddr* psa = reinterpret_cast(&m_sa); const int connection_timeout_ms = 999; + cout << "[ ]\r[" << flush; + 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) + { + cout << "!\n"; break; + } + + cout << "." << flush; // Set connection timeout to 999 ms to reduce the test execution time. // Also need to hit a time point between two threads: @@ -198,15 +206,125 @@ TEST_F(TestConnectionTimeout, BlockingLoop) EXPECT_EQ(error_code, SRT_ENOSERVER); if (error_code != SRT_ENOSERVER) { - cerr << "Connection attempt no. " << i << " resulted with: " + cout << "!\nConnection attempt no. " << i << " resulted with: " << error_code << " " << srt_getlasterror_str() << "\n"; break; } EXPECT_EQ(srt_close(client_sock), SRT_SUCCESS); } + cout << endl; +} + +TEST_F(TestConnectionTimeout, BlockingInterrupted) +{ + const SRTSOCKET client_sock = srt_create_socket(); + EXPECT_GT(client_sock, 0); // socket_id should be > 0 + + const int connection_timeout_ms = 10000; + EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); + + using namespace std::chrono; + + steady_clock::time_point begin = steady_clock::now(); + + std::thread interrupter ( [client_sock] () { + cout << "[T] START: Waiting 1s\n"; + std::this_thread::sleep_for(seconds(1)); + steady_clock::time_point b = steady_clock::now(); + + cout << "[T] CLOSING @" << client_sock << "\n"; + srt_close(client_sock); + steady_clock::time_point e = steady_clock::now(); + auto passed = duration_cast(e - b); + int close_time_passed_ms = passed.count(); + EXPECT_LT(close_time_passed_ms, 2000); + + cout << "[T] Thread exit\n"; + }); + + const sockaddr* psa = reinterpret_cast(&m_sa); + cout << "START: Connect @" << client_sock << " blind\n"; + EXPECT_EQ(srt_connect(client_sock, psa, sizeof m_sa), SRT_ERROR); + cout << "STOP: Connect\n"; + + steady_clock::time_point end = steady_clock::now(); + auto passed = duration_cast(end - begin); + int time_passed_ms = passed.count(); + cout << "Interrupted after " << time_passed_ms << "ms\n"; + + EXPECT_LT(time_passed_ms, 8000); + + interrupter.join(); } +TEST_F(TestConnectionTimeout, NonblockingInterrupted) +{ + const SRTSOCKET client_sock = srt_create_socket(); + // const SRTSOCKET extra_sock = srt_create_socket(); + EXPECT_GT(client_sock, 0); // socket_id should be > 0 + + const int connection_timeout_ms = 10000; + EXPECT_EQ(srt_setsockopt(client_sock, 0, SRTO_CONNTIMEO, &connection_timeout_ms, sizeof connection_timeout_ms), SRT_SUCCESS); + + const bool non_blocking = false; + EXPECT_EQ(srt_setsockflag(client_sock, SRTO_RCVSYN, &non_blocking, sizeof non_blocking), SRT_SUCCESS); + EXPECT_EQ(srt_setsockflag(client_sock, SRTO_SNDSYN, &non_blocking, sizeof non_blocking), SRT_SUCCESS); + + using namespace std::chrono; + + int eid = srt_epoll_create(); + + int conn_err = SRT_EPOLL_OUT | SRT_EPOLL_ERR; + + srt_epoll_add_usock(eid, client_sock, &conn_err); + // srt_epoll_add_usock(eid, extra_sock, &conn_err); + + steady_clock::time_point begin = steady_clock::now(); + + std::thread interrupter ( [client_sock] () { + cout << "[T] START: Waiting 1s\n"; + std::this_thread::sleep_for(seconds(1)); + steady_clock::time_point b = steady_clock::now(); + + cout << "[T] CLOSING @" << client_sock << "\n"; + srt_close(client_sock); + steady_clock::time_point e = steady_clock::now(); + auto passed = duration_cast(e - b); + int close_time_passed_ms = passed.count(); + EXPECT_LT(close_time_passed_ms, 2000); + + cout << "[T] Thread exit\n"; + }); + + const sockaddr* psa = reinterpret_cast(&m_sa); + cout << "START: Connect @" << client_sock << " blind\n"; + + // Result should be CONNREQ because this is non-blocking connect + EXPECT_EQ(srt_connect(client_sock, psa, sizeof m_sa), 0); + + cout << "EPOLL - wait for connect\n"; + + // Expect uwait to return -1 because all sockets have been + // removed from EID, so the call would block forever. + SRT_EPOLL_EVENT fdset[2]; + EXPECT_EQ(srt_epoll_uwait(eid, fdset, 2, 3000), -1); + SRT_SOCKSTATUS socket_status = srt_getsockstate(client_sock); + + steady_clock::time_point end = steady_clock::now(); + auto passed = duration_cast(end - begin); + int time_passed_ms = passed.count(); + cout << "Interrupted after " << time_passed_ms << "ms\n"; + + EXPECT_LT(time_passed_ms, 8000); + + interrupter.join(); + + cout << "SOCKET STATUS: " << SockStatusStr(socket_status) << endl; + EXPECT_GE(socket_status, SRTS_CLOSED); + + // srt_close(extra_sock); +} TEST(TestConnectionAPI, Accept) { diff --git a/test/test_file_transmission.cpp b/test/test_file_transmission.cpp index bfd668ac7c..e7140bf69b 100644 --- a/test/test_file_transmission.cpp +++ b/test/test_file_transmission.cpp @@ -50,9 +50,13 @@ TEST(Transmission, FileUpload) sa_lsn.sin_addr.s_addr = INADDR_ANY; sa_lsn.sin_port = htons(5555); + MAKE_UNIQUE_SOCK(sock_lsn_u, "listener", sock_lsn); + MAKE_UNIQUE_SOCK(sock_clr_u, "listener", sock_clr); + // Find unused a port not used by any other service. // Otherwise srt_connect may actually connect. int bind_res = -1; + std::cout << "Looking for a free port... " << std::flush; for (int port = 5000; port <= 5555; ++port) { sa_lsn.sin_port = htons(port); @@ -97,10 +101,13 @@ TEST(Transmission, FileUpload) std::atomic thread_exit { false }; + std::cout << "Running accept [A] thread\n"; + auto client = std::thread([&] { sockaddr_in remote; int len = sizeof remote; + std::cout << "[A] waiting for connection\n"; const SRTSOCKET accepted_sock = srt_accept(sock_lsn, (sockaddr*)&remote, &len); ASSERT_GT(accepted_sock, 0); @@ -115,27 +122,30 @@ TEST(Transmission, FileUpload) std::vector buf(1456); + std::cout << "[A] Connected, reading data...\n"; for (;;) { int n = srt_recv(accepted_sock, buf.data(), 1456); EXPECT_NE(n, SRT_ERROR) << srt_getlasterror_str(); if (n == 0) { - std::cerr << "Received 0 bytes, breaking.\n"; + std::cout << "Received 0 bytes, breaking.\n"; break; } else if (n == -1) { - std::cerr << "READ FAILED, breaking anyway\n"; + std::cout << "READ FAILED, breaking anyway\n"; break; } // Write to file any amount of data received copyfile.write(buf.data(), n); } + std::cout << "[A] Closing socket\n"; EXPECT_NE(srt_close(accepted_sock), SRT_ERROR); + std::cout << "[A] Exit\n"; thread_exit = true; }); @@ -144,6 +154,7 @@ TEST(Transmission, FileUpload) sa.sin_port = sa_lsn.sin_port; ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1); + std::cout << "Connecting...\n"; srt_connect(sock_clr, (sockaddr*)&sa, sizeof(sa)); std::cout << "Connection initialized" << std::endl; @@ -151,6 +162,7 @@ TEST(Transmission, FileUpload) std::ifstream ifile("file.source", std::ios::in | std::ios::binary); std::vector buf(1456); + std::cout << "Reading file and sending...\n"; for (;;) { size_t n = ifile.read(buf.data(), 1456).gcount(); From f5e6bb98d5ff1532c270e8314ec20a2de13df620 Mon Sep 17 00:00:00 2001 From: Tomoya Fujita Date: Mon, 12 Jan 2026 19:07:32 +0900 Subject: [PATCH 55/92] Old URL (404 - no longer exists) (#3271) Signed-off-by: Tomoya.Fujita --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4255c06634..16a1d8e084 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

- SRT + SRT

From 80c871e1fe6604ef0d95556b9e7ba03497daa606 Mon Sep 17 00:00:00 2001 From: Tomoya Fujita Date: Mon, 12 Jan 2026 22:41:37 +0900 Subject: [PATCH 56/92] Detec IPv6 with colon to harden the check if IPv6 is used in the test. (#3264) * Detec IPv6 with colon to harden the check if IPv6 is used in the test. Signed-off-by: Tomoya.Fujita * just relies on srt::CreateAddr to resolve IP family. Signed-off-by: Tomoya Fujita --------- Signed-off-by: Tomoya.Fujita Signed-off-by: Tomoya Fujita --- test/test_reuseaddr.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/test_reuseaddr.cpp b/test/test_reuseaddr.cpp index 14789f897f..929f6b3db9 100644 --- a/test/test_reuseaddr.cpp +++ b/test/test_reuseaddr.cpp @@ -148,15 +148,6 @@ class ReuseAddr : public srt::Test int yes = 1; int no = 0; - int family = AF_INET; - string famname = "IPv4"; - if (ip.substr(0, 2) == "6.") - { - family = AF_INET6; - ip = ip.substr(2); - famname = "IPv6"; - } - cout << "[T/C] Setting up client socket\n"; ASSERT_NE(client_sock, SRT_INVALID_SOCK); ASSERT_EQ(srt_getsockstate(client_sock), SRTS_INIT); @@ -171,7 +162,8 @@ class ReuseAddr : public srt::Test int epoll_out = SRT_EPOLL_OUT; srt_epoll_add_usock(client_pollid, client_sock, &epoll_out); - sockaddr_any sa = srt::CreateAddr(ip, port, family); + sockaddr_any sa = srt::CreateAddr(ip, port, AF_UNSPEC); + string famname = (sa.family() == AF_INET) ? "IPv4" : "IPv6"; cout << "[T/C] Connecting to: " << sa.str() << " (" << famname << ")" << endl; From 2220de504027b0fc45012b459365505834300e31 Mon Sep 17 00:00:00 2001 From: Tomoya Fujita Date: Tue, 20 Jan 2026 17:25:10 +0900 Subject: [PATCH 57/92] [docs] fix bunch of typos and enable codespell via github workflow. (#3263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix bunch of typos and enable codespell via github workflow. Signed-off-by: Tomoya.Fujita * address review comments. Signed-off-by: Tomoya Fujita * Replace instability by unstability and insecure by unsecure. * Rollback instability! * Moving the configuration files to scripts/codespell. * Replace connenctions by connections. --------- Signed-off-by: Tomoya.Fujita Signed-off-by: Tomoya Fujita Co-authored-by: Clément Gérouville --- .github/workflows/codespell.yml | 21 ++++++ CMakeLists.txt | 6 +- README.md | 2 +- apps/srt-live-transmit.cpp | 2 +- apps/srt-tunnel.cpp | 2 +- common/win/unistd.h | 2 +- docs/API/API-socket-options.md | 2 +- docs/API/statistics.md | 6 +- docs/apps/srt-file-transmit.md | 2 +- docs/build/build-win.md | 57 ++++++++-------- docs/features/access-control.md | 4 +- docs/features/bonding-main-backup.md | 2 +- docs/features/handshake.md | 55 ++++++++-------- docs/features/live-streaming.md | 2 +- docs/features/socket-groups.md | 8 +-- examples/fork-test/srt_server.c | 2 +- haicrypt/cryspr-botan.h | 2 +- haicrypt/cryspr-gnutls.c | 2 +- haicrypt/cryspr-gnutls.h | 4 +- haicrypt/cryspr-mbedtls.c | 2 +- haicrypt/cryspr-mbedtls.h | 2 +- haicrypt/cryspr-openssl-evp.c | 2 +- haicrypt/cryspr-openssl-evp.h | 4 +- haicrypt/cryspr-openssl.c | 2 +- haicrypt/cryspr-openssl.h | 4 +- haicrypt/cryspr.h | 2 +- haicrypt/hcrypt.c | 6 +- haicrypt/hcrypt_ctx_tx.c | 2 +- haicrypt/hcrypt_msg.h | 2 +- scripts/codespell/codespell.cfg | 14 ++++ scripts/codespell/codespell_dictionary.txt | 0 scripts/codespell/codespell_whitelist.txt | 30 +++++++++ scripts/generate-logging-defs.tcl | 4 +- scripts/googletest.cmake | 2 +- scripts/srt-dev.lua | 4 +- scripts/srt-proxy.py | 4 +- scripts/visionOS.cmake | 2 +- scripts/win-installer/README.md | 2 +- scripts/win-installer/build-win-installer.ps1 | 2 +- srtcore/api.cpp | 18 ++--- srtcore/api.h | 2 +- srtcore/atomic.h | 4 +- srtcore/buffer_rcv.cpp | 2 +- srtcore/buffer_rcv.h | 6 +- srtcore/buffer_snd.cpp | 2 +- srtcore/channel.cpp | 2 +- srtcore/common.h | 4 +- srtcore/core.cpp | 66 +++++++++---------- srtcore/core.h | 6 +- srtcore/crypto.cpp | 4 +- srtcore/epoll.cpp | 2 +- srtcore/epoll.h | 8 +-- srtcore/fec.cpp | 4 +- srtcore/fec.h | 4 +- srtcore/group.cpp | 20 +++--- srtcore/group.h | 6 +- srtcore/group_backup.h | 4 +- srtcore/list.cpp | 2 +- srtcore/list.h | 2 +- srtcore/packet.cpp | 8 +-- srtcore/packet.h | 2 +- srtcore/packetfilter.h | 2 +- srtcore/queue.cpp | 4 +- srtcore/queue.h | 4 +- srtcore/socketconfig.cpp | 6 +- srtcore/socketconfig.h | 6 +- srtcore/srt.h | 4 +- srtcore/srt_compat.c | 2 +- srtcore/srt_compat.h | 2 +- srtcore/sync.cpp | 2 +- srtcore/sync.h | 10 +-- srtcore/sync_posix.cpp | 2 +- srtcore/tsbpd_time.cpp | 2 +- srtcore/utilities.h | 6 +- srtcore/window.cpp | 2 +- srtcore/window.h | 2 +- test/any.hpp | 6 +- test/test_bonding.cpp | 4 +- test/test_buffer_rcv.cpp | 2 +- test/test_epoll.cpp | 8 +-- test/test_listen_callback.cpp | 4 +- test/test_muxer.cpp | 8 +-- test/test_reuseaddr.cpp | 8 +-- test/test_seqno.cpp | 4 +- test/test_snd_rate_estimator.cpp | 2 +- test/test_socket_options.cpp | 2 +- test/test_sync.cpp | 28 ++++---- test/test_utilities.cpp | 2 +- testing/srt-test-live.cpp | 4 +- testing/srt-test-mpbond.cpp | 2 +- testing/srt-test-multiplex.cpp | 6 +- testing/srt-test-relay.cpp | 4 +- testing/testactivemedia.cpp | 2 +- testing/testmedia.cpp | 4 +- 94 files changed, 338 insertions(+), 271 deletions(-) create mode 100644 .github/workflows/codespell.yml create mode 100644 scripts/codespell/codespell.cfg create mode 100644 scripts/codespell/codespell_dictionary.txt create mode 100644 scripts/codespell/codespell_whitelist.txt diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 0000000000..d19a0cacdd --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,21 @@ +name: codespell + +on: pull_request + +jobs: + spellcheck: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install dependencies with pip + run: pip install --no-warn-script-location --user codespell + + - name: Spellcheck + run: codespell --config scripts/codespell/codespell.cfg diff --git a/CMakeLists.txt b/CMakeLists.txt index cf9b360ed5..130a0adf99 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,7 +200,7 @@ Please use SRT_USE_OPENSSL_STATIC_LIBS instead.") endif() # NOTE: Use ATOMIC_USE_SRT_SYNC_MUTEX and will override the auto-detection of the -# Atomic implemetation in srtcore/atomic.h. +# Atomic implementation in srtcore/atomic.h. option(ATOMIC_USE_SRT_SYNC_MUTEX "Use srt::sync::Mutex to Implement Atomics" OFF) if (ATOMIC_USE_SRT_SYNC_MUTEX) add_definitions(-DATOMIC_USE_SRT_SYNC_MUTEX=1) @@ -264,7 +264,7 @@ endif() set(USE_ENCLIB "${USE_ENCLIB}" CACHE STRING "The crypto library that SRT uses") set_property(CACHE USE_ENCLIB PROPERTY STRINGS "openssl" "openssl-evp" "gnutls" "mbedtls" "botan") -# Make sure DLLs and executabes go to the same path regardles of subdirectory +# Make sure DLLs and executables go to the same path regardless of subdirectory set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) @@ -1384,7 +1384,7 @@ macro(srt_make_application name) if (LINUX AND ENABLE_RELATIVE_LIBPATH AND NOT srt_libspec_static) # This is only needed on Linux, on Windows (including Cygwin) the library file will - # be placed into the binrary directory anyway. + # be placed into the binary directory anyway. # XXX not sure about Mac. # See this name used already in install(${TARGET_srt} LIBRARY DESTINATION...). set(FORCE_RPATH LINK_FLAGS -Wl,-rpath,.,-rpath,../${CMAKE_INSTALL_LIBDIR} BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH_USE_LINK_PATH TRUE) diff --git a/README.md b/README.md index 16a1d8e084..b0df582af0 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ In live streaming configurations, the SRT protocol maintains a constant end-to-e - [Why SRT?](./docs/misc/why-srt-was-created.md) - A brief history and rationale for SRT by Marc Cymontkowski. - [RTMP vs. SRT: Comparing Latency and Maximum Bandwidth](https://www.haivision.com/resources/white-paper/srt-versus-rtmp/) White Paper. -- [Documentation on GitHub](./docs#documentation-overview) with SRT API documents, features decsriptions, etc. +- [Documentation on GitHub](./docs#documentation-overview) with SRT API documents, features descriptions, etc. - The SRT Protocol Internet Draft: [Datatracker](https://datatracker.ietf.org/doc/draft-sharabayko-srt/) | [Latest Version](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01) | [Latest Working Copy](https://haivision.github.io/srt-rfc/draft-sharabayko-srt.html) | [GitHub Repo](https://github.com/Haivision/srt-rfc) - If you are curious about live streaming using SRT with QUIC datagrams as an alternative to UDP transport, take a look at the following [blog post](https://medium.com/innovation-labs-blog/live-streaming-using-srt-with-quic-datagrams-7896f7ce7bf3?source=friends_link&sk=d0a00e79861d89673e27a04260f279b5) on Medium. diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index 932bcd1423..3468c78b58 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -899,7 +899,7 @@ void TestLogHandler(void* opaque, int level, const char* file, int line, const c #ifdef _MSC_VER // That's something weird that happens on Microsoft Visual Studio 2013 - // Trying to keep portability, while every version of MSVS is a different plaform. + // Trying to keep portability, while every version of MSVS is a different platform. // On MSVS 2015 there's already a standard-compliant snprintf, whereas _snprintf // is available on backward compatibility and it doesn't work exactly the same way. #define snprintf _snprintf diff --git a/apps/srt-tunnel.cpp b/apps/srt-tunnel.cpp index 90bd43eae9..9ca3e2cc28 100644 --- a/apps/srt-tunnel.cpp +++ b/apps/srt-tunnel.cpp @@ -387,7 +387,7 @@ void Engine::Worker() throw Medium::ReadEOF(""); case Medium::RD_AGAIN: - // Theoreticall RD_AGAIN should not be reported + // Theoretically RD_AGAIN should not be reported // because it should be taken care of internally by // repeated sending - unless we get m_broken set. // If it is, however, it should be handled just like error. diff --git a/common/win/unistd.h b/common/win/unistd.h index 9c61cad8aa..063f2a086e 100644 --- a/common/win/unistd.h +++ b/common/win/unistd.h @@ -3,7 +3,7 @@ /* This file intended to serve as a drop-in replacement for * unistd.h on Windows - * Please add functionality as neeeded + * Please add functionality as needed */ #include diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index c550845a71..16b1f41454 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -1651,7 +1651,7 @@ will be able to retrieve this stream ID from the socket that is returned from `srt_accept` (for a connected socket with that stream ID). You usually use SET on the socket used for `srt_connect`, and GET on the socket retrieved from `srt_accept`. This string can be used completely free-form. However, it's highly -recommended to follow the [SRT Access Control (Stream ID) Guidlines](../features/access-control.md). +recommended to follow the [SRT Access Control (Stream ID) Guidelines](../features/access-control.md). - As this uses internally the `std::string` type, there are additional functions for it in the legacy/C++ API (udt.h): `srt::setstreamid` and `srt::getstreamid`. diff --git a/docs/API/statistics.md b/docs/API/statistics.md index bc34ecca8b..0e4b2d31e8 100644 --- a/docs/API/statistics.md +++ b/docs/API/statistics.md @@ -182,7 +182,7 @@ If the packet `s` is received out of order (`s < next_exp`), the statistic is no Note that only original (not retransmitted) SRT DATA packets are taken into account. Refer to [pktRcvRetransTotal](#pktRcvRetransTotal) for the formula for obtaining the total number of lost retransmitted packets. -In SRT v1.4.0, v1.4.1, the `pktRcvLossTotal` statistic includes packets that failed to be decrypted. To receive the number of presently missing packets, substract [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) from the current one. This is going to be fixed in SRT v.1.5.0. +In SRT v1.4.0, v1.4.1, the `pktRcvLossTotal` statistic includes packets that failed to be decrypted. To receive the number of presently missing packets, subtract [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) from the current one. This is going to be fixed in SRT v.1.5.0. #### pktRetransTotal @@ -245,7 +245,7 @@ Packets may be dropped conditionally when both `SRTO_TSBPDMODE` and `SRTO_TLPKTD #### pktRcvUndecryptTotal The total number of packets that failed to be decrypted at the receiver side. Available for receiver. -The statistic also counts unencrypted packets that were expected to be uncrypted on a secured connection (see [SRTO_KM_S_SECURED](API-socket-options.md#srt_km_state)) +The statistic also counts unencrypted packets that were expected to be unencrypted on a secured connection (see [SRTO_KM_S_SECURED](API-socket-options.md#srt_km_state)) and hence dropped as not encrypted (undecrypted). #### pktSndFilterExtraTotal @@ -657,7 +657,7 @@ Instantaneous (current) value of `pktRcvBuf`, expressed in bytes, including payl The timespan (msec) of acknowledged packets in the receiver's buffer. Receiver side. -If TSBPD mode is enabled (defualt for **live mode**), +If TSBPD mode is enabled (default for **live mode**), a packet can be acknowledged, but not yet ready to play. This range includes all packets regardless of whether they are ready to play or not. diff --git a/docs/apps/srt-file-transmit.md b/docs/apps/srt-file-transmit.md index 2bf3b11159..04afb1c452 100644 --- a/docs/apps/srt-file-transmit.md +++ b/docs/apps/srt-file-transmit.md @@ -40,7 +40,7 @@ If you use sender in listener mode, then you start it with specifying either the full filename path, or only the directory where the file is located; in the latter case the filename will be tried from the `streamid` option extracted from the connected socket (as set by the other side's caller). If the full filename was -specified, it must match the rootname extraced from this option, or otherwise +specified, it must match the rootname extracted from this option, or otherwise transmission will not be done. If you use receiver in listener mode, then you start it with specifying either diff --git a/docs/build/build-win.md b/docs/build/build-win.md index e8a1e63517..1a7d7a754c 100644 --- a/docs/build/build-win.md +++ b/docs/build/build-win.md @@ -2,33 +2,34 @@ -- [1. Prerequisites](#1-prerequisites) - - [1.1. Build Tool Dependencies](#11-build-tool-dependencies) - - [1.2. External Library Dependencies](#12-external-library-dependencies) - - [1.2.1. Cryptographic Library](#121-cryptographic-library) - - [1.2.2. Threading Library](#122-threading-library) - - [1.3. Package Managers](#13-package-managers) - - [1.3.1. VCpkg Packet Manager (optional)](#131-vcpkg-packet-manager-optional) - - [1.3.2. NuGet Manager (optional)](#132-nuget-manager-optional) -- [2. Preparing Dependencies](#2-preparing-dependencies) - - [2.1. Cryptographic Library](#21-cryptographic-library) - - [2.1.1. Install OpenSSL](#211-install-openssl) - - [2.1.1.1. Using vcpkg](#2111-using-vcpkg) - - [2.1.1.2. Using Installer](#2112-using-installer-windows) - - [2.1.1.3. Build from Sources](#2113-build-from-sources) - - [2.1.2. Install MbedTLS](#212-install-mbedtls) - - [2.1.3. Install LibreSSL](#213-install-libressl) - - [2.2. Threading Library](#22-threading-library) - - [2.2.1. Using C++11 Threading](#221-using-c11-threading) - - [2.2.2. Building PThreads](#222-building-pthreads) - - [2.2.2.1. Using vcpkg](#2221-using-vcpkg) - - [2.2.2.2. Using NuGet](#2222-using-nuget) - - [2.2.2.3. Build pthreads4w from Sources](#2223-build-pthreads4w-from-sources) - - [2.2.2.4. Build pthread-win32 from Sources](#2224-build-pthread-win32-from-sources) -- [3. Building SRT](#3-building-srt) - - [3.1. Cloning the Source Code](#31-cloning-the-source-code) - - [3.2. Generate Build Files](#32-generate-build-files) - - [3.3. Build SRT](#33-build-srt) +- [Building SRT on Windows](#building-srt-on-windows) + - [1. Prerequisites](#1-prerequisites) + - [1.1. Build Tool Dependencies](#11-build-tool-dependencies) + - [1.2. External Library Dependencies](#12-external-library-dependencies) + - [1.2.1. Cryptographic Library](#121-cryptographic-library) + - [1.2.2. Threading Library](#122-threading-library) + - [1.3. Package Managers](#13-package-managers) + - [1.3.1. VCpkg Packet Manager (optional)](#131-vcpkg-packet-manager-optional) + - [1.3.2. NuGet Manager (optional)](#132-nuget-manager-optional) + - [2. Preparing Dependencies](#2-preparing-dependencies) + - [2.1 Cryptographic Library](#21-cryptographic-library) + - [2.1.1. Install OpenSSL](#211-install-openssl) + - [2.1.1.1. Using vcpkg](#2111-using-vcpkg) + - [2.1.1.2. Using Installer (Windows)](#2112-using-installer-windows) + - [2.1.1.3. Build from Sources](#2113-build-from-sources) + - [2.1.2. Install MbedTLS](#212-install-mbedtls) + - [2.1.3. Install LibreSSL](#213-install-libressl) + - [2.2. Threading Library](#22-threading-library) + - [2.2.1. Using C++11 Threading](#221-using-c11-threading) + - [2.2.2. Building PThreads](#222-building-pthreads) + - [2.2.2.1. Using vcpkg](#2221-using-vcpkg) + - [2.2.2.2. Using NuGet](#2222-using-nuget) + - [2.2.2.3. Build pthreads4w from Sources](#2223-build-pthreads4w-from-sources) + - [2.2.2.4. Build pthread-win32 from Sources](#2224-build-pthread-win32-from-sources) + - [3. Building SRT](#3-building-srt) + - [3.1. Cloning the Source Code](#31-cloning-the-source-code) + - [3.2. Generate Build Files](#32-generate-build-files) + - [3.3. Build SRT](#33-build-srt) @@ -296,7 +297,7 @@ In case vcpkg was used to build pthreads or OpenSSL, provide: - `-DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\\scripts\\buildsystems\\vcpkg.cmake` -In case NuGet was used to get pre-built pthreads libray, provide: +In case NuGet was used to get pre-built pthreads library, provide: - `-DPTHREAD_INCLUDE_DIR` - `-DPTHREAD_LIBRARY` diff --git a/docs/features/access-control.md b/docs/features/access-control.md index 6375802b2b..297295fd61 100644 --- a/docs/features/access-control.md +++ b/docs/features/access-control.md @@ -82,9 +82,9 @@ password. - `r`: **Resource Name** identifies the name of the resource and facilitates selection should the listener party be able to serve multiple resources. - `h`: **Host Name** identifies the hostname of the resource. For example, -to request a stream with the URI `somehost.com/videos/querry.php?vid=366` the +to request a stream with the URI `somehost.com/videos/query.php?vid=366` the `hostname` field should have `somehost.com`, and the resource name can have -`videos/querry.php?vid=366` or simply `366`. Note that this is still a key to be +`videos/query.php?vid=366` or simply `366`. Note that this is still a key to be specified explicitly. Support tools that apply simplifications and URI extraction are expected to insert only the host portion of the URI here. - `s`: **Session ID** is a temporary resource identifier negotiated with diff --git a/docs/features/bonding-main-backup.md b/docs/features/bonding-main-backup.md index b56df7851a..0e0190fdfd 100644 --- a/docs/features/bonding-main-backup.md +++ b/docs/features/bonding-main-backup.md @@ -26,7 +26,7 @@ Transmission happens over the main link until it is considered broken or is pres ### Sensitivity Levels -The only sensitivity level implemented at the moment is a **pre-emptive switch** to a backup path from an unstable main path before it breaks. The goal is to predict an upcoming link breakage before it happens, and to be ready to switch to an activated backup link while losing as few packets as possible. +The only sensitivity level implemented at the moment is a **preemptive switch** to a backup path from an unstable main path before it breaks. The goal is to predict an upcoming link breakage before it happens, and to be ready to switch to an activated backup link while losing as few packets as possible. An additional sensitivity level (**handover switch**) may be added in the future for cases where low latency and packet loss is not critical. The switch would take place once the link is actually broken, without trying to predict it thereby reducing processing overhead. Since the main path in this case is already broken, there would be a delay associated with activating the backup path resulting in a discontinuity in streaming (data loss during the switch). diff --git a/docs/features/handshake.md b/docs/features/handshake.md index f8ce9b5253..4b2daa0771 100644 --- a/docs/features/handshake.md +++ b/docs/features/handshake.md @@ -7,31 +7,32 @@ Last updated: 2018-06-28 **Contents** -- [Overview](#overview) -- [Short Introduction to SRT Packet Structure](#short-introduction-to-srt-packet-structure) -- [Handshake Structure](#handshake-structure) -- [The "UDT Legacy" and "SRT Extended" Handshakes](#the-udt-legacy-and-srt-extended-handshakes) - - [UDT Legacy Handshake](#udt-legacy-handshake) - - [Initiator and Responder](#initiator-and-responder) - - [The Request Type Field](#the-request-type-field) - - [The Type Field](#the-type-field) -- [The Caller-Listener Handshake](#the-caller-listener-handshake) - - [The Induction Phase](#the-induction-phase) - - [The Conclusion Phase](#the-conclusion-phase) -- [The Rendezvous Handshake](#the-rendezvous-handshake) - - [HSv4 Rendezvous Process](#hsv4-rendezvous-process) - - [HSv5 Rendezvous Process](#hsv5-rendezvous-process) - - [Serial Handshake Flow](#serial-handshake-flow) - - [Parallel Handshake Flow](#parallel-handshake-flow) - - [Rendezvous Between Different Versions](#rendezvous-between-different-versions) -- [The SRT Extended Handshake](#the-srt-extended-handshake) - - [HSv4 Extended Handshake Process](#hsv4-extended-handshake-process) - - [HSv5 Extended Handshake Process](#hsv5-extended-handshake-process) - - [SRT Extension Commands](#srt-extension-commands) - - [HSREQ and HSRSP](#hsreq-and-hsrsp) - - [KMREQ and KMRSP](#kmreq-and-kmrsp) - - [Congestion controller](#congestion-controller) - - [Stream ID (SID)](#stream-id-sid) +- [SRT Handshake](#srt-handshake) + - [Overview](#overview) + - [Short Introduction to SRT Packet Structure](#short-introduction-to-srt-packet-structure) + - [Handshake Structure](#handshake-structure) + - [The "UDT Legacy" and "SRT Extended" Handshakes](#the-udt-legacy-and-srt-extended-handshakes) + - [UDT Legacy Handshake](#udt-legacy-handshake) + - [Initiator and Responder](#initiator-and-responder) + - [The Request Type Field](#the-request-type-field) + - [The Type Field](#the-type-field) + - [The Caller-Listener Handshake](#the-caller-listener-handshake) + - [The Induction Phase](#the-induction-phase) + - [The Conclusion Phase](#the-conclusion-phase) + - [The Rendezvous Handshake](#the-rendezvous-handshake) + - [HSv4 Rendezvous Process](#hsv4-rendezvous-process) + - [HSv5 Rendezvous Process](#hsv5-rendezvous-process) + - [Serial Handshake Flow](#serial-handshake-flow) + - [Parallel Handshake Flow](#parallel-handshake-flow) + - [Rendezvous Between Different Versions](#rendezvous-between-different-versions) + - [The SRT Extended Handshake](#the-srt-extended-handshake) + - [HSv4 Extended Handshake Process](#hsv4-extended-handshake-process) + - [HSv5 Extended Handshake Process](#hsv5-extended-handshake-process) + - [SRT Extension Commands](#srt-extension-commands) + - [HSREQ and HSRSP](#hsreq-and-hsrsp) + - [KMREQ and KMRSP](#kmreq-and-kmrsp) + - [Congestion controller](#congestion-controller) + - [Stream ID (SID)](#stream-id-sid) ## Overview @@ -1552,7 +1553,7 @@ the user's choice that can be passed from the Caller to the Listener. The symbol for this extension is `SRT_CMD_SID`. The extension block for this extension is encoded the same way as described -for Congestion Controler above. +for Congestion Controller above. The Stream ID is a string of up to 512 characters that a Caller can pass to a Listener (it's actually passed from an Initiator to a Responder in general, but @@ -1561,4 +1562,4 @@ application should set it on a Caller socket using the `SRTO_STREAMID` option. Upon connection, the accepted socket on the Listener side will have exactly the same value set, and it can be retrieved using the same option. For more details about the prospective use of this option, please refer to the -[SRT API Socket Options](../API/API-socket-options.md) and [SRT Access Control (Stream ID) Guidlines](access-control.md). +[SRT API Socket Options](../API/API-socket-options.md) and [SRT Access Control (Stream ID) Guidelines](access-control.md). diff --git a/docs/features/live-streaming.md b/docs/features/live-streaming.md index 434f4c2aad..659e806ef3 100644 --- a/docs/features/live-streaming.md +++ b/docs/features/live-streaming.md @@ -33,7 +33,7 @@ However, the transmission must still satisfy the Live Streaming Requirements. The MPEG-TS stream, as a good example, consists of Frames. Each Frame is a portion of data assigned to a particular stream (usually you have -multipe streams interleaved, at least video and audio). The video stream always has +multiple streams interleaved, at least video and audio). The video stream always has its playing speed expressed in fps (frames per second) units. This value maps to a duration for one video frame. So, for example, 60 fps means that one video frame should be "displayed" for a duration of 1/60 of a second. You can diff --git a/docs/features/socket-groups.md b/docs/features/socket-groups.md index cef0ee98ef..359c2aae15 100644 --- a/docs/features/socket-groups.md +++ b/docs/features/socket-groups.md @@ -113,7 +113,7 @@ time to realize that the link might be broken and time required for resending all unacknowledged packets, before the time to play comes for the received packets. If this time isn't met, packets will be dropped and your advantage of having the backup link might be impaired. According to the tests on the -local network it turns out that the most sensible unstability timeout is about +local network it turns out that the most sensible instability timeout is about 50ms, while normally ACK timeout is 30ms, so extra 100ms latency tax seems to be an absolute minimum. @@ -431,7 +431,7 @@ to maintain, whether the list is constant or can be dynamically modified, or whether a dead link is not to be revived by some reason - all these things are out of the interest of the library. It's up to the application to decide when and by what reason the connection is to be established. All that your -application has to do is to monitor the conenctions (that is, be conscious +application has to do is to monitor the connections (that is, be conscious about that particular links are up and running or get broken) and take appropriate action in response. @@ -514,7 +514,7 @@ by `srt_sendmsg2` or `srt_recvmsg2`. This is very simple. Call the sending function (recommended is `srt_sendmsg2`) to send the data, passing group ID in the place of socket ID. By recognizing the ID as group ID, this will be resolved internally as sending the payload -by approprately using the bonded links as defined for particular group type. +by appropriately using the bonded links as defined for particular group type. The current implementation for most of the bonding groups (broadcast and backup) relies on synchronizing the sequence numbers of the packets so that @@ -539,7 +539,7 @@ This is also simple from the user's perspective. Simply call the reading function, such as `srt_recvmsg2`, passing the group ID instead of socket ID. -Also the dillema of blocking and nonblocking is the same thing. With blocking +Also the dilemma of blocking and nonblocking is the same thing. With blocking mode (`SRTO_RCVSYN`), simply wait until your payload is retrieved. The internal group reading facility will take care that you get your payload in the right order and at the time to play, and the redundant payloads retrieved over diff --git a/examples/fork-test/srt_server.c b/examples/fork-test/srt_server.c index a680f1966b..168b9c1678 100644 --- a/examples/fork-test/srt_server.c +++ b/examples/fork-test/srt_server.c @@ -38,7 +38,7 @@ int run(char *command) { getpid(), grandchild_pid); // Intermediate process exits immediately sleep(10); - printf("[PARENT] exitting\n"); + printf("[PARENT] exiting\n"); exit(0); } // Grandchild process diff --git a/haicrypt/cryspr-botan.h b/haicrypt/cryspr-botan.h index 704d29695e..6de59c8cb0 100644 --- a/haicrypt/cryspr-botan.h +++ b/haicrypt/cryspr-botan.h @@ -46,7 +46,7 @@ typedef struct botan_aes_context #define CRYSPR_HAS_PBKDF2 1 /* -#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. +#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK It is set from the keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_XXX. diff --git a/haicrypt/cryspr-gnutls.c b/haicrypt/cryspr-gnutls.c index 95eaf2856c..b8cecf38e5 100644 --- a/haicrypt/cryspr-gnutls.c +++ b/haicrypt/cryspr-gnutls.c @@ -187,7 +187,7 @@ CRYSPR_methods *crysprGnuTLS(void) crysprGnuTLS_methods.aes_ecb_cipher = crysprGnuTLS_AES_EcbCipher; #endif #if !CRYSPR_HAS_PBKDF2 - crysprGnuTLS_methods.sha1_msg_digest= crysprGnuTLS_SHA1_MsgDigest; //Onl required if using generic KmPbkdf2 + crysprGnuTLS_methods.sha1_msg_digest= crysprGnuTLS_SHA1_MsgDigest; //Only required if using generic KmPbkdf2 #endif //--Crypto Session (Top API) diff --git a/haicrypt/cryspr-gnutls.h b/haicrypt/cryspr-gnutls.h index 78f7c86ec9..53ae72c586 100644 --- a/haicrypt/cryspr-gnutls.h +++ b/haicrypt/cryspr-gnutls.h @@ -48,9 +48,9 @@ written by #define CRYSPR_HAS_PBKDF2 1 /* -#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. +#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK -It is set from hte keystring through CRYSPR_methods.aes_set_key and passed +It is set from the keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_XXX. */ typedef struct aes_ctx CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ diff --git a/haicrypt/cryspr-mbedtls.c b/haicrypt/cryspr-mbedtls.c index a961cca61e..5b3d2db780 100644 --- a/haicrypt/cryspr-mbedtls.c +++ b/haicrypt/cryspr-mbedtls.c @@ -246,7 +246,7 @@ CRYSPR_methods *crysprMbedtls(void) crysprMbedtls_methods.aes_ecb_cipher = crysprMbedtls_AES_EcbCipher; #endif #if !CRYSPR_HAS_PBKDF2 - crysprMbedtls_methods.sha1_msg_digest= crysprMbedtls_SHA1_MsgDigest; //Onl required if using generic KmPbkdf2 + crysprMbedtls_methods.sha1_msg_digest= crysprMbedtls_SHA1_MsgDigest; //Only required if using generic KmPbkdf2 #endif //--Crypto Session (Top API) diff --git a/haicrypt/cryspr-mbedtls.h b/haicrypt/cryspr-mbedtls.h index f62b32e486..aff218af28 100644 --- a/haicrypt/cryspr-mbedtls.h +++ b/haicrypt/cryspr-mbedtls.h @@ -50,7 +50,7 @@ written by #endif /* -#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. +#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK It is set from the keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_XXX. diff --git a/haicrypt/cryspr-openssl-evp.c b/haicrypt/cryspr-openssl-evp.c index 3e87e9a5ef..02cfb590bf 100644 --- a/haicrypt/cryspr-openssl-evp.c +++ b/haicrypt/cryspr-openssl-evp.c @@ -385,7 +385,7 @@ CRYSPR_methods* crysprOpenSSL_EVP(void) #if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) /* AES-ECB only required if cryspr has no AES-CTR and no AES KeyWrap */ /* OpenSSL has both AESCTR and AESKWRP and the AESECB wrapper is only used - to test the falback methods */ + to test the fallback methods */ crysprOpenSSL_EVP_methods.aes_ecb_cipher = crysprOpenSSL_EVP_AES_EcbCipher; #endif #if !CRYSPR_HAS_PBKDF2 diff --git a/haicrypt/cryspr-openssl-evp.h b/haicrypt/cryspr-openssl-evp.h index a1fb0b925f..3e2932e8bc 100644 --- a/haicrypt/cryspr-openssl-evp.h +++ b/haicrypt/cryspr-openssl-evp.h @@ -55,9 +55,9 @@ written by #define CRYSPR_HAS_PBKDF2 1 /* Define to 1 if CRYSPR has Password-based Key Derivaion Function 2 */ /* -#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. +#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK -It is set from hte keystring through CRYSPR_methods.aes_set_key and passed +It is set from the keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_*. */ typedef EVP_CIPHER_CTX CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ diff --git a/haicrypt/cryspr-openssl.c b/haicrypt/cryspr-openssl.c index 87eb535ec5..2839d0e125 100644 --- a/haicrypt/cryspr-openssl.c +++ b/haicrypt/cryspr-openssl.c @@ -207,7 +207,7 @@ CRYSPR_methods *crysprOpenSSL(void) #if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP) /* AES-ECB only required if cryspr has no AES-CTR and no AES KeyWrap */ /* OpenSSL has both AESCTR and AESKWRP and the AESECB wrapper is only used - to test the falback methods */ + to test the fallback methods */ crysprOpenSSL_methods.aes_ecb_cipher = crysprOpenSSL_AES_EcbCipher; #endif #if !CRYSPR_HAS_PBKDF2 diff --git a/haicrypt/cryspr-openssl.h b/haicrypt/cryspr-openssl.h index 75a52518f3..2ebfe7528f 100644 --- a/haicrypt/cryspr-openssl.h +++ b/haicrypt/cryspr-openssl.h @@ -52,9 +52,9 @@ written by #define CRYSPR_HAS_PBKDF2 1 /* Define to 1 if CRYSPR has Password-based Key Derivaion Function 2 */ /* -#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object. +#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object. This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK -It is set from hte keystring through CRYSPR_methods.aes_set_key and passed +It is set from the keystring through CRYSPR_methods.aes_set_key and passed to CRYSPR_methods.aes_*. */ typedef AES_KEY CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */ diff --git a/haicrypt/cryspr.h b/haicrypt/cryspr.h index 42426f4223..febde4ae98 100644 --- a/haicrypt/cryspr.h +++ b/haicrypt/cryspr.h @@ -148,7 +148,7 @@ typedef struct tag_CRYSPR_methods { /* * km_setkey: - * Set the Key Encypting Key for Wrap (Encryption) or UnWrap (Decryption). + * Set the Key Encrypting Key for Wrap (Encryption) or UnWrap (Decryption). * Context (ctx) tells if it's for Wrap or Unwrap * A Context flags (ctx->flags) also tells if this is for wrap(encryption) or unwrap(decryption) context (HCRYPT_CTX_F_ENCRYPT) */ diff --git a/haicrypt/hcrypt.c b/haicrypt/hcrypt.c index a2b81d8321..62fb6dea02 100644 --- a/haicrypt/hcrypt.c +++ b/haicrypt/hcrypt.c @@ -91,7 +91,7 @@ static hcrypt_Session* sHaiCrypt_PrepareHandle(const HaiCrypt_Cfg* cfg, HaiCrypt crypto->km.refresh_rate = cfg->km_refresh_rate_pkt; crypto->km.pre_announce = cfg->km_pre_announce_pkt; - /* Indentify each context */ + /* Identify each context */ crypto->ctx_pair[0].flags = HCRYPT_MSG_F_eSEK | (tx ? HCRYPT_CTX_F_ENCRYPT : 0); crypto->ctx_pair[1].flags = HCRYPT_MSG_F_oSEK | (tx ? HCRYPT_CTX_F_ENCRYPT : 0); /* Point to each other */ @@ -307,7 +307,7 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl timerclear(&cryptoClone->km.tx_last); /* Adjust pointers pointing into cryproSrc after copy - msg_info and crysprs are extern statics so this is ok*/ + msg_info and crysprs are extern static so this is ok*/ cryptoClone->ctx_pair[0].alt = &cryptoClone->ctx_pair[1]; cryptoClone->ctx_pair[1].alt = &cryptoClone->ctx_pair[0]; @@ -327,7 +327,7 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl } /* Clear salt to force later regeneration of KEK as AES decrypting key, - copyed one is encrypting key */ + copied one is encrypting key */ cryptoClone->ctx_pair[0].flags &= ~HCRYPT_CTX_F_ENCRYPT; cryptoClone->ctx_pair[1].flags &= ~HCRYPT_CTX_F_ENCRYPT; memset(cryptoClone->ctx_pair[0].salt, 0, sizeof(cryptoClone->ctx_pair[0].salt)); diff --git a/haicrypt/hcrypt_ctx_tx.c b/haicrypt/hcrypt_ctx_tx.c index e66c9497b9..75f861c5b1 100644 --- a/haicrypt/hcrypt_ctx_tx.c +++ b/haicrypt/hcrypt_ctx_tx.c @@ -110,7 +110,7 @@ int hcryptCtx_Tx_CloneKey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const hcrypt_ const hcrypt_Ctx* ctxSrc = cryptoSrc->ctx; if (!ctxSrc) { - /* Probbly the context is not yet completely initialized, so + /* Probably the context is not yet completely initialized, so * use blindly the first context from the pair */ ctxSrc = &cryptoSrc->ctx_pair[0]; diff --git a/haicrypt/hcrypt_msg.h b/haicrypt/hcrypt_msg.h index c35ddd046d..0249975d27 100644 --- a/haicrypt/hcrypt_msg.h +++ b/haicrypt/hcrypt_msg.h @@ -42,7 +42,7 @@ typedef uint32_t hcrypt_Pki; #define HCRYPT_MSG_PT_MS 1 /* Media stream */ #define HCRYPT_MSG_PT_KM 2 /* Keying Material */ -#define HCRYPT_MSG_PT_RESV7 7 /* Reserved to dicriminate MPEG-TS packet (SyncByte=0x47) */ +#define HCRYPT_MSG_PT_RESV7 7 /* Reserved to discriminate MPEG-TS packet (SyncByte=0x47) */ #define HCRYPT_MSG_F_eSEK 0x01 /* Even Stream Encrypting Key */ diff --git a/scripts/codespell/codespell.cfg b/scripts/codespell/codespell.cfg new file mode 100644 index 0000000000..a338746099 --- /dev/null +++ b/scripts/codespell/codespell.cfg @@ -0,0 +1,14 @@ +[codespell] + +# Enable built-in dictionaries/rules. +# See more details for https://github.com/codespell-project/codespell/tree/main/codespell_lib/data. +builtin = clear,rare,informal,code + +# Ignore words listed in this file. +ignore-words = ./scripts/codespell/codespell_whitelist.txt + +# Add custom dictionary file. +dictionary = ./scripts/codespell/codespell_dictionary.txt,- + +# Skip checking files or directories. +skip = ./build/*,./.git/* diff --git a/scripts/codespell/codespell_dictionary.txt b/scripts/codespell/codespell_dictionary.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/codespell/codespell_whitelist.txt b/scripts/codespell/codespell_whitelist.txt new file mode 100644 index 0000000000..6e845b75cd --- /dev/null +++ b/scripts/codespell/codespell_whitelist.txt @@ -0,0 +1,30 @@ +AnyOther +ANS +CAS +connectIn +deque +dur +endcode +fo +inout +kinda +lene +lets +lite +LITE +mane +mut +MUSL +nd +numer +od +RO +stdio +stoll +snd +Snd +SND +uint +unsecure +UNSECURE +ws diff --git a/scripts/generate-logging-defs.tcl b/scripts/generate-logging-defs.tcl index a86ab9a62b..66622675d8 100755 --- a/scripts/generate-logging-defs.tcl +++ b/scripts/generate-logging-defs.tcl @@ -36,7 +36,7 @@ set loggers { RSRC rs 5 "System resource allocation and management" CONGEST cc 7 "Congestion control module" PFILTER pf 8 "Packet filter module" - API_CTRL ac 11 "API part for socket and library managmenet" + API_CTRL ac 11 "API part for socket and library management" QUE_CTRL qc 13 "Queue control activities" EPOLL_UPD ei 16 "EPoll, internal update activities" @@ -140,7 +140,7 @@ proc GenerateModelForSrtH {} { continue } - # End of generated section. Switch back to pass-thru. + # End of generated section. Switch back to pass-through. # First fill the gap append contents "\n\$entries\n\n" diff --git a/scripts/googletest.cmake b/scripts/googletest.cmake index 57e5c937a5..96115ddbf0 100644 --- a/scripts/googletest.cmake +++ b/scripts/googletest.cmake @@ -24,7 +24,7 @@ macro(fetch_googletest _download_module_path _download_root) ${_download_root} ) - # adds the targers: gtest, gtest_main, gmock, gmock_main + # adds the targets: gtest, gtest_main, gmock, gmock_main add_subdirectory( ${_download_root}/googletest-src ${_download_root}/googletest-build diff --git a/scripts/srt-dev.lua b/scripts/srt-dev.lua index a90ac487d9..efb875de5b 100644 --- a/scripts/srt-dev.lua +++ b/scripts/srt-dev.lua @@ -65,7 +65,7 @@ local msg_type_select = { [0x7FFF] = "[Message Extension Type]" } fields.msg_type = ProtoField.uint16("srt_dev.msg_type", "Message Type", base.HEX, msg_type_select, 0x7FFF) -fields.msg_ext_type = ProtoField.uint16("srt_dev.msg_ext_type", "Message Extented Type", base.DEC) +fields.msg_ext_type = ProtoField.uint16("srt_dev.msg_ext_type", "Message Extended Type", base.DEC) local flag_state_select = { [0] = "Unset", @@ -281,7 +281,7 @@ function srt_dev.dissector (tvb, pinfo, tree) subtree:add(fields.ext_fld, tvb(offset, 2)):append_text(" [HSv5 MAGIC]") else -- Extension Field is HS_EXT_prefix - -- The define is in fiel handshake.h + -- The define is in file handshake.h local ext_fld_tree = subtree:add(fields.ext_fld_tree, tvb(offset, 2)) local str_table = { " [" } ext_fld_tree:add(fields.hsreq, tvb(offset, 2)) diff --git a/scripts/srt-proxy.py b/scripts/srt-proxy.py index ad2e8ac347..27bbed0125 100755 --- a/scripts/srt-proxy.py +++ b/scripts/srt-proxy.py @@ -58,7 +58,7 @@ def start(self): #Modify the NAK processedData = data # NAK - if(data[1] == 0x03 and len(data) == 24 and self.PACKETBREAK_NAK != 0): # IS A NAK, This should check for a 1 in the first byte aswell. + if(data[1] == 0x03 and len(data) == 24 and self.PACKETBREAK_NAK != 0): # IS A NAK, This should check for a 1 in the first byte as well. print("Modifying NAK.") newBytes = b"\xff\xff\xff\xff" processedData = data[:20] + newBytes + data[20 + len(newBytes):] @@ -94,7 +94,7 @@ def start(self): # Receive from server data, addr = sock.recvfrom(65535) - # Drop a sequence of data packets (as to atleast 3 to trigger an NAK with a range) + # Drop a sequence of data packets (as to at least 3 to trigger an NAK with a range) if len(data) == 1332: # Packet with data self.sentPacketCounter += 1 if (self.PACKETBREAK_NAK != 0 and self.sentPacketCounter in range(self.PACKETBREAK_NAK, self.PACKETBREAK_NAK+5)): diff --git a/scripts/visionOS.cmake b/scripts/visionOS.cmake index 0f0f60b69e..cfb6006738 100644 --- a/scripts/visionOS.cmake +++ b/scripts/visionOS.cmake @@ -9,7 +9,7 @@ # OS - the default, used to build for Vision Pro physical device, which have an arm arch. # SIMULATOR - used to build for the Simulator platforms, which have an x86 arch. # -# VISIONOS_ARCH = arm64 (default for OS), x86_64 (addiitonal support for SIMULATOR64) +# VISIONOS_ARCH = arm64 (default for OS), x86_64 (additional support for SIMULATOR64) # # CMAKE_VISIONOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder # By default this location is automatcially chosen based on the VISIONOS_PLATFORM value above. diff --git a/scripts/win-installer/README.md b/scripts/win-installer/README.md index cab9d76619..135071b39b 100644 --- a/scripts/win-installer/README.md +++ b/scripts/win-installer/README.md @@ -35,7 +35,7 @@ Optional parameters: - `-Version name` : Use the specified string as version number for libsrt. By default, if the current commit has a tag, use that tag (initial "v" removed, for instance - `1.4.3`). Otherwise, the defaut version is a detailed version number (most + `1.4.3`). Otherwise, the default version is a detailed version number (most recent version, number of commits since then, short commit SHA, for instance `1.4.3-32-g22cc924`). Use that option if necessary to specify some other non-standard form of version string. diff --git a/scripts/win-installer/build-win-installer.ps1 b/scripts/win-installer/build-win-installer.ps1 index b6621550c5..f486eac0ba 100644 --- a/scripts/win-installer/build-win-installer.ps1 +++ b/scripts/win-installer/build-win-installer.ps1 @@ -18,7 +18,7 @@ Use the specified string as version number from libsrt. By default, if the current commit has a tag, use that tag (initial 'v' removed). Otherwise, - the defaut version is a detailed version number (most recent version, number + the default version is a detailed version number (most recent version, number of commits since then, short commit SHA). .PARAMETER NoBuild diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 0b96a33bcd..9bdc8a71cb 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -329,7 +329,7 @@ void srt::CUDTUnited::cleanupAllSockets() void srt::CUDTUnited::closeAllSockets() { // remove all sockets and multiplexers - HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock..."); + HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquiring control lock..."); { // Pre-closing: run over all open sockets and close them. @@ -870,10 +870,10 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, g->m_bConnected = true; } - // XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily, + // XXX PROBLEM!!! These events are subscribed here so that this is done once, lazily, // but groupwise connections could be accepted from multiple listeners for the same group! // m_listener MUST BE A CONTAINER, NOT POINTER!!! - // ALSO: Maybe checking "the same listener" is not necessary as subscruption may be done + // ALSO: Maybe checking "the same listener" is not necessary as subscription may be done // multiple times anyway? if (!g->m_listener) { @@ -1613,7 +1613,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " deleted in process"); // Someone deleted the socket in the meantime? // Unlikely, but possible in theory. - // Don't delete anyhting - it's alreay done. + // Don't delete anything - it's already done. continue; } @@ -1659,7 +1659,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i } } - // XXX This should be reenabled later, this should + // XXX This should be re-enabled later, this should // be probably still in use to exchange information about // packets asymmetrically lost. But for no other purpose. /* @@ -1789,7 +1789,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // pending, about to be broken, and srt_connect() is called again? // SHOULD BLOCK the latter, even though is OPEN. // Or, OPEN should be removed from here and srt_connect(_group) - // should block always if the group doesn't have neither 1 conencted link + // should block always if the group doesn't have neither 1 connected link g.m_bOpened = true; g.m_stats.tsLastSampleTime = steady_clock::now(); @@ -1985,7 +1985,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i // function will still be polling sockets to determine the last man // standing. Each one could, however, break by a different reason, // for example, one by timeout, another by wrong passphrase. Check - // the `errorcode` field to determine the reaon for particular link. + // the `errorcode` field to determine the reason for particular link. if (retval == -1) throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); @@ -3321,7 +3321,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons { LOGC(smlog.Error, log << "bind: Wildcard address: " << reqaddr.str() - << " conflicts with existting IP binding: " << mux_addr.str()); + << " conflicts with existing IP binding: " << mux_addr.str()); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } // If this is bound to a certain address, AND: @@ -3639,7 +3639,7 @@ SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt) srt::sync::ExclusiveLock globlock(uglobal().m_GlobControlLock); return newGroup(gt).id(); // Note: potentially, after this function exits, the group - // could be deleted, immediately, from a separate thread (tho + // could be deleted, immediately, from a separate thread (though // unlikely because the other thread would need some handle to // keep it). But then, the first call to any API function would // return invalid ID error. diff --git a/srtcore/api.h b/srtcore/api.h index 789d68d0cf..9f5e560bc1 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -529,7 +529,7 @@ class CUDTUnited /// @brief Checks if channel configuration matches the socket configuration. /// @param cfgMuxer multiplexer configuration. /// @param cfgSocket socket configuration. - /// @return tru if configurations match, false otherwise. + /// @return true if configurations match, false otherwise. static bool channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket); static bool inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket); diff --git a/srtcore/atomic.h b/srtcore/atomic.h index f391be9416..6f510beaca 100644 --- a/srtcore/atomic.h +++ b/srtcore/atomic.h @@ -75,11 +75,11 @@ #define ATOMIC_USE_GCC_INTRINSICS #elif defined(__GNUC__) \ && ( (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) ) - // NOTE: The __atomic_* family of intrisics were introduced in GCC-4.7.0. + // NOTE: The __atomic_* family of intrinsics were introduced in GCC-4.7.0. // NOTE: This follows #if defined(__clang__), because most if, not all, // versions of Clang define __GNUC__ and __GNUC_MINOR__ but often define // them to 4.4 or an even earlier version. Most of the newish versions - // of Clang also support GCC Atomic Intrisics even if they set GCC version + // of Clang also support GCC Atomic Intrinsics even if they set GCC version // macros to <4.7. #define ATOMIC_USE_GCC_INTRINSICS #elif defined(__GNUC__) && !defined(ATOMIC_USE_SRT_SYNC_MUTEX) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index e74d48c075..ae4898eb2d 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -483,7 +483,7 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl) if (!m_tsbpd.isEnabled()) // We need updateFirstReadableOutOfOrder() here even if we are reading inorder, - // incase readable inorder packets are all read out. + // in case readable inorder packets are all read out. updateFirstReadableOutOfOrder(); const int bytes_read = int(dst - data); diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h index c5fca428b7..eac1c7c47e 100644 --- a/srtcore/buffer_rcv.h +++ b/srtcore/buffer_rcv.h @@ -94,7 +94,7 @@ class CRcvBuffer /// @param seqnolo sequence number of the first packet in the dropping range. /// @param seqnohi sequence number of the last packet in the dropping range. /// @param msgno message number to drop (0 if unknown) - /// @param actionOnExisting Should an exising SOLO packet be dropped from the buffer or preserved? + /// @param actionOnExisting Should an existing SOLO packet be dropped from the buffer or preserved? /// @return the number of packets actually dropped. int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting); @@ -162,7 +162,7 @@ class CRcvBuffer int getRcvDataSize() const; /// Get the number of packets, bytes and buffer timespan. - /// Differs from getRcvDataSize() that it counts all packets in the buffer, not only continious. + /// Differs from getRcvDataSize() that it counts all packets in the buffer, not only continuous. int getRcvDataSize(int& bytes, int& timespan) const; struct PacketInfo @@ -221,7 +221,7 @@ class CRcvBuffer /// Return buffer capacity. /// One slot had to be empty in order to tell the difference between "empty buffer" and "full buffer". - /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continiously. + /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continuously. /// TODO: Old receiver buffer capacity returns the actual size. Check for conflicts. size_t capacity() const { diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index ef1bc498cd..2db82a686b 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -202,7 +202,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl) s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); if (i == iNumBlocks - 1) s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); - // NOTE: if i is neither 0 nor size-1, it resuls with PB_SUBSEQUENT. + // NOTE: if i is neither 0 nor size-1, it results with PB_SUBSEQUENT. // if i == 0 == size-1, it results with PB_SOLO. // Packets assigned to one message can be: // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 81dbc4d35f..81f082a1fe 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -387,7 +387,7 @@ void srt::CChannel::setUDPSockOpt() m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)) ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize); #else - // for other systems, if requested is greated than maximum, the maximum value will be automactally used + // for other systems, if requested is greater than maximum, the maximum value will be automatically used if ((-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) || diff --git a/srtcore/common.h b/srtcore/common.h index 15fa39e376..5a53c05e60 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -565,7 +565,7 @@ struct EventSlot // UDT Sequence Number 0 - (2^31 - 1) -// seqcmp: compare two seq#, considering the wraping +// seqcmp: compare two seq#, considering the wrapping // seqlen: length from the 1st to the 2nd seq#, including both // seqoff: offset from the 2nd to the 1st seq# // incseq: increase the seq# by 1 @@ -601,7 +601,7 @@ class CSeqNo return seqcmp(value, other.value) <= 0; } - // circular arithmetics + // circular arithmetic friend int operator-(const CSeqNo& c1, const CSeqNo& c2) { return seqoff(c2.value, c1.value); diff --git a/srtcore/core.cpp b/srtcore/core.cpp index d13221f1cd..6232da9428 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -93,7 +93,7 @@ const SRTSOCKET UDT::INVALID_SOCK = srt::CUDT::INVALID_SOCK; const int UDT::ERROR = srt::CUDT::ERROR; //#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */ -#define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */ +#define SRT_CMD_HSREQ_MINSZ 8 /* Minimum Compatible (1.x.x) packet size (bytes) */ #define SRT_CMD_HSREQ_SZ 12 /* Current version packet size */ #if SRT_CMD_HSREQ_SZ > SRT_CMD_MAXSZ #error SRT_CMD_MAXSZ too small @@ -106,7 +106,7 @@ const int UDT::ERROR = srt::CUDT::ERROR; */ //#define SRT_CMD_HSRSP 2 /* SRT Handshake Response (receiver) */ -#define SRT_CMD_HSRSP_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */ +#define SRT_CMD_HSRSP_MINSZ 8 /* Minimum Compatible (1.x.x) packet size (bytes) */ #define SRT_CMD_HSRSP_SZ 12 /* Current version packet size */ #if SRT_CMD_HSRSP_SZ > SRT_CMD_MAXSZ #error SRT_CMD_MAXSZ too small @@ -297,7 +297,7 @@ void srt::RateMeasurement::pickup(const clock_time& time) // If the container is empty, do not stockpile empty slices. if (m_slices.empty() && slice.bytes == 0) { - HLOGC(bslog.Debug, log << "... NOT COLLECING packet with " << slice.bytes << " bytes to " << m_slices.size() << " total"); + HLOGC(bslog.Debug, log << "... NOT COLLECTING packet with " << slice.bytes << " bytes to " << m_slices.size() << " total"); // Empty-to-empty: DO NOT stockpile. // NOTE: the container is never cleared when it has // any older data, so this situation is only possible: @@ -438,7 +438,7 @@ void srt::CUDT::construct() m_zSndMaxPacketSize = 0; #endif - // Initilize mutex and condition variables. + // Initialize mutex and condition variables. initSynch(); // TODO: Uncomment when the callback is implemented. @@ -520,7 +520,7 @@ srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) srt::CUDT::~CUDT() { - // release mutex/condtion variables + // release mutex/condition variables destroySynch(); // destroy the data structures @@ -1628,7 +1628,7 @@ bool srt::CUDT::createSrtHandshake( // Once you are certain that the version is HSv5, set the enc type flags // to advertise pbkeylen. Otherwise make sure that the old interpretation - // will correctly pick up the type field. PBKEYLEN should be advertized + // will correctly pick up the type field. PBKEYLEN should be advertised // regardless of what URQ stage the handshake is (note that in case of rendezvous // CONCLUSION might be the FIRST MESSAGE EVER RECEIVED by a party). if (w_hs.m_iVersion > HS_VERSION_UDT4) @@ -1784,7 +1784,7 @@ bool srt::CUDT::createSrtHandshake( // it was an abnormal interrupt during the processing so the handshake process should be aborted // anyway, and that's what will be done. - // LOCKING INFORMATION: accesing this field just for NULL check doesn't + // LOCKING INFORMATION: accessing this field just for NULL check doesn't // hurt, even if this field could be dangling in the moment. This will be // followed by an additional check, done this time under lock, and there will // be no dangling pointers at this time. @@ -1886,7 +1886,7 @@ bool srt::CUDT::createSrtHandshake( #if ENABLE_BONDING // Note that this will fire in both cases: // - When the group has been set by the user on a socket (or socket was created as a part of the group), - // and the handshake request is to be sent with informing the peer that this conenction belongs to a group + // and the handshake request is to be sent with informing the peer that this connection belongs to a group // - When the agent received a HS request with a group, has created its mirror group on its side, and // now sends the HS response to the peer, with ITS OWN group id (the mirror one). // @@ -4074,7 +4074,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType); e = CUDTException(MJ_SETUP, MN_REJECTED, 0); } - else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check + else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // security check e = CUDTException(MJ_SETUP, MN_SECURITY, 0); } @@ -4543,7 +4543,7 @@ EConnectStatus srt::CUDT::processRendezvous( HLOGC( cnlog.Debug, log << CONID() - << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)"); + << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on existing data)"); } else { @@ -4658,7 +4658,7 @@ EConnectStatus srt::CUDT::processRendezvous( // the request time must be updated so that the next handshake can be sent out immediately HLOGC(cnlog.Debug, log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType) - << " REQ-TIME: LOW to send immediately, consider yourself conencted"); + << " REQ-TIME: LOW to send immediately, consider yourself connected"); m_tsLastReqTime = steady_clock::time_point(); } else @@ -4961,7 +4961,7 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT m_SourceAddr = pHspkt->udpDestAddr(); HLOGC(cnlog.Debug, - log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize + log << CONID() << "applyResponseSettings: HANDSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " peer-ISN=" << m_ConnRes.m_iISN << " local-ISN=" << m_iISN << " peerID=" << m_ConnRes.m_iID @@ -6279,7 +6279,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC() // XXX Not sure about that. May happen that AGENT wants // tsbpd mode, but PEER doesn't, even in bidirectional mode. - // This way, the reception side should get precedense. + // This way, the reception side should get precedence. // if (bidirectional || m_config.bDataSender || m_bTwoWayData) // m_bPeerTsbPd = m_bTSBPD; @@ -6353,7 +6353,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC() if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET))) { - LOGC(rslog.Error, log << CONID() << "setupCC: IPE: resrouces not yet initialized!"); + LOGC(rslog.Error, log << CONID() << "setupCC: IPE: resources not yet initialized!"); return SRT_REJ_IPE; } return SRT_REJ_UNKNOWN; @@ -6785,7 +6785,7 @@ int srt::CUDT::sndDropTooLate() // high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms) // Minimum value must accommodate an I-Frame (~8 x average frame size) - // >>need picture rate or app to set min treshold + // >>need picture rate or app to set min threshold // >>using 1 sec for worse case 1 frame using all bit budget. // picture rate would be useful in auto SRT setting for min latency // XXX Make SRT_TLPKTDROP_MINTHRESHOLD_MS option-configurable @@ -7019,7 +7019,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) /* * The code below is to return ETIMEOUT when blocking mode could not get free buffer in time. - * If no free buffer available in non-blocking mode, we alredy returned. If buffer available, + * If no free buffer available in non-blocking mode, we already returned. If buffer available, * we test twice if this code is outside the else section. * This fix move it in the else (blocking-mode) section */ @@ -7106,7 +7106,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) w_mctrl.pktseq = seqno; // Now seqno is the sequence to which it was scheduled - // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurrate. + // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurate. HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:" << (w_mctrl.srctime ? FormatTime(ts_srctime) : "none") << " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno @@ -7141,7 +7141,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) << " size=" << size << " #" << w_mctrl.msgno << " SCHED %" << orig_seqno << "(>> %" << seqno << ") !" << BufferStamp(data, size)); - if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried. + if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as required. { // write is not available any more uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false); @@ -7398,7 +7398,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ { // `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be // only until the time that is now + timeout since the first moment - // when this started, or sliced-waiting for 1 second, if timtout is + // when this started, or sliced-waiting for 1 second, if timeout is // higher than this. const steady_clock::time_point exptime = steady_clock::now() + recv_timeout; @@ -7950,7 +7950,7 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg) // Use the values: // - if SRTO_MAXBW is >0, use it. // - if SRTO_MAXBW == 0, use SRTO_INPUTBW + SRTO_OHEADBW - // - if SRTO_INPUTBW == 0, pass 0 to requst in-buffer sampling + // - if SRTO_INPUTBW == 0, pass 0 to request in-buffer sampling // Bytes/s const int64_t bw = m_config.llMaxBW != 0 ? m_config.llMaxBW : // When used SRTO_MAXBW m_config.llInputBW != 0 ? withOverhead(m_config.llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW @@ -8249,7 +8249,7 @@ 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 == 0) // Don't send SHUTDOWN if we don't know peer ID. break; ctrlpkt.pack(pkttype); ctrlpkt.set_id(m_PeerID); @@ -8271,7 +8271,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp break; - case UMSG_EXT: // 0x7FFF - Resevered for future use + case UMSG_EXT: // 0x7FFF - Reserved for future use break; default: @@ -8790,7 +8790,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ * Therefore, a distinct ack state is used for received Ack (iSndLastFullAck) * and ack position in send buffer (m_iSndLastDataAck). * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) - * occures, we drop received acks (as duplicates) and do not update stats like RTT, + * occurs, we drop received acks (as duplicates) and do not update stats like RTT, * which may go crazy and stay there, preventing proper stream recovery. */ @@ -9212,7 +9212,7 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show()); - if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...??? + if ((req.m_iReqType > URQ_INDUCTION_TYPES) // actually it catches URQ_INDUCTION and URQ_ERROR_* symbols...??? || (m_config.bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION { // The peer side has not received the handshake message, so it keeps querying @@ -9483,7 +9483,7 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) // int err_type = packet.getAddInfo(); // currently only this error is signalled from the peer side - // if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately + // if recvfile() fails (e.g., due to disk fail), blocked sendfile/send should return immediately // giving the app a chance to fix the issue m_bPeerHealth = false; @@ -9611,7 +9611,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv) std::pair srt::CUDT::getCleanRexmitOffset() { // This function is required to look into the loss list and - // drop all sequences that are alrady revoked from the sender + // drop all sequences that are already revoked from the sender // buffer, send the drop request if needed, and return the sender // buffer offset for the next packet to retransmit, or -1 if // there is no retransmission candidate at the moment. @@ -9686,7 +9686,7 @@ std::pair srt::CUDT::getCleanRexmitOffset() if (seq == SRT_SEQNO_NONE) return std::make_pair(SRT_SEQNO_NONE, -1); - // For the right sequence though check also the rigt time. + // For the right sequence though check also the right time. const steady_clock::time_point time_now = steady_clock::now(); if (!checkRexmitRightTime(offset, time_now)) { @@ -10016,7 +10016,7 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts) bool srt::CUDT::isRegularSendingPriority() { - // In order to have regular packets take precedense over retransmitted: + // In order to have regular packets take precedence over retransmitted: // - SRTO_TLPKTDROP = true // - SRTO_MESSAGEAPI = true // NOTE: @@ -10035,7 +10035,7 @@ bool srt::CUDT::isRegularSendingPriority() // might be better in some situations. For example, it should be acceptable // that a packet that has very little time to be recovered is sent before a // regular packet that has still STT + Latency time to deliver. The regular - // packets should still be favorized, but not necessarily at the expence of + // packets should still be favored, but not necessarily at the expense of // dismissing a recovery chance that wouldn't endanger the delivery of a regular // packet. Criteria might be various, for example, the number of scheduled // packets and their late delivery time might be taken into account. @@ -10508,7 +10508,7 @@ void srt::CUDT::sendLossReport(const std::vector > & bool srt::CUDT::overrideSndSeqNo(int32_t seq) { // This function is intended to be called from the socket - // group management functions to synchronize the sequnece in + // group management functions to synchronize the sequence in // all sockes in the bonding group. THIS sequence given // here is the sequence TO BE STAMPED AT THE EXACTLY NEXT // sent payload. Therefore, screw up the ISN to exactly this @@ -10997,7 +10997,7 @@ int srt::CUDT::processData(CUnit* in_unit) // this socket as member (that is, 'gi' cannot be a dangling pointer). if (gi != NULL) { - if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely + if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, though PENDING is unlikely { HLOGC(qrlog.Debug, log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate] @@ -11175,7 +11175,7 @@ int srt::CUDT::processData(CUnit* in_unit) // If so, send the loss report and remove this element. // - Since the first element that has TTL>0, iterate until the end of container and decrease TTL. // - // This will be efficient becase the loop to increment one field (without any condition check) + // This will be efficient because the loop to increment one field (without any condition check) // can be quite well optimized. vector lossdata; @@ -11598,7 +11598,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) return m_RejectReason; } - // Dunno why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE. + // Don't know why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE. // It doesn't seem to make sense to deserialize it into the handshake structure if we are not // sure that the packet contains the handshake at all! if (!packet.isControl(UMSG_HANDSHAKE)) diff --git a/srtcore/core.h b/srtcore/core.h index 81a5142a50..e5fea5d949 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -313,7 +313,7 @@ class CUDT typedef sync::AtomicClock atomic_time_point; typedef sync::AtomicDuration atomic_duration; -private: // constructor and desctructor +private: // constructor and destructor void construct(); void clearData(); CUDT(CUDTSocket* parent); @@ -1225,10 +1225,10 @@ class CUDT /// Forms and sends ACK packet /// @note Assumes @ctrlpkt already has a timestamp. /// - /// @param ctrlpkt A control packet structure to fill. It must have a timestemp already set. + /// @param ctrlpkt A control packet structure to fill. It must have a timestamp already set. /// @param size Sends lite ACK if size is SEND_LITE_ACK, Full ACK otherwise /// - /// @returns the nmber of packets sent. + /// @returns the number of packets sent. int sendCtrlAck(CPacket& ctrlpkt, int size); void sendLossReport(const std::vector< std::pair >& losslist); diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index e7fe0be895..68d551e589 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -81,7 +81,7 @@ void srt::CCryptoControl::globalInit() { #ifdef SRT_ENABLE_ENCRYPTION // We need to force the Cryspr to be initialized during startup to avoid the - // possibility of multiple threads initialzing the same static data later on. + // possibility of multiple threads initializing the same static data later on. HaiCryptCryspr_Get_Instance(); #endif } @@ -872,7 +872,7 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU // If not "secured", it means that it won't be able to decrypt packets, // so there's no point to even try to send them to HaiCrypt_Rx_Data. // Actually the current conditions concerning m_hRcvCrypto are such that this object - // is cretaed in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt, + // is created in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt, // but with SRT_KM_S_NOSECRET m_hRcvCrypto is not even created (is NULL), which // will then cause an error to be reported, misleadingly. Simply don't try to // decrypt anything as long as you are not sure that the connection is secured. diff --git a/srtcore/epoll.cpp b/srtcore/epoll.cpp index c8c3c74b71..e3545b7380 100644 --- a/srtcore/epoll.cpp +++ b/srtcore/epoll.cpp @@ -486,7 +486,7 @@ int srt::CEPoll::setflags(const int eid, int32_t flags) int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut) { - // It is allowed to call this function witn fdsSize == 0 + // It is allowed to call this function with fdsSize == 0 // and therefore also NULL fdsSet. This will then only report // the number of ready sockets, just without information which. if (fdsSize < 0 || (fdsSize > 0 && !fdsSet)) diff --git a/srtcore/epoll.h b/srtcore/epoll.h index 00d46ceb41..09e0f0ec73 100644 --- a/srtcore/epoll.h +++ b/srtcore/epoll.h @@ -157,7 +157,7 @@ std::string DisplayEpollWatch(); ewatch_t m_USockWatchState; /// Objects representing changes in SRT sockets. - /// Objects are removed from here when an event is registerred as edge-triggered. + /// Objects are removed from here when an event is registered as edge-triggered. /// Otherwise it is removed only when all events as per subscription /// are no longer on. enotice_t m_USockEventNotice; @@ -429,7 +429,7 @@ friend class srt::CRendezvousQueue; /// @param d the internal structure of the epoll container /// @param st output container for the results: { socket_type, event } /// @param msTimeOut timeout after which return with empty output is allowed - /// @param report_by_exception if true, errors will result in exception intead of returning -1 + /// @param report_by_exception if true, errors will result in exception instead of returning -1 /// @retval -1 error occurred /// @retval >=0 number of ready sockets (actually size of `st`) int swait(CEPollDesc& d, fmap_t& st, int64_t msTimeOut, bool report_by_exception = true); @@ -438,7 +438,7 @@ friend class srt::CRendezvousQueue; bool empty(const CEPollDesc& d) const; /// Reports which events are ready on the given socket. - /// @param mp socket event map retirned by `swait` + /// @param mp socket event map returned by `swait` /// @param sock which socket to ask /// @return event flags for given socket, or 0 if none static int ready(const fmap_t& mp, SRTSOCKET sock) @@ -450,7 +450,7 @@ friend class srt::CRendezvousQueue; } /// Reports whether socket is ready for given event. - /// @param mp socket event map retirned by `swait` + /// @param mp socket event map returned by `swait` /// @param sock which socket to ask /// @param event which events it should be ready for /// @return true if the given socket is ready for given event diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index fd762b88bc..4ee5e44284 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -297,7 +297,7 @@ void FECFilterBuiltin::ConfigureColumns(Container& which, int32_t isn) size_t zero = which.size(); // The first series of initialization should embrace: - // - if multiplyer == 1, EVERYTHING (also the case of SOLID matrix) + // - if multiplayer == 1, EVERYTHING (also the case of SOLID matrix) // - if more, ONLY THE FIRST SQUARE. which.resize(zero + numberCols()); @@ -891,7 +891,7 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) HLOGC(pflog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " RESULT=" << hangname[okh] - << (discrep ? " IPE: H successul and V failed!" : "") + << (discrep ? " IPE: H successful and V failed!" : "") << " IRRECOVERABLE: " << Printable(irrecover_col)); } diff --git a/srtcore/fec.h b/srtcore/fec.h index 0297094752..f4ed0e4cca 100644 --- a/srtcore/fec.h +++ b/srtcore/fec.h @@ -132,10 +132,10 @@ class FECFilterBuiltin: public SrtPacketFilterBase // Base index at the oldest column platform determines // the base index of the queue. Meaning, first you need - // to determnine the column index, where the index 0 is + // to determine the column index, where the index 0 is // the fistmost element of this queue. After determining // the column index, there must be also a second factor - // deteremined - which column series it is. So, this can + // determined - which column series it is. So, this can // start by extracting the base sequence of the element // at the index column. This is the series 0. Now, the // distance between these two sequences, divided by diff --git a/srtcore/group.cpp b/srtcore/group.cpp index ed18a72d6a..b873bb710d 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -90,7 +90,7 @@ bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_ // NOTE: the groupwise scheduling sequence might have been set // already. If so, it means that it was set by either: - // - the call of this function on the very first conencted socket (see below) + // - the call of this function on the very first connected socket (see below) // - the call to `sendBroadcast` or `sendBackup` // In both cases, we want THIS EXACTLY value to be reported if (m_iLastSchedSeqNo != -1) @@ -205,7 +205,7 @@ void CUDTGroup::debugMasterData(SRTSOCKET slave) else { // The returned master_st is the master's start time. Calculate the - // differene time. + // difference time. IF_LOGGING(steady_clock::duration master_tdiff = m_tsStartTime - start_time); LOGC(cnlog.Debug, log << CONID() << "FOUND GROUP MASTER LINK: peer=$" << mpeer << " - start time diff: " << FormatDuration(master_tdiff)); @@ -595,7 +595,7 @@ void CUDTGroup::deriveSettings(CUDT* u) IM(SRTO_RCVLATENCY, iRcvLatency); IM(SRTO_PEERLATENCY, iPeerLatency); IM(SRTO_SNDDROPDELAY, iSndDropDelay); - // Special handling of SRTO_PAYLOADSIZE becuase API stores the value as int32_t, + // Special handling of SRTO_PAYLOADSIZE because API stores the value as int32_t, // while the config structure stores it as size_t. importTrivialOption(m_config, SRTO_PAYLOADSIZE, (int)u->m_config.zExpPayloadSize); IMF(SRTO_TLPKTDROP, m_bTLPktDrop); @@ -905,7 +905,7 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen) return; } - // Found a value set on or derived by a group. Prefer returing it over the one taken from a member socket. + // Found a value set on or derived by a group. Prefer returning it over the one taken from a member socket. // Check the size first. if (w_optlen < int(i->value.size())) throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); @@ -935,7 +935,7 @@ SRT_KM_STATE CUDTGroup::getGroupEncryptionState() // no password, but peer did, and ENFORCEDENCRYPTION=false allowed // this connection to be established. UNSECURED can't be taken in this // case because this would suggest that BOTH are unsecured, that is, - // we have established an unsecured connection (which ain't true). + // we have established an unsecured connection (which is not true). if (gst == SRT_KM_S_UNSECURED && cc->m_SndKmState == SRT_KM_S_NOSECRET) gst = SRT_KM_S_NOSECRET; kmstates.insert(gst); @@ -1150,7 +1150,7 @@ void CUDTGroup::close() // Release blocked clients // XXX This looks like a dead code. Group receiver functions // do not use any lock on m_RcvDataLock, it is likely a remainder - // of the old, internal impementation. + // of the old, internal implementation. // CSync::lock_notify_one(m_RcvDataCond, m_RcvDataLock); } @@ -2976,7 +2976,7 @@ CUDTGroup::BackupMemberState CUDTGroup::sendBackup_QualifyActiveState(const gli_ const int64_t probing_period_us = initial_stabtout_us + 5 * CUDT::COMM_SYN_INTERVAL_US; // RTT and RTTVar values are still being refined during the probing period, - // therefore the dymanic timeout should not be used during the probing period. + // therefore the dynamic timeout should not be used during the probing period. const bool is_activation_phase = !is_zero(u.freshActivationStart()) && (count_microseconds(currtime - u.freshActivationStart()) <= probing_period_us); @@ -3511,7 +3511,7 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx if ((num_unstable + num_wary + num_pending == 0) || !w_none_succeeded) return; - HLOGC(gslog.Debug, log << "grp/sendBackup: no successfull sending: " + HLOGC(gslog.Debug, log << "grp/sendBackup: no successful sending: " << (num_unstable + num_wary) << " unstable links, " << num_pending << " pending - waiting to retry sending..."); @@ -3699,7 +3699,7 @@ void CUDTGroup::sendBackup_SilenceRedundantLinks(SendBackupCtx& w_sendBackupCtx, { // The most important principle is to keep the data being sent constantly, // even if it means temporarily full redundancy. - // A member can be silenced only if there is at least one stable memebr. + // A member can be silenced only if there is at least one stable member. const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE); if (num_stable == 0) return; @@ -4183,7 +4183,7 @@ void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli) // the sequence per being IDLE and empty buffer), so a large portion of initial // series of packets may come with past sequence, delaying this way with ACK, // which may result not only with exceeded stability timeout (which fortunately - // isn't being measured in this case), but also with receiveing keepalive + // isn't being measured in this case), but also with receiving keepalive // (therefore we also don't reset the link to IDLE in the temporary activation period). if (gli->sndstate == SRT_GST_RUNNING && is_zero(gli->ps->core().m_tsFreshActivation)) { diff --git a/srtcore/group.h b/srtcore/group.h index 56f1456f8c..e47f1243a5 100644 --- a/srtcore/group.h +++ b/srtcore/group.h @@ -223,7 +223,7 @@ class CUDTGroup bool send_CheckIdle(const gli_t d, std::vector& w_wipeme, std::vector& w_pendingLinks); - /// This function checks if the member has just become idle (check if sender buffer is empty) to send a KEEPALIVE immidiatelly. + /// This function checks if the member has just become idle (check if sender buffer is empty) to send a KEEPALIVE immediately. /// @todo Check it is some abandoned logic. void sendBackup_CheckIdleTime(gli_t w_d); @@ -247,11 +247,11 @@ class CUDTGroup /// @param[in,out] w_mc message control /// @param[in] currtime current time /// @param[in] currseq current packet sequence number - /// @param[out] w_nsuccessful number of members with successfull sending. + /// @param[out] w_nsuccessful number of members with successful sending. /// @param[in,out] maxActiveWeight /// @param[in,out] sendBackupCtx context /// @param[in,out] w_cx error - /// @return group send result: -1 if sending over all members has failed; number of bytes sent overwise. + /// @return group send result: -1 if sending over all members has failed; number of bytes sent otherwise. int sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_mc, const steady_clock::time_point& currtime, int32_t& w_curseq, size_t& w_nsuccessful, uint16_t& w_maxActiveWeight, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx); diff --git a/srtcore/group_backup.h b/srtcore/group_backup.h index ed2fb29ffb..71a1f7abba 100644 --- a/srtcore/group_backup.h +++ b/srtcore/group_backup.h @@ -92,13 +92,13 @@ namespace groups /// @brief Adds or updates a record of the member socket state. /// @param pSocketDataIt Iterator to a socket - /// @param st State of the memmber socket + /// @param st State of the member socket /// @todo Implement updating member state void recordMemberState(SocketData* pSocketDataIt, BackupMemberState st); /// @brief Updates a record of the member socket state. /// @param pSocketDataIt Iterator to a socket - /// @param st State of the memmber socket + /// @param st State of the member socket /// @todo To be replaced by recordMemberState /// @todo Update max weights? void updateMemberState(const SocketData* pSocketDataIt, BackupMemberState st); diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 0fe7d2b4b7..94eaa5da0d 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -740,7 +740,7 @@ int32_t srt::CRcvLossList::removeUpTo(int32_t seqno_last) if (CSeqNo::seqcmp(seqno_last, first) < 0) { - //HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " - first %" << first << " is newer, exitting"); + //HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " - first %" << first << " is newer, exiting"); return first; // seqno_last older than first - nothing to remove } diff --git a/srtcore/list.h b/srtcore/list.h index 40b8ee0c9b..802ede9a2d 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -162,7 +162,7 @@ class CRcvLossList /// Insert a series of loss seq. no. between "seqno1" and "seqno2" into the receiver's loss list. /// @param [in] seqno1 sequence number starts. - /// @param [in] seqno2 seqeunce number ends. + /// @param [in] seqno2 sequence number ends. /// @return length of the loss record inserted (seqlen(seqno1, seqno2)), -1 on error. int insert(int32_t seqno1, int32_t seqno2); diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index 1806230399..e0b1edfb38 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -110,7 +110,7 @@ modified by // Control Info: None // 2: Acknowledgement (UMSG_ACK) // Add. Info: The ACK sequence number -// Control Info: The sequence number to which (but not include) all the previous packets have beed received +// Control Info: The sequence number to which (but not include) all the previous packets have been received // Optional: RTT // RTT Variance // available receiver buffer size (in bytes) @@ -131,7 +131,7 @@ modified by // 7: Message Drop Request (UMSG_DROPREQ) // Add. Info: Message ID // Control Info: first sequence number of the message -// last seqeunce number of the message +// last sequence number of the message // 8: Error Signal from the Peer Side (UMSG_PEERERROR) // Add. Info: Error code // Control Info: None @@ -151,7 +151,7 @@ modified by // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // // Loss List Field Coding: -// For any consecutive lost seqeunce numbers that the differnece between +// For any consecutive lost sequence numbers that the difference between // the last and first is more than 1, only record the first (a) and the // the last (b) sequence numbers in the loss list field, and modify the // the first bit of a to 1. @@ -174,7 +174,7 @@ using namespace srt_logging; namespace srt { -// Set up the aliases in the constructure +// Set up the aliases in the constructor CPacket::CPacket() : m_nHeader() // Silences GCC 12 warning "used uninitialized". , m_extra_pad() diff --git a/srtcore/packet.h b/srtcore/packet.h index 5a6d6eb152..6747e244c9 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -64,7 +64,7 @@ namespace srt ////////////////////////////////////////////////////////////////////////////// // The purpose of the IOVector class is to proide a platform-independet interface -// to the WSABUF on Windows and iovec on Linux, that can be easilly converted +// to the WSABUF on Windows and iovec on Linux, that can be easily converted // to the native structure for use in WSARecvFrom() and recvmsg(...) functions class IOVector #ifdef _WIN32 diff --git a/srtcore/packetfilter.h b/srtcore/packetfilter.h index 2d1105042c..8d4dc6369a 100644 --- a/srtcore/packetfilter.h +++ b/srtcore/packetfilter.h @@ -184,7 +184,7 @@ class PacketFilter // Copy constructor - important when listener-spawning // Things being done: // 1. The filter is individual, so don't copy it. Set NULL. - // 2. This will be configued anyway basing on possibly a new rule set. + // 2. This will be configured anyway basing on possibly a new rule set. PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {} // This function will be called by the parent CUDT diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 88c0d30f63..4ced92d6f9 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -1559,7 +1559,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUni // side just started sending its handshake packets, the local side has already // run the CRcvQueue::worker thread, and this worker thread is trying to dispatch // the handshake packet too early, before the dispatcher has a chance to see - // this socket registerred in the RendezvousQueue, which causes the packet unable + // this socket registered in the RendezvousQueue, which causes the packet unable // to be dispatched. Therefore simply treat every "out of band" packet (with socket // not belonging to the connection and not registered as rendezvous) as "possible // attack" and ignore it. This also should better protect the rendezvous socket @@ -1620,7 +1620,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUni // that we KNOW (by the cst == CONN_ACCEPT result) that the socket should be inserted // into the pending anteroom. - CUDT* ne = getNewEntry(); // This function actuall removes the entry and returns it. + CUDT* ne = getNewEntry(); // This function actually removes the entry and returns it. // This **should** now always return a non-null value, but check it first // because if this accidentally isn't true, the call to worker_ProcessAddressedPacket will // result in redirecting it to here and so on until the call stack overflow. In case of diff --git a/srtcore/queue.h b/srtcore/queue.h index cf31097673..930ddd92a5 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -339,7 +339,7 @@ class CRendezvousQueue /// @brief Update status of connections in the pending queue. /// Stop connecting if TTL expires. Resend handshake request every 250 ms if no response from the peer. - /// @param rst result of reading from a UDP socket: received packet / nothin read / read error. + /// @param rst result of reading from a UDP socket: received packet / nothing read / read error. /// @param cst target status for pending connection: reject or proceed. /// @param pktIn packet received from the UDP socket. void updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit); @@ -368,7 +368,7 @@ class CRendezvousQueue /// - Sockets with expired TTL go to the 'to_remove' list and removed from the queue straight away. /// - If HS request is to be resent (resend 250 ms if no response from the peer) go to the 'to_process' list. /// - /// @param rst result of reading from a UDP socket: received packet / nothin read / read error. + /// @param rst result of reading from a UDP socket: received packet / nothing read / read error. /// @param cst target status for pending connection: reject or proceed. /// @param iDstSockID destination socket ID of the received packet. /// @param[in,out] toRemove stores sockets with expired TTL. diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp index 1b94c9197a..e3ad5cde25 100644 --- a/srtcore/socketconfig.cpp +++ b/srtcore/socketconfig.cpp @@ -56,7 +56,7 @@ namespace srt { int RcvBufferSizeOptionToValue(int val, int flightflag, int mss) { - // Mimimum recv buffer size is 32 packets + // Minimum recv buffer size is 32 packets const int mssin_size = mss - CPacket::UDP_HDR_SIZE; int bufsize; @@ -1025,7 +1025,7 @@ bool CSrtConfig::payloadSizeFits(size_t val, int /*ip_family*/, std::string& w_e } } - // Not checking AUTO to allow defaul 1456 bytes. + // Not checking AUTO to allow default 1456 bytes. if ((this->iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM) && (val > (SRT_LIVE_MAX_PLSIZE - HAICRYPT_AUTHTAG_MAX))) { @@ -1089,7 +1089,7 @@ bool SRT_SocketOptionObject::add(SRT_SOCKOPT optname, const void* optval, size_t // Header size will get the size likely aligned, but it won't // hurt if the memory size will be up to 4 bytes more than - // needed - and it's better to not risk that alighment rules + // needed - and it's better to not risk that alignment rules // will make these calculations result in less space than needed. const size_t headersize = sizeof(SingleOption); const size_t payload = std::max(sizeof(uint32_t), optlen); diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index 0f6c798878..f44e3c24cf 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -93,7 +93,7 @@ struct CSrtMuxerConfig int iUDPSndBufSize; // UDP sending buffer size int iUDPRcvBufSize; // UDP receiving buffer size - // NOTE: this operator is not reversable. The syntax must use: + // NOTE: this operator is not reversible. The syntax must use: // muxer_entry == socket_entry bool isCompatWith(const CSrtMuxerConfig& other) const { @@ -212,7 +212,7 @@ struct CSrtConfig: CSrtMuxerConfig static const int COMM_RESPONSE_TIMEOUT_MS = 5 * 1000; // 5 seconds static const uint32_t COMM_DEF_MIN_STABILITY_TIMEOUT_MS = 60; // 60 ms - // Mimimum recv flight flag size is 32 packets + // Minimum recv flight flag size is 32 packets static const int DEF_MIN_FLIGHT_PKT = 32; static const size_t MAX_SID_LENGTH = 512; static const size_t MAX_PFILTER_LENGTH = 64; @@ -339,7 +339,7 @@ struct CSrtConfig: CSrtMuxerConfig // Default congestion is "live". // Available builtin congestions: "file". - // Others can be registerred. + // Others can be registered. sCongestion.set("live", 4); } diff --git a/srtcore/srt.h b/srtcore/srt.h index 2d8ba39978..9fd9b20c8a 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -221,7 +221,7 @@ typedef enum SRT_SOCKOPT { SRTO_PAYLOADSIZE, // Maximum payload size sent in one UDP packet (0 if unlimited) SRTO_TRANSTYPE = 50, // Transmission type (set of options required for given transmission type) SRTO_KMREFRESHRATE, // After sending how many packets the encryption key should be flipped to the new key - SRTO_KMPREANNOUNCE, // How many packets before key flip the new key is annnounced and after key flip the old one decommissioned + SRTO_KMPREANNOUNCE, // How many packets before key flip the new key is announced and after key flip the old one decommissioned SRTO_ENFORCEDENCRYPTION, // Connection to be rejected or quickly broken when one side encryption set or bad password SRTO_IPV6ONLY, // IPV6_V6ONLY mode SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms] @@ -599,7 +599,7 @@ enum SRT_REJECT_REASON #define SRT_LOGFA_CONGEST 7 // cclog: Congestion control module #define SRT_LOGFA_PFILTER 8 // pflog: Packet filter module -#define SRT_LOGFA_API_CTRL 11 // aclog: API part for socket and library managmenet +#define SRT_LOGFA_API_CTRL 11 // aclog: API part for socket and library management #define SRT_LOGFA_QUE_CTRL 13 // qclog: Queue control activities diff --git a/srtcore/srt_compat.c b/srtcore/srt_compat.c index bbdf7f795e..819fe41f55 100644 --- a/srtcore/srt_compat.c +++ b/srtcore/srt_compat.c @@ -108,7 +108,7 @@ extern const char * SysStrError(int errnum, char * buf, size_t buflen) // 0 for success, otherwise it's: // - possibly -1 and the error code is in ::errno // - possibly the error code itself - // The details of the errror are not interesting; simply + // The details of the error are not interesting; simply // craft a fallback message in this case. if (strerror_r(errnum, buf, buflen) != 0) { diff --git a/srtcore/srt_compat.h b/srtcore/srt_compat.h index d4fd2361e0..46fe6aa885 100644 --- a/srtcore/srt_compat.h +++ b/srtcore/srt_compat.h @@ -24,7 +24,7 @@ written by extern "C" { #endif -/* Ensures that we store the error in the buffer and return the bufer. */ +/* Ensures that we store the error in the buffer and return the buffer. */ const char * SysStrError(int errnum, char * buf, size_t buflen); #ifdef __cplusplus diff --git a/srtcore/sync.cpp b/srtcore/sync.cpp index 96cb1a9d9d..ff16a7146a 100644 --- a/srtcore/sync.cpp +++ b/srtcore/sync.cpp @@ -347,7 +347,7 @@ int srt::sync::genRandomInt(int minVal, int maxVal) #endif // Map onto [minVal, maxVal]. - // Note. There is a minuscule probablity to get maxVal+1 as the result. + // Note. There is a minuscule probability to get maxVal+1 as the result. // So we have to use long long to handle cases when maxVal = INT32_MAX. // Also we must check 'res' does not exceed maxVal, // which may happen if rand_0_1 = 1, even though the chances are low. diff --git a/srtcore/sync.h b/srtcore/sync.h index 72da5869be..a5ac6d6840 100644 --- a/srtcore/sync.h +++ b/srtcore/sync.h @@ -431,7 +431,7 @@ class Condition ~Condition(); public: /// These functions do not align with C++11 version. They are here hopefully as a temporal solution - /// to avoud issues with static initialization of CV on windows. + /// to avoid issues with static initialization of CV on windows. void init(); void reset(); void destroy(); @@ -511,13 +511,13 @@ class SharedMutex ~SharedMutex(); public: - /// Acquire the lock for writting purposes. Only one thread can acquire this lock at a time + /// Acquire the lock for writing purposes. Only one thread can acquire this lock at a time /// Once it is locked, no reader can acquire it void lock(); bool try_lock(); void unlock(); - /// Acquire the lock if no writter already has it. For read purpose only + /// Acquire the lock if no writer already has it. For read purpose only /// Several readers can lock this at the same time. void lock_shared(); bool try_lock_shared(); @@ -547,7 +547,7 @@ inline void releaseMutex(SharedMutex&) {} /// A version of std::scoped_lock (or lock_guard for C++11). /// We could have used the srt::sync::ScopedLock making it a template-based class. -/// But in that case all usages would have to be specificed like ScopedLock in C++03. +/// But in that case all usages would have to be specified like ScopedLock in C++03. class SRT_ATTR_SCOPED_CAPABILITY ExclusiveLock { public: @@ -856,7 +856,7 @@ class CTimer /// Wakes up waiting thread (sleep_until(..)) without /// changing the target waiting time to force a recheck - /// of the current time in comparisson to the target time. + /// of the current time in comparison to the target time. void tick(); private: diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp index 09def5c334..cfbd2ab6d2 100644 --- a/srtcore/sync_posix.cpp +++ b/srtcore/sync_posix.cpp @@ -567,7 +567,7 @@ CUDTException& GetThreadLocalError() { // In POSIX version we take into account the possibility // of having an allocation error here. Therefore we need to - // allow thie value to return NULL and have some fallback + // allow this value to return NULL and have some fallback // for that case. The dynamic memory allocation failure should // be the only case as to why it is unable to get the pointer // to the error description. diff --git a/srtcore/tsbpd_time.cpp b/srtcore/tsbpd_time.cpp index d45215a011..b85aa5060f 100644 --- a/srtcore/tsbpd_time.cpp +++ b/srtcore/tsbpd_time.cpp @@ -266,7 +266,7 @@ void CTsbpdTime::updateBaseTime(uint32_t usPktTimestamp) // Check if timestamp is within the TSBPD_WRAP_PERIOD before reaching the MAX_TIMESTAMP. if (usPktTimestamp > (CPacket::MAX_TIMESTAMP - TSBPD_WRAP_PERIOD)) { - // Approching wrap around point, start wrap check period (if for packet delivery head) + // Approaching wrap around point, start wrap check period (if for packet delivery head) m_bTsbPdWrapCheck = true; LOGC(tslog.Debug, log << "tsbpd wrap period begins with ts=" << usPktTimestamp diff --git a/srtcore/utilities.h b/srtcore/utilities.h index cf3add810c..9f0ab78aa3 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -285,7 +285,7 @@ inline void ItoHLA(uint32_t* dst, const uint32_t* src, size_t size) // Usage: typedef Bits MASKTYPE; // MASKTYPE is a name of your choice. // // With this defined, you can use the following members: -// - MASKTYPE::mask - to get the int32_t value with bimask (used bits set to 1, others to 0) +// - MASKTYPE::mask - to get the int32_t value with bitmask (used bits set to 1, others to 0) // - MASKTYPE::offset - to get the lowermost bit number, or number of bits to shift // - MASKTYPE::wrap(int value) - to create a bitset where given value is encoded in given bits // - MASKTYPE::unwrap(int bitset) - to extract an integer value from the bitset basing on mask definition @@ -1072,7 +1072,7 @@ class DriftTracer // overdrift() should be read only immediately after update() returned // true. It will stay available with this value until the next time when // update() returns true, in which case the value will be cleared. - // Therefore, after calling update() if it retuns true, you should read + // Therefore, after calling update() if it returns true, you should read // overdrift() immediately an make some use of it. Next valid overdrift // will be then relative to every previous overdrift. // @@ -1215,7 +1215,7 @@ inline size_t safe_advance(It& it, size_t num, It end) return num; // will be effectively 0, if reached the required point, or >0, if end was by that number earlier } -// This is available only in C++17, dunno why not C++11 as it's pretty useful. +// This is available only in C++17, don't know why not C++11 as it's pretty useful. template inline ATR_CONSTEXPR size_t Size(const V (&)[N]) ATR_NOEXCEPT { return N; } diff --git a/srtcore/window.cpp b/srtcore/window.cpp index 5deb1d097b..5a926c7212 100644 --- a/srtcore/window.cpp +++ b/srtcore/window.cpp @@ -176,7 +176,7 @@ int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, AccumulatePassFilterParallel(window, asize, filter, abytes, (sum), (count), (bytes)); - // claculate speed, or return 0 if not enough valid value + // calculate speed, or return 0 if not enough valid value if (count <= (asize/2)) { w_bytesps = 0; diff --git a/srtcore/window.h b/srtcore/window.h index dbe4b7179c..4e548d3ef0 100644 --- a/srtcore/window.h +++ b/srtcore/window.h @@ -322,7 +322,7 @@ class CPktTimeWindow: CPktTimeWindowTools // Meaning: if the packet is fully packed, probe_case = timediff. // Otherwise the timediff will be "converted" to a time that a fully packed packet "would take", // provided the arrival time is proportional to the payload size and skipping - // the ETH+IP+UDP+SRT header part elliminates the constant packet delivery time influence. + // the ETH+IP+UDP+SRT header part eliminates the constant packet delivery time influence. // const size_t pktsz = pkt.getLength(); m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? int(timediff_times_pl_size / pktsz) : int(timediff); diff --git a/test/any.hpp b/test/any.hpp index 9ecb96cc59..afe3fc7915 100644 --- a/test/any.hpp +++ b/test/any.hpp @@ -209,11 +209,11 @@ class any final /// The state of the union after this call is unspecified, caller must ensure not to use src anymore. void(*destroy)(storage_union&) noexcept; - /// Copies the **inner** content of the src union into the yet unitialized dest union. + /// Copies the **inner** content of the src union into the yet uninitialized dest union. /// As such, both inner objects will have the same state, but on separate memory locations. void(*copy)(const storage_union& src, storage_union& dest); - /// Moves the storage from src to the yet unitialized dest union. + /// Moves the storage from src to the yet uninitialized dest union. /// The state of src after this call is unspecified, caller must ensure not to use src anymore. void(*move)(storage_union& src, storage_union& dest) noexcept; @@ -251,7 +251,7 @@ class any final static void swap(storage_union& lhs, storage_union& rhs) noexcept { - // just exchage the storage pointers. + // just exchange the storage pointers. std::swap(lhs.dynamic, rhs.dynamic); } }; diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 982145faef..11d2904f66 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -326,7 +326,7 @@ TEST(Bonding, CloseGroupAndSocket) std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (srt_send(ss, buf, 1316) == -1) { - std::cout << "[Sender] sending failure, exitting after sending " << n << " packets\n"; + std::cout << "[Sender] sending failure, exiting after sending " << n << " packets\n"; break; } @@ -393,7 +393,7 @@ TEST(Bonding, Options) EXPECT_NE(srt_setsockflag(grp, SRTO_PACKETFILTER, packet_filter.c_str(), (int)packet_filter.size()), SRT_ERROR); // ================ - // Linger is an option of a trivial type, but differes from other integer-typed options. + // Linger is an option of a trivial type, but differs from other integer-typed options. // Therefore checking it specifically. const linger l = {1, 10}; srt_setsockflag(grp, SRTO_LINGER, &l, sizeof l); diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 511c2dcb1d..90950911fc 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -25,7 +25,7 @@ class CRcvBufferReadMsg // SetUp() is run immediately before a test starts. void SetUp() override { - // make_unique is unfortunatelly C++14 + // make_unique is unfortunately C++14 m_unit_queue.reset(new CUnitQueue(m_buff_size_pkts, 1500)); ASSERT_NE(m_unit_queue.get(), nullptr); diff --git a/test/test_epoll.cpp b/test/test_epoll.cpp index f9d8380308..c2152ceb91 100644 --- a/test/test_epoll.cpp +++ b/test/test_epoll.cpp @@ -592,7 +592,7 @@ class TestEPoll: public srt::Test ASSERT_NE(srt_epoll_wait(m_client_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR); @@ -603,7 +603,7 @@ class TestEPoll: public srt::Test char buffer[1316] = {1, 2, 3, 4}; ASSERT_NE(srt_sendmsg(m_client_sock, buffer, sizeof buffer, - -1, // infinit ttl + -1, // infinite ttl true // in order must be set to true ), SRT_ERROR); @@ -670,7 +670,7 @@ class TestEPoll: public srt::Test ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); @@ -697,7 +697,7 @@ class TestEPoll: public srt::Test ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); diff --git a/test/test_listen_callback.cpp b/test/test_listen_callback.cpp index 33753c2135..7b03437183 100644 --- a/test/test_listen_callback.cpp +++ b/test/test_listen_callback.cpp @@ -94,7 +94,7 @@ class ListenerCallback auto state = srt_getsockstate(server_sock); if (int(state) > int(SRTS_CONNECTED)) { - std::cout << "[T] Listener socket closed, exitting\n"; + std::cout << "[T] Listener socket closed, exiting\n"; break; } @@ -107,7 +107,7 @@ class ListenerCallback SRTSOCKET acp = srt_accept(server_sock, NULL, NULL); if (acp == SRT_INVALID_SOCK) { - std::cout << "[T] Accept failed, so exitting\n"; + std::cout << "[T] Accept failed, so exiting\n"; break; } srt_close(acp); diff --git a/test/test_muxer.cpp b/test/test_muxer.cpp index e85a12118c..369cd8cfc7 100644 --- a/test/test_muxer.cpp +++ b/test/test_muxer.cpp @@ -71,7 +71,7 @@ class TestMuxer ASSERT_NE(srt_epoll_wait(m_client_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR); @@ -82,7 +82,7 @@ class TestMuxer char buffer[1316] = {1, 2, 3, 4}; ASSERT_NE(srt_sendmsg(m_caller_sock, buffer, sizeof buffer, - -1, // infinit ttl + -1, // infinite ttl true // in order must be set to true ), SRT_ERROR); } @@ -150,7 +150,7 @@ TEST_F(TestMuxer, IPv4_and_IPv6) ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); @@ -177,7 +177,7 @@ TEST_F(TestMuxer, IPv4_and_IPv6) ASSERT_NE(srt_epoll_wait(m_server_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); diff --git a/test/test_reuseaddr.cpp b/test/test_reuseaddr.cpp index 929f6b3db9..64d1c510db 100644 --- a/test/test_reuseaddr.cpp +++ b/test/test_reuseaddr.cpp @@ -194,7 +194,7 @@ class ReuseAddr : public srt::Test EXPECT_NE(srt_epoll_wait(client_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR) << srt_getlasterror_str(); @@ -204,7 +204,7 @@ class ReuseAddr : public srt::Test char buffer[1316] = {1, 2, 3, 4}; EXPECT_NE(srt_sendmsg(client_sock, buffer, sizeof buffer, - -1, // infinit ttl + -1, // infinite ttl true // in order must be set to true ), SRT_ERROR); @@ -324,7 +324,7 @@ class ReuseAddr : public srt::Test EXPECT_NE(srt_epoll_wait(server_pollid, read, &rlen, write, &wlen, - 10000, // -1 is set for debuging purpose. + 10000, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR) << srt_getlasterror_str(); @@ -363,7 +363,7 @@ class ReuseAddr : public srt::Test EXPECT_NE(srt_epoll_wait(server_pollid, read, &rlen, write, &wlen, - -1, // -1 is set for debuging purpose. + -1, // -1 is set for debugging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR) << srt_getlasterror_str(); diff --git a/test/test_seqno.cpp b/test/test_seqno.cpp index f2e374f765..f129e83381 100644 --- a/test/test_seqno.cpp +++ b/test/test_seqno.cpp @@ -11,14 +11,14 @@ const int32_t CSeqNo::m_iMaxSeqNo; TEST(CSeqNo, constants) { - // Compare two seq#, considering the wraping. + // Compare two seq#, considering the wrapping. EXPECT_EQ(CSeqNo::m_iMaxSeqNo, 0x7FFFFFFF); EXPECT_EQ(CSeqNo::m_iSeqNoTH, 0x3FFFFFFF); } TEST(CSeqNo, seqcmp) { - // Compare two seq#, considering the wraping. + // Compare two seq#, considering the wrapping. EXPECT_EQ(CSeqNo::seqcmp(0x7FFFFFFF, 0x7FFFFFFF), 0); // abs(seq1 - seq2) < 0x3FFFFFFF : seq1 - seq2 diff --git a/test/test_snd_rate_estimator.cpp b/test/test_snd_rate_estimator.cpp index c658c659c4..5dca5f4b78 100644 --- a/test/test_snd_rate_estimator.cpp +++ b/test/test_snd_rate_estimator.cpp @@ -30,7 +30,7 @@ class CSndRateEstFixture // SetUp() is run immediately before a test starts. void SetUp() override { - // make_unique is unfortunatelly C++14 + // make_unique is unfortunately C++14 } diff --git a/test/test_socket_options.cpp b/test/test_socket_options.cpp index fe27bb387a..67a3001e52 100644 --- a/test/test_socket_options.cpp +++ b/test/test_socket_options.cpp @@ -534,7 +534,7 @@ void TestDefaultValues(SRTSOCKET s) if (!(entry.flags & Flags::R)) { - // TODO: Check reading retuns an error. + // TODO: Check reading returns an error. LOGD(cerr << "Skipping " << entry.optname << ": not readable.\n"); continue; // The flag must be READABLE and WRITABLE for this. } diff --git a/test/test_sync.cpp b/test/test_sync.cpp index 9fb1b3db36..ffbbc2b282 100644 --- a/test/test_sync.cpp +++ b/test/test_sync.cpp @@ -740,14 +740,14 @@ TEST(Sync, FormatTime) const string time4 = FormatTime(a + seconds_from(1)); const string time5 = FormatTime(a + seconds_from(5)); const string time6 = FormatTime(a + milliseconds_from(-4350)); - cerr << "Current time formated: " << time1 << endl; + cerr << "Current time formatted: " << time1 << endl; const long long diff_2_1 = parse_time(time2) - parse_time(time1); - cerr << "Same time formated again: " << time2 << " (" << diff_2_1 << " us)" << endl; - print_timediff("Same time formated again: ", time2, time1); - print_timediff("Time +500 ms formated: ", time3, time1); - print_timediff("Time +1 sec formated: ", time4, time1); - print_timediff("Time +5 sec formated: ", time5, time1); - print_timediff("Time -4350 ms formated: ", time6, time1); + cerr << "Same time formatted again: " << time2 << " (" << diff_2_1 << " us)" << endl; + print_timediff("Same time formatted again: ", time2, time1); + print_timediff("Time +500 ms formatted: ", time3, time1); + print_timediff("Time +1 sec formatted: ", time4, time1); + print_timediff("Time +5 sec formatted: ", time5, time1); + print_timediff("Time -4350 ms formatted: ", time6, time1); EXPECT_TRUE(time1 == time2); } @@ -782,14 +782,14 @@ TEST(Sync, FormatTimeSys) const string time4 = FormatTimeSys(a + seconds_from(1)); const string time5 = FormatTimeSys(a + seconds_from(5)); const string time6 = FormatTimeSys(a + milliseconds_from(-4350)); - cerr << "Current time formated: " << time1 << endl; + cerr << "Current time formatted: " << time1 << endl; const long long diff_2_1 = parse_time(time2) - parse_time(time1); - cerr << "Same time formated again: " << time2 << " (" << diff_2_1 << " us)" << endl; - print_timediff("Same time formated again: ", time2, time1); - print_timediff("Time +500 ms formated: ", time3, time1); - print_timediff("Time +1 sec formated: ", time4, time1); - print_timediff("Time +5 sec formated: ", time5, time1); - print_timediff("Time -4350 ms formated: ", time6, time1); + cerr << "Same time formatted again: " << time2 << " (" << diff_2_1 << " us)" << endl; + print_timediff("Same time formatted again: ", time2, time1); + print_timediff("Time +500 ms formatted: ", time3, time1); + print_timediff("Time +1 sec formatted: ", time4, time1); + print_timediff("Time +5 sec formatted: ", time5, time1); + print_timediff("Time -4350 ms formatted: ", time6, time1); EXPECT_TRUE(time1 == time2); } diff --git a/test/test_utilities.cpp b/test/test_utilities.cpp index e8494e744b..d14f5f9cc8 100644 --- a/test/test_utilities.cpp +++ b/test/test_utilities.cpp @@ -22,7 +22,7 @@ struct Double Double(): d(0.0) { instance = ++sourceid; - IF_HEAVY_LOGGING(cerr << "(Double/" << instance << ": empty costruction)\n"); + IF_HEAVY_LOGGING(cerr << "(Double/" << instance << ": empty construction)\n"); } Double(double dd): d(dd) diff --git a/testing/srt-test-live.cpp b/testing/srt-test-live.cpp index 12478e4614..abc37e089f 100644 --- a/testing/srt-test-live.cpp +++ b/testing/srt-test-live.cpp @@ -934,7 +934,7 @@ int main( int argc, char** argv ) if (!skip_flushing) { - Verror("(DEBUG) EOF when reading file. Looping until the sending bufer depletes.\n"); + Verror("(DEBUG) EOF when reading file. Looping until the sending buffer depletes.\n"); for (;;) { size_t still = tar->Still(); @@ -1011,7 +1011,7 @@ void TestLogHandler(void* opaque, int level, const char* file, int line, const c #ifdef _MSC_VER // That's something weird that happens on Microsoft Visual Studio 2013 - // Trying to keep portability, while every version of MSVS is a different plaform. + // Trying to keep portability, while every version of MSVS is a different platform. // On MSVS 2015 there's already a standard-compliant snprintf, whereas _snprintf // is available on backward compatibility and it doesn't work exactly the same way. #define snprintf _snprintf diff --git a/testing/srt-test-mpbond.cpp b/testing/srt-test-mpbond.cpp index 157fa9728b..3635ff585b 100644 --- a/testing/srt-test-mpbond.cpp +++ b/testing/srt-test-mpbond.cpp @@ -274,7 +274,7 @@ int main( int argc, char** argv ) if (!skip_flushing) { - Verror() << "(DEBUG) EOF when reading file. Looping until the sending bufer depletes.\n"; + Verror() << "(DEBUG) EOF when reading file. Looping until the sending buffer depletes.\n"; for (;;) { size_t still = tar->Still(); diff --git a/testing/srt-test-multiplex.cpp b/testing/srt-test-multiplex.cpp index 857557a5c8..31dc0b5237 100644 --- a/testing/srt-test-multiplex.cpp +++ b/testing/srt-test-multiplex.cpp @@ -436,7 +436,7 @@ void Help(string program) "The URIs specified as -i INPUT... will be used for input and therefore SRT for output,\n" "and in the other way around if you use -o OUTPUT...\n" "For every such URI you must specify additionally a parameter named 'id', which will be\n" -"interperted by the application and used to set resource id on an SRT socket when connecting\n" +"interpreted by the application and used to set resource id on an SRT socket when connecting\n" "or to match with the id extracted from the accepted socket of incoming connection.\n" "Example:\n" "\tSender: srt-multiplex srt://remhost:2000 -i udp://:5000?id=low udp://:6000?id=high\n" @@ -501,7 +501,7 @@ int main( int argc, char** argv ) // Extra parameters: // // mode: caller/listener/rendezvous. Default: if host empty, listener, otherwise caller. - // adapter: IP to select network device for listner or rendezvous. Default: for listener taken from host, otherwise 0.0.0.0 + // adapter: IP to select network device for listener or rendezvous. Default: for listener taken from host, otherwise 0.0.0.0 // port: default=0. Used only for caller mode, sets the outgoing port number. If 0, system-selected (default behavior) // // Syntax cases for -i: @@ -511,7 +511,7 @@ int main( int argc, char** argv ) // // Syntax cases for -o: // - // EMPTY ARGS...: use 'output%.dat' file patter for every stream. + // EMPTY ARGS...: use 'output%.dat' file pattern for every stream. // PATTERN (one argument that contains % somewhere): define the output file pattern // URI...: try to match the input stream to particular URI by 'name' parameter. If none matches, ignore. diff --git a/testing/srt-test-relay.cpp b/testing/srt-test-relay.cpp index 912af555e2..f3921f2e2b 100755 --- a/testing/srt-test-relay.cpp +++ b/testing/srt-test-relay.cpp @@ -240,7 +240,7 @@ int main( int argc, char** argv ) if (input_spec != "" && input_echoback) { - cerr << "ERROR: input-echoback is treated as input specifcation, -i can't be specified together.\n"; + cerr << "ERROR: input-echoback is treated as input specification, -i can't be specified together.\n"; return 1; } @@ -375,7 +375,7 @@ SrtMainLoop::SrtMainLoop(const string& srt_uri, bool input_echoback, const strin { // Initialize input medium and do not add SRT medium // to the output list, as this will be fed directly - // by the data from this input medium in a spearate engine. + // by the data from this input medium in a separate engine. Verb() << "Setting up input: " << input_spec; m_input_medium.Setup(Source::Create(input_spec), g_chunksize); diff --git a/testing/testactivemedia.cpp b/testing/testactivemedia.cpp index dd0393ea6b..5e75ecc986 100644 --- a/testing/testactivemedia.cpp +++ b/testing/testactivemedia.cpp @@ -43,7 +43,7 @@ MediaPacket SourceMedium::Extract() } else { - // Don't worry about the media status as long as you have somthing in the buffer. + // Don't worry about the media status as long as you have something in the buffer. // Purge the buffer first, then worry about the other things. if (!running) { diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index 2c36a4d127..92262d5cab 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -1513,7 +1513,7 @@ void SrtCommon::UpdateGroupStatus(const SRT_SOCKGROUPDATA* grpdata, size_t grpda { // This happens when you passed too small array. Treat this as error and stop. cerr << "ERROR: broadcast group update reports " << grpdata_size - << " existing sockets, but app registerred only " << m_group_nodes.size() << endl; + << " existing sockets, but app registered only " << m_group_nodes.size() << endl; Error("Too many unpredicted sockets in the group"); } @@ -2251,7 +2251,7 @@ bytevector SrtSource::GroupRead(size_t chunk) if (have_ready || have_connectors) { Verb() << "(still have: " << (have_ready ? "+" : "-") << "ready, " - << (have_connectors ? "+" : "-") << "conenctors)."; + << (have_connectors ? "+" : "-") << "connectors)."; goto RETRY_READING; } From c47c3e3c59f42b9773af8b02f5740393af6c8a1b Mon Sep 17 00:00:00 2001 From: Rostyslav Kurylo Date: Wed, 4 Feb 2026 23:47:27 -0800 Subject: [PATCH 58/92] [bug][core] Fix group data length check in handshake processing (#3272) * Fix group data length check in handshake processing * Apply suggestions from code review Changed the maximum size of copied data to the size of the array. * Apply suggestion from @ethouris Fixed through a separate variable. --------- Co-authored-by: Sektor van Skijlen --- srtcore/core.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 6232da9428..0a451f26f5 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -3080,9 +3080,10 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, LOGC(cnlog.Error, log << CONID() << "PEER'S GROUP wrong size: " << (bytelen/GRPD_FIELD_SIZE)); return false; } - size_t groupdata_size = bytelen / GRPD_FIELD_SIZE; + size_t used_bytelen = std::min(bytelen, sizeof(groupdata)); + size_t groupdata_size = used_bytelen / GRPD_FIELD_SIZE; - memcpy(groupdata, begin+1, bytelen); + memcpy(groupdata, begin+1, used_bytelen); if (!interpretGroup(groupdata, groupdata_size, hsreq_type_cmd) ) { // m_RejectReason handled inside interpretGroup(). From ce54b5ea363dee0e16d4fdf2c43d96f5d896706a Mon Sep 17 00:00:00 2001 From: Maxim Sharabayko Date: Fri, 20 Feb 2026 11:49:44 +0100 Subject: [PATCH 59/92] [docs] Fixed SRTO_PASSWORD max length description. --- docs/API/API-socket-options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index 16b1f41454..aaa1401abb 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -1112,7 +1112,7 @@ For details, see [SRT Packet Filtering & FEC](../features/packet-filtering-and-f Sets the passphrase for encryption. This enables encryption on this party (or disables it, if an empty passphrase is passed). The password must be minimum -10 and maximum 79 characters long. +10 and maximum 80 characters long. The passphrase is the shared secret between the sender and the receiver. It is used to generate the Key Encrypting Key using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2) From 951a87a8a794f1ddd4ae8f88d78d845f812eea1e Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 4 Mar 2026 15:38:52 +0100 Subject: [PATCH 60/92] [core] [bug] Fixed bug: interrupting a blocking connect loop must be recognized per the same condition (#3290) Co-authored-by: Mikolaj Malecki --- srtcore/common.cpp | 2 ++ srtcore/core.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 9d0b8e338c..23b3413131 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -67,6 +67,7 @@ modified by #include "logging.h" #include "packet.h" #include "threadname.h" +#include "logger_defs.h" using namespace std; using namespace srt::sync; @@ -92,6 +93,7 @@ m_iMinor(minor) m_iErrno = NET_ERROR; else m_iErrno = err; + HLOGC(aclog.Debug, log << "CREATED SRT EXCEPTION: " << (1000*major+minor) << " errno=" << m_iErrno); } const char* srt::CUDTException::getErrorMessage() const ATR_NOTHROW diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 0a451f26f5..7b110d4323 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -4068,7 +4068,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) if (e.getErrorCode() == 0) { - if (m_bClosing) // if the socket is closed before connection... + if (m_bClosing || m_bBreaking) // if the socket is closed before connection... e = CUDTException(MJ_SETUP, MN_CLOSED, 0); else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected { From 15cb088561b86f1f1d086439cfaa5e704a3392a6 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 6 Mar 2026 11:21:24 +0100 Subject: [PATCH 61/92] [core][bug] Fixed misleading error code when connecting a broken socket (#3291) Co-authored-by: Mikolaj Malecki --- srtcore/api.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 9bdc8a71cb..80d41582be 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -2022,7 +2022,12 @@ int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, i } else { - if (s->m_Status != SRTS_OPENED) + // Only SRTS_OPENED status is otherwise expected. + // Although depending on the state, different type of errors + if (int(s->m_Status) >= SRTS_BROKEN) // BROKEN, CLOSING, CLOSED, NONEXIST + throw CUDTException(MJ_SETUP, MN_CLOSED, 0); + + if (s->m_Status != SRTS_OPENED) // LISTENING, CONNECTING, CONNECTED throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); // status = SRTS_OPENED, so family should be known already. From f04c00cdd7a04a0f57b0bd8efb68d934c7d1b4ec Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Tue, 17 Mar 2026 17:31:17 +0100 Subject: [PATCH 62/92] [build] Fixed use of CMP0077 policy in too old cmake (#3295) --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 130a0adf99..3422b73d1e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,11 +122,15 @@ endforeach() # SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ # SRT_ENABLE_FREQUENT_LOG_TRACE 0 : set to 1 to enable printing reason for suppressed freq logs -# CMake offers sometimes OLD behavior, where names that are defined below with +# CMake offers the OLD behavior, where names that are defined below with # option() function, will be deleted if they are not in cache or do not have set # type - so effectively options provided through LIBSRT_* prefix would be rejected. # We do want these variables, obviously. -cmake_policy(SET CMP0077 NEW) +# NOTE: This is introduced in cmake 3.13, so the namespaced-option feature will not +# work properly in the old cmake. +if (CMAKE_VERSION VERSION_GREATER 3.12) + cmake_policy(SET CMP0077 NEW) +endif() # Import all options preceded with LIBSRT_ likely from a parent scope. # Names are not being checked, at worst it will set an unused variable. From 44d106f491333a022351f8c105e23886aad4e248 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 30 Mar 2026 10:34:34 +0200 Subject: [PATCH 63/92] [BUG] Incorrect error code when srt_listen on closed socket (#3298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [BUG] Incorrect error code when srt_listen on closed socket * Fixed API description * Ask Codespell to ignore the TEST() macro. * Replace few if statements by a switch statement. * Status SRTS_NONEXIST throws MN_CLOSED instead of MN_ISUNBOUND * Manage the rendez-vous case first --------- Co-authored-by: Mikolaj Malecki Co-authored-by: Clément Gérouville --- docs/API/API-functions.md | 3 ++- scripts/codespell/codespell.cfg | 2 ++ srtcore/api.cpp | 41 +++++++++++++++++++------------- test/test_connection_timeout.cpp | 26 ++++++++++++++++++++ 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 928fd6aaa3..fc82776ab9 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -612,9 +612,10 @@ the listener socket to accept group connections | [`SRT_EINVPARAM`](#srt_einvparam) | Value of `backlog` is 0 or negative. | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid SRT socket. | | [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) | [`srt_bind`](#srt_bind) has not yet been called on that socket. | +| [`SRT_ESCLOSED`](#srt_esclosed) | The socket has been closed | | [`SRT_ERDVNOSERV`](#srt_erdvnoserv) | [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) flag is set to true on specified socket. | | [`SRT_EINVOP`](#srt_einvop) | Internal error (should not happen when [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) is reported). | -| [`SRT_ECONNSOCK`](#srt_econnsock) | The socket is already connected. | +| [`SRT_ECONNSOCK`](#srt_econnsock) | The socket is currently being used to establish a connection (like by `srt_connect`) | | [`SRT_EDUPLISTEN`](#srt_eduplisten) | The address used in [`srt_bind`](#srt_bind) by this socket is already occupied by another listening socket.
Binding multiple sockets to one IP address and port is allowed, as long as
[`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to true, but only one of these sockets can be set up as a listener. | | | | diff --git a/scripts/codespell/codespell.cfg b/scripts/codespell/codespell.cfg index a338746099..44f2ec9374 100644 --- a/scripts/codespell/codespell.cfg +++ b/scripts/codespell/codespell.cfg @@ -6,9 +6,11 @@ builtin = clear,rare,informal,code # Ignore words listed in this file. ignore-words = ./scripts/codespell/codespell_whitelist.txt +ignore-regex = TEST\(.*\) # Add custom dictionary file. dictionary = ./scripts/codespell/codespell_dictionary.txt,- # Skip checking files or directories. skip = ./build/*,./.git/* + diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 80d41582be..cc3bd49b75 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -1144,27 +1144,34 @@ int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog) // it could have changed the state. It could be also set listen in another // thread, so check it out. - // do nothing if the socket is already listening - if (s->m_Status == SRTS_LISTENING) - return 0; - - // a socket can listen only if is in OPENED status - if (s->m_Status != SRTS_OPENED) - throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); - - // [[using assert(s->m_Status == OPENED)]]; - - // listen is not supported in rendezvous connection setup if (s->core().m_config.bRendezvous) throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); - s->m_uiBackLog = backlog; - - // [[using assert(s->m_Status == OPENED)]]; // (still, unchanged) + switch(s->m_Status) + { + case SRTS_INIT: + throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); + break; + case SRTS_OPENED: + s->m_uiBackLog = backlog; + s->core().setListenState(); // propagates CUDTException, + s->m_Status = SRTS_LISTENING; + break; + case SRTS_LISTENING: + s->m_uiBackLog = backlog; + break; + case SRTS_CONNECTING: + case SRTS_CONNECTED: + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + break; + case SRTS_BROKEN: + case SRTS_CLOSING: + case SRTS_CLOSED: + case SRTS_NONEXIST: + throw CUDTException(MJ_SETUP, MN_CLOSED, 0); + break; + } - s->core().setListenState(); // propagates CUDTException, - // if thrown, remains in OPENED state if so. - s->m_Status = SRTS_LISTENING; return 0; } diff --git a/test/test_connection_timeout.cpp b/test/test_connection_timeout.cpp index 281376ce1f..2c44baaa4e 100644 --- a/test/test_connection_timeout.cpp +++ b/test/test_connection_timeout.cpp @@ -390,4 +390,30 @@ TEST(TestConnectionAPI, Accept) srt_cleanup(); } +TEST(TestConnectionAPI, Listen) +{ + using namespace std::chrono; + using namespace srt; + srt_startup(); + + SRTSOCKET s = srt_create_socket(); + int listen_stat1, listen_stat2, listen_stat3; + + sockaddr_any sa = srt::CreateAddr("localhost", 5555, AF_INET); + + ASSERT_NE(srt_bind(s, sa.get(), sa.size()), -1); + listen_stat1 = srt_listen(s, 1); + listen_stat2 = srt_listen(s, 5); + srt_close(s); + listen_stat3 = srt_listen(s, 5); + + int err = srt_getlasterror(NULL); + std::cout << "Listen after close error: " << srt_strerror(err, 0) << std::endl; + + EXPECT_EQ(listen_stat1, 0); + EXPECT_EQ(listen_stat2, 0); + EXPECT_EQ(listen_stat3, -1); + + srt_cleanup(); +} From c3cfa17e2a77ac6900b35030b283ec4613a46ba3 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Tue, 7 Apr 2026 09:49:14 +0200 Subject: [PATCH 64/92] [doc] Update badges in README.md (#3301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [doc] Remove a badge (Travis) * Update repology's badges --------- Co-authored-by: Clément Gérouville --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b0df582af0..9d6a6bcc6c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ [![License: MPLv2.0][license-badge]](./LICENSE) [![Latest release][release-badge]][github releases] [![codecov][codecov-badge]][codecov-project] -[![Build Status Linux and macOS][travis-badge]][travis] [![Build Status Windows][appveyor-badge]][appveyor] [![Ubuntu 24.04][Ubuntu-badge]][Ubuntu-package] @@ -216,8 +215,6 @@ By contributing code to the SRT project, you agree to license your contribution [appveyor-badge]: https://img.shields.io/appveyor/ci/Haivision/srt/master.svg?label=Windows [appveyor]: https://ci.appveyor.com/project/Haivision/srt -[travis-badge]: https://img.shields.io/travis/Haivision/srt/master.svg?label=Linux/macOS -[travis]: https://travis-ci.org/Haivision/srt [license-badge]: https://img.shields.io/badge/License-MPLv2.0-blue [Vcpkg-package]: https://repology.org/project/srt/versions @@ -236,10 +233,10 @@ By contributing code to the SRT project, you agree to license your contribution [debian-package]: https://packages.debian.org/testing/libs/libsrt1.5-gnutls [fedora-package]: https://repology.org/project/srt/versions -[fedora-badge]: https://repology.org/badge/version-for-repo/fedora_37/srt.svg +[fedora-badge]: https://repology.org/badge/version-for-repo/fedora_43/srt.svg [homebrew-package]: https://repology.org/project/srt/versions [homebrew-badge]: https://repology.org/badge/version-for-repo/homebrew/srt.svg [Ubuntu-package]: https://repology.org/project/srt/versions -[Ubuntu-badge]: https://repology.org/badge/version-for-repo/ubuntu_24_04/srt.svg +[Ubuntu-badge]: https://repology.org/badge/version-for-repo/ubuntu_26_04/srt.svg From b6b4ae990daa8193625a4ddeaeaed03023b23125 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 17 Apr 2026 08:56:57 +0200 Subject: [PATCH 65/92] [build] Add enabler variable to importing variable feature (#3307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Małecki --- CMakeLists.txt | 31 ++++++++++++++++++------------- docs/build/build-options.md | 25 ++++++++++++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3422b73d1e..d69a8d9911 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,19 +122,24 @@ endforeach() # SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ # SRT_ENABLE_FREQUENT_LOG_TRACE 0 : set to 1 to enable printing reason for suppressed freq logs -# CMake offers the OLD behavior, where names that are defined below with -# option() function, will be deleted if they are not in cache or do not have set -# type - so effectively options provided through LIBSRT_* prefix would be rejected. -# We do want these variables, obviously. -# NOTE: This is introduced in cmake 3.13, so the namespaced-option feature will not -# work properly in the old cmake. -if (CMAKE_VERSION VERSION_GREATER 3.12) - cmake_policy(SET CMP0077 NEW) -endif() - -# Import all options preceded with LIBSRT_ likely from a parent scope. -# Names are not being checked, at worst it will set an unused variable. -srt_import_parent_options() +# Blocked by a general enabler, LIBSRT_ENABLE_IMPORT_VARIABLES, to prevent accidental use. +if ( (DEFINED LIBSRT_ENABLE_IMPORT_VARIABLES) AND (LIBSRT_ENABLE_IMPORT_VARIABLES EQUAL 1)) + unset(LIBSRT_ENABLE_IMPORT_VARIABLES) + + # CMake offers sometimes OLD behavior, where names that are defined below with + # option() function, will be deleted if they are not in cache or do not have set + # type - so effectively options provided through LIBSRT_* prefix would be rejected. + # We do want these variables, obviously. + # NOTE: This is introduced in cmake 3.13, so the namespaced-option feature will not + # work properly in the old cmake. + if (CMAKE_VERSION VERSION_GREATER 3.12) + cmake_policy(SET CMP0077 NEW) + endif() + + # Import all options preceded with LIBSRT_ likely from a parent scope. + # Names are not being checked, at worst it will set an unused variable. + srt_import_parent_options() +endif() # option defaults set(ENABLE_HEAVY_LOGGING_DEFAULT OFF) diff --git a/docs/build/build-options.md b/docs/build/build-options.md index 05fcf8be4e..2ec4d454a5 100644 --- a/docs/build/build-options.md +++ b/docs/build/build-options.md @@ -20,12 +20,22 @@ document and in the [SRT CookBook](https://srtlab.github.io/srt-cookbook/getting ## Building as a subproject The CMake tool offers the ability to add a complete project as a subdirectory. -Variables used by the SRT project in this case remain in their own scope, but -all variables from the parent scope are reflected. In order to prevent name -clashes for option-designating variables, SRT provides a namespace-like -prefixing for the optional variables it uses. If you want to configure optional -variables from the level of `CMakeLists.txt` of the parent project, use the -`LIBSRT_` prefix for the option names. +If you do this with SRT, note that all variables that can be optionally set +will get values from the parent configuration file including the one from SRT. + +To allow isolation of these variables and setting them explicitly to desired +values in case when the parent project uses variables with the same names, +there's a special feature provided: set the desired variables for the SRT +project using `LIBSRT_` prefix - this way they will get the values to the +right variables, visible only in the scope of the SRT build configuration. + +NOTE: This feature needs to be generally enabled by: + +``` +set (LIBSRT_ENABLE_IMPORT_VARIABLES 1) +``` + +otherwise all other variables with `LIBSRT_` prefix will be ignored. This will not prevent the variables from being seen as derived in SRT project scope, but if you explicitly set a variable this way, it will be set to the @@ -34,9 +44,10 @@ parent project, and it will also override (locally in SRT project only) any value of a variable with the same name in the parent project. For example, if you want to set `ENABLE_SHARED=OFF` in the parent project, -simply do: +add this before importing the SRT project: ``` +set (LIBSRT_ENABLE_IMPORT_VARIABLES 1) set (LIBSRT_ENABLE_SHARED OFF) ``` From 8e68635cf1075618dd8b47074f13503b54d8b740 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 6 May 2026 10:16:03 +0200 Subject: [PATCH 66/92] [BUG] Fixed a heavy-logging instruction that may crash on initialization (#3310) * [BUG] Fixed a heavy-logging instruction that may crash on initialization * Protect calls to logger functions by checking on src_config NULL --------- Co-authored-by: Mikolaj Malecki --- srtcore/common.cpp | 4 +++- srtcore/logging.h | 21 ++++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/srtcore/common.cpp b/srtcore/common.cpp index 23b3413131..b301b80a99 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -93,7 +93,9 @@ m_iMinor(minor) m_iErrno = NET_ERROR; else m_iErrno = err; - HLOGC(aclog.Debug, log << "CREATED SRT EXCEPTION: " << (1000*major+minor) << " errno=" << m_iErrno); + // XXX No logging allowed because this constructor can be also used for + // initializing global object. That problem should be solved separately. + // HLOGC(aclog.Debug, log << "CREATED SRT EXCEPTION: " << (1000*major+minor) << " errno=" << m_iErrno); } const char* srt::CUDTException::getErrorMessage() const ATR_NOTHROW diff --git a/srtcore/logging.h b/srtcore/logging.h index 7782245a25..dd9be95a81 100644 --- a/srtcore/logging.h +++ b/srtcore/logging.h @@ -189,6 +189,7 @@ struct SRT_API LogDispatcher ~LogDispatcher() { + src_config = NULL; // Allow to call CheckEnabled after destruction } bool CheckEnabled(); @@ -390,6 +391,12 @@ struct LogDispatcher::Proxy #endif + +// IMPORTANT: +// 1. Logger objects are only allowed to be global. The CheckEnabled +// method RELIES ON THAT it may run before the Logger() constructor is run. +// 2. Destructor resets src_config to NULL to prevent any logging instruction +// to access the facility after destruction. class Logger { int m_fa; @@ -425,12 +432,16 @@ inline bool LogDispatcher::CheckEnabled() // 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(); + if (config) + { + 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; + return configured_enabled_fa && level <= configured_maxlevel; + } + return false; } From 604a406546c1e875423477290080b310f70b7169 Mon Sep 17 00:00:00 2001 From: Matthew Szatmary Date: Mon, 18 May 2026 01:29:15 -0700 Subject: [PATCH 67/92] [BUG] Validate KMRSP wire length to prevent stack overflow (#3319) --- srtcore/crypto.cpp | 14 ++++++++++++++ test/test_crypto.cpp | 28 +++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index 68d551e589..20c8ba47e9 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -370,6 +370,20 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) { + // Validate the wire-supplied length before using it: + // - oversize would overflow the fixed-size stack buffer below; + // - non-word-aligned or too-small payloads are malformed by protocol and would + // feed uninitialised stack into downstream key-matching logic. + if (len > SRT_CMD_MAXSZ + || len < sizeof(uint32_t) + || (len % sizeof(uint32_t)) != 0) + { + LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: malformed len " << len + << " (must be a non-zero multiple of " << sizeof(uint32_t) + << ", up to " << SRT_CMD_MAXSZ << ") - rejecting"); + return SRT_CMD_NONE; + } + /* All 32-bit msg fields (if present) swapped on reception * But HaiCrypt expect network order message * Re-swap to cancel it. diff --git a/test/test_crypto.cpp b/test/test_crypto.cpp index 47b18dd1ac..466497d459 100644 --- a/test/test_crypto.cpp +++ b/test/test_crypto.cpp @@ -1,10 +1,36 @@ #include #include +#include #include "gtest/gtest.h" -#if defined(SRT_ENABLE_ENCRYPTION) && defined(ENABLE_AEAD_API_PREVIEW) #include "crypto.h" +#include "handshake.h" + +// processSrtMsg_KMRSP must reject malformed wire-supplied lengths before they +// reach the fixed-size stack buffer / uninitialised-read paths inside the +// function. Built into the library unconditionally, so this test runs +// regardless of SRT_ENABLE_ENCRYPTION. +TEST(CryptoKMRSP, RejectsMalformedLengths) +{ + srt::CCryptoControl crypt(0); + std::vector garbage(SRT_CMD_MAXSZ, 0); + const unsigned srtv = srt::SrtVersion(1, 5, 3); + + // Oversize: would overflow uint32_t srtd[SRTDATA_MAXSIZE]. + EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), SRT_CMD_MAXSZ + sizeof(uint32_t), srtv), + srt::SRT_CMD_NONE); + + // Non-word-aligned: silently drops bytes and risks misinterpretation. + EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 7, srtv), srt::SRT_CMD_NONE); + + // Empty / under-a-word: HtoNLA writes nothing and downstream code would read + // uninitialised stack from srtd[]. + EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 0, srtv), srt::SRT_CMD_NONE); + EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 3, srtv), srt::SRT_CMD_NONE); +} + +#if defined(SRT_ENABLE_ENCRYPTION) && defined(ENABLE_AEAD_API_PREVIEW) #include "hcrypt.h" // Imports the CRYSPR_HAS_AESGCM definition. #include "socketconfig.h" From 798f660be1d3f6a9bcacf51165678a640808c6fa Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 18 May 2026 10:37:25 +0200 Subject: [PATCH 68/92] [core] Vulnerability fixed: unchecked buffer size for KMREQ (#3317) * [core] Vulnerability fixed: unchecked buffer size for KMREQ * force word-aligned data length. Co-authored-by: Matthew Szatmary --------- Co-authored-by: Mikolaj Malecki Co-authored-by: cl-ment Co-authored-by: Matthew Szatmary --- srtcore/core.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 7b110d4323..3d31ba3318 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -2184,8 +2184,12 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t srtdata_out[SRTDATA_MAXSIZE]; size_t len_out = 0; - res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, - (srtdata_out), (len_out)); + if (len % sizeof(srtdata_out[0]) == 0 && len <= sizeof srtdata_out) + { + res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, + (srtdata_out), (len_out)); + } // On error, it will stay with NONE and reject the packet + if (res == SRT_CMD_KMRSP) { if (len_out == 1) @@ -2208,11 +2212,11 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) } sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); } - // XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now. - // Please review later. + // NOTE: processSrtMsg_KMREQ doesn't return any other value now. + // But this may be a result of the size check EPE. else { - LOGC(cnlog.Warn, log << CONID() << "KMREQ failed to process the request - ignoring"); + LOGC(cnlog.Warn, log << CONID() << "EPE: KMREQ failed to process the request - ignoring"); } return true; // already done what's necessary From d7957ac5d97a1883e1c5a4eb6afe3cd2f2b23085 Mon Sep 17 00:00:00 2001 From: Matthew Szatmary Date: Tue, 19 May 2026 00:45:27 -0700 Subject: [PATCH 69/92] [BUG] Fixed OOB read in DROPREQ payload parsing (#3320) --- srtcore/core.cpp | 8 +++++ srtcore/core.h | 1 + test/filelist.maf | 1 + test/test_control_packets.cpp | 57 +++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 test/test_control_packets.cpp diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 3d31ba3318..e6087fd686 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9325,6 +9325,14 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { + // dropdata[0..1] are indexed unconditionally below. + if (ctrlpkt.getLength() < 2 * sizeof(int32_t)) + { + LOGC(inlog.Warn, log << CONID() << "DROPREQ: payload " << ctrlpkt.getLength() + << " bytes < " << (2 * sizeof(int32_t)) << " - rejecting"); + return; + } + const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; { diff --git a/srtcore/core.h b/srtcore/core.h index e5fea5d949..a4a9acaeeb 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -307,6 +307,7 @@ class CUDT friend class PacketFilter; friend class CUDTGroup; friend class TestMockCUDT; // unit tests + friend class TestMockControlPackets; // unit tests typedef sync::steady_clock::time_point time_point; typedef sync::steady_clock::duration duration; diff --git a/test/filelist.maf b/test/filelist.maf index 7ae5693b19..e39d6ac592 100644 --- a/test/filelist.maf +++ b/test/filelist.maf @@ -7,6 +7,7 @@ test_main.cpp test_buffer_rcv.cpp test_common.cpp test_connection_timeout.cpp +test_control_packets.cpp test_crypto.cpp test_cryspr.cpp test_enforced_encryption.cpp diff --git a/test/test_control_packets.cpp b/test/test_control_packets.cpp new file mode 100644 index 0000000000..6b7f322c5e --- /dev/null +++ b/test/test_control_packets.cpp @@ -0,0 +1,57 @@ +#include "gtest/gtest.h" +#include "test_env.h" + +#include "srt.h" +#include "api.h" +#include "core.h" +#include "packet.h" + +using namespace srt; + +namespace srt { + // Friend wrapper for unit tests that drive private CUDT control-packet + // entry points. Declared as a friend in core.h. + class TestMockControlPackets + { + public: + CUDT* core; + + void processCtrlDropReq(const CPacket& pkt) { core->processCtrlDropReq(pkt); } + int32_t rcvCurrSeqNo() const { return core->m_iRcvCurrSeqNo; } + void setRcvCurrSeqNo(int32_t v) { core->m_iRcvCurrSeqNo = v; } + }; +} + +// processCtrlDropReq must reject DROPREQs whose payload is smaller than two +// seqno words; otherwise dropdata[1] reads past the wire payload. +TEST(ControlPackets, DropReqRejectsShortPayload) +{ + srt::TestInit srtinit; + + CUDTSocket* s1 = NULL; + SRTSOCKET sid1 = CUDT::uglobal().newSocket(&s1); + ASSERT_NE(sid1, SRT_INVALID_SOCK); + + TestMockControlPackets m1; + m1.core = &s1->core(); + + const int32_t sentinel = 100; + m1.setRcvCurrSeqNo(sentinel); + + CPacket pkt; + pkt.allocate(1500); + + // Each of these is shorter than the 8-byte minimum and must be rejected + // by the guard at the top of processCtrlDropReq. + const size_t short_lens[] = { 0, 1, 4, 7 }; + for (size_t i = 0; i < sizeof(short_lens) / sizeof(short_lens[0]); ++i) + { + pkt.setLength(short_lens[i]); + m1.processCtrlDropReq(pkt); + EXPECT_EQ(m1.rcvCurrSeqNo(), sentinel) + << "DROPREQ with payload " << short_lens[i] << " bytes must not be processed"; + } + + pkt.deallocate(); + srt_close(sid1); +} From 033d1d547f8d344c2275c98f6c0357860e3edbb3 Mon Sep 17 00:00:00 2001 From: Matthew Szatmary Date: Thu, 21 May 2026 01:28:16 -0700 Subject: [PATCH 70/92] [BUG] Fixed OOB read in LOSSREPORT range parsing (#3324) --- srtcore/core.cpp | 8 +++++++ test/test_control_packets.cpp | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index e6087fd686..e4577a27d2 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -9070,6 +9070,14 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) // IF the loss is a range if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) { + // The range-first marker means the next cell holds HI. Reject if it isn't present + // in the body — otherwise losslist[i+1] reads past the wire payload. + if (i + 1 >= n) + { + LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT: trailing range-first word with no HI - DISCARDING"); + secure = false; + break; + } // Then it's this is a specification with HI in a consecutive cell. const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]); const int32_t losslist_hi = losslist[i + 1]; diff --git a/test/test_control_packets.cpp b/test/test_control_packets.cpp index 6b7f322c5e..98ad378317 100644 --- a/test/test_control_packets.cpp +++ b/test/test_control_packets.cpp @@ -17,8 +17,10 @@ namespace srt { CUDT* core; void processCtrlDropReq(const CPacket& pkt) { core->processCtrlDropReq(pkt); } + void processCtrlLossReport(const CPacket& pkt) { core->processCtrlLossReport(pkt); } int32_t rcvCurrSeqNo() const { return core->m_iRcvCurrSeqNo; } void setRcvCurrSeqNo(int32_t v) { core->m_iRcvCurrSeqNo = v; } + bool isBroken() const { return core->m_bBroken; } }; } @@ -55,3 +57,40 @@ TEST(ControlPackets, DropReqRejectsShortPayload) pkt.deallocate(); srt_close(sid1); } + +// processCtrlLossReport must reject a LOSSREPORT whose final cell carries +// the LOSSDATA_SEQNO_RANGE_FIRST marker but has no HI cell behind it; +// otherwise losslist[i+1] reads past the wire payload (4-byte OOB read of +// adjacent heap). The handler should mark the connection broken via the +// `secure = false` path. +TEST(ControlPackets, LossReportRejectsTrailingRangeFirst) +{ + srt::TestInit srtinit; + + CUDTSocket* s = NULL; + SRTSOCKET sid = CUDT::uglobal().newSocket(&s); + ASSERT_NE(sid, SRT_INVALID_SOCK); + + TestMockControlPackets m; + m.core = &s->core(); + ASSERT_FALSE(m.isBroken()) << "fresh socket should not be marked broken"; + + // Single 4-byte payload, high bit set => LOSSDATA_SEQNO_RANGE_FIRST. + // Without the guard, the handler would dereference losslist[1] (the + // missing HI cell), reading 4 bytes past the packet payload. + CPacket pkt; + pkt.allocate(64); + int32_t* data = (int32_t*) pkt.m_pcData; + data[0] = SEQNO_VALUE::wrap(100) | LOSSDATA_SEQNO_RANGE_FIRST; + pkt.setLength(sizeof(int32_t)); + pkt.setControl(UMSG_LOSSREPORT); + + m.processCtrlLossReport(pkt); + + EXPECT_TRUE(m.isBroken()) + << "LOSSREPORT with trailing range-first marker must take the " + "secure=false bail path and mark the connection broken"; + + pkt.deallocate(); + srt_close(sid); +} From 2e2ab20ee0f2eb43cf0fe974695aafec9767654f Mon Sep 17 00:00:00 2001 From: Matthew Szatmary Date: Thu, 21 May 2026 05:20:43 -0700 Subject: [PATCH 71/92] [BUG] Reject CRcvBuffer::dropMessage range past buffer end (#3322) dropMessage early-returns when offset_b < 0 (the entire range is before the buffer start), but does not symmetrically guard against offset_a >= m_szSize (the entire range is past the buffer end). In that case start_off = offset_a is unbounded; incPos() wraps modulo m_szSize and the loop walks legitimate buffer entries, marking them EntryState_Drop -- corrupting receiver state from a single malformed call. Add a symmetric early return for offset_a >= m_szSize. Add CRcvBufferReadMsg.PacketDropLoPastBufferEnd covering the new guard: insert two packets, request a drop whose lo is well past the buffer end, assert no packets were dropped and the buffered packets are still readable. Co-authored-by: Claude Opus 4.7 (1M context) --- srtcore/buffer_rcv.cpp | 4 ++-- test/test_buffer_rcv.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp index ae4898eb2d..88f94103ba 100644 --- a/srtcore/buffer_rcv.cpp +++ b/srtcore/buffer_rcv.cpp @@ -276,10 +276,10 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi); - if (offset_b < 0) + if (offset_b < 0 || offset_a >= (int) m_szSize) { LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; " - << seqnohi << "]. Buffer start " << m_iStartSeqNo << "."); + << seqnohi << "]. Buffer start " << m_iStartSeqNo << " size " << m_szSize << "."); return 0; } diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp index 90950911fc..5e766de46f 100644 --- a/test/test_buffer_rcv.cpp +++ b/test/test_buffer_rcv.cpp @@ -296,6 +296,34 @@ TEST_F(CRcvBufferReadMsg, PacketDropBySeqNo) EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity()); } +// Drop ranges whose low seqno is already past the buffer end must be a +// no-op. Without the offset_a >= m_szSize guard, incPos() wraps modulo +// m_szSize and the loop walks legitimate entries -- mirroring the DoS +// shape of a reversed range, just triggered from the other side. +TEST_F(CRcvBufferReadMsg, PacketDropLoPastBufferEnd) +{ + EXPECT_EQ(addMessage(1, 1, m_init_seqno), 0); + EXPECT_EQ(addMessage(1, 2, CSeqNo::incseq(m_init_seqno)), 0); + + auto& rcv_buffer = *m_rcv_buffer.get(); + EXPECT_TRUE(hasAvailablePackets()); + + // Buffer size is m_buff_size_pkts (16); pick a range that begins + // well past the last valid slot. + const int32_t lo = m_init_seqno + 2 * m_buff_size_pkts; + const int32_t hi = lo + 4; + EXPECT_EQ(rcv_buffer.dropMessage(lo, hi, SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING), 0); + + // The two packets we inserted must still be present and readable. + EXPECT_TRUE(hasAvailablePackets()); + EXPECT_TRUE(rcv_buffer.isRcvDataReady()); + array buff; + EXPECT_EQ(readMessage(buff.data(), buff.size()), (int) m_payload_sz); + EXPECT_TRUE(verifyPayload(buff.data(), m_payload_sz, m_init_seqno)); + EXPECT_EQ(readMessage(buff.data(), buff.size()), (int) m_payload_sz); + EXPECT_TRUE(verifyPayload(buff.data(), m_payload_sz, CSeqNo::incseq(m_init_seqno))); +} + // Test dropping a message by message number and sequence number. TEST_F(CRcvBufferReadMsg, PacketDropByMsgNoSeqNo) { From 1e4c908c5130fb0fd5ed9708562eb0ef3fe48997 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Fri, 5 Jun 2026 10:26:00 +0200 Subject: [PATCH 72/92] [core] Removed unnecessary post-cleaning of closed socket in the cleanup. (#3327) * Trial changes for SRT package * [core] Added death-garbage-aware cleanup * post fixes * Fixed: removed special procedure for cleanup on final destruction. Removed cleaning up sockets after GC exit. Added description * Renamed forceRemove * Fixed codespell --------- Co-authored-by: Mikolaj Malecki --- docs/API/API-functions.md | 16 +++++-- scripts/win-installer/build-win-installer.ps1 | 44 ++++++++++++++++--- srtcore/api.cpp | 4 +- srtcore/queue.cpp | 21 ++++++--- srtcore/queue.h | 4 +- test/test_bonding.cpp | 2 +- 6 files changed, 72 insertions(+), 19 deletions(-) diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index fc82776ab9..0e6da34b62 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -269,14 +269,24 @@ 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. +**IMPORTANT NOTES**: + +1. This function must be called from within `main()`, preferably at the end. +Calling it from any C++ global destructor is pointless, as SRT does it by +itself. But relying on it is strongly discouraged - at best only if you are +completely certain that all resources in the application are maintained in the +strict creation-destruction order, including threads. If this condition isn't +satisfied, then the behavior of the cleanup outside of `main()` is undefined. + +2. 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 +`srt_cleanup` function exactly the same number of times. + | Returns | | |:----------------------------- |:--------------------------------------------------------------- | | 0 | A possibility to return other values 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 -`srt_cleanup` function exactly the same number of times. [:arrow_up:   Back to List of Functions & Structures](#srt-api-functions) diff --git a/scripts/win-installer/build-win-installer.ps1 b/scripts/win-installer/build-win-installer.ps1 index f486eac0ba..c788e01abf 100644 --- a/scripts/win-installer/build-win-installer.ps1 +++ b/scripts/win-installer/build-win-installer.ps1 @@ -37,7 +37,8 @@ param( [string]$Version = "", [switch]$NoBuild = $false, - [switch]$NoPause = $false + [switch]$NoPause = $false, + [string]$OnlyPlatform = "" ) Write-Output "Building the SRT static libraries installer for Windows" @@ -113,6 +114,9 @@ function ssl-lib($pf, $conf, $lib) { return "$(ssl-libdir $pf $conf)\$($LIBFILE. Write-Output "Searching OpenSSL libraries ..." $Missing = 0 foreach ($Platform in $SSL.Keys) { + if ($OnlyPlatform -ne "" -and $Platform -ne $OnlyPlatform) { + continue + } if (-not (Test-Path -PathType Container $(ssl-include $Platform))) { Write-Output "**** Missing $(ssl-include $Platform)" $Missing = $Missing + 1 @@ -173,7 +177,16 @@ Write-Output "NSIS: $NSIS" #----------------------------------------------------------------------------- if (-not $NoBuild) { + Write-Output "BUILD ENABLED - configuring build" + if ($OnlyPlatform -ne "") { + Write-Output "LIMIT TO PLATFORM: $OnlyPlatform" + } foreach ($Platform in $ARCH.Keys) { + if ($OnlyPlatform -ne "" -and $Platform -ne $OnlyPlatform) { + Write-Output "Skipping platform $Platform." + continue + } + Write-Output "Preparing platform $Platform." # Build directory. Cleanup to force a fresh cmake config. $BuildDir = "$TmpDir\build.$Platform" Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir @@ -181,11 +194,15 @@ if (-not $NoBuild) { # Run CMake. Write-Output "Configuring build for platform $Platform ..." - & $CMake -S $RepoDir -B $BuildDir -A $Platform ` - -DENABLE_STDCXX_SYNC=ON ` - -DOPENSSL_INCLUDE_DIR="$(ssl-include $Platform)" ` - -DOPENSSL_ROOT_DIR="$(ssl-libdir $Platform Release)" - + $params = @("-S", "$RepoDir", + "-B", "$BuildDir", + "-A", "$Platform", + "-DENABLE_STDCXX_SYNC=ON", + "-DOPENSSL_INCLUDE_DIR=$(ssl-include $Platform)", + "-DOPENSSL_ROOT_DIR=$(ssl-libdir $Platform Release)" + ) + Write-Output "Running: cmake $params" + & $CMake $params # Patch version string in version.h Get-Content "$BuildDir\version.h" | ForEach-Object { @@ -197,7 +214,17 @@ if (-not $NoBuild) { # Compile SRT. Write-Output "Building for platform $Platform ..." foreach ($Conf in $LIBDIR.Keys) { - & $MSBuild "$BuildDir\SRT.sln" /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static + # The solution file format depends on the CMake version. + # Try new XML solution file format first. + $SolFile = "$BuildDir\SRT.slnx" + if (-not (Test-Path $SolFile)) { + # Try legacy solution file format. + $SolFile = "$BuildDir\SRT.sln" + } + if (-not (Test-Path $SolFile)) { + Exit-Script "Solution file $BuildDir\SRT.sln[x] not found, check CMake output" + } + & $MSBuild $SolFile /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static } } } @@ -207,6 +234,9 @@ Write-Output "Checking compiled libraries ..." $Missing = 0 foreach ($Conf in $LIBDIR.Keys) { foreach ($Platform in $SSL.Keys) { + if ($OnlyPlatform -ne "" -and $Platform -ne $OnlyPlatform) { + continue + } $Path = "$TmpDir\build.$Platform\$Conf\srt_static.lib" if (-not (Test-Path $Path)) { Write-Output "**** Missing $Path" diff --git a/srtcore/api.cpp b/srtcore/api.cpp index cc3bd49b75..7b57095717 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -222,7 +222,9 @@ srt::CUDTUnited::~CUDTUnited() enterCS(m_InitLock); stopGarbageCollector(); leaveCS(m_InitLock); - closeAllSockets(); + + // DO NOT call closeAllSockets() here; instead rely on that + // all sockets have been closed when exiting the GC. releaseMutex(m_GlobControlLock); releaseMutex(m_IDLock); releaseMutex(m_InitLock); diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 4ced92d6f9..69eb63a8d9 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -1251,7 +1251,7 @@ void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CCh } } -void* srt::CRcvQueue::worker(void* param) +void* srt::CRcvQueue::worker(void* param) ATR_NOEXCEPT { CRcvQueue* self = (CRcvQueue*)param; sockaddr_any sa(self->getIPversion()); @@ -1328,7 +1328,11 @@ void* srt::CRcvQueue::worker(void* param) << "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway."); } cst = CONN_REJECT; - break; + + // DO NOT interrupt though - the worker thread must run until all + // sockets are removed from the multiplexer. Alternatively you can forcefully + // remove all sockets from the receive U list. + continue; } // OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue. @@ -1350,10 +1354,7 @@ void* srt::CRcvQueue::worker(void* param) { HLOGC(qrlog.Debug, log << CUDTUnited::CONID(u->m_SocketID) << " SOCKET broken, REMOVING FROM RCV QUEUE/MAP."); - // the socket must be removed from Hash table first, then RcvUList - self->m_pHash->remove(u->m_SocketID); - self->m_pRcvUList->remove(u); - u->m_pRNode->m_bOnList = false; + self->removeFromLists(u); } ul = self->m_pRcvUList->m_pUList; @@ -1842,6 +1843,14 @@ void srt::CRcvQueue::storePktClone(int32_t id, const CPacket& pkt) } } +void srt::CRcvQueue::removeFromLists(CUDT* u) +{ + // the socket must be removed from Hash table first, then RcvUList + m_pHash->remove(u->m_SocketID); + m_pRcvUList->remove(u); + u->m_pRNode->m_bOnList = false; +} + void srt::CMultiplexer::resetAtFork() { if (m_pRcvQueue != NULL) diff --git a/srtcore/queue.h b/srtcore/queue.h index 930ddd92a5..2b537a59d9 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -517,9 +517,11 @@ class CRcvQueue int getIPversion() { return m_iIPversion; } + void removeFromLists(CUDT* u); + void stop(); private: - static void* worker(void* param); + static void* worker(void* param) ATR_NOEXCEPT; sync::CThread m_WorkerThread; // Subroutines of worker EReadStatus worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr_any& sa); diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 11d2904f66..fa20b6098a 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -530,7 +530,7 @@ TEST(Bonding, Options) #if SRT_ENABLE_ENCRYPTION - uint32_t kms = -1; + int32_t kms = -1; EXPECT_NE(srt_getsockflag(grp, SRTO_KMSTATE, &kms, &optsize), SRT_ERROR); EXPECT_EQ(optsize, (int) sizeof kms); From 2dc8c18c7e258a836e5895e577eb8a33fc05c777 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 8 Jun 2026 09:42:39 +0200 Subject: [PATCH 73/92] fix: searchParameters separator (#3328) --- docs/apps/srt-live-transmit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/apps/srt-live-transmit.md b/docs/apps/srt-live-transmit.md index 48741df236..718508ba0c 100644 --- a/docs/apps/srt-live-transmit.md +++ b/docs/apps/srt-live-transmit.md @@ -236,7 +236,7 @@ the presence of the **adapter** parameter: * Caller mode: if you specify *host* part, but not **adapter** parameter: - `srt://remote.host.com:1234` * Rendezvous mode: if you specify *host* AND **adapter** parameter: - - `srt://remote.host.com:1234&adapter=my.remote.addr` + - `srt://remote.host.com:1234?adapter=my.remote.addr` Sometimes the required parameter specification results in a different mode than desired; in this case you should specify the mode explicitly. From c39196c9a568ae4e3289dd65cf54ba4154deb4a1 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Mon, 8 Jun 2026 14:31:38 +0200 Subject: [PATCH 74/92] [CI] Added configuration for Linux with various matrix combinations (#3300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [CI] Added configuration for Linux with various matrix combinations * Some minor fixes * Fixed YAML syntax error * Fixed C++ std spec * Consolidated build options * Fixed exclude list * Fixed exclude list * Fixed matrix combinations * Replaced with two linear matrices * Fixed description * Changed names and split to single files * Blocked matrix, added variants to C++03 * Fixed wrong config on C++03 * Fixed names * Small fixes in crypto matrix * Removed crypto builds. Added MinGW build * Fixed MinGW typo. Added core handling for Linux * Added more platforms with C++11. Unset -Werror for MinGW * Added fixes per build break on MinGW * More Windows fixes * Fixed invalid specialization after fixing for Windows * More fixes for Windows and MinGW * Fixed invalid definition for UDP socket in testing apps * Renamed Windows workflow * Fixed multiple machines for Linux/C++11 syntax * Linux matrix: blocked gdb for other than ubuntu-latest * Linux matrix: blocked gdb for other than ubuntu-latest (fixed syntax error) * Separated preinstallation action to track the problem * Configured noenc on platforms with no SSL available * Separated preinstallation action to track the problem 2 * Blocked installation except ubuntu-latest * Removed slim as it has no access to cmake --------- Co-authored-by: Mikołaj Małecki --- .../blocked-workflows/ubuntu-c++11-crypto.yml | 37 ++++++++++ .github/blocked-workflows/ubuntu-matrix.yml | 67 +++++++++++++++++++ .github/workflows/cxx03-ubuntu.yaml | 29 -------- .github/workflows/cxx11-ubuntu.yaml | 27 -------- .../workflows/{cxx11-macos.yaml => macos.yml} | 4 +- .github/workflows/ubuntu-c++03.yml | 42 ++++++++++++ .github/workflows/ubuntu-c++11.yml | 46 +++++++++++++ .github/workflows/ubuntu-mingw.yml | 27 ++++++++ ...{cxx11-win.yaml => windows-msvc-noenc.yml} | 4 +- apps/apputil.cpp | 2 +- apps/socketoptions.hpp | 10 ++- apps/srt-live-transmit.cpp | 6 +- apps/transmitbase.hpp | 4 +- apps/transmitmedia.cpp | 20 +++--- srtcore/channel.cpp | 2 + srtcore/srt.h | 2 + testing/srt-test-relay.cpp | 2 +- testing/testmedia.cpp | 6 +- testing/testmedia.hpp | 8 +-- 19 files changed, 258 insertions(+), 87 deletions(-) create mode 100644 .github/blocked-workflows/ubuntu-c++11-crypto.yml create mode 100644 .github/blocked-workflows/ubuntu-matrix.yml delete mode 100644 .github/workflows/cxx03-ubuntu.yaml delete mode 100644 .github/workflows/cxx11-ubuntu.yaml rename .github/workflows/{cxx11-macos.yaml => macos.yml} (96%) create mode 100644 .github/workflows/ubuntu-c++03.yml create mode 100644 .github/workflows/ubuntu-c++11.yml create mode 100644 .github/workflows/ubuntu-mingw.yml rename .github/workflows/{cxx11-win.yaml => windows-msvc-noenc.yml} (94%) diff --git a/.github/blocked-workflows/ubuntu-c++11-crypto.yml b/.github/blocked-workflows/ubuntu-c++11-crypto.yml new file mode 100644 index 0000000000..bfe62a8eef --- /dev/null +++ b/.github/blocked-workflows/ubuntu-c++11-crypto.yml @@ -0,0 +1,37 @@ +name: Linux + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + types: [opened, synchronize, reopened] +jobs: + build: + strategy: + matrix: + crypto: [OFF, openssl, openssl-evp, gnutls, mbedtls, botan] + + name: Build Crypto=${{ matrix.crypto }} C++11 + runs-on: [ubuntu-latest, ubuntu-slim, ubuntu-24.04-arm] + steps: + - uses: actions/checkout@v3 + - name: Configure packages and system + run: | + sudo apt install -y tcl cmake libssl-dev libgnutls28-dev libmbedtls-dev libbotan-2-dev gdb + echo "core.%e" | sudo tee /proc/sys/kernel/core_pattern + - name: Configure + run: | + mkdir _build && cd _build + if [[ ${{ matrix.crypto }} != OFF ]]; then CRYPTO_OPTIONS=-DUSE_ENCLIB=${{ matrix.crypto }}; else CRYPTO_OPTIONS=-DENABLE_ENCRYPTION=OFF; fi + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DUSE_CXX_STD=11 -DENABLE_STDCXX_SYNC=ON $CRYPTO_OPTIONS -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_HEAVY_LOGGING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: Build + run: cd _build && make -j -k + - name: test + run: | + ulimit -c unlimited + cd _build && ctest --extra-verbose + SUCCESS=$? + if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + test $SUCCESS == 0; + diff --git a/.github/blocked-workflows/ubuntu-matrix.yml b/.github/blocked-workflows/ubuntu-matrix.yml new file mode 100644 index 0000000000..c820e594f7 --- /dev/null +++ b/.github/blocked-workflows/ubuntu-matrix.yml @@ -0,0 +1,67 @@ +name: Linux + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + types: [opened, synchronize, reopened] +jobs: + build_options: + strategy: + matrix: + cxxstdsync: [OFF, ON] + logging: [OFF, ON] + bonding: [OFF, ON] + cxxstd: ["03", "11"] + exclude: + - cxxstd: "11" + logging: ON + bonding: ON + - cxxstd: "03" + cxxstdsync: ON + bonding: OFF + name: Build C++${{ matrix.cxxstd }} C++sync=${{ matrix.cxxstdsync }} Bonding=${{ matrix.bonding }} Logging=${{ matrix.logging }} + runs-on: [ubuntu-latest, ubuntu-slim, ubuntu-24.04-arm] + steps: + - uses: actions/checkout@v3 + - name: Configure packages and system + run: | + sudo apt install -y tcl cmake libssl-dev libmbedtls-dev libbotan-2-dev gdb + echo "core.%e" | sudo tee /proc/sys/kernel/core_pattern + - name: Configure + run: | + mkdir _build && cd _build + # NOTE > Crypto options are default - enabled, openssl + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DUSE_CXX_STD=${{ matrix.cxxstd }} -DENABLE_STDCXX_SYNC=${{ matrix.cxxstdsync }} -DENABLE_UNITTESTS=ON -DENABLE_BONDING=${{ matrix.bonding }} -DENABLE_LOGGING=${{ matrix.logging }} -DENABLE_HEAVY_LOGGING=${{ matrix.logging }} -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: Build + run: cd _build && make -j -k + - name: test + run: | + ulimit -c unlimited + cd _build && ctest --extra-verbose + SUCCESS=$? + if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + test $SUCCESS == 0; + + - name: codecov + run: | + source ./scripts/collect-gcov.sh + bash <(curl -s https://codecov.io/bash) + + build_mingw: + name: Build Mingw32 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Dependencies + run: | + sudo apt install -y g++-mingw-w64-x86-64-posix gcc-mingw-w64-x86-64-posix + - name: Configure + run: | + mkdir _build && cd _build + export CC=x86_64-w64-mingw32-gcc-posix CXX=x86_64-w64-mingw32-g++-posix + # NOTE > Temporarily blocked encryption due to problems with enclib + cmake .. -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=OFF -DUSE_OPENSSL_PC=OFF -DCMAKE_SYSTEM_NAME=Windows + - name: Build + run: cd _build && make -j -k diff --git a/.github/workflows/cxx03-ubuntu.yaml b/.github/workflows/cxx03-ubuntu.yaml deleted file mode 100644 index ce27436aa1..0000000000 --- a/.github/workflows/cxx03-ubuntu.yaml +++ /dev/null @@ -1,29 +0,0 @@ -name: C++03 (old compat) - -on: - push: - branches: [ "master", "dev" ] - pull_request: - branches: [ "master", "dev" ] - types: [opened, synchronize, reopened] -jobs: - build: - name: ubuntu - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: configure - run: | - mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=OFF -DUSE_CXX_STD=c++03 -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - - name: build - # That below is likely SonarQube remains, which was removed earlier. - #run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . - run: cd _build && cmake --build . - - name: test - run: | - cd _build && ctest --extra-verbose - - name: codecov - run: | - source ./scripts/collect-gcov.sh - bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/cxx11-ubuntu.yaml b/.github/workflows/cxx11-ubuntu.yaml deleted file mode 100644 index eda7ecca3e..0000000000 --- a/.github/workflows/cxx11-ubuntu.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: C++11 - -on: - push: - branches: [ "master", "dev" ] - pull_request: - branches: [ "master", "dev" ] - types: [opened, synchronize, reopened] -jobs: - build: - name: ubuntu - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: configure - run: | - mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - - name: build - run: cd _build && cmake --build . - - name: test - run: | - cd _build && ctest --extra-verbose - - name: codecov - run: | - source ./scripts/collect-gcov.sh - bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/cxx11-macos.yaml b/.github/workflows/macos.yml similarity index 96% rename from .github/workflows/cxx11-macos.yaml rename to .github/workflows/macos.yml index b03b4f75ff..7ec1cfb802 100644 --- a/.github/workflows/cxx11-macos.yaml +++ b/.github/workflows/macos.yml @@ -1,4 +1,4 @@ -name: C++11 +name: MacOS on: push: @@ -8,7 +8,7 @@ on: jobs: build: - name: macos + name: C++11 noenc runs-on: macos-latest steps: diff --git a/.github/workflows/ubuntu-c++03.yml b/.github/workflows/ubuntu-c++03.yml new file mode 100644 index 0000000000..3f38598cad --- /dev/null +++ b/.github/workflows/ubuntu-c++03.yml @@ -0,0 +1,42 @@ +name: Ubuntu C++03 + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + types: [opened, synchronize, reopened] +jobs: + build: + strategy: + matrix: + logging: [OFF, ON] + bonding: [OFF, ON] + exclude: + - bonding: OFF + logging: OFF + name: logging=${{ matrix.logging }} bonding=${{ matrix.bonding }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: configure + run: | + sudo apt install -y tcl cmake libssl-dev gdb + mkdir _build && cd _build + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=OFF -DUSE_CXX_STD=03 -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=${{ matrix.bonding }} -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DENABLE_LOGGING=${{ matrix.logging }} + - name: build + # That below is likely SonarQube remains, which was removed earlier. + #run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . + run: cd _build && cmake --build . + - name: test + run: | + echo "core.%e" | sudo tee /proc/sys/kernel/core_pattern + ulimit -c unlimited + cd _build && ctest --extra-verbose + SUCCESS=$? + if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + test $SUCCESS == 0; + - name: codecov + run: | + source ./scripts/collect-gcov.sh + bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/ubuntu-c++11.yml b/.github/workflows/ubuntu-c++11.yml new file mode 100644 index 0000000000..59efed7de8 --- /dev/null +++ b/.github/workflows/ubuntu-c++11.yml @@ -0,0 +1,46 @@ +name: Linux C++11 + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + types: [opened, synchronize, reopened] +jobs: + build: + strategy: + matrix: + # machine: [ubuntu-latest, ubuntu-slim, ubuntu-24.04-arm] + # ubuntu-slim excluded because it doesn't have cmake and installation not allowed + machine: [ubuntu-latest, ubuntu-24.04-arm] + name: ON ${{ matrix.machine }} + runs-on: "${{ matrix.machine }}" + steps: + - uses: actions/checkout@v3 + - name: prepare + run: | + RUNON=${{ matrix.machine }} + if [[ $RUNON == ubuntu-latest ]]; then + sudo apt install -y gdb + fi + - name: configure + run: | + RUNON=${{ matrix.machine }} + ENCRYPTION=ON + [[ $RUNON == ubuntu-24.04-arm ]] && ENCRYPTION=OFF + mkdir _build && cd _build + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DUSE_CXX_STD=11 -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=$ENCRYPTION -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: build + run: cd _build && cmake --build . + - name: test + run: | + echo "core.%e" | sudo tee /proc/sys/kernel/core_pattern + ulimit -c unlimited + cd _build && ctest --extra-verbose + SUCCESS=$? + if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + test $SUCCESS == 0; + - name: codecov + run: | + source ./scripts/collect-gcov.sh + bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/ubuntu-mingw.yml b/.github/workflows/ubuntu-mingw.yml new file mode 100644 index 0000000000..f9df4bc8ce --- /dev/null +++ b/.github/workflows/ubuntu-mingw.yml @@ -0,0 +1,27 @@ +name: Ubuntu MinGW + +on: + push: + branches: [ "master", "dev" ] + pull_request: + branches: [ "master", "dev" ] + types: [opened, synchronize, reopened] +jobs: + build: + name: noenc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Dependencies + run: | + sudo apt install -y g++-mingw-w64-x86-64-posix gcc-mingw-w64-x86-64-posix + - name: Configure + run: | + mkdir _build && cd _build + export CC=x86_64-w64-mingw32-gcc-posix CXX=x86_64-w64-mingw32-g++-posix + #cmake .. -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=OFF -DUSE_OPENSSL_PC=OFF -DCMAKE_SYSTEM_NAME=Windows -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + # Warn-on-error turned off because there's a warning about epoll about unimplemented system fd subscription system + cmake .. -DENABLE_ENCRYPTION=OFF -DENABLE_UNITTESTS=OFF -DUSE_OPENSSL_PC=OFF -DCMAKE_SYSTEM_NAME=Windows -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + - name: Build + run: cd _build && make -j -k + diff --git a/.github/workflows/cxx11-win.yaml b/.github/workflows/windows-msvc-noenc.yml similarity index 94% rename from .github/workflows/cxx11-win.yaml rename to .github/workflows/windows-msvc-noenc.yml index 2947809ab7..a2c97cf34f 100644 --- a/.github/workflows/cxx11-win.yaml +++ b/.github/workflows/windows-msvc-noenc.yml @@ -1,4 +1,4 @@ -name: C++11 +name: Windows on: push: @@ -9,7 +9,7 @@ on: jobs: build: - name: windows + name: C++11 noenc runs-on: windows-latest steps: diff --git a/apps/apputil.cpp b/apps/apputil.cpp index 22c31521bf..7c37d8e18f 100644 --- a/apps/apputil.cpp +++ b/apps/apputil.cpp @@ -37,7 +37,7 @@ using namespace srt; // See: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms742214(v=vs.85).aspx // http://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedInternet3b.html -#if defined(_WIN32) && !defined(HAVE_INET_PTON) +#if defined(_WIN32) && !defined(HAVE_INET_PTON) && !defined(InetPton) namespace // Prevent conflict in case when still defined { int inet_pton(int af, const char * src, void * dst) diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp index b8aa67b861..ee8f1595c5 100644 --- a/apps/socketoptions.hpp +++ b/apps/socketoptions.hpp @@ -58,14 +58,18 @@ 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::something something = Object::something; + 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); } @@ -80,7 +84,7 @@ inline int SocketOption::setso(SRT_SOCKO template<> -inline int SocketOption::setso(int socket, int proto, int sym, const void* data, size_t size) +inline int SocketOption::setso(SYSSOCKET socket, int proto, int sym, const void* data, size_t size) { return ::setsockopt(socket, proto, sym, (const char *)data, (int) size); } diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp index 3468c78b58..e4968767d0 100644 --- a/apps/srt-live-transmit.cpp +++ b/apps/srt-live-transmit.cpp @@ -792,12 +792,12 @@ int main(int argc, char** argv) } if (!srcReady && sysrfdslen > 0) { - int sock = src->GetSysSocket(); - if (sock != -1) + SYSSOCKET sock = src->GetSysSocket(); + if (sock != SYSSOCKET_INVALID) { for (int n = 0; n < sysrfdslen && !(srcReady = (sock == sysrfds[n])); n++); } - } + } } // read a few chunks at a time in attempt to deplete // read buffers as much as possible on each read event diff --git a/apps/transmitbase.hpp b/apps/transmitbase.hpp index 7451c59b78..4590877bc1 100644 --- a/apps/transmitbase.hpp +++ b/apps/transmitbase.hpp @@ -70,7 +70,7 @@ class Source: public Location }; virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; } - virtual int GetSysSocket() const { return -1; } + virtual SYSSOCKET GetSysSocket() const { return -1; } virtual bool MayBlock() const { return false; } virtual bool AcceptNewClient() { return false; } }; @@ -87,7 +87,7 @@ class Target: public Location virtual ~Target() {} virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; } - virtual int GetSysSocket() const { return -1; } + virtual SYSSOCKET GetSysSocket() const { return -1; } virtual bool AcceptNewClient() { return false; } }; diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp index 862875fa2e..7a158c9d7f 100644 --- a/apps/transmitmedia.cpp +++ b/apps/transmitmedia.cpp @@ -744,7 +744,7 @@ class ConsoleSource: public Source bool IsOpen() override { return cin.good(); } bool MayBlock() const final { return may_block; } bool End() override { return cin.eof(); } - int GetSysSocket() const override { return fileno(stdin); }; + SYSSOCKET GetSysSocket() const override { return fileno(stdin); }; }; class ConsoleTarget: public Target @@ -773,7 +773,7 @@ class ConsoleTarget: public Target bool IsOpen() override { return cout.good(); } bool Broken() override { return cout.eof(); } - int GetSysSocket() const override { return fileno(stdout); }; + SYSSOCKET GetSysSocket() const override { return fileno(stdout); }; }; template struct Console; @@ -804,7 +804,7 @@ static inline bool IsMulticast(in_addr adr) class UdpCommon { protected: - int m_sock = -1; + SYSSOCKET m_sock = SYSSOCKET_INVALID; string adapter; sockaddr_any interface_addr; sockaddr_any target_addr; @@ -813,8 +813,8 @@ class UdpCommon void Setup(string host, int port, map attr) { - m_sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (m_sock == -1) + m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (m_sock == SYSSOCKET_INVALID) Error(SysError(), "UdpCommon::Setup: socket"); int yes = 1; @@ -957,7 +957,7 @@ class UdpCommon ~UdpCommon() { #ifdef _WIN32 - if (m_sock != -1) + if (m_sock != SYSSOCKET_INVALID) { shutdown(m_sock, SD_BOTH); closesocket(m_sock); @@ -1038,10 +1038,10 @@ class UdpSource: public Source, public UdpCommon return stat; } - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } bool End() override { return eof; } - int GetSysSocket() const override { return m_sock; }; + SYSSOCKET GetSysSocket() const override { return m_sock; }; }; class UdpTarget: public Target, public UdpCommon @@ -1083,10 +1083,10 @@ class UdpTarget: public Target, public UdpCommon return stat; } - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } bool Broken() override { return false; } - int GetSysSocket() const override { return m_sock; }; + SYSSOCKET GetSysSocket() const override { return m_sock; }; }; template struct Udp; diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 81f082a1fe..9c8babed54 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -879,7 +879,9 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka if (bCompleted) res = 0; else + { LOGC(kslog.Warn, log << "CChannel::sendto call on ::WSAGetOverlappedResult failed with error: " << NET_ERROR); + } lEvent.reset(); } else diff --git a/srtcore/srt.h b/srtcore/srt.h index 9fd9b20c8a..36054e7d81 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -142,8 +142,10 @@ static const int32_t SRTGROUP_MASK = (1 << 30); #ifdef _WIN32 typedef SOCKET SYSSOCKET; + static const SYSSOCKET SYSSOCKET_INVALID = INVALID_SOCKET; #else typedef int SYSSOCKET; + static const int SYSSOCKET_INVALID = -1; #endif #ifndef ENABLE_BONDING diff --git a/testing/srt-test-relay.cpp b/testing/srt-test-relay.cpp index f3921f2e2b..d22cba4d8e 100755 --- a/testing/srt-test-relay.cpp +++ b/testing/srt-test-relay.cpp @@ -274,7 +274,7 @@ int main( int argc, char** argv ) for (auto& s: output_spec) Verb() << "\t" << s; -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__MINGW32__) // Replacement for sigaction, just use 'signal' // This may make this working kinda impaired and unexpected, // but still better that not compiling at all. diff --git a/testing/testmedia.cpp b/testing/testmedia.cpp index 92262d5cab..90b02a782e 100755 --- a/testing/testmedia.cpp +++ b/testing/testmedia.cpp @@ -2747,7 +2747,7 @@ static inline bool IsMulticast(in_addr adr) void UdpCommon::Setup(string host, int port, map attr) { m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (m_sock == -1) + if (m_sock == SYSSOCKET_INVALID) Error(SysError(), "UdpCommon::Setup: socket"); int yes = 1; @@ -2878,11 +2878,11 @@ void UdpCommon::Error(int err, string src) UdpCommon::~UdpCommon() { #ifdef _WIN32 - if (m_sock != -1) + if (m_sock != SYSSOCKET_INVALID) { shutdown(m_sock, SD_BOTH); closesocket(m_sock); - m_sock = -1; + m_sock = SYSSOCKET_INVALID; } #else close(m_sock); diff --git a/testing/testmedia.hpp b/testing/testmedia.hpp index 36a6491300..56eea65506 100644 --- a/testing/testmedia.hpp +++ b/testing/testmedia.hpp @@ -303,7 +303,7 @@ class SrtModel: public SrtCommon class UdpCommon { protected: - int m_sock = -1; + SYSSOCKET m_sock = -1; std::string adapter; srt::sockaddr_any interface_addr; srt::sockaddr_any target_addr; @@ -325,7 +325,7 @@ class UdpSource: public virtual Source, public virtual UdpCommon MediaPacket Read(size_t chunk) override; - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } bool End() override { return eof; } }; @@ -335,7 +335,7 @@ class UdpTarget: public virtual Target, public virtual UdpCommon UdpTarget(std::string host, int port, const std::map& attr); void Write(const MediaPacket& data) override; - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } bool Broken() override { return false; } }; @@ -348,7 +348,7 @@ class UdpRelay: public Relay, public UdpSource, public UdpTarget { } - bool IsOpen() override { return m_sock != -1; } + bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; } }; From e6a870e276a3e62580320af5843bd50e1fffca33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Mon, 20 Jul 2026 07:12:00 +0200 Subject: [PATCH 75/92] srtla: flush partial ACK batches by age SRTLA ACKs no longer wait for a full batch of 10: a partial batch is flushed (variable length) once its oldest entry ages out, both on ingress and in the periodic pass. --- srtcore/srtla_rec.cpp | 28 +++++++++++++++++++++++----- srtcore/srtla_rec.h | 8 +++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp index ccf38c0855..45ebe47c4e 100644 --- a/srtcore/srtla_rec.cpp +++ b/srtcore/srtla_rec.cpp @@ -240,12 +240,17 @@ void srt::SrtlaRec::sendKeepalive(const sockaddr_any& dst) // SRT data sequence numbers (big-endian, as received). void srt::SrtlaRec::sendSrtlaAck(const sockaddr_any& dst, Link& l) { + if (l.ack_count <= 0) + return; + + // Variable-length ACK: header word + one word per pending SN. uint8_t buf[SRTLA_ACK_LEN]; - memset(buf, 0, sizeof buf); store_be32(buf, uint32_t(T_SRTLA_ACK) << 16); // 0x91000000 - for (int i = 0; i < RECV_ACK_INT; ++i) + const int n = l.ack_count; + for (int i = 0; i < n; ++i) store_be32(buf + 4 + 4 * i, l.ack_log[i]); - m_pChannel->sendtoRaw(dst, (const char*)buf, SRTLA_ACK_LEN); + m_pChannel->sendtoRaw(dst, (const char*)buf, 4 + 4 * (size_t)n); + l.ack_count = 0; } // Echo a received keepalive verbatim, padded to at least MIN_PAD (spec §3.4). @@ -569,11 +574,14 @@ srt::SrtlaRec::Ingress srt::SrtlaRec::onIngress(const sockaddr_any& src, CUnit* l->have_prev_transit = true; } + if (l->ack_count == 0) + l->ack_first_pending = now; l->ack_log[l->ack_count++] = uint32_t(sn); - if (l->ack_count >= RECV_ACK_INT) + // Flush on a full batch or when the oldest pending entry has aged out. + if (l->ack_count >= RECV_ACK_INT + || count_microseconds(now - l->ack_first_pending) >= ACK_FLUSH_US) { sendSrtlaAck(src, *l); - l->ack_count = 0; } } @@ -658,6 +666,16 @@ void srt::SrtlaRec::onPeriodic(const time_point& now) return; } + // Flush aged partial SRTLA-ACK batches (covers links that went quiet). + for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g) + { + for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it) + { + if (it->ack_count > 0 && count_microseconds(now - it->ack_first_pending) >= ACK_FLUSH_US) + sendSrtlaAck(it->addr, *it); + } + } + if (count_milliseconds(now - m_LastCleanup) >= 3000) // CLEANUP_PERIOD { doCleanup(now); diff --git a/srtcore/srtla_rec.h b/srtcore/srtla_rec.h index 59422f6bd3..0350145ad1 100644 --- a/srtcore/srtla_rec.h +++ b/srtcore/srtla_rec.h @@ -60,7 +60,8 @@ class SrtlaRec static const size_t SRTLA_ACK_LEN = 44; // header word + 10 SNs static const size_t SRT_MIN_LEN = 16; // min length treated as an SRT packet static const size_t MIN_PAD = 32; // short control packets padded to this - static const int RECV_ACK_INT = 10; // SRT data packets per SRTLA ACK + static const int RECV_ACK_INT = 10; // SRT data packets per SRTLA ACK (batch cap) + static const int64_t ACK_FLUSH_US = 30000; // partial-batch flush age static const size_t MAX_CONNS_PER_GROUP = 16; // == SRT_SRTLA_MAX_PEERS static const size_t MAX_GROUPS = 200; static const int32_t SN_WINDOW_SIZE = 65536; // retransmission-tracking bitmap @@ -125,8 +126,9 @@ class SrtlaRec bool recovering; // SRTLA-ACK batch: raw 31-bit SNs in receive order (spec §3.5). - uint32_t ack_log[RECV_ACK_INT]; - int ack_count; + uint32_t ack_log[RECV_ACK_INT]; + int ack_count; + time_point ack_first_pending; // when the oldest unsent entry was queued uint32_t connectionId; // FNV-1a of addr+port From 6fff03da80a80d7e11e2067f372bb309977b2912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Mon, 20 Jul 2026 07:41:00 +0200 Subject: [PATCH 76/92] srtla: time-gated loss reporting Under SRTO_SRTLA, loss reports (first report and repeats) are emitted solely from checkNAKTimer, gated by an adaptive time hold derived from per-link measurements, with RTT-based repeat spacing. Loss records keep their time history across splits and persist until recovered or dropped. SRTLA connections enforce a minimum receiver latency. Plain SRT behaviour is unchanged. Full test suite passes. --- srtcore/core.cpp | 166 +++++++++++++++++++++++++------------ srtcore/core.h | 10 +++ srtcore/list.cpp | 15 +++- srtcore/list.h | 4 +- srtcore/srtla_rec.cpp | 48 +++++++++++ srtcore/srtla_rec.h | 4 + test/test_losslist_rcv.cpp | 69 +++++++++++++++ 7 files changed, 261 insertions(+), 55 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 949e709fd8..afa402d11d 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -262,6 +262,27 @@ CUDTUnited& srt::CUDT::uglobal() #endif +// SRTLA loss-report timing parameters (active only with SRTO_SRTLA). +namespace { +const int SRTLA_WITNESS_TTL = 2; +const uint32_t SRTLA_HOLD_FLOOR_US = 100000; +const uint32_t SRTLA_HOLD_CAP_US = 500000; +const uint32_t SRTLA_HOLD_FALLBACK_US = 250000; +const uint32_t SRTLA_HOLD_EVIDENCE_MARGIN_US = 30000; +const int64_t SRTLA_HOLD_DECAY_TAU_US = 20000000; +const double SRTLA_RENAK_RTT_FACTOR = 1.3; +const int SRTLA_MIN_RCVLATENCY_MS = 1000; + +inline uint32_t srtlaDecayedFastHold(uint32_t stored_us, int64_t age_us) +{ + if (stored_us == 0 || age_us >= SRTLA_HOLD_DECAY_TAU_US) + return 0; + if (age_us <= 0) + return stored_us; + return (uint32_t)((int64_t)stored_us * (SRTLA_HOLD_DECAY_TAU_US - age_us) / SRTLA_HOLD_DECAY_TAU_US); +} +} // namespace + void srt::CUDT::construct() { m_pSndBuffer = NULL; @@ -269,6 +290,8 @@ void srt::CUDT::construct() m_pSndLossList = NULL; m_pRcvLossList = NULL; m_iReorderTolerance = 0; + m_uiSrtlaHoldSteadyUs = 0; + m_uiSrtlaHoldFastUs = 0; // How many times so far the packet considered lost has been received // before TTL expires. m_iConsecEarlyDelivery = 0; @@ -379,6 +402,8 @@ srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) m_SrtHsSide = ancestor.m_SrtHsSide; // actually it sets it to HSD_RESPONDER m_bTLPktDrop = ancestor.m_bTLPktDrop; m_iReorderTolerance = m_config.iMaxReorderTolerance; // Initialize with maximum value + if (m_config.bSRTLA && m_config.iRcvLatency < SRTLA_MIN_RCVLATENCY_MS) + m_config.iRcvLatency = SRTLA_MIN_RCVLATENCY_MS; // SRTLA minimum receiver latency // Runtime m_pCache = ancestor.m_pCache; @@ -10430,7 +10455,7 @@ int srt::CUDT::processData(CUnit* in_unit) // always immediately. int initial_loss_ttl = 0; if (m_config.bSRTLA && m_bPeerRexmitFlag) - initial_loss_ttl = m_config.iMaxReorderTolerance; + initial_loss_ttl = SRTLA_WITNESS_TTL; else if (!m_config.bSRTLA && m_bPeerRexmitFlag) initial_loss_ttl = m_iReorderTolerance; @@ -10676,6 +10701,19 @@ int srt::CUDT::processData(CUnit* in_unit) { deque::iterator i = m_FreshLoss.begin(); + if (m_config.bSRTLA) + { + // SRTLA: reports are driven from checkNAKTimer(); records + // persist until recovered or dropped. + for (; i != m_FreshLoss.end(); ++i) + { + if (i->ttl > 0) + --i->ttl; + } + } + else + { + // Phase 1: take while TTL <= 0. // There can be more than one record with the same TTL, if it has happened before // that there was an 'unlost' (@c dropFromLossLists) sequence that has split one detected loss @@ -10708,6 +10746,8 @@ int srt::CUDT::processData(CUnit* in_unit) // Phase 2: rest of the records should have TTL decreased. for (; i != m_FreshLoss.end(); ++i) --i->ttl; + + } } } if (!lossdata.empty()) @@ -10808,6 +10848,31 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source) /// do not include the lacking packet. /// The tolerance is not increased infinitely - it's bordered by iMaxReorderTolerance. /// This value can be set in options - SRT_LOSSMAXTTL. +// Caller holds m_RcvLossLock. +uint32_t srt::CUDT::srtlaReorderHoldUs(const steady_clock::time_point& now) +{ + uint32_t steady_us = m_uiSrtlaHoldSteadyUs.load(); + if (steady_us == 0) + steady_us = SRTLA_HOLD_FALLBACK_US; + + const uint32_t fast_us = srtlaDecayedFastHold(m_uiSrtlaHoldFastUs, + count_microseconds(now - m_tsSrtlaHoldFastSet)); + if (fast_us == 0) + m_uiSrtlaHoldFastUs = 0; + + uint32_t hold = std::max(steady_us, fast_us); + if (hold < SRTLA_HOLD_FLOOR_US) + hold = SRTLA_HOLD_FLOOR_US; + + uint32_t cap = SRTLA_HOLD_CAP_US; + const uint32_t lat_half_us = (uint32_t)m_iTsbPdDelay_ms * 500; + if (lat_half_us > 0 && lat_half_us < cap) + cap = lat_half_us; + if (hold > cap) + hold = cap; + return hold; +} + void srt::CUDT::unlose(const CPacket &packet) { ScopedLock lg(m_RcvLossLock); @@ -10863,13 +10928,33 @@ void srt::CUDT::unlose(const CPacket &packet) // (in this case it's empty anyway) // - decrease current reorder tolerance based on whether packets come in order // (current reorder tolerance is 0 anyway) - if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) + if (m_bPeerRexmitFlag == 0 || (!m_config.bSRTLA && m_iReorderTolerance == 0)) return; int had_ttl = 0; - if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl))) + steady_clock::time_point detect_time; + if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl), (&detect_time))) { HLOGC(qrlog.Debug, log << "sequence " << sequence << " removed from belated lossreport record"); + + if (m_config.bSRTLA && was_reordered && !is_zero(detect_time)) + { + const int64_t late_us = count_microseconds(steady_clock::now() - detect_time); + if (late_us > 0) + { + const uint32_t want_us = (uint32_t)std::min(late_us + SRTLA_HOLD_EVIDENCE_MARGIN_US, SRTLA_HOLD_CAP_US); + const uint32_t cur_fast_us = srtlaDecayedFastHold(m_uiSrtlaHoldFastUs, + count_microseconds(steady_clock::now() - m_tsSrtlaHoldFastSet)); + if (want_us > cur_fast_us) + { + HLOGC(qrlog.Debug, log << CONID() << "SRTLA: belated original %" << sequence + << " late by " << (late_us / 1000) << "ms - raising reorder hold to " + << (want_us / 1000) << "ms"); + m_uiSrtlaHoldFastUs = want_us; + m_tsSrtlaHoldFastSet = steady_clock::now(); + } + } + } } if (was_reordered) @@ -11469,60 +11554,39 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) if (currtime <= m_tsNextNAKTime.load()) return BECAUSE_NO_REASON; // wait for next NAK time - if (m_config.bSRTLA) + if (m_config.bSRTLA && m_bPeerRexmitFlag) { + // SRTLA: time-gated loss reporting (single emitter for first + // reports and repeats). + if (count_microseconds(currtime - m_tsSrtlaHoldPollTime) >= 1000000) + { + m_tsSrtlaHoldPollTime = currtime; + if (m_pRcvQueue && m_pRcvQueue->m_pSrtlaRec) + m_uiSrtlaHoldSteadyUs.store(m_pRcvQueue->m_pSrtlaRec->holdSteadyUs(m_SocketID)); + } + vector lossdata; { ScopedLock lk(m_RcvLossLock); - const int cap = m_iMaxSRTPayloadSize / 4; - int32_t* arr = new int32_t[cap]; - int arrlen = 0; - m_pRcvLossList->getLossArray(arr, arrlen, cap); - const steady_clock::duration max_age = milliseconds_from(250); - for (int n = 0; n < arrlen; ) + const uint32_t hold_us = srtlaReorderHoldUs(currtime); + const int64_t srtt_us = m_iSRTT.load(); + const int64_t spacing_us = std::max( + (int64_t)(SRTLA_RENAK_RTT_FACTOR * (double)srtt_us), + count_microseconds(m_tdNAKInterval)); + const size_t cap = (size_t)m_iMaxSRTPayloadSize / 4; + for (deque::iterator i = m_FreshLoss.begin(); + i != m_FreshLoss.end() && lossdata.size() + 2 <= cap; ++i) { - int32_t lo, hi; - if (arr[n] & LOSSDATA_SEQNO_RANGE_FIRST) - { - lo = arr[n] & ~LOSSDATA_SEQNO_RANGE_FIRST; - hi = arr[n + 1]; - n += 2; - } - else - { - lo = hi = arr[n]; - n += 1; - } - int32_t runStart = SRT_SEQNO_NONE; - for (int32_t s = lo; ; s = CSeqNo::incseq(s)) - { - bool fresh = false; - for (size_t k = 0; k < m_FreshLoss.size(); ++k) - { - if (CSeqNo::seqcmp(s, m_FreshLoss[k].seq[0]) >= 0 && CSeqNo::seqcmp(s, m_FreshLoss[k].seq[1]) <= 0) - { - if (currtime - m_FreshLoss[k].timestamp < max_age) - fresh = true; - break; - } - } - if (!fresh) - { - if (runStart == SRT_SEQNO_NONE) - runStart = s; - } - else if (runStart != SRT_SEQNO_NONE) - { - addLossRecord(lossdata, runStart, CSeqNo::decseq(s)); - runStart = SRT_SEQNO_NONE; - } - if (s == hi) - break; - } - if (runStart != SRT_SEQNO_NONE) - addLossRecord(lossdata, runStart, hi); + if (i->ttl > 0) + continue; + if (count_microseconds(currtime - i->timestamp) < (int64_t)hold_us) + continue; + if (!is_zero(i->report_time) + && count_microseconds(currtime - i->report_time) < spacing_us) + continue; + addLossRecord(lossdata, i->seq[0], i->seq[1]); + i->report_time = currtime; } - delete[] arr; } if (!lossdata.empty()) { diff --git a/srtcore/core.h b/srtcore/core.h index 1bfcb76fbb..1c77fc4847 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -974,6 +974,16 @@ class CUDT SRT_ATTR_GUARDED_BY(m_RcvLossLock) std::deque m_FreshLoss; //< Lost sequence already added to m_pRcvLossList, but not yet sent UMSG_LOSSREPORT for. + // SRTLA loss-report timing state (bSRTLA only). + sync::atomic m_uiSrtlaHoldSteadyUs; + SRT_ATTR_GUARDED_BY(m_RcvLossLock) + uint32_t m_uiSrtlaHoldFastUs; + SRT_ATTR_GUARDED_BY(m_RcvLossLock) + sync::steady_clock::time_point m_tsSrtlaHoldFastSet; + sync::steady_clock::time_point m_tsSrtlaHoldPollTime; + + uint32_t srtlaReorderHoldUs(const sync::steady_clock::time_point& now); + int m_iReorderTolerance; //< Current value of dynamic reorder tolerance int m_iConsecEarlyDelivery; //< Increases with every OOO packet that came & w_container, int32_t sequence, int* pw_had_ttl) +bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32_t sequence, int* pw_had_ttl, + srt::sync::steady_clock::time_point* pw_detect_time) { for (size_t i = 0; i < w_container.size(); ++i) { @@ -907,6 +909,10 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32 if (wh == NONE) continue; // Not found. Search again. + // Read before a possible erase below; revoke() never touches the timestamp. + if (pw_detect_time) + *pw_detect_time = w_container[i].timestamp; + if (wh == DELETE) // ... oo ... x ... o ... => ... oo ... o ... { // Removed the only element in the record - remove the record. @@ -927,8 +933,11 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32 // Use position of the NEXT element because insertion happens BEFORE pointed element. // Use the same TTL (will stay the same in the other one). - w_container.insert(w_container.begin() + i + 1, - CRcvFreshLoss(next_begin, next_end, w_container[i].ttl)); + // Both halves keep the original time history. + CRcvFreshLoss upper(next_begin, next_end, w_container[i].ttl); + upper.timestamp = w_container[i].timestamp; + upper.report_time = w_container[i].report_time; + w_container.insert(w_container.begin() + i + 1, upper); } // For STRIPPED: ... xooo ... => ... ooo ... // i.e. there's nothing to do. diff --git a/srtcore/list.h b/srtcore/list.h index 40b8ee0c9b..684d05e91d 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -281,6 +281,7 @@ struct CRcvFreshLoss int32_t seq[2]; int ttl; srt::sync::steady_clock::time_point timestamp; + srt::sync::steady_clock::time_point report_time; // last report time (zero = never); unused in plain SRT CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl); @@ -300,7 +301,8 @@ struct CRcvFreshLoss Emod revoke(int32_t sequence); Emod revoke(int32_t lo, int32_t hi); - static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL); + static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL, + srt::sync::steady_clock::time_point* w_detect_time = NULL); }; } // namespace srt diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp index 45ebe47c4e..d70cb98f0e 100644 --- a/srtcore/srtla_rec.cpp +++ b/srtcore/srtla_rec.cpp @@ -747,6 +747,54 @@ void srt::SrtlaRec::doCleanup(const time_point& now) } } +uint32_t srt::SrtlaRec::holdSteadyUs(SRTSOCKET socket_id) +{ + ScopedLock lock(m_Lock); + + Group* grp = NULL; + for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g) + { + if (g->bound && g->socket_id == socket_id) + { + grp = &*g; + break; + } + } + if (!grp) + return 0; + + double tmin = 0.0, tmax = 0.0, jmax = 0.0; + bool have = false; + for (std::list::iterator it = grp->links.begin(); it != grp->links.end(); ++it) + { + if (!it->transit_valid) + continue; + if (!have) + { + tmin = tmax = it->transit_ewma; + have = true; + } + else + { + if (it->transit_ewma < tmin) + tmin = it->transit_ewma; + if (it->transit_ewma > tmax) + tmax = it->transit_ewma; + } + if (it->jitter_ewma > jmax) + jmax = it->jitter_ewma; + } + if (!have) + return 0; + + double hold = (tmax - tmin) + 4.0 * jmax; + if (hold < 0.0) + hold = 0.0; + if (hold > 10e6) + hold = 10e6; + return (uint32_t)hold; +} + // Fill @a out with the group's live per-link EWMA values (read fresh); rates get a // read-time decay so they are current at this instant. bool srt::SrtlaRec::fillStats(SRTSOCKET socket_id, SRT_SRTLA_STATS* out) diff --git a/srtcore/srtla_rec.h b/srtcore/srtla_rec.h index 0350145ad1..d0fe254d7a 100644 --- a/srtcore/srtla_rec.h +++ b/srtcore/srtla_rec.h @@ -113,6 +113,10 @@ class SrtlaRec /// Returns true (out.valid = 1) if such a group exists. bool fillStats(SRTSOCKET socket_id, SRT_SRTLA_STATS* out); + /// Link-timing summary for the group bound to @a socket_id, in + /// microseconds. Returns 0 while no valid measurement exists. + uint32_t holdSteadyUs(SRTSOCKET socket_id); + private: typedef sync::steady_clock::time_point time_point; diff --git a/test/test_losslist_rcv.cpp b/test/test_losslist_rcv.cpp index 94fea6ceb7..3a1a5ffc14 100644 --- a/test/test_losslist_rcv.cpp +++ b/test/test_losslist_rcv.cpp @@ -137,3 +137,72 @@ TEST(CRcvFreshLossListTest, CheckFreshLossList) EXPECT_EQ(floss.size(), 4u); } + +/// A SPLIT must preserve the record's time history in both halves. +TEST(CRcvFreshLossListTest, SplitPreservesTimeHistory) +{ + std::deque floss; + floss.push_back(CRcvFreshLoss(100, 120, 5)); + + // Simulate history: detected some time ago, reported later. + const srt::sync::steady_clock::time_point detected = + srt::sync::steady_clock::now() - srt::sync::milliseconds_from(400); + const srt::sync::steady_clock::time_point reported = + srt::sync::steady_clock::now() - srt::sync::milliseconds_from(150); + floss[0].timestamp = detected; + floss[0].report_time = reported; + + // SPLIT: sequence in the middle of the range. + int had_ttl = 0; + srt::sync::steady_clock::time_point detect_out; + const bool rm = CRcvFreshLoss::removeOne((floss), 110, &had_ttl, &detect_out); + ASSERT_TRUE(rm); + ASSERT_EQ(floss.size(), 2u); + + // The out-parameter reports the ORIGINAL detection time. + EXPECT_EQ(detect_out, detected); + + // Lower half keeps its history. + EXPECT_EQ(floss[0].seq[0], 100); + EXPECT_EQ(floss[0].seq[1], 109); + EXPECT_EQ(floss[0].timestamp, detected); + EXPECT_EQ(floss[0].report_time, reported); + + // Upper half is a new object but must carry the same history. + EXPECT_EQ(floss[1].seq[0], 111); + EXPECT_EQ(floss[1].seq[1], 120); + EXPECT_EQ(floss[1].timestamp, detected); + EXPECT_EQ(floss[1].report_time, reported); + EXPECT_EQ(floss[1].ttl, floss[0].ttl); +} + +/// STRIPPED/DELETE: history survives shrinking; detect-time out-param stays valid. +TEST(CRcvFreshLossListTest, StripAndDeleteReportDetectTime) +{ + std::deque floss; + floss.push_back(CRcvFreshLoss(200, 202, 3)); + const srt::sync::steady_clock::time_point detected = + srt::sync::steady_clock::now() - srt::sync::milliseconds_from(300); + floss[0].timestamp = detected; + + srt::sync::steady_clock::time_point detect_out; + + // STRIPPED (front): range shrinks, history stays. + ASSERT_TRUE(CRcvFreshLoss::removeOne((floss), 200, NULL, &detect_out)); + EXPECT_EQ(detect_out, detected); + ASSERT_EQ(floss.size(), 1u); + EXPECT_EQ(floss[0].seq[0], 201); + EXPECT_EQ(floss[0].timestamp, detected); + + // STRIPPED (back). + ASSERT_TRUE(CRcvFreshLoss::removeOne((floss), 202, NULL, &detect_out)); + EXPECT_EQ(detect_out, detected); + ASSERT_EQ(floss.size(), 1u); + EXPECT_EQ(floss[0].seq[0], 201); + EXPECT_EQ(floss[0].seq[1], 201); + + // DELETE (last element): detect time still reported although erased. + ASSERT_TRUE(CRcvFreshLoss::removeOne((floss), 201, NULL, &detect_out)); + EXPECT_EQ(detect_out, detected); + EXPECT_TRUE(floss.empty()); +} From c63c311e88aa55e430e3b7d94b89d790994f88c4 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Mon, 20 Jul 2026 16:12:08 +0200 Subject: [PATCH 77/92] [core] Fix kmreq CVE (#3345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Mimimum patch to fix spoofed MKREQ failure * Fixed test to match the current internal conditions * Removed rejection of KMX addressing the currently used key * Simplified error value transfer * Update to version 1.5.6 * [crypto] Added rollback for changed KEK when km_unwrap failed * Fix KMREQ buffer overflow at the right place. * [core] Fixed response for rogue KMREQ message --------- Co-authored-by: Mikolaj Malecki Co-authored-by: Clément Gérouville --- CMakeLists.txt | 2 +- haicrypt/haicrypt.h | 3 + haicrypt/hcrypt_ctx_rx.c | 40 ++- haicrypt/hcrypt_rx.c | 30 ++ srtcore/core.cpp | 17 +- srtcore/core.h | 3 +- srtcore/crypto.cpp | 403 +++++++++++----------- srtcore/crypto.h | 2 + test/test_bonding.cpp | 4 +- test/test_crypto.cpp | 704 ++++++++++++++++++++++++++++++++++----- 10 files changed, 910 insertions(+), 298 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d69a8d9911..87aba46664 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ # cmake_minimum_required (VERSION 3.5 FATAL_ERROR) -set (SRT_VERSION 1.5.5) +set (SRT_VERSION 1.5.6) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(CheckSymbolExists) diff --git a/haicrypt/haicrypt.h b/haicrypt/haicrypt.h index da0ad3493a..573efbf3b3 100644 --- a/haicrypt/haicrypt.h +++ b/haicrypt/haicrypt.h @@ -106,6 +106,9 @@ int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, void *out_p[], size_t out_len_p[], int maxout); +// Length not needed, as long as it's pre-verified that it's at least SALT length. +int HaiCrypt_GetKeyIndex(HaiCrypt_Handle hhc, unsigned char *in); + int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc); int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout); int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); diff --git a/haicrypt/hcrypt_ctx_rx.c b/haicrypt/hcrypt_ctx_rx.c index 2b67490d3e..907acdf09f 100644 --- a/haicrypt/hcrypt_ctx_rx.c +++ b/haicrypt/hcrypt_ctx_rx.c @@ -152,24 +152,25 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m return(-1); } + hcrypt_Ctx new_ctx = *ctx; /* Check Salt and get if new */ - if ((salt_len != ctx->salt_len) - || (0 != memcmp(ctx->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len))) { + if ((salt_len != new_ctx.salt_len) + || (0 != memcmp(new_ctx.salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len))) { /* Salt changed (or 1st KMmsg received) */ - memcpy(ctx->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len); - ctx->salt_len = salt_len; + memcpy(new_ctx.salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len); + new_ctx.salt_len = salt_len; do_pbkdf = 1; /* Impact on password derived kek */ } /* Check SEK length and get if new */ - if (sek_len != ctx->sek_len) { + if (sek_len != new_ctx.sek_len) { /* Key length changed or 1st KMmsg received */ - ctx->sek_len = sek_len; + new_ctx.sek_len = sek_len; do_pbkdf = 1; /* Impact on password derived kek */ } /* Check cipher mode */ - if (ctx->mode != km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) + if (new_ctx.mode != km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) { HCRYPT_LOG(LOG_WARNING, "%s", "cipher mode mismatch\n"); return(-3); @@ -179,21 +180,32 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m * Regenerate KEK if it is password derived * and Salt or SEK length changed */ - if (ctx->cfg.pwd_len && do_pbkdf) { - if (hcryptCtx_GenSecret(crypto, ctx)) { + int rollback_kek = 0; + if (new_ctx.cfg.pwd_len && do_pbkdf) { + if (hcryptCtx_GenSecret(crypto, &new_ctx)) { return(-1); } - ctx->status = HCRYPT_CTX_S_SARDY; + new_ctx.status = HCRYPT_CTX_S_SARDY; kek_len = sek_len; /* KEK changed */ + rollback_kek = 1; } /* Unwrap SEK(s) and set in context */ - if (0 > crypto->cryspr->km_unwrap(crypto->cryspr_cb, seks, - &km_msg[HCRYPT_MSG_KM_OFS_SALT + salt_len], - (unsigned int)((sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ))) { - HCRYPT_LOG(LOG_WARNING, "%s", "unwrap key failed\n"); + unsigned int msglen = (sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ; + int wrc = crypto->cryspr->km_unwrap(crypto->cryspr_cb, seks, + &km_msg[HCRYPT_MSG_KM_OFS_SALT + salt_len], msglen); + + if (wrc < 0) { + HCRYPT_LOG(LOG_WARNING, "%s%s\n", "unwrap key failed - internal KEK: ", rollback_kek ? "ROLLBACK" : "unchanged"); + // Rollback the call to hcryptCtx_GenSecret done on the new_ctx, + // and restore the old secret from old ctx. GenSecret is required by + // km_unwrap, but only after failed call we know this should remain unchanged. + if (rollback_kek) { + hcryptCtx_GenSecret(crypto, ctx); + } return(-2); //Report unmatched shared secret } + *ctx = new_ctx; /* * First SEK in KMmsg is eSEK if both SEK present */ diff --git a/haicrypt/hcrypt_rx.c b/haicrypt/hcrypt_rx.c index 2f7e847399..4cb3f84d52 100644 --- a/haicrypt/hcrypt_rx.c +++ b/haicrypt/hcrypt_rx.c @@ -61,6 +61,29 @@ int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, return(nb); } +int HaiCrypt_GetKeyIndex(HaiCrypt_Handle hhc, unsigned char* in_msg) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + int msg_type; + + if ((NULL == crypto) + || (NULL == in_msg)) { + + HCRYPT_LOG(LOG_ERR, "%s", "invalid parameters\n"); + return(-1); + } + + /* Validate HaiCrypt message */ + if (0 > (msg_type = hcryptMsg_SRT_ParseMsg(crypto->msg_info, in_msg))) { + return(-1); + } + + if (msg_type != HCRYPT_MSG_PT_KM) + return -1; + + return hcryptMsg_GetKeyIndex(crypto->msg_info, in_msg); +} + int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, unsigned char *in_msg, size_t in_len, void *out_p[], size_t out_len_p[], int maxout) @@ -99,6 +122,13 @@ int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, HCRYPT_LOG(LOG_ERR, "%s", "cryspr had no decryptor\n"); nbout = -1; } else if (ctx->status >= HCRYPT_CTX_S_KEYED) { + if ((int)(in_len) <= crypto->msg_info->pfx_len) + { + // XXX NOTE: SRT doesn't use this type of messages, so it shouldn't + // be a danger, but then you can still find something like that on the wire. + HCRYPT_LOG(LOG_ERR, "%s%d", "Invalid enc data packet: ", in_len); + return -1; + } hcrypt_DataDesc indata; indata.pfx = in_msg; indata.payload = &in_msg[crypto->msg_info->pfx_len]; diff --git a/srtcore/core.cpp b/srtcore/core.cpp index e4577a27d2..ece511b3e1 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -2184,11 +2184,9 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) { uint32_t srtdata_out[SRTDATA_MAXSIZE]; size_t len_out = 0; - if (len % sizeof(srtdata_out[0]) == 0 && len <= sizeof srtdata_out) - { - res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, + + res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion, (srtdata_out), (len_out)); - } // On error, it will stay with NONE and reject the packet if (res == SRT_CMD_KMRSP) { @@ -10714,6 +10712,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } } + bool decrypt_successful = false; const int buffer_add_result = m_pRcvBuffer->insert(u); if (buffer_add_result < 0) { @@ -10774,8 +10773,12 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& } #endif } + else + { + decrypt_successful = true; + } } - else if (m_pCryptoControl && m_pCryptoControl->m_RcvKmState == SRT_KM_S_SECURED) + else if (m_pCryptoControl && m_pCryptoControl->m_RcvKmState != SRT_KM_S_UNSECURED) { // Unencrypted packets are not allowed. const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); @@ -10854,6 +10857,10 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool& if (CSeqNo::seqcmp(rpkt.seqno(), m_iRcvCurrSeqNo) > 0) { m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received + if (decrypt_successful && m_pCryptoControl) + { + m_pCryptoControl->m_CurrentKey = rpkt.getMsgCryptoFlags(); + } } else { diff --git a/srtcore/core.h b/srtcore/core.h index a4a9acaeeb..31cedbd4ff 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -149,10 +149,9 @@ enum SeqPairItems }; -// Extended SRT Congestion control class - only an incomplete definition required -class CCryptoControl; namespace srt { +class CCryptoControl; class CUDTUnited; class CUDTSocket; #if ENABLE_BONDING diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index 20c8ba47e9..93b5bbe60a 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -135,237 +135,239 @@ void srt::CCryptoControl::createFakeSndContext() } } +#ifdef SRT_ENABLE_ENCRYPTION + int srt::CCryptoControl::processSrtMsg_KMREQ( - const uint32_t* srtdata SRT_ATR_UNUSED, - size_t bytelen SRT_ATR_UNUSED, - int hsv SRT_ATR_UNUSED, unsigned srtv SRT_ATR_UNUSED, + const uint32_t* srtdata, size_t bytelen, int hsv, unsigned srtv, uint32_t pw_srtdata_out[], size_t& w_srtlen) { - //Receiver - /* All 32-bit msg fields swapped on reception - * But HaiCrypt expect network order message - * Re-swap to cancel it. - */ -#ifdef SRT_ENABLE_ENCRYPTION - w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]); - HtoNLA((pw_srtdata_out), srtdata, w_srtlen); unsigned char* kmdata = reinterpret_cast(pw_srtdata_out); // The side that has received KMREQ is always an HSD_RESPONDER, regardless of // what has called this function. The HSv5 handshake only enforces bidirectional // connection. + // They actually mean the same thing in HSv5, but removal of bidirectional can be + // only done when the compat with <1.3.0 is allowed to be broken. const bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; + const bool kmx_update = m_hRcvCrypto; + SRT_KM_STATE failure_state = m_KmSecret.len == 0 ? SRT_KM_S_NOSECRET : SRT_KM_S_BADSECRET; + bool bUseGCM = false; - // Local macro to return rejection appropriately. - // CHANGED. The first version made HSv5 reject the connection. - // This isn't well handled by applications, so the connection is - // still established, but unable to handle any transport. -//#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { w_srtlen = 1; goto HSv4_ErrorReport; } -#define KMREQ_RESULT_REJECTION() { w_srtlen = 1; goto HSv4_ErrorReport; } - - int rc = HAICRYPT_OK; // needed before 'goto' run from KMREQ_RESULT_REJECTION macro - bool wasb4 SRT_ATR_UNUSED = false; - size_t sek_len = 0; - - const bool bUseGCM = - (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM) || - (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM); - - m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3); - - // What we have to do: - // If encryption is on (we know that by having m_KmSecret nonempty), create - // the crypto context (if bidirectional, create for both sending and receiving). - // Both crypto contexts should be set with the same length of the key. - // The problem with interpretinting this should be reported as SRT_CMD_NONE, - // should be appropriately handled by the caller, as it expects that this - // function normally return SRT_CMD_KMRSP. - if ( bytelen <= HCRYPT_MSG_KM_OFS_SALT ) //Sanity on message + // TRY-BLOCK, with THROW done by "goto Error". { - LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT); - m_RcvKmState = SRT_KM_S_BADSECRET; - KMREQ_RESULT_REJECTION(); - } + if (bytelen % sizeof(uint32_t) != 0 || bytelen > HCRYPT_MSG_KM_MAX_SZ) + { + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too high, must be < " << HCRYPT_MSG_KM_MAX_SZ); + goto Error; + } - HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto"); - sek_len = hcryptMsg_KM_GetSekLen(kmdata); - if ( sek_len == 0 ) - { - LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!"); - m_RcvKmState = SRT_KM_S_BADSECRET; - KMREQ_RESULT_REJECTION(); - } + // Default successful settings: original length and contents + w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]); - // Write the key length - m_iRcvKmKeyLen = sek_len; - // Overwrite the key length anyway - it doesn't make sense to somehow - // keep the original setting because it will only make KMX impossible. -#if ENABLE_HEAVY_LOGGING - if (m_iSndKmKeyLen != m_iRcvKmKeyLen) - { - LOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen - << " overwritten by Peer's PBKEYLEN=" << m_iRcvKmKeyLen); - } -#endif - m_iSndKmKeyLen = m_iRcvKmKeyLen; + /* All 32-bit msg fields swapped on reception + * But HaiCrypt expect network order message + * Re-swap to cancel it. + */ + HtoNLA((pw_srtdata_out), srtdata, w_srtlen); - // This is checked only now so that the SRTO_PBKEYLEN return always the correct value, - // even if encryption is not possible because Agent didn't set a password, or supplied - // a wrong password. - if (m_KmSecret.len == 0) //We have a shared secret <==> encryption is on - { - LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!"); - m_RcvKmState = SRT_KM_S_NOSECRET; - KMREQ_RESULT_REJECTION(); - } - wasb4 = m_hRcvCrypto; + if (!kmx_update) // Only in initial/handshake + { + // If this is NOT changed anywhere later, failure_state value will be used + m_RcvKmState = SRT_KM_S_SECURING; + } - if (!createCryptoCtx((m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM)) - { - LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."); - m_RcvKmState = SRT_KM_S_NOSECRET; - KMREQ_RESULT_REJECTION(); - } + if ((m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM) + || (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM)) + bUseGCM = true; - // Deduce resulting mode. - m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR; + m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3); - if (!wasb4) - { - HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen); - } - // We have both sides set with password, so both are pending for security - m_RcvKmState = SRT_KM_S_SECURING; - // m_SndKmState is set to SECURING or UNSECURED in init(), - // or it might have been set to SECURED, NOSECRET or BADSECRET in the previous - // handshake iteration (handshakes may be sent multiple times for the same connection). - - rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0); - switch(rc >= 0 ? HAICRYPT_OK : rc) - { - case HAICRYPT_OK: - m_RcvKmState = SRT_KM_S_SECURED; - HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED."); - //Send back the whole message to confirm - break; - case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key - m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; - //Send status KMRSP message to tel error - w_srtlen = 1; - LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"); - break; - case HAICRYPT_ERROR_CIPHER: + // INITIAL ACTIONS (first time KMREQ received): + // If encryption is on (we know that by having m_KmSecret nonempty), create + // the crypto context (if bidirectional, create for both sending and receiving). + // Both crypto contexts should be set with the same length of the key. + // Report SRT_CMD_NONE if no response is to be sent or SRT_CMD_KMRSP if the + // response is filled and ready (including erroneous). + if (bytelen <= HCRYPT_MSG_KM_OFS_SALT) //Sanity on message + { + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT); + goto Error; + } + + size_t sek_len = hcryptMsg_KM_GetSekLen(kmdata); + if (sek_len == 0) + { + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!"); + goto Error; + } + + bool new_keylen = m_iSndKmKeyLen != sek_len; + + if (kmx_update && new_keylen) + { + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: KMX refresh came in with a DIFFERENT KEY LEN: " << sek_len); + goto Error; + } + + HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto sek_len=" << sek_len + << " local=" << m_iSndKmKeyLen + << (new_keylen ? " - OVERRIDE with received SEK len" : "")); + + // Write the key length. + // Overwrite the key length anyway - it doesn't make sense to somehow + // keep the original setting because it will only make KMX impossible. + m_iSndKmKeyLen = m_iRcvKmKeyLen = sek_len; + + // This is checked only now so that the SRTO_PBKEYLEN return always the correct value, + // even if encryption is not possible because Agent didn't set a password, or supplied + // a wrong password. + if (!hasPassphrase()) //We have a shared secret <==> encryption is on + { + LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!"); + goto Error; + } + + if (!createCryptoCtx((m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM)) + { + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."); + failure_state = SRT_KM_S_NOSECRET; + goto Error; + } + + if (!kmx_update) + { + // Deduce resulting mode. + m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR; + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen); + } + + // We have both sides set with password, so both are pending for security + int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0); + LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) << " result: " << rc); + + // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE, + // until the next KMREQ is received as a key regeneration. + m_bErrorReported = false; + + if (rc >= HAICRYPT_OK) + { + m_RcvKmState = SRT_KM_S_SECURED; + HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED."); + } + else + { + switch(rc) + { + case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key + failure_state = SRT_KM_S_BADSECRET; + //Send status KMRSP message to tel error + LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"); + break; + case HAICRYPT_ERROR_CIPHER: #ifdef ENABLE_AEAD_API_PREVIEW - m_RcvKmState = m_SndKmState = SRT_KM_S_BADCRYPTOMODE; + failure_state = SRT_KM_S_BADCRYPTOMODE; #else - m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback. + failure_state = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback. #endif - w_srtlen = 1; - LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADCRYPTOMODE"); - break; - case HAICRYPT_ERROR: //Other errors - default: - m_RcvKmState = m_SndKmState = SRT_KM_S_NOSECRET; - w_srtlen = 1; - LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET"); - break; - } - - LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)); - - // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE, - // until the next KMREQ is received as a key regeneration. - m_bErrorReported = false; + LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADCRYPTOMODE"); + break; + case HAICRYPT_ERROR: //Other errors + default: + failure_state = SRT_KM_S_NOSECRET; + LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET"); + break; + } - if (w_srtlen == 1) - goto HSv4_ErrorReport; + if (!kmx_update) + { + // Only initially, set this also to SND state. + m_SndKmState = failure_state; + } + goto Error; + } - // Configure the sender context also, if it succeeded to configure the - // receiver context and we are using bidirectional mode. - if (bidirectional) - { - // Note: 'bidirectional' means that we want a bidirectional key update, - // which happens only and exclusively with HSv5 handshake - not when the - // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used - // in HSv4 versions also to initialize the first key, unlike HSv5). - if (m_RcvKmState == SRT_KM_S_SECURED) + // Configure the sender context also, if it succeeded to configure the + // receiver context and we are using bidirectional mode. + if (bidirectional) { - if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto) + // Note: 'bidirectional' means that we want a bidirectional key update, + // which happens only and exclusively with HSv5 handshake - not when the + // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used + // in HSv4 versions also to initialize the first key, unlike HSv5). + if (m_RcvKmState == SRT_KM_S_SECURED) { - m_iSndKmKeyLen = m_iRcvKmKeyLen; - if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK) + if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto) { - LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!"); - if (hasPassphrase()) - m_SndKmState = SRT_KM_S_BADSECRET; + m_iSndKmKeyLen = m_iRcvKmKeyLen; + if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK) + { + LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!"); + m_SndKmState = failure_state; + } else - m_SndKmState = SRT_KM_S_NOSECRET; + { + m_SndKmState = SRT_KM_S_SECURED; + } + + LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) + << " SndKeyLen=" << m_iSndKmKeyLen + << " TX CRYPTO CTX CLONED FROM RX" + ); + + // Write the KM message into the field from which it will be next sent. + memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen); + m_SndKmMsg[0].MsgLen = bytelen; + m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :) } else { - m_SndKmState = SRT_KM_S_SECURED; + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " + << KmStateStr(m_SndKmState) << " state"); } - - LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) - << " SndKeyLen=" << m_iSndKmKeyLen - << " TX CRYPTO CTX CLONED FROM RX" - ); - - // Write the KM message into the field from which it will be next sent. - memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen); - m_SndKmMsg[0].MsgLen = bytelen; - m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :) } else { - HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in " - << KmStateStr(m_SndKmState) << " state"); + HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); } } else { - HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX"); + HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX."); } - } - else - { - HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX."); - } - -#ifdef SRT_ENABLE_ENCRYPTION - if (m_hRcvCrypto != NULL) - HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153); - if (m_hSndCrypto != NULL) - HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153); -#endif - - return SRT_CMD_KMRSP; -HSv4_ErrorReport: + if (m_hRcvCrypto != NULL) + HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153); + if (m_hSndCrypto != NULL) + HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153); - if (bidirectional && hasPassphrase()) - { - // If the Forward KMX process has failed, the reverse-KMX process was not done at all. - // This will lead to incorrect object configuration and will fail to properly declare - // the transmission state. - // Create the "fake crypto" with the passphrsae you currently have. - createFakeSndContext(); + // NOTE: The "loop" gets exit with return HERE. + return SRT_CMD_KMRSP; } -#undef KMREQ_RESULT_REJECTION - -#else - // It's ok that this is reported as error because this happens in a scenario, - // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT - // application which tries to make a security association. - LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject..."); - m_RcvKmState = SRT_KM_S_NOSECRET; -#endif +Error: // CATCH POINT + // NOTE: This is set, but if we return NONE, this will not be + // taken into account anyhow. The state is returned this way only for + // unit tests so that they recognize the result of the call. w_srtlen = 1; + pw_srtdata_out[SRT_KMR_KMSTATE] = failure_state; - pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState; - return SRT_CMD_KMRSP; + if (!kmx_update) + { + // Set the appropriate error, if it wasn't already set before + m_RcvKmState = failure_state; + if (bidirectional && hasPassphrase()) + { + // If the Forward KMX process has failed, the reverse-KMX process was not done at all. + // This will lead to incorrect object configuration and will fail to properly declare + // the transmission state. + // Create the "fake crypto" with the passphrsae you currently have. + createFakeSndContext(); + } + + return SRT_CMD_KMRSP; + } + return SRT_CMD_NONE; } int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) @@ -481,12 +483,10 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3); -#ifdef SRT_ENABLE_ENCRYPTION if (m_hRcvCrypto != NULL) HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153); if (m_hSndCrypto != NULL) HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153); -#endif } LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len)); @@ -494,6 +494,30 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len return retstatus; } +#else + +int srt::CCryptoControl::processSrtMsg_KMREQ( + const uint32_t*, size_t, int, unsigned, // ignore input + uint32_t pw_srtdata_out[], size_t& w_srtlen) +{ + // It's ok that this is reported as error because this happens in a scenario, + // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT + // application which tries to make a security association. + LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject..."); + m_RcvKmState = SRT_KM_S_NOSECRET; + pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState; + w_srtlen = 1; + + return SRT_CMD_KMRSP; +} + +int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t*, size_t, unsigned) +{ + LOGP(cnlog.Error, "processSrtMsg_KMRSP: Encryption not enabled at compile time; not expected to receive SRT_CMD_KMRSP"); + return SRT_CMD_NONE; +} +#endif + void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SRT_ATR_UNUSED) { sync::ScopedLock lck(m_mtxLock); @@ -618,6 +642,7 @@ srt::CCryptoControl::CCryptoControl(SRTSOCKET id) , m_iRcvKmKeyLen(0) , m_SndKmState(SRT_KM_S_UNSECURED) , m_RcvKmState(SRT_KM_S_UNSECURED) + , m_CurrentKey(EK_NOENC) , m_KmRefreshRatePkt(0) , m_KmPreAnnouncePkt(0) , m_iCryptoMode(CSrtConfig::CIPHER_MODE_AUTO) @@ -884,7 +909,7 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU if (m_RcvKmState != SRT_KM_S_SECURED) { // If not "secured", it means that it won't be able to decrypt packets, - // so there's no point to even try to send them to HaiCrypt_Rx_Data. + // so there's no point in even trying to send them to HaiCrypt_Rx_Data. // Actually the current conditions concerning m_hRcvCrypto are such that this object // is created in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt, // but with SRT_KM_S_NOSECRET m_hRcvCrypto is not even created (is NULL), which diff --git a/srtcore/crypto.h b/srtcore/crypto.h index 613ded8dde..a1bb42d992 100644 --- a/srtcore/crypto.h +++ b/srtcore/crypto.h @@ -22,6 +22,7 @@ written by // UDT #include "udt.h" #include "packet.h" +#include "common.h" #include "utilities.h" #include "logging.h" @@ -61,6 +62,7 @@ class CCryptoControl public: SRT_KM_STATE m_SndKmState; //Sender Km State (imposed by agent) SRT_KM_STATE m_RcvKmState; //Receiver Km State (informed by peer) + EncryptionKeySpec m_CurrentKey; private: // Partial haicrypt configuration, consider diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index fa20b6098a..8414e43e34 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -534,11 +534,11 @@ TEST(Bonding, Options) EXPECT_NE(srt_getsockflag(grp, SRTO_KMSTATE, &kms, &optsize), SRT_ERROR); EXPECT_EQ(optsize, (int) sizeof kms); - EXPECT_EQ(kms, int(SRT_KM_S_SECURED)); + EXPECT_EQ(kms, uint32_t(SRT_KM_S_SECURED)); EXPECT_NE(srt_getsockflag(grp, SRTO_PBKEYLEN, &kms, &optsize), SRT_ERROR); EXPECT_EQ(optsize, (int) sizeof kms); - EXPECT_EQ(kms, 16); + EXPECT_EQ(kms, uint32_t(16)); #ifdef ENABLE_AEAD_API_PREVIEW EXPECT_NE(srt_getsockflag(grp, SRTO_CRYPTOMODE, &kms, &optsize), SRT_ERROR); diff --git a/test/test_crypto.cpp b/test/test_crypto.cpp index 466497d459..c8cc2ce3bd 100644 --- a/test/test_crypto.cpp +++ b/test/test_crypto.cpp @@ -1,11 +1,20 @@ #include #include #include +#include +#include #include "gtest/gtest.h" +#include "test_env.h" #include "crypto.h" #include "handshake.h" +#include "hcrypt_msg.h" +#include "hcrypt.h" // Imports the CRYSPR_HAS_AESGCM definition. +#include "socketconfig.h" +#include "api.h" + +#ifdef SRT_ENABLE_ENCRYPTION // processSrtMsg_KMRSP must reject malformed wire-supplied lengths before they // reach the fixed-size stack buffer / uninitialised-read paths inside the @@ -30,107 +39,632 @@ TEST(CryptoKMRSP, RejectsMalformedLengths) EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 3, srtv), srt::SRT_CMD_NONE); } -#if defined(SRT_ENABLE_ENCRYPTION) && defined(ENABLE_AEAD_API_PREVIEW) -#include "hcrypt.h" // Imports the CRYSPR_HAS_AESGCM definition. -#include "socketconfig.h" -namespace srt +#ifdef ENABLE_AEAD_API_PREVIEW + +class Crypto + : public srt::Test { +protected: + Crypto() + : m_crypt(0) + { + // initialization code here + } - class Crypto - : public ::testing::Test + virtual ~Crypto() { - protected: - Crypto() - : m_crypt(0) - { - // initialization code here - } - - virtual ~Crypto() - { - // cleanup any pending stuff, but no exceptions allowed - } - - protected: - // SetUp() is run immediately before a test starts. - void SetUp() override - { - CSrtConfig cfg; - - memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); - cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; - cfg.CryptoSecret.len = (m_pwd.size() <= (int)sizeof(cfg.CryptoSecret.str) ? m_pwd.size() : (int)sizeof(cfg.CryptoSecret.str)); - memcpy((cfg.CryptoSecret.str), m_pwd.c_str(), m_pwd.size()); - - m_crypt.setCryptoSecret(cfg.CryptoSecret); - - // 2 = 128, 3 = 192, 4 = 256 - cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); - m_crypt.setCryptoKeylen(cfg.iSndCryptoKeyLen); - - cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_GCM; - EXPECT_TRUE(m_crypt.init(HSD_INITIATOR, cfg, true, HaiCrypt_IsAESGCM_Supported())); - - const unsigned char* kmmsg = m_crypt.getKmMsg_data(0); - const size_t km_len = m_crypt.getKmMsg_size(0); - uint32_t kmout[72]; - size_t kmout_len = 72; - - std::array km_nworder; - NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); - m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), kmout, kmout_len); - } - - void TearDown() override - { - } - - protected: - - srt::CCryptoControl m_crypt; - const std::string m_pwd = "abcdefghijk"; - }; + // cleanup any pending stuff, but no exceptions allowed + } + +protected: + void setup() override + { + using namespace srt; + + CSrtConfig cfg; + + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (m_pwd.size() <= (int)sizeof(cfg.CryptoSecret.str) ? m_pwd.size() : (int)sizeof(cfg.CryptoSecret.str)); + memcpy((cfg.CryptoSecret.str), m_pwd.c_str(), m_pwd.size()); + + m_crypt.setCryptoSecret(cfg.CryptoSecret); + + // 2 = 128, 3 = 192, 4 = 256 + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + m_crypt.setCryptoKeylen(cfg.iSndCryptoKeyLen); + + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_GCM; + EXPECT_TRUE(m_crypt.init(HSD_INITIATOR, cfg, true, HaiCrypt_IsAESGCM_Supported())); + const unsigned char* kmmsg = m_crypt.getKmMsg_data(0); + const size_t km_len = m_crypt.getKmMsg_size(0); + uint32_t kmout[72]; + size_t kmout_len = 72; - // Check that destroying the buffer also frees memory units. - TEST_F(Crypto, GCM) + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), kmout, kmout_len); + } + + void teardown() override { - if (HaiCrypt_IsAESGCM_Supported() == 0) - GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + } + +protected: + + srt::CCryptoControl m_crypt; + const std::string m_pwd = "abcdefghijk"; +}; + + +// Check that destroying the buffer also frees memory units. +TEST_F(Crypto, GCM) +{ + using namespace srt; + + if (HaiCrypt_IsAESGCM_Supported() == 0) + GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + + const size_t mtu_size = 1500; + const size_t pld_size = 1316; + const size_t tag_len = 16; + + CPacket pkt; + pkt.allocate(mtu_size); + + const int seqno = 1; + const int msgno = 1; + const int inorder = 1; + const int kflg = m_crypt.getSndCryptoFlags(); + + pkt.set_seqno(seqno); + pkt.set_msgflags(msgno | inorder | PacketBoundaryBits(PB_SOLO) | MSGNO_ENCKEYSPEC::wrap(kflg)); + pkt.set_timestamp(356); + + std::iota(pkt.data(), pkt.data() + pld_size, '0'); + pkt.setLength(pld_size); + + EXPECT_EQ(m_crypt.encrypt(pkt), ENCS_CLEAR); + EXPECT_EQ(pkt.getLength(), pld_size + tag_len); + + auto pkt_enc = std::unique_ptr(pkt.clone()); + + EXPECT_EQ(m_crypt.decrypt(pkt), ENCS_CLEAR); + EXPECT_EQ(pkt.getLength(), pld_size); + + // Modify the payload and expect auth to fail. + pkt_enc->data()[10] = '5'; + EXPECT_EQ(m_crypt.decrypt(*pkt_enc.get()), ENCS_FAILED); +} + +// KMREQ that fails AES-KW unwrap must not downgrade a SECURED session. +TEST_F(Crypto, KMREQ_Unwrap_Failure_Does_Not_Downgrade_Secured) +{ + using namespace srt; + + if (HaiCrypt_IsAESGCM_Supported() == 0) + GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + + ASSERT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + + // KMREQ wrapped with a different passphrase -> HAICRYPT_ERROR_WRONG_SECRET. + CCryptoControl other(/*socket id*/1); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + const std::string other_pwd = "completely_different_xy"; + cfg.CryptoSecret.len = (int)other_pwd.size(); + memcpy(cfg.CryptoSecret.str, other_pwd.c_str(), other_pwd.size()); + other.setCryptoSecret(cfg.CryptoSecret); + + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + other.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_GCM; + ASSERT_TRUE(other.init(HSD_INITIATOR, cfg, true, HaiCrypt_IsAESGCM_Supported())); + + const unsigned char* kmmsg = other.getKmMsg_data(0); + const size_t km_len = other.getKmMsg_size(0); + ASSERT_GT(km_len, 0u); - const size_t mtu_size = 1500; - const size_t pld_size = 1316; - const size_t tag_len = 16; + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); - CPacket pkt; - pkt.allocate(mtu_size); + uint32_t kmout[72]; + size_t kmout_len = 72; + m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), + kmout, kmout_len); - const int seqno = 1; - const int msgno = 1; - const int inorder = 1; - const int kflg = m_crypt.getSndCryptoFlags(); + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); +} + +// Malformed KMREQ (too-small payload) on a SECURED session must not +// downgrade state. +TEST_F(Crypto, KMREQ_MalformedSize_Does_Not_Downgrade_Secured) +{ + using namespace srt; + + if (HaiCrypt_IsAESGCM_Supported() == 0) + GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + + ASSERT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + + // Payload <= HCRYPT_MSG_KM_OFS_SALT trips the size sanity check. + uint32_t tiny[2] = {0, 0}; + uint32_t kmout[72]; + size_t kmout_len = 72; + EXPECT_EQ(m_crypt.processSrtMsg_KMREQ(tiny, sizeof(tiny), + 5, SrtVersion(1, 5, 3), kmout, kmout_len), + SRT_CMD_NONE); + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + EXPECT_EQ(kmout[SRT_KMR_KMSTATE], (uint32_t)SRT_KM_S_BADSECRET); + EXPECT_EQ(kmout_len, 1u); +} - pkt.set_seqno(seqno); - pkt.set_msgflags(msgno | inorder | PacketBoundaryBits(PB_SOLO) | MSGNO_ENCKEYSPEC::wrap(kflg)); - pkt.set_timestamp(356); +// KMREQ with KLEN byte zeroed -> hcryptMsg_KM_GetSekLen returns 0, +// tripping the empty-SEK rejection. Must not downgrade SECURED state. +TEST_F(Crypto, KMREQ_EmptySEK_Does_Not_Downgrade_Secured) +{ + using namespace srt; + + if (HaiCrypt_IsAESGCM_Supported() == 0) + GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + + ASSERT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + + // Take a structurally-valid KMREQ and zero the KLEN field. + const unsigned char* kmmsg = m_crypt.getKmMsg_data(0); + const size_t km_len = m_crypt.getKmMsg_size(0); + std::array patched; + memcpy(patched.data(), kmmsg, km_len); + patched[HCRYPT_MSG_KM_OFS_KLEN] = 0; + + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(patched.data()), km_len); + + uint32_t kmout[72] = {0}; + size_t kmout_len = 72; + EXPECT_EQ(m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, + 5, SrtVersion(1, 5, 3), kmout, kmout_len), + SRT_CMD_NONE); + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + EXPECT_EQ(kmout[SRT_KMR_KMSTATE], (uint32_t)SRT_KM_S_BADSECRET); + EXPECT_EQ(kmout_len, 1u); +} - std::iota(pkt.data(), pkt.data() + pld_size, '0'); - pkt.setLength(pld_size); +// Forged KMRSP claiming any peer-failure state must not downgrade a +// SECURED session. The dispatcher accepts KMRSPs unconditionally so each +// peerstate branch in processSrtMsg_KMRSP is reachable off-path. +TEST_F(Crypto, DISABLED_KMRSP_PeerFailure_Does_Not_Downgrade_Secured) +{ + using namespace srt; + + if (HaiCrypt_IsAESGCM_Supported() == 0) + GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + + const SRT_KM_STATE wire_peerstates[] = { + SRT_KM_S_BADSECRET, + SRT_KM_S_NOSECRET, + SRT_KM_S_UNSECURED, +#ifdef ENABLE_AEAD_API_PREVIEW + SRT_KM_S_BADCRYPTOMODE, +#endif + // An out-of-enum value drives the default ("IPE: unknown peer + // error state") branch in the switch. + (SRT_KM_STATE)99, + }; + + for (size_t i = 0; i < sizeof(wire_peerstates)/sizeof(wire_peerstates[0]); ++i) + { + // Reset the agent into a fully-SECURED state for each iteration. + m_crypt.m_RcvKmState = SRT_KM_S_SECURED; + m_crypt.m_SndKmState = SRT_KM_S_SECURED; + + // Wire format is network byte order; the function will HtoNLA it + // back. Pre-NtoHLA so srtd[SRT_KMR_KMSTATE] inside the function + // reads the intended peerstate. + uint32_t wire = (uint32_t)wire_peerstates[i]; + uint32_t input = 0; + NtoHLA(&input, &wire, 1); + EXPECT_EQ(m_crypt.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3)), + SRT_CMD_NONE); + + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED) + << "peerstate=" << (int)wire_peerstates[i] << " downgraded m_RcvKmState"; + EXPECT_EQ(m_crypt.m_SndKmState, SRT_KM_S_SECURED) + << "peerstate=" << (int)wire_peerstates[i] << " downgraded m_SndKmState"; + } +} - EXPECT_EQ(m_crypt.encrypt(pkt), ENCS_CLEAR); - EXPECT_EQ(pkt.getLength(), pld_size + tag_len); +// After a forged KMREQ unwrap failure on a SECURED session, m_SndKmState +// must also stay SECURED so sendingAllowed() keeps returning true. +TEST_F(Crypto, KMREQ_Unwrap_Failure_Preserves_SndKmState_Secured) +{ + using namespace srt; + + if (HaiCrypt_IsAESGCM_Supported() == 0) + GTEST_SKIP() << "The crypto service provider does not support AES GCM."; + + // setup() leaves m_SndKmState=SECURING. Simulate a fully-handshaken + // session where the peer's KMRSP would have moved it to SECURED. + m_crypt.m_SndKmState = SRT_KM_S_SECURED; + ASSERT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + ASSERT_EQ(m_crypt.m_SndKmState, SRT_KM_S_SECURED); + + // Forge a KMREQ via a second CCryptoControl with a different passphrase. + CCryptoControl other(/*socket id*/1); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + const std::string other_pwd = "different_passphrase_xy"; + cfg.CryptoSecret.len = (int)other_pwd.size(); + memcpy(cfg.CryptoSecret.str, other_pwd.c_str(), other_pwd.size()); + other.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + other.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_GCM; + ASSERT_TRUE(other.init(HSD_INITIATOR, cfg, true, HaiCrypt_IsAESGCM_Supported())); + + const unsigned char* kmmsg = other.getKmMsg_data(0); + const size_t km_len = other.getKmMsg_size(0); + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + + uint32_t kmout[72]; + size_t kmout_len = 72; + m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), + kmout, kmout_len); + + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + EXPECT_EQ(m_crypt.m_SndKmState, SRT_KM_S_SECURED); +} - auto pkt_enc = std::unique_ptr(pkt.clone()); +// Regression-test fixture that runs in default builds (CTR mode, no AEAD +// preview required). Mirrors the setup of the Crypto fixture above. +class CryptoCtr +: public srt::Test +{ +protected: + CryptoCtr() : m_crypt(0) {} - EXPECT_EQ(m_crypt.decrypt(pkt), ENCS_CLEAR); - EXPECT_EQ(pkt.getLength(), pld_size); + void setup() override + { + using namespace srt; + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (m_pwd.size() <= (int)sizeof(cfg.CryptoSecret.str) ? m_pwd.size() : (int)sizeof(cfg.CryptoSecret.str)); + memcpy((cfg.CryptoSecret.str), m_pwd.c_str(), m_pwd.size()); + + m_crypt.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + m_crypt.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + EXPECT_TRUE(m_crypt.init(HSD_INITIATOR, cfg, true, false)); + + const unsigned char* kmmsg = m_crypt.getKmMsg_data(0); + const size_t km_len = m_crypt.getKmMsg_size(0); + uint32_t kmout[72]; + size_t kmout_len = 72; + + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), kmout, kmout_len); + } - // Modify the payload and expect auth to fail. - pkt_enc->data()[10] = '5'; - EXPECT_EQ(m_crypt.decrypt(*pkt_enc.get()), ENCS_FAILED); + void teardown() override + { } -} // namespace srt +protected: + srt::CCryptoControl m_crypt; + const std::string m_pwd = "abcdefghijk"; +}; + +// Regression test: validates that the legitimate SECURING -> SECURED +// transition still happens on a valid KMREQ. Catches a future over- +// applied SECURED-preserving guard that would block this path. +TEST_F(CryptoCtr, InitialHandshakeReachesSecured) +{ + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); +} + +// Regression test: from the SECURING initial state, a KMREQ wrapped with +// the wrong passphrase must still drive m_RcvKmState to BADSECRET. The +// SECURED-preserving guard must not block the initial transition. +TEST_F(CryptoCtr, WrongPassphraseAtInitialReachesBadSecret) +{ + using namespace srt; + + // Build a fresh crypter that has NOT been driven through setup's + // bootstrap KMREQ, so m_RcvKmState is still SECURING. + CCryptoControl fresh(/*socket id*/2); + CSrtConfig fresh_cfg; + memset(&fresh_cfg.CryptoSecret, 0, sizeof(fresh_cfg.CryptoSecret)); + fresh_cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + fresh_cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(fresh_cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + fresh.setCryptoSecret(fresh_cfg.CryptoSecret); + fresh_cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + fresh.setCryptoKeylen(fresh_cfg.iSndCryptoKeyLen); + fresh_cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(fresh.init(HSD_INITIATOR, fresh_cfg, true, false)); + ASSERT_NE(fresh.m_RcvKmState, SRT_KM_S_SECURED); + + // Forge a KMREQ from a peer using a different passphrase. + CCryptoControl other(/*socket id*/3); + CSrtConfig other_cfg; + memset(&other_cfg.CryptoSecret, 0, sizeof(other_cfg.CryptoSecret)); + other_cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + const std::string other_pwd = "wrong_passphrase_xy"; + other_cfg.CryptoSecret.len = (int)other_pwd.size(); + memcpy(other_cfg.CryptoSecret.str, other_pwd.c_str(), other_pwd.size()); + other.setCryptoSecret(other_cfg.CryptoSecret); + other_cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + other.setCryptoKeylen(other_cfg.iSndCryptoKeyLen); + other_cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(other.init(HSD_INITIATOR, other_cfg, true, false)); + + const unsigned char* kmmsg = other.getKmMsg_data(0); + const size_t km_len = other.getKmMsg_size(0); + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + + const SRT_KM_STATE prev_state = fresh.m_RcvKmState; + + uint32_t kmout[72]; + size_t kmout_len = 72; + int cmd = fresh.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), + kmout, kmout_len); + + // The guard must NOT block this transition: from SECURING the state + // must reach BADSECRET so the connection can be rejected. + // XXX NOTE: The behavior has been changed and now KMREQ failure is + // simply ignored, if it was done as update. And as we create the + // crypto with init, this is initialized just like through handshake, + // so this KMREQ is considered a KMX update. We have then the right + // state in the output array, but the state remains secure. + EXPECT_EQ(fresh.m_RcvKmState, prev_state); + EXPECT_EQ(cmd, SRT_CMD_NONE); + EXPECT_EQ(kmout[0], SRT_KM_S_BADSECRET); +} + +// Regression test: a fresh KMREQ wrapped with the SAME passphrase +// (i.e. legitimate key rotation) on an already-SECURED session must +// succeed. State stays SECURED and the KMRSP returned is a full success +// response, not a 1-word error. +// +// This is the highest-risk regression vector for the SECURED-preserving +// guards: if they accidentally over-applied to the success path, key +// rotation would silently fail and streams would eventually drop. +TEST_F(CryptoCtr, KmRefreshOnSecuredSucceeds) +{ + using namespace srt; + + ASSERT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + + // Build a fresh sender with the SAME passphrase. Its generated KM + // message will carry a different SEK but the same KEK as m_crypt, + // so the unwrap will succeed on m_crypt's side. This mirrors what + // happens when the live sender's regenCryptoKm rolls a new key. + CCryptoControl rotator(/*socket id*/4); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + rotator.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + rotator.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(rotator.init(HSD_INITIATOR, cfg, true, false)); + + const unsigned char* kmmsg = rotator.getKmMsg_data(0); + const size_t km_len = rotator.getKmMsg_size(0); + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + + uint32_t kmout[72] = {0}; + size_t kmout_len = 72; + m_crypt.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, SrtVersion(1, 5, 3), + kmout, kmout_len); + + // State remains SECURED across the rotation. + EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED); + + // The success KMRSP echoes the input KM message (multi-word). A + // rejection path would have set kmout_len == 1 and written a single + // SRT_KMR_KMSTATE word. + EXPECT_GT(kmout_len, 1u); +} + +// Regression test: a RESPONDER-side bidirectional handshake must reach +// SECURED on BOTH directions via the RX -> TX context clone path in +// processSrtMsg_KMREQ. This is the most complex success transition in +// the handler and is unguarded by design (gated by m_SndKmState == +// SECURING && !m_hSndCrypto). +TEST_F(CryptoCtr, ResponderHandshakeReachesSecuredViaClone) +{ + using namespace srt; + + // RESPONDER doesn't create m_hSndCrypto in init(), so the + // SECURING + !m_hSndCrypto precondition for the clone block holds. + CCryptoControl responder(/*socket id*/8); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + responder.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + responder.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(responder.init(HSD_RESPONDER, cfg, true, false)); + + // Use the fixture's own (INITIATOR-side) KM message as the KMREQ + // payload for the responder. Same passphrase, so unwrap succeeds. + const unsigned char* kmmsg = m_crypt.getKmMsg_data(0); + const size_t km_len = m_crypt.getKmMsg_size(0); + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + + uint32_t kmout[72]; + size_t kmout_len = 72; + responder.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, + SrtVersion(1, 5, 3), kmout, kmout_len); + + EXPECT_EQ(responder.m_RcvKmState, SRT_KM_S_SECURED); + EXPECT_EQ(responder.m_SndKmState, SRT_KM_S_SECURED); +} + +// Regression test: when the agent has no passphrase but the peer sends +// a valid KMREQ, m_RcvKmState must transition UNSECURED -> NOSECRET. The +// SECURED-preserving guard must not block this legitimate non-SECURED +// transition. +TEST_F(CryptoCtr, AgentWithoutPasswordGetsNoSecret) +{ + using namespace srt; + + // Agent with no passphrase. init() leaves both states at UNSECURED. + CCryptoControl no_pw_agent(/*socket id*/5); + CSrtConfig agent_cfg; + memset(&agent_cfg.CryptoSecret, 0, sizeof(agent_cfg.CryptoSecret)); + // typ left as 0 / len 0 -> hasPassphrase() returns false. + no_pw_agent.setCryptoSecret(agent_cfg.CryptoSecret); + agent_cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + no_pw_agent.setCryptoKeylen(agent_cfg.iSndCryptoKeyLen); + agent_cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(no_pw_agent.init(HSD_INITIATOR, agent_cfg, true, false)); + ASSERT_NE(no_pw_agent.m_RcvKmState, SRT_KM_S_SECURED); + + // Peer with a valid passphrase sends KMREQ. + CCryptoControl peer(/*socket id*/6); + CSrtConfig peer_cfg; + memset(&peer_cfg.CryptoSecret, 0, sizeof(peer_cfg.CryptoSecret)); + peer_cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + peer_cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(peer_cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + peer.setCryptoSecret(peer_cfg.CryptoSecret); + peer_cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + peer.setCryptoKeylen(peer_cfg.iSndCryptoKeyLen); + peer_cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(peer.init(HSD_INITIATOR, peer_cfg, true, false)); + + const unsigned char* kmmsg = peer.getKmMsg_data(0); + const size_t km_len = peer.getKmMsg_size(0); + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + + uint32_t kmout[72]; + size_t kmout_len = 72; + no_pw_agent.processSrtMsg_KMREQ(km_nworder.data(), km_len, 5, + SrtVersion(1, 5, 3), kmout, kmout_len); + + EXPECT_EQ(no_pw_agent.m_RcvKmState, SRT_KM_S_NOSECRET); +} + +// Regression test: receiving a KMRSP error report on a non-SECURED +// session must still update state per the peer's report. The SECURED- +// preserving guard must not block this. +TEST_F(CryptoCtr, KmrspPeerNoSecretOnNonSecured) +{ + using namespace srt; + + // Fresh crypter with passphrase but no bootstrap KMREQ processed. + CCryptoControl fresh(/*socket id*/7); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + fresh.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + fresh.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(fresh.init(HSD_INITIATOR, cfg, true, false)); + ASSERT_NE(fresh.m_RcvKmState, SRT_KM_S_SECURED); + + // KMRSP carrying a single peerstate word = NOSECRET (peer has no PW). + uint32_t wire = (uint32_t)SRT_KM_S_NOSECRET; + uint32_t input = 0; + NtoHLA(&input, &wire, 1); + fresh.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3)); + + // Per crypto.cpp KMRSP NOSECRET branch: RX -> UNSECURED, SND -> NOSECRET. + EXPECT_EQ(fresh.m_RcvKmState, SRT_KM_S_UNSECURED); + EXPECT_EQ(fresh.m_SndKmState, SRT_KM_S_NOSECRET); +} + +// Regression test: a successful KMRSP (a KMRSP whose multi-word body +// matches a stored sender KM message) must transition both m_RcvKmState +// and m_SndKmState to SECURED via the success branch in +// processSrtMsg_KMRSP. Mirror of the KMREQ success path. +TEST_F(CryptoCtr, KmrspSuccessTransitionsToSecured) +{ + using namespace srt; + + // Fresh initiator with a passphrase and the standard SND KM message + // built by init(), but without the bootstrap KMREQ that setup ran on + // m_crypt. m_SndKmState should be SECURING here. + CCryptoControl fresh(/*socket id*/9); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + fresh.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + fresh.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(fresh.init(HSD_INITIATOR, cfg, true, false)); + ASSERT_NE(fresh.m_SndKmState, SRT_KM_S_SECURED); + + // A successful KMRSP carries back the same KM payload the agent had + // sent. Feed the agent's own stored KM message back as the KMRSP body. + const unsigned char* kmmsg = fresh.getKmMsg_data(0); + const size_t km_len = fresh.getKmMsg_size(0); + ASSERT_GT(km_len, 0u); + + std::array km_nworder; + NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); + + fresh.processSrtMsg_KMRSP(km_nworder.data(), km_len, SrtVersion(1, 5, 3)); + + EXPECT_EQ(fresh.m_RcvKmState, SRT_KM_S_SECURED); + EXPECT_EQ(fresh.m_SndKmState, SRT_KM_S_SECURED); +} + +// Regression test: KMRSP carrying the UNSECURED peer-error code on a +// non-SECURED session must transition RX -> NOSECRET, SND -> UNSECURED. +// Mirror of the NOSECRET branch but with the opposite target mapping. +TEST_F(CryptoCtr, KmrspPeerUnsecuredOnNonSecured) +{ + using namespace srt; + + CCryptoControl fresh(/*socket id*/10); + CSrtConfig cfg; + memset(&cfg.CryptoSecret, 0, sizeof(cfg.CryptoSecret)); + cfg.CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE; + cfg.CryptoSecret.len = (int)m_pwd.size(); + memcpy(cfg.CryptoSecret.str, m_pwd.c_str(), m_pwd.size()); + fresh.setCryptoSecret(cfg.CryptoSecret); + cfg.iSndCryptoKeyLen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(4); + fresh.setCryptoKeylen(cfg.iSndCryptoKeyLen); + cfg.iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR; + ASSERT_TRUE(fresh.init(HSD_INITIATOR, cfg, true, false)); + ASSERT_NE(fresh.m_RcvKmState, SRT_KM_S_SECURED); + + uint32_t wire = (uint32_t)SRT_KM_S_UNSECURED; + uint32_t input = 0; + NtoHLA(&input, &wire, 1); + fresh.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3)); + + // Per crypto.cpp KMRSP UNSECURED branch: RX -> NOSECRET, SND -> UNSECURED. + EXPECT_EQ(fresh.m_RcvKmState, SRT_KM_S_NOSECRET); + EXPECT_EQ(fresh.m_SndKmState, SRT_KM_S_UNSECURED); +} + +#endif // AEAD -#endif //SRT_ENABLE_ENCRYPTION && ENABLE_AEAD_API_PREVIEW +#endif //SRT_ENABLE_ENCRYPTION From 06620ecee27963ccc49e31fef771d94879631d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Tue, 21 Jul 2026 20:34:17 +0200 Subject: [PATCH 78/92] stats: add send/receive quality percentage and confirmed-loss counter Adds pctSndQuality / pctRcvQuality to CBytePerfMon (100 = clean, 0 = nothing through), reported over a one-second sampling window that the timer thread rolls on its own cadence. Readers therefore get the last completed window: the value is independent of srt_bstats()'s clear flag and of how often the caller polls, so repeated reads within a window agree and a bad second does not linger in the following ones. Receive quality uses a new pktRcvLossConfirmed counter that, unlike pktRcvLoss (RFC 4737 sequence-discontinuity, which counts reordering as loss), counts only sequences the receiver actually concluded were lost and requested back. NAK repeats are excluded (first report only, keyed on the loss record's report_time). Exposed in statswriter; groups report a clean window, as loss is a per-member property. --- apps/statswriter.cpp | 3 ++ srtcore/common.h | 13 +++++++++ srtcore/core.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++ srtcore/core.h | 23 ++++++++++++++++ srtcore/group.cpp | 6 ++++ srtcore/srt.h | 15 ++++++++++ srtcore/stats.h | 3 ++ test/test_common.cpp | 45 ++++++++++++++++++++++++++++++ 8 files changed, 173 insertions(+) diff --git a/apps/statswriter.cpp b/apps/statswriter.cpp index f1aa343b59..42b4194de6 100644 --- a/apps/statswriter.cpp +++ b/apps/statswriter.cpp @@ -55,6 +55,7 @@ struct SrtStatsTableInit STAT(SEND, packetsLost, pktSndLoss); STAT(SEND, packetsDropped, pktSndDrop); STAT(SEND, packetsRetransmitted, pktRetrans); + STAT(SEND, quality, pctSndQuality); STAT(SEND, packetsFilterExtra, pktSndFilterExtra); STAT(SEND, bytes, byteSent); STAT(SEND, bytesUnique, byteSentUnique); @@ -67,6 +68,8 @@ struct SrtStatsTableInit STAT(RECV, packets, pktRecv); STAT(RECV, packetsUnique, pktRecvUnique); STAT(RECV, packetsLost, pktRcvLoss); + STAT(RECV, packetsLostConfirmed, pktRcvLossConfirmed); + STAT(RECV, quality, pctRcvQuality); STAT(RECV, packetsDropped, pktRcvDrop); STAT(RECV, packetsRetransmitted, pktRcvRetrans); STAT(RECV, packetsBelated, pktRcvBelated); diff --git a/srtcore/common.h b/srtcore/common.h index ff84c3fafc..fbb365cabf 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -1435,6 +1435,19 @@ inline bool checkMappedIPv4(const sockaddr_in6& sa) return checkMappedIPv4(addr); } +/// Share of the traffic in a sampled window that was not impaired, in percent. +/// @param clean packets that made it through on the first attempt +/// @param impaired packets that were lost, retransmitted or dropped +/// @return 100 for an empty window, 0 when nothing came through cleanly +inline double StatsQualityPct(int64_t clean, int64_t impaired) +{ + const int64_t total = clean + impaired; + if (total <= 0) + return 100.0; + + return (100.0 * clean) / total; +} + std::string FormatLossArray(const std::vector< std::pair >& lra); std::ostream& PrintEpollEvent(std::ostream& os, int events, int et_events = 0); diff --git a/srtcore/core.cpp b/srtcore/core.cpp index afa402d11d..b3f387636b 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -273,6 +273,9 @@ const int64_t SRTLA_HOLD_DECAY_TAU_US = 20000000; const double SRTLA_RENAK_RTT_FACTOR = 1.3; const int SRTLA_MIN_RCVLATENCY_MS = 1000; +// Sampling window for pctSndQuality / pctRcvQuality. +const int64_t QUALITY_WINDOW_US = 1000000; // 1 s + inline uint32_t srtlaDecayedFastHold(uint32_t stored_us, int64_t age_us) { if (stored_us == 0 || age_us >= SRTLA_HOLD_DECAY_TAU_US) @@ -964,6 +967,14 @@ void srt::CUDT::clearData() m_stats.traceReorderDistance = 0; m_stats.traceBelatedTime = 0; m_stats.sndDuration = m_stats.m_sndDurationTotal = 0; + + m_stats.tsQualityWindow = m_stats.tsStartTime; + m_stats.qualBaseSndClean = 0; + m_stats.qualBaseSndImpaired = 0; + m_stats.qualBaseRcvClean = 0; + m_stats.qualBaseRcvImpaired = 0; + m_stats.pctSndQuality = 100.0; + m_stats.pctRcvQuality = 100.0; } // Resetting these data because this happens when agent isn't connected. @@ -7548,6 +7559,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) perf->pktSndLoss = m_stats.sndr.lost.trace.count(); perf->pktRcvLoss = m_stats.rcvr.lost.trace.count(); + perf->pktRcvLossConfirmed = m_stats.rcvr.lostConfirmed.trace.count(); perf->pktRetrans = m_stats.sndr.sentRetrans.trace.count(); perf->pktRcvRetrans = m_stats.rcvr.recvdRetrans.trace.count(); perf->pktSentACK = m_stats.rcvr.sentAck.trace.count(); @@ -7586,6 +7598,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) perf->pktRecvUniqueTotal = m_stats.rcvr.recvdUnique.total.count(); perf->pktSndLossTotal = m_stats.sndr.lost.total.count(); perf->pktRcvLossTotal = m_stats.rcvr.lost.total.count(); + perf->pktRcvLossConfirmedTotal = m_stats.rcvr.lostConfirmed.total.count(); perf->pktRetransTotal = m_stats.sndr.sentRetrans.total.count(); perf->pktSentACKTotal = m_stats.rcvr.sentAck.total.count(); perf->pktRecvACKTotal = m_stats.sndr.recvdAck.total.count(); @@ -7629,6 +7642,12 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0; + // Last completed sampling window, maintained by the timer thread. Read + // only - independent of @a clear and of the caller's polling cadence, so + // repeated reads inside one window all yield the same value. + perf->pctSndQuality = m_stats.pctSndQuality; + perf->pctRcvQuality = m_stats.pctRcvQuality; + if (clear) { m_stats.sndr.resetTrace(); @@ -9989,12 +10008,23 @@ void srt::CUDT::processClose() CGlobEvent::triggerEvent(); } +void srt::CUDT::countConfirmedLoss(int pkts) +{ + if (pkts <= 0) + return; + + ScopedLock lg(m_StatsLock); + m_stats.rcvr.lostConfirmed.count(stats::Packets((uint32_t) pkts)); +} + void srt::CUDT::sendLossReport(const std::vector > &loss_seqs) { + int confirmed = 0; vector seqbuffer; seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i) { + confirmed += CSeqNo::seqoff(i->first, i->second) + 1; if (i->first == i->second) { seqbuffer.push_back(i->first); @@ -10012,6 +10042,7 @@ void srt::CUDT::sendLossReport(const std::vector > & if (!seqbuffer.empty()) { sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], (int) seqbuffer.size()); + countConfirmedLoss(confirmed); } } @@ -10692,6 +10723,7 @@ int srt::CUDT::processData(CUnit* in_unit) // can be quite well optimized. vector lossdata; + int confirmed_loss = 0; { ScopedLock lg(m_RcvLossLock); @@ -10723,6 +10755,7 @@ int srt::CUDT::processData(CUnit* in_unit) HLOGC(qrlog.Debug, log << "Packet seq " << i->seq[0] << "-" << i->seq[1] << " (" << (CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1) << " packets) considered lost - sending LOSSREPORT"); addLossRecord(lossdata, i->seq[0], i->seq[1]); + confirmed_loss += CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1; } // Remove elements that have been processed and prepared for lossreport. @@ -10754,6 +10787,7 @@ int srt::CUDT::processData(CUnit* in_unit) { sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int) lossdata.size()); } + countConfirmedLoss(confirmed_loss); // was_sent_in_order means either of: // - packet was sent in order (first if branch above) @@ -11566,6 +11600,7 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) } vector lossdata; + int confirmed_loss = 0; { ScopedLock lk(m_RcvLossLock); const uint32_t hold_us = srtlaReorderHoldUs(currtime); @@ -11585,12 +11620,15 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) && count_microseconds(currtime - i->report_time) < spacing_us) continue; addLossRecord(lossdata, i->seq[0], i->seq[1]); + if (is_zero(i->report_time)) // first report for this record, not a repeat + confirmed_loss += CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1; i->report_time = currtime; } } if (!lossdata.empty()) { sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int)lossdata.size()); + countConfirmedLoss(confirmed_loss); debug_decision = BECAUSE_NAKREPORT; } } @@ -11779,6 +11817,31 @@ void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime) m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); } +void srt::CUDT::updateQualityWindow(const steady_clock::time_point& currtime) +{ + ScopedLock stat_lock(m_StatsLock); + + if (count_microseconds(currtime - m_stats.tsQualityWindow) < QUALITY_WINDOW_US) + return; + + const int64_t snd_clean = m_stats.sndr.sentUnique.total.count(); + const int64_t snd_impaired = m_stats.sndr.sentRetrans.total.count() + + m_stats.sndr.dropped.total.count(); + const int64_t rcv_clean = m_stats.rcvr.recvdUnique.total.count(); + const int64_t rcv_impaired = m_stats.rcvr.lostConfirmed.total.count(); + + m_stats.pctSndQuality = StatsQualityPct(snd_clean - m_stats.qualBaseSndClean, + snd_impaired - m_stats.qualBaseSndImpaired); + m_stats.pctRcvQuality = StatsQualityPct(rcv_clean - m_stats.qualBaseRcvClean, + rcv_impaired - m_stats.qualBaseRcvImpaired); + + m_stats.qualBaseSndClean = snd_clean; + m_stats.qualBaseSndImpaired = snd_impaired; + m_stats.qualBaseRcvClean = rcv_clean; + m_stats.qualBaseRcvImpaired = rcv_impaired; + m_stats.tsQualityWindow = currtime; +} + void srt::CUDT::checkTimers() { // update CC parameters @@ -11786,6 +11849,8 @@ void srt::CUDT::checkTimers() const steady_clock::time_point currtime = steady_clock::now(); + updateQualityWindow(currtime); + // This is a very heavy log, unblock only for temporary debugging! #if 0 HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_tsNextACKTime) diff --git a/srtcore/core.h b/srtcore/core.h index 1c77fc4847..97d88536b7 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1088,6 +1088,13 @@ class CUDT int sendCtrlAck(CPacket& ctrlpkt, int size); void sendLossReport(const std::vector< std::pair >& losslist); + /// Record packets that are being requested back for the first time, that is, + /// that were still missing once the reorder grace period was over. Feeds + /// pctRcvQuality. NAK repeats must not be passed here. + /// Takes m_StatsLock, so the caller must not hold it. + /// @param pkts number of sequence numbers confirmed as lost + void countConfirmedLoss(int pkts); + void processCtrl(const CPacket& ctrlpkt); /// @brief Process incoming control ACK packet. @@ -1209,8 +1216,24 @@ class CUDT int64_t sndDuration; // real time for sending time_point sndDurationCounter; // timers to record the sending Duration + // Quality sampling window. Rolled on a fixed cadence by the timer thread + // (not by a reader), so the reported percentages describe the last + // completed window and stay identical for every read within it. + time_point tsQualityWindow; // start of the window currently filling + int64_t qualBaseSndClean; // cumulative counters snapshotted at window start + int64_t qualBaseSndImpaired; + int64_t qualBaseRcvClean; + int64_t qualBaseRcvImpaired; + double pctSndQuality; // last completed window, percent + double pctRcvQuality; + } m_stats; + /// Roll the quality sampling window if it is due, and recompute the + /// percentages from what accumulated inside it. Called from the timer + /// thread; takes m_StatsLock. + void updateQualityWindow(const time_point& currtime); + public: static const int SELF_CLOCK_INTERVAL = 64; // ACK interval for self-clocking static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq diff --git a/srtcore/group.cpp b/srtcore/group.cpp index 6b0b16484b..f7ca25b873 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -2549,6 +2549,12 @@ void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear) perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; + // The group keeps no loss counters of its own - loss is a per-member property, + // and so is the sampling window that backs these. Report a clean window and + // let the caller query the member sockets for the real thing. + perf->pctSndQuality = 100.0; + perf->pctRcvQuality = 100.0; + if (clear) { m_stats.reset(); diff --git a/srtcore/srt.h b/srtcore/srt.h index a28fb1411e..e80a644fc6 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -406,6 +406,21 @@ struct CBytePerfMon int64_t pktRecvUnique; // number of packets to be received by the application uint64_t byteSentUnique; // number of data bytes, sent by the application uint64_t byteRecvUnique; // number of data bytes to be received by the application + + // Packets that were still missing once the reorder grace period was over and were + // therefore requested back. Unlike pktRcvLoss (a sequence discontinuity metric per + // RFC 4737, which counts reordering as loss and never takes it back), this counts + // only losses the receiver itself concluded were real. NAK repeats are not counted. + int pktRcvLossConfirmedTotal; // total number of packets confirmed as lost (receiver side) + int pktRcvLossConfirmed; // number of packets confirmed as lost (receiver side) + + // Share of the traffic that was not impaired, in percent (100 = clean, 0 = nothing + // got through), over the last completed one-second sampling window. The window is + // rolled internally on its own cadence, so these are independent of @a clear and of + // how often the caller polls: every read inside the same window returns the same + // value. Both are 100 for an idle window. + double pctSndQuality; // 100 * pktSentUnique / (pktSentUnique + pktRetrans + pktSndDrop) + double pctRcvQuality; // 100 * pktRecvUnique / (pktRecvUnique + pktRcvLossConfirmed) }; //////////////////////////////////////////////////////////////////////////////// diff --git a/srtcore/stats.h b/srtcore/stats.h index 947489eb16..1b6a585bd4 100644 --- a/srtcore/stats.h +++ b/srtcore/stats.h @@ -176,6 +176,7 @@ struct Receiver Metric recvdUnique; Metric recvdRetrans; // The number of retransmitted data packets received by the receiver. Metric lost; // The number of packets detected by the receiver as lost. + Metric lostConfirmed; // The number of packets the receiver requested back, i.e. still missing once the reorder grace period was over. Counts a sequence once; NAK repeats are not counted. Metric dropped; // The number of packets dropped by the receiver (as too-late to be delivered). Metric recvdBelated; // The number of belated packets received (dropped as too late but eventually received). Metric undecrypted; // The number of packets received by the receiver that failed to be decrypted. @@ -193,6 +194,7 @@ struct Receiver recvdUnique.reset(); recvdRetrans.reset(); lost.reset(); + lostConfirmed.reset(); dropped.reset(); recvdBelated.reset(); undecrypted.reset(); @@ -209,6 +211,7 @@ struct Receiver recvdUnique.resetTrace(); recvdRetrans.resetTrace(); lost.resetTrace(); + lostConfirmed.resetTrace(); dropped.resetTrace(); recvdBelated.resetTrace(); undecrypted.resetTrace(); diff --git a/test/test_common.cpp b/test/test_common.cpp index 1a8cca061a..84b107b55a 100644 --- a/test/test_common.cpp +++ b/test/test_common.cpp @@ -71,3 +71,48 @@ TEST(CIPAddress, IPv4_in_IPv6_pton) test_cipaddress_pton(peer_ip, AF_INET6, ip); } + +// Quality percentage: the share of a sampling window that was not impaired. +TEST(StatsQuality, Percentage) +{ + // An empty window is reported clean rather than 0/0. + EXPECT_DOUBLE_EQ(StatsQualityPct(0, 0), 100.0); + + EXPECT_DOUBLE_EQ(StatsQualityPct(100, 0), 100.0); // nothing impaired + EXPECT_DOUBLE_EQ(StatsQualityPct(0, 100), 0.0); // nothing got through + EXPECT_DOUBLE_EQ(StatsQualityPct(75, 25), 75.0); + EXPECT_DOUBLE_EQ(StatsQualityPct(1, 1), 50.0); + + // Negative deltas cannot occur (counters are monotonic) but must not blow up. + EXPECT_DOUBLE_EQ(StatsQualityPct(-5, 0), 100.0); +} + +// The metric is a per-window value: each window is scored on its own deltas, so a +// bad second does not drag the following ones down (the failure mode of a +// cumulative counter, which can only ever creep back towards its long-run mean). +TEST(StatsQuality, WindowIsIndependentOfHistory) +{ + // Cumulative counters as they would be sampled at successive window ends. + struct { int64_t clean, impaired; } snap[] = { + { 0, 0}, // window start + {1000, 0}, // w1: clean + {1500, 500}, // w2: heavily impaired + {2500, 500}, // w3: clean again + }; + + const double w1 = StatsQualityPct(snap[1].clean - snap[0].clean, + snap[1].impaired - snap[0].impaired); + const double w2 = StatsQualityPct(snap[2].clean - snap[1].clean, + snap[2].impaired - snap[1].impaired); + const double w3 = StatsQualityPct(snap[3].clean - snap[2].clean, + snap[3].impaired - snap[2].impaired); + + EXPECT_DOUBLE_EQ(w1, 100.0); + EXPECT_DOUBLE_EQ(w2, 50.0); // scored on its own second only + EXPECT_DOUBLE_EQ(w3, 100.0); // snaps back, no memory of w2 + + // A cumulative reading of the same data would still be dragged down at w3 + // and could only improve from there - the behaviour this replaces. + const double cumulative_w3 = StatsQualityPct(snap[3].clean, snap[3].impaired); + EXPECT_LT(cumulative_w3, w3); +} From d8822d7cc26aad1c8d17da36f93f892916f3010c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:45:37 +0200 Subject: [PATCH 79/92] fix(srtla): stop the loss-report feedback loop with the sender dropFromLossLists() never revoked SRTLA loss records because the reorder tolerance is 0 there, so unrecovered losses stayed in the report for the life of the connection, crowding out fresh losses and drawing DROPREQ replies from the sender. Align the guard with unlose(), stop requesting records that no longer fit a round trip in the play budget, and move the selection into srtlaPlanNak() so it can be unit-tested. --- srtcore/core.cpp | 51 ++++++++----- srtcore/list.cpp | 55 ++++++++++++++ srtcore/list.h | 55 ++++++++++++++ test/test_losslist_rcv.cpp | 151 +++++++++++++++++++++++++++++++++++++ 4 files changed, 294 insertions(+), 18 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index b3f387636b..aa15de96c2 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -272,6 +272,7 @@ const uint32_t SRTLA_HOLD_EVIDENCE_MARGIN_US = 30000; const int64_t SRTLA_HOLD_DECAY_TAU_US = 20000000; const double SRTLA_RENAK_RTT_FACTOR = 1.3; const int SRTLA_MIN_RCVLATENCY_MS = 1000; +const int64_t SRTLA_DEADLINE_MARGIN_US = 30000; // Sampling window for pctSndQuality / pctRcvQuality. const int64_t QUALITY_WINDOW_US = 1000000; // 1 s @@ -11067,7 +11068,9 @@ void srt::CUDT::dropFromLossLists(int32_t from, int32_t to) << range.str() << " packets)"); #endif - if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0) + // SRTLA fills m_FreshLoss from a time-based hold, independently of the reorder + // tolerance. Same condition as in unlose(). + if (m_bPeerRexmitFlag == 0 || (!m_config.bSRTLA && m_iReorderTolerance == 0)) return; // All code below concerns only "belated lossreport" feature. @@ -11603,26 +11606,38 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) int confirmed_loss = 0; { ScopedLock lk(m_RcvLossLock); - const uint32_t hold_us = srtlaReorderHoldUs(currtime); - const int64_t srtt_us = m_iSRTT.load(); - const int64_t spacing_us = std::max( + + const int64_t srtt_us = m_iSRTT.load(); + + SrtlaNakParams np; + np.hold_us = srtlaReorderHoldUs(currtime); + np.spacing_us = std::max( (int64_t)(SRTLA_RENAK_RTT_FACTOR * (double)srtt_us), count_microseconds(m_tdNAKInterval)); - const size_t cap = (size_t)m_iMaxSRTPayloadSize / 4; - for (deque::iterator i = m_FreshLoss.begin(); - i != m_FreshLoss.end() && lossdata.size() + 2 <= cap; ++i) + // No TSBPD means no play deadline and no TLPKTDROP: deadline handling off. + np.budget_us = m_bTsbPd ? (int64_t)m_iTsbPdDelay_ms * 1000 : 0; + np.rtt_us = srtt_us; + np.margin_us = SRTLA_DEADLINE_MARGIN_US; + np.cap = (size_t)m_iMaxSRTPayloadSize / 4; + + const SrtlaNakPlan plan = srtlaPlanNak(m_FreshLoss, currtime, np); + + lossdata.reserve(2 * plan.report.size()); + for (size_t k = 0; k < plan.report.size(); ++k) { - if (i->ttl > 0) - continue; - if (count_microseconds(currtime - i->timestamp) < (int64_t)hold_us) - continue; - if (!is_zero(i->report_time) - && count_microseconds(currtime - i->report_time) < spacing_us) - continue; - addLossRecord(lossdata, i->seq[0], i->seq[1]); - if (is_zero(i->report_time)) // first report for this record, not a repeat - confirmed_loss += CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1; - i->report_time = currtime; + CRcvFreshLoss& rec = m_FreshLoss[plan.report[k]]; + addLossRecord(lossdata, rec.seq[0], rec.seq[1]); + rec.report_time = currtime; + } + confirmed_loss = plan.confirmed; + + // The indices above address the untouched container, so retire only now. + if (plan.retire > 0) + { + HLOGC(qrlog.Debug, + log << CONID() << "SRTLA: retiring " << plan.retire << " loss record(s) past the " + << m_iTsbPdDelay_ms << "ms play budget (" << m_FreshLoss.size() << " held)"); + m_FreshLoss.erase(m_FreshLoss.begin(), m_FreshLoss.begin() + plan.retire); } } if (!lossdata.empty()) diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 0c02e0b176..a1b1788fd4 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -955,3 +955,58 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32 } +srt::SrtlaNakPlan srt::srtlaPlanNak(const std::deque& fresh, + const srt::sync::steady_clock::time_point& now, + const SrtlaNakParams& params) +{ + using namespace srt::sync; + + SrtlaNakPlan plan; + + // Records are appended in detection order, and a split in removeOne() copies the + // original timestamp into both halves, so the container is sorted by age: everything + // past the play budget is a prefix. + if (params.budget_us > 0) + { + while (plan.retire < fresh.size() + && count_microseconds(now - fresh[plan.retire].timestamp) > params.budget_us) + { + ++plan.retire; + } + } + + size_t used = 0; // 32-bit words already claimed in the report + + for (size_t i = plan.retire; i < fresh.size(); ++i) + { + const CRcvFreshLoss& rec = fresh[i]; + + if (rec.ttl > 0) + continue; // not yet witnessed by enough subsequent packets + + const int64_t age_us = count_microseconds(now - rec.timestamp); + if (age_us < params.hold_us) + continue; // still within the reordering grace period + + // No round trip fits in what is left of the play budget. + if (params.budget_us > 0 && age_us + params.rtt_us + params.margin_us > params.budget_us) + continue; + + const bool first_report = is_zero(rec.report_time); + if (!first_report && count_microseconds(now - rec.report_time) < params.spacing_us) + continue; // repeat not due yet + + const size_t cost = (rec.seq[0] == rec.seq[1]) ? 1 : 2; + if (used + cost > params.cap) + break; // report is full, the remaining records get their turn next cycle + + plan.report.push_back(i); + used += cost; + + if (first_report) + plan.confirmed += CSeqNo::seqoff(rec.seq[0], rec.seq[1]) + 1; + } + + return plan; +} + diff --git a/srtcore/list.h b/srtcore/list.h index 684d05e91d..d9281abbc2 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -54,6 +54,7 @@ modified by #define INC_SRT_LIST_H #include +#include #include "udt.h" #include "common.h" @@ -305,6 +306,60 @@ struct CRcvFreshLoss srt::sync::steady_clock::time_point* w_detect_time = NULL); }; +/// Inputs for one SRTLA loss-report decision (see @c srtlaPlanNak). All durations +/// are in microseconds. Only used when SRTO_SRTLA is set. +struct SrtlaNakParams +{ + int64_t hold_us; //< reordering grace period before a record may be reported + int64_t spacing_us; //< minimum distance between two reports of the same record + int64_t budget_us; //< TSBPD play budget; 0 switches all deadline handling off + int64_t rtt_us; //< round trip a retransmission is expected to take + int64_t margin_us; //< safety margin kept free before the play deadline + size_t cap; //< capacity of the loss report payload, in 32-bit words + + SrtlaNakParams() + : hold_us(0) + , spacing_us(0) + , budget_us(0) + , rtt_us(0) + , margin_us(0) + , cap(0) + { + } +}; + +/// What to do with the fresh-loss records in this NAK cycle. +struct SrtlaNakPlan +{ + /// Indices into the container passed to @c srtlaPlanNak, in report order. They are + /// only valid as long as that container is unmodified, so stamp @c report_time on + /// these records FIRST and erase the @c retire prefix afterwards. + std::vector report; + int confirmed; //< packets in @c report that are being reported for the first time + size_t retire; //< number of leading records that outlived the play budget + + SrtlaNakPlan() + : confirmed(0) + , retire(0) + { + } +}; + +/// Decide which fresh-loss records belong in the next SRTLA loss report. +/// +/// Unlike plain SRT - where a record is reported once, as soon as its TTL expires, and +/// then dropped - SRTLA keeps a record until it is either recovered or dropped, and +/// repeats the request on a timer. This applies the reordering hold, the repeat spacing +/// and the play deadline, and caps the report at the payload size. +/// +/// @param fresh loss records, ordered by detection time (oldest first) +/// @param now current time +/// @param params timing and capacity limits +/// @return indices to report, the first-report packet count, and the prefix to retire +SrtlaNakPlan srtlaPlanNak(const std::deque& fresh, + const srt::sync::steady_clock::time_point& now, + const SrtlaNakParams& params); + } // namespace srt #endif diff --git a/test/test_losslist_rcv.cpp b/test/test_losslist_rcv.cpp index 3a1a5ffc14..455735cabc 100644 --- a/test/test_losslist_rcv.cpp +++ b/test/test_losslist_rcv.cpp @@ -206,3 +206,154 @@ TEST(CRcvFreshLossListTest, StripAndDeleteReportDetectTime) EXPECT_EQ(detect_out, detected); EXPECT_TRUE(floss.empty()); } + +// -------------------------------------------------------------------------- +// srtlaPlanNak: SRTLA loss-report policy +// -------------------------------------------------------------------------- + +namespace +{ +using srt::sync::steady_clock; + +/// A record detected @a age_ms ago, never reported, TTL already satisfied. +CRcvFreshLoss makeAged(int32_t lo, int32_t hi, int64_t age_ms) +{ + CRcvFreshLoss rec(lo, hi, 0); + rec.timestamp = steady_clock::now() - srt::sync::milliseconds_from(age_ms); + return rec; +} + +/// Params for a 1000 ms play budget: 100 ms hold, 200 ms repeat spacing, 50 ms RTT. +srt::SrtlaNakParams makeParams() +{ + srt::SrtlaNakParams p; + p.hold_us = 100000; + p.spacing_us = 200000; + p.budget_us = 1000000; + p.rtt_us = 50000; + p.margin_us = 30000; + p.cap = 364; // as for a 1456-byte payload + return p; +} +} // namespace + +/// The reordering hold gates the first report; an aged record passes it. +TEST(SrtlaPlanNak, HoldGatesFirstReport) +{ + std::deque floss; + floss.push_back(makeAged(10, 12, 50)); // younger than the 100 ms hold + floss.push_back(makeAged(20, 20, 150)); // past it + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, steady_clock::now(), makeParams()); + + ASSERT_EQ(plan.report.size(), 1u); + EXPECT_EQ(plan.report[0], 1u); + EXPECT_EQ(plan.confirmed, 1); // one packet, first report + EXPECT_EQ(plan.retire, 0u); +} + +/// A record whose TTL has not expired yet is not reported, however old it is. +TEST(SrtlaPlanNak, WitnessTtlBlocksReport) +{ + std::deque floss; + floss.push_back(makeAged(10, 10, 300)); + floss[0].ttl = 1; + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, steady_clock::now(), makeParams()); + + EXPECT_TRUE(plan.report.empty()); + EXPECT_EQ(plan.retire, 0u); +} + +/// A repeat is held back until the spacing has elapsed. +TEST(SrtlaPlanNak, SpacingGatesRepeat) +{ + const steady_clock::time_point now = steady_clock::now(); + + std::deque floss; + floss.push_back(makeAged(10, 10, 300)); + floss.push_back(makeAged(20, 20, 300)); + floss[0].report_time = now - srt::sync::milliseconds_from(100); // too recent + floss[1].report_time = now - srt::sync::milliseconds_from(250); // due again + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, now, makeParams()); + + ASSERT_EQ(plan.report.size(), 1u); + EXPECT_EQ(plan.report[0], 1u); + EXPECT_EQ(plan.confirmed, 0); // a repeat is not a newly confirmed loss +} + +/// Once no round trip fits in what is left of the play budget, the record is no +/// longer requested. +TEST(SrtlaPlanNak, DeadlineStopsRequesting) +{ + srt::SrtlaNakParams p = makeParams(); // budget 1000 ms, rtt 50 ms, margin 30 ms + + std::deque floss; + floss.push_back(makeAged(10, 10, 950)); // 950 + 50 + 30 > 1000 -> hopeless + floss.push_back(makeAged(20, 20, 800)); // 800 + 50 + 30 < 1000 -> still worth it + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, steady_clock::now(), p); + + ASSERT_EQ(plan.report.size(), 1u); + EXPECT_EQ(plan.report[0], 1u); + EXPECT_EQ(plan.retire, 0u); // not past the budget yet, so not retired either + + // With deadline handling off (no TSBPD) both are requested again. + p.budget_us = 0; + const srt::SrtlaNakPlan unbounded = srtlaPlanNak(floss, steady_clock::now(), p); + EXPECT_EQ(unbounded.report.size(), 2u); + EXPECT_EQ(unbounded.retire, 0u); +} + +/// Records past the play budget form a prefix and are retired, not reported. +TEST(SrtlaPlanNak, RetiresPrefixPastBudget) +{ + std::deque floss; + floss.push_back(makeAged(10, 10, 3000)); // long dead + floss.push_back(makeAged(20, 20, 1500)); // dead + floss.push_back(makeAged(30, 30, 150)); // live and due + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, steady_clock::now(), makeParams()); + + EXPECT_EQ(plan.retire, 2u); + ASSERT_EQ(plan.report.size(), 1u); + EXPECT_EQ(plan.report[0], 2u); // index refers to the untouched container + EXPECT_EQ(plan.confirmed, 1); +} + +/// The report is capped at the payload size, counting 2 words per range and 1 per +/// single sequence. Nothing is reported beyond the cap. +TEST(SrtlaPlanNak, RespectsPayloadCap) +{ + srt::SrtlaNakParams p = makeParams(); + p.cap = 5; // room for two ranges (4 words) plus one single sequence + + std::deque floss; + floss.push_back(makeAged(10, 12, 150)); // range -> 2 words + floss.push_back(makeAged(20, 22, 150)); // range -> 2 words + floss.push_back(makeAged(30, 30, 150)); // single -> 1 word + floss.push_back(makeAged(40, 40, 150)); // does not fit any more + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, steady_clock::now(), p); + + ASSERT_EQ(plan.report.size(), 3u); + EXPECT_EQ(plan.report[2], 2u); + EXPECT_EQ(plan.confirmed, 3 + 3 + 1); +} + +/// The prefix assumption behind the retire scan: a SPLIT keeps the deque ordered by +/// detection time, so retiring may never scan the whole container. +TEST(SrtlaPlanNak, SplitKeepsDequeOrderedByAge) +{ + std::deque floss; + floss.push_back(makeAged(10, 10, 900)); + floss.push_back(makeAged(20, 30, 500)); + floss.push_back(makeAged(40, 40, 100)); + + ASSERT_TRUE(CRcvFreshLoss::removeOne((floss), 25, NULL, NULL)); // splits record 1 + ASSERT_EQ(floss.size(), 4u); + + for (size_t i = 1; i < floss.size(); ++i) + EXPECT_LE(floss[i - 1].timestamp, floss[i].timestamp) << "order broken at " << i; +} From 9b2c6499513174cd0962961ec285e0b6a9b31e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:47:14 +0200 Subject: [PATCH 80/92] feat(srtla): pace and prioritise loss reports for the sender's window srtla_send charges a hundred times more per needless request than it pays back per acknowledged packet. Space repeats by a full round trip plus the reorder hold instead of 1.3 x SRTT, report fresh losses before repeats so a backlog cannot crowd them out, and keep the SRTLA emitter active with SRTO_NAKREPORT=0, since it is the only loss-report path there. --- srtcore/core.cpp | 13 ++++--- srtcore/list.cpp | 77 +++++++++++++++++++++++++++----------- test/test_losslist_rcv.cpp | 26 +++++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index aa15de96c2..715dce5ed6 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -270,7 +270,6 @@ const uint32_t SRTLA_HOLD_CAP_US = 500000; const uint32_t SRTLA_HOLD_FALLBACK_US = 250000; const uint32_t SRTLA_HOLD_EVIDENCE_MARGIN_US = 30000; const int64_t SRTLA_HOLD_DECAY_TAU_US = 20000000; -const double SRTLA_RENAK_RTT_FACTOR = 1.3; const int SRTLA_MIN_RCVLATENCY_MS = 1000; const int64_t SRTLA_DEADLINE_MARGIN_US = 30000; @@ -11570,7 +11569,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) // by the filter. By this reason they appear often out of order // and for adding them properly the loss list container wasn't // prepared. This then requires some more effort to implement. - if (!m_config.bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) + // Under SRTLA this is the only loss-report emitter, so SRTO_NAKREPORT must not + // disable it - the hold is the pacing. The packet-filter condition still applies. + const bool srtla_emitter = m_config.bSRTLA && m_bPeerRexmitFlag; + if ((!m_config.bRcvNakReport && !srtla_emitter) || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS) return BECAUSE_NO_REASON; /* @@ -11591,7 +11593,7 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) if (currtime <= m_tsNextNAKTime.load()) return BECAUSE_NO_REASON; // wait for next NAK time - if (m_config.bSRTLA && m_bPeerRexmitFlag) + if (srtla_emitter) { // SRTLA: time-gated loss reporting (single emitter for first // reports and repeats). @@ -11611,8 +11613,9 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) SrtlaNakParams np; np.hold_us = srtlaReorderHoldUs(currtime); - np.spacing_us = std::max( - (int64_t)(SRTLA_RENAK_RTT_FACTOR * (double)srtt_us), + // A round trip plus the reorder spread: SRTT tracks the fastest path, + // the retransmission may travel the slowest. + np.spacing_us = std::max(srtt_us + np.hold_us, count_microseconds(m_tdNAKInterval)); // No TSBPD means no play deadline and no TLPKTDROP: deadline handling off. np.budget_us = m_bTsbPd ? (int64_t)m_iTsbPdDelay_ms * 1000 : 0; diff --git a/srtcore/list.cpp b/srtcore/list.cpp index a1b1788fd4..8330e25b92 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -52,6 +52,8 @@ modified by #include "platform_sys.h" +#include + #include "list.h" #include "packet.h" #include "logging.h" @@ -955,6 +957,39 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32 } +namespace +{ +// Whether a record belongs in this report, disregarding the payload cap. +// @a want_first selects the pass: true = records never reported before. +bool srtlaNakEligible(const srt::CRcvFreshLoss& rec, + const srt::sync::steady_clock::time_point& now, + const srt::SrtlaNakParams& params, + bool want_first) +{ + using namespace srt::sync; + + const bool first_report = is_zero(rec.report_time); + if (first_report != want_first) + return false; + + if (rec.ttl > 0) + return false; // not yet witnessed by enough subsequent packets + + const int64_t age_us = count_microseconds(now - rec.timestamp); + if (age_us < params.hold_us) + return false; // still within the reordering grace period + + // No round trip fits in what is left of the play budget. + if (params.budget_us > 0 && age_us + params.rtt_us + params.margin_us > params.budget_us) + return false; + + if (!first_report && count_microseconds(now - rec.report_time) < params.spacing_us) + return false; // repeat not due yet + + return true; +} +} // namespace + srt::SrtlaNakPlan srt::srtlaPlanNak(const std::deque& fresh, const srt::sync::steady_clock::time_point& now, const SrtlaNakParams& params) @@ -977,36 +1012,34 @@ srt::SrtlaNakPlan srt::srtlaPlanNak(const std::deque& fresh, size_t used = 0; // 32-bit words already claimed in the report - for (size_t i = plan.retire; i < fresh.size(); ++i) + // Two passes so a never-reported loss cannot be crowded out of a full report by + // repeats of older ones. + for (int pass = 0; pass < 2; ++pass) { - const CRcvFreshLoss& rec = fresh[i]; + const bool want_first = (pass == 0); - if (rec.ttl > 0) - continue; // not yet witnessed by enough subsequent packets - - const int64_t age_us = count_microseconds(now - rec.timestamp); - if (age_us < params.hold_us) - continue; // still within the reordering grace period - - // No round trip fits in what is left of the play budget. - if (params.budget_us > 0 && age_us + params.rtt_us + params.margin_us > params.budget_us) - continue; + for (size_t i = plan.retire; i < fresh.size(); ++i) + { + const CRcvFreshLoss& rec = fresh[i]; - const bool first_report = is_zero(rec.report_time); - if (!first_report && count_microseconds(now - rec.report_time) < params.spacing_us) - continue; // repeat not due yet + if (!srtlaNakEligible(rec, now, params, want_first)) + continue; - const size_t cost = (rec.seq[0] == rec.seq[1]) ? 1 : 2; - if (used + cost > params.cap) - break; // report is full, the remaining records get their turn next cycle + const size_t cost = (rec.seq[0] == rec.seq[1]) ? 1 : 2; + if (used + cost > params.cap) + break; // report is full, the remaining records get their turn next cycle - plan.report.push_back(i); - used += cost; + plan.report.push_back(i); + used += cost; - if (first_report) - plan.confirmed += CSeqNo::seqoff(rec.seq[0], rec.seq[1]) + 1; + if (want_first) + plan.confirmed += CSeqNo::seqoff(rec.seq[0], rec.seq[1]) + 1; + } } + // Selection is fresh-first, transmission stays in sequence order. + std::sort(plan.report.begin(), plan.report.end()); + return plan; } diff --git a/test/test_losslist_rcv.cpp b/test/test_losslist_rcv.cpp index 455735cabc..41ef19bf34 100644 --- a/test/test_losslist_rcv.cpp +++ b/test/test_losslist_rcv.cpp @@ -342,6 +342,32 @@ TEST(SrtlaPlanNak, RespectsPayloadCap) EXPECT_EQ(plan.confirmed, 3 + 3 + 1); } +/// A never-reported loss is not crowded out of a full report by repeats of older +/// records, and the report still goes out in sequence order. +TEST(SrtlaPlanNak, FreshLossOutranksRepeats) +{ + const steady_clock::time_point now = steady_clock::now(); + + srt::SrtlaNakParams p = makeParams(); + p.cap = 2; // room for exactly two single sequences + + std::deque floss; + floss.push_back(makeAged(10, 10, 400)); // old, already reported, repeat is due + floss.push_back(makeAged(20, 20, 400)); // ditto + floss.push_back(makeAged(30, 30, 150)); // never reported + floss[0].report_time = now - srt::sync::milliseconds_from(250); + floss[1].report_time = now - srt::sync::milliseconds_from(250); + + const srt::SrtlaNakPlan plan = srtlaPlanNak(floss, now, p); + + ASSERT_EQ(plan.report.size(), 2u); + EXPECT_NE(std::find(plan.report.begin(), plan.report.end(), size_t(2)), plan.report.end()); + EXPECT_EQ(plan.confirmed, 1); + + for (size_t i = 1; i < plan.report.size(); ++i) + EXPECT_LT(plan.report[i - 1], plan.report[i]); +} + /// The prefix assumption behind the retire scan: a SPLIT keeps the deque ordered by /// detection time, so retiring may never scan the whole container. TEST(SrtlaPlanNak, SplitKeepsDequeOrderedByAge) From 2c95222f9cc9c8852b78236be302f48e8fa66766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:47:48 +0200 Subject: [PATCH 81/92] feat(srtla): send loss reports over the best links, not all of them Fanning loss reports out over every link cost the sender a packet-log walk and a duplicate retransmission per extra copy; they now go over the link that most recently delivered plus one for redundancy, while ACKs keep going everywhere because the sender rebuilds its in-flight counts from them. Also: stale links no longer widen the reorder hold, partial SRTLA-ACK batches flush after 10 ms so thin links are not starved, and the per-link pass in onPeriodic() is throttled. --- srtcore/srtla_rec.cpp | 73 +++++++++++++++++++++++++++++++++---------- srtcore/srtla_rec.h | 9 ++++-- 2 files changed, 64 insertions(+), 18 deletions(-) diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp index d70cb98f0e..0b36f21a46 100644 --- a/srtcore/srtla_rec.cpp +++ b/srtcore/srtla_rec.cpp @@ -11,6 +11,7 @@ #include "platform_sys.h" +#include #include #include "srtla_rec.h" @@ -600,24 +601,52 @@ bool srt::SrtlaRec::onEgress(const sockaddr_any& peer, CPacket& pkt, const socka if (!g) return false; // not an SRTLA group destination: caller sends normally + if (g->links.empty()) + { + // Registration gone but the CUDT still alive: fall back to its peer address. + m_pChannel->sendto(peer, pkt, src); + return true; + } + if (pkt.isControl()) { const UDTMessageType mt = pkt.getType(); - if (mt == UMSG_ACK || mt == UMSG_LOSSREPORT) + + if (mt == UMSG_ACK) { - // Fan SRT ACK / NAK out to every link so the sender can balance and - // recover on all paths. sendto() round-trips byte order per call. + // Every link: the sender rebuilds its per-link in-flight counts from these. for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it) m_pChannel->sendto(it->addr, pkt, src); - if (g->links.empty()) - m_pChannel->sendto(peer, pkt, src); + return true; + } + + if (mt == UMSG_LOSSREPORT) + { + // NAK_FANOUT links only, starting with the one that most recently delivered. + size_t sent = 0; + for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it) + { + if (g->has_last_addr && it->addr == g->last_addr) + { + m_pChannel->sendto(it->addr, pkt, src); + ++sent; + break; + } + } + for (std::list::iterator it = g->links.begin(); + it != g->links.end() && sent < NAK_FANOUT; ++it) + { + if (g->has_last_addr && it->addr == g->last_addr) + continue; + m_pChannel->sendto(it->addr, pkt, src); + ++sent; + } return true; } } - // Everything else goes to the group's most recently active link. - const sockaddr_any& dst = g->has_last_addr ? g->last_addr : peer; - m_pChannel->sendto(dst, pkt, src); + // Everything else goes over the most recently active link. + m_pChannel->sendto(g->has_last_addr ? g->last_addr : peer, pkt, src); return true; } @@ -661,18 +690,25 @@ void srt::SrtlaRec::onPeriodic(const time_point& now) if (!m_HaveTimers) { - m_LastCleanup = now; - m_HaveTimers = true; + m_LastCleanup = now; + m_LastLinkPass = now; + m_HaveTimers = true; return; } - // Flush aged partial SRTLA-ACK batches (covers links that went quiet). - for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g) + // Flush aged partial SRTLA-ACK batches (covers links that went quiet). Throttled: + // the pass is O(groups x links) and the receive worker reaches it once per datagram. + if (count_microseconds(now - m_LastLinkPass) >= LINK_PASS_US) { - for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it) + m_LastLinkPass = now; + + for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g) { - if (it->ack_count > 0 && count_microseconds(now - it->ack_first_pending) >= ACK_FLUSH_US) - sendSrtlaAck(it->addr, *it); + for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it) + { + if (it->ack_count > 0 && count_microseconds(now - it->ack_first_pending) >= ACK_FLUSH_US) + sendSrtlaAck(it->addr, *it); + } } } @@ -763,12 +799,17 @@ uint32_t srt::SrtlaRec::holdSteadyUs(SRTSOCKET socket_id) if (!grp) return 0; + const time_point now = steady_clock::now(); + double tmin = 0.0, tmax = 0.0, jmax = 0.0; bool have = false; for (std::list::iterator it = grp->links.begin(); it != grp->links.end(); ++it) { - if (!it->transit_valid) + // transit_ewma only advances on arriving data, so a stale one is not a current + // measurement and must not widen the spread. + if (!it->transit_valid || count_microseconds(now - it->last_transit) > LINK_FRESH_US) continue; + if (!have) { tmin = tmax = it->transit_ewma; diff --git a/srtcore/srtla_rec.h b/srtcore/srtla_rec.h index d0fe254d7a..b3c68578b7 100644 --- a/srtcore/srtla_rec.h +++ b/srtcore/srtla_rec.h @@ -61,9 +61,13 @@ class SrtlaRec static const size_t SRT_MIN_LEN = 16; // min length treated as an SRT packet static const size_t MIN_PAD = 32; // short control packets padded to this static const int RECV_ACK_INT = 10; // SRT data packets per SRTLA ACK (batch cap) - static const int64_t ACK_FLUSH_US = 30000; // partial-batch flush age + static const int64_t ACK_FLUSH_US = 10000; // partial-batch flush age static const size_t MAX_CONNS_PER_GROUP = 16; // == SRT_SRTLA_MAX_PEERS static const size_t MAX_GROUPS = 200; + + static const size_t NAK_FANOUT = 2; // links a loss report is duplicated over + static const int64_t LINK_FRESH_US = 1000000; // age up to which a transit measurement counts + static const int64_t LINK_PASS_US = 2000; // period of the per-link pass in onPeriodic() static const int32_t SN_WINDOW_SIZE = 65536; // retransmission-tracking bitmap static const int32_t SN_WINDOW_MASK = SN_WINDOW_SIZE - 1; @@ -172,7 +176,7 @@ class SrtlaRec SRTSOCKET socket_id; // bound CUDT socket id, or SRT_INVALID_SOCK bool bound; time_point created_at; - sockaddr_any last_addr; // most recently active link (reverse path primary) + sockaddr_any last_addr; // most recently active link (reverse path, REG1 idempotency) bool has_last_addr; bool data_seen; @@ -274,6 +278,7 @@ class SrtlaRec std::map m_EgressMap; // CUDT peer addr → group (bind lifetime) time_point m_LastCleanup; + time_point m_LastLinkPass; bool m_HaveTimers; SrtlaRec(const SrtlaRec&); From a13859e845cf374f2bbbd53d1819ad400dcede66 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Thu, 30 Jul 2026 14:17:50 +0200 Subject: [PATCH 82/92] Fixed gcov rule. Fixed ABI compliance checker to use local installation (#3349) * Fixed gcov rule. Fixed ABI compliance checker to use local installation * Removed codecov for C++03. Fixed wrong call path for gdb after tests * Fixed crypto tests build break when encryption disabled * Fixed CI on macos. Tracking a problem on ABI * Refactored TestEnforcedEncryption to simplify initialization * Fixed ABI. Fixed mac warn build break on ENC=no * Added missing ASH variable to output * Tracking problem with ABI * Changed abi compliance checker to official package. Fixed options in configure-data * Preserved RES up until the end to prevent error blocking report download * Changed command call to swallow error result (prevents premature interrupt of the step) * Fixed uploading HTML report for ABI * I hate YAML * Using Create Browser Link instead * Changed ABI reporting to uploading html with no zipping * Updated Ubuntu packages before installing --------- Co-authored-by: Mikolaj Malecki --- .github/workflows/abi.yml | 41 +++++---- .github/workflows/macos.yml | 1 + .github/workflows/ubuntu-c++03.yml | 13 ++- .github/workflows/ubuntu-c++11.yml | 17 ++-- CMakeLists.txt | 21 +++-- configure-data.tcl | 59 ++++++------- scripts/codecov/update.sh | 5 ++ scripts/collect-gcov.sh | 7 -- srtcore/crypto.cpp | 1 + test/test_crypto.cpp | 5 +- test/test_enforced_encryption.cpp | 128 +++++++++++++---------------- 11 files changed, 157 insertions(+), 141 deletions(-) create mode 100755 scripts/codecov/update.sh delete mode 100644 scripts/collect-gcov.sh diff --git a/.github/workflows/abi.yml b/.github/workflows/abi.yml index 4d4d6012c6..846dbbb2e8 100644 --- a/.github/workflows/abi.yml +++ b/.github/workflows/abi.yml @@ -26,13 +26,13 @@ jobs: run: | cd gitview_pr mkdir _build && cd _build - cmake -DCMAKE_BUILD_TYPE=Debug ../ + cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_BONDING=1 -DENABLE_PKTINFO=1 -DENABLE_MAXREXMITBW=1 ../ - id: build name: Build and dump run: | - sudo apt install -y abi-dumper - sudo apt install -y tcl - cd gitview_pr/_build && cmake --build ./ + sudo apt install -y abi-dumper abi-compliance-checker tcl + cd gitview_pr + cd _build && cmake --build ./ make install DESTDIR=./installdir SRT_TAG_VERSION=v$(../scripts/get-build-version.tcl full) echo "SRT_TAG_VERSION=$SRT_TAG_VERSION" >> "$GITHUB_OUTPUT" @@ -49,6 +49,9 @@ jobs: echo "WILL CHECK ABI changes $SRT_BASE - $SRT_TAG_VERSION" echo "SRT_BASE=$SRT_BASE" >> "$GITHUB_OUTPUT" fi + echo "--- GITHUB_OUTPUT ---" + cat $GITHUB_OUTPUT + echo "---------------------" - id: upload_pr_dump uses: actions/upload-artifact@v4 with: @@ -81,7 +84,7 @@ jobs: fi cd gitview_base mkdir _build && cd _build - cmake -DCMAKE_BUILD_TYPE=Debug ../ + cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_BONDING=1 -DENABLE_PKTINFO=1 -DENABLE_MAXREXMITBW=1 ../ - id: build_tag name: Build and dump if: ${{ success() }} @@ -121,22 +124,30 @@ jobs: path: . - name: abi-check run: | - 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 ../.. + sudo apt install -y abi-dumper abi-compliance-checker tcl echo "FILESYSTEM state before running abi-check at $PWD" ls -l sha256sum libsrt-base.dump sha256sum libsrt-pr.dump - abi-compliance-checker -l libsrt -old libsrt-base.dump -new libsrt-pr.dump - RES=$? + RES=0 + abi-compliance-checker -l libsrt -old libsrt-base.dump -new libsrt-pr.dump || RES=$? + # Flatten the report for download-preview + cd compat_reports + REPORT=$(find . -name *.html) + cp $REPORT compat_report.html + cd .. if (( $RES != 0 )); then echo "ABI/API Compatibility check failed with value $?" - exit $RES + echo "RES=$RES" >>$GITHUB_ENV + exit 0 fi - name: Download report - uses: actions/download-artifact@v4 + uses: actions/upload-artifact@v7 with: - path: compat_reports + name: abi-compliance-report + path: compat_reports/compat_report.html + archive: false + - name: Final result + run: | + echo "Final result: $RES" + exit $RES diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7ec1cfb802..d8b1f3b892 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -16,6 +16,7 @@ jobs: run: | brew tap-new Haivision/gt-local brew extract --version=1.12.1 --force googletest Haivision/gt-local + brew trust Haivision/gt-local brew install googletest@1.12.1 # NOTE: 1.12.1 is the last version that requires C++11; might need update later # curl -o googletest.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/23e7fb4dc0cc73facc3772815741e1deb87d6406/Formula/googletest.rb diff --git a/.github/workflows/ubuntu-c++03.yml b/.github/workflows/ubuntu-c++03.yml index 3f38598cad..8b9b685f56 100644 --- a/.github/workflows/ubuntu-c++03.yml +++ b/.github/workflows/ubuntu-c++03.yml @@ -19,11 +19,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: configure + - name: prepare run: | + sudo apt update sudo apt install -y tcl cmake libssl-dev gdb + - name: configure + run: | mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=OFF -DUSE_CXX_STD=03 -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=${{ matrix.bonding }} -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DENABLE_LOGGING=${{ matrix.logging }} + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DENABLE_STDCXX_SYNC=OFF -DUSE_CXX_STD=03 -DENABLE_ENCRYPTION=ON -DENABLE_UNITTESTS=ON -DENABLE_BONDING=${{ matrix.bonding }} -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DENABLE_LOGGING=${{ matrix.logging }} - name: build # That below is likely SonarQube remains, which was removed earlier. #run: cd _build && build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build . @@ -34,9 +37,5 @@ jobs: ulimit -c unlimited cd _build && ctest --extra-verbose SUCCESS=$? - if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core.test-srt -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; test $SUCCESS == 0; - - name: codecov - run: | - source ./scripts/collect-gcov.sh - bash <(curl -s https://codecov.io/bash) diff --git a/.github/workflows/ubuntu-c++11.yml b/.github/workflows/ubuntu-c++11.yml index 59efed7de8..e0152a09d4 100644 --- a/.github/workflows/ubuntu-c++11.yml +++ b/.github/workflows/ubuntu-c++11.yml @@ -19,17 +19,20 @@ jobs: - uses: actions/checkout@v3 - name: prepare run: | + sudo apt update RUNON=${{ matrix.machine }} if [[ $RUNON == ubuntu-latest ]]; then sudo apt install -y gdb fi + sudo apt install -y tcl cmake + [[ $RUNON == ubuntu-24.04-arm ]] || sudo apt install libssl-dev - name: configure run: | RUNON=${{ matrix.machine }} ENCRYPTION=ON [[ $RUNON == ubuntu-24.04-arm ]] && ENCRYPTION=OFF mkdir _build && cd _build - cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DUSE_CXX_STD=11 -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=$ENCRYPTION -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake ../ -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DUSE_CXX_STD=11 -DENABLE_STDCXX_SYNC=ON -DENABLE_ENCRYPTION=$ENCRYPTION -DENABLE_UNITTESTS=ON -DENABLE_BONDING=ON -DENABLE_TESTING=ON -DENABLE_EXAMPLES=ON -DENABLE_DEBUG=ON -DENABLE_CODE_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - name: build run: cd _build && cmake --build . - name: test @@ -38,9 +41,13 @@ jobs: ulimit -c unlimited cd _build && ctest --extra-verbose SUCCESS=$? - if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; + if [ -f core.test-srt ]; then gdb -batch ./test-srt -c core.test-srt -ex bt -ex "info thread" -ex quit; else echo "NO CORE - NO CRY!"; fi; test $SUCCESS == 0; - name: codecov - run: | - source ./scripts/collect-gcov.sh - bash <(curl -s https://codecov.io/bash) + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + # name: codecov + # run: | + # ./scripts/codecov/update.sh + # ./scripts/codecov/codecov upload-process ${CODECOV_TOKEN} --search-dir _build diff --git a/CMakeLists.txt b/CMakeLists.txt index 87aba46664..18895c0401 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -910,13 +910,24 @@ if (ENABLE_PROFILE) endif() if (ENABLE_CODE_COVERAGE) - if (HAVE_COMPILER_GNU_COMPAT) - add_definitions(-g -O0 --coverage) - link_libraries(--coverage) - message(STATUS "ENABLE_CODE_COVERAGE: ON") - else() + if (NOT HAVE_COMPILER_GNU_COMPAT) message(FATAL_ERROR "ENABLE_CODE_COVERAGE: option is not supported on this platform") endif() + + block() + if (ENABLE_DEBUG EQUAL 2) + elseif (NOT ENABLE_DEBUG) + else() + set (is_debug 1) + endif() + if (NOT is_debug) + message(FATAL_ERROR "ENABLE_CODE_COVERAGE: requires ENABLE_DEBUG or Debug build type") + endif() + endblock() + + add_definitions(--coverage) + link_libraries(--coverage) + message(STATUS "ENABLE_CODE_COVERAGE: ON") endif() # On Linux pthreads have to be linked even when using C++11 threads diff --git a/configure-data.tcl b/configure-data.tcl index d7b9d49376..0faec42b82 100644 --- a/configure-data.tcl +++ b/configure-data.tcl @@ -34,48 +34,49 @@ set internal_options { # Options that refer directly to variables used in CMakeLists.txt set cmake_options { + atomic-use-srt-sync-mutex "Use mutex to implement atomics (alias: --with-atomic=sync-mutex) (default: OFF)" cygwin-use-posix "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin. (default: OFF)" - enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON, with gcc < 4.7 OFF)" + enable-aead-api-preview "Enable AEAD API preview in SRT (default: OFF)" enable-apps "Should the Support Applications be Built? (default: ON)" enable-bonding "Enable 'bonding' SRT feature (default: OFF)" - enable-testing "Should developer testing applications be built (default: OFF)" - enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)" - enable-logging "Should logging be enabled (default: ON)" - enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)" + enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON, with gcc < 4.7 OFF)" + enable-c++-deps "Extra library dependencies in srt.pc for C language (default: ON)" + enable-clang-tsa "Enable Clang's Thread-Safety-Analysis (default: OFF)" + enable-code-coverage "Enable code coverage reporting (default: OFF)" + enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)" + enable-encryption "Should encryption features be enabled (default: ON)" + enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)" enable-haicrypt-logging "Should logging in haicrypt be enabled (default: OFF)" + enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)" + enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)" + enable-logging "Should logging be enabled (default: ON)" + enable-maxrexmitbw "Enable SRTO_MAXREXMITBW (v1.6.0 API preview) (default: OFF)" + enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)" enable-pktinfo "Should pktinfo reading and using be enabled (POSIX only) (default: OFF)" + enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)" + enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)" enable-shared "Should libsrt be built as a shared library (default: ON)" + enable-show-project-config "Enables use of ShowProjectConfig() in cmake (default: OFF)" + enable-sock-cloexec "Enable setting SOCK_CLOEXEC on a socket (default: ON)" enable-static "Should libsrt be built as a static library (default: ON)" - enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)" - enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)" - enable-unittests "Enable Unit Tests (will download Google UT) (default: OFF)" - enable-unittests-discovery "Enable UT Discovery (will run when building) (default: ON)" - enable-encryption "Should encryption features be enabled (default: ON)" - enable-c++-deps "Extra library dependencies in srt.pc for C language (default: ON)" - use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)" - enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)" - enable-code-coverage "Enable code coverage reporting (default: OFF)" - enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)" - enable-thread-check "Enable #include that implements THREAD_* macros" enable-stdc++-sync "Use standard C++11 chrono/threads instead of pthread wrapper (default: OFF, on Windows: ON)" - use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)" - openssl-use-static-libs "Link OpenSSL statically (default: OFF)." - use-busy-waiting "Enable more accurate sending times at a cost of potentially higher CPU load (default: OFF)" - use-gnustl "Get c++ library/headers from the gnustl.pc" - enable-sock-cloexec "Enable setting SOCK_CLOEXEC on a socket (default: ON)" - enable-show-project-config "Enables use of ShowProjectConfig() in cmake (default: OFF)" - enable-new-rcvbuffer "Enables the new receiver buffer implementation (default: ON)" - enable-clang-tsa "Enable Clang's Thread-Safety-Analysis (default: OFF)" - atomic-use-srt-sync-mutex "Use mutex to implement atomics (alias: --with-atomic=sync-mutex) (default: OFF)" - - use-enclib "Encryption library to be used: openssl(default), gnutls, mbedtls, botan" - enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)" - pkg-config-executable= "pkg-config executable" + enable-testing "Should developer testing applications be built (default: OFF)" + enable-thread-check "Enable #include that implements THREAD_* macros" + enable-unittests-discovery "Enable UT Discovery (will run when building) (default: ON)" + enable-unittests "Enable Unit Tests (will download Google UT) (default: OFF)" openssl-crypto-library= "OpenSSL: Path to a libcrypto library." openssl-include-dir= "OpenSSL: Path to includes." openssl-ssl-library= "OpenSSL: Path to a libssl library." + openssl-use-static-libs "Link OpenSSL statically (default: OFF)." + pkg-config-executable= "pkg-config executable" pthread-include-dir= "PThread: Path to includes" pthread-library= "PThread: Path to the pthread library." + srt-use-openssl-static-libs "Link OpenSSL libraries statically. (default: OFF)" + use-busy-waiting "Enable more accurate sending times at a cost of potentially higher CPU load (default: OFF)" + use-enclib "Encryption library to be used: openssl(default), gnutls, mbedtls, botan" + use-gnustl "Get c++ library/headers from the gnustl.pc" + use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)" + use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)" } set options $internal_options$cmake_options diff --git a/scripts/codecov/update.sh b/scripts/codecov/update.sh new file mode 100755 index 0000000000..1aadf9277c --- /dev/null +++ b/scripts/codecov/update.sh @@ -0,0 +1,5 @@ +#!/bin/bash +HERE=`dirname $0` +cd $HERE +curl -L -o codecov https://cli.codecov.io/latest/linux/codecov +chmod +x codecov diff --git a/scripts/collect-gcov.sh b/scripts/collect-gcov.sh deleted file mode 100644 index 7b458e6c1a..0000000000 --- a/scripts/collect-gcov.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -shopt -s globstar -gcov_data_dir="." -for x in ./**/*.o; do - echo "x: $x" - gcov "$gcov_data_dir/$x" -done diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index 93b5bbe60a..cf92175d5c 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -946,6 +946,7 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU HLOGC(cnlog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc); return ENCS_CLEAR; #else + (void)m_bErrorReported; // otherwise warning! return ENCS_NOTSUP; #endif } diff --git a/test/test_crypto.cpp b/test/test_crypto.cpp index c8cc2ce3bd..47fcad8f23 100644 --- a/test/test_crypto.cpp +++ b/test/test_crypto.cpp @@ -7,6 +7,9 @@ #include "gtest/gtest.h" #include "test_env.h" +#ifdef SRT_ENABLE_ENCRYPTION + + #include "crypto.h" #include "handshake.h" #include "hcrypt_msg.h" @@ -14,8 +17,6 @@ #include "socketconfig.h" #include "api.h" -#ifdef SRT_ENABLE_ENCRYPTION - // processSrtMsg_KMRSP must reject malformed wire-supplied lengths before they // reach the fixed-size stack buffer / uninitialised-read paths inside the // function. Built into the library unconditionally, so this test runs diff --git a/test/test_enforced_encryption.cpp b/test/test_enforced_encryption.cpp index 717b4549ca..b3807ffd1e 100644 --- a/test/test_enforced_encryption.cpp +++ b/test/test_enforced_encryption.cpp @@ -59,10 +59,10 @@ enum TEST_CASE TEST_CASE_D_3, TEST_CASE_D_4, TEST_CASE_D_5, + TEST_CASE_COUNT }; - -struct TestResultNonBlocking +struct Expect { int connect_ret; int accept_ret; @@ -72,26 +72,18 @@ struct TestResultNonBlocking int 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]; -}; - - -template +template struct TestCase { + static const bool blocking = BLOCKING; + bool enforcedenc [PEER_COUNT]; const std::string (&password)[PEER_COUNT]; - TResult expected_result; + Expect expected_result; }; -typedef TestCase TestCaseNonBlocking; -typedef TestCase TestCaseBlocking; +typedef TestCase TestCaseNonBlocking; +typedef TestCase TestCaseBlocking; @@ -139,10 +131,10 @@ static const std::string s_pwd_no(""); const int IGNORE_EPOLL = -2; const int IGNORE_SRTS = -1; -const TestCaseNonBlocking g_test_matrix_non_blocking[] = +const TestCaseNonBlocking g_test_matrix_non_blocking[TEST_CASE_COUNT] = { - // ENFORCEDENC | Password | | EPoll wait | socket_state | KM State - // caller | listener | caller | listener | connect_ret accept_ret | ret | event | caller accepted | caller listener + // 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}}}, @@ -183,33 +175,33 @@ const TestCaseNonBlocking g_test_matrix_non_blocking[] = * * In the cases C.2-C.4 it is the listener who rejects the connection, so we don't have an accepted socket. */ -const TestCaseBlocking g_test_matrix_blocking[] = +const TestCaseBlocking g_test_matrix_blocking[TEST_CASE_COUNT] = { - // 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 | | Epoll wait (ignored) | 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, IGNORE_EPOLL, 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_INVALID_SOCK, SRT_INVALID_SOCK, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, +/*A.3 */ { {true, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, +/*A.4 */ { {true, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, +/*A.5 */ { {true, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 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, IGNORE_EPOLL, 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_INVALID_SOCK, -2, IGNORE_EPOLL, SRT_EPOLL_IN, {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, IGNORE_EPOLL, SRT_EPOLL_IN, {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, IGNORE_EPOLL, SRT_EPOLL_IN, {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, IGNORE_EPOLL, 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, IGNORE_EPOLL, 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_INVALID_SOCK, SRT_INVALID_SOCK, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, +/*C.3 */ { {false, true }, {s_pwd_a, s_pwd_no}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, +/*C.4 */ { {false, true }, {s_pwd_no, s_pwd_b}, { SRT_INVALID_SOCK, SRT_INVALID_SOCK, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_OPENED, -1}, {SRT_KM_S_UNSECURED, -1}}}, +/*C.5 */ { {false, true }, {s_pwd_no, s_pwd_no}, { SRT_SUCCESS, 0, IGNORE_EPOLL, 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, IGNORE_EPOLL, 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, IGNORE_EPOLL, 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, IGNORE_EPOLL, 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, IGNORE_EPOLL, 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, IGNORE_EPOLL, SRT_EPOLL_IN, {SRTS_CONNECTED, SRTS_CONNECTED}, {SRT_KM_S_UNSECURED, SRT_KM_S_UNSECURED}}}, }; @@ -321,14 +313,13 @@ class TestEnforcedEncryption int WaitOnEpoll(const TResult &expect); - template - const TestCase& GetTestMatrix(TEST_CASE test_case) const; + template + const TCase& GetTestMatrix(TEST_CASE test_case) const; - template + template void TestConnect(TEST_CASE test_case/*, bool is_blocking*/) { - const bool is_blocking = std::is_same::value; - if (is_blocking) + if (TCase::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); @@ -344,7 +335,7 @@ class TestEnforcedEncryption } // Prepare input state - const TestCase &test = GetTestMatrix(test_case); + const auto& 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); @@ -356,7 +347,7 @@ class TestEnforcedEncryption const bool case_both_relaxed = !test.enforcedenc[PEER_LISTENER] && !test.enforcedenc[PEER_CALLER]; const bool case_sender_enc = test.password[PEER_CALLER] != ""; - const TResult &expect = test.expected_result; + const auto& expect = test.expected_result; // Start testing srt::sync::atomic caller_done; @@ -372,7 +363,7 @@ class TestEnforcedEncryption SRTSOCKET accepted_socket = -1; auto accepting_thread = std::thread([&] { - const int epoll_event = WaitOnEpoll(expect); + const int epoll_event = WaitOnEpoll(test); // In a blocking mode we expect a socket returned from srt_accept() if the srt_connect succeeded. // In a non-blocking mode we expect a socket returned from srt_accept() if the srt_connect succeeded, @@ -460,7 +451,7 @@ class TestEnforcedEncryption caller_done = true; - if (is_blocking == false) + if (TCase::blocking == false) accepting_thread.join(); if (m_is_tracing) @@ -475,7 +466,7 @@ class TestEnforcedEncryption // If a blocking call to srt_connect() returned error, then the state is not valid, // but we still check it because we know what it should be. This way we may see potential changes in the core behavior. - if (is_blocking) + if (TCase::blocking) { EXPECT_EQ(srt_getsockstate(m_caller_socket), expect.socket_state[CHECK_SOCKET_CALLER]); } @@ -496,7 +487,7 @@ class TestEnforcedEncryption EXPECT_EQ(srt_getsockstate(m_listener_socket), SRTS_LISTENING); EXPECT_EQ(GetKMState(m_listener_socket), SRT_KM_S_UNSECURED); - if (!is_blocking && case_both_relaxed && case_pw_failure && case_sender_enc) + if (!TCase::blocking && case_both_relaxed && case_pw_failure && case_sender_enc) { // Additionally check decryption failure does not trigger read-readiness (see issue #2503). @@ -543,7 +534,7 @@ class TestEnforcedEncryption srt_epoll_release(epollRead); } - if (is_blocking) + if (TCase::blocking) { // srt_accept() has no timeout, so we have to close the socket and wait for the thread to exit. // Just give it some time and close the socket. @@ -574,7 +565,7 @@ class TestEnforcedEncryption template<> -int TestEnforcedEncryption::WaitOnEpoll(const TestResultBlocking &) +int TestEnforcedEncryption::WaitOnEpoll(const TestCaseBlocking &) { return SRT_EPOLL_IN; } @@ -607,8 +598,9 @@ static std::ostream& PrintEpollEvent(std::ostream& os, int events, int et_events } template<> -int TestEnforcedEncryption::WaitOnEpoll(const TestResultNonBlocking &expect) +int TestEnforcedEncryption::WaitOnEpoll(const TestCaseNonBlocking &tcase) { + const auto& expect = tcase.expected_result; const int default_len = 3; SRT_EPOLL_EVENT ready[default_len]; const int epoll_res = srt_epoll_uwait(m_pollid, ready, default_len, 500); @@ -649,13 +641,13 @@ int TestEnforcedEncryption::WaitOnEpoll(const TestResultN template<> -const TestCase& TestEnforcedEncryption::GetTestMatrix(TEST_CASE test_case) const +const TestCaseBlocking& TestEnforcedEncryption::GetTestMatrix(TEST_CASE test_case) const { return g_test_matrix_blocking[test_case]; } template<> -const TestCase& TestEnforcedEncryption::GetTestMatrix(TEST_CASE test_case) const +const TestCaseNonBlocking& TestEnforcedEncryption::GetTestMatrix(TEST_CASE test_case) const { return g_test_matrix_non_blocking[test_case]; } @@ -740,20 +732,14 @@ TEST_F(TestEnforcedEncryption, SetGetDefault) } -#define CREATE_TEST_CASE_BLOCKING(CASE_NUMBER, DESC) TEST_F(TestEnforcedEncryption, CASE_NUMBER##_Blocking_##DESC)\ +#define CREATE_TEST_C(IFBLOCKING, CASE_NUMBER, DESC) TEST_F(TestEnforcedEncryption, CASE_NUMBER##_##IFBLOCKING##_##DESC)\ {\ - TestConnect(TEST_##CASE_NUMBER);\ + TestConnect(TEST_##CASE_NUMBER);\ } -#define CREATE_TEST_CASE_NONBLOCKING(CASE_NUMBER, DESC) TEST_F(TestEnforcedEncryption, CASE_NUMBER##_NonBlocking_##DESC)\ -{\ - TestConnect(TEST_##CASE_NUMBER);\ -} - - #define CREATE_TEST_CASES(CASE_NUMBER, DESC) \ - CREATE_TEST_CASE_NONBLOCKING(CASE_NUMBER, DESC) \ - CREATE_TEST_CASE_BLOCKING(CASE_NUMBER, DESC) + CREATE_TEST_C(Blocking, CASE_NUMBER, DESC) \ + CREATE_TEST_C(NonBlocking, CASE_NUMBER, DESC) #ifdef SRT_ENABLE_ENCRYPTION CREATE_TEST_CASES(CASE_A_1, Enforced_On_On_Pwd_Set_Set_Match) From fcae57145c000a9e7b72aa777adb8f85c2463242 Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Thu, 6 Aug 2026 11:43:45 +0200 Subject: [PATCH 83/92] [core] Added checks to prevent rogue CMD MSG to sneak thru (#3323) * [core] Added checks to prevent rogue CMD MSG to sneak thru * [core] Fixed OOB read in ACK payload parsing. * Added protection against rogue DROPREQ. Added status return for cmd dispatchers. Changed tests for cmd dispatchers to work on a connected socket. Added protection against rogue DROP in receiver buffer * Fixed codespell * Wrong keyword for CUnit in tests --------- Co-authored-by: Mikolaj Malecki --- srtcore/api.h | 1 + srtcore/core.cpp | 156 ++++++++++++++++++++++++---------- srtcore/core.h | 20 ++--- srtcore/crypto.cpp | 11 +-- test/test_bonding.cpp | 4 +- test/test_control_packets.cpp | 115 ++++++++++++++++++------- test/test_crypto.cpp | 8 -- test/test_env.h | 31 ++++++- test/test_fec_rebuilding.cpp | 52 +++++++++--- test/test_main.cpp | 40 +++++++++ 10 files changed, 324 insertions(+), 114 deletions(-) diff --git a/srtcore/api.h b/srtcore/api.h index 9f5e560bc1..96302f118b 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -245,6 +245,7 @@ class CUDTUnited friend class CUDTGroup; friend class CRendezvousQueue; friend class CCryptoControl; + friend class TestMockCUDT; public: CUDTUnited(); diff --git a/srtcore/core.cpp b/srtcore/core.cpp index ece511b3e1..1e98f75a20 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -8686,9 +8686,24 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno) leaveCS(m_StatsLock); } -void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) -{ +bool srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime) +{ + // Valid ACK payloads are either LITE (just an ack seqno) or at least SMALL + // (RCVLASTACK + RTT + RTTVAR + BUFFERLEFT = 16 B). Anything else would OOB-read. + const size_t pktlen = ctrlpkt.getLength(); + const bool isLiteAck = pktlen == size_t(SEND_LITE_ACK); + if (!isLiteAck && pktlen < ACKD_TOTAL_SIZE_SMALL * ACKD_FIELD_SIZE) + { + LOGC(inlog.Warn, log << CONID() << "ACK: EPE: wrong payload size=" << pktlen + << " expected 4 or at least SMALL (" + << (ACKD_TOTAL_SIZE_SMALL * ACKD_FIELD_SIZE) + << ") - rejecting"); + return false; + } + const int32_t* ackdata = (const int32_t*)ctrlpkt.m_pcData; + + // Note: minimum of one 4-byte field is granted before the call. const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK]; // Check the value of ACK in case when it was some rogue peer @@ -8700,10 +8715,9 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ // This check MUST BE DONE before making any operation on this number. LOGC(inlog.Error, log << CONID() << "ACK: IPE/EPE: received invalid ACK value: " << ackdata_seqno << " " << std::hex << ackdata_seqno << " (IGNORED)"); - return; + return false; } - const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK; HLOGC(inlog.Debug, log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck << "]" << (isLiteAck ? "[LITE]" : "[FULL]")); @@ -8722,7 +8736,16 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } - return; + return true; + } + + const size_t acksize = pktlen / ACKD_FIELD_SIZE; // ACTUAL VALUE + + // Check minimum size acceptable. If less, reject it. + if (acksize < ACKD_TOTAL_SIZE_SMALL) + { + LOGC(inlog.Error, log << CONID() << "EPE: ACK msg received with too small size: " << pktlen); + return false; } // Decide to send ACKACK or not @@ -8764,7 +8787,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! - return; + return false; } if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) @@ -8800,7 +8823,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0) { // discard it if it is a repeated ACK - return; + return true; } m_iSndLastFullAck = ackdata_seqno; } @@ -8820,24 +8843,12 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ } #endif - size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING - bool wrongsize = 0 != (acksize % ACKD_FIELD_SIZE); - acksize = acksize / ACKD_FIELD_SIZE; // ACTUAL VALUE - - if (wrongsize) - { - // Issue a log, but don't do anything but skipping the "odd" bytes from the payload. - LOGC(inlog.Warn, - log << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to " - << acksize << " fields"); - } - // Start with checking the base size. if (acksize < ACKD_TOTAL_SIZE_SMALL) { LOGC(inlog.Warn, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!"); // Ack is already interpreted, just skip further parts. - return; + return false; } // This check covers fields up to ACKD_BUFFERLEFT. @@ -8947,9 +8958,11 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ enterCS(m_StatsLock); m_stats.sndr.recvdAck.count(1); leaveCS(m_StatsLock); + + return true; } -void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) +bool srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival) { int32_t ack = 0; @@ -8975,14 +8988,14 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr } #endif - return; + return false; } LOGC(inlog.Error, log << CONID() << "ACK record not found, can't estimate RTT " << "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo << ", RTT (EWMA): " << m_iSRTT << ")"); - return; + return false; } if (rtt <= 0) @@ -8990,7 +9003,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr LOGC(inlog.Error, log << CONID() << "IPE: invalid RTT estimate " << rtt << ", possible time shift. Clock: " << SRT_SYNC_CLOCK_STR); - return; + return false; } // If increasing delay is detected. @@ -9045,12 +9058,13 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr // Update last ACK that has been received by the sender if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) m_iRcvLastAckAck = ack; + return true; } -void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) +bool srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) { const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData); - const size_t losslist_len = ctrlpkt.getLength() / 4; + const size_t losslist_len = ctrlpkt.getLength() / sizeof(int32_t); bool secure = true; @@ -9205,7 +9219,7 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS! - return; + return false; } // the lost packet (retransmission) should be sent out immediately @@ -9214,12 +9228,18 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt) enterCS(m_StatsLock); m_stats.sndr.recvdNak.count(1); leaveCS(m_StatsLock); + + return true; } -void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) +bool srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) { CHandShake req; - req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); + if (-1 == req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength())) + { + LOGC(inlog.Error, log << CONID() << "processCtrlHS: EPE: Handshake has wrong size: " << ctrlpkt.getLength()); + return false; + } HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show()); @@ -9326,21 +9346,51 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) else { HLOGC(inlog.Debug, log << CONID() << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."); + return false; } + return true; } -void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) +bool srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) { // dropdata[0..1] are indexed unconditionally below. if (ctrlpkt.getLength() < 2 * sizeof(int32_t)) { LOGC(inlog.Warn, log << CONID() << "DROPREQ: payload " << ctrlpkt.getLength() << " bytes < " << (2 * sizeof(int32_t)) << " - rejecting"); - return; + return false; } const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData; + // The wire format carries a (lo, hi) seqno range. Reject reversed + // ranges: dropMessage walks a circular buffer from offset(lo) to + // offset(hi)+1 via incPos(); when seqcmp(lo, hi) > 0 the loop wraps + // and clears nearly the entire receive buffer (DoS primitive). The + // analogous LOSSREPORT path already rejects reversed ranges. + + // This is for check only - one packet read will not spoil it + m_RcvBufferLock.lock(); + const int32_t hookseq_begin = m_pRcvBuffer->getStartSeqNo(); + m_RcvBufferLock.unlock(); + + int dist_begin = CSeqNo::seqoff(dropdata[0], hookseq_begin); + int dist_rel = CSeqNo::seqoff(dropdata[0], dropdata[1]); + + if (abs(dist_begin) > CSeqNo::m_iSeqNoTH/2) + { + LOGC(inlog.Warn, log << CONID() << "EPE: rcv DROPREQ low rng %" + << dropdata[0] << " - too distant to receiver buffer %" << hookseq_begin + << " by " << dist_begin << " (exceeds threshold)"); + return false; + } + if (dist_rel < 0 || dist_rel > CSeqNo::m_iSeqNoTH/2) + { + LOGC(inlog.Warn, log << CONID() << "EPE: rcv DROPREQ rng %" + << dropdata[0] << " - %" << dropdata[1] << " - REVERSED RANGE, DISCARDING"); + return false; + } + { CUniqueSync rcvtscc (m_RecvLock, m_RcvTsbPdCond); // With both TLPktDrop and TsbPd enabled, a message always consists only of one packet. @@ -9402,9 +9452,11 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt) HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %" << dropdata[0] << "-" << dropdata[1] << " current %" << m_iRcvCurrSeqNo); } + + return true; } -void srt::CUDT::processCtrlShutdown() +bool srt::CUDT::processCtrlShutdown() { m_bShutdown = true; m_bClosing = true; @@ -9415,9 +9467,10 @@ void srt::CUDT::processCtrlShutdown() // just we know about this state prematurely thanks to this message. updateBrokenConnection(); completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS! + return true; } -void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) +bool srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) { HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) @@ -9445,31 +9498,44 @@ void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt) { updateCC(TEV_CUSTOM, EventVariant(&ctrlpkt)); } + return true; } -void srt::CUDT::processCtrl(const CPacket &ctrlpkt) +bool srt::CUDT::processCtrl(const CPacket &ctrlpkt) { // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; const steady_clock::time_point currtime = steady_clock::now(); m_tsLastRspTime = currtime; + // Extra check for the payload size: + // - must be aligned to int32_t + // - cannot be 0 (msgs with no args use 4-byte zero-filled padding). + size_t pktlen = ctrlpkt.getLength(); + if (!pktlen || pktlen % sizeof(int32_t) != 0) + { + LOGC(inlog.Error, log << CONID() << "EPE: incoming UMSG: " << ctrlpkt.getType() << " INVALID SIZE: " << pktlen + << " (expected > 0 and aligned to " << sizeof(int32_t) << " bytes)"); + return false; + } + HLOGC(inlog.Debug, log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.id()); + bool result = false; switch (ctrlpkt.getType()) { case UMSG_ACK: // 010 - Acknowledgement - processCtrlAck(ctrlpkt, currtime); + result = processCtrlAck(ctrlpkt, currtime); break; case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement - processCtrlAckAck(ctrlpkt, currtime); + result = processCtrlAckAck(ctrlpkt, currtime); break; case UMSG_LOSSREPORT: // 011 - Loss Report - processCtrlLossReport(ctrlpkt); + result = processCtrlLossReport(ctrlpkt); break; case UMSG_CGWARNING: // 100 - Delay Warning @@ -9483,19 +9549,19 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) break; case UMSG_KEEPALIVE: // 001 - Keep-alive - processKeepalive(ctrlpkt, currtime); + result = processKeepalive(ctrlpkt, currtime); break; case UMSG_HANDSHAKE: // 000 - Handshake - processCtrlHS(ctrlpkt); + result = processCtrlHS(ctrlpkt); break; case UMSG_SHUTDOWN: // 101 - Shutdown - processCtrlShutdown(); + result = processCtrlShutdown(); break; case UMSG_DROPREQ: // 111 - Msg drop request - processCtrlDropReq(ctrlpkt); + result = processCtrlDropReq(ctrlpkt); break; case UMSG_PEERERROR: // 1000 - An error has happened to the peer side @@ -9505,16 +9571,18 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt) // if recvfile() fails (e.g., due to disk fail), blocked sendfile/send should return immediately // giving the app a chance to fix the issue m_bPeerHealth = false; + result = true; break; case UMSG_EXT: // 0x7FFF - reserved and user defined messages - processCtrlUserDefined(ctrlpkt); + result = processCtrlUserDefined(ctrlpkt); break; default: break; } + return result; } void srt::CUDT::updateSrtRcvSettings() @@ -12541,7 +12609,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak return true; } -void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) +bool srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival) { // Here can be handled some protocol definition // for extra data sent through keepalive. @@ -12570,6 +12638,8 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp()); if (m_config.bDriftTracer) m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1); + + return true; } namespace srt { diff --git a/srtcore/core.h b/srtcore/core.h index 31cedbd4ff..f2f1dc8bd2 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -1232,35 +1232,35 @@ class CUDT int sendCtrlAck(CPacket& ctrlpkt, int size); void sendLossReport(const std::vector< std::pair >& losslist); - void processCtrl(const CPacket& ctrlpkt); - + bool processCtrl(const CPacket& ctrlpkt); + /// @brief Process incoming control ACK packet. /// @param ctrlpkt incoming ACK packet /// @param currtime current clock time - void processCtrlAck(const CPacket& ctrlpkt, const time_point& currtime); + bool processCtrlAck(const CPacket& ctrlpkt, const time_point& currtime); /// @brief Process incoming control ACKACK packet. /// @param ctrlpkt incoming ACKACK packet /// @param tsArrival time when packet has arrived (used to calculate RTT) - void processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival); + bool processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival); /// @brief Process incoming loss report (NAK) packet. /// @param ctrlpkt incoming NAK packet - void processCtrlLossReport(const CPacket& ctrlpkt); + bool processCtrlLossReport(const CPacket& ctrlpkt); /// @brief Process incoming handshake control packet /// @param ctrlpkt incoming HS packet - void processCtrlHS(const CPacket& ctrlpkt); + bool processCtrlHS(const CPacket& ctrlpkt); /// @brief Process incoming drop request control packet /// @param ctrlpkt incoming drop request packet - void processCtrlDropReq(const CPacket& ctrlpkt); + bool processCtrlDropReq(const CPacket& ctrlpkt); /// @brief Process incoming shutdown control packet - void processCtrlShutdown(); + bool processCtrlShutdown(); /// @brief Process incoming user defined control packet /// @param ctrlpkt incoming user defined packet - void processCtrlUserDefined(const CPacket& ctrlpkt); + bool processCtrlUserDefined(const CPacket& ctrlpkt); /// @brief Update sender's loss list on an incoming acknowledgement. /// @param ackdata_seqno sequence number of a data packet being acknowledged @@ -1334,7 +1334,7 @@ class CUDT static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi); int32_t bake(const sockaddr_any& addr, int32_t previous_cookie = 0, int correction = 0); - void processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival); + bool processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival); SRT_ATTR_REQUIRES(m_RcvBufferLock) diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index cf92175d5c..ccc4714609 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -372,17 +372,16 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) { + uint32_t srtd[SRTDATA_MAXSIZE]; + size_t srtlen = len/sizeof(uint32_t); // Validate the wire-supplied length before using it: // - oversize would overflow the fixed-size stack buffer below; // - non-word-aligned or too-small payloads are malformed by protocol and would // feed uninitialised stack into downstream key-matching logic. - if (len > SRT_CMD_MAXSZ - || len < sizeof(uint32_t) - || (len % sizeof(uint32_t)) != 0) + if (srtlen > SRTDATA_MAXSIZE) { LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: malformed len " << len - << " (must be a non-zero multiple of " << sizeof(uint32_t) - << ", up to " << SRT_CMD_MAXSZ << ") - rejecting"); + << " (must be up to " << SRT_CMD_MAXSZ << ") - rejecting"); return SRT_CMD_NONE; } @@ -390,8 +389,6 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len * But HaiCrypt expect network order message * Re-swap to cancel it. */ - uint32_t srtd[SRTDATA_MAXSIZE]; - size_t srtlen = len/sizeof(uint32_t); HtoNLA(srtd, srtdata, srtlen); int retstatus = -1; diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 8414e43e34..5948eed7c8 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -542,8 +542,8 @@ TEST(Bonding, Options) #ifdef ENABLE_AEAD_API_PREVIEW EXPECT_NE(srt_getsockflag(grp, SRTO_CRYPTOMODE, &kms, &optsize), SRT_ERROR); - EXPECT_EQ(optsize, sizeof kms); - EXPECT_EQ(kms, 1); + EXPECT_EQ(optsize, (int) sizeof kms); + EXPECT_EQ(kms, uint32_t(SRT_KM_S_SECURING)); #endif #endif diff --git a/test/test_control_packets.cpp b/test/test_control_packets.cpp index 98ad378317..c588b8a3b2 100644 --- a/test/test_control_packets.cpp +++ b/test/test_control_packets.cpp @@ -1,3 +1,5 @@ +#include + #include "gtest/gtest.h" #include "test_env.h" @@ -16,7 +18,7 @@ namespace srt { public: CUDT* core; - void processCtrlDropReq(const CPacket& pkt) { core->processCtrlDropReq(pkt); } + bool processCtrl(const CPacket& pkt) { return core->processCtrl(pkt); } void processCtrlLossReport(const CPacket& pkt) { core->processCtrlLossReport(pkt); } int32_t rcvCurrSeqNo() const { return core->m_iRcvCurrSeqNo; } void setRcvCurrSeqNo(int32_t v) { core->m_iRcvCurrSeqNo = v; } @@ -24,24 +26,66 @@ namespace srt { }; } -// processCtrlDropReq must reject DROPREQs whose payload is smaller than two -// seqno words; otherwise dropdata[1] reads past the wire payload. -TEST(ControlPackets, DropReqRejectsShortPayload) +class ControlPackets: public srt::Test { - srt::TestInit srtinit; +public: + SRTSOCKET caller = SRT_INVALID_SOCK; + SRTSOCKET listener = SRT_INVALID_SOCK; + SRTSOCKET accepted = SRT_INVALID_SOCK; + CUDTSocket* pcaller = NULL; + TestMockControlPackets cmock; + + static void swipe(SRTSOCKET& sockid) + { + if (sockid == SRT_INVALID_SOCK) + return; + + EXPECT_NE(srt_close(sockid), SRT_ERROR); + sockid = SRT_INVALID_SOCK; + } + + void setup() override + { + caller = CUDT::uglobal().newSocket(&pcaller); + ASSERT_NE(caller, SRT_INVALID_SOCK); + cmock.core = &pcaller->core(); - CUDTSocket* s1 = NULL; - SRTSOCKET sid1 = CUDT::uglobal().newSocket(&s1); - ASSERT_NE(sid1, SRT_INVALID_SOCK); + ASSERT_NE(listener = srt_create_socket(), SRT_INVALID_SOCK); - TestMockControlPackets m1; - m1.core = &s1->core(); + srt::sockaddr_any sa = srt::CreateAddr("localhost", 5555, AF_INET); + ASSERT_NE(srt_bind(listener, sa.get(), sa.size()), SRT_ERROR); + ASSERT_NE(srt_listen(listener, 1), SRT_ERROR); + + std::thread spawned_connect( [this, &sa] { EXPECT_NE(srt_connect(caller, sa.get(), sa.size()), SRT_ERROR); }); + + accepted = srt_accept(listener, NULL, 0); + spawned_connect.join(); + ASSERT_NE(accepted, SRT_ERROR); + } + + void stop() + { + swipe(caller); + } + + void teardown() override + { + swipe(caller); + swipe(accepted); + swipe(listener); + } +}; +// processCtrlDropReq must reject DROPREQs whose payload is smaller than two +// seqno words; otherwise dropdata[1] reads past the wire payload. +TEST_F(ControlPackets, DropReqRejectsShortPayload) +{ const int32_t sentinel = 100; - m1.setRcvCurrSeqNo(sentinel); + cmock.setRcvCurrSeqNo(sentinel); CPacket pkt; pkt.allocate(1500); + pkt.setControl(UMSG_DROPREQ); // Each of these is shorter than the 8-byte minimum and must be rejected // by the guard at the top of processCtrlDropReq. @@ -49,13 +93,34 @@ TEST(ControlPackets, DropReqRejectsShortPayload) for (size_t i = 0; i < sizeof(short_lens) / sizeof(short_lens[0]); ++i) { pkt.setLength(short_lens[i]); - m1.processCtrlDropReq(pkt); - EXPECT_EQ(m1.rcvCurrSeqNo(), sentinel) + EXPECT_FALSE(cmock.processCtrl(pkt)); + EXPECT_EQ(cmock.rcvCurrSeqNo(), sentinel) << "DROPREQ with payload " << short_lens[i] << " bytes must not be processed"; } +} + +// processCtrlDropReq must reject DROPREQs whose (lo, hi) seqno range is +// reversed. Otherwise CRcvBuffer::dropMessage walks the circular buffer from +// offset(lo) past offset(hi)+1 via incPos() and wipes nearly every entry -- +// a DoS primitive triggerable by a single malicious DROPREQ. +TEST_F(ControlPackets, DropReqRejectsReversedRange) +{ + const int32_t sentinel = 1000; + cmock.setRcvCurrSeqNo(sentinel); + + CPacket pkt; + pkt.allocate(8); + int32_t* data = (int32_t*) pkt.m_pcData; + data[0] = 2000; // lo + data[1] = 1500; // hi (seqcmp(lo, hi) > 0) + pkt.setLength(8); + pkt.setControl(UMSG_DROPREQ); - pkt.deallocate(); - srt_close(sid1); + // With the guard, this returns before touching m_pRcvBuffer (NULL on + // an unconnected socket -- would crash if the guard were missing). + EXPECT_FALSE(cmock.processCtrl(pkt)); + + EXPECT_EQ(cmock.rcvCurrSeqNo(), sentinel); } // processCtrlLossReport must reject a LOSSREPORT whose final cell carries @@ -63,18 +128,8 @@ TEST(ControlPackets, DropReqRejectsShortPayload) // otherwise losslist[i+1] reads past the wire payload (4-byte OOB read of // adjacent heap). The handler should mark the connection broken via the // `secure = false` path. -TEST(ControlPackets, LossReportRejectsTrailingRangeFirst) +TEST_F(ControlPackets, LossReportRejectsTrailingRangeFirst) { - srt::TestInit srtinit; - - CUDTSocket* s = NULL; - SRTSOCKET sid = CUDT::uglobal().newSocket(&s); - ASSERT_NE(sid, SRT_INVALID_SOCK); - - TestMockControlPackets m; - m.core = &s->core(); - ASSERT_FALSE(m.isBroken()) << "fresh socket should not be marked broken"; - // Single 4-byte payload, high bit set => LOSSDATA_SEQNO_RANGE_FIRST. // Without the guard, the handler would dereference losslist[1] (the // missing HI cell), reading 4 bytes past the packet payload. @@ -85,12 +140,10 @@ TEST(ControlPackets, LossReportRejectsTrailingRangeFirst) pkt.setLength(sizeof(int32_t)); pkt.setControl(UMSG_LOSSREPORT); - m.processCtrlLossReport(pkt); + EXPECT_FALSE(cmock.processCtrl(pkt)); - EXPECT_TRUE(m.isBroken()) + EXPECT_TRUE(cmock.isBroken()) << "LOSSREPORT with trailing range-first marker must take the " "secure=false bail path and mark the connection broken"; - - pkt.deallocate(); - srt_close(sid); } + diff --git a/test/test_crypto.cpp b/test/test_crypto.cpp index 47fcad8f23..1d04b19238 100644 --- a/test/test_crypto.cpp +++ b/test/test_crypto.cpp @@ -30,14 +30,6 @@ TEST(CryptoKMRSP, RejectsMalformedLengths) // Oversize: would overflow uint32_t srtd[SRTDATA_MAXSIZE]. EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), SRT_CMD_MAXSZ + sizeof(uint32_t), srtv), srt::SRT_CMD_NONE); - - // Non-word-aligned: silently drops bytes and risks misinterpretation. - EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 7, srtv), srt::SRT_CMD_NONE); - - // Empty / under-a-word: HtoNLA writes nothing and downstream code would read - // uninitialised stack from srtd[]. - EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 0, srtv), srt::SRT_CMD_NONE); - EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 3, srtv), srt::SRT_CMD_NONE); } diff --git a/test/test_env.h b/test/test_env.h index edceee6e65..3f90ccaf5d 100644 --- a/test/test_env.h +++ b/test/test_env.h @@ -7,6 +7,7 @@ #include #include "gtest/gtest.h" +#include "sync.h" namespace srt { @@ -145,8 +146,36 @@ class Test: public testing::Test } }; +class CUDT; +class CPacket; +struct CUnit; +class CUDTSocket; + +class TestMockCUDT +{ +public: + CUDT* core; + + TestMockCUDT() : core(NULL) {} + + bool setSocket(int32_t socket); + + // This is used in TestFEC; leaving with a single forwarder + // to keep the test as is. The class can be as well extended. + bool checkApplyFilterConfig(const std::string& s); + + bool processSrtMsg(const srt::CPacket *ctrlpkt); + int rcvKmState(); + int processData(CUnit* u); + CUDTSocket* locateSocket(int32_t s); + + void processCtrlAck(const CPacket& pkt, const sync::steady_clock::time_point& t); + int flowWindowSize() const; + void setFlowWindowSize(int v); +}; + struct sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_family); -} //namespace +} //namespace srt #endif diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index 3910c959b9..da78aaa62f 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -109,18 +109,6 @@ static std::future spawn_connect(SRTSOCKET s, sockaddr_in& sa, int timeout_ }); } -namespace srt { - class TestMockCUDT - { - public: - CUDT* core; - - bool checkApplyFilterConfig(const string& s) - { - return core->checkApplyFilterConfig(s); - } - }; -} // The expected whole procedure of connection using FEC is // expected to: @@ -946,3 +934,43 @@ TEST_F(TestFECRebuilding, Rebuild) EXPECT_EQ(memcmp(skipped.data(), rebuilt.data(), rebuilt.size()), 0); } + +// processCtrlAck has two OOB-read sites for intermediate payload sizes: +// - ackdata[ACKD_RCVLASTACK] (index 0) is read up front, OOB for 0-3 byte payloads; +// - ackdata[ACKD_BUFFERLEFT] (index 3) is read in the slow path, OOB for 5-15 byte +// payloads (the lite-ACK fast path matches exactly 4 bytes). +// Valid payloads are LITE (4 B) or SMALL+ (>=16 B). The guard at the top of the +// handler rejects everything else. +TEST(TestCUDT, AckRejectsIntermediatePayload) +{ + srt::TestInit srtinit; + + CUDTSocket* s1 = NULL; + SRTSOCKET sid1 = CUDT::uglobal().newSocket(&s1); + + TestMockCUDT m1; + m1.core = &s1->core(); + + const int sentinel = 0x5A5A5A5A; + m1.setFlowWindowSize(sentinel); + + CPacket pkt; + pkt.allocate(1500); + + // Fill the payload with bytes that would be plausible ack-seqnos if interpreted + // as int32 (non-negative), so the ackdata_seqno < 0 early return doesn't mask + // the bug for the 0-3 byte cases. + std::memset(pkt.m_pcData, 0x01, 1500); + + const size_t bad_lens[] = { 0, 1, 3, 5, 8, 12, 15 }; + const sync::steady_clock::time_point now = sync::steady_clock::now(); + for (size_t i = 0; i < sizeof(bad_lens) / sizeof(bad_lens[0]); ++i) + { + pkt.setLength(bad_lens[i]); + m1.processCtrlAck(pkt, now); + EXPECT_EQ(m1.flowWindowSize(), sentinel) + << "ACK with payload " << bad_lens[i] << " bytes must not corrupt m_iFlowWindowSize"; + } + + srt_close(sid1); +} diff --git a/test/test_main.cpp b/test/test_main.cpp index e2243a3065..701a8670c3 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -8,6 +8,7 @@ #include "srt.h" #include "netinet_any.h" +#include "api.h" using namespace std; @@ -212,4 +213,43 @@ void UniqueSocket::close() } } +bool TestMockCUDT::checkApplyFilterConfig(const string& s) +{ + return core->checkApplyFilterConfig(s); +} + +bool TestMockCUDT::processSrtMsg(const srt::CPacket *ctrlpkt) +{ + return core->processSrtMsg(ctrlpkt); +} + +int TestMockCUDT::rcvKmState() +{ + return core->m_pCryptoControl->m_RcvKmState; +} + +int TestMockCUDT::processData(CUnit* u) +{ + return core->processData(u); +} + +CUDTSocket* TestMockCUDT::locateSocket(int32_t s) +{ + SRTSOCKET sock (s); + return CUDT::uglobal().locateSocket(sock); +} + +bool TestMockCUDT::setSocket(int32_t sock) +{ + CUDTSocket* s = locateSocket(sock); + if (!s) + return false; + core = &s->core(); + return true; +} + +void TestMockCUDT::processCtrlAck(const CPacket& pkt, const sync::steady_clock::time_point& t) { core->processCtrlAck(pkt, t); } +int TestMockCUDT::flowWindowSize() const { return core->m_iFlowWindowSize; } +void TestMockCUDT::setFlowWindowSize(int v) { core->m_iFlowWindowSize = v; } + } From 6f817b637824f9c4d563d9f2747bfbee010127ad Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 26 Aug 2026 09:41:42 +0200 Subject: [PATCH 84/92] [core] Security improvements and enhancements (#3359) * Fixed vulnerabilities reported as SRTX-61 * Next portion of fixes from SRTX-62 * Fixed #11 for SRTX-62 * Added MD5 check to the dependency installer scripts * Applied safety fixes for Windows installer scripts * Post-review fixes for vulnerability issues * Post-review fixes, take 3 * Remaining parts of Take 3 * Fixes, take 4. Fixed ASSERT usage in FEC tests * Take 5, possibly final * Fixed build break with --disable-encryption * Fixed codespell --------- Co-authored-by: Mikolaj Malecki --- CMakeLists.txt | 7 +- apps/srt-file-transmit.cpp | 59 +++- scripts/codecov/update.sh | 43 ++- .../win-installer/ATTIC/old-install-nsis.ps1 | 128 ++++++++ scripts/win-installer/build-win-installer.ps1 | 3 +- scripts/win-installer/install-libsrt.ps1 | 3 +- scripts/win-installer/install-nsis.ps1 | 285 ++++++++++-------- scripts/win-installer/install-openssl.ps1 | 98 ++++-- srtcore/api.cpp | 93 ++++-- srtcore/buffer_snd.cpp | 7 + srtcore/common.h | 6 + srtcore/congctl.cpp | 6 +- srtcore/core.cpp | 179 ++++++----- srtcore/core.h | 19 +- srtcore/crypto.cpp | 120 ++++---- srtcore/crypto.h | 2 +- srtcore/fec.cpp | 176 ++++++----- srtcore/fec.h | 8 +- srtcore/group.cpp | 31 +- srtcore/group_backup.cpp | 24 ++ srtcore/group_backup.h | 4 + srtcore/handshake.cpp | 5 +- srtcore/list.cpp | 8 +- srtcore/list.h | 9 +- srtcore/packet.h | 7 +- srtcore/queue.cpp | 54 ++-- srtcore/srt.h | 7 +- test/test_bonding.cpp | 7 + test/test_crypto.cpp | 19 +- test/test_fec_rebuilding.cpp | 146 ++++----- 30 files changed, 1056 insertions(+), 507 deletions(-) create mode 100644 scripts/win-installer/ATTIC/old-install-nsis.ps1 mode change 100644 => 100755 scripts/win-installer/build-win-installer.ps1 mode change 100644 => 100755 scripts/win-installer/install-libsrt.ps1 mode change 100644 => 100755 scripts/win-installer/install-nsis.ps1 mode change 100644 => 100755 scripts/win-installer/install-openssl.ps1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 18895c0401..e10715a258 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ else() endif() option(ENABLE_APPS "Should the Support Applications be Built?" ON) option(ENABLE_BONDING "Should the bonding functionality be enabled?" OFF) +option(ENABLE_EXAMPLES "Compile also examples (NOTE: MAY BE UNSAFE!)" OFF) option(ENABLE_TESTING "Should the Developer Test Applications be Built?" OFF) option(ENABLE_PROFILE "Should instrument the code for profiling. Ignored for non-GNU compiler." $ENV{HAI_BUILD_PROFILE}) option(ENABLE_LOGGING "Should logging be enabled" ON) @@ -914,16 +915,14 @@ if (ENABLE_CODE_COVERAGE) message(FATAL_ERROR "ENABLE_CODE_COVERAGE: option is not supported on this platform") endif() - block() if (ENABLE_DEBUG EQUAL 2) elseif (NOT ENABLE_DEBUG) else() - set (is_debug 1) + set (ENABLE_STRICTLY_DEBUG_MODE 1) endif() - if (NOT is_debug) + if (NOT ENABLE_STRICTLY_DEBUG_MODE) message(FATAL_ERROR "ENABLE_CODE_COVERAGE: requires ENABLE_DEBUG or Debug build type") endif() - endblock() add_definitions(--coverage) link_libraries(--coverage) diff --git a/apps/srt-file-transmit.cpp b/apps/srt-file-transmit.cpp index 327ad68097..aa3696dc5f 100644 --- a/apps/srt-file-transmit.cpp +++ b/apps/srt-file-transmit.cpp @@ -465,6 +465,12 @@ bool DoDownload(UriParser& us, string directory, string filename, bool connected = false; int pollid = -1; string id; + // This will be set to TRUE if the ID has been obtained from the socket, + // while as caller socket it was set to this option beforehand. If it remains + // false, it means that it was the ID passed from the caller and extracted + // from the accepted socket - and as such the name can't be trusted, so + // if a file with this name exists, it will be not overwritten. + bool id_is_local = false; ofstream ofile; SRT_SOCKSTATUS status; SRTSOCKET efd; @@ -548,6 +554,7 @@ bool DoDownload(UriParser& us, string directory, string filename, cerr << "Source connected (caller), id [" << id << "]" << endl; connected = true; + id_is_local = true; } } break; @@ -579,10 +586,58 @@ bool DoDownload(UriParser& us, string directory, string filename, if (!ofile.is_open()) { - const char * fn = id.empty() ? filename.c_str() : id.c_str(); + std::string fn; + bool overwrite = false; + if (id.empty()) + { + fn = filename; + overwrite = true; + } + else + { + fn = id; + if (id_is_local) + overwrite = true; + } directory.append("/"); directory.append(fn); - ofile.open(directory.c_str(), ios::out | ios::trunc | ios::binary); + + std::ios::openmode flags = ios::out | ios::binary; + if (overwrite) + flags = flags | ios::trunc; + else + { + struct stat state; + int st = stat(directory.c_str(), &state); + if (st == 0) // File can be obtained + { + cerr << "Error: File exists: " << directory << endl; + cerr << "Error: As the name is remote-provided, overwriting denied for security reasons." << endl; + goto exit; + } + + // Additionally check if the path is PWD-based; + // reject any foreign-defined paths that are not + // effectively local. + + // NOTE: The file is always copied to the directory + // specified locally, with the original filename. Therefore + // it is not allowed that the file contain a path. + + static const size_t notfound = std::string::npos; + if ( fn.find('/') != notfound + || fn.find('\\') != notfound + || fn.find(':') != notfound + || fn.find("..") != notfound) // Any parent-referring + { + cerr << "Error: the foreign-specified path reaches outside PWD - REJECTED\n"; + cerr << "Path: " << directory << endl; + cerr << "NOTE: remote path is only allowed to point inside the current directory\n"; + goto exit; + } + } + + ofile.open(directory, flags); if (!ofile.is_open()) { diff --git a/scripts/codecov/update.sh b/scripts/codecov/update.sh index 1aadf9277c..2d2b8b62d0 100755 --- a/scripts/codecov/update.sh +++ b/scripts/codecov/update.sh @@ -1,5 +1,46 @@ #!/bin/bash HERE=`dirname $0` cd $HERE -curl -L -o codecov https://cli.codecov.io/latest/linux/codecov + +VERSION=hardcoded +if [[ -n $1 ]]; then + VERSION=$1 +fi + +if [[ $VERSION == latest ]]; then + BASEURL=https://cli.codecov.io/latest/linux/codecov + SHA= +else + BASEURL=https://cli.codecov.io/v11.3.1/linux/codecov + SHA="ca1d64196d2d34771084afe76ea657d581bf628e31d993ff8e52ea09cc88a56d codecov" + echo 'USING HARDCODED VERSION: 11.3.1. Use "update.sh latest" to get the latest version' + echo 'NOTE: Using the latest version is unsafe as SHA is also downloaded over the network' +fi + +echo "DOWNLOADING: $BASEURL" +rm -f codecov || { echo "Can't delete 'codecov'; please delete manually" ; exit 1; } + +curl -L -o codecov $BASEURL +FILESHA=$(sha256sum codecov) + +if [[ -z $SHA ]]; then + echo "DOWNLOADING HASH: ${BASEURL}.SHA256SUM" + curl -L -o codecov.SHA256SUM ${BASEURL}.SHA256SUM + SHA=$(cat codecov.SHA256SUM) + echo "Downloaded HASH: $SHA" +else + echo "Hardcoded HASH: $SHA" +fi + +if [[ "$FILESHA" == "$SHA" ]]; then + echo "Checksum SHA256 matches." +else + echo "ERROR: WRONG SHA256 CHECKSUM:" + echo "EXPECTED: $SHA" + echo "RECEIVED: $FILESHA" + rm codecov + echo "Corrupt file deleted" + exit 1 +fi + chmod +x codecov diff --git a/scripts/win-installer/ATTIC/old-install-nsis.ps1 b/scripts/win-installer/ATTIC/old-install-nsis.ps1 new file mode 100644 index 0000000000..0bf58afbc1 --- /dev/null +++ b/scripts/win-installer/ATTIC/old-install-nsis.ps1 @@ -0,0 +1,128 @@ +#----------------------------------------------------------------------------- +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021, Thierry Lelegard +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#----------------------------------------------------------------------------- + +<# + .DESCRIPTION + + Download, expand and install NSIS, the NullSoft Installer Scripting. + + IMPORTANT NOTE: This is the old version, left for historical reasons. Usage + of this script is discouraged because this scripts doesn't check the MD5 + checksum on the downloaded file and therefore is considered unsafe. + + .PARAMETER ForceDownload + + Force a download even if NSIS is already downloaded. + + .PARAMETER NoInstall + + Do not install the NSIS package. By default, NSIS is installed. + + .PARAMETER NoPause + + Do not wait for the user to press at end of execution. By default, + execute a "pause" instruction at the end of execution, which is useful + when the script was run from Windows Explorer. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [switch]$ForceDownload = $false, + [switch]$NoInstall = $false, + [switch]$NoPause = $false +) + +Write-Output "NSIS download and installation procedure" +$NSISPage = "https://nsis.sourceforge.io/Download" +$FallbackURL = "http://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download" + +# A function to exit this script. +function Exit-Script([string]$Message = "") +{ + $Code = 0 + if ($Message -ne "") { + Write-Output "ERROR: $Message" + $Code = 1 + } + if (-not $NoPause) { + pause + } + exit $Code +} + +# Local file names. +$RootDir = $PSScriptRoot +$TmpDir = "$RootDir\tmp" + +# Create the directory for external products when necessary. +[void] (New-Item -Path $TmpDir -ItemType Directory -Force) + +# Without this, Invoke-WebRequest is awfully slow. +$ProgressPreference = 'SilentlyContinue' + +# Get the HTML page for NSIS downloads. +$status = 0 +$message = "" +$Ref = $null +try { + $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $NSISPage + $status = [int] [Math]::Floor($response.StatusCode / 100) +} +catch { + $message = $_.Exception.Message +} + +if ($status -ne 1 -and $status -ne 2) { + # Error fetch NSIS download page. + if ($message -eq "" -and (Test-Path variable:response)) { + Write-Output "Status code $($response.StatusCode), $($response.StatusDescription)" + } + else { + Write-Output "#### Error accessing ${NSISPage}: $message" + } +} +else { + # Parse HTML page to locate the latest installer. + $Ref = $response.Links.href | Where-Object { $_ -like "*/nsis-*-setup.exe?download" } | Select-Object -First 1 +} + +if (-not $Ref) { + # Could not find a reference to NSIS installer. + $Url = [System.Uri]$FallbackURL +} +else { + # Build the absolute URL's from base URL (the download page) and href links. + $Url = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$NSISPage, $Ref) +} + +Write-Output "DOWNLOADING: $Url" + +$InstallerName = (Split-Path -Leaf $Url.LocalPath) +$InstallerPath = "$TmpDir\$InstallerName" + +# Download installer +if (-not $ForceDownload -and (Test-Path $InstallerPath)) { + Write-Output "$InstallerName already downloaded, use -ForceDownload to download again" +} +else { + Write-Output "Downloading $Url ..." + Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $InstallerPath + if (-not (Test-Path $InstallerPath)) { + Exit-Script "$Url download failed" + } +} + +# Install NSIS +if (-not $NoInstall) { + Write-Output "Installing $InstallerName" + Start-Process -FilePath $InstallerPath -ArgumentList @("/S") -Wait +} + +Exit-Script diff --git a/scripts/win-installer/build-win-installer.ps1 b/scripts/win-installer/build-win-installer.ps1 old mode 100644 new mode 100755 index c788e01abf..4778426ebb --- a/scripts/win-installer/build-win-installer.ps1 +++ b/scripts/win-installer/build-win-installer.ps1 @@ -1,4 +1,5 @@ -#----------------------------------------------------------------------------- +#!/usr/bin/env PowerShell +#----------------------------------------------------------------------------- # # SRT - Secure, Reliable, Transport # Copyright (c) 2021, Thierry Lelegard diff --git a/scripts/win-installer/install-libsrt.ps1 b/scripts/win-installer/install-libsrt.ps1 old mode 100644 new mode 100755 index ae1b8133e6..4aacb085c9 --- a/scripts/win-installer/install-libsrt.ps1 +++ b/scripts/win-installer/install-libsrt.ps1 @@ -1,4 +1,5 @@ -# SRT library download and install for Windows. +#!/usr/bin/env PowerShell +# SRT library download and install for Windows. # Copyright (c) 2021, Thierry Lelegard # All rights reserved. diff --git a/scripts/win-installer/install-nsis.ps1 b/scripts/win-installer/install-nsis.ps1 old mode 100644 new mode 100755 index 14879b5217..ea48d14ae3 --- a/scripts/win-installer/install-nsis.ps1 +++ b/scripts/win-installer/install-nsis.ps1 @@ -1,122 +1,163 @@ -#----------------------------------------------------------------------------- -# -# SRT - Secure, Reliable, Transport -# Copyright (c) 2021, Thierry Lelegard -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. -# -#----------------------------------------------------------------------------- - -<# - .SYNOPSIS - - Download, expand and install NSIS, the NullSoft Installer Scripting. - - .PARAMETER ForceDownload - - Force a download even if NSIS is already downloaded. - - .PARAMETER NoInstall - - Do not install the NSIS package. By default, NSIS is installed. - - .PARAMETER NoPause - - Do not wait for the user to press at end of execution. By default, - execute a "pause" instruction at the end of execution, which is useful - when the script was run from Windows Explorer. -#> -[CmdletBinding(SupportsShouldProcess=$true)] -param( - [switch]$ForceDownload = $false, - [switch]$NoInstall = $false, - [switch]$NoPause = $false -) - -Write-Output "NSIS download and installation procedure" -$NSISPage = "https://nsis.sourceforge.io/Download" -$FallbackURL = "http://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download" - -# A function to exit this script. -function Exit-Script([string]$Message = "") -{ - $Code = 0 - if ($Message -ne "") { - Write-Output "ERROR: $Message" - $Code = 1 - } - if (-not $NoPause) { - pause - } - exit $Code -} - -# Local file names. -$RootDir = $PSScriptRoot -$TmpDir = "$RootDir\tmp" - -# Create the directory for external products when necessary. -[void] (New-Item -Path $TmpDir -ItemType Directory -Force) - -# Without this, Invoke-WebRequest is awfully slow. -$ProgressPreference = 'SilentlyContinue' - -# Get the HTML page for NSIS downloads. -$status = 0 -$message = "" -$Ref = $null -try { - $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $NSISPage - $status = [int] [Math]::Floor($response.StatusCode / 100) -} -catch { - $message = $_.Exception.Message -} - -if ($status -ne 1 -and $status -ne 2) { - # Error fetch NSIS download page. - if ($message -eq "" -and (Test-Path variable:response)) { - Write-Output "Status code $($response.StatusCode), $($response.StatusDescription)" - } - else { - Write-Output "#### Error accessing ${NSISPage}: $message" - } -} -else { - # Parse HTML page to locate the latest installer. - $Ref = $response.Links.href | Where-Object { $_ -like "*/nsis-*-setup.exe?download" } | Select-Object -First 1 -} - -if (-not $Ref) { - # Could not find a reference to NSIS installer. - $Url = [System.Uri]$FallbackURL -} -else { - # Build the absolute URL's from base URL (the download page) and href links. - $Url = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$NSISPage, $Ref) -} - -$InstallerName = (Split-Path -Leaf $Url.LocalPath) -$InstallerPath = "$TmpDir\$InstallerName" - -# Download installer -if (-not $ForceDownload -and (Test-Path $InstallerPath)) { - Write-Output "$InstallerName already downloaded, use -ForceDownload to download again" -} -else { - Write-Output "Downloading $Url ..." - Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $InstallerPath - if (-not (Test-Path $InstallerPath)) { - Exit-Script "$Url download failed" - } -} - -# Install NSIS -if (-not $NoInstall) { - Write-Output "Installing $InstallerName" - Start-Process -FilePath $InstallerPath -ArgumentList @("/S") -Wait -} - -Exit-Script +#!/usr/bin/env PowerShell +#----------------------------------------------------------------------------- +# +# SRT - Secure, Reliable, Transport +# Copyright (c) 2021, Thierry Lelegard +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +#----------------------------------------------------------------------------- + +<# + .DESCRIPTION + + Download, expand and install NSIS, the NullSoft Installer Scripting. + + .PARAMETER ForceDownload + + Force a download even if NSIS is already downloaded. By default the + installer file is not redownloaded, if already found, although the + MD5 sum will still be checked. + + .PARAMETER NoInstall + + Do not install the NSIS package. By default, NSIS is installed. + + .PARAMETER NoPause + + Do not wait for the user to press at end of execution. By default, + execute a "pause" instruction at the end of execution, which is useful + when the script was run from Windows Explorer. +#> +[CmdletBinding(SupportsShouldProcess=$true)] +param( + [switch]$ForceDownload = $false, + [switch]$NoInstall = $false, + [switch]$NoPause = $false, + [switch]$Latest = $false +) + +# A function to exit this script. +function Exit-Script([string]$Message = "") +{ + $Code = 0 + if ($Message -ne "") { + Write-Output "ERROR: $Message" + $Code = 1 + } + if (-not $NoPause) { + pause + } + exit $Code +} + +# Local file names. +$RootDir = $PSScriptRoot +$TmpDir = "$RootDir\tmp" + +# Create the directory for external products when necessary. +[void] (New-Item -Path $TmpDir -ItemType Directory -Force) + +# Without this, Invoke-WebRequest is awfully slow. +$ProgressPreference = 'SilentlyContinue' + +# 1. Define Project URLs +$ProjectUrl = "https://sourceforge.net/projects/nsis/files/NSIS%203/" +$UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" +$DownloadHead = "https://prdownloads.sourceforge.net/nsis/" + +# Latest download, on-demand only. Default is a hardcoded version. +if ($Latest) { + # 2. Find the Latest Version Folder + Write-Host "Checking for the latest NSIS version..." + $ProjectPage = Invoke-WebRequest -Uri $ProjectUrl -UserAgent $UserAgent -UseBasicParsing + # Matches version folders like "3.10", "3.12", etc. + $Versions = [regex]::matches($ProjectPage.Content, 'title="([\d\.]+)"') | + ForEach-Object { $_.Groups[1].Value } | + Sort-Object {[version]$_} -Descending + $LatestVer = $Versions[0] + Write-Host "Latest version found: $LatestVer" + + # 3. Build Folder and File Target URLs + $FolderUrl = "${ProjectUrl}${LatestVer}/" + $FolderPage = Invoke-WebRequest -Uri $FolderUrl -UserAgent $UserAgent -UseBasicParsing + + # Find the exact .exe installer filename (e.g., nsis-3.12-setup.exe) + $FileMatch = [regex]::match($FolderPage.Content, "nsis-${LatestVer}-setup\.exe") + if (-not $FileMatch.Success) { + throw "Could not find the setup.exe file for version $LatestVer" + } + $FileName = $FileMatch.Value + + # 4. Extract the Official MD5 Checksum + Write-Host "Extracting official MD5 checksum..." + # SourceForge stores file metadata in a JSON-like 'data-files' attribute or specific table rows + # This regex extracts the MD5 hash associated directly with the target filename + #$HashPattern = 'tr[^>]*?data-name="' + [regex]::Escape($FileName) + '"[^>]*?md5">([^<]+)' + #$MD5Match = [regex]::match($FolderPage.Content, $HashPattern) + + $filedata_inpage = [regex]::match($FolderPage.Content, "net.sf.files = ([^;]+);") + if (-not $filedata_inpage.Success) { + Exit-Script "net.sf.files not found" + } + + $filedata_json = $filedata_inpage.Groups[1].Value.Trim().ToLower() + + $filedata = ConvertFrom-Json $filedata_json + + $ExpectedMD5 = $filedata.$FileName.md5 + + if ($ExpectedMD5 -eq $null) { + Exit-Script "MD5 not found for $FileName" + } + + Write-Host "MD5: $ExpectedMD5" +} else { + Write-Host "USING PREDEFINED VERSION with hardcoded MD5: 3.12" + Write-Host "Use -Latest to force latest version; note that this can be prone to MITM attacks." + + $ExpectedMD5 = "d5d54c2a96c1bcb25764adc9f9ff97f2" + $FileName = "nsis-3.12-setup.exe" +} + +# 5. Download the File +$DownloadUrl = "$DownloadHead$FileName" + "?download" +$OutputPath = "$TmpDir\$FileName" + +Write-Host "Installer path: $OutputPath" + +if (-not $ForceDownload -and (Test-Path $OutputPath)) { + Write-Host "Already downloaded, use -ForceDownload to download anyway." +} else { + Write-Host "Downloading from: $DownloadUrl" + Invoke-WebRequest -Uri $DownloadUrl -UserAgent Download -UseBasicParsing -OutFile $OutputPath +} + +# 6. Verify the MD5 Checksum +Write-Host "Verifying checksum..." +$LocalMD5 = (Get-FileHash -Path $OutputPath -Algorithm MD5).Hash.ToLower() + +if ($LocalMD5 -eq $ExpectedMD5) { + Write-Host "Checksum OK." -ForegroundColor Green +} else { + Write-Error "ERROR: Checksum mismatch!" + Write-Host "Expected: $ExpectedMD5" -ForegroundColor Red + Write-Host "Got: $LocalMD5" -ForegroundColor Red + Remove-Item -Path $OutputPath -Force + Write-Host "Corrupted file removed." + Exit-Script "Installation not possible" +} + +if (-not $NoInstall) { + Write-Host "Installing $FileName" + Start-Process -FilePath $OutputPath -ArgumentList @("/S") -Wait +} else { + Write-Host "Installation not requested." +} + +Exit-Script + + + diff --git a/scripts/win-installer/install-openssl.ps1 b/scripts/win-installer/install-openssl.ps1 old mode 100644 new mode 100755 index c9ec580683..d0a95fb94b --- a/scripts/win-installer/install-openssl.ps1 +++ b/scripts/win-installer/install-openssl.ps1 @@ -1,4 +1,5 @@ -#----------------------------------------------------------------------------- +#!/usr/bin/env PowerShell +#----------------------------------------------------------------------------- # # SRT - Secure, Reliable, Transport # Copyright (c) 2021-2024, Thierry Lelegard @@ -32,7 +33,8 @@ param( [switch]$ForceDownload = $false, [switch]$NoInstall = $false, - [switch]$NoPause = $false + [switch]$NoPause = $false, + [switch]$Latest = $false ) Write-Output "OpenSSL download and installation procedure" @@ -64,36 +66,55 @@ $TmpDir = "$RootDir\tmp" # Without this, Invoke-WebRequest is awfully slow. $ProgressPreference = 'SilentlyContinue' -# Get the JSON configuration file for OpenSSL downloads. -$status = 0 -$message = "" -try { - $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $PackageList - $status = [int] [Math]::Floor($response.StatusCode / 100) -} -catch { - $message = $_.Exception.Message -} -if ($status -ne 1 -and $status -ne 2) { - if ($message -eq "" -and (Test-Path variable:response)) { - Exit-Script "Status code $($response.StatusCode), $($response.StatusDescription)" - } - else { - Exit-Script "#### Error accessing ${PackageList}: $message" - } -} -$config = ConvertFrom-Json $Response.Content - -# Find the URL of the latest "universal" installer in the JSON config file. -$Url = $config.files | Get-Member | ForEach-Object { - $name = $_.name - $info = $config.files.$($_.name) - if (-not $info.light -and $info.installer -like "exe" -and $info.arch -like "universal") { - $info.url - } -} | Select-Object -Last 1 -if (-not $Url) { - Exit-Script "#### No universal installer found" +if ($Latest) { + + # Get the JSON configuration file for OpenSSL downloads. + $status = 0 + $message = "" + try { + Write-Output "Getting file index: $PackageList" + $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $PackageList + $status = [int] [Math]::Floor($response.StatusCode / 100) + } + catch { + $message = $_.Exception.Message + } + if ($status -ne 1 -and $status -ne 2) { + if ($message -eq "" -and (Test-Path variable:response)) { + Exit-Script "Status code $($response.StatusCode), $($response.StatusDescription)" + } + else { + Exit-Script "#### Error accessing ${PackageList}: $message" + } + } + $config = ConvertFrom-Json $Response.Content + + Write-Output "Getting installer file with .light and .installer =~ 'exe' and .arch =~ 'Universal'" + + # Find the URL of the latest "universal" installer in the JSON config file. + #$Url = + $have = 0 + $config.files | Get-Member | ForEach-Object { + $name = $_.name + $info = $config.files.$($_.name) + if (-not $info.light -and $info.installer -like "exe" -and $info.arch -like "universal") { + $found_info = $info + $have = 1 + } + } # | Select-Object -Last 1 + if (-not $have) { + Exit-Script "#### No universal installer found" + } + + $Url = $found_info.url + $xsum = $found_info.md5 +} else { + + Write-Host "USING PREDEFINED VERSION with hardcoded MD5: 4.0.1" + Write-Host "Use -Latest to force latest version; note that this can be prone to MITM attacks." + + $Url = "https://slproweb.com/download/WinUniversalOpenSSL-4_0_1.exe" + $xsum = "34051060e0e9f48e63cfc1f3356191f6" } $ExeName = (Split-Path -Leaf $Url) @@ -111,9 +132,20 @@ if (-not (Test-Path $ExePath)) { Exit-Script "$Url download failed" } +Write-Output "Will check tmp\$ExeName for MD5: $xsum ..." + +$filehash = get-filehash $ExePath -algorithm md5 + +if ($filehash.Hash -ne $xsum) { + Exit-Script "$ExePath MD5: $filehash - NOT MATCHING" +} + if (-not $NoInstall) { - Write-Output "Installing $ExeName" + Write-Output "CHECKSUM MATCHES. Installing $ExeName" Start-Process -FilePath $ExePath -ArgumentList @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/ALLUSERS") -Wait +} else { + Write-Output "Installation not requested." } + Exit-Script diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 7b57095717..c01eaa28c1 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -2834,36 +2834,66 @@ void srt::CUDTUnited::checkBrokenSockets() vector tbc; vector tbr; + bool forced_closing = m_bClosing; + for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i) { CUDTSocket* s = i->second; if (!s->core().m_bBroken) - continue; + { + if (!forced_closing) + { + continue; + } + else + { + // Set forcefully, we are in cleanup and close everything + LOGC(smlog.Warn, log << "CLEANUP: Forcefully breaking socket @" << s->m_SocketID); + s->core().m_bBroken = true; + } + } if (s->m_Status == SRTS_LISTENING) { - const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp.load(); - // A listening socket should wait an extra 3 seconds - // in case a client is connecting. - if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS)) - continue; + if (!forced_closing) + { + const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp.load(); + // A listening socket should wait an extra 3 seconds + // in case a client is connecting. + if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS)) + continue; + } } else { CUDT& u = s->core(); - enterCS(u.m_RcvBufferLock); - bool has_avail_packets = u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets(); - leaveCS(u.m_RcvBufferLock); + // For decent closing, just keep it as long as it still + // has data in the buffer. + if (!forced_closing) + { + enterCS(u.m_RcvBufferLock); + bool has_avail_packets = u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets(); + leaveCS(u.m_RcvBufferLock); - if (has_avail_packets) + if (has_avail_packets) + { + const int bc = u.m_iBrokenCounter.load(); + if (bc > 0) + { + // if there is still data in the receiver buffer, wait longer + s->core().m_iBrokenCounter.store(bc - 1); + continue; + } + } + } + else { - const int bc = u.m_iBrokenCounter.load(); - if (bc > 0) + // Forced closing: any data still in the buffer - delete them. + ScopedLock cgb (u.m_RcvBufferLock); + if (u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets()) { - // if there is still data in the receiver buffer, wait longer - s->core().m_iBrokenCounter.store(bc - 1); - continue; + u.m_pRcvBuffer->dropAll(); } } } @@ -2890,7 +2920,8 @@ void srt::CUDTUnited::checkBrokenSockets() { ls = m_ClosedSockets.find(s->m_ListenSocket); if (ls == m_ClosedSockets.end()) - continue; + continue; // END LOOP AS NOT FOUND + // OTHERWISE PROCEED with erasing as queued } enterCS(ls->second->m_AcceptLock); @@ -2911,6 +2942,9 @@ void srt::CUDTUnited::checkBrokenSockets() // other conditions applying on the socket that prevent it from being deleted. if (ps->isStillBusy()) { + // NOTE: you can't use forced_closing to prevent it because isStillBusy + // means that some facility has acquired it and is going to use it for + // operations; forced deletion may lead to UB/crash. HLOGC(smlog.Debug, log << "checkBrokenSockets: @" << ps->m_SocketID << " is still busy, SKIPPING THIS CYCLE."); continue; } @@ -3437,7 +3471,11 @@ 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); + + // NOTE: Receiver Queue packet size must be of the maximum possible because you never + // know what kind of packet will come over the network, while this must accept any kind + // of packet. + m.m_pRcvQueue->init(128, s->core().controlPayloadSize(reqaddr.family()), 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. @@ -3558,11 +3596,30 @@ void* srt::CUDTUnited::garbageCollect(void* p) UniqueLock gclock(self->m_GCStopLock); - while (!self->m_bClosing) + for (;;) { INCREMENT_THREAD_ITERATIONS(); self->checkBrokenSockets(); + if (self->m_bClosing) + { + // If GC is requested to close, it means the global cleanup + // was requested. But before exiting make sure all sockets + // and multiplexers are closed. + + { + SharedLock glock(self->m_GlobControlLock); + if (self->m_Sockets.empty() && self->m_ClosedSockets.empty()) + break; + + HLOGC(smlog.Debug, log << "GC: REQUESTED CLOSE, DELAYING EXIT - still " + << self->m_Sockets.size() << " running and " + << self->m_ClosedSockets.size() << " closed sockets"); + } + self->m_GCStopCond.wait_for(gclock, milliseconds_from(200)); + continue; + } + HLOGC(inlog.Debug, log << "GC: sleep 1 s"); self->m_GCStopCond.wait_for(gclock, seconds_from(1)); } diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp index 2db82a686b..7b8c44f999 100644 --- a/srtcore/buffer_snd.cpp +++ b/srtcore/buffer_snd.cpp @@ -550,6 +550,13 @@ void CSndBuffer::ackData(int offset) { ScopedLock bufferguard(m_BufLock); + if (offset > m_iCount) + { + LOGC(bslog.Warn, log << "ackData: offset=" << offset << " > count=" << m_iCount + << " - adjusting"); + offset = m_iCount; + } + bool move = false; for (int i = 0; i < offset; ++i) { diff --git a/srtcore/common.h b/srtcore/common.h index 5a53c05e60..3d9bf65f19 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -95,6 +95,12 @@ modified by #define SRT_STATIC_ASSERT(cond, msg) #endif +#if HAVE_FULL_CXX11 +#define FUNID() __func__ +#else +#define FUNID() __FUNCTION__ +#endif + #include namespace srt_logging diff --git a/srtcore/congctl.cpp b/srtcore/congctl.cpp index 9bc43db8b7..8de14d8931 100644 --- a/srtcore/congctl.cpp +++ b/srtcore/congctl.cpp @@ -79,10 +79,10 @@ class LiveCC: public SrtCongestionControlBase m_llSndMaxBW = BW_INFINITE; // 1 Gbbps in Bytes/sec BW_INFINITE m_zMaxPayloadSize = parent->OPT_PayloadSize(); if (m_zMaxPayloadSize == 0) - m_zMaxPayloadSize = parent->maxPayloadSize(); + m_zMaxPayloadSize = parent->maxDataPayloadSize(); m_zSndAvgPayloadSize = m_zMaxPayloadSize; - m_zHeaderSize = parent->m_config.iMSS - parent->maxPayloadSize(); + m_zHeaderSize = parent->m_config.iMSS - parent->maxDataPayloadSize(); m_iMinNakInterval_us = 20000; //Minimum NAK Report Period (usec) m_iNakReportAccel = 2; //Default NAK Report Period (RTT) accelerator (send periodic NAK every RTT/2) @@ -319,7 +319,7 @@ class FileCC : public SrtCongestionControlBase /// and request ACK to be sent immediately. bool needsQuickACK(const CPacket& pkt) ATR_OVERRIDE { - if (pkt.getLength() < m_parent->maxPayloadSize()) + if (pkt.getLength() < m_parent->maxDataPayloadSize()) { // This is not a regular fixed size packet... // an irregular sized packet usually indicates the end of a message, so send an ACK immediately diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 1e98f75a20..9379da5464 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -1042,17 +1042,17 @@ string srt::CUDT::getstreamid(SRTSOCKET u) void srt::CUDT::clearData() { const size_t full_hdr_size = CPacket::UDP_HDR_SIZE - CPacket::HDR_SIZE; - m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; - HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + m_iMaxDataPayloadSize = m_config.iMSS - full_hdr_size; + HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxDataPayloadSize); - m_SndTimeWindow.initialize(full_hdr_size, m_iMaxSRTPayloadSize); - m_RcvTimeWindow.initialize(full_hdr_size, m_iMaxSRTPayloadSize); + m_SndTimeWindow.initialize(full_hdr_size, m_iMaxDataPayloadSize); + m_RcvTimeWindow.initialize(full_hdr_size, m_iMaxDataPayloadSize); m_iEXPCount = 1; m_iBandwidth = 1; // pkts/sec // XXX use some constant for this 16 m_iDeliveryRate = 16; - m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize; + m_iByteDeliveryRate = 16 * m_iMaxDataPayloadSize; m_iAckSeqNo = 0; m_tsLastAckTime = steady_clock::now(); @@ -1822,7 +1822,7 @@ bool srt::CUDT::createSrtHandshake( // Now use the original function to store the actual SRT_HS data // ra_size after that - // NOTE: so far, ra_size is m_iMaxSRTPayloadSize expressed in number of elements. + // NOTE: so far, ra_size is controlPayloadSize() expressed in number of elements. // WILL BE CHANGED HERE. ra_size = fillSrtHandshake((p + offset), total_ra_size - offset, srths_cmd, HS_VERSION_SRT1); *pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size); @@ -1837,7 +1837,7 @@ bool srt::CUDT::createSrtHandshake( // Now prepare the string with 4-byte alignment. The string size is limited // to half the payload size. Just a sanity check to not pack too much into // the conclusion packet. - size_t size_limit = m_iMaxSRTPayloadSize / 2; + size_t size_limit = controlPayloadSize() / 2; if (m_config.sStreamName.size() >= size_limit) { @@ -2223,7 +2223,7 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt) case SRT_CMD_KMRSP: { // KMRSP doesn't expect any following action - m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion); + m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion, false); return true; // nothing to do } @@ -2628,7 +2628,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, m_RejectReason = SRT_REJ_VERSION; // This means that a version with minimum 1.3.0 that features HSv5 is required, // hence all HSv4 clients should be rejected. - LOGP(cnlog.Error, "interpretSrtHandshake: minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client"); + LOGP(cnlog.Error, string(FUNID()) + ": minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client"); return false; } return true; // do nothing @@ -2666,7 +2666,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ)) { - HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting HSREQ/RSP type extension"); + HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": extracting HSREQ/RSP type extension"); uint32_t *begin = p; uint32_t *next = 0; size_t length = size / sizeof(uint32_t); @@ -2698,7 +2698,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, { // m_RejectReason already set LOGC(cnlog.Error, - log << CONID() << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd); + log << CONID() << FUNID() << ": process HSREQ returned unexpected value " << rescmd); return false; } handshakeDone(); @@ -2729,7 +2729,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (m_RejectReason == SRT_REJ_UNKNOWN) m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Error, - log << CONID() << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd); + log << CONID() << FUNID() << ": process HSRSP returned unexpected value " << rescmd); return false; } handshakeDone(); @@ -2739,7 +2739,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, { m_RejectReason = SRT_REJ_ROGUE; LOGC(cnlog.Warn, - log << CONID() << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!"); + log << CONID() << FUNID() << ": no HSREQ/HSRSP block found in the handshake msg!"); // This means that there can be no more processing done by FindExtensionBlock(). // And we haven't found what we need - otherwise one of the above cases would pass // and lead to exit this loop immediately. @@ -2758,7 +2758,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } } - HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: HSREQ done, checking KMREQ"); + HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": HSREQ done, checking KMREQ"); // Now check the encrypted @@ -2766,7 +2766,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ)) { - HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting KMREQ/RSP type extension"); + HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": extracting KMREQ/RSP type extension"); #ifdef SRT_ENABLE_ENCRYPTION if (!m_pCryptoControl->hasPassphrase()) @@ -2799,7 +2799,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); HLOGC(cnlog.Debug, - log << CONID() << "interpretSrtHandshake: found extension: (" << cmd << ") " + log << CONID() << FUNID() << ": found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); size_t bytelen = blocklen * sizeof(uint32_t); @@ -2819,7 +2819,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, m_RejectReason = SRT_REJ_IPE; // Something went wrong. HLOGC(cnlog.Debug, - log << CONID() << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " + log << CONID() << FUNID() << ": IPE/EPE KMREQ processing failed - returned " << res); return false; } @@ -2832,7 +2832,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, m_RejectReason = SRT_REJ_CRYPTO; LOGC(cnlog.Error, log << CONID() - << "interpretSrtHandshake: KMREQ result: Bad crypto mode - rejecting"); + << FUNID() << ": KMREQ result: Bad crypto mode - rejecting"); return false; } #endif @@ -2851,7 +2851,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } LOGC(cnlog.Error, log << CONID() - << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption"); + << FUNID() << ": KMREQ result abnornal - rejecting per enforced encryption"); return false; } } @@ -2859,7 +2859,12 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } else if (cmd == SRT_CMD_KMRSP) { - int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion); + // Normally this is a handshake, but this one can be also called through + // the in-connected dispatcher and processCtrlHS(). The is_handshake can be + // still potentially set back to true inside when the KMX was detected as + // not done for the sake of HSv4. + bool is_handshake = m_parent->m_Status != SRTS_CONNECTED; + int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion, is_handshake); if (m_config.bEnforcedEnc && res == -1) { if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADSECRET) @@ -2884,7 +2889,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, } else { - HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); + HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); if (NextExtensionBlock((begin), next, (length))) continue; } @@ -2926,7 +2931,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG)) { - HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting various CONFIG extensions"); + HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": extracting various CONFIG extensions"); uint32_t *begin = p; uint32_t *next = 0; @@ -2938,7 +2943,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, int cmd = FindExtensionBlock(begin, length, (blocklen), (next)); HLOGC(cnlog.Debug, - log << CONID() << "interpretSrtHandshake: found extension: (" << cmd << ") " + log << CONID() << FUNID() << ": found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd)); const size_t bytelen = blocklen * sizeof(uint32_t); @@ -2947,7 +2952,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { LOGC(cnlog.Error, - log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " + log << CONID() << FUNID() << ": STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } @@ -2997,7 +3002,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (!bytelen || bytelen > CSrtConfig::MAX_CONG_LENGTH) { LOGC(cnlog.Error, - log << CONID() << "interpretSrtHandshake: CONGESTION-control type length " << bytelen + log << CONID() << FUNID() << ": CONGESTION-control type length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_CONG_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } @@ -3040,7 +3045,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, if (!bytelen || bytelen > CSrtConfig::MAX_PFILTER_LENGTH) { LOGC(cnlog.Error, - log << CONID() << "interpretSrtHandshake: packet-filter type length " << bytelen + log << CONID() << FUNID() << ": packet-filter type length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_PFILTER_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } @@ -3106,7 +3111,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs, { // Found some block that is not interesting here. Skip this and get the next one. HLOGC(cnlog.Debug, - log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); + log << CONID() << FUNID() << ": ... skipping " << MessageTypeStr(UMSG_EXT, cmd)); } if (!NextExtensionBlock((begin), next, (length))) @@ -3792,7 +3797,8 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // Inform the server my configurations. CPacket reqpkt; reqpkt.setControl(UMSG_HANDSHAKE); - reqpkt.allocate(m_iMaxSRTPayloadSize); + size_t hs_size = controlPayloadSize(serv_addr.family()); + reqpkt.allocate(hs_size); // XXX NOTE: Now the memory for the payload part is allocated automatically, // and such allocated memory is also automatically deallocated in the // destructor. If you use CPacket::allocate, remember that you must not: @@ -3807,7 +3813,6 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // ID = 0, connection request reqpkt.set_id(0); - size_t hs_size = m_iMaxSRTPayloadSize; m_ConnReq.store_to((reqpkt.m_pcData), (hs_size)); // Note that CPacket::allocate() sets also the size @@ -3864,7 +3869,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // next incoming packet. CPacket response; response.setControl(UMSG_HANDSHAKE); - response.allocate(m_iMaxSRTPayloadSize); + response.allocate(controlPayloadSize(serv_addr.family())); CUDTException e; EConnectStatus cst = CONN_CONTINUE; @@ -3916,7 +3921,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) } cst = CONN_CONTINUE; - response.setLength(m_iMaxSRTPayloadSize); + response.setLength(controlPayloadSize(serv_addr.family())); if (m_pRcvQueue->recvfrom(m_SocketID, (response)) > 0) { use_source_adr = response.udpDestAddr(); @@ -3969,7 +3974,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) m_RejectReason = SRT_REJ_ROGUE; // rejection or erroneous code. - reqpkt.setLength(m_iMaxSRTPayloadSize); + reqpkt.setLength(controlPayloadSize(serv_addr.family())); reqpkt.setControl(UMSG_HANDSHAKE); sendRendezvousRejection(serv_addr, (reqpkt)); } @@ -3997,12 +4002,12 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn) // Now serialize the handshake again to the existing buffer so that it's // then sent later in this loop. - // First, set the size back to the original size, m_iMaxSRTPayloadSize because + // First, set the size back to the original size, controlPayloadSize() because // this is the size of the originally allocated space. It might have been // shrunk by serializing the INDUCTION handshake (which was required before // sending this packet to the output queue) and therefore be too // small to store the CONCLUSION handshake (with HSv5 extensions). - reqpkt.setLength(m_iMaxSRTPayloadSize); + reqpkt.setLength(controlPayloadSize(serv_addr.family())); HLOGC(cnlog.Debug, log << CONID() << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength()); @@ -4132,7 +4137,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst, CPacket reqpkt; reqpkt.setControl(UMSG_HANDSHAKE); - reqpkt.allocate(m_iMaxSRTPayloadSize); + reqpkt.allocate(controlPayloadSize(serv_addr.family())); const steady_clock::time_point now = steady_clock::now(); setPacketTS(reqpkt, now); @@ -4450,7 +4455,7 @@ EConnectStatus srt::CUDT::processRendezvous( m_ConnReq.m_iReqType = rsp_type; m_ConnReq.m_extension = needs_extension; - // This must be done before prepareConnectionObjects(), because it sets ISN and m_iMaxSRTPayloadSize needed to create buffers. + // This must be done before prepareConnectionObjects(), because it sets ISN needed to create buffers. if (!applyResponseSettings(pResponse)) { LOGC(cnlog.Error, log << CONID() << "processRendezvous: peer settings rejected"); @@ -4514,7 +4519,7 @@ EConnectStatus srt::CUDT::processRendezvous( log << CONID() << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize); - w_reqpkt.setLength(m_iMaxSRTPayloadSize); + w_reqpkt.setLength(controlPayloadSize(serv_addr.family())); if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (w_reqpkt), (m_ConnReq))) @@ -4591,7 +4596,7 @@ EConnectStatus srt::CUDT::processRendezvous( // serialization. m_ConnReq.m_extension = needs_extension; - w_reqpkt.setLength(m_iMaxSRTPayloadSize); + w_reqpkt.setLength(controlPayloadSize(serv_addr.family())); if (m_RdvState == CHandShake::RDV_CONNECTED) { int cst = postConnect(pResponse, true, 0); @@ -4934,6 +4939,24 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx return postConnect(&response, false, eout); } +static size_t MinimumMSS(int family) +{ + const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; + const size_t full_hdr_size_i6 = CPacket::UDP_HDR_SIZE_IPv6 + CPacket::HDR_SIZE; + + size_t min_mss_size = 4; // initial: required for passing any data + if (family == AF_INET6) + { + min_mss_size += full_hdr_size_i6; + } + else + { + min_mss_size += full_hdr_size; + } + + return min_mss_size; +} + bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) ATR_NOEXCEPT { if (!m_ConnRes.valid()) @@ -4947,12 +4970,10 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT m_config.iMSS = m_ConnRes.m_iMSS; const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; - m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; - HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + m_iMaxDataPayloadSize = m_config.iMSS - full_hdr_size; + HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxDataPayloadSize); m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; - const int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE; - m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE; m_iPeerISN = m_ConnRes.m_iISN; setInitialRcvSeq(m_iPeerISN); @@ -4964,7 +4985,7 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT m_SourceAddr = pHspkt->udpDestAddr(); HLOGC(cnlog.Debug, - log << CONID() << "applyResponseSettings: HANDSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize + log << CONID() << "applyResponseSettings: HANDSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxDataPayloadSize << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " peer-ISN=" << m_ConnRes.m_iISN << " local-ISN=" << m_iISN << " peerID=" << m_ConnRes.m_iID @@ -5943,16 +5964,17 @@ bool srt::CUDT::prepareBuffers(CUDTException* eout) try { - // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. + // 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); + SRT_ASSERT(m_iMaxDataPayloadSize != 0); - HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << m_iMaxSRTPayloadSize + HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << m_iMaxDataPayloadSize << " snd-bufsize=" << 32 << " authtag=" << authtag); - m_pSndBuffer = new CSndBuffer(AF_INET, 32, m_iMaxSRTPayloadSize, authtag); + m_pSndBuffer = new CSndBuffer(AF_INET, 32, m_iMaxDataPayloadSize, 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. @@ -5997,13 +6019,23 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS + const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; // Uses the smaller MSS between the peers - m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS); + size_t mss_size = std::min(m_config.iMSS, w_hs.m_iMSS); + if (mss_size < MinimumMSS(peer.family())) + { + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: peer's MSS=" << w_hs.m_iMSS + << " is too small, can't accept the connection"); + m_RejectReason = SRT_REJ_ROGUE; + w_hs.m_iReqType = URQFailure(m_RejectReason); + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); + } - const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE; - m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size; + m_config.iMSS = mss_size; + + m_iMaxDataPayloadSize = m_config.iMSS - full_hdr_size; - HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize); + HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxDataPayloadSize); // exchange info for maximum flow window size m_iFlowWindowSize = w_hs.m_iFlightFlagSize; @@ -6026,7 +6058,6 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& rewriteHandshakeData(peer, (w_hs)); - // Prepare all structures if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0)) { @@ -6154,7 +6185,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& // TODO: Here create CONCLUSION RESPONSE with: // - just the UDT handshake, if HS_VERSION_UDT4, // - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP. - size_t size = m_iMaxSRTPayloadSize; + size_t size = controlPayloadSize(peer.family()); // Allocate the maximum possible memory for an SRT payload. // This is a maximum you can send once. CPacket rsppkt; @@ -6934,11 +6965,11 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // out a message of a length that exceeds the total size of the sending // buffer (configurable by SRTO_SNDBUF). - if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxSRTPayloadSize)) + if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxDataPayloadSize)) { LOGC(aslog.Error, log << CONID() << "Message length (" << len << ") exceeds the size of sending buffer: " - << (m_config.iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed."); + << (m_config.iSndBufSize * m_iMaxDataPayloadSize) << ". Use SRTO_SNDBUF if needed."); throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); } @@ -7069,7 +7100,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl) // Just return how many bytes were actually scheduled for writing. // XXX May be reasonable to add a flag that requires that the function // not return until the buffer is sent completely. - size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize); + size = min(len, sndBuffersLeft() * m_iMaxDataPayloadSize); } { @@ -7361,7 +7392,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_ // After signaling the tsbpd for ready data, report the bandwidth. #if ENABLE_HEAVY_LOGGING - double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxSRTPayloadSize ); + double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxDataPayloadSize ); HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth << " buffers per second)"); #endif } @@ -7853,7 +7884,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous) const int64_t availbw = m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth.load(); - perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize)); + perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxDataPayloadSize + pktHdrSize)); if (tryEnterCS(m_ConnectionLock)) { @@ -8193,22 +8224,18 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp // this is periodically NAK report; make sure NAK cannot be sent back too often // read loss list from the local receiver loss list - int32_t *data = new int32_t[m_iMaxSRTPayloadSize / 4]; - int losslen; - m_pRcvLossList->getLossArray(data, losslen, m_iMaxSRTPayloadSize / 4); - - if (0 < losslen) + FixedArray data (m_iMaxDataPayloadSize / sizeof(int32_t)); + int losslen = m_pRcvLossList->getLossArray(data); + if (losslen > 0) { - ctrlpkt.pack(pkttype, NULL, data, losslen * 4); + ctrlpkt.pack(pkttype, /*lparam*/ NULL, data.data(), losslen * sizeof(int32_t)); ctrlpkt.set_id(m_PeerID); - nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); + nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr); enterCS(m_StatsLock); m_stats.rcvr.sentNak.count(1); leaveCS(m_StatsLock); } - - delete[] data; } // update next NAK time, which should wait enough time for the retansmission, but not too long @@ -8555,11 +8582,12 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size) data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed((rcvRate)); data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); + // XXX Likely this can be removed already. //>>Patch while incompatible (1.0.2) receiver floating around if (m_uPeerSrtVersion == SrtVersion(1, 0, 2)) { data[ACKD_RCVRATE] = rcvRate; // bytes/sec - data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec + data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxDataPayloadSize; // bytes/sec ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102_ONLY; } else if (m_uPeerSrtVersion >= SrtVersion(1, 0, 3)) @@ -8937,10 +8965,11 @@ bool srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_ /* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE_VER102_ONLY]) and delivery rate (pcData[ACKD_RCVRATE]) in * bytes/sec instead of pkts/sec */ /* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */ + // XXX v1.0.2 handling can be likely deleted. if (acksize > ACKD_TOTAL_SIZE_UDTBASE) bytesps = ackdata[ACKD_RCVRATE]; else - bytesps = pktps * m_iMaxSRTPayloadSize; + bytesps = pktps * m_iMaxDataPayloadSize; m_iBandwidth = avg_iir<8>(m_iBandwidth.load(), bandwidth); m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate.load(), pktps); @@ -9325,7 +9354,7 @@ bool srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) CPacket rsppkt; rsppkt.setControl(UMSG_HANDSHAKE); - rsppkt.allocate(m_iMaxSRTPayloadSize); + rsppkt.allocate(controlPayloadSize()); // If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE. // There is also no possible IPE condition in case of HSv4 - for this version it will always return true. @@ -9342,6 +9371,12 @@ bool srt::CUDT::processCtrlHS(const CPacket& ctrlpkt) m_tsLastSndTime.store(steady_clock::now()); } } + + // If a REJECTION HS has been sent, break also the connection locally. + if (initdata.m_iReqType >= URQ_FAILURE_TYPES) + { + processCtrlShutdown(); // always returns true + } } else { @@ -11924,7 +11959,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet) if (conn != CONN_ACCEPT) return conn; - packet.setLength(m_iMaxSRTPayloadSize); + packet.setLength(controlPayloadSize(addr.family())); if (!acpu->createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (packet), (hs))) @@ -12544,7 +12579,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH) { LOGC(cnlog.Error, - log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " + log << CONID() << FUNID() << ": STREAMID length " << bytelen << " is 0 or > " << +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING"); return false; } diff --git a/srtcore/core.h b/srtcore/core.h index f2f1dc8bd2..fb9f733be8 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -475,7 +475,7 @@ class CUDT uint32_t peerLatency_us() const { return m_iPeerTsbPdDelay_ms * 1000; } int peerIdleTimeout_ms() const { return m_config.iPeerIdleTimeout_ms; } - size_t maxPayloadSize() const { return m_iMaxSRTPayloadSize; } + size_t maxDataPayloadSize() const { return m_iMaxDataPayloadSize; } size_t OPT_PayloadSize() const { return m_config.zExpPayloadSize; } size_t payloadSize() const { @@ -488,7 +488,7 @@ class CUDT // If SRTO_PAYLOADSIZE was remaining with 0 (default for FILE mode) // then return the maximum payload size per packet. - return m_iMaxSRTPayloadSize; + return m_iMaxDataPayloadSize; } int sndLossLength() { return m_pSndLossList->getLossLength(); } @@ -536,12 +536,21 @@ class CUDT int minSndSize(int len = 0) const { - const int ps = (int) maxPayloadSize(); + const int ps = (int) maxDataPayloadSize(); if (len == 0) // weird, can't use non-static data member as default argument! len = ps; return m_config.bMessageAPI ? (len+ps-1)/ps : 1; } + // This returns the biggest possible size for an SRT payload with + // the default 1500 MTU size. When in doubt, use AF_INET, which returns + // bigger size, if needed for a safe allocation. + static int controlPayloadSize(int family = AF_INET) + { + int header = family == AF_INET6 ? CPacket::UDP_HDR_SIZE_IPv6 : CPacket::UDP_HDR_SIZE; + return CPacket::ETH_MAX_MTU_SIZE - header; + } + static int32_t makeTS(const time_point& from_time, const time_point& tsStartTime) { // NOTE: @@ -879,7 +888,7 @@ class CUDT int sndSpaceLeft() { - return static_cast(sndBuffersLeft() * maxPayloadSize()); + return static_cast(sndBuffersLeft() * maxDataPayloadSize()); } int sndBuffersLeft() @@ -946,7 +955,7 @@ class CUDT #endif private: - int m_iMaxSRTPayloadSize; // Maximum/regular payload size, in bytes + int m_iMaxDataPayloadSize; // Maximum data payload size, in bytes int m_iTsbPdDelay_ms; // Rx delay to absorb burst, in milliseconds int m_iPeerTsbPdDelay_ms; // Tx delay that the peer uses to absorb burst, in milliseconds bool m_bTLPktDrop; // Enable Too-late Packet Drop diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp index ccc4714609..c6b18911a1 100644 --- a/srtcore/crypto.cpp +++ b/srtcore/crypto.cpp @@ -370,7 +370,32 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( return SRT_CMD_NONE; } -int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv) +// RETURNS: +// first: the converted state_value, or fallback if it was wrong +// second: the value to be set in the opposite direction, if different +inline std::pair ErraticKMState(uint32_t state_value) +{ + if (state_value >= uint32_t(SRT_KM_S_E_SIZE)) + { + LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer state value: " << state_value); + return std::make_pair(SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET); + } + + SRT_KM_STATE state = SRT_KM_STATE(state_value); + switch (state) + { + case SRT_KM_S_NOSECRET: + return std::make_pair(state, SRT_KM_S_UNSECURED); + + case SRT_KM_S_UNSECURED: + return std::make_pair(state, SRT_KM_S_NOSECRET); + + default: ; // do nothing + } + return std::make_pair(state, state); +} + +int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv, bool is_handshake) { uint32_t srtd[SRTDATA_MAXSIZE]; size_t srtlen = len/sizeof(uint32_t); @@ -385,13 +410,27 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len return SRT_CMD_NONE; } + // Still handle HSv4 post-handshake-handshake. XXX: DEPRECATED. Remove in next major version. + if (!is_handshake) + { + // HSv4 version is unidirectional and encryption is only set in one direction. + // So, as KMRSP handler, the agent should be expected to be a sender, hence m_hSndCrypto + // should be created, but m_hRcvCrypto not. In case of HSv5, both should be simultaneously + // either NULL or valid pointers. + if (!m_hRcvCrypto && m_SndKmState != SRT_KM_S_SECURED) + is_handshake = true; + + if (!m_hSndCrypto) + return SRT_CMD_NONE; + } + /* All 32-bit msg fields (if present) swapped on reception * But HaiCrypt expect network order message * Re-swap to cancel it. */ HtoNLA(srtd, srtdata, srtlen); - int retstatus = -1; + int retstatus = -1; // Error by default, unless all is confirmed // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE, // until the next KMREQ is received as a key regeneration. @@ -399,55 +438,30 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len if (srtlen == 1) // Error report. Set accordingly. { - SRT_KM_STATE peerstate = SRT_KM_STATE(srtd[SRT_KMR_KMSTATE]); /* Bad or no passphrase */ - m_SndKmMsg[0].iPeerRetry = 0; - m_SndKmMsg[1].iPeerRetry = 0; - - switch (peerstate) - { - case SRT_KM_S_BADSECRET: - m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET; - retstatus = -1; - break; - - // Default embraces two cases: - // NOSECRET: this KMRSP was sent by secured Peer, but Agent supplied no password. - // UNSECURED: this KMRSP was sent by unsecure Peer because Agent sent KMREQ. - - case SRT_KM_S_NOSECRET: - // This means that the peer did not set the password, while Agent did. - m_RcvKmState = SRT_KM_S_UNSECURED; - m_SndKmState = SRT_KM_S_NOSECRET; - retstatus = -1; - break; - - case SRT_KM_S_UNSECURED: - // This means that KMRSP was sent without KMREQ, to inform the Agent, - // that the Peer, unlike Agent, does use password. Agent can send then, - // but can't decrypt what Peer would send. - m_RcvKmState = SRT_KM_S_NOSECRET; - m_SndKmState = SRT_KM_S_UNSECURED; + SRT_KM_STATE peerstate, revstate; + Tie2(peerstate, revstate) = ErraticKMState(srtd[SRT_KMR_KMSTATE]); + if (peerstate == SRT_KM_S_UNSECURED) retstatus = 0; - break; -#ifdef ENABLE_AEAD_API_PREVIEW - case SRT_KM_S_BADCRYPTOMODE: - // The peer expects to use a different cryptographic mode (e.g. AES-GCM, not AES-CTR). - m_RcvKmState = SRT_KM_S_BADCRYPTOMODE; - m_SndKmState = SRT_KM_S_BADCRYPTOMODE; - retstatus = -1; - break; -#endif - default: - LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer error state: " - << KmStateStr(peerstate) << " (" << int(peerstate) << ")"); - m_RcvKmState = SRT_KM_S_NOSECRET; - m_SndKmState = SRT_KM_S_NOSECRET; - retstatus = -1; //This is IPE - break; + // If the erroneous KMRSP was received while the connection is established, we state + // the connection should be SECURED already, so no change of the state is done. + if (!is_handshake) + { + LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: rogue KMRSP received as KMX update - ignoring"); + // Still proceed with the summary logging. + } + else + { + m_SndKmMsg[0].iPeerRetry = 0; + m_SndKmMsg[1].iPeerRetry = 0; + m_SndKmState = peerstate; + m_RcvKmState = revstate; + LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState)); } - - LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState)); + } + else if (srtlen == 0) // 0-size is a special value for MsgLen, so avoid checks! + { + LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: IPE/EPE KM response key is empty - ignoring"); } else { @@ -467,14 +481,18 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len else { retstatus = -1; - LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE??? KM response key matches no key"); + LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE/EPE KM response key matches no key"); /* XXX INSECURE LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len) << "] matches no key 0=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[0].Msg, m_SndKmMsg[0].MsgLen) << "] 1=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[1].Msg, m_SndKmMsg[1].MsgLen) << "]"); */ - m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET; + // DO NOT change the state in case of KMX update with secured sender. + if (is_handshake) + { + m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET; + } } HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry); @@ -508,7 +526,7 @@ int srt::CCryptoControl::processSrtMsg_KMREQ( return SRT_CMD_KMRSP; } -int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t*, size_t, unsigned) +int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t*, size_t, unsigned, bool) { LOGP(cnlog.Error, "processSrtMsg_KMRSP: Encryption not enabled at compile time; not expected to receive SRT_CMD_KMRSP"); return SRT_CMD_NONE; diff --git a/srtcore/crypto.h b/srtcore/crypto.h index a1bb42d992..4a0d6aa02b 100644 --- a/srtcore/crypto.h +++ b/srtcore/crypto.h @@ -141,7 +141,7 @@ class CCryptoControl /// 1 - the given payload is the same as the currently used key /// 0 - there's no key in agent or the payload is error message with agent NOSECRET. /// -1 - the payload is error message with other state or it doesn't match the key - int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv); + int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv, bool is_handshake); void createFakeSndContext(); const unsigned char* getKmMsg_data(size_t ki) const { return m_SndKmMsg[ki].Msg; } diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp index 4ee5e44284..498c521830 100644 --- a/srtcore/fec.cpp +++ b/srtcore/fec.cpp @@ -59,11 +59,11 @@ bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error) string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows"); - int out_rows = 1; + int out_rows = 1, out_cols = 2; if (colspec != "") { - int out_cols = atoi(colspec.c_str()); + out_cols = atoi(colspec.c_str()); if (out_cols < 2) { w_error = "at least 'cols' must be specified and > 1"; @@ -81,6 +81,12 @@ bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error) } } + if (out_cols > MAX_GROUP_SIZE || out_rows > MAX_GROUP_SIZE || out_rows < -MAX_GROUP_SIZE) + { + w_error = "rows/cols size must not exceed 16-bit max value"; + return false; + } + // Extra interpret level, if found, default never. // Check only those that are managed. string level = map_get(cfg.parameters, "arq"); @@ -213,74 +219,85 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector // Required to store in the header when rebuilding rcv.id = socketID(); - // Setup the bit matrix, initialize everything with false. - - // Vertical size (y) - rcv.cells.resize(sizeCol() * sizeRow(), false); - - // These sequence numbers are both the value of ISN-1 at the moment - // when the handshake is done. The sender ISN is generated here, the - // receiver ISN by the peer. Both should be known after the handshake. - // Later they will be updated as packets are transmitted. - - int32_t snd_isn = CSeqNo::incseq(sndISN()); - int32_t rcv_isn = CSeqNo::incseq(rcvISN()); - - // Alright, now we need to get the ISN from m_parent - // to extract the sequence number allowing qualification to the group. - // The base values must be prepared so that feedSource can qualify them. - - // SEPARATE FOR SENDING AND RECEIVING! - - // Now, assignment of the groups requires: - // For row groups, simply the size of the group suffices. - // For column groups, you need a whole matrix of all sequence - // numbers that are base sequence numbers for the group. - // Sequences that belong to this group are: - // 1. First packet has seq+1 towards the base. - // 2. Every next packet has this value + the size of the row group. - // So: group dispatching is: - // - get the column number - // - extract the group data for that column - // - check if the sequence is later than the group base sequence, if not, report no group for the packet - // - sanity check, if the seqdiff divided by row size gets 0 remainder - // - The result from the above division can't exceed the column size, otherwise - // it's another group. The number of currently collected data should be in 'collected'. + // This will catch any memory allocation error and translate + // to MJ_SETUP / MN_NORES. + try + { + // Setup the bit matrix, initialize everything with false. + + // Vertical size (y) + rcv.cells.resize(sizeCol() * sizeRow(), false); + + // These sequence numbers are both the value of ISN-1 at the moment + // when the handshake is done. The sender ISN is generated here, the + // receiver ISN by the peer. Both should be known after the handshake. + // Later they will be updated as packets are transmitted. + + int32_t snd_isn = CSeqNo::incseq(sndISN()); + int32_t rcv_isn = CSeqNo::incseq(rcvISN()); + + // Alright, now we need to get the ISN from m_parent + // to extract the sequence number allowing qualification to the group. + // The base values must be prepared so that feedSource can qualify them. + + // SEPARATE FOR SENDING AND RECEIVING! + + // Now, assignment of the groups requires: + // For row groups, simply the size of the group suffices. + // For column groups, you need a whole matrix of all sequence + // numbers that are base sequence numbers for the group. + // Sequences that belong to this group are: + // 1. First packet has seq+1 towards the base. + // 2. Every next packet has this value + the size of the row group. + // So: group dispatching is: + // - get the column number + // - extract the group data for that column + // - check if the sequence is later than the group base sequence, if not, report no group for the packet + // - sanity check, if the seqdiff divided by row size gets 0 remainder + // - The result from the above division can't exceed the column size, otherwise + // it's another group. The number of currently collected data should be in 'collected'. + + // Now set up the group starting sequences. + // The very first group in both dimensions will have the value of ISN in particular direction. + + // Set up sender part. + // + // Size: rows + // Step: 1 (next packet in group is 1 past the previous one) + // Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows') + HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row"); + ConfigureGroup(snd.row, snd_isn, 1, sizeRow()); + + // In the beginning we need just one reception group. New reception + // groups will be created in tact with receiving packets outside this one. + // The value of rcv.row[0].base will be used as an absolute base for calculating + // the index of the group for a given received packet. + rcv.rowq.resize(1); + HLOGP(pflog.Debug, "FEC: INIT: receiver first row"); + ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow()); - // Now set up the group starting sequences. - // The very first group in both dimensions will have the value of ISN in particular direction. + if (sizeCol() > 1) + { + // Size: cols + // Step: rows (the next packet in the group is one row later) + // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down) + + HLOGP(pflog.Debug, "FEC: INIT: sender first N columns"); + ConfigureColumns(snd.cols, snd_isn); + HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns"); + ConfigureColumns(rcv.colq, rcv_isn); + } - // Set up sender part. - // - // Size: rows - // Step: 1 (next packet in group is 1 past the previous one) - // Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows') - HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row"); - ConfigureGroup(snd.row, snd_isn, 1, sizeRow()); - - // In the beginning we need just one reception group. New reception - // groups will be created in tact with receiving packets outside this one. - // The value of rcv.row[0].base will be used as an absolute base for calculating - // the index of the group for a given received packet. - rcv.rowq.resize(1); - HLOGP(pflog.Debug, "FEC: INIT: receiver first row"); - ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow()); - - if (sizeCol() > 1) + // The bit markers that mark the received/lost packets will be expanded + // as packets come in. + rcv.cell_base = rcv_isn; + } + catch (std::bad_alloc& be) { - // Size: cols - // Step: rows (the next packet in the group is one row later) - // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down) - - HLOGP(pflog.Debug, "FEC: INIT: sender first N columns"); - ConfigureColumns(snd.cols, snd_isn); - HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns"); - ConfigureColumns(rcv.colq, rcv_isn); + LOGC(pflog.Error, log << "FEC: INIT: Memory allocation error with cols=" + << numberCols() << " rows=" << numberRows()); + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); } - - // The bit markers that mark the received/lost packets will be expanded - // as packets come in. - rcv.cell_base = rcv_isn; } template @@ -544,11 +561,16 @@ void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt) // Clipping a control packet does merely the same, just the packet has // different contents, so it must be differetly interpreted. -void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) +bool FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) { // Both length and timestamp must be taken as NETWORK ORDER // before applying the clip. + if (pkt.size() < 4) // paranoid check + { + return false; + } + const char* fec_header = pkt.data(); const char* payload = fec_header + 4; size_t payload_clip_len = pkt.size() - 4; @@ -567,6 +589,8 @@ void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt) << " LENGTH[ne]=" << g.length_clip << " TS[he]=" << g.timestamp_clip << " PL4=" << (*(uint32_t*)&g.payload_clip[0])); + + return true; } void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt) @@ -601,6 +625,10 @@ void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg, HLOGC(pflog.Debug, log << "FEC CLIP: data pkt.size=" << payload_size << " to a clip buffer size=" << payloadSize()); + // Paranoid check + if (payload_size > payloadSize()) + payload_size = payloadSize(); + // Payload goes "as is". for (size_t i = 0; i < payload_size; ++i) { @@ -887,7 +915,7 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs) // - Both HangVertical and HangHorizontal okv = HangVertical(rpkt, isfec.colx, irrecover_col); - IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? int(okh) < HANG_CRAZY : false); + IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? okh < HANG_CRAZY : false); HLOGC(pflog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo() << " msgno=" << rpkt.getMsgSeq() << " RESULT=" << hangname[okh] @@ -1162,7 +1190,11 @@ FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangHorizontal(const CPacket& rp { if (!rowg.fec) { - ClipControlPacket(rowg, rpkt); + if (!ClipControlPacket(rowg, rpkt)) + { + LOGC(pflog.Error, log << "FEC/H: Rogue control packet received"); + return HANG_CRAZY; // That's the only "complete failure" statement + } rowg.fec = true; HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL packet clipped, %" << seq << " base=%" << rowg.base); } @@ -1863,7 +1895,11 @@ FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangVertical(const CPacket& rpkt { if (!colg.fec) { - ClipControlPacket(colg, rpkt); + if (!ClipControlPacket(colg, rpkt)) + { + LOGC(pflog.Error, log << "FEC/H: Rogue control packet received"); + return HANG_CRAZY; // That's the only "complete failure" statement + } colg.fec = true; HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL packet clipped, %" << seq << " FOR COLUMN " << int(fec_col) << " base=%" << colg.base); diff --git a/srtcore/fec.h b/srtcore/fec.h index f4ed0e4cca..c5715786e4 100644 --- a/srtcore/fec.h +++ b/srtcore/fec.h @@ -34,6 +34,12 @@ class FECFilterBuiltin: public SrtPacketFilterBase public: + // rows/cols size can have this size at maximum, otherwise + // there's a risk to have an overflow memory size value on + // 32-bit systems, which may result in a false success followed + // by a crash. + static const int MAX_GROUP_SIZE = 0xFFFF; + size_t numberCols() const { return m_number_cols; } size_t numberRows() const { return m_number_rows; } @@ -217,7 +223,7 @@ class FECFilterBuiltin: public SrtPacketFilterBase EHangStatus HangHorizontal(const CPacket& pkt, bool fec_ctl, loss_seqs_t& irrecover); EHangStatus HangVertical(const CPacket& pkt, signed char fec_colx, loss_seqs_t& irrecover); - void ClipControlPacket(Group& g, const CPacket& pkt); + SRT_ATR_NODISCARD bool ClipControlPacket(Group& g, const CPacket& pkt); void ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt); void RcvRebuild(Group& g, int32_t seqno, Group::Type tp); int32_t RcvGetLossSeqHoriz(Group& g); diff --git a/srtcore/group.cpp b/srtcore/group.cpp index b873bb710d..bd034a516f 100644 --- a/srtcore/group.cpp +++ b/srtcore/group.cpp @@ -3587,9 +3587,8 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx CUDTSocket* s = m_Global.locateSocket(id, CUDTUnited::ERH_RETURN); // << LOCKS m_GlobControlLock! if (s) { - HLOGC(gslog.Debug, - log << "grp/sendBackup: swait/ex on @" << (id) - << " while waiting for any writable socket - CLOSING"); + HLOGC(gslog.Debug, log << "grp/sendBackup: swait/ex on @" << id + << " while waiting for any writable socket - CLOSING"); CUDT::uglobal().close(s); // << LOCKS m_GlobControlLock, then GroupLock! } else @@ -3621,6 +3620,32 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); } + // IMPORTANT! + // There was a socket deletion possibly done above, and as well there + // was the m_GroupLock lifted for that check activity, so potentially any socket + // from m_Group container could be deleted; review them and remove any dangling + // objects from w_sendBackupCtx. + // + // Due to the nature of the m_Group container, use mark-and-sweep method. + + set remain; + w_sendBackupCtx.getSocketIds( (remain) ); + + // MARK + for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d) + { + remain.erase(d->id); + } + + HLOGC(gslog.Debug, log << "grp/sendBackup: RE-LOCK, checking members deleted in the meantime: " + << Printable(remain)); + + // SWEEP + for (set::iterator i = remain.begin(); i != remain.end(); ++i) + { + w_sendBackupCtx.deleteById(*i); + } + // Ok, now check if we have at least one write-ready. // Note that the procedure of activation of a new link in case of // no stable links found embraces also rexmit-sending and status diff --git a/srtcore/group_backup.cpp b/srtcore/group_backup.cpp index 9adb19607b..825791dab2 100644 --- a/srtcore/group_backup.cpp +++ b/srtcore/group_backup.cpp @@ -144,6 +144,30 @@ unsigned SendBackupCtx::countMembersByState(BackupMemberState st) const return m_stateCounter[st]; } +void SendBackupCtx::getSocketIds(std::set& ids) const +{ + typedef vector::const_iterator const_iter_t; + for (const_iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) + { + ids.insert(i->socketID); + } +} + +bool SendBackupCtx::deleteById(SRTSOCKET id) +{ + typedef vector::iterator iter_t; + for (iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i) + { + if (i->socketID == id) + { + m_memberStates.erase(i); + return true; + } + } + + return false; +} + std::string SendBackupCtx::printMembers() const { stringstream ss; diff --git a/srtcore/group_backup.h b/srtcore/group_backup.h index 71a1f7abba..4705cc3ad3 100644 --- a/srtcore/group_backup.h +++ b/srtcore/group_backup.h @@ -107,6 +107,8 @@ namespace groups /// Higher weight comes first, same weight: stable first, then fresh active. void sortByWeightAndState(); + bool deleteById(SRTSOCKET id); + BackupMemberState getMemberState(const SocketData* pSocketDataIt) const; unsigned countMembersByState(BackupMemberState st) const; @@ -122,6 +124,8 @@ namespace groups const CRateEstimator& getRateEstimate() const { return m_rateEstimate; } + void getSocketIds(std::set& out) const; + private: std::vector m_memberStates; // TODO: consider std::map here? unsigned m_stateCounter[BKUPST_E_SIZE]; diff --git a/srtcore/handshake.cpp b/srtcore/handshake.cpp index c97b4e2a34..878b90452a 100644 --- a/srtcore/handshake.cpp +++ b/srtcore/handshake.cpp @@ -51,10 +51,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include "handshake.h" +#include "packet.h" #include "udt.h" #include "api.h" #include "core.h" -#include "handshake.h" #include "utilities.h" using namespace std; @@ -198,7 +199,7 @@ bool srt::CHandShake::valid() { if (m_iVersion < CUDT::HS_VERSION_UDT4 || m_iISN < 0 || m_iISN >= CSeqNo::m_iMaxSeqNo - || m_iMSS < 32 + || m_iMSS < int(CPacket::UDP_HDR_SIZE_IPv6 + CPacket::HDR_SIZE) || m_iFlightFlagSize < 2) return false; diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 94eaa5da0d..8573ee258d 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -791,13 +791,12 @@ int32_t srt::CRcvLossList::getFirstLostSeq() const return m_caSeq[m_iHead].seqstart; } -void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit) +int srt::CRcvLossList::getLossArray(FixedArray& array) { - len = 0; - + int len = 0; int i = m_iHead; - while ((len < limit - 1) && (-1 != i)) + while ((len < int(array.size()) - 1) && (i != -1)) { array[len] = m_caSeq[i].seqstart; if (SRT_SEQNO_NONE != m_caSeq[i].seqend) @@ -812,6 +811,7 @@ void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit) i = m_caSeq[i].inext; } + return len; } srt::CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age) diff --git a/srtcore/list.h b/srtcore/list.h index 802ede9a2d..539c18478d 100644 --- a/srtcore/list.h +++ b/srtcore/list.h @@ -57,6 +57,7 @@ modified by #include "udt.h" #include "common.h" +#include "utilities.h" namespace srt { @@ -203,11 +204,9 @@ class CRcvLossList int32_t getFirstLostSeq() const; /// Get a encoded loss array for NAK report. - /// @param [out] array the result list of seq. no. to be included in NAK. - /// @param [out] len physical length of the result array. - /// @param [in] limit maximum length of the array. - - void getLossArray(int32_t* array, int& len, int limit); + /// @param [inout] array the result list of seq. no. to be included in NAK. + /// @return physical length of the result array. + int getLossArray(FixedArray& array); private: struct Seq diff --git a/srtcore/packet.h b/srtcore/packet.h index 6747e244c9..a735c4cb0d 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -372,7 +372,12 @@ 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 BARE_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 = 40; // 40 bytes IPv6 + + static const size_t UDP_HDR_SIZE = BARE_UDP_HDR_SIZE + IPv4_HDR_SIZE; + static const size_t UDP_HDR_SIZE_IPv6 = BARE_UDP_HDR_SIZE + IPv6_HDR_SIZE; static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 69eb63a8d9..278d802be6 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -927,7 +927,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst return; HLOGC(cnlog.Debug, - log << "updateConnStatus: collected " << toProcess.size() << " for processing, " << toRemove.size() + log << FUNID() << ": collected " << toProcess.size() << " for processing, " << toRemove.size() << " to close"); // Repeat (resend) connection request. @@ -947,43 +947,45 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst // to interpret these data (for caller-listener this was already done by `processConnectRequest` // before calling this function), and it checks for the data presence. - EReadStatus read_st = rst; - EConnectStatus conn_st = cst; - CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id); if (!sk.socket) { // Socket deleted already, so stop this and proceed to the next loop. - LOGC(cnlog.Error, log << "updateConnStatus: IPE: socket @" << i->id << " already closed, proceed to only removal from lists"); + LOGC(cnlog.Error, log << FUNID() << ": IPE: socket @" << i->id << " already closed, proceed to only removal from lists"); toRemove.push_back(*i); continue; } + EReadStatus read_st = rst; + EConnectStatus conn_st = cst; - if (cst != CONN_RENDEZVOUS && dest_id != 0) - { - if (i->id != dest_id) - { - HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " but for RID @" << i->id - << " dest_id=@" << dest_id << " - resetting to AGAIN"); + // Ok, we should have 3 cases here: + // 1. id == 0 ==> conn_st cannot be == CONN_RENDEZVOUS; reset to AGAIN always + // 2. conn_st == CONN_RENDEZVOUS -> id > 0 and no "alien" sockets are expected to be in the loop -> never reset to AGAIN + // 3. id > 0 and no rendezvous -> reset to AGAIN, unless id == dest_id. - read_st = RST_AGAIN; - conn_st = CONN_AGAIN; - } - else - { - HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " for @" - << i->id); - } + // Condition: + // IF CONN_RENDEZVOUS -> never reset to AGAIN. + // ELSE IF dest_id == id -> don't reset to AGAIN + // ELSE: reset to again. + + if (cst == CONN_RENDEZVOUS || i->id == dest_id) + { + HLOGC(cnlog.Debug, log << FUNID() << ": applied to @" << i->id + << (cst == CONN_RENDEZVOUS ? "[RDV] " : "") + << " with target @" << dest_id << " -- remains: cst=" << ConnectStatusStr(cst)); } else { - HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " and dest_id=@" << dest_id - << " - NOT checking against RID @" << i->id); + HLOGC(cnlog.Debug, log << FUNID() << ": applied to @" << i->id + << " with target @" << dest_id << " -- resetting to AGAIN"); + + read_st = RST_AGAIN; + conn_st = CONN_AGAIN; } HLOGC(cnlog.Debug, - log << "updateConnStatus: processing async conn for @" << i->id << " FROM " << i->peeraddr.str()); + log << FUNID() << ": processing async conn for @" << i->id << " FROM " << i->peeraddr.str()); if (!i->u->processAsyncConnectRequest(read_st, conn_st, pkt, i->peeraddr)) { @@ -1004,14 +1006,14 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst for (vector::iterator i = toRemove.begin(); i != toRemove.end(); ++i) { - HLOGC(cnlog.Debug, log << "updateConnStatus: COMPLETING dep objects update on failed @" << i->id); + HLOGC(cnlog.Debug, log << FUNID() << ": COMPLETING dep objects update on failed @" << i->id); remove(i->id); CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id); if (!sk.socket) { // This actually shall never happen, so it's a kind of paranoid check. - LOGC(cnlog.Error, log << "updateConnStatus: IPE: socket @" << i->id << " already closed, NOT ACCESSING its contents"); + LOGC(cnlog.Error, log << FUNID() << ": IPE: socket @" << i->id << " already closed, NOT ACCESSING its contents"); continue; } @@ -1048,7 +1050,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst if (find_if(toRemove.begin(), toRemove.end(), LinkStatusInfo::HasID(i->m_iID)) != toRemove.end()) { LOGC(cnlog.Error, - log << "updateConnStatus: processAsyncConnectRequest FAILED on @" << i->m_iID + log << FUNID() << ": processAsyncConnectRequest FAILED on @" << i->m_iID << ". Setting TTL as EXPIRED."); i->m_tsTTL = steady_clock::time_point(); // Make it expire right now, will be picked up at the next iteration @@ -1069,7 +1071,7 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst, return false; // nothing to process. HLOGC(cnlog.Debug, - log << "updateConnStatus: updating after getting pkt with DST socket ID @" << iDstSockID + log << FUNID() << ": updating after getting pkt with DST socket ID @" << iDstSockID << " status: " << ConnectStatusStr(cst)); for (list::iterator i = m_lRendezvousID.begin(), i_next = i; i != m_lRendezvousID.end(); i = i_next) diff --git a/srtcore/srt.h b/srtcore/srt.h index 36054e7d81..43c6593726 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -641,10 +641,9 @@ 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. + SRT_KM_S_E_SIZE }; enum SRT_EPOLL_OPT diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp index 5948eed7c8..bc870ce58d 100644 --- a/test/test_bonding.cpp +++ b/test/test_bonding.cpp @@ -1042,6 +1042,9 @@ TEST(Bonding, BackupPriorityBegin) EXPECT_EQ(backup->memberstate, SRT_GST_IDLE); acthr.join(); + + srt_close(g_listen_socket); + srt_close(ss); } @@ -1237,6 +1240,9 @@ TEST(Bonding, BackupPriorityTakeover) EXPECT_EQ(backup->memberstate, SRT_GST_RUNNING); acthr.join(); + + srt_close(g_listen_socket); + srt_close(ss); } @@ -1590,6 +1596,7 @@ TEST(Bonding, BackupPrioritySelection) acthr.join(); + srt_close(g_listen_socket); srt_close(ss); } diff --git a/test/test_crypto.cpp b/test/test_crypto.cpp index 1d04b19238..d531303d89 100644 --- a/test/test_crypto.cpp +++ b/test/test_crypto.cpp @@ -28,8 +28,11 @@ TEST(CryptoKMRSP, RejectsMalformedLengths) const unsigned srtv = srt::SrtVersion(1, 5, 3); // Oversize: would overflow uint32_t srtd[SRTDATA_MAXSIZE]. - EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), SRT_CMD_MAXSZ + sizeof(uint32_t), srtv), + EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), SRT_CMD_MAXSZ + sizeof(uint32_t), srtv, false), srt::SRT_CMD_NONE); + // Empty / under-a-word: HtoNLA writes nothing and downstream code would read + // uninitialised stack from srtd[]. + EXPECT_EQ(crypt.processSrtMsg_KMRSP(garbage.data(), 0, srtv, false), srt::SRT_CMD_NONE); } @@ -229,7 +232,7 @@ TEST_F(Crypto, KMREQ_EmptySEK_Does_Not_Downgrade_Secured) // Forged KMRSP claiming any peer-failure state must not downgrade a // SECURED session. The dispatcher accepts KMRSPs unconditionally so each // peerstate branch in processSrtMsg_KMRSP is reachable off-path. -TEST_F(Crypto, DISABLED_KMRSP_PeerFailure_Does_Not_Downgrade_Secured) +TEST_F(Crypto, KMRSP_PeerFailure_Does_Not_Downgrade_Secured) { using namespace srt; @@ -260,8 +263,10 @@ TEST_F(Crypto, DISABLED_KMRSP_PeerFailure_Does_Not_Downgrade_Secured) uint32_t wire = (uint32_t)wire_peerstates[i]; uint32_t input = 0; NtoHLA(&input, &wire, 1); - EXPECT_EQ(m_crypt.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3)), - SRT_CMD_NONE); + + // NOTE: We do not check the result here; success is only expected with + // successful report. + m_crypt.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3), false); EXPECT_EQ(m_crypt.m_RcvKmState, SRT_KM_S_SECURED) << "peerstate=" << (int)wire_peerstates[i] << " downgraded m_RcvKmState"; @@ -582,7 +587,7 @@ TEST_F(CryptoCtr, KmrspPeerNoSecretOnNonSecured) uint32_t wire = (uint32_t)SRT_KM_S_NOSECRET; uint32_t input = 0; NtoHLA(&input, &wire, 1); - fresh.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3)); + fresh.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3), true); // Per crypto.cpp KMRSP NOSECRET branch: RX -> UNSECURED, SND -> NOSECRET. EXPECT_EQ(fresh.m_RcvKmState, SRT_KM_S_UNSECURED); @@ -622,7 +627,7 @@ TEST_F(CryptoCtr, KmrspSuccessTransitionsToSecured) std::array km_nworder; NtoHLA(km_nworder.data(), reinterpret_cast(kmmsg), km_len); - fresh.processSrtMsg_KMRSP(km_nworder.data(), km_len, SrtVersion(1, 5, 3)); + fresh.processSrtMsg_KMRSP(km_nworder.data(), km_len, SrtVersion(1, 5, 3), true); EXPECT_EQ(fresh.m_RcvKmState, SRT_KM_S_SECURED); EXPECT_EQ(fresh.m_SndKmState, SRT_KM_S_SECURED); @@ -651,7 +656,7 @@ TEST_F(CryptoCtr, KmrspPeerUnsecuredOnNonSecured) uint32_t wire = (uint32_t)SRT_KM_S_UNSECURED; uint32_t input = 0; NtoHLA(&input, &wire, 1); - fresh.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3)); + fresh.processSrtMsg_KMRSP(&input, sizeof(input), SrtVersion(1, 5, 3), true); // Per crypto.cpp KMRSP UNSECURED branch: RX -> NOSECRET, SND -> UNSECURED. EXPECT_EQ(fresh.m_RcvKmState, SRT_KM_S_NOSECRET); diff --git a/test/test_fec_rebuilding.cpp b/test/test_fec_rebuilding.cpp index da78aaa62f..f73ee85648 100644 --- a/test/test_fec_rebuilding.cpp +++ b/test/test_fec_rebuilding.cpp @@ -247,6 +247,8 @@ TEST(TestFEC, ConfigExchange) TEST(TestFEC, ConfigExchangeFaux) { srt::TestInit srtinit; + using namespace std; + CUDTSocket* s1; @@ -259,12 +261,15 @@ TEST(TestFEC, ConfigExchangeFaux) "fec,cols:10,rows:-1", // E3: invalid value for rows "fec,cols:10,layout:stairwars", // E4: invalid value for layout "fec,cols:10,arq:sometimes", // E5: invalid value for arq - "fec,cols:10,weight:2" // F: invalid parameter name + "fec,cols:10,weight:2", // F: invalid parameter name + "fec,cols:80000,rows:70000", // oversized + "fec,cols:10,rows:-70000" // negative oversized rows }; for (auto badconfig: fec_config_wrong) { - ASSERT_EQ(srt_setsockflag(sid1, SRTO_PACKETFILTER, badconfig, (int)strlen(badconfig)), -1); + cout << "CASE: " << badconfig << endl; + EXPECT_EQ(srt_setsockflag(sid1, SRTO_PACKETFILTER, badconfig, (int)strlen(badconfig)), -1); } TestMockCUDT m1; @@ -285,27 +290,27 @@ TEST(TestFEC, ConfigExchangeFaux) TEST(TestFEC, Connection) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:10"; const char fec_config2 [] = "fec,cols:10,arq:never"; const char fec_config_final [] = "fec,cols:10,rows:10,arq:never,layout:staircase"; - ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); - ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); + EXPECT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); + EXPECT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); - srt_listen(l, 1); + EXPECT_NE(srt_listen(l, 1), -1); auto connect_res = spawn_connect(s, sa, 1); @@ -316,7 +321,7 @@ TEST(TestFEC, Connection) // that 1s might not be enough. SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 5000); - ASSERT_NE(a, SRT_ERROR); + EXPECT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -336,35 +341,36 @@ TEST(TestFEC, Connection) EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); - srt_close(a); - srt_close(s); - srt_close(l); + // Exceptionally blocked here to test "forgotten socket cleanup" additionally + //srt_close(a); + //srt_close(s); + //srt_close(l); } TEST(TestFEC, ConnectionReorder) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:10"; const char fec_config2 [] = "fec,rows:10,cols:10"; const char fec_config_final [] = "fec,cols:10,rows:10,arq:onreq,layout:staircase"; - ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); - ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); + EXPECT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); + EXPECT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); int conntimeo = 10000; - ASSERT_NE(srt_setsockflag(s, SRTO_CONNTIMEO, &conntimeo, sizeof (conntimeo)), SRT_ERROR); + EXPECT_NE(srt_setsockflag(s, SRTO_CONNTIMEO, &conntimeo, sizeof (conntimeo)), SRT_ERROR); srt_listen(l, 1); @@ -375,7 +381,7 @@ TEST(TestFEC, ConnectionReorder) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 5000); - ASSERT_NE(a, SRT_ERROR); + EXPECT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -402,25 +408,25 @@ TEST(TestFEC, ConnectionReorder) TEST(TestFEC, ConnectionFull1) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:20,arq:never,layout:even"; const char fec_config2 [] = "fec,layout:even,rows:20,cols:10,arq:never"; const char fec_config_final [] = "fec,cols:10,rows:20,arq:never,layout:even"; - ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); - ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); + EXPECT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); + EXPECT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); @@ -430,7 +436,7 @@ TEST(TestFEC, ConnectionFull1) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 5000); - ASSERT_NE(a, SRT_ERROR); + EXPECT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -457,25 +463,25 @@ TEST(TestFEC, ConnectionFull1) TEST(TestFEC, ConnectionFull2) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:20,arq:always,layout:even"; const char fec_config2 [] = "fec,layout:even,rows:20,cols:10,arq:always"; const char fec_config_final [] = "fec,cols:10,rows:20,arq:always,layout:even"; - ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); - ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); + EXPECT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); + EXPECT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); srt_listen(l, 1); @@ -486,7 +492,7 @@ TEST(TestFEC, ConnectionFull2) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 5000); - ASSERT_NE(a, SRT_ERROR); + EXPECT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -513,27 +519,27 @@ TEST(TestFEC, ConnectionFull2) TEST(TestFEC, ConnectionMess) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:,cols:10"; const char fec_config2 [] = "fec,cols:,rows:10"; const char fec_config_final [] = "fec,cols:10,rows:10,arq:onreq,layout:staircase"; - ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); - ASSERT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); + EXPECT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); + EXPECT_NE(srt_setsockflag(l, SRTO_PACKETFILTER, fec_config2, (sizeof fec_config2)-1), -1); - srt_listen(l, 1); + EXPECT_NE(srt_listen(l, 1), -1); auto connect_res = spawn_connect(s, sa); @@ -542,7 +548,7 @@ TEST(TestFEC, ConnectionMess) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 5000); - ASSERT_NE(a, SRT_ERROR) << srt_getlasterror_str(); + EXPECT_NE(a, SRT_ERROR) << srt_getlasterror_str(); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -569,23 +575,23 @@ TEST(TestFEC, ConnectionMess) TEST(TestFEC, ConnectionForced) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,rows:20,cols:20"; const char fec_config_final [] = "fec,cols:20,rows:20"; - ASSERT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); + EXPECT_NE(srt_setsockflag(s, SRTO_PACKETFILTER, fec_config1, (sizeof fec_config1)-1), -1); srt_listen(l, 1); @@ -596,7 +602,7 @@ TEST(TestFEC, ConnectionForced) SRTSOCKET la[] = { l }; SRTSOCKET a = srt_accept_bond(la, 1, 5000); - ASSERT_NE(a, SRT_ERROR); + EXPECT_NE(a, SRT_ERROR); EXPECT_EQ(connect_res.get(), SRT_SUCCESS); // Now that the connection is established, check negotiated config @@ -619,17 +625,17 @@ TEST(TestFEC, ConnectionForced) TEST(TestFEC, RejectionConflict) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,cols:10,rows:10"; @@ -664,17 +670,17 @@ TEST(TestFEC, RejectionConflict) TEST(TestFEC, RejectionIncompleteEmpty) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,rows:10"; @@ -707,17 +713,17 @@ TEST(TestFEC, RejectionIncompleteEmpty) TEST(TestFEC, RejectionIncomplete) { - srt::TestInit srtinit; - - SRTSOCKET s = srt_create_socket(); - SRTSOCKET l = srt_create_socket(); - 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); + srt::TestInit srtinit; + + SRTSOCKET s = srt_create_socket(); + SRTSOCKET l = srt_create_socket(); + srt_bind(l, (sockaddr*)& sa, sizeof(sa)); const char fec_config1 [] = "fec,rows:10"; From d99d2e1a3b1a213b03c7dfbea5133898935fdeea Mon Sep 17 00:00:00 2001 From: cmollahan <43041498+cmollahan@users.noreply.github.com> Date: Wed, 26 Aug 2026 02:45:09 -0500 Subject: [PATCH 85/92] [doc] Update README.md (#3356) Updated link to SRT Alliance Deployment Guide --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d6a6bcc6c..7e8c654462 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ In live streaming configurations, the SRT protocol maintains a constant end-to-e |:-----------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------:| | [The SRT API](./docs#srt-api-documents) | [IETF Internet Draft](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01) | [Sample Apps](./docs#sample-applications) | | Reference documentation for the SRT library API | The SRT Protocol Internet Draft | Instructions for using test apps (`srt-live-transmit`, `srt-file-transmit`, etc.) | -| [SRT Technical Overview](https://github.com/Haivision/srt/files/2489142/SRT_Protocol_TechnicalOverview_DRAFT_2018-10-17.pdf) | [SRT Deployment Guide](https://www.srtalliance.org/srt-deployment-guide/) | [SRT CookBook](https://srtlab.github.io/srt-cookbook) | +| [SRT Technical Overview](https://github.com/Haivision/srt/files/2489142/SRT_Protocol_TechnicalOverview_DRAFT_2018-10-17.pdf) | [SRT Deployment Guide](https://www3.haivision.com/srt-deployment-guide/) | [SRT CookBook](https://srtlab.github.io/srt-cookbook) | | Early draft technical overview (precursor to the Internet Draft) | A comprehensive overview of the protocol with deployment guidelines | Development notes on the SRT protocol | | [Innovation Labs Blog](https://medium.com/innovation-labs-blog/tagged/secure-reliable-transport) | [SRTLab YouTube Channel](https://www.youtube.com/channel/UCr35JJ32jKKWIYymR1PTdpA) | [Slack](https://srtalliance.slack.com) | | The blog on Medium with SRT-related technical articles | Technical YouTube channel with useful videos | Slack channels to get the latest updates and ask questions
[Join SRT Alliance on Slack](https://slackin-srtalliance.azurewebsites.net/) | From 899348d8318eb9a3c5a5b6ec43c4a1114288773a Mon Sep 17 00:00:00 2001 From: cl-ment Date: Fri, 28 Aug 2026 08:51:08 +0200 Subject: [PATCH 86/92] Change to version 1.5.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Gérouville --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e10715a258..4933de247b 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ # cmake_minimum_required (VERSION 3.5 FATAL_ERROR) -set (SRT_VERSION 1.5.6) +set (SRT_VERSION 1.5.7) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(CheckSymbolExists) From b052502e1f473cee1dc4bef2208e0c3a5334db87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:30:00 +0200 Subject: [PATCH 87/92] feat(srtla): pace loss reports for lossy bonds and document the latency floor Repeat spacing follows the measured inter-link spread instead of the floored reorder hold, the NAK timer runs at a fixed 20 ms period under SRTLA, and repeats speed up towards the play deadline. The previous timing waited too long for lost retransmissions under sustained loss. The 1000 ms minimum receiver latency under SRTLA is logged when it raises a lower setting and documented with SRTO_SRTLA. --- docs/API/API-socket-options.md | 31 +++++++++++++++++++++++++++++++ srtcore/core.cpp | 32 +++++++++++++++++++++++++++----- srtcore/list.cpp | 18 +++++++++++++----- srtcore/srt.h | 6 ++++-- srtcore/srtla_rec.cpp | 32 +++++++++++++------------------- 5 files changed, 88 insertions(+), 31 deletions(-) diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index 3291f6e278..2aa2bbc362 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -254,6 +254,7 @@ The following table lists SRT API socket options in alphabetical order. Option d | [`SRTO_SNDKMSTATE`](#SRTO_SNDKMSTATE) | 1.2.0 | | `int32_t` | enum | | | R | S | | [`SRTO_SNDSYN`](#SRTO_SNDSYN) | | post | `bool` | | true | | RW | GSI | | [`SRTO_SNDTIMEO`](#SRTO_SNDTIMEO) | | post | `int32_t` | ms | -1 | -1.. | RW | GSI | +| [`SRTO_SRTLA`](#SRTO_SRTLA) | fork | pre-bind | `bool` | | false | | RW | S | | [`SRTO_STATE`](#SRTO_STATE) | | | `int32_t` | enum | | | R | S | | [`SRTO_STREAMID`](#SRTO_STREAMID) | 1.3.0 | pre | `string` | | "" | [512] | RW | GSD | | [`SRTO_TLPKTDROP`](#SRTO_TLPKTDROP) | 1.0.6 | pre | `bool` | | \* | | RW | GSD | @@ -1299,6 +1300,9 @@ This value is only significant when [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) is enabl **Default value**: 120 ms in Live mode, 0 in File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)). +On connections accepted by an SRTLA listener ([`SRTO_SRTLA`](#SRTO_SRTLA)) the value is never below +1000 ms; a lower setting is raised on accept. + The latency value defines the **minimum** receiver buffering delay before delivering an SRT data packet from a receiving SRT socket to a receiving application. @@ -1580,6 +1584,33 @@ if in "non-blocking mode". The -1 value means no time limit. --- +#### SRTO_SRTLA + +| OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | +| ------------ | ----- | -------- | ------ | ----- | ------- | ----- | --- | ------ | +| `SRTO_SRTLA` | fork | pre-bind | `bool` | | false | | RW | S | + +Set on a listener before binding, this designates the socket's UDP port as an SRTLA (SRT Link +Aggregation) demux: bonded links register on it with the SRTLA protocol, their packets are merged +into one SRT connection, and the receiver's SRT control packets are fanned back out over the links. +A muxer designated this way never merges with a plain SRT muxer. + +Accepted connections inherit the flag. On them it enables the SRTLA multipath delivery tuning +(time-based loss reporting, reorder hold, loss-report fan-out) and enforces the **SRTLA minimum +receiver latency of 1000 ms**: + +- A lower [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY) / [`SRTO_LATENCY`](#SRTO_LATENCY) on the listener is + raised to 1000 ms when the connection is accepted (logged at `note` level), and the raised value is + negotiated to the sender as its peer latency. The sender then buffers for the raised value as well. +- The raise is one-directional: a latency of 1000 ms or more is left as configured. +- Rationale: the reorder hold over bonded links may grow up to half the latency (capped at + 500 ms) and one full recovery round trip has to fit behind it; below 1000 ms a lost + retransmission can no longer be recovered before the play deadline. + +Encoders for bonded streaming default to 2000 ms (BELABOX) or 3000 ms; the practical +recommendation for cellular bonding is 2000 ms or more. Latencies up to 5000 ms are exercised by +the end-to-end harness in `testing/srtla-e2e`. + #### SRTO_STATE | OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 715dce5ed6..ec72f05f39 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -271,6 +271,10 @@ const uint32_t SRTLA_HOLD_FALLBACK_US = 250000; const uint32_t SRTLA_HOLD_EVIDENCE_MARGIN_US = 30000; const int64_t SRTLA_HOLD_DECAY_TAU_US = 20000000; const int SRTLA_MIN_RCVLATENCY_MS = 1000; +// Under SRTLA the NAK timer is the only loss-report emitter and the hold already paces the +// reports, so it runs at a fixed short period: the stock (SRTT + 4 RTTVar) / 2 would turn the +// inter-link RTT variance of a bond into a hidden reorder tolerance. +const int64_t SRTLA_NAK_PERIOD_US = 20000; const int64_t SRTLA_DEADLINE_MARGIN_US = 30000; // Sampling window for pctSndQuality / pctRcvQuality. @@ -405,8 +409,17 @@ srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) m_SrtHsSide = ancestor.m_SrtHsSide; // actually it sets it to HSD_RESPONDER m_bTLPktDrop = ancestor.m_bTLPktDrop; m_iReorderTolerance = m_config.iMaxReorderTolerance; // Initialize with maximum value + + // SRTLA protocol rule: the receiver latency is never below SRTLA_MIN_RCVLATENCY_MS. + // The reorder hold may grow to half the latency (capped at 500 ms) and one recovery + // round trip has to fit behind it, so anything lower cannot be served. Raise-only; the + // raised value reaches the sender through the handshake response as its peer latency. if (m_config.bSRTLA && m_config.iRcvLatency < SRTLA_MIN_RCVLATENCY_MS) - m_config.iRcvLatency = SRTLA_MIN_RCVLATENCY_MS; // SRTLA minimum receiver latency + { + LOGC(cnlog.Note, log << CONID() << "SRTLA: receiver latency " << m_config.iRcvLatency + << " ms is below the protocol minimum, raised to " << SRTLA_MIN_RCVLATENCY_MS << " ms"); + m_config.iRcvLatency = SRTLA_MIN_RCVLATENCY_MS; + } // Runtime m_pCache = ancestor.m_pCache; @@ -11606,6 +11619,7 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) vector lossdata; int confirmed_loss = 0; + int64_t dbg_hold_us = 0, dbg_spacing_us = 0; size_t dbg_records = 0; // for the debug log below { ScopedLock lk(m_RcvLossLock); @@ -11613,9 +11627,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) SrtlaNakParams np; np.hold_us = srtlaReorderHoldUs(currtime); - // A round trip plus the reorder spread: SRTT tracks the fastest path, - // the retransmission may travel the slowest. - np.spacing_us = std::max(srtt_us + np.hold_us, + // A round trip plus the measured link spread: SRTT tracks the fastest path, the + // retransmission may travel the slowest. The floored, evidence-raised hold is + // deliberately not used here (it doubled the wait under load). + np.spacing_us = std::max(srtt_us + std::max(m_uiSrtlaHoldSteadyUs.load(), 20000), count_microseconds(m_tdNAKInterval)); // No TSBPD means no play deadline and no TLPKTDROP: deadline handling off. np.budget_us = m_bTsbPd ? (int64_t)m_iTsbPdDelay_ms * 1000 : 0; @@ -11633,6 +11648,7 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) rec.report_time = currtime; } confirmed_loss = plan.confirmed; + dbg_hold_us = np.hold_us; dbg_spacing_us = np.spacing_us; dbg_records = plan.report.size(); // The indices above address the untouched container, so retire only now. if (plan.retire > 0) @@ -11648,6 +11664,9 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int)lossdata.size()); countConfirmedLoss(confirmed_loss); debug_decision = BECAUSE_NAKREPORT; + HLOGC(qrlog.Debug, log << CONID() << "SRTLA: loss report with " << dbg_records << " record(s), hold " + << (dbg_hold_us / 1000) << " ms, spacing " << (dbg_spacing_us / 1000) << " ms, timer period " + << count_milliseconds(m_tdNAKInterval) << " ms"); } } else @@ -11657,7 +11676,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) } } - m_tsNextNAKTime.store(currtime + m_tdNAKInterval); + steady_clock::duration next = m_tdNAKInterval; + if (srtla_emitter && next > microseconds_from(SRTLA_NAK_PERIOD_US)) + next = microseconds_from(SRTLA_NAK_PERIOD_US); + m_tsNextNAKTime.store(currtime + next); return debug_decision; } diff --git a/srtcore/list.cpp b/srtcore/list.cpp index 8330e25b92..d1b8d6d996 100644 --- a/srtcore/list.cpp +++ b/srtcore/list.cpp @@ -979,11 +979,18 @@ bool srtlaNakEligible(const srt::CRcvFreshLoss& rec, if (age_us < params.hold_us) return false; // still within the reordering grace period - // No round trip fits in what is left of the play budget. - if (params.budget_us > 0 && age_us + params.rtt_us + params.margin_us > params.budget_us) - return false; - - if (!first_report && count_microseconds(now - rec.report_time) < params.spacing_us) + // What is left of the play budget for one more round trip. + const int64_t remaining_us = params.budget_us > 0 ? params.budget_us - age_us - params.rtt_us - params.margin_us : 1; + if (remaining_us < 0) + return false; // no round trip fits any more + + int64_t spacing_us = params.spacing_us; + // The closer the play deadline, the sooner a repeat. With plenty of budget left the normal + // spacing applies (few duplicates); once only a couple of round trips fit, repeat at half the + // remaining time so at least two more attempts can be made before the deadline. + if (remaining_us > 0 && remaining_us / 2 < spacing_us) + spacing_us = std::max(remaining_us / 2, 50000); + if (!first_report && count_microseconds(now - rec.report_time) < spacing_us) return false; // repeat not due yet return true; @@ -1008,6 +1015,7 @@ srt::SrtlaNakPlan srt::srtlaPlanNak(const std::deque& fresh, { ++plan.retire; } + } size_t used = 0; // 32-bit words already claimed in the report diff --git a/srtcore/srt.h b/srtcore/srt.h index e80a644fc6..941a5b4611 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -236,8 +236,10 @@ typedef enum SRT_SOCKOPT { SRTO_SRTLA = 120, // On a listener: designate an SRTLA (link-aggregation) demux listener. // Inherited by accepted connections, where it also enables the SRTLA - // multipath delivery tuning (retransmit-flag / ordered-delivery heuristics). - // (Formerly SRTO_SRTLAPATCHES.) + // multipath delivery tuning (retransmit-flag / ordered-delivery heuristics) + // and enforces the SRTLA minimum receiver latency of 1000 ms: a lower + // SRTO_RCVLATENCY / SRTO_LATENCY is raised on accept and the raised value + // is negotiated to the sender. (Formerly SRTO_SRTLAPATCHES.) SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp index 0b36f21a46..a39cbab581 100644 --- a/srtcore/srtla_rec.cpp +++ b/srtcore/srtla_rec.cpp @@ -12,6 +12,7 @@ #include "platform_sys.h" #include +#include #include #include "srtla_rec.h" @@ -801,34 +802,27 @@ uint32_t srt::SrtlaRec::holdSteadyUs(SRTSOCKET socket_id) const time_point now = steady_clock::now(); - double tmin = 0.0, tmax = 0.0, jmax = 0.0; - bool have = false; + std::vector transit, jitter; for (std::list::iterator it = grp->links.begin(); it != grp->links.end(); ++it) { // transit_ewma only advances on arriving data, so a stale one is not a current // measurement and must not widen the spread. if (!it->transit_valid || count_microseconds(now - it->last_transit) > LINK_FRESH_US) continue; - - if (!have) - { - tmin = tmax = it->transit_ewma; - have = true; - } - else - { - if (it->transit_ewma < tmin) - tmin = it->transit_ewma; - if (it->transit_ewma > tmax) - tmax = it->transit_ewma; - } - if (it->jitter_ewma > jmax) - jmax = it->jitter_ewma; + transit.push_back(it->transit_ewma); + jitter.push_back(it->jitter_ewma); } - if (!have) + if (transit.empty()) return 0; - + // The slowest link the hold waits for: a link that is late by more than the cap is not + // covered and gets starved by the sender's own loss penalties, links inside it stay in the + // bond. (A median-of-links policy was measured end to end and rejected.) + const double tmin = *std::min_element(transit.begin(), transit.end()); + const double tmax = *std::max_element(transit.begin(), transit.end()); + const double jmax = *std::max_element(jitter.begin(), jitter.end()); double hold = (tmax - tmin) + 4.0 * jmax; + HLOGC(qrlog.Debug, log << "SRTLA hold: " << transit.size() << " fresh links, spread " << ((tmax - tmin) / 1000.0) + << " ms, jitter " << (jmax / 1000.0) << " ms -> steady " << (hold / 1000.0) << " ms"); if (hold < 0.0) hold = 0.0; if (hold > 10e6) From d77ed3eff3ff87e93913ece0cc0c9aa49453b774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:50:00 +0200 Subject: [PATCH 88/92] test: add srtla end-to-end harness with ci score Drives receiver builds with the unmodified BELABOX sender stack through a userland link-impairment proxy (2 to 8 links, 1000 to 5000 ms latency, a catalogue of real-world scenarios) and scores them against a base build. The workflow runs the ci set on pull requests against main and posts the scorecard. Third-party code is neither copied nor patched. --- .github/workflows/srtla-e2e.yml | 120 ++++++++++++ testing/srtla-e2e/.gitignore | 4 + testing/srtla-e2e/Dockerfile | 10 + testing/srtla-e2e/README.md | 184 ++++++++++++++++++ testing/srtla-e2e/build.sh | 77 ++++++++ testing/srtla-e2e/compare.py | 16 ++ testing/srtla-e2e/docker.sh | 22 +++ testing/srtla-e2e/holes.py | 29 +++ testing/srtla-e2e/macshim.h | 19 ++ testing/srtla-e2e/proxy.py | 183 +++++++++++++++++ testing/srtla-e2e/receiver.cpp | 128 ++++++++++++ testing/srtla-e2e/repeats_table.py | 18 ++ testing/srtla-e2e/run_matrix.py | 303 +++++++++++++++++++++++++++++ testing/srtla-e2e/score.py | 131 +++++++++++++ testing/srtla-e2e/sender.cpp | 120 ++++++++++++ testing/srtla-e2e/sweep_table.py | 29 +++ 16 files changed, 1393 insertions(+) create mode 100644 .github/workflows/srtla-e2e.yml create mode 100644 testing/srtla-e2e/.gitignore create mode 100644 testing/srtla-e2e/Dockerfile create mode 100644 testing/srtla-e2e/README.md create mode 100755 testing/srtla-e2e/build.sh create mode 100755 testing/srtla-e2e/compare.py create mode 100755 testing/srtla-e2e/docker.sh create mode 100755 testing/srtla-e2e/holes.py create mode 100644 testing/srtla-e2e/macshim.h create mode 100755 testing/srtla-e2e/proxy.py create mode 100644 testing/srtla-e2e/receiver.cpp create mode 100755 testing/srtla-e2e/repeats_table.py create mode 100755 testing/srtla-e2e/run_matrix.py create mode 100755 testing/srtla-e2e/score.py create mode 100644 testing/srtla-e2e/sender.cpp create mode 100755 testing/srtla-e2e/sweep_table.py diff --git a/.github/workflows/srtla-e2e.yml b/.github/workflows/srtla-e2e.yml new file mode 100644 index 0000000000..0a9c1d83ab --- /dev/null +++ b/.github/workflows/srtla-e2e.yml @@ -0,0 +1,120 @@ +# SRTLA end-to-end regression score. Builds two receivers from this repository (the pull request +# as it would be merged, and the tip of the main branch), drives both with the real BELABOX sender stack through the impairment proxy under +# Linux (see testing/srtla-e2e/README.md), scores the runs and, on pull requests, posts the +# scorecard as a comment. build.sh clones the BELABOX repositories at pinned commits and builds +# them unmodified. +name: srtla-e2e + +on: + pull_request: + paths: ['srtcore/**', 'testing/srtla-e2e/**', '.github/workflows/srtla-e2e.yml'] + workflow_dispatch: + inputs: + base: + description: 'git ref to compare against (default: tip of the default branch, main)' + default: '' + scenarios: + description: "scenario set or comma list for run_matrix.py ('ci' = catalogue at 2000 ms plus 5000 ms, 2 and 8 links, slow carriers)" + default: 'ci' + +permissions: + contents: read + pull-requests: write + +concurrency: + group: srtla-e2e-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e: + runs-on: ubuntu-24.04 + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + # ten parallel jobs; each clones and builds its own binaries (~8 min) and runs every tenth scenario of the set + group: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + steps: + - uses: actions/checkout@v4 + with: + path: srt + fetch-depth: 0 + - name: Tools + run: sudo apt-get update -q && sudo apt-get install -y -q ninja-build + - name: Resolve base (always the main branch unless overridden) + id: base + working-directory: srt + run: | + ref="${{ inputs.base }}" + [ -n "$ref" ] || ref="origin/${{ github.event.repository.default_branch }}" + echo "sha=$(git rev-parse "$ref")" >> "$GITHUB_OUTPUT" + echo "head: $(git log --oneline -1 HEAD)" + echo "base: $ref ($(git log --oneline -1 "$ref"))" + - name: Build receivers, sender, srtla_send + working-directory: srt/testing/srtla-e2e + run: ./build.sh head=HEAD base=${{ steps.base.outputs.sha }} + - name: Run scenarios (two seeds, base and head interleaved) + working-directory: srt/testing/srtla-e2e + run: | + set="${{ inputs.scenarios || 'ci' }}" + ./run_matrix.py base,head "$set" --group ${{ matrix.group }}/10 --out ci-s1 --seed 1 + ./run_matrix.py base,head "$set" --group ${{ matrix.group }}/10 --out ci-s2 --seed 2 + - uses: actions/upload-artifact@v4 + if: always() + with: + name: srtla-e2e-${{ matrix.group }} + path: srt/testing/srtla-e2e/results + retention-days: 30 + + score: + needs: e2e + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + path: srt + - uses: actions/download-artifact@v4 + with: + pattern: srtla-e2e-* + path: parts + - name: Merge and score + working-directory: srt/testing/srtla-e2e + run: | + python3 - <<'PY' + import json, glob, os, shutil + for seed in ('ci-s1', 'ci-s2'): + merged = {}; os.makedirs(os.path.join('results', seed), exist_ok=True) + for part in sorted(glob.glob(os.path.join('../../../parts/srtla-e2e-*', seed))): + s = os.path.join(part, 'summary.json') + if os.path.exists(s): merged.update(json.load(open(s))) + for d in os.listdir(part): + if os.path.isdir(os.path.join(part, d)): shutil.copytree(os.path.join(part, d), os.path.join('results', seed, d), dirs_exist_ok=True) + json.dump(merged, open(os.path.join('results', seed, 'summary.json'), 'w'), indent=1) + print(seed, len(merged), 'scenarios merged') + PY + ./score.py ci-s1,ci-s2 --base base --head head --markdown scorecard.md --json scorecard.json --fail-on-regression \ + --title "SRTLA end-to-end score" || echo "regression=1" >> "$GITHUB_ENV" + cat scorecard.md >> "$GITHUB_STEP_SUMMARY" + - uses: actions/upload-artifact@v4 + with: + name: srtla-e2e-scorecard + path: | + srt/testing/srtla-e2e/scorecard.md + srt/testing/srtla-e2e/scorecard.json + srt/testing/srtla-e2e/results/ci-s1/summary.json + srt/testing/srtla-e2e/results/ci-s2/summary.json + - name: Comment on the pull request + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const body = fs.readFileSync('srt/testing/srtla-e2e/scorecard.md', 'utf8') + '\nsrtla-e2e run ' + context.runId + ''; + const marker = '### SRTLA end-to-end score'; + const { data: comments } = await github.rest.issues.listComments({ ...context.repo, issue_number: context.issue.number, per_page: 100 }); + const mine = comments.find(c => c.user.type === 'Bot' && c.body.startsWith(marker)); + if (mine) await github.rest.issues.updateComment({ ...context.repo, comment_id: mine.id, body }); + else await github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body }); + - name: Fail on regression + if: env.regression == '1' + run: echo "::error::SRTLA end-to-end score regressed against the base (see scorecard)"; exit 1 diff --git a/testing/srtla-e2e/.gitignore b/testing/srtla-e2e/.gitignore new file mode 100644 index 0000000000..3cec8c1aa2 --- /dev/null +++ b/testing/srtla-e2e/.gitignore @@ -0,0 +1,4 @@ +build/ +build-linux/ +results/ +__pycache__/ diff --git a/testing/srtla-e2e/Dockerfile b/testing/srtla-e2e/Dockerfile new file mode 100644 index 0000000000..c8f88c550e --- /dev/null +++ b/testing/srtla-e2e/Dockerfile @@ -0,0 +1,10 @@ +# Linux runtime for the SRTLA end-to-end harness. Linux routes the whole 127.0.0.0/8 on lo, +# so srtla_send can bind as many links as a scenario needs (127.0.0.2, .3, ...) without root +# or interface aliases. The fork is bind-mounted by docker.sh and build.sh clones the BELABOX +# repositories into its build directory; nothing is copied into the image. +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake ninja-build git python3 ca-certificates procps \ + && rm -rf /var/lib/apt/lists/* \ + && git config --global --add safe.directory '*' +WORKDIR /work/srt/testing/srtla-e2e diff --git a/testing/srtla-e2e/README.md b/testing/srtla-e2e/README.md new file mode 100644 index 0000000000..46f62017ac --- /dev/null +++ b/testing/srtla-e2e/README.md @@ -0,0 +1,184 @@ +# SRTLA end-to-end harness + +Regression harness for the SRTLA receiver in this fork (`srtcore/srtla_rec.*` plus the +SRTLA-gated loss-report logic in `core.cpp` / `list.cpp`). It drives the **real sender +stack** a BELABOX-style encoder uses, so that receiver-side feedback changes (which NAKs go +where, and when) are judged by their effect on the sender: how the links are used, how fast +holes are closed, and how much sits unacknowledged in the send buffer (the signal an +encoder-side bitrate controller reacts to). + +``` +sender (own code, on BELABOX/srt) --> srtla_send (BELABOX/srtla, unmodified) --> proxy.py --> receiver- (this repo) + one socket per link (local IPs) per-link delay/jitter/loss/ + rate/outage, no root needed +``` + +## Licensing + +Only code written for this harness lives in this directory. The sender stack is third-party +and is used the way any user would use it, without copying or modifying it: + +- `srtla_send` (BELABOX/srtla, AGPL-3) is compiled unmodified straight from a checkout; on + macOS a compatibility header of our own (`macshim.h`) is passed via `-include`. +- libsrt (BELABOX/srt, MPL-2.0) is built unmodified and linked as a library by our sender. +- belacoder (GPL-3) is not used. Our sender is a constant-bitrate generator that reports the + unacknowledged send buffer, which is what an adaptive-bitrate controller would read. +- `build.sh` clones both repositories at pinned commits and builds them, locally and in CI; nothing + is redistributed. + +Everything runs on one machine, without root: `srtla_send` binds one socket per local IPv4 +address, and the userland proxy keys the links by source IP and forwards each from its own +socket, so the receiver sees distinct link addresses like it would with real modems. + +- **Linux** (native, CI, or `docker.sh` on a Mac): the whole `127.0.0.0/8` is routable on + `lo`, so `127.0.0.1` to `127.0.0.8` are used and every scenario, including the four-link + real-world set, runs without any setup. +- **macOS native**: only the host's real IPv4 addresses work (loopback, LAN, VM bridge; link-local + `169.254.x` does not), typically three links. Four-link scenarios refuse to start there; use + `docker.sh`. + +## Files + +| File | What | +|---|---| +| `build.sh` | clones BELABOX/srt and BELABOX/srtla at the pinned commits into `build/deps/`; builds `build/receiver-` per `name=ref`, `build/sender`, `build/srtla_send` | +| `receiver.cpp` | SRTLA listener (`SRTO_SRTLA`, `SRTO_LOSSMAXTTL=50` like srt-live-server), checks the payload sequence and one-way latency, logs `srt_bstats` + `srt_srtla_stats` once a second | +| `sender.cpp` | paced constant-bitrate payload generator over BELABOX/srt, configured like a bonded live encoder; 20 ms trace of the unacknowledged send buffer | +| `macshim.h` | compatibility header so the unmodified `srtla_send` compiles on macOS | +| `score.py` | scores a results directory and compares two variants (see CI) | +| `proxy.py` | link impairment proxy: per-direction delay and jitter, FIFO or reordering, random and burst (Gilbert-Elliott) loss, duplicates, rate cap with bounded queue, dead links, timeline `set`/`ramp` events | +| `run_matrix.py` | scenarios, orchestration, per-run summary (`results//summary.json`) | +| `compare.py` | one table per scenario, variants side by side | +| `repeats_table.py` | aggregate a scenario over several seeds | +| `holes.py` | recovery-time (ACK lag) distribution from the sender's 20 ms buffer trace | +| `Dockerfile`, `docker.sh` | run build and matrix under Linux, where eight loopback addresses give any number of links | + +## Requirements + +- git and network access for the first build: `build.sh` clones `BELABOX/srt` (the libsrt fork + the sender links against) and `BELABOX/srtla` (`srtla_send`) at the pinned commits into + `build/deps/`. `BELABOX_SRT_REF` and `BELABOX_SRTLA_REF` select other commits. +- cmake (>= 3.19; CLion's bundled one is found automatically on macOS), ninja optional, + a C/C++ compiler, python3 (no packages). +- At least two non-link-local IPv4 addresses on the host for a 2-link test (a Wi-Fi/LAN + address plus loopback is enough; a VM bridge gives a third). + +## Run + +```sh +./build.sh head=HEAD main=main # the receiver under test and the reference, the tip of main +./run_matrix.py head,main rw # reference catalogue (Linux, 4 links, 2000 ms), ~60 s per run +./run_matrix.py head,main ci --group 1/10 # one tenth of the pull-request set, as a CI job does +./run_matrix.py head,main # every scenario except rw_soak +./run_matrix.py head,main lat200_6000,lat200_500 --out lat --seed 2 +./compare.py # results/default/summary.json as tables +./repeats_table.py default,s2,s3 heavy_loss # same scenario over several --out dirs + +./docker.sh build head=HEAD main=main # same, but under Linux in a container +./docker.sh run head,main rw --out linux # results/linux/ on the host +``` + +The two variants are the branch under test (`head`) and the tip of `main`, the pair CI scores as +well. `build.sh head=.` builds the working tree as it is, uncommitted changes included. + +Per run, `results////` holds `snd.jsonl` (sender, 1 Hz: send rate, +`bs` = unacked send buffer, RTT, retransmissions), `snd.bstrace` (the same buffer every 20 ms), +`rcv.jsonl` (receiver: NAKs sent, retransmissions received, TLPKTDROPs, belated packets, +application gaps, one-way latency percentiles, per-link SRTLA stats) and `proxy.jsonl` (per +link and second: forwarded / lost / queue-dropped packets, current impairment). + +Ports 15000-17000 are used; `test-srt` uses 5000+, so both can run at once (mind the CPU). + +`build/wt/` are git worktrees of this repository (`git worktree remove build/wt/` +to drop one). Keep the machine otherwise idle during runs: a 1-2 s stall of the host shows up +as hundreds of sender drops and an e2e p99 far above the latency, and is not a receiver result. + +## Scenario catalogue + +The `rw_*` scenarios are generated from 15 templates, each the four-link picture of one thing a +bonded mobile encoder meets on the road. Every template runs with any link set, link count and +latency; the reference is four decent carriers (25/40/55/70 ms one-way, 0.5 % loss) at 2000 ms. + +| template | real-world situation | +|---|---| +| `baseline` | healthy modems | +| `one_slow` / `two_slow` | one (two) carriers with a far higher base RTT: +200 ms (+160 / +240 ms) | +| `bloat` | bufferbloat: one link's delay ramps +560 ms over 8 s, holds, drains back | +| `bursty` | Gilbert-Elliott burst loss (~10-packet bursts) on two links | +| `flapping` | one modem drops out for 4 s every 10 s | +| `handover` | 3 s outage every 15 s on the fastest link (cell handover, short tunnel) | +| `join_late` | a modem boots 15 s into the stream and registers while streaming | +| `asym_dir` | uplink as configured but downlink +90 ms on every link (the NAK/ACK path is the slow one) | +| `reorder` | HARQ-style reordering inside every link (15 ms non-FIFO jitter) | +| `dup` | 2 % duplicated packets on one link | +| `capacity` | 2 Mbit; every link capped at 3 Mbit, the bond shrinks to 1.6 Mbit for 15 s: queues overflow | +| `dead_zone` | 2 Mbit; all links get 20 % loss and +300 ms for 15 s, then recover | +| `highrate` / `lowrate` | 25 Mbit; 400 kbit with 2 % loss on two links | + +Dimensions and sets (`run_matrix.py VARIANTS `): + +| set | what | scenarios | +|---|---|---| +| `rw` | the reference: four carriers, 2000 ms | 15 | +| `rw-lat` | the catalogue at 1000, 3000, 4000, 5000 ms (`_lat`) | 60 | +| `rw-slow` | the catalogue on slow carriers 100/120/140/160 ms (`_slow`) | 15 | +| `rw-links` | the catalogue with 2, 3, 5, 6, 7, 8 links (`_links`; delays 25 + 15 ms per link) | 90 | +| `rw-extra` | one link 1.1 s / 2 s late, a bloat ramp to 2 s, the slow link listed first (TSBPD time base) | 4 | +| `loss` | the lossy scenarios that separate algorithms (the clean ones any algorithm passes): dead zone in two strengths, 8 % on all links, 15 % on two, bursts on all four, handover with loss, capacity, a slow link with loss, 4 % at the 1000 ms floor | 10 | +| `ci` | the pull-request set: `rw` plus the extremes of each dimension on the whole catalogue: 5000 ms, 2 links, 8 links, slow carriers, and `rw_floor` (both sides ask for 500 ms; the receiver must raise to the SRTLA minimum of 1000 ms). What holds at both ends is assumed to hold in between. | 76 | +| `full` | everything except the 5-minute `rw_soak` | 195 | + +SRTLA does not run below 1000 ms receiver latency (protocol rule, see `SRTO_SRTLA` in +`docs/API/API-socket-options.md`; `score.py` flags any run below it) and encoders use 2000 (BELABOX) +or 3000 ms; the catalogue covers 1000 to 5000. The two- and three-link scenarios (`clean2`, `asym_rtt`, +`loss_both`, `heavy_loss`, `outage`, `thin_link`, `highrate`, `jitter_reorder`, `three_*`, `lat*`) +stay for macOS-native runs. Not covered: a Moblin sender +(iOS), several groups on one listener, NAT rebinding of a link mid-stream. + + +## CI: score and pull-request comment + +`.github/workflows/srtla-e2e.yml` runs on pull requests that touch `srtcore/` or this +directory (and on demand). Ten parallel Ubuntu jobs each build two receivers from this +repository, the pull request (merge commit) and the tip of `main`, plus libsrt and `srtla_send` from +the pinned BELABOX/srt and BELABOX/srtla commits, and run their share of the `ci` set (75 scenarios) with two seeds, base and +head interleaved so both see the same machine. A final job merges the results, runs `score.py`, +writes the scorecard to the job summary and posts (or updates) one comment on the pull request. +A regression fails the check. + +`score.py` gives every scenario and variant five sub-scores from 0 to 100: + +| sub-score | weight | measures | +|---|---|---| +| delivery | 0.40 | packets the application lost: `100 * exp(-lost permille / 5)`: one permille = 82 points, five = 37, twenty-five = 1 | +| efficiency | 0.20 | share of retransmissions that were duplicates (a request the sender paid for twice) | +| bond | 0.15 | the weakest healthy link's share against half of its fair share: does every usable link stay in the bond | +| recovery | 0.15 | ACK-lag p99 against half the latency: how fast the oldest hole gets closed | +| latency | 0.10 | end-to-end latency beyond what the fastest link allows, against 500 ms | + +The total per variant is the mean over scenarios. The verdict is **regression** when the total +drops by more than 3, any scenario by more than 10, the application lost 3+ packets where the +base lost none, or more than twice what the base lost. Runs during which the host stalled +(proxy ticks more than 1.5 s apart) are excluded and listed. Locally: + +```sh +./run_matrix.py main,head ci --out s1 --seed 1 && ./run_matrix.py main,head ci --out s2 --seed 2 +./score.py s1,s2 --base main --head head +``` + +Pull requests from forks get the check result and the job summary, but no comment (the token +is read-only there). + +## Reading the numbers + +- `missing` / `rcvdrop` / `snddrop`: application gaps, receiver TLPKTDROPs, sender drops. + Any non-zero value is a hard regression. +- `NAKs`, `rexmit`, `belated`: loss-report packets sent, retransmissions the sender made, + retransmissions that arrived for packets already recovered (duplicate requests). +- `bs mean` / `bs max`: unacknowledged packets in the sender's buffer (`SRTO_SNDDATA`). This is + what an encoder-side bitrate controller reacts to; it grows with the age of the oldest unrecovered hole. +- `share`: per-link share of forwarded uplink packets. A bond that collapses onto one link + shows here. +- `lag p99`: ACK lag, the age of the oldest unacknowledged packet (from the 20 ms trace), i.e. + how long the oldest hole stayed open. `stall`: longest gap between proxy ticks; a host stall + invalidates the run. diff --git a/testing/srtla-e2e/build.sh b/testing/srtla-e2e/build.sh new file mode 100755 index 0000000000..c06d4718ce --- /dev/null +++ b/testing/srtla-e2e/build.sh @@ -0,0 +1,77 @@ +#!/bin/sh +# Build everything the SRTLA end-to-end harness needs, into ./build: +# receiver- test receiver linked against this repo at (one per name=ref argument) +# sender test sender (own code) linked against the BELABOX libsrt fork (BELABOX/srt, MPL-2.0) +# srtla_send BELABOX's bonding sender (BELABOX/srtla, AGPL-3), compiled unmodified from its clone +# +# The two BELABOX repositories are cloned at pinned commits into build/deps/ and built as they are; +# nothing of them is copied into this repository. +# +# Usage: build.sh [name=ref ...] default: head=HEAD +# e.g. build.sh head=HEAD main=main +# Env: BELABOX_SRT_REF, BELABOX_SRTLA_REF commits of the BELABOX repositories (default: the pinned ones below) +# CMAKE cmake binary (default: cmake on PATH, else CLion's bundled one) +set -eu +HERE=$(cd "$(dirname "$0")" && pwd); REPO=$(cd "$HERE/../.." && pwd); B="$HERE/${BUILD_DIR:-build}" +# BELABOX sender stack, pinned to known-good commits; CI builds the same ones +BELABOX_SRT_REF=${BELABOX_SRT_REF:-a1054cfe8bf225d49356576137274396c5742b9d} +BELABOX_SRTLA_REF=${BELABOX_SRTLA_REF:-37862da3d0c13b46956efd3f88877053293d97d6} +if [ -z "${CMAKE:-}" ]; then + if command -v cmake >/dev/null 2>&1; then CMAKE=cmake + else CMAKE=$(ls /Applications/CLion.app/Contents/bin/cmake/mac/*/bin/cmake 2>/dev/null | head -1); fi +fi +[ -n "$CMAKE" ] || { echo "cmake not found (set CMAKE=...)"; exit 1; } +export CMAKE_POLICY_VERSION_MINIMUM=3.5 # both SRT trees declare cmake_minimum_required < 3.5; the env var also reaches sub-cmakes +JOBS=$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) +CMAKE_FLAGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SHARED=OFF -DENABLE_STATIC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_APPS=OFF -DENABLE_UNITTESTS=OFF" +command -v ninja >/dev/null 2>&1 || CMAKE_FLAGS=$(echo "$CMAKE_FLAGS" | sed 's/-G Ninja //') +mkdir -p "$B" + +# fetch_pinned : one commit of a BELABOX repository, shallow, detached; reused once it is there +fetch_pinned() { + [ -d "$3/.git" ] || { git init -q "$3" && git -C "$3" remote add origin "$1"; } + if git -C "$3" cat-file -e "$2^{commit}" 2>/dev/null; then git -C "$3" checkout -q --detach "$2" + else git -C "$3" fetch -q --depth 1 origin "$2" && git -C "$3" checkout -q --detach FETCH_HEAD; fi + echo " $1 @ $(git -C "$3" log --oneline -1)" +} + +# name=ref receiver built from a git ref (checked out into build/wt/) +# name=. receiver built from the working tree as it is (uncommitted changes included) +# name=ref:FLAGS same, with extra compiler flags, e.g. cap200=.:-DSRTLA_HOLD_CAP_MS=200 +[ $# -gt 0 ] || set -- head=HEAD +for spec in "$@"; do + name=${spec%%=*}; rest=${spec#*=}; ref=${rest%%:*}; flags="" + case "$rest" in *:*) flags=${rest#*:};; esac + if [ "$ref" = . ]; then + wt="$REPO"; echo "== receiver-$name (working tree${flags:+, $flags})" + else + wt="$B/wt/$name" + if [ -d "$wt" ]; then git -C "$wt" checkout -q --detach "$(git -C "$REPO" rev-parse "$ref")" + else git -C "$REPO" worktree add -q -f --detach "$wt" "$ref"; fi + echo "== receiver-$name ($(git -C "$wt" log --oneline -1)${flags:+, $flags})" + fi + # a build dir configured from another source tree (worktree vs working tree) has to start over + if [ -f "$B/lib/$name/CMakeCache.txt" ] && ! grep -q "^CMAKE_HOME_DIRECTORY:INTERNAL=$wt\$" "$B/lib/$name/CMakeCache.txt"; then rm -rf "$B/lib/$name"; fi + $CMAKE -S "$wt" -B "$B/lib/$name" $CMAKE_FLAGS -DCMAKE_CXX_FLAGS="$flags" >"$B/cmake-$name.log" 2>&1 || { tail -20 "$B/cmake-$name.log"; exit 1; } + $CMAKE --build "$B/lib/$name" -j "$JOBS" >>"$B/cmake-$name.log" 2>&1 || { grep -E 'error' "$B/cmake-$name.log" | head; exit 1; } + c++ -std=c++11 -O2 -g -I"$wt/srtcore" -I"$B/lib/$name" -o "$B/receiver-$name" "$HERE/receiver.cpp" "$B/lib/$name/libsrt.a" -lpthread +done + +echo "== sender (BELABOX/srt)" +SRT="$B/deps/srt"; fetch_pinned https://github.com/BELABOX/srt.git "$BELABOX_SRT_REF" "$SRT" +$CMAKE -S "$SRT" -B "$B/lib/belabox-srt" $CMAKE_FLAGS >"$B/cmake-belabox-srt.log" 2>&1 || { tail -20 "$B/cmake-belabox-srt.log"; exit 1; } +$CMAKE --build "$B/lib/belabox-srt" -j "$JOBS" >>"$B/cmake-belabox-srt.log" 2>&1 || { grep -E 'error' "$B/cmake-belabox-srt.log" | head; exit 1; } +c++ -std=c++11 -O2 -g -I"$SRT/srtcore" -I"$B/lib/belabox-srt" -o "$B/sender" "$HERE/sender.cpp" "$B/lib/belabox-srt/libsrt.a" -lpthread + +echo "== srtla_send (BELABOX/srtla)" +# Compiled unmodified, straight from the clone (AGPL-3, BELABOX). Nothing is copied or +# patched. On macOS a compatibility header supplies endian.h / CLOCK_MONOTONIC_COARSE / +# SOCK_NONBLOCK; on Linux the sources build as they are. +SRTLA="$B/deps/srtla"; fetch_pinned https://github.com/BELABOX/srtla.git "$BELABOX_SRTLA_REF" "$SRTLA" +SHIM="" +if [ "$(uname)" = Darwin ]; then + mkdir -p "$B/shim" && cp "$HERE/macshim.h" "$B/shim/macshim.h" && printf '#pragma once\n#include "macshim.h"\n' >"$B/shim/endian.h" + SHIM="-I$B/shim -include $B/shim/macshim.h" +fi +cc -O2 -g -w -DVERSION=\"e2e\" $SHIM -o "$B/srtla_send" "$SRTLA/srtla_send.c" "$SRTLA/common.c" || { echo "srtla_send build failed"; exit 1; } +echo "== done: $(ls "$B" | grep -E '^(receiver-|sender$|srtla_send$)' | tr '\n' ' ')" diff --git a/testing/srtla-e2e/compare.py b/testing/srtla-e2e/compare.py new file mode 100755 index 0000000000..8915950caf --- /dev/null +++ b/testing/srtla-e2e/compare.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Print one table per scenario from results//summary.json (default out: 'default').""" +import json, os, sys +ROOT = os.path.dirname(os.path.abspath(__file__)) +out = sys.argv[1] if len(sys.argv) > 1 else 'default' +res = json.load(open(os.path.join(ROOT, 'results', out, 'summary.json'))) +COLS = [('lat_negotiated', 'lat ms'), ('mbps_mean', 'Mbit/s'), ('app_missing', 'missing'), ('rcv_drop', 'rcvdrop'), ('snd_drop', 'snddrop'), + ('rcv_nak', 'NAKs'), ('snd_retrans', 'rexmit'), ('rcv_belated', 'belated'), ('proxy_lost', 'injected'), ('bs_mean', 'bs mean'), + ('lag_p99', 'lag p99'), ('lat_p50_median', 'e2e p50'), ('lat_p99_max', 'e2e p99'), ('stall_max_ms', 'stall')] +for name, vs in res.items(): + print('\n### %s' % name) + print('%-8s' % 'variant' + ''.join('%10s' % c[1] for c in COLS) + ' share %') + for v, m in vs.items(): + row = '%-8s' % v + ''.join('%10s' % m.get(c[0], '') for c in COLS) + share = ' '.join('%s:%s' % (ip.split('.')[-1] if ip != '127.0.0.1' else 'lo', s) for ip, s in m['share'].items()) + print(row + ' ' + share) diff --git a/testing/srtla-e2e/docker.sh b/testing/srtla-e2e/docker.sh new file mode 100755 index 0000000000..a5a1a97959 --- /dev/null +++ b/testing/srtla-e2e/docker.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# Run the harness under Linux in a container (4+ links, no root on the host). +# +# docker.sh build [name=ref ...] build receivers/sender/srtla_send into build-linux/ +# docker.sh run VARIANTS [SCENARIOS] [run_matrix.py options...] +# docker.sh shell interactive shell in the container +# +# The fork is bind-mounted; build.sh clones the BELABOX repositories inside the container into +# build-linux/deps/, and results land in results// on the host like a native run. +# Env: IMAGE, BELABOX_SRT_REF, BELABOX_SRTLA_REF. +set -eu +HERE=$(cd "$(dirname "$0")" && pwd); REPO=$(cd "$HERE/../.." && pwd) +IMAGE=${IMAGE:-srtla-e2e} +docker image inspect "$IMAGE" >/dev/null 2>&1 || docker build -t "$IMAGE" -f "$HERE/Dockerfile" "$HERE" +mounts="-v $REPO:/work/srt -e BELABOX_SRT_REF -e BELABOX_SRTLA_REF" +cmd=${1:-run}; [ $# -gt 0 ] && shift +case "$cmd" in + build) exec docker run --rm $mounts -e BUILD_DIR=build-linux "$IMAGE" ./build.sh "$@" ;; + run) exec docker run --rm $mounts -e BUILD_DIR=build-linux "$IMAGE" ./run_matrix.py "$@" ;; + shell) exec docker run --rm -it $mounts -e BUILD_DIR=build-linux "$IMAGE" bash ;; + *) echo "usage: docker.sh build|run|shell ..."; exit 2 ;; +esac diff --git a/testing/srtla-e2e/holes.py b/testing/srtla-e2e/holes.py new file mode 100755 index 0000000000..8b5b18a4ec --- /dev/null +++ b/testing/srtla-e2e/holes.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Recovery-time view from the sender's 20 ms send-buffer trace (snd.bstrace). + +The unacknowledged send buffer (SRTO_SNDDATA) grows with the age of the oldest packet the +receiver has not yet acknowledged, i.e. with the time the oldest unrecovered loss has been +open. Dividing by the packet rate turns it into an "ACK lag" in milliseconds. This prints +the lag distribution per run, so first-NAK delay and repeat spacing become visible as time, +independent of latency and bitrate. + + holes.py OUT SCENARIO [VARIANTS] +""" +import json, os, statistics, sys +ROOT = os.path.dirname(os.path.abspath(__file__)) +out, scn = sys.argv[1], sys.argv[2] +variants = sys.argv[3].split(',') if len(sys.argv) > 3 else sorted(os.listdir(os.path.join(ROOT, 'results', out, scn))) +print('%-8s%8s%8s%8s%8s%8s%9s%9s%9s %s' % ('variant', 'p50 ms', 'p90 ms', 'p99 ms', 'max ms', 'mean', '>300ms', '>500ms', '>800ms', 'time share of ACK lag above threshold')) +for v in variants: + d = os.path.join(ROOT, 'results', out, scn, v) + try: + rows = [tuple(map(float, l.split())) for l in open(os.path.join(d, 'snd.bstrace')) if l.strip()] + except FileNotFoundError: + continue + snd = [json.loads(l) for l in open(os.path.join(d, 'snd.jsonl')) if l.strip() and 'final' not in l] + pps = statistics.mean(r['pktSent'] for r in snd[2:]) if len(snd) > 2 else 1 + lag = sorted(bs / pps * 1000.0 for t, bs, fl, rx in rows if t > 3.0) # skip start-up + if not lag: continue + q = lambda p: lag[min(len(lag) - 1, int(p * len(lag)))] + share = lambda th: 100.0 * sum(1 for x in lag if x > th) / len(lag) + print('%-8s%8.0f%8.0f%8.0f%8.0f%8.0f%8.1f%%%8.1f%%%8.1f%%' % (v, q(0.5), q(0.9), q(0.99), lag[-1], statistics.mean(lag), share(300), share(500), share(800))) diff --git a/testing/srtla-e2e/macshim.h b/testing/srtla-e2e/macshim.h new file mode 100644 index 0000000000..250ce04548 --- /dev/null +++ b/testing/srtla-e2e/macshim.h @@ -0,0 +1,19 @@ +/* Compatibility header so that BELABOX's srtla_send (Linux-only sources) compiles on macOS + * without any change to its source files: passed via -include by build.sh. */ +#pragma once +#include +#include +#include +#ifndef CLOCK_MONOTONIC_COARSE +#define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +#endif +#ifndef SOCK_NONBLOCK +#define SOCK_NONBLOCK 0 /* sockets stay blocking; srtla_send only reads after select() */ +#endif +#ifdef __APPLE__ +#include +#define htobe16(x) OSSwapHostToBigInt16(x) +#define be16toh(x) OSSwapBigToHostInt16(x) +#define htobe32(x) OSSwapHostToBigInt32(x) +#define be32toh(x) OSSwapBigToHostInt32(x) +#endif diff --git a/testing/srtla-e2e/proxy.py b/testing/srtla-e2e/proxy.py new file mode 100755 index 0000000000..afe3aae09a --- /dev/null +++ b/testing/srtla-e2e/proxy.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Userland UDP link impairment proxy for SRTLA tests (no root needed). + +srtla_send binds one socket per local source IP and sends every link to one SRTLA +host:port. This proxy listens there, identifies each link by its source IP, forwards it to +the real receiver from a per-link socket (so the receiver sees distinct link addresses) and +impairs each direction independently. + +Per-link config keys (all optional): + delay_ms, delay_down_ms one-way delay per direction (down defaults to up) + jitter_ms, jitter_down_ms uniform extra delay per packet + reorder false (default): jitter never reorders within the link (queueing) + true: packets may overtake each other (HARQ-style reordering) + loss, loss_down random loss per direction + burst: {p_bad, p_good, loss_bad} Gilbert-Elliott burst loss on the uplink, on top of 'loss' + dup probability of duplicating an uplink packet + rate_kbps, queue_ms uplink capacity cap with a bounded FIFO queue (tail drop) + up false = link dead in both directions (modem off, tunnel) + +Timeline events (t = seconds after the first packet from srtla_send): + {"t": 10, "link": "", "set": {...}} apply immediately + {"t": 10, "link": "", "ramp": {"delay_ms": 600}, "dur": 8} linear ramp of numeric keys + +Log: one JSON line per link and second with forwarded / lost / queue-dropped counters. +""" +import asyncio, json, random, socket, sys + +NUMERIC = ('delay_ms', 'delay_down_ms', 'jitter_ms', 'jitter_down_ms', 'loss', 'loss_down', 'dup', 'rate_kbps', 'queue_ms') + +class Link: + def __init__(self, ip, cfg, seed, loop, upstream, log): + self.ip = ip; self.cfg = dict(cfg); self.loop = loop; self.upstream = upstream; self.log = log + self.rng = random.Random(seed) + self.socks = {} # srtla_send socket addr -> upstream socket + self.last_up = 0.0; self.last_down = 0.0 + self.bad = False # Gilbert-Elliott state + self.backlog_ms = 0.0 + self.c = dict(up_fwd=0, up_bytes=0, up_loss=0, up_burst=0, up_qdrop=0, up_dup=0, up_dead=0, up_enobufs=0, + down_fwd=0, down_bytes=0, down_loss=0, down_dead=0, down_enobufs=0) + # --- config --- + def set(self, kv): + self.cfg.update(kv) + def g(self, key, default=0.0): + return self.cfg.get(key, default) + def delay(self, down): + if down: + d = self.g('delay_down_ms', self.g('delay_ms')) / 1000.0; j = self.g('jitter_down_ms', self.g('jitter_ms')) / 1000.0 + else: + d = self.g('delay_ms') / 1000.0; j = self.g('jitter_ms') / 1000.0 + return d + (self.rng.random() * j if j > 0 else 0.0) + def lossy(self): + """Uplink loss decision: random loss plus an optional Gilbert-Elliott burst process.""" + b = self.cfg.get('burst') + if b: + if self.bad: + if self.rng.random() < b.get('p_good', 0.1): self.bad = False + elif self.rng.random() < b.get('p_bad', 0.0): self.bad = True + if self.bad and self.rng.random() < b.get('loss_bad', 0.5): + self.c['up_burst'] += 1; return True + if self.rng.random() < self.g('loss'): + self.c['up_loss'] += 1; return True + return False + # --- sender -> receiver --- + def up(self, data, src, listen_sock): + if src not in self.socks: + us = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); us.setblocking(False) + us.bind(('127.0.0.1', 0)); us.connect(self.upstream) + us.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 4 * 1024 * 1024) + self.socks[src] = us + self.loop.add_reader(us, self.down, us, src, listen_sock) + us = self.socks[src] + if not self.cfg.get('up', True): + self.c['up_dead'] += 1; return + if self.lossy(): + return + now = self.loop.time() + depart = now + self.delay(False) + if not self.cfg.get('reorder', False): + depart = max(depart, self.last_up) # FIFO: queueing, no overtaking + rate = self.g('rate_kbps') * 1000.0 + if rate > 0: + depart = max(depart, self.last_up + len(data) * 8.0 / rate) + q = depart - now - self.g('delay_ms') / 1000.0 + self.backlog_ms = q * 1000.0 + if q * 1000.0 > self.g('queue_ms', 300): + self.c['up_qdrop'] += 1; return + self.last_up = max(self.last_up, depart) + self.loop.call_at(depart, self._send_up, us, data) + if self.g('dup') > 0 and self.rng.random() < self.g('dup'): + self.c['up_dup'] += 1 + self.loop.call_at(depart + 0.001, self._send_up, us, data) + def _send_up(self, us, data): + try: + us.send(data); self.c['up_fwd'] += 1; self.c['up_bytes'] += len(data) + except OSError: + self.c['up_enobufs'] += 1 + # --- receiver -> sender --- + def down(self, us, src, listen_sock): + try: + while True: + data = us.recv(2048) + if not self.cfg.get('up', True): + self.c['down_dead'] += 1; continue + if self.rng.random() < self.g('loss_down'): + self.c['down_loss'] += 1; continue + now = self.loop.time() + depart = now + self.delay(True) + if not self.cfg.get('reorder', False): + depart = max(depart, self.last_down) + self.last_down = max(self.last_down, depart) + self.loop.call_at(depart, self._send_down, listen_sock, data, src) + except (BlockingIOError, InterruptedError): + pass + except OSError: + pass + def _send_down(self, listen_sock, data, src): + try: + listen_sock.sendto(data, src); self.c['down_fwd'] += 1; self.c['down_bytes'] += len(data) + except OSError: + self.c['down_enobufs'] += 1 + # --- log --- + def flush(self, t): + snap = dict(up=self.cfg.get('up', True), delay_ms=round(self.g('delay_ms'), 1), delay_down_ms=round(self.g('delay_down_ms', self.g('delay_ms')), 1), + loss=round(self.g('loss'), 4), loss_down=round(self.g('loss_down'), 4), rate_kbps=round(self.g('rate_kbps')), bad=self.bad) + self.log.write(json.dumps(dict(t=round(t, 1), link=self.ip, backlog_ms=round(self.backlog_ms, 1), **self.c, cfg=snap)) + '\n'); self.log.flush() + for k in self.c: self.c[k] = 0 + +async def main(cfg): + loop = asyncio.get_running_loop() + upstream = tuple(cfg['upstream']) + log = open(cfg.get('log', 'proxy.jsonl'), 'w') + ls = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); ls.setblocking(False) + ls.bind(tuple(cfg['listen'])); ls.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024) + seed = cfg.get('seed', 1) + links = {ip: Link(ip, lc, seed * 100 + i, loop, upstream, log) for i, (ip, lc) in enumerate(cfg['links'].items())} + unknown = set(); t_first = [None] + def event(ev): + lk = links[ev['link']] + if 'set' in ev: + lk.set(ev['set']) + log.write(json.dumps(dict(event=ev, t=round(loop.time() - t_first[0], 1))) + '\n'); log.flush() + print('proxy: t=%s link %s set %s' % (ev['t'], ev['link'], ev['set']), file=sys.stderr) + if 'ramp' in ev: + dur = float(ev.get('dur', 5)); steps = max(1, int(dur / 0.1)) + start = {k: lk.g(k, lk.g('delay_ms') if k == 'delay_down_ms' else 0.0) for k in ev['ramp']} + def step(i): + f = i / steps + lk.set({k: start[k] + (ev['ramp'][k] - start[k]) * f for k in ev['ramp']}) + if i < steps: loop.call_later(dur / steps, step, i + 1) + else: + log.write(json.dumps(dict(event=ev, done=True, t=round(loop.time() - t_first[0], 1))) + '\n'); log.flush() + print('proxy: t=%s link %s ramp %s over %ss' % (ev['t'], ev['link'], ev['ramp'], dur), file=sys.stderr) + step(1) + def on_listen(): + try: + while True: + data, src = ls.recvfrom(2048) + if t_first[0] is None: + t_first[0] = loop.time() + for ev in cfg.get('timeline', []): + loop.call_at(t_first[0] + ev['t'], event, ev) + lk = links.get(src[0]) + if lk is None: + if src[0] not in unknown: + unknown.add(src[0]); print('proxy: dropping packets from unconfigured link %s' % (src,), file=sys.stderr) + continue + lk.up(data, src, ls) + except (BlockingIOError, InterruptedError): + pass + loop.add_reader(ls, on_listen) + print('proxy: listening on %s -> %s, links %s' % (cfg['listen'], upstream, list(links)), file=sys.stderr) + t0 = loop.time() + while True: + await asyncio.sleep(1.0) + t = loop.time() - (t_first[0] if t_first[0] is not None else t0) + for lk in links.values(): lk.flush(t) + +if __name__ == '__main__': + cfg = json.load(open(sys.argv[1])) + try: + asyncio.run(main(cfg)) + except KeyboardInterrupt: + pass diff --git a/testing/srtla-e2e/receiver.cpp b/testing/srtla-e2e/receiver.cpp new file mode 100644 index 0000000000..e90916b201 --- /dev/null +++ b/testing/srtla-e2e/receiver.cpp @@ -0,0 +1,128 @@ +// Test receiver: SRTLA listener over the fork's libsrt (like srt-live-server's listener), +// verifies the payload stream (seq/timestamp) and logs SRT + SRTLA stats once a second. +#include "srt.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint64_t now_us() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); +} +static uint64_t mono_ms() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); +} +// fields that exist only on newer branches (06620ec+): detected at compile time +template auto rcv_quality(const T& p, int) -> decltype(p.pctRcvQuality) { return p.pctRcvQuality; } +template double rcv_quality(const T&, long) { return -1; } +template auto loss_confirmed(const T& p, int) -> decltype(p.pktRcvLossConfirmed) { return p.pktRcvLossConfirmed; } +template int loss_confirmed(const T&, long) { return -1; } + +int main(int argc, char** argv) { + if (argc < 2) { fprintf(stderr, "usage: receiver PORT [--latency MS] [--lossmaxttl N] [--duration S] [--stats FILE] [--srtla 0|1] [--kbps N] [--debug 0|1]\n"); return 2; } + int port = atoi(argv[1]); int latency = 2000, lossmaxttl = 50, duration = 40, srtla = 1, kbps = 8000, debug = 0; std::string stats_path; + for (int i = 2; i + 1 < argc; i += 2) { + std::string k = argv[i]; const char* v = argv[i+1]; + if (k == "--latency") latency = atoi(v); + else if (k == "--lossmaxttl") lossmaxttl = atoi(v); + else if (k == "--duration") duration = atoi(v); + else if (k == "--stats") stats_path = v; + else if (k == "--srtla") srtla = atoi(v); + else if (k == "--kbps") kbps = atoi(v); + else if (k == "--debug") debug = atoi(v); + } + FILE* sf = stats_path.empty() ? stdout : fopen(stats_path.c_str(), "w"); + srt_startup(); + srt_setloglevel(debug ? LOG_DEBUG : LOG_NOTICE); // notes such as the SRTLA latency raise; --debug 1 for heavy-logging builds + SRTSOCKET ls = srt_create_socket(); + int yes = 1, no = 0; + srt_setsockflag(ls, SRTO_LOSSMAXTTL, &lossmaxttl, sizeof lossmaxttl); + srt_setsockflag(ls, SRTO_LATENCY, &latency, sizeof latency); + // Receive buffer for bitrate x (latency + 1 s), so 5 s at 25 Mbit does not stall the sender's flow window. + int pkts = std::max(25600, (int)((double)kbps * 1000 / 8 / 1316 * (latency / 1000.0 + 1.0) * 1.5)); + int rcvbuf = pkts * 1456; + srt_setsockflag(ls, SRTO_FC, &pkts, sizeof pkts); + srt_setsockflag(ls, SRTO_RCVBUF, &rcvbuf, sizeof rcvbuf); + if (srtla && srt_setsockflag(ls, SRTO_SRTLA, &yes, sizeof yes) != 0) { fprintf(stderr, "SRTO_SRTLA failed: %s\n", srt_getlasterror_str()); return 1; } + srt_setsockflag(ls, SRTO_RCVSYN, &no, sizeof no); + sockaddr_in sa; memset(&sa, 0, sizeof sa); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = htonl(INADDR_ANY); + if (srt_bind(ls, (sockaddr*)&sa, sizeof sa) != 0) { fprintf(stderr, "bind failed: %s\n", srt_getlasterror_str()); return 1; } + if (srt_listen(ls, 1) != 0) { fprintf(stderr, "listen failed: %s\n", srt_getlasterror_str()); return 1; } + fprintf(stderr, "listening on %d (srtla=%d latency=%d lossmaxttl=%d fc=%d rcvbuf=%d)\n", port, srtla, latency, lossmaxttl, pkts, rcvbuf); + uint64_t deadline = mono_ms() + (uint64_t)duration * 1000; + SRTSOCKET s = SRT_INVALID_SOCK; + int eid = srt_epoll_create(); int ev = SRT_EPOLL_IN | SRT_EPOLL_ERR; srt_epoll_add_usock(eid, ls, &ev); + while (mono_ms() < deadline) { + SRTSOCKET rd[1]; int rn = 1; + if (srt_epoll_wait(eid, rd, &rn, NULL, NULL, 100, NULL, NULL, NULL, NULL) <= 0) continue; + sockaddr_storage peer; int plen = sizeof peer; + s = srt_accept(ls, (sockaddr*)&peer, &plen); + if (s != SRT_INVALID_SOCK) break; + } + srt_epoll_release(eid); + if (s == SRT_INVALID_SOCK) { fprintf(stderr, "no connection\n"); return 3; } + uint64_t t_conn = mono_ms(); + fprintf(stderr, "accepted\n"); + int to = 100; srt_setsockflag(s, SRTO_RCVTIMEO, &to, sizeof to); + int peer_lat = 0, l = sizeof peer_lat; srt_getsockflag(s, SRTO_RCVLATENCY, &peer_lat, &l); + fprintf(stderr, "effective rcv latency %d\n", peer_lat); + + char buf[1500]; + uint64_t next_stats = mono_ms() + 1000, t0 = now_us(); + // app-level payload accounting (per second + total) + long rcvd = 0, missing = 0, dups = 0, ooo = 0, tot_rcvd = 0, tot_missing = 0, tot_dups = 0, tot_ooo = 0, tot_bytes = 0, bytes = 0; + int64_t next_seq = -1; std::vector lat; lat.reserve(4000); + double lat_max_all = 0; long lat_over_latency = 0; + SRT_TRACEBSTATS last_tot; memset(&last_tot, 0, sizeof last_tot); + while (mono_ms() < deadline) { + int n = srt_recvmsg2(s, buf, sizeof buf, NULL); + if (n > 0) { + uint32_t magic, seq; uint64_t ts; memcpy(&magic, buf, 4); memcpy(&seq, buf + 4, 4); memcpy(&ts, buf + 8, 8); + if (n >= 16 && magic == 0x53525431) { + rcvd++; bytes += n; + double lms = (double)((int64_t)now_us() - (int64_t)ts) / 1000.0; lat.push_back(lms); if (lms > lat_max_all) lat_max_all = lms; + if (lms > latency + 500) lat_over_latency++; + if (next_seq < 0) next_seq = seq; + if ((int64_t)seq == next_seq) next_seq++; + else if ((int64_t)seq > next_seq) { missing += (int64_t)seq - next_seq; next_seq = seq + 1; } + else { // older than expected: either a dup or delivered late (TSBPD delivers in order, so treat as reorder) + ooo++; } + } + } else { + int e = srt_getlasterror(NULL); + if (e != SRT_EASYNCRCV && e != SRT_ETIMEOUT) { fprintf(stderr, "recv ended: %s\n", srt_getlasterror_str()); break; } + } + uint64_t m = mono_ms(); + if (m >= next_stats) { + next_stats += 1000; + SRT_TRACEBSTATS p; memset(&p, 0, sizeof p); srt_bstats(s, &p, 1); + { SRT_TRACEBSTATS c; if (srt_bstats(s, &c, 0) == 0) last_tot = c; } + std::sort(lat.begin(), lat.end()); + double lp50 = lat.empty() ? 0 : lat[lat.size() / 2], lp99 = lat.empty() ? 0 : lat[(size_t)(lat.size() * 0.99)], lmax = lat.empty() ? 0 : lat.back(), lmin = lat.empty() ? 0 : lat.front(); + SRT_SRTLA_STATS ss; memset(&ss, 0, sizeof ss); srt_srtla_stats(s, &ss); + fprintf(sf, "{\"t\":%.1f,\"mbpsRecvRate\":%.3f,\"pktRecv\":%lld,\"pktRecvUnique\":%lld,\"pktRcvLoss\":%d,\"pktRcvRetrans\":%d,\"pktRcvDrop\":%d,\"pktRcvBelated\":%lld,\"pktSentNAK\":%d,\"pktSentACK\":%d,\"msRTT\":%.1f,\"pktRcvBuf\":%d,\"msRcvBuf\":%d,\"pktRcvFilterSupply\":%d,\"pctRcvQuality\":%.2f,\"pktRcvLossConfirmed\":%d,\"app_rcvd\":%ld,\"app_missing\":%ld,\"app_ooo\":%ld,\"lat_min\":%.1f,\"lat_p50\":%.1f,\"lat_p99\":%.1f,\"lat_max\":%.1f,\"peers\":[", + (now_us() - t0) / 1e6, p.mbpsRecvRate, (long long)p.pktRecv, (long long)p.pktRecvUnique, p.pktRcvLoss, p.pktRcvRetrans, p.pktRcvDrop, (long long)p.pktRcvBelated, p.pktSentNAK, p.pktSentACK, p.msRTT, p.pktRcvBuf, p.msRcvBuf, p.pktRcvFilterSupply, rcv_quality(p, 0), loss_confirmed(p, 0), rcvd, missing, ooo, lmin, lp50, lp99, lmax); + for (int i = 0; i < ss.numPeers; i++) { + fprintf(sf, "%s{\"id\":%u,\"kbps\":%u,\"kbpsUnique\":%u,\"transitUs\":%d,\"jitterUs\":%u}", i ? "," : "", ss.peers[i].connectionId, ss.peers[i].kbpsRecvRate, ss.peers[i].kbpsRecvUnique, ss.peers[i].transitUs, ss.peers[i].usJitter); + } + fprintf(sf, "]}\n"); fflush(sf); + tot_rcvd += rcvd; tot_missing += missing; tot_dups += dups; tot_ooo += ooo; tot_bytes += bytes; rcvd = missing = dups = ooo = bytes = 0; lat.clear(); + } + } + SRT_TRACEBSTATS p = last_tot; { SRT_TRACEBSTATS c; if (srt_bstats(s, &c, 0) == 0) p = c; } + fprintf(sf, "{\"final\":1,\"app_rcvd\":%ld,\"app_missing\":%ld,\"app_ooo\":%ld,\"app_bytes\":%ld,\"lat_max_all\":%.1f,\"lat_over_latency\":%ld,\"pktRecvTotal\":%lld,\"pktRcvLossTotal\":%d,\"pktRcvDropTotal\":%d,\"pktSentNAKTotal\":%d,\"pktRcvUniqueTotal\":%lld,\"connected_ms\":%llu}\n", + tot_rcvd, tot_missing, tot_ooo, tot_bytes, lat_max_all, lat_over_latency, (long long)p.pktRecvTotal, p.pktRcvLossTotal, p.pktRcvDropTotal, p.pktSentNAKTotal, (long long)p.pktRecvUniqueTotal, (unsigned long long)(mono_ms() - t_conn)); + fflush(sf); + srt_close(s); srt_close(ls); srt_cleanup(); + return 0; +} diff --git a/testing/srtla-e2e/repeats_table.py b/testing/srtla-e2e/repeats_table.py new file mode 100755 index 0000000000..1cb49e3ef9 --- /dev/null +++ b/testing/srtla-e2e/repeats_table.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +"""Aggregate a scenario over several results dirs (different seeds): repeats_table.py OUT1,OUT2,... SCN1,SCN2 [VARIANTS]""" +import json, os, statistics, sys +ROOT = os.path.dirname(os.path.abspath(__file__)) +outs = sys.argv[1].split(','); scns = sys.argv[2].split(','); variants = sys.argv[3].split(',') if len(sys.argv) > 3 else None +for scn in scns: + print('\n### %s (%s)' % (scn, ', '.join(outs))) + print('%-8s%10s%8s%8s%9s%9s%9s' % ('variant', 'bs mean', 'NAKs', 'rexmit', 'belated', 'missing', 'lag p99')) + rows_by_v = {} + for o in outs: + s = json.load(open(os.path.join(ROOT, 'results', o, 'summary.json'))).get(scn, {}) + for v, m in s.items(): + if variants and v not in variants: continue + rows_by_v.setdefault(v, []).append(dict(bs=m['bs_mean'], nak=m['rcv_nak'], rex=m['snd_retrans'], bel=m['rcv_belated'], miss=m['app_missing'], lag=m.get('lag_p99', 0))) + for v, rows in rows_by_v.items(): + g = lambda k: statistics.mean(r[k] for r in rows) + print('%-8s%10.0f%8.0f%8.0f%9.0f%9.1f%9.0f per run bs: %s | lag p99: %s' % ( + v, g('bs'), g('nak'), g('rex'), g('bel'), g('miss'), g('lag'), ' '.join(str(r['bs']) for r in rows), ' '.join(str(r['lag']) for r in rows))) diff --git a/testing/srtla-e2e/run_matrix.py b/testing/srtla-e2e/run_matrix.py new file mode 100755 index 0000000000..07cd5c5065 --- /dev/null +++ b/testing/srtla-e2e/run_matrix.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +"""Run impairment scenarios against one or more receiver builds and summarise. + + run_matrix.py VARIANTS [SCENARIOS] [--seed N] [--out NAME] [--ips A,B,C] + +VARIANTS comma-separated receiver builds (build/receiver-, see build.sh) +SCENARIOS comma-separated keys of SCENARIOS below (default: all) +Results land in results//// plus results//summary.json. +Runs for one scenario are interleaved over the variants (base, head, base, head ...), so both see +the same host conditions. +""" +import argparse, json, os, re, signal, statistics, subprocess, sys, time + +ROOT = os.path.dirname(os.path.abspath(__file__)) +BUILD = os.path.join(ROOT, os.environ.get('BUILD_DIR', 'build')) +LAT = 2000 # SRT latency (ms) unless a scenario sets 'lat' +PORT_RCV, PORT_PROXY, PORT_SRT = 15000, 16000, 17000 # keep clear of test-srt (uses 5000+) + +def L(delay, jitter=0, loss=0.0, loss_down=0.0, rate=0, queue=300): + """One link: one-way delay/jitter (ms), uplink loss, downlink loss, rate cap (kbps), queue (ms).""" + return dict(delay_ms=delay, jitter_ms=jitter, loss=loss, loss_down=loss_down, rate_kbps=rate, queue_ms=queue) + +# Timeline events: t is seconds after the first packet; 'link' indexes 'links'. +SCENARIOS = { + # --- two/three links (also runnable natively on macOS) --- + 'clean2': dict(kbps=6000, dur=40, links=[L(20), L(40)], timeline=[]), + 'asym_rtt': dict(kbps=6000, dur=40, links=[L(20, 0, 0.01), L(150, 20, 0.01)], timeline=[]), + 'loss_both': dict(kbps=6000, dur=40, links=[L(30, 5, 0.03, 0.03), L(50, 5, 0.03, 0.03)], timeline=[]), + 'heavy_loss': dict(kbps=4000, dur=45, links=[L(30, 5, 0.08), L(60, 5, 0.08)], timeline=[]), + 'outage': dict(kbps=6000, dur=45, links=[L(20, 0, 0.01), L(40, 0, 0.01), L(60, 0, 0.01)], + timeline=[dict(t=10, link=2, set=dict(loss=1.0)), dict(t=18, link=2, set=dict(loss=0.01)), + dict(t=26, link=0, set=dict(loss=1.0)), dict(t=32, link=0, set=dict(loss=0.01))]), + 'thin_link': dict(kbps=8000, dur=40, links=[L(20), L(30), L(80, 5, 0.0, 0.0, 800, 250)], timeline=[]), + 'highrate': dict(kbps=16000, dur=40, links=[L(20, 0, 0.005), L(35, 0, 0.005)], timeline=[]), + 'jitter_reorder':dict(kbps=6000, dur=40, links=[L(40, 60, 0.005), L(40, 60, 0.005)], timeline=[]), + 'three_clean': dict(kbps=8000, dur=40, links=[L(20, 0, 0.005), L(40, 0, 0.005), L(80, 10, 0.005)], timeline=[]), + 'three_rev': dict(kbps=8000, dur=40, links=[L(20, 0, 0.01, 0.05), L(40, 0, 0.01, 0.05), L(80, 10, 0.01, 0.05)], timeline=[]), + 'three_outage_rev': dict(kbps=8000, dur=45, links=[L(20, 0, 0.01, 0.05), L(40, 0, 0.01, 0.05), L(80, 10, 0.01, 0.05)], + timeline=[dict(t=10, link=0, set=dict(loss=1.0, loss_down=1.0)), dict(t=20, link=0, set=dict(loss=0.01, loss_down=0.05))]), +} +# --- real-world catalogue, generated from templates ------------------------------------------- +# Every template takes the link list and returns a scenario, so each one can run with any number +# of links, any latency (SRTLA allows 1000 ms upwards; encoders default to 2000 or 3000) and either +# link set. Link roles: index 0 = fastest carrier, index -1 = slowest. +LATENCIES = (1000, 2000, 3000, 4000, 5000) + +def links_n(n, slow=False): + """n carriers with staggered one-way delays. Fast set: 25/40/55/70 ms (four decent carriers); + slow set: 100/120/140/160 ms (far gateways, RTTs of 200-320 ms). 0.5 % loss, a little jitter.""" + base, step = (100, 20) if slow else (25, 15) + return [L(base + step * i, 3 + min(5, i * 2), 0.005) for i in range(n)] +FOUR = links_n(4); FOUR_SLOW = links_n(4, slow=True) + +def outages(link, period, dur, t0, t_end): + """Periodic full outages of one link (handover, tunnel): down for dur every period seconds.""" + ev = []; t = t0 + while t + dur <= t_end: + ev.append(dict(t=t, link=link, set=dict(up=False))); ev.append(dict(t=t + dur, link=link, set=dict(up=True))); t += period + return ev + +def with_(links, idx, **o): + links = [dict(l) for l in links]; links[idx].update(o); return links + +def dur_for(lat, base): + """Longer runs at higher latency: the buffer takes latency to fill and drain.""" + return base + max(0, (lat - 2000) // 1000) * 3 + +TEMPLATES = { + # four healthy modems + 'baseline': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 40), links=ls, timeline=[]), + # one carrier with a far higher base RTT (bad carrier, distant gateway): must not be starved nor poison the hold + 'one_slow': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 40), links=with_(ls, -1, delay_ms=ls[-1]['delay_ms'] + 200, jitter_ms=20, loss=0.01), timeline=[]), + 'two_slow': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 40), links=with_(with_(ls, -2, delay_ms=ls[-2]['delay_ms'] + 160, jitter_ms=20), -1, delay_ms=ls[-1]['delay_ms'] + 240, jitter_ms=30), timeline=[]), + # bufferbloat: one link's delay ramps +560 ms over 8 s, holds, drains back (FIFO) + 'bloat': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 60), links=ls, timeline=[dict(t=10, link=1, ramp=dict(delay_ms=ls[1]['delay_ms'] + 560), dur=8), dict(t=30, link=1, ramp=dict(delay_ms=ls[1]['delay_ms']), dur=8)]), + # bursty (Gilbert-Elliott) loss on two links: ~10-packet bursts, a few per second + 'bursty': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 45), links=with_(with_(ls, 1, burst=dict(p_bad=0.004, p_good=0.1, loss_bad=0.6)), -1, burst=dict(p_bad=0.004, p_good=0.1, loss_bad=0.6)), timeline=[]), + # one modem flaps: 4 s down / 6 s up (loose SIM, marginal coverage) + 'flapping': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 60), links=ls, timeline=outages(len(ls) // 2, 10, 4, 8, 58)), + # handover / short tunnel: 3 s outage every 15 s on the fastest link + 'handover': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 60), links=ls, timeline=outages(0, 15, 3, 10, 58)), + # a modem that boots late: the slowest link joins at t=15 (srtla_send re-registers it once the path opens) + 'join_late': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 50), links=with_(ls, -1, up=False), timeline=[dict(t=15, link=len(ls) - 1, set=dict(up=True))]), + # uplink fast but downlink +90 ms on every link (LTE uplink scheduling): the NAK/ACK path is the slow one + 'asym_dir': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 40), links=[dict(l, delay_down_ms=l['delay_ms'] + 90, loss=0.01) for l in ls], timeline=[]), + # HARQ-style reordering inside every link (non-FIFO jitter) + 'reorder': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 40), links=[dict(l, jitter_ms=15, reorder=True) for l in ls], timeline=[]), + # duplicated packets on one link + 'dup': lambda ls, lat: dict(kbps=8000, dur=dur_for(lat, 40), links=with_(ls, 1, dup=0.02), timeline=[]), + # capacity: every link rate-capped, the bond shrinks below the bitrate for 15 s (queues overflow; + # the encoder-side controller that would back off is not part of this harness) + 'capacity': lambda ls, lat: dict(kbps=2000, dur=dur_for(lat, 70), links=[dict(l, rate_kbps=3000, queue_ms=300) for l in ls], + timeline=[dict(t=30, link=i, set=dict(rate_kbps=1600 // len(ls))) for i in range(len(ls))] + [dict(t=45, link=i, set=dict(rate_kbps=3000)) for i in range(len(ls))]), + # dead zone: all links get 20 % loss and +300 ms for 15 s, then recover + 'dead_zone': lambda ls, lat: dict(kbps=2000, dur=dur_for(lat, 70), links=ls, + timeline=[dict(t=30, link=i, set=dict(loss=0.2, delay_ms=ls[i]['delay_ms'] + 300)) for i in range(len(ls))] + [dict(t=45, link=i, set=dict(loss=0.005, delay_ms=ls[i]['delay_ms'])) for i in range(len(ls))]), + 'highrate': lambda ls, lat: dict(kbps=25000, dur=dur_for(lat, 40), links=ls, timeline=[]), + 'lowrate': lambda ls, lat: dict(kbps=400, dur=dur_for(lat, 45), links=with_(with_(ls, 0, loss=0.02), 1, loss=0.02), timeline=[]), +} + +def make(name, links, lat): + scn = TEMPLATES[name](links, lat); scn['lat'] = lat; return scn + +SETS = {'rw': [], 'rw-lat': [], 'rw-slow': [], 'rw-links': [], 'rw-extra': []} +for _n in TEMPLATES: + SCENARIOS['rw_' + _n] = make(_n, FOUR, 2000); SETS['rw'].append('rw_' + _n) # the reference catalogue + for _lat in LATENCIES: + if _lat != 2000: + SCENARIOS['rw_%s_lat%d' % (_n, _lat)] = make(_n, FOUR, _lat); SETS['rw-lat'].append('rw_%s_lat%d' % (_n, _lat)) + SCENARIOS['rw_%s_slow' % _n] = make(_n, FOUR_SLOW, 2000); SETS['rw-slow'].append('rw_%s_slow' % _n) + for _k in (2, 3, 5, 6, 7, 8): + SCENARIOS['rw_%s_links%d' % (_n, _k)] = make(_n, links_n(_k), 2000); SETS['rw-links'].append('rw_%s_links%d' % (_n, _k)) +# beyond the reorder budget: one link 1.1 s / 2 s late, a bloat ramp to 2 s, and the slow link listed +# first (srtla_send prepends its list, so the handshake takes the LAST listed link: compare the +# end-to-end latency with rw_one_slow to see whose arrival set the TSBPD time base) +SCENARIOS['rw_one_slow_1100'] = dict(make('baseline', with_(FOUR, -1, delay_ms=1100, jitter_ms=50), 2000), dur=45) +SCENARIOS['rw_one_slow_2000'] = dict(make('baseline', with_(FOUR, -1, delay_ms=2000, jitter_ms=50), 2000), dur=45) +SCENARIOS['rw_bloat_2000'] = dict(kbps=8000, dur=75, lat=2000, links=FOUR, timeline=[dict(t=10, link=1, ramp=dict(delay_ms=2000), dur=10), dict(t=40, link=1, ramp=dict(delay_ms=40), dur=10)]) +SCENARIOS['rw_one_slow_first'] = make('baseline', with_(FOUR, 0, delay_ms=250, jitter_ms=20, loss=0.01), 2000) +SETS['rw-extra'] = ['rw_one_slow_1100', 'rw_one_slow_2000', 'rw_bloat_2000', 'rw_one_slow_first'] +# protocol rule: SRTLA never runs below 1000 ms. Both sides ask for 500 ms; the receiver must raise +# to 1000 and negotiate that to the sender (score.py treats anything below 1000 as a violation). +SCENARIOS['rw_floor'] = make('baseline', FOUR, 500) +SCENARIOS['rw_soak'] = dict(kbps=8000, dur=300, lat=2000, links=with_(FOUR, -1, delay_ms=200, jitter_ms=20, burst=dict(p_bad=0.002, p_good=0.1, loss_bad=0.5)), + timeline=outages(2, 40, 4, 20, 290) + [dict(t=100, link=1, ramp=dict(delay_ms=500), dur=10), dict(t=130, link=1, ramp=dict(delay_ms=40), dur=10)]) +# the pull-request set: the common configuration (four decent carriers, 2000 ms) plus the extremes +# of each dimension, on the whole catalogue: 5000 ms latency, 2 and 8 links, slow carriers. What +# holds at both ends is assumed to hold in between; the other latencies and link counts stay in +# 'rw-lat' / 'rw-links' / 'full' for manual runs. +SETS['ci'] = (SETS['rw'] + ['rw_%s_lat5000' % n for n in TEMPLATES] + ['rw_%s_links2' % n for n in TEMPLATES] + + ['rw_%s_links8' % n for n in TEMPLATES] + ['rw_%s_slow' % n for n in TEMPLATES] + ['rw_floor']) +SETS['full'] = [k for k in SCENARIOS if k != 'rw_soak'] + +# --- latency sweep on two links (runs natively on macOS): 30/60 ms, 2 % loss up, 1 % down --- +# 500 ms is below the SRTLA minimum on purpose: it shows the raise to 1000 ms. +for _kbps in (6000, 500): + for _lat in (500,) + LATENCIES: + SCENARIOS['lat%d_%d' % (_lat, _kbps)] = dict(kbps=_kbps, dur=dur_for(_lat, 40), lat=_lat, links=[L(30, 5, 0.02, 0.01), L(60, 5, 0.02, 0.01)], timeline=[]) +SETS['lat'] = [k for k in SCENARIOS if k.startswith('lat')] +# plain SRT control (no SRTLA at all): one link, the receiver is a normal SRT listener. The branch must +# not change plain SRT, so base and head have to come out identical here. +SCENARIOS['plain_srt'] = dict(kbps=6000, dur=40, lat=2000, plain=True, links=[L(30, 5, 0.02, 0.01)], timeline=[]) +SETS['ci'].append('plain_srt') +# --- loss-focused set: the scenarios that separate algorithms (the clean ones any algorithm passes) --- +def all_links(ls, **o): return [dict(l, **o) for l in ls] +SCENARIOS.update({ + 'loss_dead_zone': SCENARIOS['rw_dead_zone'], # 20 % + 300 ms on all, 15 s + 'loss_dead_zone10': dict(kbps=2000, dur=70, lat=2000, links=FOUR, + timeline=[dict(t=30, link=i, set=dict(loss=0.10, delay_ms=FOUR[i]['delay_ms'] + 150)) for i in range(4)] + [dict(t=45, link=i, set=dict(loss=0.005, delay_ms=FOUR[i]['delay_ms'])) for i in range(4)]), + 'loss_all8': dict(kbps=4000, dur=45, lat=2000, links=all_links(FOUR, loss=0.08), timeline=[]), # 8 % everywhere, sustained + 'loss_two15': dict(kbps=6000, dur=45, lat=2000, links=with_(with_(FOUR, 1, loss=0.15), 3, loss=0.15), timeline=[]), + 'loss_bursty_all': dict(kbps=6000, dur=45, lat=2000, links=all_links(FOUR, burst=dict(p_bad=0.006, p_good=0.08, loss_bad=0.7)), timeline=[]), + 'loss_handover': dict(kbps=6000, dur=60, lat=2000, links=all_links(FOUR, loss=0.03), timeline=outages(0, 15, 3, 10, 58)), + 'loss_capacity': SCENARIOS['rw_capacity'], + 'loss_one_slow': dict(kbps=6000, dur=45, lat=2000, links=with_(all_links(FOUR, loss=0.03), -1, delay_ms=250, jitter_ms=20, loss=0.05), timeline=[]), + 'loss_lat1000': dict(kbps=6000, dur=45, lat=1000, links=all_links(FOUR, loss=0.04), timeline=[]), # the floor: least budget +}) +SETS['loss'] = [k for k in SCENARIOS if k.startswith('loss_')] +SETS['full'] = [k for k in SCENARIOS if k != 'rw_soak'] + +def local_ips(): + """Usable srtla_send bind addresses. Linux routes all of 127.0.0.0/8 on lo, so eight + loopback addresses are always there; elsewhere every IPv4 of the host except link-local.""" + if sys.platform.startswith('linux'): + return ['127.0.0.%d' % i for i in range(1, 9)] + try: + out = subprocess.run(['ifconfig'], capture_output=True, text=True).stdout + except FileNotFoundError: + out = subprocess.run(['ip', '-4', '-o', 'addr'], capture_output=True, text=True).stdout + ips = [] + for ip in re.findall(r'inet (?:addr:)?(\d+\.\d+\.\d+\.\d+)', out): + if ip.startswith('169.254.') or ip in ips: + continue + ips.append(ip) + return ['127.0.0.1'] + [ip for ip in ips if ip != '127.0.0.1'] + +def run(scn, name, variant, out, seed, ips): + d = os.path.join(out, name, variant); os.makedirs(d, exist_ok=True) + if len(ips) < len(scn['links']): + sys.exit('scenario %s needs %d links but only %d local IPs are usable: %s' % (name, len(scn['links']), len(ips), ips)) + ips = ips[:len(scn['links'])] + open(os.path.join(d, 'ips.txt'), 'w').write('\n'.join(ips) + '\n') + cfg = dict(listen=['0.0.0.0', PORT_PROXY], upstream=['127.0.0.1', PORT_RCV], seed=seed, log=os.path.join(d, 'proxy.jsonl'), + links={ip: lc for ip, lc in zip(ips, scn['links'])}, + timeline=[dict(e, link=ips[e['link']]) for e in scn['timeline']]) # link index -> bind IP + json.dump(cfg, open(os.path.join(d, 'proxy.json'), 'w'), indent=1) + dur, lat = scn['dur'], scn.get('lat', LAT) + procs = [] + def start(args, err): + p = subprocess.Popen(args, stderr=open(os.path.join(d, err), 'w'), stdout=subprocess.DEVNULL); procs.append(p); return p + plain = scn.get('plain', False) + rcv = start([os.path.join(BUILD, 'receiver-' + variant), str(PORT_RCV), '--latency', str(lat), '--lossmaxttl', '50', '--kbps', str(scn['kbps']), + '--srtla', '0' if plain else '1', '--duration', str(dur + 25), '--stats', os.path.join(d, 'rcv.jsonl')], 'rcv.err') + prx = start([sys.executable, os.path.join(ROOT, 'proxy.py'), os.path.join(d, 'proxy.json')], 'proxy.err') + time.sleep(0.5) + if plain: + ss = None; open(os.path.join(d, 'srtla_send.err'), 'w').write('plain SRT: no srtla_send\n') + else: + ss = start([os.path.join(BUILD, 'srtla_send'), str(PORT_SRT), '127.0.0.1', str(PORT_PROXY), os.path.join(d, 'ips.txt')], 'srtla_send.err') + time.sleep(1.5) + snd = start([os.path.join(BUILD, 'sender'), '127.0.0.1', str(PORT_PROXY if plain else PORT_SRT), '--bitrate', str(scn['kbps']), '--duration', str(dur), + '--latency', str(lat), '--stats', os.path.join(d, 'snd.jsonl'), '--bstrace', os.path.join(d, 'snd.bstrace')], 'snd.err') + try: + snd.wait(timeout=dur + 30 + lat // 1000) + except subprocess.TimeoutExpired: + print(' sender timeout', file=sys.stderr) + time.sleep(1.0) + for p in (ss, prx): + if p is not None: p.send_signal(signal.SIGTERM) + try: rcv.wait(timeout=10) + except subprocess.TimeoutExpired: rcv.kill() + for p in procs: + try: p.wait(timeout=3) + except subprocess.TimeoutExpired: p.kill() + time.sleep(1.0) # let the ports drain before the next run + return d + +def jl(path): + try: return [json.loads(l) for l in open(path) if l.strip()] + except FileNotFoundError: return [] + +def lag_percentiles(d, pps): + """ACK lag (age of the oldest unacknowledged packet) from the sender's 20 ms buffer trace, in ms.""" + try: + rows = [tuple(map(float, l.split())) for l in open(os.path.join(d, 'snd.bstrace')) if l.strip()] + except FileNotFoundError: + return {} + lag = sorted(bs / pps * 1000.0 for t, bs, fl, rx in rows if t > 3.0 and pps > 0) + if not lag: return {} + q = lambda p: round(lag[min(len(lag) - 1, int(p * len(lag)))]) + return dict(lag_p50=q(0.5), lag_p90=q(0.9), lag_p99=q(0.99), lag_max=round(lag[-1])) + +def summarise(d, scn=None): + snd = jl(os.path.join(d, 'snd.jsonl')); rcv = jl(os.path.join(d, 'rcv.jsonl')); prx = jl(os.path.join(d, 'proxy.jsonl')) + s_rows = [r for r in snd if 'final' not in r]; r_rows = [r for r in rcv if 'final' not in r] + r_fin = next((r for r in rcv if 'final' in r), {}) + mean = lambda xs: statistics.mean(xs) if xs else 0 + m = dict( + snd_sent=sum(r['pktSent'] for r in s_rows), snd_retrans=sum(r['pktRetrans'] for r in s_rows), snd_nak=sum(r['pktRecvNAK'] for r in s_rows), + snd_drop=sum(r['pktSndDrop'] for r in s_rows), app_drop=sum(r['app_drop'] for r in s_rows), + bs_mean=round(mean([r['bs'] for r in s_rows])), bs_max=max((r['bs'] for r in s_rows), default=0), + rtt_max=max((r['msRTT'] for r in s_rows), default=0), rtt_mean=round(mean([r['msRTT'] for r in s_rows]), 1), + app_rcvd=r_fin.get('app_rcvd', 0), app_missing=r_fin.get('app_missing', 0), rcv_drop=sum(r['pktRcvDrop'] for r in r_rows), + rcv_nak=sum(r['pktSentNAK'] for r in r_rows), rcv_retrans=sum(r['pktRcvRetrans'] for r in r_rows), + rcv_belated=sum(r['pktRcvBelated'] for r in r_rows), rcv_loss_confirmed=sum(max(r['pktRcvLossConfirmed'], 0) for r in r_rows), + lat_p99_max=max((r['lat_p99'] for r in r_rows), default=0), lat_max=r_fin.get('lat_max_all', 0), + lat_p50_median=statistics.median([r['lat_p50'] for r in r_rows if r['lat_p50'] > 0]) if any(r['lat_p50'] > 0 for r in r_rows) else 0, + mbps_mean=round(mean([r['mbpsRecvRate'] for r in r_rows]), 2), + ) + for fn, key, pat in (('snd.err', 'lat_negotiated', r'Negotiated latency: (\d+)'), ('rcv.err', 'lat_rcv_effective', r'effective rcv latency (\d+)')): + try: + mm = re.search(pat, open(os.path.join(d, fn)).read()); m[key] = int(mm.group(1)) if mm else -1 + except FileNotFoundError: + m[key] = -1 + # per link: uplink packets forwarded (= share) and packets the proxy took away (the injected loss) + up, lost, ticks = {}, 0, [] + for r in prx: + if 'link' not in r: continue + up[r['link']] = up.get(r['link'], 0) + r['up_fwd'] + lost += r['up_loss'] + r.get('up_burst', 0) + r.get('up_dead', 0) + r['up_qdrop'] + if r['link'] == min(up): ticks.append(r['t']) + tot = sum(up.values()) or 1 + m['share'] = {ip: round(100 * n / tot, 1) for ip, n in up.items()} + m['proxy_lost'] = lost + # a host stall shows as a late proxy tick (they are 1.0 s apart); such a run is not a receiver result + m['stall_max_ms'] = round(max((b - a for a, b in zip(ticks, ticks[1:])), default=1.0) * 1000 - 1000) + pps = m['snd_sent'] / max(1, len(s_rows)) + m.update(lag_percentiles(d, pps)) + if scn is not None: + m['kbps'] = scn['kbps']; m['lat'] = scn.get('lat', LAT) + m['min_delay_ms'] = min(l['delay_ms'] for l in scn['links']) + m['healthy_links'] = sum(1 for l in scn['links'] if l.get('up', True) and l['delay_ms'] <= 500) + return m + +if __name__ == '__main__': + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('variants'); ap.add_argument('scenarios', nargs='?', default='all', help="comma list, or a set: rw (catalogue, 4 links, 2000 ms), rw-lat, rw-slow, rw-links, rw-extra, loss (lossy scenarios), ci (pull-request set), full, all") + ap.add_argument('--group', help='k/n: run only every n-th scenario of the set, starting at k (parallel CI jobs)') + ap.add_argument('--seed', type=int, default=1, help='proxy RNG seed (loss pattern)') + ap.add_argument('--out', default='default', help='results//') + ap.add_argument('--ips', help='comma-separated local bind IPs for srtla_send (default: auto-detect)') + a = ap.parse_args() + ips = a.ips.split(',') if a.ips else local_ips() + out = os.path.join(ROOT, 'results', a.out); os.makedirs(out, exist_ok=True) + summary_path = os.path.join(out, 'summary.json') + results = json.load(open(summary_path)) if os.path.exists(summary_path) else {} + print('links available:', ips, file=sys.stderr) + names = [] + for tok in a.scenarios.split(','): # each token is a set name or a scenario name + for k in (SETS[tok] if tok in SETS else [k for k in SCENARIOS if k != 'rw_soak'] if tok == 'all' else [tok]): + if k not in names: names.append(k) + if a.group: + k, n = map(int, a.group.split('/')); names = names[k - 1::n] # deterministic split for parallel CI jobs + for name in names: + scn = SCENARIOS[name] + for v in a.variants.split(','): + t0 = time.time(); print('== %s / %s' % (name, v), file=sys.stderr, flush=True) + d = run(scn, name, v, out, a.seed, ips) + m = summarise(d, scn); results.setdefault(name, {})[v] = m + print(' %.0fs missing %d rcvdrop %d snddrop %d/%d nak %d retrans %d belated %d lag p99 %s stall %d share %s' % ( + time.time() - t0, m['app_missing'], m['rcv_drop'], m['snd_drop'], m['app_drop'], m['rcv_nak'], m['snd_retrans'], m['rcv_belated'], + m.get('lag_p99', '-'), m['stall_max_ms'], m['share']), file=sys.stderr, flush=True) + json.dump(results, open(summary_path, 'w'), indent=1) diff --git a/testing/srtla-e2e/score.py b/testing/srtla-e2e/score.py new file mode 100755 index 0000000000..00569c0e2e --- /dev/null +++ b/testing/srtla-e2e/score.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Score a results directory: how well did the receiver do, and did a change make it better or worse? + + score.py OUT[,OUT2,...] --base BASE --head HEAD [--markdown FILE] [--json FILE] [--fail-on-regression] + +Several results directories (runs with different --seed) are aggregated: sub-scores are averaged, +lost packets are summed. + +Per scenario and variant, five sub-scores in 0..100 (higher is better): + delivery 100 * exp(-lost permille / 5): what the application actually lost; 1 permille = 82, 5 = 37, 25 = 1 + efficiency 100 * (1 - duplicate share of retransmissions): a duplicate is a request the sender paid for twice + bond min share of a healthy link against half its fair share: does every usable link stay in the bond + recovery 1 - (ACK-lag p99 / half the latency): how fast the oldest hole gets closed, relative to the budget + latency 1 - (avoidable end-to-end latency / 500 ms): delivery later than the fastest link allows +Weights 0.40 / 0.20 / 0.15 / 0.15 / 0.10. A run whose host stalled (> 1.5 s gap) is excluded. +Total per variant = mean over scenarios. Verdict: regression if the total drops by more than 3, any +scenario by more than 10, the application lost 3+ packets where the base lost none, it lost more +than twice what the base lost (plus 5), or a receiver ran below the SRTLA minimum latency of 1000 ms. +""" +import argparse, json, math, os, sys +ROOT = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, ROOT) +from run_matrix import lag_percentiles +WEIGHTS = dict(delivery=0.40, efficiency=0.20, bond=0.15, recovery=0.15, latency=0.10) +STALL_LIMIT_MS = 1500 + +def clamp(x, lo=0.0, hi=1.0): return max(lo, min(hi, x)) + +def links_of(out, scn, variant): + """(ips, scenario link configs) for a run, from its proxy.json.""" + try: + cfg = json.load(open(os.path.join(ROOT, 'results', out, scn, variant, 'proxy.json'))) + except FileNotFoundError: + return [], {} + return list(cfg['links']), cfg['links'] + +def subscores(m, out, scn, variant): + sent = max(1, m.get('snd_sent', 0) or m.get('app_rcvd', 0) + m.get('app_missing', 0)) + sc = {} + sc['delivery'] = 100.0 * math.exp(-1000.0 * m.get('app_missing', 0) / sent / 5.0) # 1 permille = 82, 5 = 37, 25 = 1 + rex = max(1, m.get('snd_retrans', 0)) + sc['efficiency'] = 100.0 * clamp(1.0 - m.get('rcv_belated', 0) / rex) + ips, links = links_of(out, scn, variant) + if ips: + healthy = [ip for ip in ips if links[ip].get('up', True) and links[ip].get('delay_ms', 0) <= 500] + if healthy: + fair = 100.0 / len(ips) + sc['bond'] = 100.0 * clamp(min(m['share'].get(ip, 0.0) for ip in healthy) / (0.5 * fair)) + lat = m.get('lat', m.get('lat_negotiated', 0)) or 0 + if m.get('lag_p99') is None: # older summaries: derive from the trace file + d = os.path.join(ROOT, 'results', out, scn, variant) + m.update(lag_percentiles(d, max(1, m.get('snd_sent', 0)) / max(1, m.get('duration_s', 40)))) + if m.get('lag_p99') is not None and lat > 0: + sc['recovery'] = 100.0 * clamp(1.0 - m['lag_p99'] / (0.5 * lat)) + if m.get('lat_p50_median', 0) > 0 and lat > 0 and ips: + min_delay = min(links[ip].get('delay_ms', 0) for ip in ips) + extra = m['lat_p50_median'] - lat - min_delay + sc['latency'] = 100.0 * clamp(1.0 - max(0.0, extra) / 500.0) + return sc + +def total(sc): + w = sum(WEIGHTS[k] for k in sc) + return sum(WEIGHTS[k] * v for k, v in sc.items()) / w if w else 0.0 + +def main(): + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('out'); ap.add_argument('--base', required=True); ap.add_argument('--head', required=True) + ap.add_argument('--markdown'); ap.add_argument('--json'); ap.add_argument('--fail-on-regression', action='store_true') + ap.add_argument('--title', default='SRTLA end-to-end score') + a = ap.parse_args() + outs = a.out.split(',') + rows, notes, excluded = [], [], [] + scns = [] + for out in outs: + for scn in json.load(open(os.path.join(ROOT, 'results', out, 'summary.json'))): + if scn not in scns: scns.append(scn) + for scn in scns: + agg = {} + for out in outs: + res = json.load(open(os.path.join(ROOT, 'results', out, 'summary.json'))) + for v in (a.base, a.head): + m = res.get(scn, {}).get(v) + if not m: continue + if m.get('stall_max_ms', 0) > STALL_LIMIT_MS: + excluded.append('%s/%s/%s (host stall %d ms)' % (out, scn, v, m['stall_max_ms'])); continue + sc = subscores(m, out, scn, v) + e = agg.setdefault(v, dict(sub={}, n=0, missing=0, snddrop=0, nak=0, rexmit=0, belated=0, lat_rcv_min=10**9)) + e['lat_rcv_min'] = min(e['lat_rcv_min'], m.get('lat_rcv_effective', 10**9) if m.get('lat_rcv_effective', -1) > 0 else 10**9) + for k, x in sc.items(): e['sub'][k] = e['sub'].get(k, 0.0) + x + e['n'] += 1; e['missing'] += m.get('app_missing', 0); e['snddrop'] += m.get('snd_drop', 0) + e['nak'] += m.get('rcv_nak', 0); e['rexmit'] += m.get('snd_retrans', 0); e['belated'] += m.get('rcv_belated', 0) + pair = {} + for v, e in agg.items(): + sub = {k: x / e['n'] for k, x in e['sub'].items()} + pair[v] = dict(sub=sub, total=total(sub), runs=e['n'], missing=e['missing'], snddrop=e['snddrop'], nak=e['nak'], rexmit=e['rexmit'], belated=e['belated'], lat_rcv_min=e['lat_rcv_min']) + if a.base in pair and a.head in pair: + rows.append((scn, pair[a.base], pair[a.head])) + if not rows: + print('no comparable runs', file=sys.stderr); sys.exit(2) + tb = sum(r[1]['total'] for r in rows) / len(rows); th = sum(r[2]['total'] for r in rows) / len(rows); delta = th - tb + worst = min(rows, key=lambda r: r[2]['total'] - r[1]['total']) + new_loss = [r[0] for r in rows if (r[2]['missing'] >= 3 and r[1]['missing'] == 0) or r[2]['missing'] > 2 * r[1]['missing'] + 5] + violations = [r[0] for r in rows if r[2].get('lat_rcv_min', 1000) < 1000] # SRTLA protocol minimum latency + regression = delta < -3.0 or (worst[2]['total'] - worst[1]['total']) < -10.0 or bool(new_loss) or bool(violations) + verdict = 'regression' if regression else 'improvement' if delta > 3.0 else 'neutral' + icon = {'regression': ':red_circle:', 'improvement': ':green_circle:', 'neutral': ':white_circle:'}[verdict] + + md = ['### %s: %s **%.1f** (base %.1f, %+.1f) - %s' % (a.title, icon, th, tb, delta, verdict), '', + '| scenario | base | head | delta | delivery | efficiency | bond | recovery | latency | missing | rexmit / dup |', '|---|---|---|---|---|---|---|---|---|---|---|'] + for scn, b, h in rows: + d = h['total'] - b['total'] + cell = lambda k: ('%.0f' % h['sub'][k]) + ((' (%+.0f)' % (h['sub'][k] - b['sub'][k])) if k in b['sub'] and abs(h['sub'][k] - b['sub'][k]) >= 1 else '') if k in h['sub'] else '-' + flag = ' :warning:' if d < -10 or scn in new_loss else '' + md.append('| %s%s | %.1f | %.1f | %+.1f | %s | %s | %s | %s | %s | %d / %d | %d / %d |' % ( + scn, flag, b['total'], h['total'], d, cell('delivery'), cell('efficiency'), cell('bond'), cell('recovery'), cell('latency'), + b['missing'], h['missing'], h['rexmit'], h['belated'])) + md += ['', 'Sub-scores are the head values, with the change against the base in brackets. %d scenario(s), %d run(s) each.' % (len(rows), rows[0][2]['runs'])] + if new_loss: md.append('**Application-level loss beyond the base:** ' + ', '.join(new_loss)) + if violations: md.append('**Protocol violation, receiver latency below 1000 ms under SRTLA:** ' + ', '.join(violations)) + if excluded: md.append('Excluded (host stall during the run): ' + ', '.join(excluded)) + text = '\n'.join(md) + print(text) + if a.markdown: open(a.markdown, 'w').write(text + '\n') + if a.json: + json.dump(dict(base=a.base, head=a.head, total_base=tb, total_head=th, delta=delta, verdict=verdict, new_loss=new_loss, excluded=excluded, + scenarios={scn: dict(base=b, head=h) for scn, b, h in rows}), open(a.json, 'w'), indent=1) + if a.fail_on_regression and regression: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/testing/srtla-e2e/sender.cpp b/testing/srtla-e2e/sender.cpp new file mode 100644 index 0000000000..841b7db8fc --- /dev/null +++ b/testing/srtla-e2e/sender.cpp @@ -0,0 +1,120 @@ +// Test sender for the SRTLA end-to-end harness. Sends a paced constant-bitrate payload +// stream (sequence number + timestamp per packet) through the BELABOX libsrt fork (BELABOX/srt), +// configured like a bonded live encoder (SRTO_MAXBW 0, SRTO_OHEADBW 20, SRTO_RETRANSMITALGO 1). +// It reports the unacknowledged send buffer (SRTO_SNDDATA) once a second, the signal an +// adaptive-bitrate controller on the encoder side reacts to. +#include "srt.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint64_t now_us() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); +} +static uint64_t mono_ms() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +static SRTSOCKET sock; +static int srt_latency = 2000; +static int max_bitrate = 6000 * 1000; +static int cur_bitrate; +static const int BITRATE_UPDATE_INT = 20; // ms between stats polls (an encoder-side controller polls at this rate) + +int main(int argc, char** argv) { + if (argc < 3) { fprintf(stderr, "usage: sender HOST PORT [--bitrate KBPS] [--duration S] [--latency MS] [--stats FILE] [--bstrace FILE]\n"); return 2; } + const char* host = argv[1]; const char* port = argv[2]; + int duration = 30; std::string stats_path, trace_path; + for (int i = 3; i + 1 < argc; i += 2) { + std::string k = argv[i]; const char* v = argv[i+1]; + if (k == "--bitrate") max_bitrate = atoi(v) * 1000; + else if (k == "--duration") duration = atoi(v); + else if (k == "--latency") srt_latency = atoi(v); + else if (k == "--stats") stats_path = v; + else if (k == "--bstrace") trace_path = v; + } + cur_bitrate = max_bitrate; + FILE* sf = stats_path.empty() ? stdout : fopen(stats_path.c_str(), "w"); + FILE* tf = trace_path.empty() ? NULL : fopen(trace_path.c_str(), "w"); + + srt_startup(); + struct addrinfo hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; + struct addrinfo* addrs; if (getaddrinfo(host, port, &hints, &addrs) != 0) { fprintf(stderr, "resolve failed\n"); return 1; } + sock = srt_create_socket(); + int64_t max_bw = 0; srt_setsockflag(sock, SRTO_MAXBW, &max_bw, sizeof max_bw); + int32_t ohead = 20; srt_setsockflag(sock, SRTO_OHEADBW, &ohead, sizeof ohead); + srt_setsockflag(sock, SRTO_LATENCY, &srt_latency, sizeof srt_latency); + int32_t algo = 1; srt_setsockflag(sock, SRTO_RETRANSMITALGO, &algo, sizeof algo); + // Send buffer for bitrate x (latency + 1 s): packets stay until acknowledged, so 5 s at 25 Mbit + // needs far more than the default 8192. + int pkts = std::max(25600, (int)((double)max_bitrate / 8 / 1316 * (srt_latency / 1000.0 + 1.0) * 1.5)); + int sndbuf = pkts * 1456; + srt_setsockflag(sock, SRTO_FC, &pkts, sizeof pkts); + srt_setsockflag(sock, SRTO_SNDBUF, &sndbuf, sizeof sndbuf); + const char* sid = "test"; srt_setsockflag(sock, SRTO_STREAMID, sid, strlen(sid)); + int conn_to = 10000; srt_setsockflag(sock, SRTO_CONNTIMEO, &conn_to, sizeof conn_to); + + uint64_t t_connect0 = mono_ms(); + if (srt_connect(sock, addrs->ai_addr, addrs->ai_addrlen) != 0) { fprintf(stderr, "connect failed: %s\n", srt_getlasterror_str()); return 1; } + int len = sizeof srt_latency; srt_getsockflag(sock, SRTO_PEERLATENCY, &srt_latency, &len); + fprintf(stderr, "SRT connected after %llu ms. Negotiated latency: %d ms\n", (unsigned long long)(mono_ms() - t_connect0), srt_latency); + bool nb = false; srt_setsockflag(sock, SRTO_SNDSYN, &nb, sizeof nb); + + static char pkt[1316]; memset(pkt, 0xAB, sizeof pkt); + uint32_t seq = 0; + double credit = 0; uint64_t last = now_us(); uint64_t t0 = last; + uint64_t next_poll = mono_ms() + BITRATE_UPDATE_INT, next_stats = mono_ms() + 1000; + long a_sent = 0, a_retrans = 0, a_snddrop = 0, a_nak = 0, a_ack = 0, app_drop = 0, app_sent = 0; + double last_rtt = 0, last_rate = 0; int last_flight = 0, bs = 0; long last_availbuf = 0; + while (true) { + uint64_t t = now_us(); + if (t - t0 >= (uint64_t)duration * 1000000ULL) break; + credit += (double)cur_bitrate / 8.0 * (double)(t - last) / 1e6; last = t; + if (credit > 4.0 * 1316) credit = 4.0 * 1316; + while (credit >= 1316) { + credit -= 1316; + uint32_t magic = 0x53525431; memcpy(pkt, &magic, 4); memcpy(pkt + 4, &seq, 4); uint64_t ts = now_us(); memcpy(pkt + 8, &ts, 8); + int r = srt_sendmsg2(sock, pkt, sizeof pkt, NULL); + if (r != (int)sizeof pkt) { app_drop++; if (srt_getlasterror(NULL) != SRT_EASYNCSND) { fprintf(stderr, "send error: %s\n", srt_getlasterror_str()); goto out; } } + else app_sent++; + seq++; + } + uint64_t m = mono_ms(); + if (m >= next_poll) { + next_poll += BITRATE_UPDATE_INT; + SRT_TRACEBSTATS st; if (srt_bstats(sock, &st, 1) == 0) { + a_sent += st.pktSent; a_retrans += st.pktRetrans; a_snddrop += st.pktSndDrop; a_nak += st.pktRecvNAK; a_ack += st.pktRecvACK; + last_rtt = st.msRTT; last_rate = st.mbpsSendRate; last_flight = st.pktFlightSize; last_availbuf = st.byteAvailSndBuf; + int sz = sizeof(bs); if (srt_getsockflag(sock, SRTO_SNDDATA, &bs, &sz) != 0) bs = -1; + if (tf) fprintf(tf, "%.3f %d %d %d\n", (now_us() - t0) / 1e6, bs, st.pktFlightSize, st.pktRetrans); + } + } + if (m >= next_stats) { + next_stats += 1000; + fprintf(sf, "{\"t\":%.1f,\"bitrate_kbps\":%d,\"mbpsSendRate\":%.3f,\"pktSent\":%ld,\"pktRetrans\":%ld,\"pktSndDrop\":%ld,\"pktRecvNAK\":%ld,\"pktRecvACK\":%ld,\"msRTT\":%.1f,\"pktFlightSize\":%d,\"bs\":%d,\"byteAvailSndBuf\":%ld,\"app_sent\":%ld,\"app_drop\":%ld}\n", + (t - t0) / 1e6, cur_bitrate / 1000, last_rate, a_sent, a_retrans, a_snddrop, a_nak, a_ack, last_rtt, last_flight, bs, last_availbuf, app_sent, app_drop); + fflush(sf); + a_sent = a_retrans = a_snddrop = a_nak = a_ack = 0; app_sent = app_drop = 0; + } + std::this_thread::sleep_for(std::chrono::microseconds(250)); + } +out: + // Let everything in flight be delivered and acknowledged before closing, so a loss in the + // last round trip is recovered instead of being counted as a teardown drop. + std::this_thread::sleep_for(std::chrono::milliseconds(srt_latency + 1000)); + SRT_TRACEBSTATS tot; if (srt_bstats(sock, &tot, 0) == 0) { + fprintf(sf, "{\"final\":1,\"seq\":%u,\"pktSentTotal\":%lld,\"pktRetransTotal\":%d,\"pktSndDropTotal\":%d,\"pktRecvNAKTotal\":%d,\"pktSndLossTotal\":%d}\n", seq, (long long)tot.pktSentTotal, tot.pktRetransTotal, tot.pktSndDropTotal, tot.pktRecvNAKTotal, tot.pktSndLossTotal); + fflush(sf); + } + srt_close(sock); srt_cleanup(); + return 0; +} diff --git a/testing/srtla-e2e/sweep_table.py b/testing/srtla-e2e/sweep_table.py new file mode 100755 index 0000000000..fe6cade61e --- /dev/null +++ b/testing/srtla-e2e/sweep_table.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""One row per variant over a results dir: total score against a reference variant plus the raw +numbers that matter for the hold/spacing trade-off. sweep_table.py OUT REF_VARIANT""" +import json, os, statistics, sys +ROOT = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, ROOT) +from score import subscores, total +out, ref = sys.argv[1], sys.argv[2] +res = json.load(open(os.path.join(ROOT, 'results', out, 'summary.json'))) +variants = sorted({v for s in res.values() for v in s}, key=lambda v: (v != ref, v)) +print('%-10s%8s%8s%9s%9s%8s%9s%9s%10s%10s' % ('variant', 'score', 'vs ref', 'missing', 'rexmit', 'dup', 'lag p99', 'lag max', 'slow-link%', 'e2e-lat')) +totals = {} +for v in variants: + tot, miss, rex, dup, lag99, lagmax, slow, e2e = [], 0, 0, 0, [], [], [], [] + for scn, s in res.items(): + m = s.get(v) + if not m: continue + tot.append(total(subscores(m, out, scn, v))) + miss += m['app_missing']; rex += m['snd_retrans']; dup += m['rcv_belated'] + if m.get('lag_p99') is not None: lag99.append(m['lag_p99']); lagmax.append(m.get('lag_max', 0)) + ips = list(json.load(open(os.path.join(ROOT, 'results', out, scn, v, 'proxy.json')))['links']) + if 'one_slow' in scn or 'two_slow' in scn: slow.append(m['share'].get(ips[-1], 0)) + e2e.append(m['lat_p50_median'] - m.get('lat', m.get('lat_negotiated', 0))) + totals[v] = statistics.mean(tot) if tot else 0 + print('%-10s%8.1f%8s%9d%9d%8d%9.0f%9.0f%10s%10.0f' % (v, totals[v], ('%+.1f' % (totals[v] - totals[ref])) if v != ref else '-', miss, rex, dup, + statistics.mean(lag99) if lag99 else 0, max(lagmax) if lagmax else 0, ('%.1f' % statistics.mean(slow)) if slow else '-', statistics.mean(e2e) if e2e else 0)) +print('\nper scenario (total score):') +print('%-20s' % 'scenario' + ''.join('%10s' % v for v in variants)) +for scn, s in res.items(): + print('%-20s' % scn + ''.join('%10.1f' % total(subscores(s[v], out, scn, v)) if v in s else '%10s' % '-' for v in variants)) From 9a455e40a5d423543ca892fd8fe8b00a9326d8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Thu, 10 Sep 2026 20:25:00 +0200 Subject: [PATCH 89/92] fix(srtla): keep retransmissions out of the per-link transit measurement A retransmission carries the original packet's timestamp, so sampling it measured the age of the loss instead of the link delay. Under heavy loss this inflated the measured spread to seconds, pinned the reorder hold at its cap and delayed every request until the sender had already dropped the packet. Retransmissions still count as received data. Heavy-logging builds now trace every loss record's detection, requests and recovery. --- srtcore/core.cpp | 9 +++++++++ srtcore/srtla_rec.cpp | 10 +++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/srtcore/core.cpp b/srtcore/core.cpp index ec72f05f39..3719580c83 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -10634,6 +10634,8 @@ int srt::CUDT::processData(CUnit* in_unit) { // The LOSSREPORT will be sent after initial_loss_ttl. m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl)); + HLOGC(qrlog.Debug, log << CONID() << "SRTLA LOSSTRACE detect %" << i->first << "-%" << i->second + << " witness %" << packet.seqno()); } } } @@ -10983,6 +10985,9 @@ void srt::CUDT::unlose(const CPacket &packet) if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl), (&detect_time))) { HLOGC(qrlog.Debug, log << "sequence " << sequence << " removed from belated lossreport record"); + HLOGC(qrlog.Debug, log << CONID() << "SRTLA LOSSTRACE recovered %" << sequence << " after " + << (is_zero(detect_time) ? -1 : count_microseconds(steady_clock::now() - detect_time) / 1000) << " ms " + << (packet.getRexmitFlag() ? "retransmission" : "original")); if (m_config.bSRTLA && was_reordered && !is_zero(detect_time)) { @@ -11645,6 +11650,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime) { CRcvFreshLoss& rec = m_FreshLoss[plan.report[k]]; addLossRecord(lossdata, rec.seq[0], rec.seq[1]); + HLOGC(qrlog.Debug, log << CONID() << "SRTLA LOSSTRACE request " << (is_zero(rec.report_time) ? "first" : "repeat") + << " %" << rec.seq[0] << "-%" << rec.seq[1] << " age " << (count_microseconds(currtime - rec.timestamp) / 1000) + << " ms remaining " << ((np.budget_us - count_microseconds(currtime - rec.timestamp) - np.rtt_us - np.margin_us) / 1000) + << " ms hold " << (np.hold_us / 1000) << " ms spacing " << (np.spacing_us / 1000) << " ms"); rec.report_time = currtime; } confirmed_loss = plan.confirmed; diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp index a39cbab581..9e5ac5cc9a 100644 --- a/srtcore/srtla_rec.cpp +++ b/srtcore/srtla_rec.cpp @@ -529,11 +529,15 @@ srt::SrtlaRec::Ingress srt::SrtlaRec::onIngress(const sockaddr_any& src, CUnit* // not the rexmit flag: a retransmit the receiver never got is still new. const int32_t sn = int32_t(w0 & 0x7FFFFFFF); const bool is_new = trackSn(*g, sn); + // A retransmission carries the original packet's timestamp, so it would measure the age + // of the loss, not the link: under 20 % loss that inflated the spread to seconds and + // pinned the hold at its cap. Retransmissions count as unique data but not as transit samples. + const bool rexmit = (pkt.getHeader()[SRT_PH_MSGNO] & MSGNO_REXMIT::mask) != 0; if (is_new) - { l->unique_acc += double(total); // wire bytes, same basis as recv_acc (decayed above) - - // Relative one-way transit (new packets only). rel = (arrival - arrival_ref) + if (is_new && !rexmit) + { + // Relative one-way transit (new original packets only). rel = (arrival - arrival_ref) // - (sender_ts - ts_ref); the sender clock offset cancels. Sender timestamp // unwrapped with signed 32-bit deltas (wrap- and reorder-safe). const uint32_t sender_ts = pkt.getHeader()[SRT_PH_TIMESTAMP]; From 9d77d480decac51b9c1187eee7a266251edd113b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9?= <48084558+servusrene@users.noreply.github.com> Date: Thu, 10 Sep 2026 21:40:00 +0200 Subject: [PATCH 90/92] test(srtla-e2e): per-drop loss traces, run validity, heavy-logging builds losstrace.py builds per-drop timelines from a heavy-logging receiver log; build.sh takes CMAKE_EXTRA and RECEIVER_DEBUG=1 runs the receiver at debug level. Every run records the exit codes of its four processes, and a run whose stream died early is excluded from the score like a host stall. --- testing/srtla-e2e/README.md | 9 +++- testing/srtla-e2e/build.sh | 3 ++ testing/srtla-e2e/docker.sh | 4 +- testing/srtla-e2e/losstrace.py | 83 +++++++++++++++++++++++++++++++++ testing/srtla-e2e/run_matrix.py | 15 ++++-- testing/srtla-e2e/score.py | 2 + 6 files changed, 111 insertions(+), 5 deletions(-) create mode 100755 testing/srtla-e2e/losstrace.py diff --git a/testing/srtla-e2e/README.md b/testing/srtla-e2e/README.md index 46f62017ac..5db7443a6a 100644 --- a/testing/srtla-e2e/README.md +++ b/testing/srtla-e2e/README.md @@ -51,6 +51,7 @@ socket, so the receiver sees distinct link addresses like it would with real mod | `compare.py` | one table per scenario, variants side by side | | `repeats_table.py` | aggregate a scenario over several seeds | | `holes.py` | recovery-time (ACK lag) distribution from the sender's 20 ms buffer trace | +| `losstrace.py` | per-dropped-packet timelines (detect, requests, recovery) from a heavy-logging receiver log | | `Dockerfile`, `docker.sh` | run build and matrix under Linux, where eight loopback addresses give any number of links | ## Requirements @@ -89,6 +90,11 @@ link and second: forwarded / lost / queue-dropped packets, current impairment). Ports 15000-17000 are used; `test-srt` uses 5000+, so both can run at once (mind the CPU). +Loss traces: `CMAKE_EXTRA=-DENABLE_HEAVY_LOGGING=ON ./build.sh head=.` builds a receiver that +logs every loss record's detection, requests and recovery, `RECEIVER_DEBUG=1 ./run_matrix.py ...` +runs it at debug level, and `./losstrace.py results///head/rcv.err` builds the +per-dropped-packet timelines. Both variables pass through `docker.sh`. + `build/wt/` are git worktrees of this repository (`git worktree remove build/wt/` to drop one). Keep the machine otherwise idle during runs: a 1-2 s stall of the host shows up as hundreds of sender drops and an e2e p99 far above the latency, and is not a receiver result. @@ -181,4 +187,5 @@ is read-only there). shows here. - `lag p99`: ACK lag, the age of the oldest unacknowledged packet (from the 20 ms trace), i.e. how long the oldest hole stayed open. `stall`: longest gap between proxy ticks; a host stall - invalidates the run. + invalidates the run. `exit.json` records the exit code of all four processes; a run whose + stream died early is excluded from the score the same way. diff --git a/testing/srtla-e2e/build.sh b/testing/srtla-e2e/build.sh index c06d4718ce..7ad16a3874 100755 --- a/testing/srtla-e2e/build.sh +++ b/testing/srtla-e2e/build.sh @@ -10,6 +10,7 @@ # Usage: build.sh [name=ref ...] default: head=HEAD # e.g. build.sh head=HEAD main=main # Env: BELABOX_SRT_REF, BELABOX_SRTLA_REF commits of the BELABOX repositories (default: the pinned ones below) +# CMAKE_EXTRA extra cmake options for the receivers (e.g. -DENABLE_HEAVY_LOGGING=ON) # CMAKE cmake binary (default: cmake on PATH, else CLion's bundled one) set -eu HERE=$(cd "$(dirname "$0")" && pwd); REPO=$(cd "$HERE/../.." && pwd); B="$HERE/${BUILD_DIR:-build}" @@ -24,6 +25,8 @@ fi export CMAKE_POLICY_VERSION_MINIMUM=3.5 # both SRT trees declare cmake_minimum_required < 3.5; the env var also reaches sub-cmakes JOBS=$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) CMAKE_FLAGS="-G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SHARED=OFF -DENABLE_STATIC=ON -DENABLE_ENCRYPTION=OFF -DENABLE_APPS=OFF -DENABLE_UNITTESTS=OFF" +# CMAKE_EXTRA adds options for the receiver variants, e.g. CMAKE_EXTRA="-DENABLE_HEAVY_LOGGING=ON" for loss traces +CMAKE_FLAGS="$CMAKE_FLAGS ${CMAKE_EXTRA:-}" command -v ninja >/dev/null 2>&1 || CMAKE_FLAGS=$(echo "$CMAKE_FLAGS" | sed 's/-G Ninja //') mkdir -p "$B" diff --git a/testing/srtla-e2e/docker.sh b/testing/srtla-e2e/docker.sh index a5a1a97959..dc18d27412 100755 --- a/testing/srtla-e2e/docker.sh +++ b/testing/srtla-e2e/docker.sh @@ -7,12 +7,14 @@ # # The fork is bind-mounted; build.sh clones the BELABOX repositories inside the container into # build-linux/deps/, and results land in results// on the host like a native run. -# Env: IMAGE, BELABOX_SRT_REF, BELABOX_SRTLA_REF. +# Env: IMAGE, BELABOX_SRT_REF, BELABOX_SRTLA_REF, CMAKE_EXTRA, RECEIVER_DEBUG=1 (receiver at debug +# log level, for heavy-logging builds). set -eu HERE=$(cd "$(dirname "$0")" && pwd); REPO=$(cd "$HERE/../.." && pwd) IMAGE=${IMAGE:-srtla-e2e} docker image inspect "$IMAGE" >/dev/null 2>&1 || docker build -t "$IMAGE" -f "$HERE/Dockerfile" "$HERE" mounts="-v $REPO:/work/srt -e BELABOX_SRT_REF -e BELABOX_SRTLA_REF" +mounts="$mounts -e CMAKE_EXTRA=${CMAKE_EXTRA:-} -e RECEIVER_DEBUG=${RECEIVER_DEBUG:-0}" cmd=${1:-run}; [ $# -gt 0 ] && shift case "$cmd" in build) exec docker run --rm $mounts -e BUILD_DIR=build-linux "$IMAGE" ./build.sh "$@" ;; diff --git a/testing/srtla-e2e/losstrace.py b/testing/srtla-e2e/losstrace.py new file mode 100755 index 0000000000..dcb6ac951e --- /dev/null +++ b/testing/srtla-e2e/losstrace.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Per-loss timelines from a heavy-logging receiver log (build with CMAKE_EXTRA=-DENABLE_HEAVY_LOGGING=ON, +run with RECEIVER_DEBUG=1). For every packet the receiver dropped it shows when the gap was detected, +every request, and how late the drop came; then it sorts the drops into classes: + + unrequested dropped without a single request (hold / timer / detection too late) + one-request requested once, no answer within the budget, no second request + repeats-too-late requested several times, the last answer could not make the deadline + answered-late a retransmission arrived but after the play time (dropped anyway) + + losstrace.py results////rcv.err [--verbose] +""" +import re, sys, statistics +from collections import defaultdict + +path = sys.argv[1]; verbose = '--verbose' in sys.argv +ts_re = re.compile(r'^(\d+):(\d+):(\d+)\.(\d+)') +def ts(line): + m = ts_re.match(line) + return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) + int(m.group(4)) / 1e6 if m else None + +detect, requests, recovered, dropped, dropped_delay = {}, defaultdict(list), {}, {}, {} +first_ts = None +for line in open(path, errors='replace'): + t = ts(line) + if t is None: continue + if first_ts is None: first_ts = t + m = re.search(r'LOSSTRACE detect %(\d+)-%(\d+)', line) + if m: + for sq in range(int(m.group(1)), int(m.group(2)) + 1): detect.setdefault(sq, t) + continue + m = re.search(r'LOSSTRACE request (first|repeat) %(\d+)-%(\d+) age (\d+) ms remaining (-?\d+) ms hold (\d+) ms spacing (\d+) ms', line) + if m: + for sq in range(int(m.group(2)), int(m.group(3)) + 1): requests[sq].append((t, m.group(1), int(m.group(5)))) + continue + m = re.search(r'LOSSTRACE recovered %(\d+) after (-?\d+) ms (retransmission|original)', line) + if m: + recovered.setdefault(int(m.group(1)), (t, int(m.group(2)), m.group(3))); continue + # heavy-logging builds name the dropped range exactly: "DROPSEQ: up to seqno %X (N packets) ... delayed D ms" + m = re.search(r'tsbpd: DROPSEQ: up to seqno %(\d+) \((\d+) packets\).*delayed (\d+)\.(\d+) ms', line) + if m: + last, n, d = int(m.group(1)), int(m.group(2)), float(m.group(3) + '.' + m.group(4)) + for sq in range(last - n + 1, last + 1): dropped[sq] = t; dropped_delay[sq] = d + continue + # plain builds only log the packet delivered after the gap; attribute the drop to the packets before it + m = re.search(r'RCV-DROPPED (\d+) packet\(s\)\. Packet seqno %(\d+) delayed for ([\d.]+) ms', line) + if m and 'DROPSEQ' not in line: + n, nxt, d = int(m.group(1)), int(m.group(2)), float(m.group(3)) + for sq in range(nxt - n, nxt): + if sq not in dropped: dropped[sq] = t; dropped_delay[sq] = d + +classes = defaultdict(list) +for sq, td in dropped.items(): + reqs = requests.get(sq, []); t0 = detect.get(sq) + if sq in recovered and recovered[sq][0] <= td: cls = 'answered-late' + elif not reqs: cls = 'unrequested' + elif len(reqs) == 1: cls = 'one-request' + else: cls = 'repeats-too-late' + classes[cls].append(sq) + if verbose: + rel = lambda x: '%.0f' % ((x - t0) * 1000) if t0 else '?' + print('drop %%%d: detected t=%.1fs, requests at +%s ms, dropped at +%s ms (late by %.0f ms)%s' % ( + sq, (t0 or td) - first_ts, ', '.join(rel(r[0]) for r in reqs), rel(td), dropped_delay[sq], + '' if sq not in recovered else ', recovered at +%s ms' % rel(recovered[sq][0]))) + +n = len(dropped) +print('%s: %d packets dropped, %d loss records detected, %d recovered (%d by retransmission)' % ( + path, n, len(detect), len(recovered), sum(1 for r in recovered.values() if r[2] == 'retransmission'))) +for cls in ('unrequested', 'one-request', 'repeats-too-late', 'answered-late'): + sqs = classes.get(cls, []) + if not sqs: continue + firsts = [ (requests[s][0][0] - detect[s]) * 1000 for s in sqs if s in detect and requests.get(s)] + counts = [len(requests.get(s, [])) for s in sqs] + print(' %-17s %4d (%3.0f %%) requests per drop: median %d, max %d first request after detection: median %s ms' % ( + cls, len(sqs), 100.0 * len(sqs) / n, statistics.median(counts) if counts else 0, max(counts) if counts else 0, + '%.0f' % statistics.median(firsts) if firsts else '-')) +rec = [r[1] for r in recovered.values() if r[2] == 'retransmission' and r[1] >= 0] +if rec: + rec.sort(); print(' recovered by retransmission: %d, time from detection p50 %d ms, p90 %d ms, p99 %d ms, max %d ms' % ( + len(rec), rec[len(rec)//2], rec[int(len(rec)*.9)], rec[int(len(rec)*.99)], rec[-1])) +req_counts = [len(v) for v in requests.values()] +if req_counts: + print(' requests per detected record: mean %.2f, share with >1: %.0f %%' % (statistics.mean(req_counts), 100.0 * sum(1 for c in req_counts if c > 1) / len(req_counts))) diff --git a/testing/srtla-e2e/run_matrix.py b/testing/srtla-e2e/run_matrix.py index 07cd5c5065..479faaeec9 100755 --- a/testing/srtla-e2e/run_matrix.py +++ b/testing/srtla-e2e/run_matrix.py @@ -190,7 +190,8 @@ def start(args, err): p = subprocess.Popen(args, stderr=open(os.path.join(d, err), 'w'), stdout=subprocess.DEVNULL); procs.append(p); return p plain = scn.get('plain', False) rcv = start([os.path.join(BUILD, 'receiver-' + variant), str(PORT_RCV), '--latency', str(lat), '--lossmaxttl', '50', '--kbps', str(scn['kbps']), - '--srtla', '0' if plain else '1', '--duration', str(dur + 25), '--stats', os.path.join(d, 'rcv.jsonl')], 'rcv.err') + '--srtla', '0' if plain else '1', '--duration', str(dur + 25), '--stats', os.path.join(d, 'rcv.jsonl')] + + (['--debug', '1'] if os.environ.get('RECEIVER_DEBUG') == '1' else []), 'rcv.err') prx = start([sys.executable, os.path.join(ROOT, 'proxy.py'), os.path.join(d, 'proxy.json')], 'proxy.err') time.sleep(0.5) if plain: @@ -213,6 +214,8 @@ def start(args, err): try: p.wait(timeout=3) except subprocess.TimeoutExpired: p.kill() time.sleep(1.0) # let the ports drain before the next run + json.dump(dict(receiver=rcv.returncode, proxy=prx.returncode, srtla_send=(ss.returncode if ss else None), sender=snd.returncode), + open(os.path.join(d, 'exit.json'), 'w')) return d def jl(path): @@ -264,6 +267,12 @@ def summarise(d, scn=None): m['proxy_lost'] = lost # a host stall shows as a late proxy tick (they are 1.0 s apart); such a run is not a receiver result m['stall_max_ms'] = round(max((b - a for a, b in zip(ticks, ticks[1:])), default=1.0) * 1000 - 1000) + # a run is only a measurement if the stream lived for the whole duration: a sender that died + # early (crash, connection break) leaves too few stat rows and an absurd end-to-end latency + try: ex = json.load(open(os.path.join(d, 'exit.json'))) + except FileNotFoundError: ex = {} + m['exit'] = ex + m['broken'] = (scn is not None and len(s_rows) < scn['dur'] - 5) or (ex.get('sender') not in (None, 0)) or (ex.get('receiver') not in (None, 0)) pps = m['snd_sent'] / max(1, len(s_rows)) m.update(lag_percentiles(d, pps)) if scn is not None: @@ -297,7 +306,7 @@ def summarise(d, scn=None): t0 = time.time(); print('== %s / %s' % (name, v), file=sys.stderr, flush=True) d = run(scn, name, v, out, a.seed, ips) m = summarise(d, scn); results.setdefault(name, {})[v] = m - print(' %.0fs missing %d rcvdrop %d snddrop %d/%d nak %d retrans %d belated %d lag p99 %s stall %d share %s' % ( + print(' %.0fs missing %d rcvdrop %d snddrop %d/%d nak %d retrans %d belated %d lag p99 %s stall %d share %s%s' % ( time.time() - t0, m['app_missing'], m['rcv_drop'], m['snd_drop'], m['app_drop'], m['rcv_nak'], m['snd_retrans'], m['rcv_belated'], - m.get('lag_p99', '-'), m['stall_max_ms'], m['share']), file=sys.stderr, flush=True) + m.get('lag_p99', '-'), m['stall_max_ms'], m['share'], ' BROKEN RUN exit=%s' % m['exit'] if m['broken'] else ''), file=sys.stderr, flush=True) json.dump(results, open(summary_path, 'w'), indent=1) diff --git a/testing/srtla-e2e/score.py b/testing/srtla-e2e/score.py index 00569c0e2e..9cd5b1d789 100755 --- a/testing/srtla-e2e/score.py +++ b/testing/srtla-e2e/score.py @@ -83,6 +83,8 @@ def main(): if not m: continue if m.get('stall_max_ms', 0) > STALL_LIMIT_MS: excluded.append('%s/%s/%s (host stall %d ms)' % (out, scn, v, m['stall_max_ms'])); continue + if m.get('broken') or (m.get('lat_p50_median', 0) - (m.get('lat') or m.get('lat_negotiated') or 0) > 1000) or m.get('lag_p99') is None: + excluded.append('%s/%s/%s (stream died early, exit %s)' % (out, scn, v, m.get('exit', '?'))); continue sc = subscores(m, out, scn, v) e = agg.setdefault(v, dict(sub={}, n=0, missing=0, snddrop=0, nak=0, rexmit=0, belated=0, lat_rcv_min=10**9)) e['lat_rcv_min'] = min(e['lat_rcv_min'], m.get('lat_rcv_effective', 10**9) if m.get('lat_rcv_effective', -1) > 0 else 10**9) From 2547ec0c866b35d64cf08cf246da0f7aa4aa566d Mon Sep 17 00:00:00 2001 From: Alexander Wagner Date: Thu, 22 Oct 2026 01:36:42 +0200 Subject: [PATCH 91/92] fix(ci): prevent shell injection in GitHub Actions inputs --- .github/workflows/srtla-e2e.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/srtla-e2e.yml b/.github/workflows/srtla-e2e.yml index 0a9c1d83ab..5083c3847b 100644 --- a/.github/workflows/srtla-e2e.yml +++ b/.github/workflows/srtla-e2e.yml @@ -44,10 +44,14 @@ jobs: - name: Resolve base (always the main branch unless overridden) id: base working-directory: srt + env: + BASE_REF: ${{ inputs.base }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} run: | - ref="${{ inputs.base }}" - [ -n "$ref" ] || ref="origin/${{ github.event.repository.default_branch }}" - echo "sha=$(git rev-parse "$ref")" >> "$GITHUB_OUTPUT" + ref="$BASE_REF" + [ -n "$ref" ] || ref="origin/$DEFAULT_BRANCH" + base_sha="$(git rev-parse --verify --end-of-options "${ref}^{commit}")" + echo "sha=$base_sha" >> "$GITHUB_OUTPUT" echo "head: $(git log --oneline -1 HEAD)" echo "base: $ref ($(git log --oneline -1 "$ref"))" - name: Build receivers, sender, srtla_send @@ -55,8 +59,10 @@ jobs: run: ./build.sh head=HEAD base=${{ steps.base.outputs.sha }} - name: Run scenarios (two seeds, base and head interleaved) working-directory: srt/testing/srtla-e2e + env: + SCENARIOS: ${{ inputs.scenarios || 'ci' }} run: | - set="${{ inputs.scenarios || 'ci' }}" + set="$SCENARIOS" ./run_matrix.py base,head "$set" --group ${{ matrix.group }}/10 --out ci-s1 --seed 1 ./run_matrix.py base,head "$set" --group ${{ matrix.group }}/10 --out ci-s2 --seed 2 - uses: actions/upload-artifact@v4 From eb081e2b1ef989190c141a8e2995de308c0b3833 Mon Sep 17 00:00:00 2001 From: AlexanderWagnerDev Date: Sun, 1 Nov 2026 23:19:41 +0100 Subject: [PATCH 92/92] fix(srtla): keep groups with live connections and reset accounting on rebind Port the v1.5.4+openirl.4 fixes onto the SRT 1.5.7 base. - Keep a group bound to a live SRT connection while it has no links, so REG2 with the same group id re-registers instead of receiving REG_NGP. Release the binding from CRcvQueue::removeFromLists() when the connection leaves the receive queue. - Reset sequence-number tracking, timestamp references, bitrate, transit and jitter state when a different SRT socket binds to a surviving group. - Account only packets addressed to the bound socket, so delayed packets from a previous connection or a refused reconnect cannot corrupt the sequence-number window. - Add SrtlaRec unit tests for all-links outages while bound, re-registration, rebind accounting reset, stale-connection packets, stats isolation, same-socket rebind and group cleanup. --- srtcore/queue.cpp | 3 + srtcore/srtla_rec.cpp | 53 ++++- srtcore/srtla_rec.h | 7 + test/filelist.maf | 1 + test/test_srtla_rec.cpp | 466 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 526 insertions(+), 4 deletions(-) create mode 100644 test/test_srtla_rec.cpp diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index c0fcaee197..523bbfefc0 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -1875,6 +1875,9 @@ void srt::CRcvQueue::removeFromLists(CUDT* u) { // the socket must be removed from Hash table first, then RcvUList m_pHash->remove(u->m_SocketID); + // SRTLA: a group bound to this connection was kept while it lived; let it go. + if (m_pSrtlaRec) + m_pSrtlaRec->unbindSocket(u->m_SocketID); m_pRcvUList->remove(u); u->m_pRNode->m_bOnList = false; } diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp index dabfb88bb9..13d379f419 100644 --- a/srtcore/srtla_rec.cpp +++ b/srtcore/srtla_rec.cpp @@ -528,7 +528,11 @@ srt::SrtlaRec::Ingress srt::SrtlaRec::onIngress(const sockaddr_any& src, CUnit* // Count first-seen (group-wide) sequence numbers as unique. First-seen tracking, // not the rexmit flag: a retransmit the receiver never got is still new. const int32_t sn = int32_t(w0 & 0x7FFFFFFF); - const bool is_new = trackSn(*g, sn); + // Accounting covers the bound connection only: packets for another socket on the + // same group (stragglers of the previous connection, a reconnect being refused) run + // in their own sequence-number and timestamp space and would corrupt the window. + const bool ours = g->bound && SRTSOCKET(pkt.getHeader()[SRT_PH_ID]) == g->socket_id; + const bool is_new = ours && trackSn(*g, sn); // A retransmission carries the original packet's timestamp, so it would measure the age // of the loss, not the link: under 20 % loss that inflated the spread to seconds and // pinned the hold at its cap. Retransmissions count as unique data but not as transit samples. @@ -672,7 +676,13 @@ void srt::SrtlaRec::bindGroup(const sockaddr_any& peer, CUDT* u) return; } - Group* g = ref.g; + Group* g = ref.g; + // A new connection on a surviving group (the sender's SRT reconnected while its links + // stayed registered) starts a new sequence-number and timestamp space. Accounting kept + // from the previous one would class its packets as old: per-link bitrate stuck at 0, + // transit and jitter frozen or offset by the restarted sender clock. + if (g->socket_id != u->socketID()) + resetAccounting(*g); g->socket_id = u->socketID(); g->bound = true; if (!g->has_last_addr) @@ -685,6 +695,38 @@ void srt::SrtlaRec::bindGroup(const sockaddr_any& peer, CUDT* u) HLOGC(cnlog.Debug, log << "SRTLA bindGroup: peer " << peer.str() << " -> CUDT @" << u->socketID()); } +void srt::SrtlaRec::unbindSocket(SRTSOCKET socket_id) +{ + ScopedLock lock(m_Lock); + + for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g) + { + if (g->bound && g->socket_id == socket_id) + { + HLOGC(cnlog.Debug, log << "SRTLA unbindSocket: @" << socket_id << " gone, group released"); + g->bound = false; + g->socket_id = SRT_INVALID_SOCK; + } + } +} + +void srt::SrtlaRec::resetAccounting(Group& g) +{ + g.sn_valid = false; // trackSn clears the window on the next packet + g.sn_max = 0; + g.ts_init = false; + g.ts_last_raw = 0; + g.sender_ts_acc = 0; + for (std::list::iterator it = g.links.begin(); it != g.links.end(); ++it) + { + it->unique_acc = 0.0; + it->transit_ewma = 0.0; + it->transit_valid = false; + it->jitter_ewma = 0.0; + it->have_prev_transit = false; + } +} + // -------------------------------------------------------------------------- // Periodic: timeouts, recovery keepalives, per-second stats (spec §6, §3.6) // -------------------------------------------------------------------------- @@ -774,8 +816,11 @@ void srt::SrtlaRec::doCleanup(const time_point& now) } } - // Remove empty groups past the initial grace period (GROUP_TIMEOUT 4 s). - if (g->links.empty() && count_milliseconds(now - g->created_at) > 4000) + // Remove empty groups past the initial grace period (GROUP_TIMEOUT 4 s). A group bound + // to a live connection stays: when every link drops at once (all modems out for a few + // seconds) the sender re-registers with REG2 and the same group id, and must find the + // group again rather than get REG_NGP while its SRT connection is still up. + if (g->links.empty() && !g->bound && count_milliseconds(now - g->created_at) > 4000) { HLOGC(cnlog.Debug, log << "SRTLA group timeout (empty)"); std::list::iterator dead = g++; diff --git a/srtcore/srtla_rec.h b/srtcore/srtla_rec.h index b3c68578b7..0c73789255 100644 --- a/srtcore/srtla_rec.h +++ b/srtcore/srtla_rec.h @@ -108,6 +108,10 @@ class SrtlaRec /// reverse path can find the group. void bindGroup(const sockaddr_any& peer, CUDT* u); + /// The connection @a socket_id has left the receive queue (closed or broken): release + /// its group, which from then on times out like any other once its links are gone. + void unbindSocket(SRTSOCKET socket_id); + /// Periodic pass (receive worker timing section): link/group timeouts and recovery /// keepalives. Internally throttled. void onPeriodic(const sync::steady_clock::time_point& now); @@ -258,6 +262,7 @@ class SrtlaRec // --- accounting helpers --- bool trackSn(Group& g, int32_t sn); // returns true if unique (first seen) + void resetAccounting(Group& g); // new connection bound: forget the old one's // --- cleanup --- void doCleanup(const time_point& now); @@ -283,6 +288,8 @@ class SrtlaRec SrtlaRec(const SrtlaRec&); SrtlaRec& operator=(const SrtlaRec&); + + friend class SrtlaRecTestAccess; // unit tests }; } // namespace srt diff --git a/test/filelist.maf b/test/filelist.maf index e39d6ac592..a8e94f5c59 100644 --- a/test/filelist.maf +++ b/test/filelist.maf @@ -30,6 +30,7 @@ test_utilities.cpp test_reuseaddr.cpp test_socketdata.cpp test_snd_rate_estimator.cpp +test_srtla_rec.cpp # Tests for bonding only - put here! diff --git a/test/test_srtla_rec.cpp b/test/test_srtla_rec.cpp new file mode 100644 index 0000000000..4c7951ba96 --- /dev/null +++ b/test/test_srtla_rec.cpp @@ -0,0 +1,466 @@ +/* + * SRTLA receiver demux regression tests. + * Copyright (c) 2026 OpenIRL + * + * SPDX-License-Identifier: AGPL-3.0-only + * + * Covers the "keep a group with its connection and reset its accounting on + * rebind" fixes: a group bound to a live SRT connection survives an outage of + * all its links, a new connection on a surviving group starts fresh accounting, + * packets of another socket do not touch it, and a terminated connection + * releases its group. + */ + +#include +#include + +#include "gtest/gtest.h" +#include "test_env.h" + +#include "channel.h" +#include "core.h" +#include "packet.h" +#include "queue.h" +#include "srtla_rec.h" + +using namespace srt; + +namespace srt +{ + +/// Test-only access to the demux's private registry and accounting helpers. +/// Declared a friend by SrtlaRec so the per-link / per-group state can be +/// inspected and aged deterministically, without waiting for real timeouts. +class SrtlaRecTestAccess +{ +public: + typedef SrtlaRec::Group Group; + typedef SrtlaRec::Link Link; + typedef SrtlaRec::time_point time_point; + + /// Create a group through the real REG1 path and return its full (client + + /// server half) id. + static void reg1(SrtlaRec& rec, const sockaddr_any& src, uint8_t seed, uint8_t* full_id) + { + uint8_t wire[SrtlaRec::REG_LEN]; + memset(wire, 0, sizeof wire); + wire[0] = 0x92; + wire[1] = 0x00; + memset(wire + 2, seed, SrtlaRec::SRTLA_ID_HALF); + + rec.handleReg1(src, wire); + + EXPECT_FALSE(rec.m_Groups.empty()); + Group& g = rec.m_Groups.back(); + memcpy(full_id, g.id, SrtlaRec::SRTLA_ID_LEN); + } + + /// Register another link through the real REG2 path. + static void reg2(SrtlaRec& rec, const sockaddr_any& src, const uint8_t* full_id) + { + uint8_t wire[SrtlaRec::REG_LEN]; + memset(wire, 0, sizeof wire); + wire[0] = 0x92; + wire[1] = 0x01; + memcpy(wire + 2, full_id, SrtlaRec::SRTLA_ID_LEN); + + rec.handleReg2(src, wire); + } + + static Group* groupById(SrtlaRec& rec, const uint8_t* full_id) { return rec.findGroupById(full_id); } + + static size_t groupCount(const SrtlaRec& rec) { return rec.m_Groups.size(); } + + static size_t linkCount(const Group& g) { return g.links.size(); } + + static Link* linkByAddr(Group& g, const sockaddr_any& addr) + { + for (std::list::iterator it = g.links.begin(); it != g.links.end(); ++it) + { + if (it->addr == addr) + return &*it; + } + return nullptr; + } + + static void ageLink(Group& g, const sockaddr_any& addr, const time_point& last_rcvd) + { + Link* l = linkByAddr(g, addr); + ASSERT_NE(l, nullptr); + l->last_rcvd = last_rcvd; + } + + static void ageGroup(Group& g, const time_point& created_at) { g.created_at = created_at; } + + static void cleanup(SrtlaRec& rec, const time_point& now) { rec.doCleanup(now); } + + static bool snTrackingValid(const Group& g) { return g.sn_valid; } + static int32_t snMax(const Group& g) { return g.sn_max; } + static bool timestampValid(const Group& g) { return g.ts_init; } + static int64_t senderTimeAcc(const Group& g) { return g.sender_ts_acc; } + + static double uniqueBytes(const Link& l) { return l.unique_acc; } + static double transitUs(const Link& l) { return l.transit_ewma; } + static double absTransitUs(const Link& l) { return l.transit_ewma < 0 ? -l.transit_ewma : l.transit_ewma; } + static bool transitValid(const Link& l) { return l.transit_valid; } + static double jitterUs(const Link& l) { return l.jitter_ewma; } + static bool havePrevTransit(const Link& l) { return l.have_prev_transit; } +}; + +} // namespace srt + +namespace +{ + +sockaddr_any LinkAddr(unsigned short port) +{ + return CreateAddr("127.0.0.1", port, AF_INET); +} + +int64_t iabs(int64_t v) +{ + return v < 0 ? -v : v; +} + +class SrtlaRecTest : public ::testing::Test +{ +protected: + TestInit m_init; + CChannel m_chan; + SrtlaRec* m_rec; + + void SetUp() override + { + m_chan.open(AF_INET); + m_rec = new SrtlaRec(&m_chan); + } + + void TearDown() override + { + delete m_rec; + m_rec = nullptr; + m_chan.close(); + } + + /// Deliver one SRT data packet as the channel would after byte-order + /// conversion: the header is in host order and the source address is the + /// registered link. + void feedData(const sockaddr_any& src, int32_t sn, uint32_t timestamp, SRTSOCKET dest, + size_t payload = 1000, bool rexmit = false) + { + CUnit unit; + unit.m_Packet.setLength(payload); + uint32_t* h = unit.m_Packet.getHeader(); + h[SRT_PH_SEQNO] = uint32_t(sn); + h[SRT_PH_TIMESTAMP] = timestamp; + h[SRT_PH_MSGNO] = rexmit ? MSGNO_REXMIT::mask : 0; + h[SRT_PH_ID] = int32_t(dest); + + EXPECT_EQ(m_rec->onIngress(src, &unit), SrtlaRec::SRTLA_PASS); + } + + CUDT* coreOf(SRTSOCKET s) + { + TestMockCUDT mock; + return mock.setSocket(s) ? mock.core : nullptr; + } +}; + +// All links of a group drop while its SRT connection stays alive: the group +// must survive cleanup (no REG_NGP on re-registration), accept the links back +// under the same id, and only go away once the connection is unbound. +TEST_F(SrtlaRecTest, GroupSurvivesLinkOutageWhileBound) +{ + const sockaddr_any link1 = LinkAddr(45001); + const sockaddr_any link2 = LinkAddr(45002); + uint8_t group_id[SrtlaRec::SRTLA_ID_LEN]; + SrtlaRecTestAccess::reg1(*m_rec, link1, 0xA1, group_id); + SrtlaRecTestAccess::reg2(*m_rec, link1, group_id); + SrtlaRecTestAccess::reg2(*m_rec, link2, group_id); + + ASSERT_EQ(SrtlaRecTestAccess::groupCount(*m_rec), 1u); + SrtlaRecTestAccess::Group* g = SrtlaRecTestAccess::groupById(*m_rec, group_id); + ASSERT_NE(g, nullptr); + ASSERT_EQ(SrtlaRecTestAccess::linkCount(*g), 2u); + + SRTSOCKET sock = srt_create_socket(); + ASSERT_NE(sock, SRT_INVALID_SOCK); + CUDT* core = coreOf(sock); + ASSERT_NE(core, nullptr); + m_rec->bindGroup(link1, core); + ASSERT_TRUE(g->bound); + ASSERT_EQ(g->socket_id, sock); + + // Every link goes silent for longer than CONN_TIMEOUT, while the SRT + // connection is still up. + const sync::steady_clock::time_point now = sync::steady_clock::now(); + SrtlaRecTestAccess::ageLink(*g, link1, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::ageLink(*g, link2, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::cleanup(*m_rec, now); + + ASSERT_EQ(SrtlaRecTestAccess::linkCount(*g), 0u); + ASSERT_EQ(SrtlaRecTestAccess::groupCount(*m_rec), 1u) << "a bound group must survive an empty link set"; + + // The sender re-registers with REG2 and the same group id: it must find the + // group and become a link again. + SrtlaRecTestAccess::reg2(*m_rec, link1, group_id); + EXPECT_EQ(SrtlaRecTestAccess::groupCount(*m_rec), 1u); + SrtlaRecTestAccess::Group* still = SrtlaRecTestAccess::groupById(*m_rec, group_id); + ASSERT_NE(still, nullptr); + EXPECT_EQ(still, g) << "re-registration must reuse the existing group"; + EXPECT_EQ(SrtlaRecTestAccess::linkCount(*still), 1u); + EXPECT_TRUE(still->bound); + EXPECT_EQ(still->socket_id, sock); + + // Once the connection leaves the receive queue, the empty group times out. + m_rec->unbindSocket(sock); + EXPECT_FALSE(g->bound); + EXPECT_EQ(g->socket_id, SRT_INVALID_SOCK); + SrtlaRecTestAccess::ageGroup(*g, now - sync::milliseconds_from(10000)); + const sync::steady_clock::time_point later = now + sync::milliseconds_from(5000); + SrtlaRecTestAccess::ageLink(*g, link1, later - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::cleanup(*m_rec, later); + EXPECT_EQ(SrtlaRecTestAccess::groupCount(*m_rec), 0u); + + srt_close(sock); +} + +// A group without links and without a connection keeps the original cleanup +// behaviour: it is removed past the initial grace period. +TEST_F(SrtlaRecTest, UnboundEmptyGroupTimesOut) +{ + const sockaddr_any link = LinkAddr(45003); + uint8_t group_id[SrtlaRec::SRTLA_ID_LEN]; + SrtlaRecTestAccess::reg1(*m_rec, link, 0xB2, group_id); + + ASSERT_EQ(SrtlaRecTestAccess::groupCount(*m_rec), 1u); + SrtlaRecTestAccess::Group* g = SrtlaRecTestAccess::groupById(*m_rec, group_id); + ASSERT_NE(g, nullptr); + + const sync::steady_clock::time_point now = sync::steady_clock::now(); + SrtlaRecTestAccess::ageGroup(*g, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::cleanup(*m_rec, now); + EXPECT_EQ(SrtlaRecTestAccess::groupCount(*m_rec), 0u); +} + +// The sender's SRT reconnects while the SRTLA links stay registered. The new +// socket must start a fresh sequence-number / timestamp / rate / transit / +// jitter state, and only packets addressed to it may update that state. +TEST_F(SrtlaRecTest, RebindResetsAccountingAndIgnoresOldConnection) +{ + const sockaddr_any link = LinkAddr(45004); + uint8_t group_id[SrtlaRec::SRTLA_ID_LEN]; + SrtlaRecTestAccess::reg1(*m_rec, link, 0xC3, group_id); + SrtlaRecTestAccess::reg2(*m_rec, link, group_id); + SrtlaRecTestAccess::Group* g = SrtlaRecTestAccess::groupById(*m_rec, group_id); + ASSERT_NE(g, nullptr); + + SRTSOCKET old_sock = srt_create_socket(); + SRTSOCKET new_sock = srt_create_socket(); + ASSERT_NE(old_sock, SRT_INVALID_SOCK); + ASSERT_NE(new_sock, SRT_INVALID_SOCK); + CUDT* old_core = coreOf(old_sock); + CUDT* new_core = coreOf(new_sock); + ASSERT_NE(old_core, nullptr); + ASSERT_NE(new_core, nullptr); + + m_rec->bindGroup(link, old_core); + ASSERT_TRUE(g->bound); + ASSERT_EQ(g->socket_id, old_sock); + + // Old connection traffic establishes a sequence-number window and a transit + // reference that, if kept, would be far outside the new connection's range. + feedData(link, 1000, 0, old_sock); + feedData(link, 1001, 1000000, old_sock); + SrtlaRecTestAccess::Link* l = SrtlaRecTestAccess::linkByAddr(*g, link); + ASSERT_NE(l, nullptr); + EXPECT_TRUE(SrtlaRecTestAccess::snTrackingValid(*g)); + EXPECT_EQ(SrtlaRecTestAccess::snMax(*g), 1001); + EXPECT_TRUE(SrtlaRecTestAccess::timestampValid(*g)); + EXPECT_TRUE(SrtlaRecTestAccess::transitValid(*l)); + EXPECT_GT(SrtlaRecTestAccess::uniqueBytes(*l), 0.0); + EXPECT_TRUE(SrtlaRecTestAccess::havePrevTransit(*l)); + + // Reconnect: same group, different CUDT. + m_rec->bindGroup(link, new_core); + ASSERT_TRUE(g->bound); + ASSERT_EQ(g->socket_id, new_sock); + + EXPECT_FALSE(SrtlaRecTestAccess::snTrackingValid(*g)); + EXPECT_FALSE(SrtlaRecTestAccess::timestampValid(*g)); + EXPECT_EQ(SrtlaRecTestAccess::uniqueBytes(*l), 0.0); + EXPECT_FALSE(SrtlaRecTestAccess::transitValid(*l)); + EXPECT_EQ(SrtlaRecTestAccess::transitUs(*l), 0.0); + EXPECT_EQ(SrtlaRecTestAccess::jitterUs(*l), 0.0); + EXPECT_FALSE(SrtlaRecTestAccess::havePrevTransit(*l)); + EXPECT_EQ(SrtlaRecTestAccess::senderTimeAcc(*g), 0u); + + // A delayed packet of the old connection (addressed to the old socket) and + // a refused reconnect attempt must not reopen or advance the window. + feedData(link, 5000, 2000000, old_sock); + EXPECT_FALSE(SrtlaRecTestAccess::snTrackingValid(*g)); + EXPECT_FALSE(SrtlaRecTestAccess::timestampValid(*g)); + EXPECT_FALSE(SrtlaRecTestAccess::transitValid(*l)); + EXPECT_EQ(SrtlaRecTestAccess::uniqueBytes(*l), 0.0); + + // The new connection's first packet starts in its own space. A large sender + // timestamp (a restarted sender clock) must not produce a huge relative + // transit from the old arrival reference. + feedData(link, 7, 3000000000u, new_sock); + EXPECT_TRUE(SrtlaRecTestAccess::snTrackingValid(*g)); + EXPECT_EQ(SrtlaRecTestAccess::snMax(*g), 7) << "a stale packet must not move the window base"; + EXPECT_TRUE(SrtlaRecTestAccess::timestampValid(*g)); + EXPECT_GT(SrtlaRecTestAccess::uniqueBytes(*l), 0.0); + EXPECT_TRUE(SrtlaRecTestAccess::transitValid(*l)); + EXPECT_LT(SrtlaRecTestAccess::absTransitUs(*l), 1000000.0) + << "transit must be relative to the new connection"; + + // Jitter is measured again from the new connection's packets: a varying + // sender timestamp must produce a non-zero RFC 3550 estimate. + feedData(link, 8, 2999995000u, new_sock); + EXPECT_GT(SrtlaRecTestAccess::jitterUs(*l), 0.0); + EXPECT_LT(SrtlaRecTestAccess::absTransitUs(*l), 1000000.0); + + srt_close(old_sock); + srt_close(new_sock); +} + +// Stats queried for a socket follow the current binding only: after a rebind the +// old socket reports nothing and the new one reports the new connection's +// per-link bitrate / transit / jitter. +TEST_F(SrtlaRecTest, StatsFollowRebind) +{ + const sockaddr_any link = LinkAddr(45005); + uint8_t group_id[SrtlaRec::SRTLA_ID_LEN]; + SrtlaRecTestAccess::reg1(*m_rec, link, 0xD4, group_id); + SrtlaRecTestAccess::reg2(*m_rec, link, group_id); + SrtlaRecTestAccess::Group* g = SrtlaRecTestAccess::groupById(*m_rec, group_id); + ASSERT_NE(g, nullptr); + + SRTSOCKET old_sock = srt_create_socket(); + SRTSOCKET new_sock = srt_create_socket(); + ASSERT_NE(old_sock, SRT_INVALID_SOCK); + ASSERT_NE(new_sock, SRT_INVALID_SOCK); + CUDT* old_core = coreOf(old_sock); + CUDT* new_core = coreOf(new_sock); + ASSERT_NE(old_core, nullptr); + ASSERT_NE(new_core, nullptr); + + m_rec->bindGroup(link, old_core); + for (int32_t i = 0; i < 5; ++i) + feedData(link, 100 + i, uint32_t(i) * 1000, old_sock); + + SRT_SRTLA_STATS stats; + ASSERT_TRUE(m_rec->fillStats(old_sock, &stats)); + EXPECT_EQ(stats.valid, 1); + ASSERT_EQ(stats.numPeers, 1); + EXPECT_GT(stats.peers[0].kbpsRecvUnique, 0u); + + m_rec->bindGroup(link, new_core); + + // The previous connection's statistics must not be attributed to the new + // one before it has sent anything. + ASSERT_TRUE(m_rec->fillStats(new_sock, &stats)); + EXPECT_EQ(stats.valid, 1); + ASSERT_EQ(stats.numPeers, 1); + EXPECT_EQ(stats.peers[0].kbpsRecvUnique, 0u); + EXPECT_EQ(stats.peers[0].transitUs, 0); + EXPECT_EQ(stats.peers[0].usJitter, 0u); + EXPECT_FALSE(m_rec->fillStats(old_sock, &stats)); + + for (int32_t i = 0; i < 5; ++i) + feedData(link, i, uint32_t(i) * 1000, new_sock); + + ASSERT_TRUE(m_rec->fillStats(new_sock, &stats)); + ASSERT_EQ(stats.numPeers, 1); + EXPECT_GT(stats.peers[0].kbpsRecvUnique, 0u); + EXPECT_LT(iabs(stats.peers[0].transitUs), 1000000); + + srt_close(old_sock); + srt_close(new_sock); +} + +// Re-binding the same connection must not throw its accounting away: only a +// different socket starts a new measurement space. +TEST_F(SrtlaRecTest, SameSocketRebindKeepsAccounting) +{ + const sockaddr_any link = LinkAddr(45006); + uint8_t group_id[SrtlaRec::SRTLA_ID_LEN]; + SrtlaRecTestAccess::reg1(*m_rec, link, 0xE5, group_id); + SrtlaRecTestAccess::reg2(*m_rec, link, group_id); + SrtlaRecTestAccess::Group* g = SrtlaRecTestAccess::groupById(*m_rec, group_id); + ASSERT_NE(g, nullptr); + + SRTSOCKET sock = srt_create_socket(); + ASSERT_NE(sock, SRT_INVALID_SOCK); + CUDT* core = coreOf(sock); + ASSERT_NE(core, nullptr); + + m_rec->bindGroup(link, core); + feedData(link, 42, 0, sock); + SrtlaRecTestAccess::Link* l = SrtlaRecTestAccess::linkByAddr(*g, link); + ASSERT_NE(l, nullptr); + EXPECT_TRUE(SrtlaRecTestAccess::snTrackingValid(*g)); + EXPECT_TRUE(SrtlaRecTestAccess::timestampValid(*g)); + EXPECT_GT(SrtlaRecTestAccess::uniqueBytes(*l), 0.0); + + m_rec->bindGroup(link, core); + EXPECT_TRUE(SrtlaRecTestAccess::snTrackingValid(*g)); + EXPECT_EQ(SrtlaRecTestAccess::snMax(*g), 42); + EXPECT_TRUE(SrtlaRecTestAccess::timestampValid(*g)); + EXPECT_GT(SrtlaRecTestAccess::uniqueBytes(*l), 0.0); + + srt_close(sock); +} + +// Unbinding one connection must not disturb a group bound to another socket. +TEST_F(SrtlaRecTest, UnbindReleasesOnlyMatchingGroup) +{ + const sockaddr_any link_a = LinkAddr(45007); + const sockaddr_any link_b = LinkAddr(45008); + uint8_t id_a[SrtlaRec::SRTLA_ID_LEN]; + uint8_t id_b[SrtlaRec::SRTLA_ID_LEN]; + SrtlaRecTestAccess::reg1(*m_rec, link_a, 0x11, id_a); + SrtlaRecTestAccess::reg1(*m_rec, link_b, 0x22, id_b); + SrtlaRecTestAccess::reg2(*m_rec, link_a, id_a); + SrtlaRecTestAccess::reg2(*m_rec, link_b, id_b); + + SrtlaRecTestAccess::Group* ga = SrtlaRecTestAccess::groupById(*m_rec, id_a); + SrtlaRecTestAccess::Group* gb = SrtlaRecTestAccess::groupById(*m_rec, id_b); + ASSERT_NE(ga, nullptr); + ASSERT_NE(gb, nullptr); + + SRTSOCKET sock_a = srt_create_socket(); + SRTSOCKET sock_b = srt_create_socket(); + ASSERT_NE(sock_a, SRT_INVALID_SOCK); + ASSERT_NE(sock_b, SRT_INVALID_SOCK); + CUDT* core_a = coreOf(sock_a); + CUDT* core_b = coreOf(sock_b); + ASSERT_NE(core_a, nullptr); + ASSERT_NE(core_b, nullptr); + + m_rec->bindGroup(link_a, core_a); + m_rec->bindGroup(link_b, core_b); + + m_rec->unbindSocket(sock_a); + EXPECT_FALSE(ga->bound); + EXPECT_TRUE(gb->bound); + EXPECT_EQ(gb->socket_id, sock_b); + + // The released group is now eligible for empty-group cleanup; the still + // bound one is not. + const sync::steady_clock::time_point now = sync::steady_clock::now(); + SrtlaRecTestAccess::ageGroup(*ga, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::ageGroup(*gb, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::ageLink(*ga, link_a, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::ageLink(*gb, link_b, now - sync::milliseconds_from(10000)); + SrtlaRecTestAccess::cleanup(*m_rec, now); + + EXPECT_EQ(SrtlaRecTestAccess::groupById(*m_rec, id_a), nullptr); + EXPECT_NE(SrtlaRecTestAccess::groupById(*m_rec, id_b), nullptr); + + srt_close(sock_a); + srt_close(sock_b); +} + +} // namespace