diff -Nru srt-1.5.2/apps/socketoptions.hpp srt-1.5.3/apps/socketoptions.hpp --- srt-1.5.2/apps/socketoptions.hpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/apps/socketoptions.hpp 2023-08-29 07:44:24.000000000 +0000 @@ -258,6 +258,9 @@ #ifdef ENABLE_AEAD_API_PREVIEW ,{ "cryptomode", 0, SRTO_CRYPTOMODE, SocketOption::PRE, SocketOption::INT, nullptr } #endif +#ifdef ENABLE_MAXREXMITBW + ,{ "maxrexmitbw", 0, SRTO_MAXREXMITBW, SocketOption::POST, SocketOption::INT64, nullptr } +#endif }; } diff -Nru srt-1.5.2/apps/transmitmedia.cpp srt-1.5.3/apps/transmitmedia.cpp --- srt-1.5.2/apps/transmitmedia.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/apps/transmitmedia.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -848,7 +848,6 @@ if (is_multicast) { - ip_mreq_source mreq_ssm; ip_mreq mreq; sockaddr_any maddr (AF_INET); int opt_name; @@ -872,6 +871,7 @@ if (attr.count("source")) { #ifdef IP_ADD_SOURCE_MEMBERSHIP + 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; diff -Nru srt-1.5.2/CMakeLists.txt srt-1.5.3/CMakeLists.txt --- srt-1.5.2/CMakeLists.txt 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/CMakeLists.txt 2023-08-29 07:44:24.000000000 +0000 @@ -8,7 +8,7 @@ # cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) -set (SRT_VERSION 1.5.2) +set (SRT_VERSION 1.5.3) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(haiUtil) # needed for set_version_variables @@ -153,6 +153,7 @@ option(ENABLE_UNITTESTS "Enable unit tests" OFF) option(ENABLE_ENCRYPTION "Enable encryption in SRT" ON) option(ENABLE_AEAD_API_PREVIEW "Enable AEAD API preview in SRT" Off) +option(ENABLE_MAXREXMITBW "Enable SRTO_MAXREXMITBW (v1.6.0 API preview)" Off) option(ENABLE_CXX_DEPS "Extra library dependencies in srt.pc for the CXX libraries useful with C language" ON) option(USE_STATIC_LIBSTDCXX "Should use static rather than shared libstdc++" OFF) option(ENABLE_INET_PTON "Set to OFF to prevent usage of inet_pton when building against modern SDKs while still requiring compatibility with older Windows versions, such as Windows XP, Windows Server 2003 etc." ON) @@ -299,7 +300,13 @@ if(ENABLE_INET_PTON) set(CMAKE_REQUIRED_LIBRARIES ws2_32) check_function_exists(inet_pton HAVE_INET_PTON) - add_definitions(-D_WIN32_WINNT=0x0600) + try_compile(AT_LEAST_VISTA + ${CMAKE_BINARY_DIR} + "${CMAKE_CURRENT_SOURCE_DIR}/scripts/test_vista.c") + if(NOT AT_LEAST_VISTA) + # force targeting Vista + add_definitions(-D_WIN32_WINNT=0x0600) + endif() else() add_definitions(-D_WIN32_WINNT=0x0501) endif() @@ -460,6 +467,13 @@ set (SRT_LIBS_PRIVATE ${SRT_LIBS_PRIVATE} ${GNUSTL_LIBRARIES} ${GNUSTL_LDFLAGS}) endif() +if (ENABLE_MAXREXMITBW) + add_definitions(-DENABLE_MAXREXMITBW) + message(STATUS "MAXREXMITBW API: ENABLED") +else() + message(STATUS "MAXREXMITBW API: DISABLED") +endif() + if (USING_DEFAULT_COMPILER_PREFIX) # Detect if the compiler is GNU compatible for flags if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Intel|Clang|AppleClang") diff -Nru srt-1.5.2/debian/changelog srt-1.5.3/debian/changelog --- srt-1.5.2/debian/changelog 2023-08-25 23:50:42.000000000 +0000 +++ srt-1.5.3/debian/changelog 2023-09-15 23:56:37.000000000 +0000 @@ -1,3 +1,9 @@ +srt (1.5.3-0ubuntu1~18.04.sav0) bionic; urgency=medium + + * New upstream release + + -- Rob Savoury Fri, 15 Sep 2023 16:56:37 -0700 + srt (1.5.2-1~18.04.sav0) bionic; urgency=medium * Backport to Bionic diff -Nru srt-1.5.2/docs/API/API-functions.md srt-1.5.3/docs/API/API-functions.md --- srt-1.5.2/docs/API/API-functions.md 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/docs/API/API-functions.md 2023-08-29 07:44:24.000000000 +0000 @@ -359,8 +359,12 @@ Binds a socket to a local address and port. Binding specifies the local network interface and the UDP port number to be used for the socket. When the local address is a wildcard (`INADDR_ANY` for IPv4 or `in6addr_any` for IPv6), then -it's bound to all interfaces (although see `SRTO_IPV6ONLY` and additional -information below for details about the wildcard address in IPv6). +it's bound to all interfaces. + +**IMPORTANT**: When you bind an IPv6 wildcard address, note that the +`SRTO_IPV6ONLY` option must be set on the socket explicitly to 1 or 0 prior to +calling this function. See +[`SRTO_IPV6ONLY`](API-socket-options.md#SRTO_IPV6ONLY) for more details. Binding is necessary for every socket to be used for communication. If the socket is to be used to initiate a connection to a listener socket, which can be done, @@ -662,7 +666,7 @@ | Returns | | |:----------------------------- |:----------------------------------------------------------------------- | | socket/group ID | On success, a valid SRT socket or group ID to be used for transmission. | -| `SRT_ERROR` | (-1) on failure | +| `SRT_INVALID_SOCK` | (-1) on failure | | | | | Errors | | @@ -718,7 +722,7 @@ | Returns | | |:----------------------------- |:---------------------------------------------------------------------- | | SRT socket
group ID | On success, a valid SRT socket or group ID to be used for transmission | -| `SRT_ERROR` | (-1) on failure | +| `SRT_INVALID_SOCK` | (-1) on failure | | | | | Errors | | diff -Nru srt-1.5.2/docs/API/API-socket-options.md srt-1.5.3/docs/API/API-socket-options.md --- srt-1.5.2/docs/API/API-socket-options.md 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/docs/API/API-socket-options.md 2023-08-29 07:44:24.000000000 +0000 @@ -224,6 +224,7 @@ | [`SRTO_LINGER`](#SRTO_LINGER) | | post | `linger` | s | off \* | 0.. | RW | GSD | | [`SRTO_LOSSMAXTTL`](#SRTO_LOSSMAXTTL) | 1.2.0 | post | `int32_t` | packets | 0 | 0.. | RW | GSD+ | | [`SRTO_MAXBW`](#SRTO_MAXBW) | | post | `int64_t` | B/s | -1 | -1.. | RW | GSD | +| [`SRTO_MAXREXMITBW`](#SRTO_MAXREXMITBW) | 1.5.3 | post | `int64_t` | B/s | -1 | -1.. | RW | GSD | | [`SRTO_MESSAGEAPI`](#SRTO_MESSAGEAPI) | 1.3.0 | pre | `bool` | | true | | W | GSD | | [`SRTO_MININPUTBW`](#SRTO_MININPUTBW) | 1.4.3 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD | | [`SRTO_MINVERSION`](#SRTO_MINVERSION) | 1.3.0 | pre | `int32_t` | version | 0x010000 | \* | RW | GSD | @@ -647,10 +648,22 @@ Set system socket option level `IPPROTO_IPV6` named `IPV6_V6ONLY`. This is meaningful only when the socket is going to be bound to the IPv6 wildcard address `in6addr_any` -(known also as `::`). In this case this option must be also set explicitly to 0 or 1 -before binding, otherwise binding will fail (this is because it is not possible to -determine the default value of this above-mentioned system option in any portable or -reliable way). Possible values are: +(known also as `::`). If you bind to a wildcard address, you have the following +possibilities: + +* IPv4 only: bind to an IPv4 wildcard address +* IPv6 only: bind to an IPv6 wildcard address and set this option to 1 +* IPv4 and IPv6: bind to an IPv6 wildcard address and set this option to 0 + +This option's default value is -1 because it is not possible to determine the default +value on the current platform, and if you bind to an IPv6 wildcard address, this value +is required prior to binding. When you bind implicitly by calling `srt_connect` on the +socket, this isn't a problem -- binding will be done using the system-default value and then +extracted afterwards. But if you want to bind explicitly using `srt_bind`, this +option must be set explicitly to 0 or 1 because this information is vital for +determining any potential bind conflicts with other sockets. + +Possible values are: * -1: (default) use system-default value (can be used when not binding to IPv6 wildcard `::`) * 0: The binding to `in6addr_any` will bind to both IPv6 and IPv4 wildcard address @@ -832,6 +845,22 @@ [Return to list](#list-of-options) +--- + +#### SRTO_MAXREXMITBW + +| OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity | +| -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ | +| `SRTO_MAXREXMITBW` | 1.5.3 | post | `int64_t` | B/s | -1 | -1.. | RW | GSD | + +Maximum BW limit for retransmissions: + +- `-1`: unlimited; +- `0`: do not allow retransmissions. +- `>0`: BW usage limit in Bytes/sec for packet retransmissions (including 16 bytes of SRT header). + +[Return to list](#list-of-options) + --- #### SRTO_MESSAGEAPI diff -Nru srt-1.5.2/docs/apps/srt-live-transmit.md srt-1.5.3/docs/apps/srt-live-transmit.md --- srt-1.5.2/docs/apps/srt-live-transmit.md 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/docs/apps/srt-live-transmit.md 2023-08-29 07:44:24.000000000 +0000 @@ -285,11 +285,13 @@ NOTE: Don't use square brackets syntax in the **adapter** parameter specification, as in this case only the host is expected. -3. If you want to listen for connections from both IPv4 and IPv6, mind the -`ipv6only` option. The default value for this option is system default (see -system manual for `IPV6_V6ONLY` socket option); if unsure, you might want to -enforce `ipv6only=0` in order to be able to accept both IPv4 and IPv6 -connections by the same listener, or set `ipv6only=1` to accept exclusively IPv6. +3. If you bind to an IPv6 wildcard address (with listener mode, or when using the `bind` +option), setting the `ipv6only` option to 0 or 1 is obligatory, as it is a part +of the binding definition. If you set it to 1, the binding will apply only to +IPv6 local addresses, and if you set it to 0, it will apply to both IPv4 and +IPv6 local addresses. See the +[`SRTO_IPV6ONLY`](../API/API-socket-options.md#SRTO_IPV6ONLY) option +description for details. 4. In rendezvous mode you may only interconnect both parties using IPv4, or both using IPv6. Unlike listener mode, if you want to leave the socket @@ -303,9 +305,8 @@ * `srt://[::]:5000` defines caller mode (!) with IPv6. -* `srt://[::]:5000?mode=listener` defines listener mode with IPv6. If the - default value for `IPV6_V6ONLY` system socket option is 0, it will accept - also IPv4 connections. +* `srt://[::]:5000?mode=listener&ipv6only=1` defines listener mode with IPv6. + Only connections from IPv6 callers will be accepted. * `srt://192.168.0.5:5000?mode=rendezvous` will make a rendezvous connection with local address `INADDR_ANY` (IPv4) and port 5000 to a destination with diff -Nru srt-1.5.2/docs/build/build-options.md srt-1.5.3/docs/build/build-options.md --- srt-1.5.2/docs/build/build-options.md 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/docs/build/build-options.md 2023-08-29 07:44:24.000000000 +0000 @@ -35,6 +35,7 @@ | [`ENABLE_DEBUG`](#enable_debug) | 1.2.0 | `INT` | ON | Allows release/debug control through the `CMAKE_BUILD_TYPE` variable. | | [`ENABLE_ENCRYPTION`](#enable_encryption) | 1.3.3 | `BOOL` | ON | Enables encryption feature, with dependency on an external encryption library. | | [`ENABLE_AEAD_API_PREVIEW`](#enable_aead_api_preview) | 1.5.2 | `BOOL` | OFF | Enables AEAD preview API (encryption with integrity check). | +| [`ENABLE_MAXREXMITBW`](#enable_maxrexmitbw) | 1.5.3 | `BOOL` | OFF | Enables SRTO_MAXREXMITBW (v1.6.0 API). | | [`ENABLE_GETNAMEINFO`](#enable_getnameinfo) | 1.3.0 | `BOOL` | OFF | Enables the use of `getnameinfo` to allow using reverse DNS to resolve an internal IP address into a readable internet domain name. | | [`ENABLE_HAICRYPT_LOGGING`](#enable_haicrypt_logging) | 1.3.1 | `BOOL` | OFF | Enables logging in the *haicrypt* module, which serves as a connector to an encryption library. | | [`ENABLE_HEAVY_LOGGING`](#enable_heavy_logging) | 1.3.0 | `BOOL` | OFF | Enables heavy logging instructions in the code that occur often and cover many detailed aspects of library behavior. Default: OFF in release mode. | @@ -279,6 +280,11 @@ The AEAD API is to be official in SRT v1.6.0. +#### ENABLE_MAXREXMITBW +**`--enable-maxrexmitbw`** (default: OFF) + +When ON, the `SRTO_MAXREXMITBW` is enabled (to become official in SRT v1.6.0). + #### ENABLE_GETNAMEINFO **`--enable-getnameinfo`** (default: OFF) diff -Nru srt-1.5.2/haicrypt/hcrypt.c srt-1.5.3/haicrypt/hcrypt.c --- srt-1.5.2/haicrypt/hcrypt.c 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/haicrypt/hcrypt.c 2023-08-29 07:44:24.000000000 +0000 @@ -240,7 +240,8 @@ if (tx) { HaiCrypt_Cfg crypto_config; - HaiCrypt_ExtractConfig(hhcSrc, &crypto_config); + if (-1 == HaiCrypt_ExtractConfig(hhcSrc, &crypto_config)) + return -1; /* * Just invert the direction written in flags and use the diff -Nru srt-1.5.2/scripts/test_vista.c srt-1.5.3/scripts/test_vista.c --- srt-1.5.2/scripts/test_vista.c 1970-01-01 00:00:00.000000000 +0000 +++ srt-1.5.3/scripts/test_vista.c 2023-08-29 07:44:24.000000000 +0000 @@ -0,0 +1,10 @@ +/* Copyright © 2023 Steve Lhomme */ +/* SPDX-License-Identifier: ISC */ +#include +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600 /* _WIN32_WINNT_VISTA */ +#error NOPE +#endif +int main(void) +{ + return 0; +} diff -Nru srt-1.5.2/scripts/win-installer/libsrt.nsi srt-1.5.3/scripts/win-installer/libsrt.nsi --- srt-1.5.2/scripts/win-installer/libsrt.nsi 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/scripts/win-installer/libsrt.nsi 2023-08-29 07:44:24.000000000 +0000 @@ -125,9 +125,11 @@ ; Header files. CreateDirectory "$INSTDIR\include\srt" SetOutPath "$INSTDIR\include\srt" + File "${RepoDir}\srtcore\access_control.h" File "${RepoDir}\srtcore\logging_api.h" File "${RepoDir}\srtcore\platform_sys.h" File "${RepoDir}\srtcore\srt.h" + File "${RepoDir}\srtcore\udt.h" File "${Build64Dir}\version.h" CreateDirectory "$INSTDIR\include\win" diff -Nru srt-1.5.2/srtcore/buffer_rcv.cpp srt-1.5.3/srtcore/buffer_rcv.cpp --- srt-1.5.2/srtcore/buffer_rcv.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/buffer_rcv.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -236,10 +236,14 @@ m_iStartSeqNo = seqno; // Move forward if there are "read/drop" entries. releaseNextFillerEntries(); - // Set nonread position to the starting position before updating, - // because start position was increased, and preceding packets are invalid. - m_iFirstNonreadPos = m_iStartPos; - updateNonreadPos(); + + // If the nonread position is now behind the starting position, set it to the starting position and update. + // Preceding packets were likely missing, and the non read position can probably be moved further now. + if (CSeqNo::seqcmp(m_iFirstNonreadPos, m_iStartPos) < 0) + { + m_iFirstNonreadPos = m_iStartPos; + updateNonreadPos(); + } if (!m_tsbpd.isEnabled() && m_bMessageAPI) updateFirstReadableOutOfOrder(); return iDropCnt; @@ -257,8 +261,9 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting) { IF_RCVBUF_DEBUG(ScopedLog scoped_log); - IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropMessage: seqnolo " << seqnolo << " seqnohi " << seqnohi - << ", msgno " << msgno << " m_iStartSeqNo " << m_iStartSeqNo); + IF_RCVBUF_DEBUG(scoped_log.ss << "CRcvBuffer::dropMessage(): %(" << seqnolo << " - " << seqnohi << ")" + << " #" << msgno << " actionOnExisting=" << actionOnExisting << " m_iStartSeqNo=%" + << m_iStartSeqNo); // Drop by packet seqno range to also wipe those packets that do not exist in the buffer. const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo); @@ -293,7 +298,9 @@ if (bKeepExisting && bnd == PB_SOLO) { bDropByMsgNo = false; // Solo packet, don't search for the rest of the message. - LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): Skipped dropping an exising SOLO packet %" << packetAt(i).getSeqNo() << "."); + LOGC(rbuflog.Debug, + log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO packet %" + << packetAt(i).getSeqNo() << "."); continue; } @@ -319,13 +326,15 @@ if (bDropByMsgNo) { - // First try to drop by message number in case the message starts earlier thtan @a seqnolo. - // The sender should have the last packet of the message it is requesting to be dropped, - // therefore we don't search forward. + // If msgno is specified, potentially not the whole message was dropped using seqno range. + // The sender might have removed the first packets of the message, and thus @a seqnolo may point to a packet in the middle. + // The sender should have the last packet of the message it is requesting to be dropped. + // Therefore we don't search forward, but need to check earlier packets in the RCV buffer. + // Try to drop by the message number in case the message starts earlier than @a seqnolo. const int stop_pos = decPos(m_iStartPos); for (int i = start_pos; i != stop_pos; i = decPos(i)) { - // Can't drop is message number is not known. + // Can't drop if message number is not known. if (!m_entries[i].pUnit) // also dropped earlier. continue; @@ -336,21 +345,19 @@ if (bKeepExisting && bnd == PB_SOLO) { - LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): Skipped dropping an exising SOLO message packet %" - << packetAt(i).getSeqNo() << "."); + LOGC(rbuflog.Debug, + log << "CRcvBuffer::dropMessage(): Skipped dropping an existing SOLO message packet %" + << packetAt(i).getSeqNo() << "."); break; } ++iDropCnt; dropUnitInPos(i); m_entries[i].status = EntryState_Drop; + // As the search goes backward, i is always earlier than minDroppedOffset. + minDroppedOffset = offPos(m_iStartPos, i); - if (minDroppedOffset == -1) - minDroppedOffset = offPos(m_iStartPos, i); - else - minDroppedOffset = min(offPos(m_iStartPos, i), minDroppedOffset); - - // Break the loop if the start of message has been found. No need to search further. + // Break the loop if the start of the message has been found. No need to search further. if (bnd == PB_FIRST) break; } diff -Nru srt-1.5.2/srtcore/buffer_tools.cpp srt-1.5.3/srtcore/buffer_tools.cpp --- srt-1.5.2/srtcore/buffer_tools.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/buffer_tools.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -138,23 +138,138 @@ const bool early_update = (m_InRatePeriod < INPUTRATE_RUNNING_US) && (m_iInRatePktsCount > INPUTRATE_MAX_PACKETS); const uint64_t period_us = count_microseconds(time - m_tsInRateStartTime); - if (early_update || period_us > m_InRatePeriod) + if (!early_update && period_us <= m_InRatePeriod) + return; + + // Required Byte/sec rate (payload + headers) + m_iInRateBytesCount += (m_iInRatePktsCount * CPacket::SRT_DATA_HDR_SIZE); + m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / period_us); + HLOGC(bslog.Debug, + log << "updateInputRate: pkts:" << m_iInRateBytesCount << " bytes:" << m_iInRatePktsCount + << " rate=" << (m_iInRateBps * 8) / 1000 << "kbps interval=" << period_us); + m_iInRatePktsCount = 0; + m_iInRateBytesCount = 0; + m_tsInRateStartTime = time; + + setInputRateSmpPeriod(INPUTRATE_RUNNING_US); +} + +CSndRateEstimator::CSndRateEstimator(const time_point& tsNow) + : m_tsFirstSampleTime(tsNow) + , m_iFirstSampleIdx(0) + , m_iCurSampleIdx(0) + , m_iRateBps(0) +{ + +} + +void CSndRateEstimator::addSample(const time_point& ts, int pkts, size_t bytes) +{ + const int iSampleDeltaIdx = (int) count_milliseconds(ts - m_tsFirstSampleTime) / SAMPLE_DURATION_MS; + const int delta = NUM_PERIODS - iSampleDeltaIdx; + + // TODO: -delta <= NUM_PERIODS, then just reset the state on the estimator. + + if (iSampleDeltaIdx >= 2 * NUM_PERIODS) { - // Required Byte/sec rate (payload + headers) - m_iInRateBytesCount += (m_iInRatePktsCount * CPacket::SRT_DATA_HDR_SIZE); - m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / period_us); - HLOGC(bslog.Debug, - log << "updateInputRate: pkts:" << m_iInRateBytesCount << " bytes:" << m_iInRatePktsCount - << " rate=" << (m_iInRateBps * 8) / 1000 << "kbps interval=" << period_us); - m_iInRatePktsCount = 0; - m_iInRateBytesCount = 0; - m_tsInRateStartTime = time; + // Just reset the estimator and start like if new. + for (int i = 0; i < NUM_PERIODS; ++i) + { + const int idx = incSampleIdx(m_iFirstSampleIdx, i); + m_Samples[idx].reset(); + + if (idx == m_iCurSampleIdx) + break; + } + + m_iFirstSampleIdx = 0; + m_iCurSampleIdx = 0; + m_iRateBps = 0; + m_tsFirstSampleTime += milliseconds_from(iSampleDeltaIdx * SAMPLE_DURATION_MS); + } + else if (iSampleDeltaIdx > NUM_PERIODS) + { + // In run-time a constant flow of samples is expected. Once all periods are filled (after 1 second of sampling), + // the iSampleDeltaIdx should be either (NUM_PERIODS - 1), + // or NUM_PERIODS. In the later case it means the start of a new sampling period. + int d = delta; + while (d < 0) + { + m_Samples[m_iFirstSampleIdx].reset(); + m_iFirstSampleIdx = incSampleIdx(m_iFirstSampleIdx); + m_tsFirstSampleTime += milliseconds_from(SAMPLE_DURATION_MS); + m_iCurSampleIdx = incSampleIdx(m_iCurSampleIdx); + ++d; + } + } - setInputRateSmpPeriod(INPUTRATE_RUNNING_US); + // Check if the new sample period has started. + const int iNewDeltaIdx = (int) count_milliseconds(ts - m_tsFirstSampleTime) / SAMPLE_DURATION_MS; + if (incSampleIdx(m_iFirstSampleIdx, iNewDeltaIdx) != m_iCurSampleIdx) + { + // Now there should be some periods (at most last NUM_PERIODS) ready to be summed, + // rate estimation updated, after which all the new entry should be added. + Sample sum; + int iNumPeriods = 0; + bool bMetNonEmpty = false; + for (int i = 0; i < NUM_PERIODS; ++i) + { + const int idx = incSampleIdx(m_iFirstSampleIdx, i); + const Sample& s = m_Samples[idx]; + sum += s; + if (bMetNonEmpty || !s.empty()) + { + ++iNumPeriods; + bMetNonEmpty = true; + } + + if (idx == m_iCurSampleIdx) + break; + } + + if (iNumPeriods == 0) + { + m_iRateBps = 0; + } + else + { + m_iRateBps = sum.m_iBytesCount * 1000 / (iNumPeriods * SAMPLE_DURATION_MS); + } + + HLOGC(bslog.Note, + log << "CSndRateEstimator: new rate estimation :" << (m_iRateBps * 8) / 1000 << " kbps. Based on " + << iNumPeriods << " periods, " << sum.m_iPktsCount << " packets, " << sum.m_iBytesCount << " bytes."); + + // Shift one sampling period to start collecting the new one. + m_iCurSampleIdx = incSampleIdx(m_iCurSampleIdx); + m_Samples[m_iCurSampleIdx].reset(); + + // If all NUM_SAMPLES are recorded, the first position has to be shifted as well. + if (delta <= 0) + { + m_iFirstSampleIdx = incSampleIdx(m_iFirstSampleIdx); + m_tsFirstSampleTime += milliseconds_from(SAMPLE_DURATION_MS); + } } + + m_Samples[m_iCurSampleIdx].m_iBytesCount += bytes; + m_Samples[m_iCurSampleIdx].m_iPktsCount += pkts; } +int CSndRateEstimator::getCurrentRate() const +{ + SRT_ASSERT(m_iCurSampleIdx >= 0 && m_iCurSampleIdx < NUM_PERIODS); + return (int) avg_iir<16, unsigned long long>(m_iRateBps, m_Samples[m_iCurSampleIdx].m_iBytesCount * 1000 / SAMPLE_DURATION_MS); +} +int CSndRateEstimator::incSampleIdx(int val, int inc) const +{ + SRT_ASSERT(inc >= 0 && inc <= NUM_PERIODS); + val += inc; + while (val >= NUM_PERIODS) + val -= NUM_PERIODS; + return val; +} } diff -Nru srt-1.5.2/srtcore/buffer_tools.h srt-1.5.3/srtcore/buffer_tools.h --- srt-1.5.2/srtcore/buffer_tools.h 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/buffer_tools.h 2023-08-29 07:44:24.000000000 +0000 @@ -55,7 +55,8 @@ #include "common.h" -namespace srt { +namespace srt +{ /// The AvgBufSize class is used to calculate moving average of the buffer (RCV or SND) class AvgBufSize @@ -104,11 +105,9 @@ void setInputRateSmpPeriod(int period); /// Update input rate calculation. - /// @param [in] time current time in microseconds + /// @param [in] time current time /// @param [in] pkts number of packets newly added to the buffer /// @param [in] bytes number of payload bytes in those newly added packets - /// - /// @return Current size of the data in the sending list. void updateInputRate(const time_point& time, int pkts = 0, int bytes = 0); void resetInputRateSmpPeriod(bool disable = false) { setInputRateSmpPeriod(disable ? 0 : INPUTRATE_FAST_START_US); } @@ -120,13 +119,83 @@ static const int INPUTRATE_INITIAL_BYTESPS = BW_INFINITE; private: - int m_iInRatePktsCount; // number of payload bytes added since InRateStartTime - int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime + int m_iInRatePktsCount; // number of payload packets added since InRateStartTime. + int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime. time_point m_tsInRateStartTime; - uint64_t m_InRatePeriod; // usec - int m_iInRateBps; // Input Rate in Bytes/sec + uint64_t m_InRatePeriod; // usec + int m_iInRateBps; // Input Rate in Bytes/sec +}; + + +class CSndRateEstimator +{ + typedef sync::steady_clock::time_point time_point; + +public: + CSndRateEstimator(const time_point& tsNow); + + /// Add sample. + /// @param [in] time sample (sending) time. + /// @param [in] pkts number of packets in the sample. + /// @param [in] bytes number of payload bytes in the sample. + void addSample(const time_point& time, int pkts = 0, size_t bytes = 0); + + /// Retrieve estimated bitrate in bytes per second + int getRate() const { return m_iRateBps; } + + /// Retrieve estimated bitrate in bytes per second inluding the current sampling interval. + int getCurrentRate() const; + +private: + static const int NUM_PERIODS = 10; + static const int SAMPLE_DURATION_MS = 100; // 100 ms + struct Sample + { + int m_iPktsCount; // number of payload packets + int m_iBytesCount; // number of payload bytes + + void reset() + { + m_iPktsCount = 0; + m_iBytesCount = 0; + } + + Sample() + : m_iPktsCount(0) + , m_iBytesCount(0) + { + } + + Sample(int iPkts, int iBytes) + : m_iPktsCount(iPkts) + , m_iBytesCount(iBytes) + { + } + + Sample operator+(const Sample& other) + { + return Sample(m_iPktsCount + other.m_iPktsCount, m_iBytesCount + other.m_iBytesCount); + } + + Sample& operator+=(const Sample& other) + { + *this = *this + other; + return *this; + } + + bool empty() const { return m_iPktsCount == 0; } + }; + + int incSampleIdx(int val, int inc = 1) const; + + Sample m_Samples[NUM_PERIODS]; + + time_point m_tsFirstSampleTime; //< Start time of the first sameple. + int m_iFirstSampleIdx; //< Index of the first sample. + int m_iCurSampleIdx; //< Index of the current sample being collected. + int m_iRateBps; // Input Rate in Bytes/sec }; -} +} // namespace srt #endif diff -Nru srt-1.5.2/srtcore/channel.cpp srt-1.5.3/srtcore/channel.cpp --- srt-1.5.2/srtcore/channel.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/channel.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -774,8 +774,19 @@ #else DWORD size = (DWORD)(CPacket::HDR_SIZE + packet.getLength()); int addrsize = addr.size(); - int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, NULL, NULL); - res = (0 == res) ? size : -1; + WSAOVERLAPPED overlapped; + SecureZeroMemory((PVOID)&overlapped, sizeof(WSAOVERLAPPED)); + int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr.get(), addrsize, &overlapped, NULL); + + if (res == SOCKET_ERROR && NET_ERROR == WSA_IO_PENDING) + { + DWORD dwFlags = 0; + const bool bCompleted = WSAGetOverlappedResult(m_iSocket, &overlapped, &size, true, &dwFlags); + WSACloseEvent(overlapped.hEvent); + res = bCompleted ? 0 : -1; + } + + res = (0 == res) ? size : -1; #endif packet.toHL(); diff -Nru srt-1.5.2/srtcore/core.cpp srt-1.5.3/srtcore/core.cpp --- srt-1.5.2/srtcore/core.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/core.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -305,7 +305,13 @@ // m_cbPacketArrival.set(this, &CUDT::defaultPacketArrival); } -srt::CUDT::CUDT(CUDTSocket* parent): m_parent(parent) +srt::CUDT::CUDT(CUDTSocket* parent) + : m_parent(parent) +#ifdef ENABLE_MAXREXMITBW + , m_SndRexmitRate(sync::steady_clock::now()) +#endif + , m_iISN(-1) + , m_iPeerISN(-1) { construct(); @@ -328,7 +334,13 @@ } -srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor): m_parent(parent) +srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor) + : m_parent(parent) +#ifdef ENABLE_MAXREXMITBW + , m_SndRexmitRate(sync::steady_clock::now()) +#endif + , m_iISN(-1) + , m_iPeerISN(-1) { construct(); @@ -3689,14 +3701,20 @@ if (cst == CONN_CONTINUE) continue; - // Just in case it wasn't set, set this as a fallback - if (m_RejectReason == SRT_REJ_UNKNOWN) - m_RejectReason = SRT_REJ_ROGUE; + HLOGC(cnlog.Debug, + log << CONID() << "startConnect: processRendezvous returned cst=" << ConnectStatusStr(cst)); + + if (cst == CONN_REJECT) + { + // Just in case it wasn't set, set this as a fallback + if (m_RejectReason == SRT_REJ_UNKNOWN) + m_RejectReason = SRT_REJ_ROGUE; - // rejection or erroneous code. - reqpkt.setLength(m_iMaxSRTPayloadSize); - reqpkt.setControl(UMSG_HANDSHAKE); - sendRendezvousRejection(serv_addr, (reqpkt)); + // rejection or erroneous code. + reqpkt.setLength(m_iMaxSRTPayloadSize); + reqpkt.setControl(UMSG_HANDSHAKE); + sendRendezvousRejection(serv_addr, (reqpkt)); + } } if (cst == CONN_REJECT) @@ -4276,6 +4294,7 @@ { // m_RejectReason is already set, so set the reqtype accordingly m_ConnReq.m_iReqType = URQFailure(m_RejectReason); + return CONN_REJECT; } } // This should be false, make a kinda assert here. @@ -4679,7 +4698,8 @@ HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize - << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " isn=" << m_ConnRes.m_iISN + << " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " peer-ISN=" << m_ConnRes.m_iISN + << " local-ISN=" << m_iISN << " peerID=" << m_ConnRes.m_iID << " sourceIP=" << m_SourceAddr.str()); return true; @@ -4817,7 +4837,7 @@ // XXX Problem around CONN_CONFUSED! // If some too-eager packets were received from a listener // that thinks it's connected, but his last handshake was missed, - // they are collected by CRcvQueue::storePkt. The removeConnector + // they are collected by CRcvQueue::storePktClone. The removeConnector // function will want to delete them all, so it would be nice // if these packets can be re-delivered. Of course the listener // should be prepared to resend them (as every packet can be lost @@ -5456,6 +5476,10 @@ tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for. } + // We may just briefly unlocked the m_RecvLock, so we need to check m_bClosing again to avoid deadlock. + if (self->m_bClosing) + break; + if (!is_zero(tsNextDelivery)) { IF_HEAVY_LOGGING(const steady_clock::duration timediff = tsNextDelivery - tnow); @@ -5598,9 +5622,9 @@ // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced. const int authtag = (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) ? HAICRYPT_AUTHTAG_MAX : 0; m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize, authtag); - SRT_ASSERT(m_iISN != -1); - m_pRcvBuffer = new srt::CRcvBuffer(m_iISN, 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 space. + SRT_ASSERT(m_iPeerISN != -1); + m_pRcvBuffer = new srt::CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pRcvQueue->m_pUnitQueue, m_config.bMessageAPI); + // After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space. m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); m_pRcvLossList = new CRcvLossList(m_config.iFlightFlagSize); } @@ -6804,6 +6828,11 @@ return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); } +bool srt::CUDT::isRcvBufferReadyNoLock() const +{ + return m_pRcvBuffer->isRcvDataReady(steady_clock::now()); +} + // int by_exception: accepts values of CUDTUnited::ErrorHandling: // - 0 - by return value // - 1 - by exception @@ -6848,7 +6877,6 @@ ? m_pRcvBuffer->readMessage(data, len, &w_mctrl) : 0; leaveCS(m_RcvBufferLock); - w_mctrl.srctime = 0; // Kick TsbPd thread to schedule next wakeup (if running) if (m_bTsbPd) @@ -7904,7 +7932,6 @@ SRT_ASSERT(ctrlpkt.getMsgTimeStamp() != 0); int nbsent = 0; int local_prevack = 0; - #if ENABLE_HEAVY_LOGGING struct SaveBack { @@ -7927,21 +7954,22 @@ // The TSBPD thread may change the first lost sequence record (TLPKTDROP). // To avoid it the m_RcvBufferLock has to be acquired. UniqueLock bufflock(m_RcvBufferLock); - + // The full ACK should be sent to indicate there is now available space in the RCV buffer + // since the last full ACK. It should unblock the sender to proceed further. + const bool bNeedFullAck = (m_bBufferWasFull && getAvailRcvBufferSizeNoLock() > 0); int32_t ack; // First unacknowledged packet sequence number (acknowledge up to ack). if (!getFirstNoncontSequence((ack), (reason))) return nbsent; - if (m_iRcvLastAckAck == ack) + if (m_iRcvLastAckAck == ack && !bNeedFullAck) { - HLOGC(xtlog.Debug, - log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); - return nbsent; + HLOGC(xtlog.Debug, + log << CONID() << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK"); + return nbsent; } - // send out a lite ACK // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number - if (size == SEND_LITE_ACK) + if (size == SEND_LITE_ACK && !bNeedFullAck) { bufflock.unlock(); ctrlpkt.pack(UMSG_ACK, NULL, &ack, size); @@ -8079,7 +8107,7 @@ CGlobEvent::triggerEvent(); } } - else if (ack == m_iRcvLastAck) + else if (ack == m_iRcvLastAck && !bNeedFullAck) { // If the ACK was just sent already AND elapsed time did not exceed RTT, if ((steady_clock::now() - m_tsLastAckTime) < @@ -8090,7 +8118,7 @@ return nbsent; } } - else + else if (!bNeedFullAck) { // Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?) LOGC(xtlog.Error, log << CONID() << "sendCtrl(UMSG_ACK): IPE: curr %" << ack << " <% last %" << m_iRcvLastAck); @@ -8101,7 +8129,7 @@ // [[using locked(m_RcvBufferLock)]]; // Send out the ACK only if has not been received by the sender before - if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0) + if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0 || bNeedFullAck) { // NOTE: The BSTATS feature turns on extra fields above size 6 // also known as ACKD_TOTAL_SIZE_VER100. @@ -8117,10 +8145,7 @@ data[ACKD_RTT] = m_iSRTT; data[ACKD_RTTVAR] = m_iRTTVar; data[ACKD_BUFFERLEFT] = (int) getAvailRcvBufferSizeNoLock(); - // a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock - if (data[ACKD_BUFFERLEFT] < 2) - data[ACKD_BUFFERLEFT] = 2; - + m_bBufferWasFull = data[ACKD_BUFFERLEFT] == 0; if (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval) { int rcvRate; @@ -8295,7 +8320,6 @@ m_tsLastRspAckTime = currtime; m_iReXmitCount = 1; // Reset re-transmit count since last ACK } - return; } @@ -8336,14 +8360,25 @@ return; } - if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) - { - // 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 + if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0) + { + const int cwnd1 = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); + const bool bWasStuck = cwnd1<= getFlightSpan(); + // Update Flow Window Size, must update before and together with m_iSndLastAck + m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ackdata_seqno; + m_tsLastRspAckTime = currtime; + m_iReXmitCount = 1; // Reset re-transmit count since last ACK + + const int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); + if (bWasStuck && cwnd > getFlightSpan()) + { + m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE); + HLOGC(gglog.Debug, + log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize + << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno); } + } /* * We must not ignore full ack received by peer @@ -9245,6 +9280,10 @@ } setDataPacketTS(w_packet, tsOrigin); +#ifdef ENABLE_MAXREXMITBW + m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength()); +#endif + return payload; } @@ -9406,6 +9445,18 @@ return false; } +#ifdef ENABLE_MAXREXMITBW + m_SndRexmitRate.addSample(tnow, 0, 0); // Update the estimation. + const int64_t iRexmitRateBps = m_SndRexmitRate.getRate(); + const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW; + if (iRexmitRateLimitBps >= 0 && iRexmitRateBps > iRexmitRateLimitBps) + { + // Too many retransmissions, so don't send anything. + // TODO: When to wake up next time? + return false; + } +#endif + #if SRT_DEBUG_TRACE_SND g_snd_logger.state.canRexmit = true; #endif @@ -9754,7 +9805,7 @@ bool srt::CUDT::overrideSndSeqNo(int32_t seq) { // This function is intended to be called from the socket - // group managmenet functions to synchronize the sequnece in + // group management functions to synchronize the sequnece in // all sockes in the bonding group. THIS sequence given // here is the sequence TO BE STAMPED AT THE EXACTLY NEXT // sent payload. Therefore, screw up the ISN to exactly this @@ -9796,9 +9847,9 @@ // the latter is ahead with the number of packets already scheduled, but // not yet sent. - HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo - << " (unchanged)" - ); + HLOGC(gslog.Debug, + log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo + << " (unchanged)"); return true; } @@ -9992,6 +10043,22 @@ } } } + else if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM) + { + // Unencrypted packets are not allowed. + const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING); + + const steady_clock::time_point tnow = steady_clock::now(); + ScopedLock lg(m_StatsLock); + m_stats.rcvr.dropped.count(stats::BytesPackets(iDropCnt* rpkt.getLength(), iDropCnt)); + m_stats.rcvr.undecrypted.count(stats::BytesPackets(rpkt.getLength(), 1)); + if (frequentLogAllowed(tnow)) + { + LOGC(qrlog.Warn, log << CONID() << "Packet not encrypted (seqno %" << u->m_Packet.getSeqNo() << "), dropped " + << iDropCnt << ". pktRcvUndecryptTotal=" << m_stats.rcvr.undecrypted.total.count() << "."); + m_tsLogSlowDown = tnow; + } + } } if (adding_successful) diff -Nru srt-1.5.2/srtcore/core.h srt-1.5.3/srtcore/core.h --- srt-1.5.2/srtcore/core.h 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/core.h 2023-08-29 07:44:24.000000000 +0000 @@ -489,7 +489,7 @@ /// Create the CryptoControl object based on the HS packet. SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) - bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout); + bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException* eout); /// Allocates sender and receiver buffers and loss lists. SRT_ATR_NODISCARD SRT_ATTR_REQUIRES(m_ConnectionLock) @@ -720,10 +720,13 @@ SRT_ATTR_EXCLUDES(m_RcvBufferLock) bool isRcvBufferReady() const; + SRT_ATTR_REQUIRES(m_RcvBufferLock) + bool isRcvBufferReadyNoLock() const; + // TSBPD thread main function. static void* tsbpd(void* param); - /// Drop too late packets (receiver side). Updaet loss lists and ACK positions. + /// Drop too late packets (receiver side). Update loss lists and ACK positions. /// The @a seqno packet itself is not dropped. /// @param seqno [in] The sequence number of the first packets following those to be dropped. /// @return The number of packets dropped. @@ -819,6 +822,9 @@ CSndBuffer* m_pSndBuffer; // Sender buffer CSndLossList* m_pSndLossList; // Sender loss list CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window +#ifdef ENABLE_MAXREXMITBW + CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation. +#endif atomic_duration m_tdSendInterval; // Inter-packet time, in CPU clock cycles @@ -860,7 +866,7 @@ // and this is the sequence number that refers to the block at position [0]. Upon acknowledgement, // this value is shifted to the acknowledged position, and the blocks are removed from the // m_pSndBuffer buffer up to excluding this sequence number. - // XXX CONSIDER removing this field and give up the maintenance of this sequence number + // XXX CONSIDER removing this field and giving up the maintenance of this sequence number // to the sending buffer. This way, extraction of an old packet for retransmission should // require only the lost sequence number, and how to find the packet with this sequence // will be up to the sending buffer. @@ -934,7 +940,7 @@ int32_t m_iAckSeqNo; // Last ACK sequence number sync::atomic m_iRcvCurrSeqNo; // (RCV) Largest received sequence number. RcvQTh, TSBPDTh. int32_t m_iRcvCurrPhySeqNo; // Same as m_iRcvCurrSeqNo, but physical only (disregarding a filter) - + bool m_bBufferWasFull; // Indicate that RX buffer was full last time a ack was sent int32_t m_iPeerISN; // Initial Sequence Number of the peer side uint32_t m_uPeerSrtVersion; diff -Nru srt-1.5.2/srtcore/group.cpp srt-1.5.3/srtcore/group.cpp --- srt-1.5.2/srtcore/group.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/group.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1223,12 +1223,10 @@ // and therefore take over the leading role in setting the ISN. If the // second one fails, too, then the only remaining idle link will simply // go with its own original sequence. - // - // On the opposite side the reader should know that the link is inactive - // so the first received payload activates it. Activation of an idle link - // means that the very first packet arriving is TAKEN AS A GOOD DEAL, that is, - // no LOSSREPORT is sent even if the sequence looks like a "jumped over". - // Only for activated links is the LOSSREPORT sent upon seqhole detection. + + // On the opposite side, if the first packet arriving looks like a jump over, + // the corresponding LOSSREPORT is sent. For packets that are truly lost, + // the sender retransmits them, for packets that before ISN, DROPREQ is sent. // Now we can go to the idle links and attempt to send the payload // also over them. @@ -2295,13 +2293,14 @@ m_stats.recv.count(res); updateAvgPayloadSize(res); + bool canReadFurther = false; for (vector::const_iterator si = aliveMembers.begin(); si != aliveMembers.end(); ++si) { CUDTSocket* ps = *si; ScopedLock lg(ps->core().m_RcvBufferLock); if (m_RcvBaseSeqNo != SRT_SEQNO_NONE) { - int cnt = ps->core().rcvDropTooLateUpTo(CSeqNo::incseq(m_RcvBaseSeqNo)); + const int cnt = ps->core().rcvDropTooLateUpTo(CSeqNo::incseq(m_RcvBaseSeqNo)); if (cnt > 0) { HLOGC(grlog.Debug, @@ -2309,14 +2308,16 @@ << " packets after reading: m_RcvBaseSeqNo=" << m_RcvBaseSeqNo); } } - } - for (vector::const_iterator si = aliveMembers.begin(); si != aliveMembers.end(); ++si) - { - CUDTSocket* ps = *si; - if (!ps->core().isRcvBufferReady()) + + if (!ps->core().isRcvBufferReadyNoLock()) m_Global.m_EPoll.update_events(ps->m_SocketID, ps->core().m_sPollID, SRT_EPOLL_IN, false); + else + canReadFurther = true; } + if (!canReadFurther) + m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_IN, false); + return res; } LOGC(grlog.Error, log << "grp/recv: UNEXPECTED RUN PATH, ABANDONING."); @@ -2324,44 +2325,6 @@ throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); } -// [[using locked(m_GroupLock)]] -CUDTGroup::ReadPos* CUDTGroup::checkPacketAhead() -{ - typedef map::iterator pit_t; - ReadPos* out = 0; - - // This map no longer maps only ahead links. - // Here are all links, and whether ahead, it's defined by the sequence. - for (pit_t i = m_Positions.begin(); i != m_Positions.end(); ++i) - { - // i->first: socket ID - // i->second: ReadPos { sequence, packet } - // We are not interested with the socket ID because we - // aren't going to read from it - we have the packet already. - ReadPos& a = i->second; - - const int seqdiff = CSeqNo::seqcmp(a.mctrl.pktseq, m_RcvBaseSeqNo); - if (seqdiff == 1) - { - // The very next packet. Return it. - HLOGC(grlog.Debug, - log << "group/recv: Base %" << m_RcvBaseSeqNo << " ahead delivery POSSIBLE %" << a.mctrl.pktseq - << " #" << a.mctrl.msgno << " from @" << i->first << ")"); - out = &a; - } - else if (seqdiff < 1 && !a.packet.empty()) - { - HLOGC(grlog.Debug, - log << "group/recv: @" << i->first << " dropping collected ahead %" << a.mctrl.pktseq << "#" - << a.mctrl.msgno << " with base %" << m_RcvBaseSeqNo); - a.packet.clear(); - } - // In case when it's >1, keep it in ahead - } - - return out; -} - const char* CUDTGroup::StateStr(CUDTGroup::GroupState st) { static const char* const states[] = {"PENDING", "IDLE", "RUNNING", "BROKEN"}; diff -Nru srt-1.5.2/srtcore/group.h srt-1.5.3/srtcore/group.h --- srt-1.5.2/srtcore/group.h 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/group.h 2023-08-29 07:44:24.000000000 +0000 @@ -194,9 +194,6 @@ m_bConnected = false; } - // XXX BUGFIX - m_Positions.erase(id); - return !empty; } @@ -646,20 +643,6 @@ time_point m_tsStartTime; time_point m_tsRcvPeerStartTime; - struct ReadPos - { - std::vector packet; - SRT_MSGCTRL mctrl; - ReadPos(int32_t s) - : mctrl(srt_msgctrl_default) - { - mctrl.pktseq = s; - } - }; - std::map m_Positions; - - ReadPos* checkPacketAhead(); - void recv_CollectAliveAndBroken(std::vector& w_alive, std::set& w_broken); /// The function polls alive member sockets and retrieves a list of read-ready. diff -Nru srt-1.5.2/srtcore/packet.cpp srt-1.5.3/srtcore/packet.cpp --- srt-1.5.2/srtcore/packet.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/packet.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -221,6 +221,7 @@ if (m_data_owned) delete[](char*) m_PacketVector[PV_DATA].data(); m_PacketVector[PV_DATA].set(NULL, 0); + m_data_owned = false; } char* CPacket::release() @@ -241,8 +242,7 @@ { // PV_HEADER is always owned, PV_DATA may use a "borrowed" buffer. // Delete the internal buffer only if it was declared as owned. - if (m_data_owned) - delete[](char*) m_PacketVector[PV_DATA].data(); + deallocate(); } size_t CPacket::getLength() const @@ -561,10 +561,9 @@ { CPacket* pkt = new CPacket; memcpy((pkt->m_nHeader), m_nHeader, HDR_SIZE); - pkt->m_pcData = new char[m_PacketVector[PV_DATA].size()]; - memcpy((pkt->m_pcData), m_pcData, m_PacketVector[PV_DATA].size()); - pkt->m_PacketVector[PV_DATA].setLength(m_PacketVector[PV_DATA].size()); - + pkt->allocate(this->getLength()); + SRT_ASSERT(this->getLength() == pkt->getLength()); + memcpy((pkt->m_pcData), m_pcData, this->getLength()); pkt->m_DestAddr = m_DestAddr; return pkt; diff -Nru srt-1.5.2/srtcore/queue.cpp srt-1.5.3/srtcore/queue.cpp --- srt-1.5.2/srtcore/queue.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/queue.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1162,7 +1162,6 @@ while (!i->second.empty()) { CPacket* pkt = i->second.front(); - delete[] pkt->m_pcData; delete pkt; i->second.pop(); } @@ -1365,14 +1364,12 @@ { // no space, skip this packet CPacket temp; - temp.m_pcData = new char[m_szPayloadSize]; - temp.setLength(m_szPayloadSize); + temp.allocate(m_szPayloadSize); THREAD_PAUSED(); EReadStatus rst = m_pChannel->recvfrom((w_addr), (temp)); THREAD_RESUMED(); // Note: this will print nothing about the packet details unless heavy logging is on. LOGC(qrlog.Error, log << CONID() << "LOCAL STORAGE DEPLETED. Dropping 1 packet: " << temp.Info()); - delete[] temp.m_pcData; // Be transparent for RST_ERROR, but ignore the correct // data read and fake that the packet was dropped. @@ -1541,7 +1538,7 @@ if (cst == CONN_CONFUSED) { LOGC(cnlog.Warn, log << "AsyncOrRND: PACKET NOT HANDSHAKE - re-requesting handshake from peer"); - storePkt(id, unit->m_Packet.clone()); + storePktClone(id, unit->m_Packet); if (!u->processAsyncConnectRequest(RST_AGAIN, CONN_CONTINUE, &unit->m_Packet, u->m_PeerAddr)) { // Reuse previous behavior to reject a packet @@ -1616,7 +1613,7 @@ log << "AsyncOrRND: packet RESOLVED TO ID=" << id << " -- continuing through CENTRAL PACKET QUEUE"); // This is where also the packets for rendezvous connection will be landing, // in case of a synchronous connection. - storePkt(id, unit->m_Packet.clone()); + storePktClone(id, unit->m_Packet); return CONN_CONTINUE; } @@ -1680,7 +1677,6 @@ w_packet.setLength(newpkt->getLength()); w_packet.m_DestAddr = newpkt->m_DestAddr; - delete[] newpkt->m_pcData; delete newpkt; // remove this message from queue, @@ -1735,7 +1731,6 @@ log << "removeConnector: ... and its packet queue with " << i->second.size() << " packets collected"); while (!i->second.empty()) { - delete[] i->second.front()->m_pcData; delete i->second.front(); i->second.pop(); } @@ -1768,7 +1763,7 @@ return u; } -void srt::CRcvQueue::storePkt(int32_t id, CPacket* pkt) +void srt::CRcvQueue::storePktClone(int32_t id, const CPacket& pkt) { CUniqueSync passcond(m_BufferLock, m_BufferCond); @@ -1776,22 +1771,22 @@ if (i == m_mBuffer.end()) { - m_mBuffer[id].push(pkt); + m_mBuffer[id].push(pkt.clone()); passcond.notify_one(); } else { - // avoid storing too many packets, in case of malfunction or attack + // Avoid storing too many packets, in case of malfunction or attack. if (i->second.size() > 16) return; - i->second.push(pkt); + i->second.push(pkt.clone()); } } void srt::CMultiplexer::destroy() { - // Reverse order of the assigned + // Reverse order of the assigned. delete m_pRcvQueue; delete m_pSndQueue; delete m_pTimer; diff -Nru srt-1.5.2/srtcore/queue.h srt-1.5.3/srtcore/queue.h --- srt-1.5.2/srtcore/queue.h 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/queue.h 2023-08-29 07:44:24.000000000 +0000 @@ -551,7 +551,7 @@ bool ifNewEntry(); CUDT* getNewEntry(); - void storePkt(int32_t id, CPacket* pkt); + void storePktClone(int32_t id, const CPacket& pkt); private: sync::Mutex m_LSLock; diff -Nru srt-1.5.2/srtcore/socketconfig.cpp srt-1.5.3/srtcore/socketconfig.cpp --- srt-1.5.2/srtcore/socketconfig.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/socketconfig.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -236,6 +236,21 @@ } }; +#ifdef ENABLE_MAXREXMITBW +template<> +struct CSrtConfigSetter +{ + static void set(CSrtConfig& co, const void* optval, int optlen) + { + const int64_t val = cast_optval(optval, optlen); + if (val < -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + co.llMaxRexmitBW = val; + } +}; +#endif + template<> struct CSrtConfigSetter { @@ -997,6 +1012,9 @@ #ifdef ENABLE_AEAD_API_PREVIEW DISPATCH(SRTO_CRYPTOMODE); #endif +#ifdef ENABLE_MAXREXMITBW + DISPATCH(SRTO_MAXREXMITBW); +#endif #undef DISPATCH default: diff -Nru srt-1.5.2/srtcore/socketconfig.h srt-1.5.3/srtcore/socketconfig.h --- srt-1.5.2/srtcore/socketconfig.h 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/socketconfig.h 2023-08-29 07:44:24.000000000 +0000 @@ -227,6 +227,9 @@ int iSndTimeOut; // sending timeout in milliseconds int iRcvTimeOut; // receiving timeout in milliseconds int64_t llMaxBW; // maximum data transfer rate (threshold) +#ifdef ENABLE_MAXREXMITBW + int64_t llMaxRexmitBW; // maximum bandwidth limit for retransmissions (Bytes/s). +#endif // These fields keep the options for encryption // (SRTO_PASSPHRASE, SRTO_PBKEYLEN). Crypto object is @@ -289,6 +292,9 @@ , iSndTimeOut(-1) , iRcvTimeOut(-1) , llMaxBW(-1) +#ifdef ENABLE_MAXREXMITBW + , llMaxRexmitBW(-1) +#endif , bDataSender(false) , bMessageAPI(true) , bTSBPD(true) diff -Nru srt-1.5.2/srtcore/srt.h srt-1.5.3/srtcore/srt.h --- srt-1.5.2/srtcore/srt.h 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/srt.h 2023-08-29 07:44:24.000000000 +0000 @@ -242,6 +242,9 @@ #ifdef ENABLE_AEAD_API_PREVIEW SRTO_CRYPTOMODE = 62, // Encryption cipher mode (AES-CTR, AES-GCM, ...). #endif +#ifdef ENABLE_MAXREXMITBW + SRTO_MAXREXMITBW = 63, // Maximum bandwidth limit for retransmision (Bytes/s) +#endif SRTO_E_SIZE // Always last element, not a valid option. } SRT_SOCKOPT; diff -Nru srt-1.5.2/srtcore/sync_posix.cpp srt-1.5.3/srtcore/sync_posix.cpp --- srt-1.5.2/srtcore/sync_posix.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/srtcore/sync_posix.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -52,7 +52,7 @@ asm("mov %0=ar.itc" : "=r"(x)::"memory"); #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_AMD64_RDTSC uint32_t lval, hval; - asm("rdtsc" : "=a"(lval), "=d"(hval)); + asm volatile("rdtsc" : "=a"(lval), "=d"(hval)); x = hval; x = (x << 32) | lval; #elif SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_WINQPC diff -Nru srt-1.5.2/test/filelist.maf srt-1.5.3/test/filelist.maf --- srt-1.5.2/test/filelist.maf 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/filelist.maf 2023-08-29 07:44:24.000000000 +0000 @@ -1,7 +1,9 @@ HEADERS any.hpp +test_env.h SOURCES +test_main.cpp test_buffer_rcv.cpp test_common.cpp test_connection_timeout.cpp @@ -26,6 +28,7 @@ test_utilities.cpp test_reuseaddr.cpp test_socketdata.cpp +test_snd_rate_estimator.cpp # Tests for bonding only - put here! diff -Nru srt-1.5.2/test/test_bonding.cpp srt-1.5.3/test/test_bonding.cpp --- srt-1.5.2/test/test_bonding.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_bonding.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -5,16 +5,16 @@ #include #include "gtest/gtest.h" +#include "test_env.h" #include "srt.h" #include "netinet_any.h" TEST(Bonding, SRTConnectGroup) { + srt::TestInit srtinit; struct sockaddr_in sa; - srt_startup(); - const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); @@ -54,8 +54,6 @@ { std::cerr << "srt_close: " << srt_getlasterror_str() << std::endl; } - - srt_cleanup(); } #define ASSERT_SRT_SUCCESS(callform) ASSERT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str() @@ -133,8 +131,8 @@ TEST(Bonding, NonBlockingGroupConnect) { - srt_startup(); - + srt::TestInit srtinit; + const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); std::cout << "Created group socket: " << ss << '\n'; @@ -207,8 +205,6 @@ listen_promise.wait(); EXPECT_EQ(srt_close(ss), 0) << "srt_close: %s\n" << srt_getlasterror_str(); - - srt_cleanup(); } void ConnectCallback_Close(void* /*opaq*/, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token) @@ -226,8 +222,8 @@ TEST(Bonding, CloseGroupAndSocket) { - srt_startup(); - + srt::TestInit srtinit; + const int ss = srt_create_group(SRT_GTYPE_BROADCAST); ASSERT_NE(ss, SRT_ERROR); std::cout << "Created group socket: " << ss << '\n'; @@ -332,7 +328,5 @@ std::cout << "CLOSED GROUP. Now waiting for sender to exit...\n"; sender.join(); listen_promise.wait(); - - srt_cleanup(); } diff -Nru srt-1.5.2/test/test_common.cpp srt-1.5.3/test/test_common.cpp --- srt-1.5.2/test/test_common.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_common.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -2,6 +2,7 @@ #include #include "gtest/gtest.h" +#include "test_env.h" #include "utilities.h" #include "common.h" @@ -43,6 +44,7 @@ // Example IPv4 address: 192.168.0.1 TEST(CIPAddress, IPv4_pton) { + srt::TestInit srtinit; const char* peer_ip = "192.168.0.1"; const uint32_t ip[4] = {htobe32(0xC0A80001), 0, 0, 0}; test_cipaddress_pton(peer_ip, AF_INET, ip); @@ -51,6 +53,7 @@ // Example IPv6 address: 2001:db8:85a3:8d3:1319:8a2e:370:7348 TEST(CIPAddress, IPv6_pton) { + srt::TestInit srtinit; const char* peer_ip = "2001:db8:85a3:8d3:1319:8a2e:370:7348"; const uint32_t ip[4] = {htobe32(0x20010db8), htobe32(0x85a308d3), htobe32(0x13198a2e), htobe32(0x03707348)}; @@ -59,9 +62,10 @@ // Example IPv4 address: 192.168.0.1 // Maps to IPv6 address: 0:0:0:0:0:FFFF:192.168.0.1 -// Simplified: ::FFFF:192.168.0.1 +// Simplified: ::FFFF:192.168.0.1 TEST(CIPAddress, IPv4_in_IPv6_pton) { + srt::TestInit srtinit; const char* peer_ip = "::ffff:192.168.0.1"; const uint32_t ip[4] = {0, 0, htobe32(0x0000FFFF), htobe32(0xC0A80001)}; diff -Nru srt-1.5.2/test/test_connection_timeout.cpp srt-1.5.3/test/test_connection_timeout.cpp --- srt-1.5.2/test/test_connection_timeout.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_connection_timeout.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,5 +1,6 @@ -#include #include +#include +#include "test_env.h" #ifdef _WIN32 #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers @@ -16,7 +17,7 @@ class TestConnectionTimeout - : public ::testing::Test + : public ::srt::Test { protected: TestConnectionTimeout() @@ -32,10 +33,8 @@ protected: // SetUp() is run immediately before a test starts. - void SetUp() override + void setup() override { - ASSERT_EQ(srt_startup(), 0); - m_sa.sin_family = AF_INET; m_sa.sin_addr.s_addr = INADDR_ANY; m_udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); @@ -60,12 +59,11 @@ ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &m_sa.sin_addr), 1); } - void TearDown() override + void teardown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. - ASSERT_NE(closesocket(m_udp_sock), -1); - srt_cleanup(); + EXPECT_NE(closesocket(m_udp_sock), -1); } protected: diff -Nru srt-1.5.2/test/test_enforced_encryption.cpp srt-1.5.3/test/test_enforced_encryption.cpp --- srt-1.5.2/test/test_enforced_encryption.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_enforced_encryption.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -10,10 +10,11 @@ * Haivision Systems Inc. */ -#include #include #include #include +#include +#include "test_env.h" #include "srt.h" #include "sync.h" @@ -214,7 +215,7 @@ class TestEnforcedEncryption - : public ::testing::Test + : public srt::Test { protected: TestEnforcedEncryption() @@ -230,10 +231,8 @@ protected: // SetUp() is run immediately before a test starts. - void SetUp() + void setup() override { - ASSERT_EQ(srt_startup(), 0); - m_pollid = srt_epoll_create(); ASSERT_GE(m_pollid, 0); @@ -254,13 +253,12 @@ ASSERT_NE(srt_epoll_add_usock(m_pollid, m_listener_socket, &epoll_out), SRT_ERROR); } - void TearDown() + void teardown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. - ASSERT_NE(srt_close(m_caller_socket), SRT_ERROR); - ASSERT_NE(srt_close(m_listener_socket), SRT_ERROR); - srt_cleanup(); + EXPECT_NE(srt_close(m_caller_socket), SRT_ERROR) << srt_getlasterror_str(); + EXPECT_NE(srt_close(m_listener_socket), SRT_ERROR) << srt_getlasterror_str(); } diff -Nru srt-1.5.2/test/test_env.h srt-1.5.3/test/test_env.h --- srt-1.5.2/test/test_env.h 1970-01-01 00:00:00.000000000 +0000 +++ srt-1.5.3/test/test_env.h 2023-08-29 07:44:24.000000000 +0000 @@ -0,0 +1,89 @@ +#ifndef INC_SRT_TESTENV_H +#define INC_SRT_TESTENV_H + +#include +#include +#include +#include +#include "gtest/gtest.h" + + +namespace srt +{ +class TestEnv: public testing::Environment +{ +public: + static TestEnv* me; + std::vector args; + std::map> argmap; + + explicit TestEnv(int argc, char** argv) + : args(argv+1, argv+argc) + { + if (me) + throw std::invalid_argument("singleton"); + + me = this; + FillArgMap(); + } + + void FillArgMap(); + + bool OptionPresent(const std::string& key) + { + return argmap.count(key) > 0; + } + + std::string OptionValue(const std::string& key); + + // Specific test environment options + // All must be static, return bool. Arguments allowed. + // The name must start with Allowed_. + static bool Allowed_IPv6(); +}; + +#define SRTST_REQUIRES(feature,...) if (!srt::TestEnv::Allowed_##feature(__VA_ARGS__)) { return; } + + +class TestInit +{ +public: + int ninst; + + static void start(int& w_retstatus); + static void stop(); + + TestInit() { start((ninst)); } + ~TestInit() { stop(); } + + void HandlePerTestOptions(); + +}; + +class Test: public testing::Test +{ + std::unique_ptr init_holder; +public: + + virtual void setup() = 0; + virtual void teardown() = 0; + + void SetUp() override final + { + init_holder.reset(new TestInit); + init_holder->HandlePerTestOptions(); + setup(); + } + + void TearDown() override final + { + teardown(); + init_holder.reset(); + } +}; + +struct sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_family); + +} //namespace + +#endif diff -Nru srt-1.5.2/test/test_epoll.cpp srt-1.5.3/test/test_epoll.cpp --- srt-1.5.2/test/test_epoll.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_epoll.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -4,6 +4,7 @@ #include #include #include "gtest/gtest.h" +#include "test_env.h" #include "api.h" #include "epoll.h" @@ -14,8 +15,7 @@ TEST(CEPoll, InfiniteWait) { - ASSERT_EQ(srt_startup(), 0); - + srt::TestInit srtinit; const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -25,13 +25,11 @@ 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); - - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitNoSocketsInEpoll) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -47,12 +45,11 @@ EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitNoSocketsInEpoll2) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; const int epoll_id = srt_epoll_create(); ASSERT_GE(epoll_id, 0); @@ -63,12 +60,11 @@ EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitEmptyCall) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); @@ -87,12 +83,11 @@ -1, 0, 0, 0, 0), SRT_ERROR); EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, UWaitEmptyCall) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); @@ -111,12 +106,11 @@ EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitAllSocketsInEpollReleased) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); @@ -146,12 +140,11 @@ EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WaitAllSocketsInEpollReleased2) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); @@ -176,12 +169,11 @@ EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, WrongEpoll_idOnAddUSock) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); ASSERT_NE(client_sock, SRT_ERROR); @@ -199,13 +191,12 @@ EXPECT_EQ(srt_epoll_release(epoll_id), 0); - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, HandleEpollEvent) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); @@ -256,7 +247,6 @@ throw; } - EXPECT_EQ(srt_cleanup(), 0); } @@ -266,7 +256,7 @@ // be notified about connection break via polling the accepted socket. TEST(CEPoll, NotifyConnectionBreak) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; // 1. Prepare client SRTSOCKET client_sock = srt_create_socket(); @@ -376,13 +366,12 @@ if (!state_valid) cerr << "socket state: " << state << endl; - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, HandleEpollEvent2) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); @@ -438,13 +427,12 @@ throw; } - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, HandleEpollNoEvent) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); @@ -490,12 +478,11 @@ throw; } - EXPECT_EQ(srt_cleanup(), 0); } TEST(CEPoll, ThreadedUpdate) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; SRTSOCKET client_sock = srt_create_socket(); EXPECT_NE(client_sock, SRT_ERROR); @@ -556,13 +543,10 @@ cerr << ex.getErrorMessage() << endl; throw; } - - - EXPECT_EQ(srt_cleanup(), 0); } -class TestEPoll: public testing::Test +class TestEPoll: public srt::Test { protected: @@ -692,7 +676,7 @@ ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes - ASSERT_EQ(read[0], servsock); // read event is for bind socket + ASSERT_EQ(read[0], servsock); // read event is for bind socket } sockaddr_in scl; @@ -750,10 +734,8 @@ srt_close(servsock); } - void SetUp() override + void setup() override { - ASSERT_EQ(srt_startup(), 0); - m_client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, m_client_pollid); @@ -762,11 +744,10 @@ } - void TearDown() override + void teardown() override { (void)srt_epoll_release(m_client_pollid); (void)srt_epoll_release(m_server_pollid); - srt_cleanup(); } }; diff -Nru srt-1.5.2/test/test_fec_rebuilding.cpp srt-1.5.3/test/test_fec_rebuilding.cpp --- srt-1.5.2/test/test_fec_rebuilding.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_fec_rebuilding.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -3,6 +3,7 @@ #include #include "gtest/gtest.h" +#include "test_env.h" #include "packet.h" #include "fec.h" #include "core.h" @@ -15,7 +16,7 @@ using namespace std; using namespace srt; -class TestFECRebuilding: public testing::Test +class TestFECRebuilding: public srt::Test { protected: FECFilterBuiltin* fec = nullptr; @@ -31,7 +32,7 @@ PacketFilter::globalInit(); } - void SetUp() override + void setup() override { int timestamp = 10; @@ -86,7 +87,7 @@ } } - void TearDown() override + void teardown() override { delete fec; } @@ -207,7 +208,7 @@ TEST(TestFEC, ConfigExchange) { - srt_startup(); + srt::TestInit srtinit; CUDTSocket* s1; @@ -234,12 +235,11 @@ string exp_config = "fec,cols:10,rows:10,arq:never,layout:staircase"; EXPECT_TRUE(filterConfigSame(fec_configback, exp_config)); - srt_cleanup(); } TEST(TestFEC, ConfigExchangeFaux) { - srt_startup(); + srt::TestInit srtinit; CUDTSocket* s1; @@ -273,12 +273,11 @@ cout << "(NOTE: expecting a failure message)\n"; EXPECT_FALSE(m1.checkApplyFilterConfig("fec,cols:10,arq:never")); - srt_cleanup(); } TEST(TestFEC, Connection) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -328,12 +327,11 @@ EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); - srt_cleanup(); } TEST(TestFEC, ConnectionReorder) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -381,12 +379,11 @@ EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); - srt_cleanup(); } TEST(TestFEC, ConnectionFull1) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -434,11 +431,11 @@ EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); - srt_cleanup(); } + TEST(TestFEC, ConnectionFull2) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -486,12 +483,11 @@ EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); - srt_cleanup(); } TEST(TestFEC, ConnectionMess) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -539,12 +535,11 @@ EXPECT_TRUE(filterConfigSame(caller_config, fec_config_final)); EXPECT_TRUE(filterConfigSame(accept_config, fec_config_final)); - srt_cleanup(); } TEST(TestFEC, ConnectionForced) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -586,12 +581,11 @@ EXPECT_TRUE(filterConfigSame(result_config1, fec_config_final)); EXPECT_TRUE(filterConfigSame(result_config2, fec_config_final)); - srt_cleanup(); } TEST(TestFEC, RejectionConflict) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -629,12 +623,11 @@ int sclen = sizeof scl; EXPECT_EQ(srt_accept(l, (sockaddr*)& scl, &sclen), SRT_ERROR); - srt_cleanup(); } TEST(TestFEC, RejectionIncompleteEmpty) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -669,13 +662,12 @@ int sclen = sizeof scl; EXPECT_EQ(srt_accept(l, (sockaddr*)& scl, &sclen), SRT_ERROR); - srt_cleanup(); } TEST(TestFEC, RejectionIncomplete) { - srt_startup(); + srt::TestInit srtinit; SRTSOCKET s = srt_create_socket(); SRTSOCKET l = srt_create_socket(); @@ -713,7 +705,6 @@ int sclen = sizeof scl; EXPECT_EQ(srt_accept(l, (sockaddr*)& scl, &sclen), SRT_ERROR); - srt_cleanup(); } TEST_F(TestFECRebuilding, Prepare) diff -Nru srt-1.5.2/test/test_file_transmission.cpp srt-1.5.3/test/test_file_transmission.cpp --- srt-1.5.2/test/test_file_transmission.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_file_transmission.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -11,6 +11,7 @@ */ #include +#include "test_env.h" #ifdef _WIN32 #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers @@ -29,7 +30,7 @@ TEST(Transmission, FileUpload) { - srt_startup(); + srt::TestInit srtinit; // Generate the source file // We need a file that will contain more data @@ -195,5 +196,4 @@ remove("file.source"); remove("file.target"); - (void)srt_cleanup(); } diff -Nru srt-1.5.2/test/test_ipv6.cpp srt-1.5.3/test/test_ipv6.cpp --- srt-1.5.2/test/test_ipv6.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_ipv6.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,13 +1,15 @@ -#include "gtest/gtest.h" #include #include +#include "gtest/gtest.h" +#include "test_env.h" + #include "srt.h" #include "netinet_any.h" using srt::sockaddr_any; class TestIPv6 - : public ::testing::Test + : public srt::Test { protected: int yes = 1; @@ -25,10 +27,8 @@ protected: // SetUp() is run immediately before a test starts. - void SetUp() + void setup() override { - ASSERT_GE(srt_startup(), 0); - m_caller_sock = srt_create_socket(); ASSERT_NE(m_caller_sock, SRT_ERROR); // IPv6 calling IPv4 would otherwise fail if the system-default net.ipv6.bindv6only=1. @@ -38,13 +38,12 @@ ASSERT_NE(m_listener_sock, SRT_ERROR); } - void TearDown() + void teardown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. srt_close(m_listener_sock); srt_close(m_caller_sock); - srt_cleanup(); } public: @@ -140,6 +139,8 @@ TEST_F(TestIPv6, v6_calls_v6_mapped) { + SRTST_REQUIRES(IPv6); + sockaddr_any sa (AF_INET6); sa.hport(m_listen_port); @@ -157,6 +158,8 @@ TEST_F(TestIPv6, v6_calls_v6) { + SRTST_REQUIRES(IPv6); + sockaddr_any sa (AF_INET6); sa.hport(m_listen_port); diff -Nru srt-1.5.2/test/test_listen_callback.cpp srt-1.5.3/test/test_listen_callback.cpp --- srt-1.5.2/test/test_listen_callback.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_listen_callback.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,8 +1,9 @@ -#include #include #include #include #include +#include +#include "test_env.h" #ifdef _WIN32 #define INC_SRT_WIN_WINTIME // exclude gettimeofday from srt headers @@ -15,7 +16,7 @@ srt_listen_callback_fn SrtTestListenCallback; class ListenerCallback - : public testing::Test + : public srt::Test { protected: ListenerCallback() @@ -34,10 +35,8 @@ sockaddr_in sa; sockaddr* psa; - void SetUp() + void setup() { - ASSERT_EQ(srt_startup(), 0); - // Create server on 127.0.0.1:5555 server_sock = srt_create_socket(); @@ -124,7 +123,7 @@ srt_epoll_release(eid); } - void TearDown() + void teardown() { std::cout << "TeadDown: closing all sockets\n"; // Close the socket @@ -132,11 +131,9 @@ EXPECT_EQ(srt_close(server_sock), SRT_SUCCESS); // After that, the thread should exit - std::cout << "TearDown: joining accept thread\n"; + std::cout << "teardown: joining accept thread\n"; accept_thread.join(); - std::cout << "TearDown: SRT exit\n"; - - srt_cleanup(); + std::cout << "teardown: SRT exit\n"; } }; diff -Nru srt-1.5.2/test/test_losslist_rcv.cpp srt-1.5.3/test/test_losslist_rcv.cpp --- srt-1.5.2/test/test_losslist_rcv.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_losslist_rcv.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,5 +1,6 @@ #include #include "gtest/gtest.h" +#include "test_env.h" #include "common.h" #include "list.h" @@ -72,6 +73,7 @@ TEST(CRcvFreshLossListTest, CheckFreshLossList) { + srt::TestInit srtinit; std::deque floss { CRcvFreshLoss (10, 15, 5), CRcvFreshLoss (25, 29, 10), diff -Nru srt-1.5.2/test/test_main.cpp srt-1.5.3/test/test_main.cpp --- srt-1.5.2/test/test_main.cpp 1970-01-01 00:00:00.000000000 +0000 +++ srt-1.5.3/test/test_main.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -0,0 +1,180 @@ +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "test_env.h" + +#include "srt.h" +#include "netinet_any.h" + +using namespace std; + +int main(int argc, char **argv) +{ + string command_line_arg(argc == 2 ? argv[1] : ""); + testing::InitGoogleTest(&argc, argv); + testing::AddGlobalTestEnvironment(new srt::TestEnv(argc, argv)); + return RUN_ALL_TESTS(); +} + +namespace srt +{ + +TestEnv* TestEnv::me = 0; + +void TestEnv::FillArgMap() +{ + // The rule is: + // - first arguments go to an empty string key + // - if an argument has - in the beginning, name the key + // - key followed by args collected in a list + // - double dash prevents interpreting further args as option keys + + string key; + bool expectkey = true; + + argmap[""]; + + for (auto& a: args) + { + if (a.size() > 1) + { + if (expectkey && a[0] == '-') + { + if (a[1] == '-') + expectkey = false; + else if (a[1] == '/') + key = ""; + else + { + key = a.substr(1); + argmap[key]; // Make sure it exists even empty + } + + continue; + } + } + argmap[key].push_back(a); + } + + return; +} + +std::string TestEnv::OptionValue(const std::string& key) +{ + std::ostringstream out; + + auto it = argmap.find(key); + if (it != argmap.end() && !it->second.empty()) + { + auto iv = it->second.begin(); + out << (*iv); + while (++iv != it->second.end()) + { + out << " " << (*iv); + } + } + + return out.str(); +} + +// Specific functions +bool TestEnv::Allowed_IPv6() +{ + if (TestEnv::me->OptionPresent("disable-ipv6")) + { + std::cout << "TEST: IPv6 testing disabled, FORCED PASS\n"; + return false; + } + return true; +} + + +void TestInit::start(int& w_retstatus) +{ + ASSERT_GE(w_retstatus = srt_startup(), 0); +} + +void TestInit::stop() +{ + EXPECT_NE(srt_cleanup(), -1); +} + +// This function finds some interesting options among command +// line arguments and does specific things. +void TestInit::HandlePerTestOptions() +{ + // As a short example: + // use '-logdebug' option to turn on debug logging. + + if (TestEnv::me->OptionPresent("logdebug")) + { + srt_setloglevel(LOG_DEBUG); + } +} + +// Copied from ../apps/apputil.cpp, can't really link this file here. +sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_family) +{ + using namespace std; + + // Handle empty name. + // If family is specified, empty string resolves to ANY of that family. + // If not, it resolves to IPv4 ANY (to specify IPv6 any, use [::]). + if (name == "") + { + sockaddr_any result(pref_family == AF_INET6 ? pref_family : AF_INET); + result.hport(port); + return result; + } + + bool first6 = pref_family != AF_INET; + int families[2] = {AF_INET6, AF_INET}; + if (!first6) + { + families[0] = AF_INET; + families[1] = AF_INET6; + } + + for (int i = 0; i < 2; ++i) + { + int family = families[i]; + sockaddr_any result (family); + + // Try to resolve the name by pton first + if (inet_pton(family, name.c_str(), result.get_addr()) == 1) + { + result.hport(port); // same addr location in ipv4 and ipv6 + return result; + } + } + + // If not, try to resolve by getaddrinfo + // This time, use the exact value of pref_family + + sockaddr_any result; + addrinfo fo = { + 0, + pref_family, + 0, 0, + 0, 0, + NULL, NULL + }; + + addrinfo* val = nullptr; + int erc = getaddrinfo(name.c_str(), nullptr, &fo, &val); + if (erc == 0) + { + result.set(val->ai_addr); + result.len = result.size(); + result.hport(port); // same addr location in ipv4 and ipv6 + } + freeaddrinfo(val); + + return result; +} + + +} diff -Nru srt-1.5.2/test/test_many_connections.cpp srt-1.5.3/test/test_many_connections.cpp --- srt-1.5.2/test/test_many_connections.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_many_connections.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,10 +1,11 @@ #define _CRT_RAND_S // For Windows, rand_s -#include #include #include #include #include +#include +#include "test_env.h" #ifdef _WIN32 #include @@ -26,7 +27,7 @@ class TestConnection - : public ::testing::Test + : public ::srt::Test { protected: TestConnection() @@ -45,11 +46,8 @@ static const size_t NSOCK = 60; protected: - // SetUp() is run immediately before a test starts. - void SetUp() override + void setup() override { - ASSERT_EQ(srt_startup(), 0); - m_sa.sin_family = AF_INET; m_sa.sin_addr.s_addr = INADDR_ANY; @@ -83,9 +81,8 @@ ASSERT_NE(srt_listen(m_server_sock, NSOCK), -1); } - void TearDown() override + void teardown() override { - srt_cleanup(); } void AcceptLoop() diff -Nru srt-1.5.2/test/test_muxer.cpp srt-1.5.3/test/test_muxer.cpp --- srt-1.5.2/test/test_muxer.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_muxer.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,9 +1,11 @@ #include "gtest/gtest.h" +#include "test_env.h" + #include #include "srt.h" class TestMuxer - : public ::testing::Test + : public srt::Test { protected: TestMuxer() @@ -18,10 +20,8 @@ protected: // SetUp() is run immediately before a test starts. - void SetUp() + void setup() override { - ASSERT_GE(srt_startup(), 0); - m_caller_sock = srt_create_socket(); ASSERT_NE(m_caller_sock, SRT_ERROR); @@ -41,7 +41,7 @@ srt_epoll_add_usock(m_client_pollid, m_caller_sock, &epoll_out); } - void TearDown() + void teardown() override { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. @@ -49,7 +49,6 @@ srt_epoll_release(m_server_pollid); srt_close(m_listener_sock_ipv4); srt_close(m_listener_sock_ipv6); - srt_cleanup(); } public: @@ -100,6 +99,8 @@ TEST_F(TestMuxer, IPv4_and_IPv6) { + SRTST_REQUIRES(IPv6); + int yes = 1; int no = 0; diff -Nru srt-1.5.2/test/test_reuseaddr.cpp srt-1.5.3/test/test_reuseaddr.cpp --- srt-1.5.2/test/test_reuseaddr.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_reuseaddr.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,9 +1,10 @@ -#include "gtest/gtest.h" #include #include #ifndef _WIN32 #include #endif +#include "gtest/gtest.h" +#include "test_env.h" #include "common.h" #include "srt.h" @@ -23,67 +24,6 @@ } }; -// Copied from ../apps/apputil.cpp, can't really link this file here. -sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_family = AF_INET) -{ - using namespace std; - - // Handle empty name. - // If family is specified, empty string resolves to ANY of that family. - // If not, it resolves to IPv4 ANY (to specify IPv6 any, use [::]). - if (name == "") - { - sockaddr_any result(pref_family == AF_INET6 ? pref_family : AF_INET); - result.hport(port); - return result; - } - - bool first6 = pref_family != AF_INET; - int families[2] = {AF_INET6, AF_INET}; - if (!first6) - { - families[0] = AF_INET; - families[1] = AF_INET6; - } - - for (int i = 0; i < 2; ++i) - { - int family = families[i]; - sockaddr_any result (family); - - // Try to resolve the name by pton first - if (inet_pton(family, name.c_str(), result.get_addr()) == 1) - { - result.hport(port); // same addr location in ipv4 and ipv6 - return result; - } - } - - // If not, try to resolve by getaddrinfo - // This time, use the exact value of pref_family - - sockaddr_any result; - addrinfo fo = { - 0, - pref_family, - 0, 0, - 0, 0, - NULL, NULL - }; - - addrinfo* val = nullptr; - int erc = getaddrinfo(name.c_str(), nullptr, &fo, &val); - if (erc == 0) - { - result.set(val->ai_addr); - result.len = result.size(); - result.hport(port); // same addr location in ipv4 and ipv6 - } - freeaddrinfo(val); - - return result; -} - #ifdef _WIN32 // On Windows there's a function for it, but it requires an extra @@ -191,7 +131,7 @@ int epoll_out = SRT_EPOLL_OUT; srt_epoll_add_usock(client_pollid, g_client_sock, &epoll_out); - sockaddr_any sa = CreateAddr(ip, port, family); + sockaddr_any sa = srt::CreateAddr(ip, port, family); std::cout << "[T/C] Connecting to: " << sa.str() << " (" << famname << ")" << std::endl; @@ -274,7 +214,7 @@ bool bindSocket(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) { - sockaddr_any sa = CreateAddr(ip, port); + sockaddr_any sa = srt::CreateAddr(ip, port, AF_INET); std::string fam = (sa.family() == AF_INET) ? "IPv4" : "IPv6"; @@ -361,7 +301,7 @@ ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes - ASSERT_EQ(read[0], bindsock); // read event is for bind socket + ASSERT_EQ(read[0], bindsock); // read event is for bind socket } sockaddr_any scl; @@ -454,7 +394,7 @@ TEST(ReuseAddr, SameAddr1) { - ASSERT_EQ(srt_startup(), 0); + srt::TestInit srtinit; client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -475,16 +415,15 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, SameAddr2) { + srt::TestInit srtinit; std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. - ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -509,12 +448,12 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, SameAddrV6) { - ASSERT_EQ(srt_startup(), 0); + SRTST_REQUIRES(IPv6); + srt::TestInit srtinit; client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -539,17 +478,16 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, DiffAddr) { + srt::TestInit srtinit; std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. - ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -570,11 +508,11 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, Wildcard) { + srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; @@ -587,7 +525,6 @@ if (localip == "") return; // DISABLE TEST if this doesn't work. - ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -607,11 +544,12 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, Wildcard6) { + SRTST_REQUIRES(IPv6); + srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; @@ -629,7 +567,6 @@ // performed there. std::string localip_v4 = GetLocalIP(AF_INET); - ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -686,17 +623,18 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, ProtocolVersion6) { + SRTST_REQUIRES(IPv6); + + srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; return; #endif - ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -724,17 +662,17 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } TEST(ReuseAddr, ProtocolVersionFaux6) { + SRTST_REQUIRES(IPv6); + srt::TestInit srtinit; #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" "Forcing test to pass, PLEASE FIX.\n"; return; #endif - ASSERT_EQ(srt_startup(), 0); client_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, client_pollid); @@ -761,5 +699,4 @@ (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); - srt_cleanup(); } diff -Nru srt-1.5.2/test/TESTS_HOWTO.md srt-1.5.3/test/TESTS_HOWTO.md --- srt-1.5.2/test/TESTS_HOWTO.md 1970-01-01 00:00:00.000000000 +0000 +++ srt-1.5.3/test/TESTS_HOWTO.md 2023-08-29 07:44:24.000000000 +0000 @@ -0,0 +1,107 @@ +# Rules for writing tests for SRT + +## 1. Use automatic startup/cleanup management + +Note that most of the test require SRT library to be initialized for the +time of running the test. There are two methods how you can do it: + +* In a free test (`TEST` macro), declare this in the beginning: +`srt::TestInit srtinit;` + +* In a fixture (`TEST_F` macro), draw your class off `srt::Test` +(instead of `testing::Test`) + +In the fixture case you should also use names `setup/teardown` instead of +`SetUp/TearDown`. Both these things will properly initialize and destroy the +library resources. + +## 2. Do not misuse ASSERT macros + +**Be careful** where you are using `ASSERT_*` macros. In distinction to +`EXPECT_*` macros, they interrupt the testing procedure by throwing an exception. +This means that if this fires, nothing will be executed up to the end of the +current testing procedure, unless it's a destructor of some object constructed +inside the procedure. + +This means, however, that if you have any resource deallocation procedures, which +must be placed there for completion regardless of the test result, the call to +`ASSERT_*` macro will skip them, which may often lead to misexecution of the +remaining tests and have them falsely failed. If this interruption is necessary, +there are the following methods you can use to prevent skipping resource cleanup: + +* Do not cleanup anything in the testing procedure. Use the fixture's teardown +method for any cleaning. Remember also that it is not allowed to use `ASSERT_*` +macros in the teardown procedure, should you need to test additionally to the +cleanup. + +* You can also use a local class with a destructor so that cleanups will execute +no matter what happened inside the procedure + +* Last resort, keep the code that might use `ASSERT_*` macro in the try-catch +block and free the resources in the `catch` clause, then rethrow the exception. +A disadvantage of this solution is that you'll have to repeat the cleanup +procedure outside the try-catch block. + +* Use `EXPECT_` macros, but still check the condition again and skip required +parts of the test that could not be done without this resource. + +# Useful SRT test features + +## Test command line parameters + +The SRT tests support command-line parameters. They are available in test +procedures, startups, and through this you can control some execution +aspects. The gtest-specific options are being removed from the command +line by the gtest library itself; all other parameters are available for +the user. The main API access function for this is `srt::TestEnv`. This +is a fixed singleton object accessed through `srt::TestEnv::me` pointer. +These arguments are accessible through two fields: + +* `TestEnv::args`: a plain vector with all the command line arguments +* `TestEnv::argmap`: a map of arguments parsed according to the option syntax + +The option syntax is the following: + +* `-option` : single option without argument; can be tested for presence +* `-option param1 param2 param3` : multiple parameters assigned to an option + +Special markers: + +* `--`: end of options +* `-/`: end of parameters for the current option + +To specify free parameters after an option (and possibly its own parameters), +end the parameter list with the `-/` phrase. The `--` phrase means that the +rest of command line parameters are arguments for the last specified option, +even if they start with a dash. Note that a single dash has no special meaning. + +The `TestEnv::argmap` is using option names (except the initial dash) as keys +and the value is a vector of the parameters specified after the option. Free +parameters are collected under an empty string key. For convenience you can +also use two `TestEnv` helper methods: + +* `OptionPresent(name)`: returns true if the option of `name` is present in the +map (note that options without parameters have simply an empty vector assigned) + +* `OptionValue(name)`: returns a string that contains all parameters for that +option separated by a space (note that the value type in the map is a vector +of strings) + +## Test environment feature checks + +The macro `SRTST_REQUIRE` can be used to check if particular feature of the +test environment is available. This binds to the `TestEnv::Available_FEATURE` +option if used as `SRTST_REQUIRE(FEATURE)`. This macro makes the test function +exit immediately with success. The checking function should take care of +printing appropriate information about that the test was forcefully passed. + +To add more environment availability features, add more `TestEnv::Available_*` +methods. Methods must return `bool`, but may have parameters, which are passed +next to the first argument in the macro transparently. Availability can be +tested internally, or taken as a good deal basing on options, as it is +currently done with the IPv6 feature - it is declared as not available when the +test application gets the `-disable-ipv6` option. + +It is unknown what future tests could require particular system features, +so this solution is open for further extensions. + diff -Nru srt-1.5.2/test/test_snd_rate_estimator.cpp srt-1.5.3/test/test_snd_rate_estimator.cpp --- srt-1.5.2/test/test_snd_rate_estimator.cpp 1970-01-01 00:00:00.000000000 +0000 +++ srt-1.5.3/test/test_snd_rate_estimator.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -0,0 +1,113 @@ +#include +#include +#include "gtest/gtest.h" +#include "buffer_tools.h" +#include "sync.h" + +using namespace srt; +using namespace std; + +#ifdef ENABLE_MAXREXMITBW + +class CSndRateEstFixture + : public ::testing::Test +{ +protected: + CSndRateEstFixture() + : m_tsStart(sync::steady_clock::now()) + , m_rateEst(m_tsStart) + { + // initialization code here + } + + virtual ~CSndRateEstFixture() + { + // cleanup any pending stuff, but no exceptions allowed + } + +protected: + // SetUp() is run immediately before a test starts. + void SetUp() override + { + // make_unique is unfortunatelly C++14 + + } + + void TearDown() override + { + // Code here will be called just after the test completes. + // OK to throw exceptions from here if needed. + } + + const sync::steady_clock::time_point m_tsStart; + CSndRateEstimator m_rateEst; +}; + +// Check the available size of the receiver buffer. +TEST_F(CSndRateEstFixture, Empty) +{ + //EXPECT_EQ(getAvailBufferSize(), m_buff_size_pkts - 1); + EXPECT_EQ(m_rateEst.getRate(), 0); +} + + +TEST_F(CSndRateEstFixture, CBRSending) +{ + // Generate CBR sending for 2.1 seconds to wrap the buffer around. + for (int i = 0; i < 2100; ++i) + { + const auto t = m_tsStart + sync::milliseconds_from(i); + m_rateEst.addSample(t, 1, 1316); + + const auto rate = m_rateEst.getRate(); + if (i >= 100) + EXPECT_EQ(rate, 1316000) << "i=" << i; + else + EXPECT_EQ(rate, 0) << "i=" << i; + } + +} + +// Make a 1 second long pause and check that the rate is 0 again +// only for one sampling period. +TEST_F(CSndRateEstFixture, CBRSendingAfterPause) +{ + // Send 100 packets with 1000 bytes each + for (int i = 0; i < 3100; ++i) + { + if (i >= 1000 && i < 2000) + continue; + const auto t = m_tsStart + sync::milliseconds_from(i); + m_rateEst.addSample(t, 1, 1316); + + const auto rate = m_rateEst.getRate(); + if (i >= 100 && !(i >= 2000 && i < 2100)) + EXPECT_EQ(rate, 1316000) << "i=" << i; + else + EXPECT_EQ(rate, 0) << "i=" << i; + } +} + +// Make a short 0.5 second pause and check the bitrate goes down, but not to 0. +// Those empty samples should be included in bitrate estimation. +TEST_F(CSndRateEstFixture, CBRSendingShortPause) +{ + // Send 100 packets with 1000 bytes each + for (int i = 0; i < 3100; ++i) + { + if (i >= 1000 && i < 1500) + continue; + const auto t = m_tsStart + sync::milliseconds_from(i); + m_rateEst.addSample(t, 1, 1316); + + const auto rate = m_rateEst.getRate(); + if (i >= 1500 && i < 2000) + EXPECT_EQ(rate, 658000) << "i=" << i; + else if (i >= 100) + EXPECT_EQ(rate, 1316000) << "i=" << i; + else + EXPECT_EQ(rate, 0) << "i=" << i; + } +} + +#endif // ENABLE_MAXREXMITBW diff -Nru srt-1.5.2/test/test_socketdata.cpp srt-1.5.3/test/test_socketdata.cpp --- srt-1.5.2/test/test_socketdata.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_socketdata.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -4,10 +4,10 @@ #include #include "gtest/gtest.h" +#include "test_env.h" #include "srt.h" #include "netinet_any.h" -#include "apputil.hpp" using namespace std; using namespace std::chrono; @@ -15,6 +15,8 @@ TEST(SocketData, PeerName) { + srt::TestInit srtinit; + // Single-threaded one-app connect/accept action int csock = srt_create_socket(); @@ -25,7 +27,7 @@ srt_setsockflag(csock, SRTO_RCVSYN, &rd_nonblocking, sizeof (rd_nonblocking)); //srt_setsockflag(lsock, SRTO_RCVSYN, &rd_nonblocking, sizeof (rd_nonblocking)); - sockaddr_any addr = CreateAddr("127.0.0.1", 5000, AF_INET); + sockaddr_any addr = srt::CreateAddr("127.0.0.1", 5000, AF_INET); ASSERT_NE(srt_bind(lsock, addr.get(), addr.size()), -1); diff -Nru srt-1.5.2/test/test_socket_options.cpp srt-1.5.3/test/test_socket_options.cpp --- srt-1.5.2/test/test_socket_options.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_socket_options.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -10,10 +10,11 @@ * Haivision Systems Inc. */ -#include #include #include #include +#include +#include "test_env.h" // SRT includes #include "any.hpp" @@ -25,7 +26,7 @@ class TestSocketOptions - : public ::testing::Test + : public ::srt::Test { protected: TestSocketOptions() @@ -79,10 +80,9 @@ } protected: - // SetUp() is run immediately before a test starts. - void SetUp() + // setup() is run immediately before a test starts. + void setup() { - ASSERT_GE(srt_startup(), 0); const int yes = 1; memset(&m_sa, 0, sizeof m_sa); @@ -101,13 +101,12 @@ ASSERT_EQ(srt_setsockopt(m_listen_sock, 0, SRTO_SNDSYN, &yes, sizeof yes), SRT_SUCCESS); // for async connect } - void TearDown() + void teardown() { // Code here will be called just after the test completes. // OK to throw exceptions from here if needed. - ASSERT_NE(srt_close(m_caller_sock), SRT_ERROR); - ASSERT_NE(srt_close(m_listen_sock), SRT_ERROR); - srt_cleanup(); + EXPECT_NE(srt_close(m_caller_sock), SRT_ERROR); + EXPECT_NE(srt_close(m_listen_sock), SRT_ERROR); } protected: diff -Nru srt-1.5.2/test/test_unitqueue.cpp srt-1.5.3/test/test_unitqueue.cpp --- srt-1.5.2/test/test_unitqueue.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/test/test_unitqueue.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -1,6 +1,7 @@ #include #include #include "gtest/gtest.h" +#include "test_env.h" #include "queue.h" using namespace std; @@ -15,6 +16,7 @@ /// the very last element of the queue (it was skipped). TEST(CUnitQueue, Increase) { + srt::TestInit srtinit; const int buffer_size_pkts = 4; CUnitQueue unit_queue(buffer_size_pkts, 1500); @@ -35,6 +37,7 @@ /// beginning of the same queue. TEST(CUnitQueue, IncreaseAndFree) { + srt::TestInit srtinit; const int buffer_size_pkts = 4; CUnitQueue unit_queue(buffer_size_pkts, 1500); @@ -59,6 +62,7 @@ /// Thus the test checks if TEST(CUnitQueue, IncreaseAndFreeGrouped) { + srt::TestInit srtinit; const int buffer_size_pkts = 4; CUnitQueue unit_queue(buffer_size_pkts, 1500); diff -Nru srt-1.5.2/testing/testmedia.cpp srt-1.5.3/testing/testmedia.cpp --- srt-1.5.2/testing/testmedia.cpp 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/testing/testmedia.cpp 2023-08-29 07:44:24.000000000 +0000 @@ -2754,9 +2754,8 @@ if (is_multicast) { - ip_mreq_source mreq_ssm; ip_mreq mreq; - sockaddr_any maddr; + sockaddr_any maddr (AF_INET); int opt_name; void* mreq_arg_ptr; socklen_t mreq_arg_size; @@ -2777,6 +2776,8 @@ if (attr.count("source")) { +#ifdef IP_ADD_SOURCE_MEMBERSHIP + 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; @@ -2784,6 +2785,9 @@ inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr); mreq_arg_size = sizeof(mreq_ssm); mreq_arg_ptr = &mreq_ssm; +#else + throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS"); +#endif } else { diff -Nru srt-1.5.2/.travis.yml srt-1.5.3/.travis.yml --- srt-1.5.2/.travis.yml 2023-06-05 07:47:02.000000000 +0000 +++ srt-1.5.3/.travis.yml 2023-08-29 07:44:24.000000000 +0000 @@ -71,7 +71,6 @@ - BUILD_TYPE=Release - BUILD_OPTS='-DENABLE_MONOTONIC_CLOCK=ON' script: - - TESTS_IPv6="TestMuxer.IPv4_and_IPv6:TestIPv6.v6_calls_v6*:ReuseAddr.ProtocolVersion:ReuseAddr.*6" ; # Tests to skip due to lack of IPv6 support - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then export CC="x86_64-w64-mingw32-gcc"; export CXX="x86_64-w64-mingw32-g++"; @@ -95,7 +94,7 @@ fi - if [ "$TRAVIS_COMPILER" != "x86_64-w64-mingw32-g++" ]; then ulimit -c unlimited; - ./test-srt --gtest_filter="-$TESTS_IPv6"; + ./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;