[![License: MPLv2.0][license-badge]](./LICENSE)
[![Latest release][release-badge]][github releases]
-[![Quality Gate Status][sonarcloud-badge]][sonarcloud-project]
[![codecov][codecov-badge]][codecov-project]
-[![Build Status Linux and macOS][travis-badge]][travis]
[![Build Status Windows][appveyor-badge]][appveyor]
-[![Ubuntu 23.04][Ubuntu-badge]][Ubuntu-package]
+[![Ubuntu 24.04][Ubuntu-badge]][Ubuntu-package]
[![Fedora 37][fedora-badge]][fedora-package]
[![Debian][debian-badge]][debian-package]
[![Homebrew][Homebrew-badge]][Homebrew-package]
@@ -149,7 +147,7 @@ In live streaming configurations, the SRT protocol maintains a constant end-to-e
|:-----------------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------:|
| [The SRT API](./docs#srt-api-documents) | [IETF Internet Draft](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01) | [Sample Apps](./docs#sample-applications) |
| Reference documentation for the SRT library API | The SRT Protocol Internet Draft | Instructions for using test apps (`srt-live-transmit`, `srt-file-transmit`, etc.) |
-| [SRT Technical Overview](https://github.com/Haivision/srt/files/2489142/SRT_Protocol_TechnicalOverview_DRAFT_2018-10-17.pdf) | [SRT Deployment Guide](https://www.srtalliance.org/srt-deployment-guide/) | [SRT CookBook](https://srtlab.github.io/srt-cookbook) |
+| [SRT Technical Overview](https://github.com/Haivision/srt/files/2489142/SRT_Protocol_TechnicalOverview_DRAFT_2018-10-17.pdf) | [SRT Deployment Guide](https://www3.haivision.com/srt-deployment-guide/) | [SRT CookBook](https://srtlab.github.io/srt-cookbook) |
| Early draft technical overview (precursor to the Internet Draft) | A comprehensive overview of the protocol with deployment guidelines | Development notes on the SRT protocol |
| [Innovation Labs Blog](https://medium.com/innovation-labs-blog/tagged/secure-reliable-transport) | [SRTLab YouTube Channel](https://www.youtube.com/channel/UCr35JJ32jKKWIYymR1PTdpA) | [Slack](https://srtalliance.slack.com) |
| The blog on Medium with SRT-related technical articles | Technical YouTube channel with useful videos | Slack channels to get the latest updates and ask questions [Join SRT Alliance on Slack](https://slackin-srtalliance.azurewebsites.net/) |
@@ -158,8 +156,9 @@ In live streaming configurations, the SRT protocol maintains a constant end-to-e
- [Why SRT?](./docs/misc/why-srt-was-created.md) - A brief history and rationale for SRT by Marc Cymontkowski.
- [RTMP vs. SRT: Comparing Latency and Maximum Bandwidth](https://www.haivision.com/resources/white-paper/srt-versus-rtmp/) White Paper.
-- [Documentation on GitHub](./docs#documentation-overview) with SRT API documents, features decsriptions, etc.
+- [Documentation on GitHub](./docs#documentation-overview) with SRT API documents, features descriptions, etc.
- The SRT Protocol Internet Draft: [Datatracker](https://datatracker.ietf.org/doc/draft-sharabayko-srt/) | [Latest Version](https://datatracker.ietf.org/doc/html/draft-sharabayko-srt-01) | [Latest Working Copy](https://haivision.github.io/srt-rfc/draft-sharabayko-srt.html) | [GitHub Repo](https://github.com/Haivision/srt-rfc)
+- If you are curious about live streaming using SRT with QUIC datagrams as an alternative to UDP transport, take a look at the following [blog post](https://medium.com/innovation-labs-blog/live-streaming-using-srt-with-quic-datagrams-7896f7ce7bf3?source=friends_link&sk=d0a00e79861d89673e27a04260f279b5) on Medium.
## Build Instructions
@@ -216,18 +215,13 @@ By contributing code to the SRT project, you agree to license your contribution
[appveyor-badge]: https://img.shields.io/appveyor/ci/Haivision/srt/master.svg?label=Windows
[appveyor]: https://ci.appveyor.com/project/Haivision/srt
-[travis-badge]: https://img.shields.io/travis/Haivision/srt/master.svg?label=Linux/macOS
-[travis]: https://travis-ci.org/Haivision/srt
[license-badge]: https://img.shields.io/badge/License-MPLv2.0-blue
[Vcpkg-package]: https://repology.org/project/srt/versions
[Vcpkg-badge]: https://repology.org/badge/version-for-repo/vcpkg/srt.svg
-[ConanCenter-package]: https://repology.org/project/srt/versions
-[ConanCenter-badge]: https://repology.org/badge/version-for-repo/conancenter/srt.svg
-
-[sonarcloud-project]: https://sonarcloud.io/project/overview?id=srt
-[sonarcloud-badge]: https://sonarcloud.io/api/project_badges/measure?project=srt&metric=alert_status
+[ConanCenter-package]: https://conan.io/center/recipes/srt
+[ConanCenter-badge]: https://img.shields.io/conan/v/srt
[codecov-project]: https://codecov.io/gh/haivision/srt
[codecov-badge]: https://codecov.io/gh/haivision/srt/branch/master/graph/badge.svg
@@ -239,10 +233,10 @@ By contributing code to the SRT project, you agree to license your contribution
[debian-package]: https://packages.debian.org/testing/libs/libsrt1.5-gnutls
[fedora-package]: https://repology.org/project/srt/versions
-[fedora-badge]: https://repology.org/badge/version-for-repo/fedora_37/srt.svg
+[fedora-badge]: https://repology.org/badge/version-for-repo/fedora_43/srt.svg
[homebrew-package]: https://repology.org/project/srt/versions
[homebrew-badge]: https://repology.org/badge/version-for-repo/homebrew/srt.svg
[Ubuntu-package]: https://repology.org/project/srt/versions
-[Ubuntu-badge]: https://repology.org/badge/version-for-repo/ubuntu_23_04/srt.svg
+[Ubuntu-badge]: https://repology.org/badge/version-for-repo/ubuntu_26_04/srt.svg
diff --git a/apps/apputil.cpp b/apps/apputil.cpp
index 22c31521bf..7c37d8e18f 100644
--- a/apps/apputil.cpp
+++ b/apps/apputil.cpp
@@ -37,7 +37,7 @@ using namespace srt;
// See:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms742214(v=vs.85).aspx
// http://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedInternet3b.html
-#if defined(_WIN32) && !defined(HAVE_INET_PTON)
+#if defined(_WIN32) && !defined(HAVE_INET_PTON) && !defined(InetPton)
namespace // Prevent conflict in case when still defined
{
int inet_pton(int af, const char * src, void * dst)
diff --git a/apps/socketoptions.hpp b/apps/socketoptions.hpp
index b8aa67b861..ee8f1595c5 100644
--- a/apps/socketoptions.hpp
+++ b/apps/socketoptions.hpp
@@ -58,14 +58,18 @@ struct SocketOption
bool applyt(Object socket, std::string value) const;
template
- static int setso(Object socket, int protocol, int symbol, const void* data, size_t size);
+ static int setso(Object , int , int , const void* , size_t )
+ {
+ typename Object::something something = Object::something;
+ return -1;
+ }
template
bool extract(std::string value, OptionValue& val) const;
};
template<>
-inline int SocketOption::setso(int socket, int /*ignored*/, int sym, const void* data, size_t size)
+inline int SocketOption::setso(SRTSOCKET socket, int /*ignored*/, int sym, const void* data, size_t size)
{
return srt_setsockopt(socket, 0, SRT_SOCKOPT(sym), data, (int) size);
}
@@ -80,7 +84,7 @@ inline int SocketOption::setso(SRT_SOCKO
template<>
-inline int SocketOption::setso(int socket, int proto, int sym, const void* data, size_t size)
+inline int SocketOption::setso(SYSSOCKET socket, int proto, int sym, const void* data, size_t size)
{
return ::setsockopt(socket, proto, sym, (const char *)data, (int) size);
}
diff --git a/apps/srt-file-transmit.cpp b/apps/srt-file-transmit.cpp
index 327ad68097..aa3696dc5f 100644
--- a/apps/srt-file-transmit.cpp
+++ b/apps/srt-file-transmit.cpp
@@ -465,6 +465,12 @@ bool DoDownload(UriParser& us, string directory, string filename,
bool connected = false;
int pollid = -1;
string id;
+ // This will be set to TRUE if the ID has been obtained from the socket,
+ // while as caller socket it was set to this option beforehand. If it remains
+ // false, it means that it was the ID passed from the caller and extracted
+ // from the accepted socket - and as such the name can't be trusted, so
+ // if a file with this name exists, it will be not overwritten.
+ bool id_is_local = false;
ofstream ofile;
SRT_SOCKSTATUS status;
SRTSOCKET efd;
@@ -548,6 +554,7 @@ bool DoDownload(UriParser& us, string directory, string filename,
cerr << "Source connected (caller), id ["
<< id << "]" << endl;
connected = true;
+ id_is_local = true;
}
}
break;
@@ -579,10 +586,58 @@ bool DoDownload(UriParser& us, string directory, string filename,
if (!ofile.is_open())
{
- const char * fn = id.empty() ? filename.c_str() : id.c_str();
+ std::string fn;
+ bool overwrite = false;
+ if (id.empty())
+ {
+ fn = filename;
+ overwrite = true;
+ }
+ else
+ {
+ fn = id;
+ if (id_is_local)
+ overwrite = true;
+ }
directory.append("/");
directory.append(fn);
- ofile.open(directory.c_str(), ios::out | ios::trunc | ios::binary);
+
+ std::ios::openmode flags = ios::out | ios::binary;
+ if (overwrite)
+ flags = flags | ios::trunc;
+ else
+ {
+ struct stat state;
+ int st = stat(directory.c_str(), &state);
+ if (st == 0) // File can be obtained
+ {
+ cerr << "Error: File exists: " << directory << endl;
+ cerr << "Error: As the name is remote-provided, overwriting denied for security reasons." << endl;
+ goto exit;
+ }
+
+ // Additionally check if the path is PWD-based;
+ // reject any foreign-defined paths that are not
+ // effectively local.
+
+ // NOTE: The file is always copied to the directory
+ // specified locally, with the original filename. Therefore
+ // it is not allowed that the file contain a path.
+
+ static const size_t notfound = std::string::npos;
+ if ( fn.find('/') != notfound
+ || fn.find('\\') != notfound
+ || fn.find(':') != notfound
+ || fn.find("..") != notfound) // Any parent-referring
+ {
+ cerr << "Error: the foreign-specified path reaches outside PWD - REJECTED\n";
+ cerr << "Path: " << directory << endl;
+ cerr << "NOTE: remote path is only allowed to point inside the current directory\n";
+ goto exit;
+ }
+ }
+
+ ofile.open(directory, flags);
if (!ofile.is_open())
{
diff --git a/apps/srt-live-transmit.cpp b/apps/srt-live-transmit.cpp
index 0a918e6b06..e4968767d0 100644
--- a/apps/srt-live-transmit.cpp
+++ b/apps/srt-live-transmit.cpp
@@ -514,6 +514,7 @@ int main(int argc, char** argv)
return 1;
}
int events = SRT_EPOLL_IN | SRT_EPOLL_ERR;
+
switch (src->uri.type())
{
case UriParser::SRT:
@@ -537,14 +538,18 @@ int main(int argc, char** argv)
}
break;
case UriParser::FILE:
- if (srt_epoll_add_ssock(pollid,
- src->GetSysSocket(), &events))
{
- cerr << "Failed to add FILE source to poll, "
- << src->GetSysSocket() << endl;
- return 1;
+ const int con = src->GetSysSocket();
+ // try to make the standard input non blocking
+ if (srt_epoll_add_ssock(pollid, con, &events))
+ {
+ cerr << "Failed to add FILE source to poll, "
+ << src->GetSysSocket() << endl;
+ return 1;
+ }
+ break;
+
}
- break;
default:
break;
}
@@ -589,6 +594,7 @@ int main(int argc, char** argv)
SRTSOCKET srtrwfds[4] = {SRT_INVALID_SOCK, SRT_INVALID_SOCK , SRT_INVALID_SOCK , SRT_INVALID_SOCK };
int sysrfdslen = 2;
SYSSOCKET sysrfds[2];
+
if (srt_epoll_wait(pollid,
&srtrwfds[0], &srtrfdslen, &srtrwfds[2], &srtwfdslen,
100,
@@ -771,12 +777,34 @@ int main(int argc, char** argv)
break;
}
+
+ bool srcReady = false;
+
+ if (src.get() && src->IsOpen() && !src->End())
+ {
+ if (srtrfdslen > 0)
+ {
+ SRTSOCKET sock = src->GetSRTSocket();
+ if (sock != SRT_INVALID_SOCK)
+ {
+ for (int n = 0; n < srtrfdslen && !(srcReady = (sock == srtrwfds[n])); n ++);
+ }
+ }
+ if (!srcReady && sysrfdslen > 0)
+ {
+ SYSSOCKET sock = src->GetSysSocket();
+ if (sock != SYSSOCKET_INVALID)
+ {
+ for (int n = 0; n < sysrfdslen && !(srcReady = (sock == sysrfds[n])); n++);
+ }
+ }
+ }
// read a few chunks at a time in attempt to deplete
// read buffers as much as possible on each read event
// note that this implies live streams and does not
// work for cached/file sources
std::list> dataqueue;
- if (src.get() && src->IsOpen() && (srtrfdslen || sysrfdslen))
+ if (srcReady)
{
while (dataqueue.size() < cfg.buffering)
{
@@ -800,9 +828,10 @@ int main(int argc, char** argv)
dataqueue.push_back(pkt);
receivedBytes += pkt->payload.size();
+ if (src->MayBlock())
+ break;
}
}
-
// if there is no target, let the received data be lost
while (!dataqueue.empty())
{
@@ -870,7 +899,7 @@ void TestLogHandler(void* opaque, int level, const char* file, int line, const c
#ifdef _MSC_VER
// That's something weird that happens on Microsoft Visual Studio 2013
- // Trying to keep portability, while every version of MSVS is a different plaform.
+ // Trying to keep portability, while every version of MSVS is a different platform.
// On MSVS 2015 there's already a standard-compliant snprintf, whereas _snprintf
// is available on backward compatibility and it doesn't work exactly the same way.
#define snprintf _snprintf
diff --git a/apps/srt-tunnel.cpp b/apps/srt-tunnel.cpp
index 90bd43eae9..9ca3e2cc28 100644
--- a/apps/srt-tunnel.cpp
+++ b/apps/srt-tunnel.cpp
@@ -387,7 +387,7 @@ void Engine::Worker()
throw Medium::ReadEOF("");
case Medium::RD_AGAIN:
- // Theoreticall RD_AGAIN should not be reported
+ // Theoretically RD_AGAIN should not be reported
// because it should be taken care of internally by
// repeated sending - unless we get m_broken set.
// If it is, however, it should be handled just like error.
diff --git a/apps/statswriter.cpp b/apps/statswriter.cpp
index f1aa343b59..42b4194de6 100644
--- a/apps/statswriter.cpp
+++ b/apps/statswriter.cpp
@@ -55,6 +55,7 @@ struct SrtStatsTableInit
STAT(SEND, packetsLost, pktSndLoss);
STAT(SEND, packetsDropped, pktSndDrop);
STAT(SEND, packetsRetransmitted, pktRetrans);
+ STAT(SEND, quality, pctSndQuality);
STAT(SEND, packetsFilterExtra, pktSndFilterExtra);
STAT(SEND, bytes, byteSent);
STAT(SEND, bytesUnique, byteSentUnique);
@@ -67,6 +68,8 @@ struct SrtStatsTableInit
STAT(RECV, packets, pktRecv);
STAT(RECV, packetsUnique, pktRecvUnique);
STAT(RECV, packetsLost, pktRcvLoss);
+ STAT(RECV, packetsLostConfirmed, pktRcvLossConfirmed);
+ STAT(RECV, quality, pctRcvQuality);
STAT(RECV, packetsDropped, pktRcvDrop);
STAT(RECV, packetsRetransmitted, pktRcvRetrans);
STAT(RECV, packetsBelated, pktRcvBelated);
diff --git a/apps/transmitbase.hpp b/apps/transmitbase.hpp
index bd0af916db..4590877bc1 100644
--- a/apps/transmitbase.hpp
+++ b/apps/transmitbase.hpp
@@ -70,7 +70,8 @@ class Source: public Location
};
virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; }
- virtual int GetSysSocket() const { return -1; }
+ virtual SYSSOCKET GetSysSocket() const { return -1; }
+ virtual bool MayBlock() const { return false; }
virtual bool AcceptNewClient() { return false; }
};
@@ -86,7 +87,7 @@ class Target: public Location
virtual ~Target() {}
virtual SRTSOCKET GetSRTSocket() const { return SRT_INVALID_SOCK; }
- virtual int GetSysSocket() const { return -1; }
+ virtual SYSSOCKET GetSysSocket() const { return -1; }
virtual bool AcceptNewClient() { return false; }
};
diff --git a/apps/transmitmedia.cpp b/apps/transmitmedia.cpp
index 2752951734..7a158c9d7f 100644
--- a/apps/transmitmedia.cpp
+++ b/apps/transmitmedia.cpp
@@ -704,9 +704,15 @@ Iface* CreateSrt(const string& host, int port, const map& par) {
class ConsoleSource: public Source
{
+ const bool may_block = true;
public:
ConsoleSource()
+#ifdef _WIN32
+ : may_block(true)
+#else
+ : may_block(fcntl(fileno(stdin), F_SETFL, fcntl(fileno(stdin), F_GETFL) | O_NONBLOCK) < 0)
+#endif
{
#ifdef _WIN32
// The default stdin mode on windows is text.
@@ -720,9 +726,8 @@ class ConsoleSource: public Source
if (pkt.payload.size() < chunk)
pkt.payload.resize(chunk);
- bool st = cin.read(pkt.payload.data(), chunk).good();
- chunk = cin.gcount();
- if (chunk == 0 || !st)
+ const int ret = ::read(GetSysSocket(), pkt.payload.data(), chunk);
+ if (ret <= 0)
{
pkt.payload.clear();
return 0;
@@ -731,14 +736,15 @@ class ConsoleSource: public Source
// Save this time to potentially use it for SRT target.
pkt.time = srt_time_now();
if (chunk < pkt.payload.size())
- pkt.payload.resize(chunk);
+ pkt.payload.resize(ret);
- return (int) chunk;
+ return ret;
}
bool IsOpen() override { return cin.good(); }
+ bool MayBlock() const final { return may_block; }
bool End() override { return cin.eof(); }
- int GetSysSocket() const override { return 0; };
+ SYSSOCKET GetSysSocket() const override { return fileno(stdin); };
};
class ConsoleTarget: public Target
@@ -767,7 +773,7 @@ class ConsoleTarget: public Target
bool IsOpen() override { return cout.good(); }
bool Broken() override { return cout.eof(); }
- int GetSysSocket() const override { return 0; };
+ SYSSOCKET GetSysSocket() const override { return fileno(stdout); };
};
template struct Console;
@@ -798,15 +804,17 @@ static inline bool IsMulticast(in_addr adr)
class UdpCommon
{
protected:
- int m_sock = -1;
- sockaddr_any sadr;
+ SYSSOCKET m_sock = SYSSOCKET_INVALID;
string adapter;
+ sockaddr_any interface_addr;
+ sockaddr_any target_addr;
+ bool is_multicast = false;
map m_options;
void Setup(string host, int port, map attr)
{
- m_sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (m_sock == -1)
+ m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (m_sock == SYSSOCKET_INVALID)
Error(SysError(), "UdpCommon::Setup: socket");
int yes = 1;
@@ -815,33 +823,35 @@ class UdpCommon
// set non-blocking mode
#if defined(_WIN32)
unsigned long ulyes = 1;
- if (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR)
+ const bool ioctl_error = (ioctlsocket(m_sock, FIONBIO, &ulyes) == SOCKET_ERROR);
#else
- if (ioctl(m_sock, FIONBIO, (const char *)&yes) < 0)
+ const bool ioctl_error = (ioctl(m_sock, FIONBIO, (const char *)&yes) == -1);
#endif
+ if (ioctl_error)
{
Error(SysError(), "UdpCommon::Setup: ioctl FIONBIO");
}
- sadr = CreateAddr(host, port);
+ interface_addr = CreateAddr("", port, AF_INET);
+ target_addr = CreateAddr(host, port);
- bool is_multicast = false;
+ is_multicast = false;
if (attr.count("multicast"))
{
// XXX: Here provide support for IPv6 multicast #1479
- if (sadr.family() != AF_INET)
+ if (target_addr.family() != AF_INET)
{
throw std::runtime_error("UdpCommon: Multicast on IPv6 is not yet supported");
}
- if (!IsMulticast(sadr.sin.sin_addr))
+ if (!IsMulticast(target_addr.sin.sin_addr))
{
throw std::runtime_error("UdpCommon: requested multicast for a non-multicast-type IP address");
}
is_multicast = true;
}
- else if (sadr.family() == AF_INET && IsMulticast(sadr.sin.sin_addr))
+ else if (target_addr.family() == AF_INET && IsMulticast(target_addr.sin.sin_addr))
{
is_multicast = true;
}
@@ -849,23 +859,19 @@ class UdpCommon
if (is_multicast)
{
ip_mreq mreq;
- sockaddr_any maddr (AF_INET);
int opt_name;
- void* mreq_arg_ptr;
+ const char* mreq_arg_ptr;
socklen_t mreq_arg_size;
adapter = attr.count("adapter") ? attr.at("adapter") : string();
- if ( adapter == "" )
+ if (adapter == "")
{
Verb() << "Multicast: home address: INADDR_ANY:" << port;
- maddr.sin.sin_family = AF_INET;
- maddr.sin.sin_addr.s_addr = htonl(INADDR_ANY);
- maddr.sin.sin_port = htons(port); // necessary for temporary use
}
else
{
Verb() << "Multicast: home address: " << adapter << ":" << port;
- maddr = CreateAddr(adapter, port);
+ interface_addr = CreateAddr(adapter, port);
}
if (attr.count("source"))
@@ -874,11 +880,11 @@ class UdpCommon
ip_mreq_source mreq_ssm;
/* this is an ssm. we need to use the right struct and opt */
opt_name = IP_ADD_SOURCE_MEMBERSHIP;
- mreq_ssm.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr;
- mreq_ssm.imr_interface.s_addr = maddr.sin.sin_addr.s_addr;
+ mreq_ssm.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr;
+ mreq_ssm.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr;
inet_pton(AF_INET, attr.at("source").c_str(), &mreq_ssm.imr_sourceaddr);
mreq_arg_size = sizeof(mreq_ssm);
- mreq_arg_ptr = &mreq_ssm;
+ mreq_arg_ptr = (const char*)&mreq_ssm;
#else
throw std::runtime_error("UdpCommon: source-filter multicast not supported by OS");
#endif
@@ -886,39 +892,20 @@ class UdpCommon
else
{
opt_name = IP_ADD_MEMBERSHIP;
- mreq.imr_multiaddr.s_addr = sadr.sin.sin_addr.s_addr;
- mreq.imr_interface.s_addr = maddr.sin.sin_addr.s_addr;
+ mreq.imr_multiaddr.s_addr = target_addr.sin.sin_addr.s_addr;
+ mreq.imr_interface.s_addr = interface_addr.sin.sin_addr.s_addr;
mreq_arg_size = sizeof(mreq);
- mreq_arg_ptr = &mreq;
+ mreq_arg_ptr = (const char*)&mreq;
}
#ifdef _WIN32
- const char* mreq_arg = (const char*)mreq_arg_ptr;
const auto status_error = SOCKET_ERROR;
#else
- const void* mreq_arg = mreq_arg_ptr;
const auto status_error = -1;
#endif
+ auto res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg_ptr, mreq_arg_size);
-#if defined(_WIN32) || defined(__CYGWIN__)
- // On Windows it somehow doesn't work when bind()
- // is called with multicast address. Write the address
- // that designates the network device here.
- // Also, sets port sharing when working with multicast
- sadr = maddr;
- int reuse = 1;
- int shareAddrRes = setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&reuse), sizeof(reuse));
- if (shareAddrRes == status_error)
- {
- throw runtime_error("marking socket for shared use failed");
- }
- Verb() << "Multicast(Windows): will bind to home address";
-#else
- Verb() << "Multicast(POSIX): will bind to IGMP address: " << host;
-#endif
- int res = setsockopt(m_sock, IPPROTO_IP, opt_name, mreq_arg, mreq_arg_size);
-
- if ( res == status_error )
+ if (res == status_error)
{
Error(errno, "adding to multicast membership failed");
}
@@ -970,7 +957,7 @@ class UdpCommon
~UdpCommon()
{
#ifdef _WIN32
- if (m_sock != -1)
+ if (m_sock != SYSSOCKET_INVALID)
{
shutdown(m_sock, SD_BOTH);
closesocket(m_sock);
@@ -992,8 +979,35 @@ class UdpSource: public Source, public UdpCommon
UdpSource(string host, int port, const map& attr)
{
Setup(host, port, attr);
- int stat = ::bind(m_sock, sadr.get(), sadr.size());
- if ( stat == -1 )
+
+ // On Windows it somehow doesn't work when bind()
+ // is called with multicast address. Write the address
+ // that designates the network device here, always.
+
+ // Hance we have two fields holding the address:
+ // * target_addr: address from which reading will be done
+ // * unicast: local address of the interface
+ // * multicast: the IGMP address
+ // * interface_addr: address of the local interface
+ // (same as target_addr for unicast)
+
+ // In case of multicast, binding should be done:
+ // On most POSIX systems, to the multicast address (target_addr in this case)
+ // On Windows, to a local address (hence use interface_addr, as in this
+ // case it differs to target_addr).
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+ sockaddr_any baddr = is_multicast ? interface_addr : target_addr;
+ static const char* const sysname = "Windows";
+#else
+ static const char* const sysname = "POSIX";
+ sockaddr_any baddr = target_addr;
+#endif
+ Verb("UDP:", is_multicast ? "Multicast" : "Unicast", "(", sysname,
+ "): will bind to: ", baddr.str());
+ int stat = ::bind(m_sock, baddr.get(), baddr.size());
+
+ if (stat == -1)
Error(SysError(), "Binding address for UDP");
eof = false;
}
@@ -1003,7 +1017,7 @@ class UdpSource: public Source, public UdpCommon
if (pkt.payload.size() < chunk)
pkt.payload.resize(chunk);
- sockaddr_any sa(sadr.family());
+ sockaddr_any sa(target_addr.family());
socklen_t si = sa.size();
int stat = recvfrom(m_sock, pkt.payload.data(), (int) chunk, 0, sa.get(), &si);
if (stat < 1)
@@ -1024,10 +1038,10 @@ class UdpSource: public Source, public UdpCommon
return stat;
}
- bool IsOpen() override { return m_sock != -1; }
+ bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; }
bool End() override { return eof; }
- int GetSysSocket() const override { return m_sock; };
+ SYSSOCKET GetSysSocket() const override { return m_sock; };
};
class UdpTarget: public Target, public UdpCommon
@@ -1039,15 +1053,14 @@ class UdpTarget: public Target, public UdpCommon
cerr << "\nWARN Host for UDP target is not provided. Will send to localhost:" << port << ".\n";
Setup(host, port, attr);
- if (adapter != "")
+ if (is_multicast && interface_addr.isany() == false)
{
- sockaddr_any maddr = CreateAddr(adapter, 0);
- if (maddr.family() != AF_INET)
+ if (interface_addr.family() != AF_INET)
{
Error(0, "UDP/target: IPv6 multicast not supported in the application");
}
- in_addr addr = maddr.sin.sin_addr;
+ in_addr addr = interface_addr.sin.sin_addr;
int res = setsockopt(m_sock, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&addr), sizeof(addr));
if (res == -1)
@@ -1060,7 +1073,7 @@ class UdpTarget: public Target, public UdpCommon
int Write(const char* data, size_t len, int64_t src_time SRT_ATR_UNUSED, ostream & ignored SRT_ATR_UNUSED = cout) override
{
- int stat = sendto(m_sock, data, (int) len, 0, sadr.get(), sadr.size());
+ int stat = sendto(m_sock, data, (int)len, 0, target_addr.get(), target_addr.size());
if ( stat == -1 )
{
if ((false))
@@ -1070,10 +1083,10 @@ class UdpTarget: public Target, public UdpCommon
return stat;
}
- bool IsOpen() override { return m_sock != -1; }
+ bool IsOpen() override { return m_sock != SYSSOCKET_INVALID; }
bool Broken() override { return false; }
- int GetSysSocket() const override { return m_sock; };
+ SYSSOCKET GetSysSocket() const override { return m_sock; };
};
template struct Udp;
diff --git a/common/win/unistd.h b/common/win/unistd.h
index 9c61cad8aa..063f2a086e 100644
--- a/common/win/unistd.h
+++ b/common/win/unistd.h
@@ -3,7 +3,7 @@
/* This file intended to serve as a drop-in replacement for
* unistd.h on Windows
- * Please add functionality as neeeded
+ * Please add functionality as needed
*/
#include
diff --git a/configure-data.tcl b/configure-data.tcl
index 3510a9645e..0faec42b82 100644
--- a/configure-data.tcl
+++ b/configure-data.tcl
@@ -34,47 +34,49 @@ set internal_options {
# Options that refer directly to variables used in CMakeLists.txt
set cmake_options {
+ atomic-use-srt-sync-mutex "Use mutex to implement atomics (alias: --with-atomic=sync-mutex) (default: OFF)"
cygwin-use-posix "Should the POSIX API be used for cygwin. Ignored if the system isn't cygwin. (default: OFF)"
- enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON, with gcc < 4.7 OFF)"
+ enable-aead-api-preview "Enable AEAD API preview in SRT (default: OFF)"
enable-apps "Should the Support Applications be Built? (default: ON)"
enable-bonding "Enable 'bonding' SRT feature (default: OFF)"
- enable-testing "Should developer testing applications be built (default: OFF)"
- enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)"
- enable-logging "Should logging be enabled (default: ON)"
- enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)"
+ enable-c++11 "Should the c++11 parts (srt-live-transmit) be enabled (default: ON, with gcc < 4.7 OFF)"
+ enable-c++-deps "Extra library dependencies in srt.pc for C language (default: ON)"
+ enable-clang-tsa "Enable Clang's Thread-Safety-Analysis (default: OFF)"
+ enable-code-coverage "Enable code coverage reporting (default: OFF)"
+ enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)"
+ enable-encryption "Should encryption features be enabled (default: ON)"
+ enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)"
enable-haicrypt-logging "Should logging in haicrypt be enabled (default: OFF)"
+ enable-heavy-logging "Should heavy debug logging be enabled (default: OFF)"
+ enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)"
+ enable-logging "Should logging be enabled (default: ON)"
+ enable-maxrexmitbw "Enable SRTO_MAXREXMITBW (v1.6.0 API preview) (default: OFF)"
+ enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)"
enable-pktinfo "Should pktinfo reading and using be enabled (POSIX only) (default: OFF)"
+ enable-profile "Should instrument the code for profiling. Ignored for non-GNU compiler. (default: OFF)"
+ enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)"
enable-shared "Should libsrt be built as a shared library (default: ON)"
+ enable-show-project-config "Enables use of ShowProjectConfig() in cmake (default: OFF)"
+ enable-sock-cloexec "Enable setting SOCK_CLOEXEC on a socket (default: ON)"
enable-static "Should libsrt be built as a static library (default: ON)"
- enable-relative-libpath "Should applications contain relative library paths, like ../lib (default: OFF)"
- enable-getnameinfo "In-logs sockaddr-to-string should do rev-dns (default: OFF)"
- enable-unittests "Enable Unit Tests (will download Google UT) (default: OFF)"
- enable-encryption "Should encryption features be enabled (default: ON)"
- enable-c++-deps "Extra library dependencies in srt.pc for C language (default: ON)"
- use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)"
- enable-inet-pton "Set to OFF to prevent usage of inet_pton when building against modern SDKs (default: ON)"
- enable-code-coverage "Enable code coverage reporting (default: OFF)"
- enable-monotonic-clock "Enforced clock_gettime with monotonic clock on GC CV /temporary fix for #729/ (default: OFF)"
- enable-thread-check "Enable #include that implements THREAD_* macros"
enable-stdc++-sync "Use standard C++11 chrono/threads instead of pthread wrapper (default: OFF, on Windows: ON)"
- use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)"
- openssl-use-static-libs "Link OpenSSL statically (default: OFF)."
- use-busy-waiting "Enable more accurate sending times at a cost of potentially higher CPU load (default: OFF)"
- use-gnustl "Get c++ library/headers from the gnustl.pc"
- enable-sock-cloexec "Enable setting SOCK_CLOEXEC on a socket (default: ON)"
- enable-show-project-config "Enables use of ShowProjectConfig() in cmake (default: OFF)"
- enable-new-rcvbuffer "Enables the new receiver buffer implementation (default: ON)"
- enable-clang-tsa "Enable Clang's Thread-Safety-Analysis (default: OFF)"
- atomic-use-srt-sync-mutex "Use mutex to implement atomics (alias: --with-atomic=sync-mutex) (default: OFF)"
-
- use-enclib "Encryption library to be used: openssl(default), gnutls, mbedtls, botan"
- enable-debug=<0,1,2> "Enable debug mode (0=disabled, 1=debug, 2=rel-with-debug)"
- pkg-config-executable= "pkg-config executable"
+ enable-testing "Should developer testing applications be built (default: OFF)"
+ enable-thread-check "Enable #include that implements THREAD_* macros"
+ enable-unittests-discovery "Enable UT Discovery (will run when building) (default: ON)"
+ enable-unittests "Enable Unit Tests (will download Google UT) (default: OFF)"
openssl-crypto-library= "OpenSSL: Path to a libcrypto library."
openssl-include-dir= "OpenSSL: Path to includes."
openssl-ssl-library= "OpenSSL: Path to a libssl library."
+ openssl-use-static-libs "Link OpenSSL statically (default: OFF)."
+ pkg-config-executable= "pkg-config executable"
pthread-include-dir= "PThread: Path to includes"
pthread-library= "PThread: Path to the pthread library."
+ srt-use-openssl-static-libs "Link OpenSSL libraries statically. (default: OFF)"
+ use-busy-waiting "Enable more accurate sending times at a cost of potentially higher CPU load (default: OFF)"
+ use-enclib "Encryption library to be used: openssl(default), gnutls, mbedtls, botan"
+ use-gnustl "Get c++ library/headers from the gnustl.pc"
+ use-openssl-pc "Use pkg-config to find OpenSSL libraries (default: ON)"
+ use-static-libstdc++ "Should use static rather than shared libstdc++ (default: OFF)"
}
set options $internal_options$cmake_options
diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md
index 2fdb8a6610..0e6da34b62 100644
--- a/docs/API/API-functions.md
+++ b/docs/API/API-functions.md
@@ -269,14 +269,24 @@ exiting the application that uses the SRT library. This cleanup function will st
be called from the C++ global destructor, if not called by the application, although
relying on this behavior is strongly discouraged.
+**IMPORTANT NOTES**:
+
+1. This function must be called from within `main()`, preferably at the end.
+Calling it from any C++ global destructor is pointless, as SRT does it by
+itself. But relying on it is strongly discouraged - at best only if you are
+completely certain that all resources in the application are maintained in the
+strict creation-destruction order, including threads. If this condition isn't
+satisfied, then the behavior of the cleanup outside of `main()` is undefined.
+
+2. The startup/cleanup calls have an instance counter. This means that if you
+call [`srt_startup`](#srt_startup) multiple times, you need to call the
+`srt_cleanup` function exactly the same number of times.
+
| Returns | |
|:----------------------------- |:--------------------------------------------------------------- |
| 0 | A possibility to return other values is reserved for future use |
| | |
-**IMPORTANT**: Note that the startup/cleanup calls have an instance counter.
-This means that if you call [`srt_startup`](#srt_startup) multiple times, you need to call the
-`srt_cleanup` function exactly the same number of times.
[:arrow_up: Back to List of Functions & Structures](#srt-api-functions)
@@ -612,9 +622,10 @@ the listener socket to accept group connections
| [`SRT_EINVPARAM`](#srt_einvparam) | Value of `backlog` is 0 or negative. |
| [`SRT_EINVSOCK`](#srt_einvsock) | Socket [`u`](#u) indicates no valid SRT socket. |
| [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) | [`srt_bind`](#srt_bind) has not yet been called on that socket. |
+| [`SRT_ESCLOSED`](#srt_esclosed) | The socket has been closed |
| [`SRT_ERDVNOSERV`](#srt_erdvnoserv) | [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) flag is set to true on specified socket. |
| [`SRT_EINVOP`](#srt_einvop) | Internal error (should not happen when [`SRT_EUNBOUNDSOCK`](#srt_eunboundsock) is reported). |
-| [`SRT_ECONNSOCK`](#srt_econnsock) | The socket is already connected. |
+| [`SRT_ECONNSOCK`](#srt_econnsock) | The socket is currently being used to establish a connection (like by `srt_connect`) |
| [`SRT_EDUPLISTEN`](#srt_eduplisten) | The address used in [`srt_bind`](#srt_bind) by this socket is already occupied by another listening socket. Binding multiple sockets to one IP address and port is allowed, as long as [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to true, but only one of these sockets can be set up as a listener. |
| | |
@@ -2841,13 +2852,13 @@ associated with the last error. The system error is:
const char* srt_strerror(int code, int errnoval);
```
-Returns a string message that represents a given SRT error code and possibly the
-`errno` value, if not 0.
+Returns a string message that represents a given SRT error code.
-**NOTE:** *This function isn't thread safe. It uses a static variable to hold the
-error description. There's no problem with using it in a multithreaded environment,
-as long as only one thread in the whole application calls this function at the
-moment*
+**NOTE:** *The `errnoval` parameter is ignored. This function's old version
+was intended to get both the SRT error description and system error description,
+but this requires resolution of the reentrancy problem and dynamic strings.
+For getting the error description for a system error, you need to use the
+`strerror` function or some of its reentrant version.*
[:arrow_up: Back to List of Functions & Structures](#srt-api-functions)
diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md
index 3291f6e278..0d49b9a2e0 100644
--- a/docs/API/API-socket-options.md
+++ b/docs/API/API-socket-options.md
@@ -201,16 +201,16 @@ The following table lists SRT API socket options in alphabetical order. Option d
| Option Name | Since | Restrict | Type | Units | Default | Range | Dir |Entity |
| :------------------------------------------------------ | :---: | :------: | :-------: | :-----: | :---------------: | :------: |:---:|:-----:|
-| [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | | | RW | GSD+ |
+| [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) | 1.4.2 | pre-bind | `string` | | "" | \* | RW | S |
| [`SRTO_CONGESTION`](#SRTO_CONGESTION) | 1.3.0 | pre | `string` | | "live" | \* | W | S |
| [`SRTO_CONNTIMEO`](#SRTO_CONNTIMEO) | 1.1.2 | pre | `int32_t` | ms | 3000 | 0.. | W | GSD+ |
-| [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE) | 1.5.2 | pre | `int32_t` | | 0 (Auto) | [0, 2] | W | GSD |
+| [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE) | 1.5.2 | pre | `int32_t` | | 0 (Auto) | [0, 2] | W | GSD |
| [`SRTO_DRIFTTRACER`](#SRTO_DRIFTTRACER) | 1.4.2 | post | `bool` | | true | | RW | GSD |
| [`SRTO_ENFORCEDENCRYPTION`](#SRTO_ENFORCEDENCRYPTION) | 1.3.2 | pre | `bool` | | true | | W | GSD |
| [`SRTO_EVENT`](#SRTO_EVENT) | | | `int32_t` | flags | | | R | S |
| [`SRTO_FC`](#SRTO_FC) | | pre | `int32_t` | pkts | 25600 | 32.. | RW | GSD |
| [`SRTO_GROUPCONNECT`](#SRTO_GROUPCONNECT) | 1.5.0 | pre | `int32_t` | | 0 | 0...1 | W | S |
-| [`SRTO_GROUPMINSTABLETIMEO`](#SRTO_GROUPMINSTABLETIMEO) | 1.5.0 | pre | `int32_t` | ms | 60 | 60-... | W | GDI+ |
+| [`SRTO_GROUPMINSTABLETIMEO`](#SRTO_GROUPMINSTABLETIMEO) | 1.5.0 | pre | `int32_t` | ms | 60 | 60-... | W | GDI |
| [`SRTO_GROUPTYPE`](#SRTO_GROUPTYPE) | 1.5.0 | | `int32_t` | enum | | | R | S |
| [`SRTO_INPUTBW`](#SRTO_INPUTBW) | 1.0.5 | post | `int64_t` | B/s | 0 | 0.. | RW | GSD |
| [`SRTO_IPTOS`](#SRTO_IPTOS) | 1.0.5 | pre-bind | `int32_t` | | (system) | 0..255 | RW | GSD |
@@ -254,6 +254,7 @@ The following table lists SRT API socket options in alphabetical order. Option d
| [`SRTO_SNDKMSTATE`](#SRTO_SNDKMSTATE) | 1.2.0 | | `int32_t` | enum | | | R | S |
| [`SRTO_SNDSYN`](#SRTO_SNDSYN) | | post | `bool` | | true | | RW | GSI |
| [`SRTO_SNDTIMEO`](#SRTO_SNDTIMEO) | | post | `int32_t` | ms | -1 | -1.. | RW | GSI |
+| [`SRTO_SRTLA`](#SRTO_SRTLA) | fork | pre-bind | `bool` | | false | | RW | S |
| [`SRTO_STATE`](#SRTO_STATE) | | | `int32_t` | enum | | | R | S |
| [`SRTO_STREAMID`](#SRTO_STREAMID) | 1.3.0 | pre | `string` | | "" | [512] | RW | GSD |
| [`SRTO_TLPKTDROP`](#SRTO_TLPKTDROP) | 1.0.6 | pre | `bool` | | \* | | RW | GSD |
@@ -263,13 +264,55 @@ The following table lists SRT API socket options in alphabetical order. Option d
| [`SRTO_UDP_SNDBUF`](#SRTO_UDP_SNDBUF) | | pre-bind | `int32_t` | bytes | 65536 | \* | RW | GSD+ |
| [`SRTO_VERSION`](#SRTO_VERSION) | 1.1.0 | | `int32_t` | | | | R | S |
+### Short summary for some general options' characteristics
+
+The following options cannot be set on a group:
+
+* [`SRTO_BINDTODEVICE`](#SRTO_BINDTODEVICE) - link-specific
+* [`SRTO_CONGESTION`](#SRTO_CONGESTION) - "live" mode is the only supported for groups
+* [`SRTO_GROUPCONNECT`](#SRTO_GROUPCONNECT) - to be set for a listener only
+* [`SRTO_RENDEZVOUS`](#SRTO_RENDEZVOUS) - groups support only caller-listener mode
+* [`SRTO_SENDER`](#SRTO_SENDER) - legacy option for <1.3.0, not available for bonding
+* [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) - live mode (default) is the only supported for groups
+* [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE)
+
+The following options cannot be retrieved from a group:
+
+* [`SRTO_EVENT`](#SRTO_EVENT) - not supported
+* [`SRTO_GROUPTYPE`](#SRTO_GROUPTYPE) - this is information for an incoming socket only
+* [`SRTO_ISN`](#SRTO_ISN) - socket-specific
+* [`SRTO_KMSTATE`](#SRTO_KMSTATE) - connection-specific
+* [`SRTO_RCVDATA`](#SRTO_RCVDATA) - socket-specific
+* [`SRTO_RCVKMSTATE`](#SRTO_RCVKMSTATE) - connection-specific
+* [`SRTO_SNDDATA`](#SRTO_SNDDATA) - socket-specific (sender buffer is still one per socket, also in groups)
+* [`SRTO_SNDKMSTATE`](#SRTO_SNDKMSTATE) - connection-specific
+* [`SRTO_STATE`](#SRTO_STATE) - socket-specific state for the connection
+* [`SRTO_VERSION`](#SRTO_VERSION) - not supported
+
+The following options can be set on a socket or on a group, but for
+a group it has a different meaning than for a socket and they also
+cannot be set on a group member socket. The reason for all of them
+is such that the socket usually has this option set as required for
+the group internals to work properly, while a member socket cannot
+be used for reading and writing operations.
+
+* [`SRTO_RCVSYN`](#SRTO_RCVSYN)
+* [`SRTO_RCVTIMEO`](#SRTO_RCVTIMEO)
+* [`SRTO_SNDSYN`](#SRTO_SNDSYN)
+* [`SRTO_SNDTIMEO`](#SRTO_SNDTIMEO)
+
+The following options can be set only on a group and not on a socket:
+
+* [`SRTO_GROUPMINSTABLETIMEO`](#SRTO_GROUPMINSTABLETIMEO) - specific for a group of type `SRT_GTYPE_BACKUP`.
+
+
### Option Descriptions
#### SRTO_BINDTODEVICE
| OptName | Since | Restrict | Type | Units | Default | Range | Dir |Entity|
| --------------------- | ----- | -------- | -------- | ------ | -------- | ------ |-----|------|
-| `SRTO_BINDTODEVICE` | 1.4.2 | pre-bind | `string` | | | | RW | GSD+ |
+| `SRTO_BINDTODEVICE` | 1.4.2 | pre-bind | `string` | | | | RW | S |
Refers to the `SO_BINDTODEVICE` system socket option for `SOL_SOCKET` level.
This effectively limits the packets received by this socket to only those
@@ -304,7 +347,7 @@ if an appropriate instruction was given in the Stream ID.
Currently supported congestion controllers are designated as "live" and "file",
which correspond to the Live and File modes.
-Note that it is not recommended to change this option manually, but you should
+Note that it is not recommended to change this option directly, but you should
rather change the whole set of options using the [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE) option.
[Return to list](#list-of-options)
@@ -317,9 +360,11 @@ rather change the whole set of options using the [`SRTO_TRANSTYPE`](#SRTO_TRANST
| ------------------ | ----- | -------- | --------- | ------ | -------- | ------ | --- | ------ |
| `SRTO_CONNTIMEO` | 1.1.2 | pre | `int32_t` | msec | 3000 | 0.. | W | GSD+ |
-Connect timeout. This option applies to the caller and rendezvous connection
-modes. For the rendezvous mode (see `SRTO_RENDEZVOUS`) the effective connection timeout
-will be 10 times the value set with `SRTO_CONNTIMEO`.
+Connection timeout value in milliseconds. This is the time up to which the connecting
+facility will attempt to connect and wait for the response from the remote endpoint
+before giving up with error status. The value applies for both caller and
+rendezvous modes. For the rendezvous mode (see `SRTO_RENDEZVOUS`) the effective
+connection timeout will be 10 times the value set with `SRTO_CONNTIMEO`.
[Return to list](#list-of-options)
@@ -614,6 +659,8 @@ See [`SRTO_INPUTBW`](#SRTO_INPUTBW).
IPv4 Type of Service (see `IP_TOS` option for IP) or IPv6 Traffic Class (see `IPV6_TCLASS`
of IPv6) depending on socket address family. Applies to sender only.
+NOTE: This option has been tested to work correctly on Linux only.
+
When *getting*, the returned value is the user preset for non-connected sockets
and the actual value for connected sockets.
@@ -632,6 +679,8 @@ and the actual value for connected sockets.
IPv4 Time To Live (see `IP_TTL` option for IP) or IPv6 unicast hops (see
`IPV6_UNICAST_HOPS` for IPv6) depending on socket address family. Applies to sender only.
+NOTE: This option has been tested to work correctly on Linux only.
+
When *getting*, the returned value is the user preset for non-connected sockets
and the actual value for connected sockets.
@@ -1064,7 +1113,7 @@ For details, see [SRT Packet Filtering & FEC](../features/packet-filtering-and-f
Sets the passphrase for encryption. This enables encryption on this party (or
disables it, if an empty passphrase is passed). The password must be minimum
-10 and maximum 79 characters long.
+10 and maximum 80 characters long.
The passphrase is the shared secret between the sender and the receiver. It is
used to generate the Key Encrypting Key using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)
@@ -1299,6 +1348,9 @@ This value is only significant when [`SRTO_TSBPDMODE`](#SRTO_TSBPDMODE) is enabl
**Default value**: 120 ms in Live mode, 0 in File mode (see [`SRTO_TRANSTYPE`](#SRTO_TRANSTYPE)).
+On connections accepted by an SRTLA listener ([`SRTO_SRTLA`](#SRTO_SRTLA)) the value is never below
+1000 ms; a lower setting is raised on accept.
+
The latency value defines the **minimum** receiver buffering delay before delivering an SRT data packet
from a receiving SRT socket to a receiving application.
@@ -1580,6 +1632,33 @@ if in "non-blocking mode". The -1 value means no time limit.
---
+#### SRTO_SRTLA
+
+| OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity |
+| ------------ | ----- | -------- | ------ | ----- | ------- | ----- | --- | ------ |
+| `SRTO_SRTLA` | fork | pre-bind | `bool` | | false | | RW | S |
+
+Set on a listener before binding, this designates the socket's UDP port as an SRTLA (SRT Link
+Aggregation) demux: bonded links register on it with the SRTLA protocol, their packets are merged
+into one SRT connection, and the receiver's SRT control packets are fanned back out over the links.
+A muxer designated this way never merges with a plain SRT muxer.
+
+Accepted connections inherit the flag. On them it enables the SRTLA multipath delivery tuning
+(time-based loss reporting, reorder hold, loss-report fan-out) and enforces the **SRTLA minimum
+receiver latency of 1000 ms**:
+
+- A lower [`SRTO_RCVLATENCY`](#SRTO_RCVLATENCY) / [`SRTO_LATENCY`](#SRTO_LATENCY) on the listener is
+ raised to 1000 ms when the connection is accepted (logged at `note` level), and the raised value is
+ negotiated to the sender as its peer latency. The sender then buffers for the raised value as well.
+- The raise is one-directional: a latency of 1000 ms or more is left as configured.
+- Rationale: the reorder hold over bonded links may grow up to half the latency (capped at
+ 500 ms) and one full recovery round trip has to fit behind it; below 1000 ms a lost
+ retransmission can no longer be recovered before the play deadline.
+
+Encoders for bonded streaming default to 2000 ms (BELABOX) or 3000 ms; the practical
+recommendation for cellular bonding is 2000 ms or more. Latencies up to 5000 ms are exercised by
+the end-to-end harness in `testing/srtla-e2e`.
+
#### SRTO_STATE
| OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity |
@@ -1603,7 +1682,7 @@ will be able to retrieve this stream ID from the socket that is returned from
`srt_accept` (for a connected socket with that stream ID). You usually use SET
on the socket used for `srt_connect`, and GET on the socket retrieved from
`srt_accept`. This string can be used completely free-form. However, it's highly
-recommended to follow the [SRT Access Control (Stream ID) Guidlines](../features/access-control.md).
+recommended to follow the [SRT Access Control (Stream ID) Guidelines](../features/access-control.md).
- As this uses internally the `std::string` type, there are additional functions
for it in the legacy/C++ API (udt.h): `srt::setstreamid` and `srt::getstreamid`.
diff --git a/docs/API/statistics.md b/docs/API/statistics.md
index bc34ecca8b..0e4b2d31e8 100644
--- a/docs/API/statistics.md
+++ b/docs/API/statistics.md
@@ -182,7 +182,7 @@ If the packet `s` is received out of order (`s < next_exp`), the statistic is no
Note that only original (not retransmitted) SRT DATA packets are taken into account. Refer to [pktRcvRetransTotal](#pktRcvRetransTotal) for the formula for obtaining the total number of lost retransmitted packets.
-In SRT v1.4.0, v1.4.1, the `pktRcvLossTotal` statistic includes packets that failed to be decrypted. To receive the number of presently missing packets, substract [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) from the current one. This is going to be fixed in SRT v.1.5.0.
+In SRT v1.4.0, v1.4.1, the `pktRcvLossTotal` statistic includes packets that failed to be decrypted. To receive the number of presently missing packets, subtract [pktRcvUndecryptTotal](#pktRcvUndecryptTotal) from the current one. This is going to be fixed in SRT v.1.5.0.
#### pktRetransTotal
@@ -245,7 +245,7 @@ Packets may be dropped conditionally when both `SRTO_TSBPDMODE` and `SRTO_TLPKTD
#### pktRcvUndecryptTotal
The total number of packets that failed to be decrypted at the receiver side. Available for receiver.
-The statistic also counts unencrypted packets that were expected to be uncrypted on a secured connection (see [SRTO_KM_S_SECURED](API-socket-options.md#srt_km_state))
+The statistic also counts unencrypted packets that were expected to be unencrypted on a secured connection (see [SRTO_KM_S_SECURED](API-socket-options.md#srt_km_state))
and hence dropped as not encrypted (undecrypted).
#### pktSndFilterExtraTotal
@@ -657,7 +657,7 @@ Instantaneous (current) value of `pktRcvBuf`, expressed in bytes, including payl
The timespan (msec) of acknowledged packets in the receiver's buffer. Receiver side.
-If TSBPD mode is enabled (defualt for **live mode**),
+If TSBPD mode is enabled (default for **live mode**),
a packet can be acknowledged, but not yet ready to play.
This range includes all packets regardless of whether
they are ready to play or not.
diff --git a/docs/README.md b/docs/README.md
index 8c07ee515d..83baf258bd 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -7,7 +7,7 @@
| [SRT API](API/API.md) | [API](API/) | [API.md](API/API.md) | Detailed description of the SRT C API. |
| [SRT API Functions](API/API-functions.md) | [API](API/) | [API-functions.md](API/API-functions.md) | Reference document for SRT API functions. |
| [SRT API Socket Options](API/API-socket-options.md) | [API](API/) | [API-socket-options.md](API/API-socket-options.md) | Instructions and list of socket options for SRT API. |
-| [SRT Rejection Codes](API/rejections-codes.md) | [API](API/) | [rejection-codes.md](API/rejection-codes.md) | The list of SRT rejections codes. |
+| [SRT Rejection Codes](API/rejection-codes.md) | [API](API/) | [rejection-codes.md](API/rejection-codes.md) | The list of SRT rejections codes. |
| [SRT Statistics](API/statistics.md) | [API](API/) | [statistics.md](API/statistics.md) | How to use SRT socket and socket group statistics. |
| [Configuration Guidelines](API/configuration-guidelines.md) | [API](API/) | [configuration-guidelines.md](API/configuration-guidelines.md) | How to configure SRT buffers. |
| | | | |
diff --git a/docs/apps/srt-file-transmit.md b/docs/apps/srt-file-transmit.md
index 2bf3b11159..04afb1c452 100644
--- a/docs/apps/srt-file-transmit.md
+++ b/docs/apps/srt-file-transmit.md
@@ -40,7 +40,7 @@ If you use sender in listener mode, then you start it with specifying either the
full filename path, or only the directory where the file is located; in the latter
case the filename will be tried from the `streamid` option extracted from the
connected socket (as set by the other side's caller). If the full filename was
-specified, it must match the rootname extraced from this option, or otherwise
+specified, it must match the rootname extracted from this option, or otherwise
transmission will not be done.
If you use receiver in listener mode, then you start it with specifying either
diff --git a/docs/apps/srt-live-transmit.md b/docs/apps/srt-live-transmit.md
index 48741df236..718508ba0c 100644
--- a/docs/apps/srt-live-transmit.md
+++ b/docs/apps/srt-live-transmit.md
@@ -236,7 +236,7 @@ the presence of the **adapter** parameter:
* Caller mode: if you specify *host* part, but not **adapter** parameter:
- `srt://remote.host.com:1234`
* Rendezvous mode: if you specify *host* AND **adapter** parameter:
- - `srt://remote.host.com:1234&adapter=my.remote.addr`
+ - `srt://remote.host.com:1234?adapter=my.remote.addr`
Sometimes the required parameter specification results in a different mode
than desired; in this case you should specify the mode explicitly.
diff --git a/docs/build/build-android.md b/docs/build/build-android.md
index 7c13c9404b..da35414070 100644
--- a/docs/build/build-android.md
+++ b/docs/build/build-android.md
@@ -1,6 +1,6 @@
# Building SRT on Android
-**NOTE:** The scripts have been moved to [scripts/build-android](../../scripts/build-android/) folder.
+**NOTE:** The scripts are located in [scripts/build-android](../../scripts/build-android/) folder.
## Install the NDK and CMake
diff --git a/docs/build/build-iOS.md b/docs/build/build-iOS.md
index a1fd48ba95..c6d58844f2 100644
--- a/docs/build/build-iOS.md
+++ b/docs/build/build-iOS.md
@@ -1,44 +1,28 @@
-# Building SRT on iOS
+# Building SRT on iOS/tvOS
+
+**NOTE:** The scripts are located in [scripts/build-ios](../../scripts/build-ios/) folder.
## Prerequisites
* Xcode should be installed. Check in terminal whether `xcode-select -p` points to **/Applications/Xcode.app/Contents/Developer**
* Install Homebrew according to instructions on [https://brew.sh/](https://brew.sh/)
-* Install CMake and pkg-config with Homebrew:
+* Install CMake with Homebrew:
```
brew install cmake
-brew install pkg-config
```
## Building OpenSSL
-There is [OpenSSL for iPhone](https://github.com/x2on/OpenSSL-for-iPhone) project which have all necessary to build OpenSSL for our needs. It fetches OpenSSL code by itself, so you don't need to download it separately. So simply clone it and build with command:
+There is [OpenSSL-Universal](https://github.com/krzyzanowskim/OpenSSL) project which has all necessary to build OpenSSL for our needs. It fetches OpenSSL code by itself, so you don't need to download it separately. Build it with command:
```
-./build-libssl.sh --archs="arm64"
+./mkssl-xcf.sh
```
-Results (both libraries and headers) will be placed in bin/<SDK_VERSION>-<ARCH>.sdk directory (for example, *bin/iPhoneOS11.2-arm64.sdk*). We assume you set **IOS_OPENSSL** variable to this path (e.g. `export IOS_OPENSSL="/Users/johndoe/sources/OpenSSL-for-iPhone/bin/iPhoneOS11.2-arm64.sdk"`).
-
## Building SRT code
-Now you can build SRT providing path to OpenSSL library and toolchain file for iOS
-
+Now you can build SRT with command:
```
-./configure --cmake-prefix-path=$IOS_OPENSSL --use-openssl-pc=OFF --cmake-toolchain-file=scripts/iOS.cmake
-make
+./mksrt-xcf.sh
```
-Optionally you may add following iOS-specifc settings to configure:
-
-* `--ios-disable-bitcode=1` - disable embedding bitcode to library.
-* `--ios-arch=armv7|armv7s|arm64` - specify if you want to build for specific architecture (arm64 by default)
-* `--ios-platform=OS|SIMULATOR|SIMULATOR64` - specify for build simulator code
-* `--cmake-ios-developer-root=<path>`- specify path for platform directory; {XCODE_ROOT}/Platforms/iPhoneOS.platform/Developer by default
-* `--cmake-ios-sdk-root=` - by default searches for latest SDK version within {CMAKE_IOS_DEVELOPER_ROOT}/SDKs, set if you want to use another SDK version
-
-Note that resulting .dylib file has install path @executable_path/Frameworks/libsrt.1.dylib, so if you need to place it in some other place with your application, you may change it with *install_name_tool* command: ``install_name_tool -id "" ``, for example ``install_name_tool -id "@executable_path/Frameworks/libsrt.1.3.0.dylib" libsrt.1.3.0.dylib``
-
## Adding to Xcode project
-In Xcode project settings in General tab, add libsrt to **Linked Frameworks and Libraries** section - click Plus sign, then click "Add Other" and find libsrt.1.dylib
-
-Click plus sign in **Embedded binaries** section and choose Frameworks/libsrt.1.dylib
+Results (libcrypto.xcframework and libsrt.xcframework) will be placed in [scripts/build-ios](../../scripts/build-ios/) folder.
-In **Build settings** tab find **Header Search Paths** setting
-and add paths to SRT library sources (you should add srt, srt/common and srt/common directories).
+Follow [these steps](https://developer.apple.com/documentation/xcode/creating-a-static-framework#Embed-your-framework-in-an-app) to embed frameworks in your app. Choose the Do Not Embed option from the Embed value list.
diff --git a/docs/build/build-options.md b/docs/build/build-options.md
index 529bd5cd7e..2ec4d454a5 100644
--- a/docs/build/build-options.md
+++ b/docs/build/build-options.md
@@ -17,6 +17,50 @@ Additional information on building for Windows is available in the
document and in the [SRT CookBook](https://srtlab.github.io/srt-cookbook/getting-started/build-on-windows/).
+## Building as a subproject
+
+The CMake tool offers the ability to add a complete project as a subdirectory.
+If you do this with SRT, note that all variables that can be optionally set
+will get values from the parent configuration file including the one from SRT.
+
+To allow isolation of these variables and setting them explicitly to desired
+values in case when the parent project uses variables with the same names,
+there's a special feature provided: set the desired variables for the SRT
+project using `LIBSRT_` prefix - this way they will get the values to the
+right variables, visible only in the scope of the SRT build configuration.
+
+NOTE: This feature needs to be generally enabled by:
+
+```
+set (LIBSRT_ENABLE_IMPORT_VARIABLES 1)
+```
+
+otherwise all other variables with `LIBSRT_` prefix will be ignored.
+
+This will not prevent the variables from being seen as derived in SRT project
+scope, but if you explicitly set a variable this way, it will be set to the
+desired value inside the SRT project. It will not set the same variable in the
+parent project, and it will also override (locally in SRT project only) any
+value of a variable with the same name in the parent project.
+
+For example, if you want to set `ENABLE_SHARED=OFF` in the parent project,
+add this before importing the SRT project:
+
+```
+set (LIBSRT_ENABLE_IMPORT_VARIABLES 1)
+set (LIBSRT_ENABLE_SHARED OFF)
+```
+
+If you already have a variable named `ENABLE_SHARED` in your project (existing
+before the call to `add_subdirectory` with SRT), its value will be derived in
+the SRT project, unless you override it by setting `LIBSRT_ENABLE_SHARED` to a
+different value.
+
+Note that the trick works simply by getting the actual variable name through
+cutting off the `LIBSRT_` prefix; the check whether this variable is of any use
+will be done after that.
+
+
## List of Build Options
The following table lists available build options in alphabetical order.
@@ -64,7 +108,7 @@ Option details are given further below.
| [`USE_ENCLIB`](#use_enclib) | 1.3.3 | `STRING` | openssl | Encryption library to be used (`openssl`, `openssl-evp` (since 1.5.1), `gnutls`, `mbedtls`, `botan` (since 1.6.0)). |
| [`USE_GNUSTL`](#use_gnustl) | 1.3.4 | `BOOL` | OFF | Use `pkg-config` with the `gnustl` package name to extract the header and library path for the C++ standard library. |
| [`USE_OPENSSL_PC`](#use_openssl_pc) | 1.3.0 | `BOOL` | ON | Use `pkg-config` to find OpenSSL libraries. |
-| [`OPENSSL_USE_STATIC_LIBS`](#openssl_use_static_libs) | 1.5.0 | `BOOL` | OFF | Link OpenSSL statically. |
+| [`SRT_USE_OPENSSL_STATIC_LIBS`](#srt_use_openssl_static_libs)| 1.5.0 | `BOOL` | OFF | Link OpenSSL statically. |
| [`USE_STATIC_LIBSTDCXX`](#use_static_libstdcxx) | 1.2.0 | `BOOL` | OFF | Enforces linking the SRT library against the static `libstdc++` library. |
| [`WITH_COMPILER_PREFIX`](#with_compiler_prefix) | 1.3.0 | `STRING` | OFF | Sets C/C++ toolchains as `` and ``, overriding the default compiler. |
| [`WITH_COMPILER_TYPE`](#with_compiler_type) | 1.3.0 | `STRING` | OFF | Sets the compiler type to be used (values: gcc, cc, clang, etc.). |
@@ -618,8 +662,8 @@ built-in one).
When ON, uses `pkg-config` to find OpenSSL libraries. You can turn this OFF to
force `cmake` to find OpenSSL by its own preferred method.
-### OPENSSL_USE_STATIC_LIBS
-**`--openssl-use-static-libs`** (default: OFF)
+### SRT_USE_OPENSSL_STATIC_LIBS
+**`--srt-use-openssl-static-libs`** (default: OFF)
When ON, OpenSSL libraries are linked statically.
When `pkg-config`(`-DUSE_OPENSSL_PC=ON`) is used, static OpenSSL libraries are listed in `SSL_STATIC_LIBRARIES`. See `_STATIC` in [CMake's FindPkgConfig](https://cmake.org/cmake/help/latest/module/FindPkgConfig.html).
diff --git a/docs/build/build-win.md b/docs/build/build-win.md
index e8a1e63517..1a7d7a754c 100644
--- a/docs/build/build-win.md
+++ b/docs/build/build-win.md
@@ -2,33 +2,34 @@
-- [1. Prerequisites](#1-prerequisites)
- - [1.1. Build Tool Dependencies](#11-build-tool-dependencies)
- - [1.2. External Library Dependencies](#12-external-library-dependencies)
- - [1.2.1. Cryptographic Library](#121-cryptographic-library)
- - [1.2.2. Threading Library](#122-threading-library)
- - [1.3. Package Managers](#13-package-managers)
- - [1.3.1. VCpkg Packet Manager (optional)](#131-vcpkg-packet-manager-optional)
- - [1.3.2. NuGet Manager (optional)](#132-nuget-manager-optional)
-- [2. Preparing Dependencies](#2-preparing-dependencies)
- - [2.1. Cryptographic Library](#21-cryptographic-library)
- - [2.1.1. Install OpenSSL](#211-install-openssl)
- - [2.1.1.1. Using vcpkg](#2111-using-vcpkg)
- - [2.1.1.2. Using Installer](#2112-using-installer-windows)
- - [2.1.1.3. Build from Sources](#2113-build-from-sources)
- - [2.1.2. Install MbedTLS](#212-install-mbedtls)
- - [2.1.3. Install LibreSSL](#213-install-libressl)
- - [2.2. Threading Library](#22-threading-library)
- - [2.2.1. Using C++11 Threading](#221-using-c11-threading)
- - [2.2.2. Building PThreads](#222-building-pthreads)
- - [2.2.2.1. Using vcpkg](#2221-using-vcpkg)
- - [2.2.2.2. Using NuGet](#2222-using-nuget)
- - [2.2.2.3. Build pthreads4w from Sources](#2223-build-pthreads4w-from-sources)
- - [2.2.2.4. Build pthread-win32 from Sources](#2224-build-pthread-win32-from-sources)
-- [3. Building SRT](#3-building-srt)
- - [3.1. Cloning the Source Code](#31-cloning-the-source-code)
- - [3.2. Generate Build Files](#32-generate-build-files)
- - [3.3. Build SRT](#33-build-srt)
+- [Building SRT on Windows](#building-srt-on-windows)
+ - [1. Prerequisites](#1-prerequisites)
+ - [1.1. Build Tool Dependencies](#11-build-tool-dependencies)
+ - [1.2. External Library Dependencies](#12-external-library-dependencies)
+ - [1.2.1. Cryptographic Library](#121-cryptographic-library)
+ - [1.2.2. Threading Library](#122-threading-library)
+ - [1.3. Package Managers](#13-package-managers)
+ - [1.3.1. VCpkg Packet Manager (optional)](#131-vcpkg-packet-manager-optional)
+ - [1.3.2. NuGet Manager (optional)](#132-nuget-manager-optional)
+ - [2. Preparing Dependencies](#2-preparing-dependencies)
+ - [2.1 Cryptographic Library](#21-cryptographic-library)
+ - [2.1.1. Install OpenSSL](#211-install-openssl)
+ - [2.1.1.1. Using vcpkg](#2111-using-vcpkg)
+ - [2.1.1.2. Using Installer (Windows)](#2112-using-installer-windows)
+ - [2.1.1.3. Build from Sources](#2113-build-from-sources)
+ - [2.1.2. Install MbedTLS](#212-install-mbedtls)
+ - [2.1.3. Install LibreSSL](#213-install-libressl)
+ - [2.2. Threading Library](#22-threading-library)
+ - [2.2.1. Using C++11 Threading](#221-using-c11-threading)
+ - [2.2.2. Building PThreads](#222-building-pthreads)
+ - [2.2.2.1. Using vcpkg](#2221-using-vcpkg)
+ - [2.2.2.2. Using NuGet](#2222-using-nuget)
+ - [2.2.2.3. Build pthreads4w from Sources](#2223-build-pthreads4w-from-sources)
+ - [2.2.2.4. Build pthread-win32 from Sources](#2224-build-pthread-win32-from-sources)
+ - [3. Building SRT](#3-building-srt)
+ - [3.1. Cloning the Source Code](#31-cloning-the-source-code)
+ - [3.2. Generate Build Files](#32-generate-build-files)
+ - [3.3. Build SRT](#33-build-srt)
@@ -296,7 +297,7 @@ In case vcpkg was used to build pthreads or OpenSSL, provide:
- `-DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\\scripts\\buildsystems\\vcpkg.cmake`
-In case NuGet was used to get pre-built pthreads libray, provide:
+In case NuGet was used to get pre-built pthreads library, provide:
- `-DPTHREAD_INCLUDE_DIR`
- `-DPTHREAD_LIBRARY`
diff --git a/docs/features/access-control.md b/docs/features/access-control.md
index 6375802b2b..297295fd61 100644
--- a/docs/features/access-control.md
+++ b/docs/features/access-control.md
@@ -82,9 +82,9 @@ password.
- `r`: **Resource Name** identifies the name of the resource and facilitates
selection should the listener party be able to serve multiple resources.
- `h`: **Host Name** identifies the hostname of the resource. For example,
-to request a stream with the URI `somehost.com/videos/querry.php?vid=366` the
+to request a stream with the URI `somehost.com/videos/query.php?vid=366` the
`hostname` field should have `somehost.com`, and the resource name can have
-`videos/querry.php?vid=366` or simply `366`. Note that this is still a key to be
+`videos/query.php?vid=366` or simply `366`. Note that this is still a key to be
specified explicitly. Support tools that apply simplifications and URI extraction
are expected to insert only the host portion of the URI here.
- `s`: **Session ID** is a temporary resource identifier negotiated with
diff --git a/docs/features/bonding-main-backup.md b/docs/features/bonding-main-backup.md
index b56df7851a..0e0190fdfd 100644
--- a/docs/features/bonding-main-backup.md
+++ b/docs/features/bonding-main-backup.md
@@ -26,7 +26,7 @@ Transmission happens over the main link until it is considered broken or is pres
### Sensitivity Levels
-The only sensitivity level implemented at the moment is a **pre-emptive switch** to a backup path from an unstable main path before it breaks. The goal is to predict an upcoming link breakage before it happens, and to be ready to switch to an activated backup link while losing as few packets as possible.
+The only sensitivity level implemented at the moment is a **preemptive switch** to a backup path from an unstable main path before it breaks. The goal is to predict an upcoming link breakage before it happens, and to be ready to switch to an activated backup link while losing as few packets as possible.
An additional sensitivity level (**handover switch**) may be added in the future for cases where low latency and packet loss is not critical. The switch would take place once the link is actually broken, without trying to predict it thereby reducing processing overhead. Since the main path in this case is already broken, there would be a delay associated with activating the backup path resulting in a discontinuity in streaming (data loss during the switch).
diff --git a/docs/features/handshake.md b/docs/features/handshake.md
index f8ce9b5253..4b2daa0771 100644
--- a/docs/features/handshake.md
+++ b/docs/features/handshake.md
@@ -7,31 +7,32 @@ Last updated: 2018-06-28
**Contents**
-- [Overview](#overview)
-- [Short Introduction to SRT Packet Structure](#short-introduction-to-srt-packet-structure)
-- [Handshake Structure](#handshake-structure)
-- [The "UDT Legacy" and "SRT Extended" Handshakes](#the-udt-legacy-and-srt-extended-handshakes)
- - [UDT Legacy Handshake](#udt-legacy-handshake)
- - [Initiator and Responder](#initiator-and-responder)
- - [The Request Type Field](#the-request-type-field)
- - [The Type Field](#the-type-field)
-- [The Caller-Listener Handshake](#the-caller-listener-handshake)
- - [The Induction Phase](#the-induction-phase)
- - [The Conclusion Phase](#the-conclusion-phase)
-- [The Rendezvous Handshake](#the-rendezvous-handshake)
- - [HSv4 Rendezvous Process](#hsv4-rendezvous-process)
- - [HSv5 Rendezvous Process](#hsv5-rendezvous-process)
- - [Serial Handshake Flow](#serial-handshake-flow)
- - [Parallel Handshake Flow](#parallel-handshake-flow)
- - [Rendezvous Between Different Versions](#rendezvous-between-different-versions)
-- [The SRT Extended Handshake](#the-srt-extended-handshake)
- - [HSv4 Extended Handshake Process](#hsv4-extended-handshake-process)
- - [HSv5 Extended Handshake Process](#hsv5-extended-handshake-process)
- - [SRT Extension Commands](#srt-extension-commands)
- - [HSREQ and HSRSP](#hsreq-and-hsrsp)
- - [KMREQ and KMRSP](#kmreq-and-kmrsp)
- - [Congestion controller](#congestion-controller)
- - [Stream ID (SID)](#stream-id-sid)
+- [SRT Handshake](#srt-handshake)
+ - [Overview](#overview)
+ - [Short Introduction to SRT Packet Structure](#short-introduction-to-srt-packet-structure)
+ - [Handshake Structure](#handshake-structure)
+ - [The "UDT Legacy" and "SRT Extended" Handshakes](#the-udt-legacy-and-srt-extended-handshakes)
+ - [UDT Legacy Handshake](#udt-legacy-handshake)
+ - [Initiator and Responder](#initiator-and-responder)
+ - [The Request Type Field](#the-request-type-field)
+ - [The Type Field](#the-type-field)
+ - [The Caller-Listener Handshake](#the-caller-listener-handshake)
+ - [The Induction Phase](#the-induction-phase)
+ - [The Conclusion Phase](#the-conclusion-phase)
+ - [The Rendezvous Handshake](#the-rendezvous-handshake)
+ - [HSv4 Rendezvous Process](#hsv4-rendezvous-process)
+ - [HSv5 Rendezvous Process](#hsv5-rendezvous-process)
+ - [Serial Handshake Flow](#serial-handshake-flow)
+ - [Parallel Handshake Flow](#parallel-handshake-flow)
+ - [Rendezvous Between Different Versions](#rendezvous-between-different-versions)
+ - [The SRT Extended Handshake](#the-srt-extended-handshake)
+ - [HSv4 Extended Handshake Process](#hsv4-extended-handshake-process)
+ - [HSv5 Extended Handshake Process](#hsv5-extended-handshake-process)
+ - [SRT Extension Commands](#srt-extension-commands)
+ - [HSREQ and HSRSP](#hsreq-and-hsrsp)
+ - [KMREQ and KMRSP](#kmreq-and-kmrsp)
+ - [Congestion controller](#congestion-controller)
+ - [Stream ID (SID)](#stream-id-sid)
## Overview
@@ -1552,7 +1553,7 @@ the user's choice that can be passed from the Caller to the Listener. The
symbol for this extension is `SRT_CMD_SID`.
The extension block for this extension is encoded the same way as described
-for Congestion Controler above.
+for Congestion Controller above.
The Stream ID is a string of up to 512 characters that a Caller can pass to a
Listener (it's actually passed from an Initiator to a Responder in general, but
@@ -1561,4 +1562,4 @@ application should set it on a Caller socket using the `SRTO_STREAMID` option.
Upon connection, the accepted socket on the Listener side will have exactly the
same value set, and it can be retrieved using the same option. For more details
about the prospective use of this option, please refer to the
-[SRT API Socket Options](../API/API-socket-options.md) and [SRT Access Control (Stream ID) Guidlines](access-control.md).
+[SRT API Socket Options](../API/API-socket-options.md) and [SRT Access Control (Stream ID) Guidelines](access-control.md).
diff --git a/docs/features/live-streaming.md b/docs/features/live-streaming.md
index 434f4c2aad..659e806ef3 100644
--- a/docs/features/live-streaming.md
+++ b/docs/features/live-streaming.md
@@ -33,7 +33,7 @@ However, the transmission must still satisfy the Live Streaming Requirements.
The MPEG-TS stream, as a good example, consists of Frames. Each
Frame is a portion of data assigned to a particular stream (usually you have
-multipe streams interleaved, at least video and audio). The video stream always has
+multiple streams interleaved, at least video and audio). The video stream always has
its playing speed expressed in fps (frames per second) units. This
value maps to a duration for one video frame. So, for example, 60 fps means that
one video frame should be "displayed" for a duration of 1/60 of a second. You can
diff --git a/docs/features/socket-groups.md b/docs/features/socket-groups.md
index cef0ee98ef..359c2aae15 100644
--- a/docs/features/socket-groups.md
+++ b/docs/features/socket-groups.md
@@ -113,7 +113,7 @@ time to realize that the link might be broken and time required for resending
all unacknowledged packets, before the time to play comes for the received
packets. If this time isn't met, packets will be dropped and your advantage
of having the backup link might be impaired. According to the tests on the
-local network it turns out that the most sensible unstability timeout is about
+local network it turns out that the most sensible instability timeout is about
50ms, while normally ACK timeout is 30ms, so extra 100ms latency tax seems to
be an absolute minimum.
@@ -431,7 +431,7 @@ to maintain, whether the list is constant or can be dynamically modified, or
whether a dead link is not to be revived by some reason - all these things are
out of the interest of the library. It's up to the application to decide
when and by what reason the connection is to be established. All that your
-application has to do is to monitor the conenctions (that is, be conscious
+application has to do is to monitor the connections (that is, be conscious
about that particular links are up and running or get broken) and take
appropriate action in response.
@@ -514,7 +514,7 @@ by `srt_sendmsg2` or `srt_recvmsg2`.
This is very simple. Call the sending function (recommended is `srt_sendmsg2`)
to send the data, passing group ID in the place of socket ID. By recognizing
the ID as group ID, this will be resolved internally as sending the payload
-by approprately using the bonded links as defined for particular group type.
+by appropriately using the bonded links as defined for particular group type.
The current implementation for most of the bonding groups (broadcast and
backup) relies on synchronizing the sequence numbers of the packets so that
@@ -539,7 +539,7 @@ This is also simple from the user's perspective. Simply call the reading
function, such as `srt_recvmsg2`, passing the group ID instead of socket
ID.
-Also the dillema of blocking and nonblocking is the same thing. With blocking
+Also the dilemma of blocking and nonblocking is the same thing. With blocking
mode (`SRTO_RCVSYN`), simply wait until your payload is retrieved. The internal
group reading facility will take care that you get your payload in the right
order and at the time to play, and the redundant payloads retrieved over
diff --git a/examples/fork-test/Makefile b/examples/fork-test/Makefile
new file mode 100644
index 0000000000..57e9f3a946
--- /dev/null
+++ b/examples/fork-test/Makefile
@@ -0,0 +1,9 @@
+TARGETS=srt_server srt_client
+
+all: $(TARGETS)
+
+%: %.c
+ $(CC) $< `pkg-config --cflags --libs srt` -o `basename $< .c`
+
+clean:
+ rm -f $(TARGETS)
diff --git a/examples/fork-test/README.md b/examples/fork-test/README.md
new file mode 100644
index 0000000000..59752ad022
--- /dev/null
+++ b/examples/fork-test/README.md
@@ -0,0 +1,7 @@
+The `srt_server` and `srt_client` apps should be compiled using
+external installation of SRT (e.g. in the local directory), each
+one as a single program. This is not compiled as a part of SRT.
+
+If you want to use a local installation, simply set `PKG_CONFIG_PATH`
+environment variable to point to the local installation directory with
+"lib/pkgconfig" or "lib64/pkgconfig" suffix.
diff --git a/examples/fork-test/srt_client.c b/examples/fork-test/srt_client.c
new file mode 100644
index 0000000000..ab69d59569
--- /dev/null
+++ b/examples/fork-test/srt_client.c
@@ -0,0 +1,55 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#define SERVER_IP "127.0.0.1"
+#define SERVER_PORT 9000
+int main() {
+ if (srt_startup() != 0) {
+ fprintf(stderr, "Error initializing SRT.\n");
+ return 1;
+ }
+ SRTSOCKET client_sock = srt_create_socket();
+ if (client_sock == SRT_INVALID_SOCK) {
+ fprintf(stderr, "Error creating a socket: %s\n", srt_getlasterror_str());
+ return 1;
+ }
+ struct sockaddr_in sa;
+ memset(&sa, 0, sizeof sa);
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(SERVER_PORT);
+ inet_pton(AF_INET, SERVER_IP, &sa.sin_addr);
+ if (srt_connect(client_sock, (struct sockaddr*)&sa, sizeof(sa)) == SRT_ERROR) {
+ fprintf(stderr, "Error: srt_connect: %s\n", srt_getlasterror_str());
+ return 1;
+ }
+ printf("Connected to SRT server %s:%d\n", SERVER_IP, SERVER_PORT);
+ const char* message = "Hello from SRT client!";
+ int bytes = srt_send(client_sock, message, strlen(message));
+ if (bytes == SRT_ERROR) {
+ fprintf(stderr, "Sending error: %s\n", srt_getlasterror_str());
+ } else {
+ printf("Message sent: %s\n", message);
+ }
+
+ while (1)
+ {
+ char buffer[1500];
+ int nb = srt_recv(client_sock, buffer, sizeof(buffer));
+ if (nb <= 0)
+ {
+ printf("Closed from the server !\n");
+ srt_close(client_sock);
+ break;
+ }
+ buffer[nb] = 0;
+ printf("Server has sent: %s\n", buffer);
+ }
+ srt_cleanup();
+ return 0;
+}
+
diff --git a/examples/fork-test/srt_server.c b/examples/fork-test/srt_server.c
new file mode 100644
index 0000000000..168b9c1678
--- /dev/null
+++ b/examples/fork-test/srt_server.c
@@ -0,0 +1,118 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#define PORT 9000
+
+int run(char *command) {
+ pid_t pid = fork();
+ if (pid < 0) {
+ perror("fork (intermediate)");
+ exit(EXIT_FAILURE);
+ }
+ if (pid > 0) {
+ // Parent process
+ printf("[GRANDPARENT %d] waiting for grand-child process pid=%d to finish...\n",
+ getpid(), pid);
+ waitpid(pid, NULL, 0); // Wait for intermediate child
+ printf("[GRANDPARENT] returning\n");
+ return 0;
+ }
+ // Intermediate process
+ //srt_cleanup();
+ if (setsid() < 0) {
+ perror("setsid");
+ exit(EXIT_FAILURE);
+ }
+ pid_t grandchild_pid = fork();
+ if (grandchild_pid < 0) {
+ perror("fork (grandchild)");
+ exit(EXIT_FAILURE);
+ }
+ if (grandchild_pid > 0) {
+ printf("[PARENT %d] waiting for 10s with child process pid=%d ...\n",
+ getpid(), grandchild_pid);
+ // Intermediate process exits immediately
+ sleep(10);
+ printf("[PARENT] exiting\n");
+ exit(0);
+ }
+ // Grandchild process
+ // Redirect stdin to /dev/null
+ printf("[CHILD %d] Preparing descriptors...\n", getpid());
+ int devnull = open("/dev/null", O_RDONLY);
+ if (devnull >= 0) {
+ dup2(devnull, STDIN_FILENO);
+ close(devnull);
+ } else {
+ perror("open /dev/null");
+ }
+ // Redirect stdout to stderr
+ dup2(STDERR_FILENO, STDOUT_FILENO);
+ // Execute the command
+ printf("[CHILD] Executing process '%s'...\n", command);
+ execl("/bin/sh", "sh", "-c", command, (char *)NULL);
+ // If execl fails
+ perror("execl");
+ exit(EXIT_FAILURE);
+}
+
+int main() {
+ if (srt_startup() != 0) {
+ fprintf(stderr, "Error initializing SRT.\n");
+ return 1;
+ }
+
+ SRTSOCKET serv_sock = srt_create_socket();
+ if (serv_sock == SRT_INVALID_SOCK) {
+ fprintf(stderr, "Error creating SRT socket: %s\n", srt_getlasterror_str());
+ return 1;
+ }
+ struct sockaddr_in sa;
+ memset(&sa, 0, sizeof sa);
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(PORT);
+ sa.sin_addr.s_addr = INADDR_ANY;
+ if (srt_bind(serv_sock, (struct sockaddr*)&sa, sizeof sa) == SRT_ERROR) {
+ fprintf(stderr, "Error: srt_bind: %s\n", srt_getlasterror_str());
+ return 1;
+ }
+ if (srt_listen(serv_sock, 5) == SRT_ERROR) {
+ fprintf(stderr, "Error: srt_listen: %s\n", srt_getlasterror_str());
+ return 1;
+ }
+ printf("SRT server is listening on port %d...\n", PORT);
+ struct sockaddr_in client_addr;
+ int addr_len = sizeof(client_addr);
+ SRTSOCKET client_sock = srt_accept(serv_sock, (struct sockaddr*)&client_addr, &addr_len);
+ if (client_sock == SRT_INVALID_SOCK) {
+ fprintf(stderr, "Error: srt_accept: %s\n", srt_getlasterror_str());
+ return 1;
+ }
+ printf("Client connected via SRT !\n");
+ char buffer[1500];
+ int bytes = srt_recv(client_sock, buffer, sizeof(buffer));
+ if (bytes > 0) {
+ buffer[bytes] = '\0';
+ printf("Message received: %s\n", buffer);
+ const char resp [] = "We read you!";
+ srt_send(client_sock, resp, (sizeof resp)-1);
+ } else {
+ printf("Error: reading from srt_recv: %s.\n", srt_getlasterror_str());
+ }
+ run("date > /tmp/res");
+ printf("Server: sleep(1)\n");
+ sleep(1);
+ // Nettoyage
+ printf("Server: closing SRT sockets\n");
+ srt_close(client_sock);
+ srt_close(serv_sock);
+ printf("Server: cleanup\n");
+ srt_cleanup();
+ printf("Server: exit\n");
+ return 0;
+}
diff --git a/haicrypt/cryspr-botan.h b/haicrypt/cryspr-botan.h
index 704d29695e..6de59c8cb0 100644
--- a/haicrypt/cryspr-botan.h
+++ b/haicrypt/cryspr-botan.h
@@ -46,7 +46,7 @@ typedef struct botan_aes_context
#define CRYSPR_HAS_PBKDF2 1
/*
-#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object.
+#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object.
This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK
It is set from the keystring through CRYSPR_methods.aes_set_key and passed
to CRYSPR_methods.aes_XXX.
diff --git a/haicrypt/cryspr-gnutls.c b/haicrypt/cryspr-gnutls.c
index 95eaf2856c..b8cecf38e5 100644
--- a/haicrypt/cryspr-gnutls.c
+++ b/haicrypt/cryspr-gnutls.c
@@ -187,7 +187,7 @@ CRYSPR_methods *crysprGnuTLS(void)
crysprGnuTLS_methods.aes_ecb_cipher = crysprGnuTLS_AES_EcbCipher;
#endif
#if !CRYSPR_HAS_PBKDF2
- crysprGnuTLS_methods.sha1_msg_digest= crysprGnuTLS_SHA1_MsgDigest; //Onl required if using generic KmPbkdf2
+ crysprGnuTLS_methods.sha1_msg_digest= crysprGnuTLS_SHA1_MsgDigest; //Only required if using generic KmPbkdf2
#endif
//--Crypto Session (Top API)
diff --git a/haicrypt/cryspr-gnutls.h b/haicrypt/cryspr-gnutls.h
index 78f7c86ec9..53ae72c586 100644
--- a/haicrypt/cryspr-gnutls.h
+++ b/haicrypt/cryspr-gnutls.h
@@ -48,9 +48,9 @@ written by
#define CRYSPR_HAS_PBKDF2 1
/*
-#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object.
+#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object.
This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK
-It is set from hte keystring through CRYSPR_methods.aes_set_key and passed
+It is set from the keystring through CRYSPR_methods.aes_set_key and passed
to CRYSPR_methods.aes_XXX.
*/
typedef struct aes_ctx CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */
diff --git a/haicrypt/cryspr-mbedtls.c b/haicrypt/cryspr-mbedtls.c
index a961cca61e..5b3d2db780 100644
--- a/haicrypt/cryspr-mbedtls.c
+++ b/haicrypt/cryspr-mbedtls.c
@@ -246,7 +246,7 @@ CRYSPR_methods *crysprMbedtls(void)
crysprMbedtls_methods.aes_ecb_cipher = crysprMbedtls_AES_EcbCipher;
#endif
#if !CRYSPR_HAS_PBKDF2
- crysprMbedtls_methods.sha1_msg_digest= crysprMbedtls_SHA1_MsgDigest; //Onl required if using generic KmPbkdf2
+ crysprMbedtls_methods.sha1_msg_digest= crysprMbedtls_SHA1_MsgDigest; //Only required if using generic KmPbkdf2
#endif
//--Crypto Session (Top API)
diff --git a/haicrypt/cryspr-mbedtls.h b/haicrypt/cryspr-mbedtls.h
index f62b32e486..aff218af28 100644
--- a/haicrypt/cryspr-mbedtls.h
+++ b/haicrypt/cryspr-mbedtls.h
@@ -50,7 +50,7 @@ written by
#endif
/*
-#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object.
+#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object.
This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK
It is set from the keystring through CRYSPR_methods.aes_set_key and passed
to CRYSPR_methods.aes_XXX.
diff --git a/haicrypt/cryspr-openssl-evp.c b/haicrypt/cryspr-openssl-evp.c
index 3e87e9a5ef..02cfb590bf 100644
--- a/haicrypt/cryspr-openssl-evp.c
+++ b/haicrypt/cryspr-openssl-evp.c
@@ -385,7 +385,7 @@ CRYSPR_methods* crysprOpenSSL_EVP(void)
#if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP)
/* AES-ECB only required if cryspr has no AES-CTR and no AES KeyWrap */
/* OpenSSL has both AESCTR and AESKWRP and the AESECB wrapper is only used
- to test the falback methods */
+ to test the fallback methods */
crysprOpenSSL_EVP_methods.aes_ecb_cipher = crysprOpenSSL_EVP_AES_EcbCipher;
#endif
#if !CRYSPR_HAS_PBKDF2
diff --git a/haicrypt/cryspr-openssl-evp.h b/haicrypt/cryspr-openssl-evp.h
index a1fb0b925f..3e2932e8bc 100644
--- a/haicrypt/cryspr-openssl-evp.h
+++ b/haicrypt/cryspr-openssl-evp.h
@@ -55,9 +55,9 @@ written by
#define CRYSPR_HAS_PBKDF2 1 /* Define to 1 if CRYSPR has Password-based Key Derivaion Function 2 */
/*
-#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object.
+#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object.
This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK
-It is set from hte keystring through CRYSPR_methods.aes_set_key and passed
+It is set from the keystring through CRYSPR_methods.aes_set_key and passed
to CRYSPR_methods.aes_*.
*/
typedef EVP_CIPHER_CTX CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */
diff --git a/haicrypt/cryspr-openssl.c b/haicrypt/cryspr-openssl.c
index 87eb535ec5..2839d0e125 100644
--- a/haicrypt/cryspr-openssl.c
+++ b/haicrypt/cryspr-openssl.c
@@ -207,7 +207,7 @@ CRYSPR_methods *crysprOpenSSL(void)
#if !(CRYSPR_HAS_AESCTR && CRYSPR_HAS_AESKWRAP)
/* AES-ECB only required if cryspr has no AES-CTR and no AES KeyWrap */
/* OpenSSL has both AESCTR and AESKWRP and the AESECB wrapper is only used
- to test the falback methods */
+ to test the fallback methods */
crysprOpenSSL_methods.aes_ecb_cipher = crysprOpenSSL_AES_EcbCipher;
#endif
#if !CRYSPR_HAS_PBKDF2
diff --git a/haicrypt/cryspr-openssl.h b/haicrypt/cryspr-openssl.h
index 75a52518f3..2ebfe7528f 100644
--- a/haicrypt/cryspr-openssl.h
+++ b/haicrypt/cryspr-openssl.h
@@ -52,9 +52,9 @@ written by
#define CRYSPR_HAS_PBKDF2 1 /* Define to 1 if CRYSPR has Password-based Key Derivaion Function 2 */
/*
-#define CRYSPR_AESCTX to the CRYSPR specifix AES key context object.
+#define CRYSPR_AESCTX to the CRYSPR-specific AES key context object.
This type reserves room in the CRYPSPR control block for Haicrypt KEK and SEK
-It is set from hte keystring through CRYSPR_methods.aes_set_key and passed
+It is set from the keystring through CRYSPR_methods.aes_set_key and passed
to CRYSPR_methods.aes_*.
*/
typedef AES_KEY CRYSPR_AESCTX; /* CRYpto Service PRovider AES key context */
diff --git a/haicrypt/cryspr.h b/haicrypt/cryspr.h
index 42426f4223..febde4ae98 100644
--- a/haicrypt/cryspr.h
+++ b/haicrypt/cryspr.h
@@ -148,7 +148,7 @@ typedef struct tag_CRYSPR_methods {
/*
* km_setkey:
- * Set the Key Encypting Key for Wrap (Encryption) or UnWrap (Decryption).
+ * Set the Key Encrypting Key for Wrap (Encryption) or UnWrap (Decryption).
* Context (ctx) tells if it's for Wrap or Unwrap
* A Context flags (ctx->flags) also tells if this is for wrap(encryption) or unwrap(decryption) context (HCRYPT_CTX_F_ENCRYPT)
*/
diff --git a/haicrypt/haicrypt.h b/haicrypt/haicrypt.h
index da0ad3493a..573efbf3b3 100644
--- a/haicrypt/haicrypt.h
+++ b/haicrypt/haicrypt.h
@@ -106,6 +106,9 @@ int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len,
int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len,
void *out_p[], size_t out_len_p[], int maxout);
+// Length not needed, as long as it's pre-verified that it's at least SALT length.
+int HaiCrypt_GetKeyIndex(HaiCrypt_Handle hhc, unsigned char *in);
+
int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc);
int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout);
int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len);
diff --git a/haicrypt/hcrypt.c b/haicrypt/hcrypt.c
index a2b81d8321..62fb6dea02 100644
--- a/haicrypt/hcrypt.c
+++ b/haicrypt/hcrypt.c
@@ -91,7 +91,7 @@ static hcrypt_Session* sHaiCrypt_PrepareHandle(const HaiCrypt_Cfg* cfg, HaiCrypt
crypto->km.refresh_rate = cfg->km_refresh_rate_pkt;
crypto->km.pre_announce = cfg->km_pre_announce_pkt;
- /* Indentify each context */
+ /* Identify each context */
crypto->ctx_pair[0].flags = HCRYPT_MSG_F_eSEK | (tx ? HCRYPT_CTX_F_ENCRYPT : 0);
crypto->ctx_pair[1].flags = HCRYPT_MSG_F_oSEK | (tx ? HCRYPT_CTX_F_ENCRYPT : 0);
/* Point to each other */
@@ -307,7 +307,7 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl
timerclear(&cryptoClone->km.tx_last);
/* Adjust pointers pointing into cryproSrc after copy
- msg_info and crysprs are extern statics so this is ok*/
+ msg_info and crysprs are extern static so this is ok*/
cryptoClone->ctx_pair[0].alt = &cryptoClone->ctx_pair[1];
cryptoClone->ctx_pair[1].alt = &cryptoClone->ctx_pair[0];
@@ -327,7 +327,7 @@ int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handl
}
/* Clear salt to force later regeneration of KEK as AES decrypting key,
- copyed one is encrypting key */
+ copied one is encrypting key */
cryptoClone->ctx_pair[0].flags &= ~HCRYPT_CTX_F_ENCRYPT;
cryptoClone->ctx_pair[1].flags &= ~HCRYPT_CTX_F_ENCRYPT;
memset(cryptoClone->ctx_pair[0].salt, 0, sizeof(cryptoClone->ctx_pair[0].salt));
diff --git a/haicrypt/hcrypt_ctx_rx.c b/haicrypt/hcrypt_ctx_rx.c
index 2b67490d3e..907acdf09f 100644
--- a/haicrypt/hcrypt_ctx_rx.c
+++ b/haicrypt/hcrypt_ctx_rx.c
@@ -152,24 +152,25 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m
return(-1);
}
+ hcrypt_Ctx new_ctx = *ctx;
/* Check Salt and get if new */
- if ((salt_len != ctx->salt_len)
- || (0 != memcmp(ctx->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len))) {
+ if ((salt_len != new_ctx.salt_len)
+ || (0 != memcmp(new_ctx.salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len))) {
/* Salt changed (or 1st KMmsg received) */
- memcpy(ctx->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len);
- ctx->salt_len = salt_len;
+ memcpy(new_ctx.salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len);
+ new_ctx.salt_len = salt_len;
do_pbkdf = 1; /* Impact on password derived kek */
}
/* Check SEK length and get if new */
- if (sek_len != ctx->sek_len) {
+ if (sek_len != new_ctx.sek_len) {
/* Key length changed or 1st KMmsg received */
- ctx->sek_len = sek_len;
+ new_ctx.sek_len = sek_len;
do_pbkdf = 1; /* Impact on password derived kek */
}
/* Check cipher mode */
- if (ctx->mode != km_msg[HCRYPT_MSG_KM_OFS_CIPHER])
+ if (new_ctx.mode != km_msg[HCRYPT_MSG_KM_OFS_CIPHER])
{
HCRYPT_LOG(LOG_WARNING, "%s", "cipher mode mismatch\n");
return(-3);
@@ -179,21 +180,32 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m
* Regenerate KEK if it is password derived
* and Salt or SEK length changed
*/
- if (ctx->cfg.pwd_len && do_pbkdf) {
- if (hcryptCtx_GenSecret(crypto, ctx)) {
+ int rollback_kek = 0;
+ if (new_ctx.cfg.pwd_len && do_pbkdf) {
+ if (hcryptCtx_GenSecret(crypto, &new_ctx)) {
return(-1);
}
- ctx->status = HCRYPT_CTX_S_SARDY;
+ new_ctx.status = HCRYPT_CTX_S_SARDY;
kek_len = sek_len; /* KEK changed */
+ rollback_kek = 1;
}
/* Unwrap SEK(s) and set in context */
- if (0 > crypto->cryspr->km_unwrap(crypto->cryspr_cb, seks,
- &km_msg[HCRYPT_MSG_KM_OFS_SALT + salt_len],
- (unsigned int)((sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ))) {
- HCRYPT_LOG(LOG_WARNING, "%s", "unwrap key failed\n");
+ unsigned int msglen = (sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ;
+ int wrc = crypto->cryspr->km_unwrap(crypto->cryspr_cb, seks,
+ &km_msg[HCRYPT_MSG_KM_OFS_SALT + salt_len], msglen);
+
+ if (wrc < 0) {
+ HCRYPT_LOG(LOG_WARNING, "%s%s\n", "unwrap key failed - internal KEK: ", rollback_kek ? "ROLLBACK" : "unchanged");
+ // Rollback the call to hcryptCtx_GenSecret done on the new_ctx,
+ // and restore the old secret from old ctx. GenSecret is required by
+ // km_unwrap, but only after failed call we know this should remain unchanged.
+ if (rollback_kek) {
+ hcryptCtx_GenSecret(crypto, ctx);
+ }
return(-2); //Report unmatched shared secret
}
+ *ctx = new_ctx;
/*
* First SEK in KMmsg is eSEK if both SEK present
*/
diff --git a/haicrypt/hcrypt_ctx_tx.c b/haicrypt/hcrypt_ctx_tx.c
index e66c9497b9..75f861c5b1 100644
--- a/haicrypt/hcrypt_ctx_tx.c
+++ b/haicrypt/hcrypt_ctx_tx.c
@@ -110,7 +110,7 @@ int hcryptCtx_Tx_CloneKey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const hcrypt_
const hcrypt_Ctx* ctxSrc = cryptoSrc->ctx;
if (!ctxSrc)
{
- /* Probbly the context is not yet completely initialized, so
+ /* Probably the context is not yet completely initialized, so
* use blindly the first context from the pair
*/
ctxSrc = &cryptoSrc->ctx_pair[0];
diff --git a/haicrypt/hcrypt_msg.h b/haicrypt/hcrypt_msg.h
index c35ddd046d..0249975d27 100644
--- a/haicrypt/hcrypt_msg.h
+++ b/haicrypt/hcrypt_msg.h
@@ -42,7 +42,7 @@ typedef uint32_t hcrypt_Pki;
#define HCRYPT_MSG_PT_MS 1 /* Media stream */
#define HCRYPT_MSG_PT_KM 2 /* Keying Material */
-#define HCRYPT_MSG_PT_RESV7 7 /* Reserved to dicriminate MPEG-TS packet (SyncByte=0x47) */
+#define HCRYPT_MSG_PT_RESV7 7 /* Reserved to discriminate MPEG-TS packet (SyncByte=0x47) */
#define HCRYPT_MSG_F_eSEK 0x01 /* Even Stream Encrypting Key */
diff --git a/haicrypt/hcrypt_rx.c b/haicrypt/hcrypt_rx.c
index 2f7e847399..4cb3f84d52 100644
--- a/haicrypt/hcrypt_rx.c
+++ b/haicrypt/hcrypt_rx.c
@@ -61,6 +61,29 @@ int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc,
return(nb);
}
+int HaiCrypt_GetKeyIndex(HaiCrypt_Handle hhc, unsigned char* in_msg)
+{
+ hcrypt_Session *crypto = (hcrypt_Session *)hhc;
+ int msg_type;
+
+ if ((NULL == crypto)
+ || (NULL == in_msg)) {
+
+ HCRYPT_LOG(LOG_ERR, "%s", "invalid parameters\n");
+ return(-1);
+ }
+
+ /* Validate HaiCrypt message */
+ if (0 > (msg_type = hcryptMsg_SRT_ParseMsg(crypto->msg_info, in_msg))) {
+ return(-1);
+ }
+
+ if (msg_type != HCRYPT_MSG_PT_KM)
+ return -1;
+
+ return hcryptMsg_GetKeyIndex(crypto->msg_info, in_msg);
+}
+
int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc,
unsigned char *in_msg, size_t in_len,
void *out_p[], size_t out_len_p[], int maxout)
@@ -99,6 +122,13 @@ int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc,
HCRYPT_LOG(LOG_ERR, "%s", "cryspr had no decryptor\n");
nbout = -1;
} else if (ctx->status >= HCRYPT_CTX_S_KEYED) {
+ if ((int)(in_len) <= crypto->msg_info->pfx_len)
+ {
+ // XXX NOTE: SRT doesn't use this type of messages, so it shouldn't
+ // be a danger, but then you can still find something like that on the wire.
+ HCRYPT_LOG(LOG_ERR, "%s%d", "Invalid enc data packet: ", in_len);
+ return -1;
+ }
hcrypt_DataDesc indata;
indata.pfx = in_msg;
indata.payload = &in_msg[crypto->msg_info->pfx_len];
diff --git a/scripts/ShowProjectConfig.cmake b/scripts/ShowProjectConfig.cmake
index 108dde5dbd..9fc275bc86 100644
--- a/scripts/ShowProjectConfig.cmake
+++ b/scripts/ShowProjectConfig.cmake
@@ -166,7 +166,7 @@ function(ShowProjectConfig)
" ENABLE_MONOTONIC_CLOCK: ${ENABLE_MONOTONIC_CLOCK}\n"
" ENABLE_STDCXX_SYNC: ${ENABLE_STDCXX_SYNC}\n"
" USE_OPENSSL_PC: ${USE_OPENSSL_PC}\n"
- " OPENSSL_USE_STATIC_LIBS: ${OPENSSL_USE_STATIC_LIBS}\n"
+ " SRT_USE_OPENSSL_STATIC_LIBS: ${SRT_USE_OPENSSL_STATIC_LIBS}\n"
" USE_BUSY_WAITING: ${USE_BUSY_WAITING}\n"
" USE_GNUSTL: ${USE_GNUSTL}\n"
" ENABLE_SOCK_CLOEXEC: ${ENABLE_SOCK_CLOEXEC}\n"
diff --git a/scripts/abi-check.tcl b/scripts/abi-check.tcl
new file mode 100644
index 0000000000..7f9c70e790
--- /dev/null
+++ b/scripts/abi-check.tcl
@@ -0,0 +1,76 @@
+#!/usr/bin/tclsh
+
+set here [file dirname $argv0] ;# points to [git top]/scripts
+set top [file normalize $here/..]
+
+set abichecker [file normalize [file join $top submodules abi-compliance-checker abi-compliance-checker.pl]]
+
+if { ![file exists $abichecker] } {
+ puts stderr "Please update submodules first (compliance checker not found in the current view)"
+ exit 1
+}
+
+# Check if abi-dumper is installed
+
+set abidumper [auto_execok abi-dumper]
+if {$abidumper == ""} {
+ set installer ""
+ foreach ii {zypper dnf apt} {
+ if {[auto_execok $ii] != ""} {
+ set installer $ii
+ break
+ }
+ }
+ if {$installer != ""} {
+ puts stderr "ABI dumper not installed. Use such commands to install\n"
+ puts stderr " $installer install abi-dumper"
+ } else {
+ puts stderr "ABI dumper not installed. Find out how to install abi-dumper in your system."
+ }
+ exit 1
+}
+
+# Arguments:
+# [directory-with-base] [directory-with-pr]
+
+# NOTE: ABI dump will be done in every build directory as specified.
+
+proc generate-abi-dump {directory lver} {
+ set wd [pwd]
+ cd $directory
+ global top
+
+ # You should have libsrt.so in this directory.
+ # Use [glob] to use exception if no file exists
+ glob libsrt.so
+
+ exec >@stdout 2>@stderr abi-dumper libsrt.so -o libsrt-abi.dump -public-headers $top/srtcore -lver $lver
+ cd $wd
+}
+
+set olddir [lindex $argv 0]
+set newdir [lindex $argv 1]
+
+if {![file isdirectory $olddir] || ![file isdirectory $newdir]} {
+ puts stderr "Wrong arguments. Required build directory"
+ exit 1
+}
+
+set wd [pwd]
+cd $olddir
+set base_ver [exec $top/scripts/get-build-version.tcl]
+cd $wd
+cd $newdir
+set new_ver [exec $top/scripts/get-build-version.tcl]
+cd $wd
+
+generate-abi-dump $olddir $base_ver
+generate-abi-dump $newdir $new_ver
+
+set res [catch {exec >@stdout 2>@stderr $abichecker -l libsrt -old $olddir/libsrt-abi.dump -new $newdir/libsrt-abi.dump} out]
+
+if {$res} {
+ puts stderr "ABI compat problems found!!!\nSee the HTML report"
+}
+
+
diff --git a/scripts/build-ios/README.md b/scripts/build-ios/README.md
new file mode 100644
index 0000000000..63986286e3
--- /dev/null
+++ b/scripts/build-ios/README.md
@@ -0,0 +1,3 @@
+## Scripts for building SRT for iOS/tvOS
+
+See [Building SRT for iOS/tvOS](../../docs/build/build-iOS.md) for the instructions.
diff --git a/scripts/build-ios/mksrt-xcf.sh b/scripts/build-ios/mksrt-xcf.sh
new file mode 100755
index 0000000000..59022cdc1a
--- /dev/null
+++ b/scripts/build-ios/mksrt-xcf.sh
@@ -0,0 +1,60 @@
+#!/bin/zsh
+
+# Determine the path of the executing script
+BASE_DIR=$(readlink -f $0 | xargs dirname)
+
+srt="../.."
+ssl=$BASE_DIR/OpenSSL
+out=$BASE_DIR/libsrt
+target=13.0
+tv_target=14.0
+tvos_sdk=`xcrun -sdk appletvos --show-sdk-version`
+lib=libsrt.a
+
+ios_archs=('arm64' 'arm64' 'x86_64' 'arm64' 'arm64' 'x86_64')
+ios_platforms=('OS' 'ARM_SIMULATOR' 'SIMULATOR64' 'TV' 'TV_ARM_SIMULATOR' 'TV_SIMULATOR' )
+ios_targets=('iOS-arm64' 'iOS-simArm' 'iOS-simIntel64' 'tvOS' 'tvOS_simArm' 'tvOS_simIntel64')
+ssl_folders=('iphoneos' 'iphonesimulator' 'iphonesimulator' 'appletvos' 'appletvsimulator' 'appletvsimulator')
+rm -rf $out
+
+cd $srt
+
+for idx in {1..$#ios_archs}
+do
+ ios_arch=${ios_archs[$idx]}
+ ios_platform=${ios_platforms[$idx]}
+ dest=${ios_targets[$idx]}
+ ssl_path=${ssl}/${ssl_folders[$idx]}
+
+ git clean -fd -e scripts
+ echo '********************************************************************************'
+ echo SSL_PATH $ssl_path ARCH $ios_arch PLATFORM $ios_platform
+
+ CMAKE_GENERATOR=Xcode ./configure \
+ --enable-shared=OFF --enable-apps=OFF \
+ --cmake-toolchain-file=scripts/iOS.cmake --ios-arch=$ios_arch --ios-platform=$ios_platform \
+ --cmake-xcode-attribute-iphoneos-deployment-target=$target \
+ --cmake-xcode-attribute-tvos-deployment-target=$tv_target \
+ --cmake-prefix-path=$ssl_path --use-openssl-pc=OFF \
+ --cmake_install_prefix=$out/$dest
+
+ cmake --build . --target install --config Release
+ echo '********************************************************************************'
+done
+
+git clean -fd -e scripts
+
+lipo -create $out/iOS-simArm/lib/$lib $out/iOS-simIntel64/lib/$lib -output $out/libsrt-ios-sim.a
+lipo -create $out/tvOS_simArm/lib/$lib $out/tvOS_simIntel64/lib/$lib -output $out/libsrt-tv-sim.a
+
+srt_headers=$out/iOS-arm64/include
+rm -rf $BASE_DIR/libsrt.xcframework
+
+echo '####### create XCFramework bundle #######'
+
+xcodebuild -create-xcframework \
+ -library $out/iOS-arm64/lib/$lib -headers $srt_headers \
+ -library $out/libsrt-ios-sim.a -headers $srt_headers \
+ -library $out/tvOS/lib/$lib -headers $srt_headers \
+ -library $out/libsrt-tv-sim.a -headers $srt_headers \
+ -output $BASE_DIR/libsrt.xcframework
diff --git a/scripts/build-ios/mkssl-xcf.sh b/scripts/build-ios/mkssl-xcf.sh
new file mode 100755
index 0000000000..c7283d75d9
--- /dev/null
+++ b/scripts/build-ios/mkssl-xcf.sh
@@ -0,0 +1,22 @@
+#!/bin/zsh
+
+# Determine the path of the executing script
+BASE_DIR=$(readlink -f $0 | xargs dirname)
+
+REPO_DIR=OpenSSL
+SSL_DIR=$BASE_DIR/$REPO_DIR
+
+if [ ! -d $REPO_DIR ]
+then
+ git clone https://github.com/krzyzanowskim/OpenSSL
+fi
+cd $REPO_DIR
+make build
+
+rm -rf $BASE_DIR/libcrypto.xcframework
+xcodebuild -create-xcframework \
+ -library $SSL_DIR/iphoneos/lib/libcrypto.a \
+ -library $SSL_DIR/iphonesimulator/lib/libcrypto.a \
+ -library $SSL_DIR/appletvos/lib/libcrypto.a \
+ -library $SSL_DIR/appletvsimulator/lib/libcrypto.a \
+ -output $BASE_DIR/libcrypto.xcframework
diff --git a/scripts/build-windows.ps1 b/scripts/build-windows.ps1
index 66586158ee..a65247ccdd 100644
--- a/scripts/build-windows.ps1
+++ b/scripts/build-windows.ps1
@@ -17,6 +17,7 @@ param (
[Parameter()][String]$STATIC_LINK_SSL = "OFF",
[Parameter()][String]$CXX11 = "ON",
[Parameter()][String]$BUILD_APPS = "ON",
+ [Parameter()][String]$BUILD_TESTAPPS = "OFF",
[Parameter()][String]$UNIT_TESTS = "OFF",
[Parameter()][String]$BUILD_DIR = "_build",
[Parameter()][String]$VCPKG_OPENSSL = "OFF",
@@ -48,11 +49,25 @@ if ( $Env:APPVEYOR ) {
$CONFIGURATION = $Env:CONFIGURATION
- #appveyor has many openssl installations - place the latest one in the default location unless VS2013
- Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
- Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
- Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null
- Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null
+ # See https://www.appveyor.com/docs/windows-images-software/#visual-studio-2022
+ # According to AppVeyor environment definition:
+ #
+ # 2013 - 2017: C:\OpenSSL-Win*: v1.0.2p, C:\OpenSSL-v111-Win*: v1.1.1 (v1.1.1d for 2015+)
+ # 2019 - 2022: C:\OpenSSL-Win*: v1.1.1w and C:\OpenSSL-v3* with version 3+
+ # SO:
+ # For 2013 - 2017: Delete C:\OpenSSL-Win* and replace it with contents of C:\OpenSSL-v111-Win*
+ # For 2019 - 2022: Do nothing.
+ # Note: No guarantee that C:\OpenSSL-Win* directories will always contain version v1.1.1+ in
+ # any future versions for AppVeyor Windows environment, but then probably the SRT project
+ # would have to be adjusted to this version anyway.
+
+ if ($VS_VERSION -lt 2019) {
+ #appveyor has many openssl installations - place the latest one in the default location unless VS2013
+ Remove-Item -Path "C:\OpenSSL-Win32" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
+ Remove-Item -Path "C:\OpenSSL-Win64" -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
+ Copy-Item -Path "C:\OpenSSL-v111-Win32" "C:\OpenSSL-Win32" -Recurse | Out-Null
+ Copy-Item -Path "C:\OpenSSL-v111-Win64" "C:\OpenSSL-Win64" -Recurse | Out-Null
+ }
}
# persist VS_VERSION so it can be used in an artifact name later
@@ -135,6 +150,7 @@ $cmakeFlags = "-DCMAKE_BUILD_TYPE=$CONFIGURATION " +
"-DENABLE_APPS=$BUILD_APPS " +
"-DENABLE_ENCRYPTION=$ENABLE_ENCRYPTION " +
"-DENABLE_BONDING=$BONDING " +
+ "-DENABLE_TESTING=$BUILD_TESTAPPS " +
"-DENABLE_UNITTESTS=$UNIT_TESTS"
# if VCPKG is flagged to provide OpenSSL, checkout VCPKG and install package
@@ -175,7 +191,7 @@ if ( $VCPKG_OPENSSL -eq 'ON' ) {
$cmakeFlags += " -DCMAKE_TOOLCHAIN_FILE=$projectRoot\vcpkg\scripts\buildsystems\vcpkg.cmake"
}
else {
- $cmakeFlags += " -DOPENSSL_USE_STATIC_LIBS=$STATIC_LINK_SSL "
+ $cmakeFlags += " -DSRT_USE_OPENSSL_STATIC_LIBS=$STATIC_LINK_SSL "
}
# cmake uses a flag for architecture from vs2019, so add that as a suffix
diff --git a/scripts/codecov/update.sh b/scripts/codecov/update.sh
new file mode 100755
index 0000000000..2d2b8b62d0
--- /dev/null
+++ b/scripts/codecov/update.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+HERE=`dirname $0`
+cd $HERE
+
+VERSION=hardcoded
+if [[ -n $1 ]]; then
+ VERSION=$1
+fi
+
+if [[ $VERSION == latest ]]; then
+ BASEURL=https://cli.codecov.io/latest/linux/codecov
+ SHA=
+else
+ BASEURL=https://cli.codecov.io/v11.3.1/linux/codecov
+ SHA="ca1d64196d2d34771084afe76ea657d581bf628e31d993ff8e52ea09cc88a56d codecov"
+ echo 'USING HARDCODED VERSION: 11.3.1. Use "update.sh latest" to get the latest version'
+ echo 'NOTE: Using the latest version is unsafe as SHA is also downloaded over the network'
+fi
+
+echo "DOWNLOADING: $BASEURL"
+rm -f codecov || { echo "Can't delete 'codecov'; please delete manually" ; exit 1; }
+
+curl -L -o codecov $BASEURL
+FILESHA=$(sha256sum codecov)
+
+if [[ -z $SHA ]]; then
+ echo "DOWNLOADING HASH: ${BASEURL}.SHA256SUM"
+ curl -L -o codecov.SHA256SUM ${BASEURL}.SHA256SUM
+ SHA=$(cat codecov.SHA256SUM)
+ echo "Downloaded HASH: $SHA"
+else
+ echo "Hardcoded HASH: $SHA"
+fi
+
+if [[ "$FILESHA" == "$SHA" ]]; then
+ echo "Checksum SHA256 matches."
+else
+ echo "ERROR: WRONG SHA256 CHECKSUM:"
+ echo "EXPECTED: $SHA"
+ echo "RECEIVED: $FILESHA"
+ rm codecov
+ echo "Corrupt file deleted"
+ exit 1
+fi
+
+chmod +x codecov
diff --git a/scripts/codespell/codespell.cfg b/scripts/codespell/codespell.cfg
new file mode 100644
index 0000000000..44f2ec9374
--- /dev/null
+++ b/scripts/codespell/codespell.cfg
@@ -0,0 +1,16 @@
+[codespell]
+
+# Enable built-in dictionaries/rules.
+# See more details for https://github.com/codespell-project/codespell/tree/main/codespell_lib/data.
+builtin = clear,rare,informal,code
+
+# Ignore words listed in this file.
+ignore-words = ./scripts/codespell/codespell_whitelist.txt
+ignore-regex = TEST\(.*\)
+
+# Add custom dictionary file.
+dictionary = ./scripts/codespell/codespell_dictionary.txt,-
+
+# Skip checking files or directories.
+skip = ./build/*,./.git/*
+
diff --git a/scripts/codespell/codespell_dictionary.txt b/scripts/codespell/codespell_dictionary.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/scripts/codespell/codespell_whitelist.txt b/scripts/codespell/codespell_whitelist.txt
new file mode 100644
index 0000000000..6e845b75cd
--- /dev/null
+++ b/scripts/codespell/codespell_whitelist.txt
@@ -0,0 +1,30 @@
+AnyOther
+ANS
+CAS
+connectIn
+deque
+dur
+endcode
+fo
+inout
+kinda
+lene
+lets
+lite
+LITE
+mane
+mut
+MUSL
+nd
+numer
+od
+RO
+stdio
+stoll
+snd
+Snd
+SND
+uint
+unsecure
+UNSECURE
+ws
diff --git a/scripts/collect-gcov.sh b/scripts/collect-gcov.sh
deleted file mode 100644
index 7b458e6c1a..0000000000
--- a/scripts/collect-gcov.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-shopt -s globstar
-gcov_data_dir="."
-for x in ./**/*.o; do
- echo "x: $x"
- gcov "$gcov_data_dir/$x"
-done
diff --git a/scripts/generate-error-types.tcl b/scripts/generate-error-types.tcl
index e4d29c6233..a24fd62168 100755
--- a/scripts/generate-error-types.tcl
+++ b/scripts/generate-error-types.tcl
@@ -109,17 +109,17 @@ set errortypes {
NOTSUP "Operation not supported" {
NONE ""
ISBOUND "Cannot do this operation on a BOUND socket"
- ISCONNECTED "Cannot do this operation on a CONNECTED socket"
+ ISCONNECTED "Cannot do this operation on a CONNECTED or LISTENING socket"
INVAL "Bad parameters"
SIDINVAL "Invalid socket ID"
ISUNBOUND "Cannot do this operation on an UNBOUND socket"
NOLISTEN "Socket is not in listening state"
ISRENDEZVOUS "Listen/accept is not supported in rendezvous connection setup"
ISRENDUNBOUND "Cannot call connect on UNBOUND socket in rendezvous connection setup"
- INVALMSGAPI "Incorrect use of Message API (sendmsg/recvmsg)."
- INVALBUFFERAPI "Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)."
+ INVALMSGAPI "Incorrect use of Message API (sendmsg/recvmsg)"
+ INVALBUFFERAPI "Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)"
BUSY "Another socket is already listening on the same port"
- XSIZE "Message is too large to send (it must be less than the SRT send buffer size)"
+ XSIZE "Message too large to send (exceeds send buffer size)"
EIDINVAL "Invalid epoll ID"
EEMPTY "All sockets removed from epoll, waiting would deadlock"
BUSYPORT "Another socket is bound to that port and is not reusable for requested settings"
@@ -169,7 +169,7 @@ const char* strerror_get_message(size_t major, size_t minor)
}
const char** array = strerror_array_major[major];
- size_t size = strerror_array_sizes[major];
+ const size_t size = strerror_array_sizes[major];
if (minor >= size)
{
diff --git a/scripts/generate-logging-defs.tcl b/scripts/generate-logging-defs.tcl
index a86ab9a62b..66622675d8 100755
--- a/scripts/generate-logging-defs.tcl
+++ b/scripts/generate-logging-defs.tcl
@@ -36,7 +36,7 @@ set loggers {
RSRC rs 5 "System resource allocation and management"
CONGEST cc 7 "Congestion control module"
PFILTER pf 8 "Packet filter module"
- API_CTRL ac 11 "API part for socket and library managmenet"
+ API_CTRL ac 11 "API part for socket and library management"
QUE_CTRL qc 13 "Queue control activities"
EPOLL_UPD ei 16 "EPoll, internal update activities"
@@ -140,7 +140,7 @@ proc GenerateModelForSrtH {} {
continue
}
- # End of generated section. Switch back to pass-thru.
+ # End of generated section. Switch back to pass-through.
# First fill the gap
append contents "\n\$entries\n\n"
diff --git a/scripts/get-build-version.tcl b/scripts/get-build-version.tcl
new file mode 100755
index 0000000000..fafda061a1
--- /dev/null
+++ b/scripts/get-build-version.tcl
@@ -0,0 +1,47 @@
+#!/usr/bin/tclsh
+
+if {![file exists version.h]} {
+ puts stderr "No version.h file found - run this in the build directory!"
+ exit 1
+}
+
+set fd [open version.h r]
+
+set version ""
+set line ""
+while {[gets $fd line] != -1} {
+ # Use regexp because this is safer to avoid
+ # unexpected list interpretation mistakes
+ if {[regexp {#define SRT_VERSION_STRING} $line]} {
+ # Once matched this way, we can safely use tcl-list-like access
+ set version [lindex $line 2 0]
+ break
+ }
+}
+close $fd
+
+if {$version == ""} {
+ puts stderr "No version extracted (no SRT_VERSION_STRING found)"
+ exit 1
+}
+
+lassign $argv model part
+
+if {$model == ""} {
+ set model current
+}
+
+lassign [split $version .] major minor patch
+
+if {$part == "minor"} {
+ set prefix "$minor"
+} else {
+ set prefix "$major.$minor"
+}
+
+if {$model == "base"} {
+ puts "$prefix.0"
+} else {
+ puts "$prefix.$patch"
+}
+
diff --git a/scripts/googletest-download.cmake b/scripts/googletest-download.cmake
index 7469f4f7b1..f1f6d4902d 100644
--- a/scripts/googletest-download.cmake
+++ b/scripts/googletest-download.cmake
@@ -1,7 +1,7 @@
# code copied from https://crascit.com/2015/07/25/cmake-gtest/
-cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
-project(googletest-download NONE)
+project(GOOGLETEST_DOWNLOAD)
include(ExternalProject)
@@ -11,7 +11,7 @@ ExternalProject_Add(
BINARY_DIR "@GOOGLETEST_DOWNLOAD_ROOT@/googletest-build"
GIT_REPOSITORY
https://github.com/google/googletest.git
- GIT_TAG release-1.10.0
+ GIT_TAG release-1.12.1
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
diff --git a/scripts/googletest.cmake b/scripts/googletest.cmake
index 57e5c937a5..96115ddbf0 100644
--- a/scripts/googletest.cmake
+++ b/scripts/googletest.cmake
@@ -24,7 +24,7 @@ macro(fetch_googletest _download_module_path _download_root)
${_download_root}
)
- # adds the targers: gtest, gtest_main, gmock, gmock_main
+ # adds the targets: gtest, gtest_main, gmock, gmock_main
add_subdirectory(
${_download_root}/googletest-src
${_download_root}/googletest-build
diff --git a/scripts/haiUtil.cmake b/scripts/haiUtil.cmake
index 2226619893..312480e1e4 100644
--- a/scripts/haiUtil.cmake
+++ b/scripts/haiUtil.cmake
@@ -288,3 +288,16 @@ function (parse_compiler_type wct _type _suffix)
endif()
endif()
endfunction()
+
+macro (srt_import_parent_options)
+ getVarsWith(LIBSRT_ extoptions)
+ foreach(ef ${extoptions})
+ set (val ${${ef}})
+ string(LENGTH LIBSRT_ pflen)
+ string(LENGTH ${ef} eflen)
+ math(EXPR alen ${eflen}-${pflen})
+ string(SUBSTRING ${ef} ${pflen} ${alen} ef)
+ message(STATUS "IMPORTED OPTION: ${ef}='${val}'")
+ set (${ef} "${val}")
+ endforeach()
+endmacro()
diff --git a/scripts/iOS.cmake b/scripts/iOS.cmake
index 1f2a04b500..ed3743dd64 100644
--- a/scripts/iOS.cmake
+++ b/scripts/iOS.cmake
@@ -8,8 +8,10 @@
# This decides if SDKS will be selected from the iPhoneOS.platform or iPhoneSimulator.platform folders
# OS - the default, used to build for iPhone and iPad physical devices, which have an arm arch.
# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch.
+# TV - used to build for tvOS devices
+# TV_SIMULATOR - used to build for tvOS Simulator platforms, which have an x86 arch.
#
-# IOS_ARCH = arm64 (default for OS), armv7, armv7s, i386 (default for SIMULATOR), x86_64 (default for SIMULATOR64)
+# IOS_ARCH = arm64 (default for OS and TV), armv7, armv7s, i386 (default for SIMULATOR), x86_64 (default for SIMULATOR64 and TV_SIMULATOR)
#
# CMAKE_IOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder
# By default this location is automatcially chosen based on the IOS_PLATFORM value above.
@@ -19,8 +21,6 @@
# By default this location is automatcially chosen based on the CMAKE_IOS_DEVELOPER_ROOT value.
# In this case it will always be the most up-to-date SDK found in the CMAKE_IOS_DEVELOPER_ROOT path.
# If set manually, this will force the use of a specific SDK version
-#
-# IOS_DISABLE_BITCODE - set to 1 if you want to disable bitcode generation
# Standard settings
set (CMAKE_SYSTEM_NAME Darwin)
@@ -29,14 +29,16 @@ set (UNIX True)
set (APPLE True)
set (IOS True)
+# With Xcode 16, Apple has deprecated bitcode for all platforms
+set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
+
# Required as of cmake 2.8.10
set (CMAKE_OSX_DEPLOYMENT_TARGET "" CACHE STRING "Force unset of the deployment target for iOS" FORCE)
# Determine the cmake host system version so we know where to find the iOS SDKs
find_program (CMAKE_UNAME uname /bin /usr/bin /usr/local/bin)
if (CMAKE_UNAME)
- execute_process(COMMAND uname -r
- OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION)
+ execute_process(COMMAND uname -r OUTPUT_VARIABLE CMAKE_HOST_SYSTEM_VERSION)
string (REGEX REPLACE "^([0-9]+)\\.([0-9]+).*$" "\\1" DARWIN_MAJOR_VERSION "${CMAKE_HOST_SYSTEM_VERSION}")
endif (CMAKE_UNAME)
@@ -49,10 +51,6 @@ set (CMAKE_C_OSX_CURRENT_VERSION_FLAG "-current_version ")
set (CMAKE_CXX_OSX_COMPATIBILITY_VERSION_FLAG "${CMAKE_C_OSX_COMPATIBILITY_VERSION_FLAG}")
set (CMAKE_CXX_OSX_CURRENT_VERSION_FLAG "${CMAKE_C_OSX_CURRENT_VERSION_FLAG}")
-if (NOT DEFINED IOS_DISABLE_BITCODE)
- set (EMBED_OPTIONS "-fembed-bitcode")
-endif(NOT DEFINED IOS_DISABLE_BITCODE)
-
if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR ENABLE_DEBUG)
set(IOS_DEBUG_OPTIONS "-glldb -gmodules")
else()
@@ -102,6 +100,12 @@ if (${IOS_PLATFORM} STREQUAL OS)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphoneos")
+elseif (${IOS_PLATFORM} STREQUAL TV)
+ set (IOS_PLATFORM_LOCATION "AppleTVOS.platform")
+ unset(CMAKE_IOS_DEVELOPER_ROOT CACHE)
+ unset(CMAKE_IOS_SDK_ROOT CACHE)
+ # This causes the installers to properly locate the output libraries
+ set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvos")
elseif (${IOS_PLATFORM} STREQUAL SIMULATOR)
set (SIMULATOR true)
set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
@@ -114,15 +118,35 @@ elseif (${IOS_PLATFORM} STREQUAL SIMULATOR64)
# This causes the installers to properly locate the output libraries
set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
+elseif (${IOS_PLATFORM} STREQUAL ARM_SIMULATOR)
+ set (SIMULATOR true)
+ set (IOS_PLATFORM_LOCATION "iPhoneSimulator.platform")
+
+ # This causes the installers to properly locate the output libraries
+ set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-iphonesimulator")
+elseif (${IOS_PLATFORM} STREQUAL TV_SIMULATOR)
+ set (SIMULATOR true)
+ set (IOS_PLATFORM_LOCATION "AppleTVSimulator.platform")
+ unset(CMAKE_IOS_DEVELOPER_ROOT CACHE)
+ unset(CMAKE_IOS_SDK_ROOT CACHE)
+
+ # This causes the installers to properly locate the output libraries
+ set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvsimulator")
+elseif (${IOS_PLATFORM} STREQUAL TV_ARM_SIMULATOR)
+ set (SIMULATOR true)
+ set (IOS_PLATFORM_LOCATION "AppleTVSimulator.platform")
+ unset(CMAKE_IOS_DEVELOPER_ROOT CACHE)
+ unset(CMAKE_IOS_SDK_ROOT CACHE)
+
+ # This causes the installers to properly locate the output libraries
+ set (CMAKE_XCODE_EFFECTIVE_PLATFORMS "-appletvsimulator")
else (${IOS_PLATFORM} STREQUAL OS)
- message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS or SIMULATOR")
+ message (FATAL_ERROR "Unsupported IOS_PLATFORM value selected. Please choose OS, TV or SIMULATOR")
endif (${IOS_PLATFORM} STREQUAL OS)
# Setup iOS developer location unless specified manually with CMAKE_IOS_DEVELOPER_ROOT
if (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
- execute_process(COMMAND /usr/bin/xcode-select -print-path
- OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR)
- string(STRIP "${CMAKE_XCODE_DEVELOPER_DIR}" CMAKE_XCODE_DEVELOPER_DIR) # FIXED: remove new line character, otherwise it complain no iOS SDK's found in default search path
+ execute_process(COMMAND /usr/bin/xcode-select -print-path OUTPUT_VARIABLE CMAKE_XCODE_DEVELOPER_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
set (CMAKE_IOS_DEVELOPER_ROOT "${CMAKE_XCODE_DEVELOPER_DIR}/Platforms/${IOS_PLATFORM_LOCATION}/Developer")
endif (NOT DEFINED CMAKE_IOS_DEVELOPER_ROOT)
set (CMAKE_IOS_DEVELOPER_ROOT ${CMAKE_IOS_DEVELOPER_ROOT} CACHE PATH "Location of iOS Platform")
@@ -152,6 +176,10 @@ if (NOT DEFINED IOS_ARCH)
set (IOS_ARCH i386)
elseif (${IOS_PLATFORM} STREQUAL SIMULATOR64)
set (IOS_ARCH x86_64)
+ elseif (${IOS_PLATFORM} STREQUAL ARM_SIMULATOR)
+ set (IOS_ARCH arm64)
+ elseif (${IOS_PLATFORM} STREQUAL TV_ARM_SIMULATOR)
+ set (IOS_ARCH arm64)
endif (${IOS_PLATFORM} STREQUAL OS)
endif(NOT DEFINED IOS_ARCH)
set (CMAKE_OSX_ARCHITECTURES ${IOS_ARCH} CACHE STRING "Build architecture for iOS")
@@ -169,8 +197,8 @@ set (CMAKE_SYSTEM_FRAMEWORK_PATH
${CMAKE_IOS_SDK_ROOT}/Developer/Library/Frameworks
)
-# only search the iOS sdks, not the remainder of the host filesystem (except for programs, so that we can still find Python if needed)
-set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
+# only search the iOS sdks, not the remainder of the host filesystem
+set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
diff --git a/scripts/srt-dev.lua b/scripts/srt-dev.lua
index a90ac487d9..efb875de5b 100644
--- a/scripts/srt-dev.lua
+++ b/scripts/srt-dev.lua
@@ -65,7 +65,7 @@ local msg_type_select = {
[0x7FFF] = "[Message Extension Type]"
}
fields.msg_type = ProtoField.uint16("srt_dev.msg_type", "Message Type", base.HEX, msg_type_select, 0x7FFF)
-fields.msg_ext_type = ProtoField.uint16("srt_dev.msg_ext_type", "Message Extented Type", base.DEC)
+fields.msg_ext_type = ProtoField.uint16("srt_dev.msg_ext_type", "Message Extended Type", base.DEC)
local flag_state_select = {
[0] = "Unset",
@@ -281,7 +281,7 @@ function srt_dev.dissector (tvb, pinfo, tree)
subtree:add(fields.ext_fld, tvb(offset, 2)):append_text(" [HSv5 MAGIC]")
else
-- Extension Field is HS_EXT_prefix
- -- The define is in fiel handshake.h
+ -- The define is in file handshake.h
local ext_fld_tree = subtree:add(fields.ext_fld_tree, tvb(offset, 2))
local str_table = { " [" }
ext_fld_tree:add(fields.hsreq, tvb(offset, 2))
diff --git a/scripts/srt-proxy.py b/scripts/srt-proxy.py
new file mode 100755
index 0000000000..27bbed0125
--- /dev/null
+++ b/scripts/srt-proxy.py
@@ -0,0 +1,168 @@
+#!/usr/bin/python3
+
+# This script is a testing tool that inserts a packet interceptor for
+# a bond between two local ports and modify a packet that is sent over
+# the UDP link used by the SRT connection.
+
+# Provided by:
+# https://github.com/FelixSodermanNeti
+
+import socket, select, argparse
+
+class UDPProxy:
+ def __init__(self, caller_host, caller_port, listener_host, listener_port, break_at_pkt_NAK, break_at_pkt_ACK):
+
+ # Listening socket configuration
+ self.caller_host = caller_host
+ self.caller_port = caller_port
+ self.caller_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ self.caller_socket.bind((self.caller_host, self.caller_port))
+
+ # Forwarding destination configuration
+ self.listener_host = listener_host
+ self.listener_port = listener_port
+
+ # Client address tracking
+ self.client_address = None
+
+ # Proxy state
+ self.running = False
+
+ # Break SRT
+ self.sentPacketCounter = 0
+ self.recivedAckCounter = 0
+ self.PACKETBREAK_NAK = break_at_pkt_NAK
+ self.PACKETBREAK_ACK = break_at_pkt_ACK
+
+ def start(self):
+ try:
+ self.running = True
+ print(
+ f"UDP Proxy started. Listening on {self.caller_host}:{self.caller_port}. "
+ f"Forwarding to {self.listener_host}:{self.listener_port}"
+ )
+
+ # Create forward socket
+ listener_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+ # Use select for non-blocking I/O
+ while self.running:
+ readable, _, _ = select.select([self.caller_socket, listener_socket], [], [], 1)
+
+ for sock in readable:
+ if sock == self.caller_socket:
+ # Receive from client
+ data, addr = sock.recvfrom(65535)
+ self.client_address = addr
+
+ #Modify the NAK
+ processedData = data
+ # NAK
+ if(data[1] == 0x03 and len(data) == 24 and self.PACKETBREAK_NAK != 0): # IS A NAK, This should check for a 1 in the first byte as well.
+ print("Modifying NAK.")
+ newBytes = b"\xff\xff\xff\xff"
+ processedData = data[:20] + newBytes + data[20 + len(newBytes):]
+ """
+ print("NAK BEFORE:")
+ print(string_bytearray_in_rows(data=data))
+ print("\n\nModifying NAK.\n\n")
+ print("ŃAK AFTER:")
+ print(string_bytearray_in_rows(data=processedData))
+ """
+
+ # ACK
+ if(data[1] == 0x02 and len(data) == 44): # IS A ACK
+ self.recivedAckCounter += 1
+ if (self.PACKETBREAK_ACK != 0 and self.recivedAckCounter == self.PACKETBREAK_ACK):
+ print("Modifying ACK.")
+ newBytes = b"\x7f\xff\xff\xff"
+ offset = 16
+ processedData = data[:offset] + newBytes + data[offset + len(newBytes):]
+ """
+ print("ACK BEFORE:")
+ print(string_bytearray_in_rows(data=data))
+ print("\n\nModifying ACK.\n\n")
+ print("ACK AFTER:")
+ print(string_bytearray_in_rows(data=processedData))
+ """
+ listener_socket.sendto(
+ processedData,
+ (self.listener_host, self.listener_port)
+ )
+
+ elif sock == listener_socket:
+ # Receive from server
+ data, addr = sock.recvfrom(65535)
+
+ # Drop a sequence of data packets (as to at least 3 to trigger an NAK with a range)
+ if len(data) == 1332: # Packet with data
+ self.sentPacketCounter += 1
+ if (self.PACKETBREAK_NAK != 0 and self.sentPacketCounter in range(self.PACKETBREAK_NAK, self.PACKETBREAK_NAK+5)):
+ print("Discarding packet.")
+ continue
+
+ if not self.client_address:
+ print("No client to send data to!")
+ continue
+
+ self.caller_socket.sendto(
+ data,
+ self.client_address
+ )
+
+ except Exception as e:
+ print(f"Proxy error: {e}")
+
+ finally:
+ self.stop()
+
+ def stop(self):
+ self.running = False
+ self.caller_socket.close()
+ print("UDP Proxy stopped.")
+
+
+def string_bytearray_in_rows(data, bytes_per_row=4):
+ tmpString = "\n"
+ for i in range(0, len(data), bytes_per_row):
+ row = data[i:i+bytes_per_row]
+ tmpString += f"{i}: "
+ tmpString += (" ".join(f"{byte:02x}" for byte in row))
+ tmpString += "\n"
+ tmpString += "\n"
+ return tmpString
+
+def main():
+ # Parse command-line arguments
+ parser = argparse.ArgumentParser(description='UDP Proxy')
+ parser.add_argument('--caller-host', default='127.0.0.1',
+ help='Host to listen on (srt caller) (default: 127.0.0.1)')
+ parser.add_argument('--caller-port', type=int, required=True,
+ help='Port to listen on (srt caller)')
+ parser.add_argument('--listener-host', default='127.0.0.1',
+ help='Destination host to forward packets (srt listener)')
+ parser.add_argument('--listener-port', type=int, required=True,
+ help='Destination port to forward packets (srt listener)')
+ parser.add_argument('--break-at-pkt-NAK', type=int, default=0,
+ help='At what datapacket should the NAK -1 be sent (0=never)')
+ parser.add_argument('--break-at-pkt-ACK', type=int, default=0,
+ help='At what datapacket should the ACK -1 be sent (0=never)')
+ args = parser.parse_args()
+
+ # Create and start proxy
+ proxy = UDPProxy(
+ args.caller_host,
+ args.caller_port,
+ args.listener_host,
+ args.listener_port,
+ args.break_at_pkt_NAK,
+ args.break_at_pkt_ACK
+ )
+
+ try:
+ proxy.start()
+ except KeyboardInterrupt:
+ proxy.stop()
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/visionOS.cmake b/scripts/visionOS.cmake
index 0f0f60b69e..cfb6006738 100644
--- a/scripts/visionOS.cmake
+++ b/scripts/visionOS.cmake
@@ -9,7 +9,7 @@
# OS - the default, used to build for Vision Pro physical device, which have an arm arch.
# SIMULATOR - used to build for the Simulator platforms, which have an x86 arch.
#
-# VISIONOS_ARCH = arm64 (default for OS), x86_64 (addiitonal support for SIMULATOR64)
+# VISIONOS_ARCH = arm64 (default for OS), x86_64 (additional support for SIMULATOR64)
#
# CMAKE_VISIONOS_DEVELOPER_ROOT = automatic(default) or /path/to/platform/Developer folder
# By default this location is automatcially chosen based on the VISIONOS_PLATFORM value above.
diff --git a/scripts/win-installer/ATTIC/old-install-nsis.ps1 b/scripts/win-installer/ATTIC/old-install-nsis.ps1
new file mode 100644
index 0000000000..0bf58afbc1
--- /dev/null
+++ b/scripts/win-installer/ATTIC/old-install-nsis.ps1
@@ -0,0 +1,128 @@
+#-----------------------------------------------------------------------------
+#
+# SRT - Secure, Reliable, Transport
+# Copyright (c) 2021, Thierry Lelegard
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#-----------------------------------------------------------------------------
+
+<#
+ .DESCRIPTION
+
+ Download, expand and install NSIS, the NullSoft Installer Scripting.
+
+ IMPORTANT NOTE: This is the old version, left for historical reasons. Usage
+ of this script is discouraged because this scripts doesn't check the MD5
+ checksum on the downloaded file and therefore is considered unsafe.
+
+ .PARAMETER ForceDownload
+
+ Force a download even if NSIS is already downloaded.
+
+ .PARAMETER NoInstall
+
+ Do not install the NSIS package. By default, NSIS is installed.
+
+ .PARAMETER NoPause
+
+ Do not wait for the user to press at end of execution. By default,
+ execute a "pause" instruction at the end of execution, which is useful
+ when the script was run from Windows Explorer.
+#>
+[CmdletBinding(SupportsShouldProcess=$true)]
+param(
+ [switch]$ForceDownload = $false,
+ [switch]$NoInstall = $false,
+ [switch]$NoPause = $false
+)
+
+Write-Output "NSIS download and installation procedure"
+$NSISPage = "https://nsis.sourceforge.io/Download"
+$FallbackURL = "http://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download"
+
+# A function to exit this script.
+function Exit-Script([string]$Message = "")
+{
+ $Code = 0
+ if ($Message -ne "") {
+ Write-Output "ERROR: $Message"
+ $Code = 1
+ }
+ if (-not $NoPause) {
+ pause
+ }
+ exit $Code
+}
+
+# Local file names.
+$RootDir = $PSScriptRoot
+$TmpDir = "$RootDir\tmp"
+
+# Create the directory for external products when necessary.
+[void] (New-Item -Path $TmpDir -ItemType Directory -Force)
+
+# Without this, Invoke-WebRequest is awfully slow.
+$ProgressPreference = 'SilentlyContinue'
+
+# Get the HTML page for NSIS downloads.
+$status = 0
+$message = ""
+$Ref = $null
+try {
+ $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $NSISPage
+ $status = [int] [Math]::Floor($response.StatusCode / 100)
+}
+catch {
+ $message = $_.Exception.Message
+}
+
+if ($status -ne 1 -and $status -ne 2) {
+ # Error fetch NSIS download page.
+ if ($message -eq "" -and (Test-Path variable:response)) {
+ Write-Output "Status code $($response.StatusCode), $($response.StatusDescription)"
+ }
+ else {
+ Write-Output "#### Error accessing ${NSISPage}: $message"
+ }
+}
+else {
+ # Parse HTML page to locate the latest installer.
+ $Ref = $response.Links.href | Where-Object { $_ -like "*/nsis-*-setup.exe?download" } | Select-Object -First 1
+}
+
+if (-not $Ref) {
+ # Could not find a reference to NSIS installer.
+ $Url = [System.Uri]$FallbackURL
+}
+else {
+ # Build the absolute URL's from base URL (the download page) and href links.
+ $Url = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$NSISPage, $Ref)
+}
+
+Write-Output "DOWNLOADING: $Url"
+
+$InstallerName = (Split-Path -Leaf $Url.LocalPath)
+$InstallerPath = "$TmpDir\$InstallerName"
+
+# Download installer
+if (-not $ForceDownload -and (Test-Path $InstallerPath)) {
+ Write-Output "$InstallerName already downloaded, use -ForceDownload to download again"
+}
+else {
+ Write-Output "Downloading $Url ..."
+ Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $InstallerPath
+ if (-not (Test-Path $InstallerPath)) {
+ Exit-Script "$Url download failed"
+ }
+}
+
+# Install NSIS
+if (-not $NoInstall) {
+ Write-Output "Installing $InstallerName"
+ Start-Process -FilePath $InstallerPath -ArgumentList @("/S") -Wait
+}
+
+Exit-Script
diff --git a/scripts/win-installer/README.md b/scripts/win-installer/README.md
index ff54df2588..135071b39b 100644
--- a/scripts/win-installer/README.md
+++ b/scripts/win-installer/README.md
@@ -7,12 +7,21 @@ libsrt on Windows systems for Visual Studio applications using SRT.
### Prerequisites
-These first two steps need to be executed once only.
+These initial steps need to be executed once only.
-- Prerequisite 1: Install OpenSSL for Windows, both 64 and 32 bits.
+- Prerequisite 1: Install Visual Studio. The free Community Edition is recommended
+ for open-source software. See https://visualstudio.microsoft.com/. Be sure to
+ install the compilation tools for the all target architectures, including 64-bit Arm.
+ In the Visual Studio Installer program, select "Modify" -> "Individual Components".
+ In section "Compilers, build tools, and runtimes", select "MSVC v143 - VS2022 C++
+ ARM64/ARM64EC build tools (latest)" (the exact version may vary).
+
+- Prerequisite 2: Install OpenSSL for Windows, 64-bit Intel, 32-bit Intel, 64-bit Arm.
This can be done automatically by running the PowerShell script `install-openssl.ps1`.
+ If you are interested in more information about OpenSSL for Windows, scroll to the
+ appendix at the end of this page.
-- Prerequisite 2: Install NSIS, the NullSoft Installation Scripting system.
+- Prerequisite 3: Install NSIS, the NullSoft Installation Scripting system.
This can be done automatically by running the PowerShell script `install-nsis.ps1`.
### Building the libsrt installer
@@ -26,7 +35,7 @@ Optional parameters:
- `-Version name` :
Use the specified string as version number for libsrt. By default, if the
current commit has a tag, use that tag (initial "v" removed, for instance
- `1.4.3`). Otherwise, the defaut version is a detailed version number (most
+ `1.4.3`). Otherwise, the default version is a detailed version number (most
recent version, number of commits since then, short commit SHA, for instance
`1.4.3-32-g22cc924`). Use that option if necessary to specify some other
non-standard form of version string.
@@ -121,3 +130,80 @@ This directory contains the following files:
| libsrt.nsi | NSIS installation script (used to build the installer).
| libsrt.props | Visual Studio property files to use libsrt (embedded in the installer).
| README.md | This text file.
+
+## Appendix: Using OpenSSL on Windows
+
+The SRT protocol uses encryption. Internally, `libsrt` can use multiple cryptographic
+libraries but OpenSSL is the default choice on all platforms, including Windows.
+
+OpenSSL is not part of the base installation of a Windows platform and should be
+separately installed. OpenSSL is neither "Windows-friendly" and the OpenSSL team
+does not provide Windows binaries.
+
+The production of OpenSSL binaries for Windows is delegated to Shining Light
+Productions (http://slproweb.com/). Binaries for all architectures are available
+here: http://slproweb.com/products/Win32OpenSSL.html
+
+### How to grab OpenSSL installers for Windows
+
+This is automatically done by the PowerShell script `install-openssl.ps1` in
+this directory. Let's see what's behind the scene.
+
+First, the script determines which installers should be downloaded and where
+to get them from.
+
+Initially, the HTML for the [slproweb](http://slproweb.com/products/Win32OpenSSL.html)
+page was built on server-side. Our script grabbed the URL content, parsed the HTML
+and extracted the URL of the binaries for the latest OpenSSL packages from the
+"href" of the links.
+
+At some point in 2024, the site changed policy. The Web page was no longer
+built on server-side, but on client-side. The downloaded HTML contains some
+JavaScript which dynamically builds the URL of the binaries for the latest
+OpenSSL binaries. The previous method now finds no valid package URL from
+the downloaded page (a full browser is needed to execute the JavaScript).
+The JavaScript downloads a JSON file which contains all references to
+the OpenSSL binaries.
+
+Therefore, the script `install-openssl.ps1` now uses the same method:
+download that JSON file and parse it.
+
+### Multi-architecture builds
+
+The `libsrt` installer contains static libraries for all three architectures
+which are supported by Windows: x86, x64, Arm64. The corresponding static
+libraries for OpenSSL are also needed and included in the installer.
+
+Because Visual Studio can build libraries and executables for all three
+architectures, on any build system, we need to install the OpenSSL headers
+and libraries for all three architectures, on the build system.
+
+This is what the script `install-openssl.ps1` does. However, the way to
+do this has changed over time.
+
+As a starting point, there are three installers, one per architecture.
+All installers are x86 executables which can be run on any Windows system
+(Arm64 Windows emulates x86 and x64 when necessary). Additionally, the
+three sets of files are installed side by side, without overlap.
+
+- x86: `C:\Program Files (x86)\OpenSSL-Win32`
+- x64: `C:\Program Files\OpenSSL-Win64`
+- Arm64: `C:\Program Files\OpenSSL-Win64-ARM`
+
+So, the script `install-openssl.ps1` installed them all and `libsrt` could
+be built for all architecture.
+
+However, it works only on Arm64 systems. On x86 and x64 system, the OpenSSL
+installer for Arm64 fails. The installer executable can run but there is an
+explicit check in the installation process to abort when running on Intel
+systems.
+
+Since most Windows build servers are Intel systems, this approach was no
+longer appropriate.
+
+At some point, the producers of the OpenSSL binaries acknowledged the problem
+and they created an additional form of installer: the "universal" one. This
+package can be installed on any system and the binaries and headers are installed
+for all architectures, in one single location.
+
+- Universal: `C:\Program Files (x86)\OpenSSL-WinUniversal`
diff --git a/scripts/win-installer/build-win-installer.ps1 b/scripts/win-installer/build-win-installer.ps1
old mode 100644
new mode 100755
index d08ee186c2..4778426ebb
--- a/scripts/win-installer/build-win-installer.ps1
+++ b/scripts/win-installer/build-win-installer.ps1
@@ -1,4 +1,5 @@
-#-----------------------------------------------------------------------------
+#!/usr/bin/env PowerShell
+#-----------------------------------------------------------------------------
#
# SRT - Secure, Reliable, Transport
# Copyright (c) 2021, Thierry Lelegard
@@ -18,9 +19,14 @@
Use the specified string as version number from libsrt. By default, if
the current commit has a tag, use that tag (initial 'v' removed). Otherwise,
- the defaut version is a detailed version number (most recent version, number
+ the default version is a detailed version number (most recent version, number
of commits since then, short commit SHA).
+ .PARAMETER NoBuild
+
+ Do not rebuild the SRT libraries. Assume that they are already built.
+ Only build the installer.
+
.PARAMETER NoPause
Do not wait for the user to press at end of execution. By default,
@@ -31,7 +37,9 @@
[CmdletBinding()]
param(
[string]$Version = "",
- [switch]$NoPause = $false
+ [switch]$NoBuild = $false,
+ [switch]$NoPause = $false,
+ [string]$OnlyPlatform = ""
)
Write-Output "Building the SRT static libraries installer for Windows"
@@ -89,50 +97,36 @@ Write-Output "Windows version info: $VersionInfo"
#-----------------------------------------------------------------------------
# Locate OpenSSL root from local installation.
-# After version 3.something, OpenSSL has changed its installation layout.
-# We have to look for OpenSSL libraries in various locations.
-$SSL = @{
- "x64" = @{
- "alt" = "Win64";
- "bits" = 64;
- "root" = "C:\Program Files\OpenSSL-Win64"
- };
- "Win32" = @{
- "alt" = "x86";
- "bits" = 32;
- "root" = "C:\Program Files (x86)\OpenSSL-Win32"
- }
-}
+# We now requires the "universal" OpenSSL installer (see README.md).
+# The universal OpenSSL installer is installed in $SSL.
+# $ARCH translates between VC "platform" names to subdirectory names inside $SSL.
+# $LIBDIR translates between VC "configuration" names to library subdirectory names.
+
+$SSL = "C:\Program Files (x86)\OpenSSL-WinUniversal"
+$ARCH = @{x64 = "x64"; Win32 = "x86"; ARM64 = "arm64"}
+$LIBDIR = @{Release = "MD"; Debug = "MDd"}
+$LIBFILE = @{crypto = "libcrypto_static.lib"; ssl = "libssl_static.lib"}
+
+function ssl-include($pf) { return "$SSL\include\$($ARCH.$pf)" }
+function ssl-libdir($pf, $conf) { return "$SSL\lib\VC\$($ARCH.$pf)\$($LIBDIR.$conf)" }
+function ssl-lib($pf, $conf, $lib) { return "$(ssl-libdir $pf $conf)\$($LIBFILE.$lib)" }
# Verify OpenSSL directories and static libraries.
Write-Output "Searching OpenSSL libraries ..."
$Missing = 0
-foreach ($arch in @("x64", "Win32")) {
- $root = $SSL[$arch]["root"]
- $bits = $SSL[$arch]["bits"]
- $alt = $SSL[$arch]["alt"]
- if (-not (Test-Path $root)) {
- Write-Output "**** Missing $root"
+foreach ($Platform in $SSL.Keys) {
+ if ($OnlyPlatform -ne "" -and $Platform -ne $OnlyPlatform) {
+ continue
+ }
+ if (-not (Test-Path -PathType Container $(ssl-include $Platform))) {
+ Write-Output "**** Missing $(ssl-include $Platform)"
$Missing = $Missing + 1
}
- else {
- foreach ($lib in @("ssl", "crypto")) {
- foreach ($conf in @("MD", "MDd")) {
- $name = "lib${lib}${conf}"
- $SSL[$arch][$name] = ""
- foreach ($try in @("$root\lib\VC\static\lib${lib}${bits}${conf}.lib",
- "$root\lib\VC\${arch}\${conf}\lib${lib}_static.lib",
- "$root\lib\VC\${alt}\${conf}\lib${lib}_static.lib")) {
- if (Test-Path $try) {
- $SSL[$arch][$name] = $try
- New-Variable "lib${lib}${bits}${conf}" "$try"
- break
- }
- }
- if (-not $SSL[$arch][$name]) {
- Write-Output "**** OpenSSL static library for $name not found"
- $Missing = $Missing + 1
- }
+ foreach ($lib in $LIBFILE.Keys) {
+ foreach ($conf in $LIBDIR.Keys) {
+ if (-not (Test-Path $(ssl-lib $Platform $conf $lib))) {
+ Write-Output "**** Missing $(ssl-lib $Platform $conf $lib)"
+ $Missing = $Missing + 1
}
}
}
@@ -180,45 +174,70 @@ Write-Output "NSIS: $NSIS"
#-----------------------------------------------------------------------------
-# Configure and build SRT library using CMake on two architectures.
+# Configure and build SRT library using CMake on all architectures.
#-----------------------------------------------------------------------------
-foreach ($Platform in @("x64", "Win32")) {
-
- # Build directory. Cleanup to force a fresh cmake config.
- $BuildDir = "$TmpDir\build.$Platform"
- Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir
- [void](New-Item -Path $BuildDir -ItemType Directory -Force)
-
- # Run CMake.
- Write-Output "Configuring build for platform $Platform ..."
- $SRoot = $SSL[$Platform]["root"]
- & $CMake -S $RepoDir -B $BuildDir -A $Platform `
- -DENABLE_STDCXX_SYNC=ON `
- -DOPENSSL_ROOT_DIR="$SRoot" `
- -DOPENSSL_LIBRARIES="$SRoot\lib\libssl_static.lib;$SRoot\lib\libcrypto_static.lib" `
- -DOPENSSL_INCLUDE_DIR="$SRoot\include"
-
- # Patch version string in version.h
- Get-Content "$BuildDir\version.h" |
- ForEach-Object {
- $_ -replace "#define *SRT_VERSION_STRING .*","#define SRT_VERSION_STRING `"$Version`""
- } |
- Out-File "$BuildDir\version.new" -Encoding ascii
- Move-Item "$BuildDir\version.new" "$BuildDir\version.h" -Force
-
- # Compile SRT.
- Write-Output "Building for platform $Platform ..."
- foreach ($Conf in @("Release", "Debug")) {
- & $MSBuild "$BuildDir\SRT.sln" /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static
+if (-not $NoBuild) {
+ Write-Output "BUILD ENABLED - configuring build"
+ if ($OnlyPlatform -ne "") {
+ Write-Output "LIMIT TO PLATFORM: $OnlyPlatform"
+ }
+ foreach ($Platform in $ARCH.Keys) {
+ if ($OnlyPlatform -ne "" -and $Platform -ne $OnlyPlatform) {
+ Write-Output "Skipping platform $Platform."
+ continue
+ }
+ Write-Output "Preparing platform $Platform."
+ # Build directory. Cleanup to force a fresh cmake config.
+ $BuildDir = "$TmpDir\build.$Platform"
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $BuildDir
+ [void](New-Item -Path $BuildDir -ItemType Directory -Force)
+
+ # Run CMake.
+ Write-Output "Configuring build for platform $Platform ..."
+ $params = @("-S", "$RepoDir",
+ "-B", "$BuildDir",
+ "-A", "$Platform",
+ "-DENABLE_STDCXX_SYNC=ON",
+ "-DOPENSSL_INCLUDE_DIR=$(ssl-include $Platform)",
+ "-DOPENSSL_ROOT_DIR=$(ssl-libdir $Platform Release)"
+ )
+ Write-Output "Running: cmake $params"
+ & $CMake $params
+ # Patch version string in version.h
+ Get-Content "$BuildDir\version.h" |
+ ForEach-Object {
+ $_ -replace "#define *SRT_VERSION_STRING .*","#define SRT_VERSION_STRING `"$Version`""
+ } |
+ Out-File "$BuildDir\version.new" -Encoding ascii
+ Move-Item "$BuildDir\version.new" "$BuildDir\version.h" -Force
+
+ # Compile SRT.
+ Write-Output "Building for platform $Platform ..."
+ foreach ($Conf in $LIBDIR.Keys) {
+ # The solution file format depends on the CMake version.
+ # Try new XML solution file format first.
+ $SolFile = "$BuildDir\SRT.slnx"
+ if (-not (Test-Path $SolFile)) {
+ # Try legacy solution file format.
+ $SolFile = "$BuildDir\SRT.sln"
+ }
+ if (-not (Test-Path $SolFile)) {
+ Exit-Script "Solution file $BuildDir\SRT.sln[x] not found, check CMake output"
+ }
+ & $MSBuild $SolFile /nologo /maxcpucount /property:Configuration=$Conf /property:Platform=$Platform /target:srt_static
+ }
}
}
# Verify the presence of compiled libraries.
Write-Output "Checking compiled libraries ..."
$Missing = 0
-foreach ($Conf in @("Release", "Debug")) {
- foreach ($Platform in @("x64", "Win32")) {
+foreach ($Conf in $LIBDIR.Keys) {
+ foreach ($Platform in $SSL.Keys) {
+ if ($OnlyPlatform -ne "" -and $Platform -ne $OnlyPlatform) {
+ continue
+ }
$Path = "$TmpDir\build.$Platform\$Conf\srt_static.lib"
if (-not (Test-Path $Path)) {
Write-Output "**** Missing $Path"
@@ -245,14 +264,18 @@ Write-Output "Building installer ..."
/DOutDir="$OutDir" `
/DBuildRoot="$TmpDir" `
/DRepoDir="$RepoDir" `
- /Dlibssl32MD="$libssl32MD" `
- /Dlibssl32MDd="$libssl32MDd" `
- /Dlibcrypto32MD="$libcrypto32MD" `
- /Dlibcrypto32MDd="$libcrypto32MDd" `
- /Dlibssl64MD="$libssl64MD" `
- /Dlibssl64MDd="$libssl64MDd" `
- /Dlibcrypto64MD="$libcrypto64MD" `
- /Dlibcrypto64MDd="$libcrypto64MDd" `
+ /DlibsslWin32Release="$(ssl-lib Win32 Release ssl)" `
+ /DlibsslWin32Debug="$(ssl-lib Win32 Debug ssl)" `
+ /DlibcryptoWin32Release="$(ssl-lib Win32 Release crypto)" `
+ /DlibcryptoWin32Debug="$(ssl-lib Win32 Debug crypto)" `
+ /DlibsslWin64Release="$(ssl-lib x64 Release ssl)" `
+ /DlibsslWin64Debug="$(ssl-lib x64 Debug ssl)" `
+ /DlibcryptoWin64Release="$(ssl-lib x64 Release crypto)" `
+ /DlibcryptoWin64Debug="$(ssl-lib x64 Debug crypto)" `
+ /DlibsslArm64Release="$(ssl-lib Arm64 Release ssl)" `
+ /DlibsslArm64Debug="$(ssl-lib Arm64 Debug ssl)" `
+ /DlibcryptoArm64Release="$(ssl-lib Arm64 Release crypto)" `
+ /DlibcryptoArm64Debug="$(ssl-lib Arm64 Debug crypto)" `
"$ScriptDir\libsrt.nsi"
if (-not (Test-Path $InstallExe)) {
diff --git a/scripts/win-installer/install-libsrt.ps1 b/scripts/win-installer/install-libsrt.ps1
old mode 100644
new mode 100755
index ae1b8133e6..4aacb085c9
--- a/scripts/win-installer/install-libsrt.ps1
+++ b/scripts/win-installer/install-libsrt.ps1
@@ -1,4 +1,5 @@
-# SRT library download and install for Windows.
+#!/usr/bin/env PowerShell
+# SRT library download and install for Windows.
# Copyright (c) 2021, Thierry Lelegard
# All rights reserved.
diff --git a/scripts/win-installer/install-nsis.ps1 b/scripts/win-installer/install-nsis.ps1
old mode 100644
new mode 100755
index 14879b5217..ea48d14ae3
--- a/scripts/win-installer/install-nsis.ps1
+++ b/scripts/win-installer/install-nsis.ps1
@@ -1,122 +1,163 @@
-#-----------------------------------------------------------------------------
-#
-# SRT - Secure, Reliable, Transport
-# Copyright (c) 2021, Thierry Lelegard
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-#-----------------------------------------------------------------------------
-
-<#
- .SYNOPSIS
-
- Download, expand and install NSIS, the NullSoft Installer Scripting.
-
- .PARAMETER ForceDownload
-
- Force a download even if NSIS is already downloaded.
-
- .PARAMETER NoInstall
-
- Do not install the NSIS package. By default, NSIS is installed.
-
- .PARAMETER NoPause
-
- Do not wait for the user to press at end of execution. By default,
- execute a "pause" instruction at the end of execution, which is useful
- when the script was run from Windows Explorer.
-#>
-[CmdletBinding(SupportsShouldProcess=$true)]
-param(
- [switch]$ForceDownload = $false,
- [switch]$NoInstall = $false,
- [switch]$NoPause = $false
-)
-
-Write-Output "NSIS download and installation procedure"
-$NSISPage = "https://nsis.sourceforge.io/Download"
-$FallbackURL = "http://prdownloads.sourceforge.net/nsis/nsis-3.05-setup.exe?download"
-
-# A function to exit this script.
-function Exit-Script([string]$Message = "")
-{
- $Code = 0
- if ($Message -ne "") {
- Write-Output "ERROR: $Message"
- $Code = 1
- }
- if (-not $NoPause) {
- pause
- }
- exit $Code
-}
-
-# Local file names.
-$RootDir = $PSScriptRoot
-$TmpDir = "$RootDir\tmp"
-
-# Create the directory for external products when necessary.
-[void] (New-Item -Path $TmpDir -ItemType Directory -Force)
-
-# Without this, Invoke-WebRequest is awfully slow.
-$ProgressPreference = 'SilentlyContinue'
-
-# Get the HTML page for NSIS downloads.
-$status = 0
-$message = ""
-$Ref = $null
-try {
- $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $NSISPage
- $status = [int] [Math]::Floor($response.StatusCode / 100)
-}
-catch {
- $message = $_.Exception.Message
-}
-
-if ($status -ne 1 -and $status -ne 2) {
- # Error fetch NSIS download page.
- if ($message -eq "" -and (Test-Path variable:response)) {
- Write-Output "Status code $($response.StatusCode), $($response.StatusDescription)"
- }
- else {
- Write-Output "#### Error accessing ${NSISPage}: $message"
- }
-}
-else {
- # Parse HTML page to locate the latest installer.
- $Ref = $response.Links.href | Where-Object { $_ -like "*/nsis-*-setup.exe?download" } | Select-Object -First 1
-}
-
-if (-not $Ref) {
- # Could not find a reference to NSIS installer.
- $Url = [System.Uri]$FallbackURL
-}
-else {
- # Build the absolute URL's from base URL (the download page) and href links.
- $Url = New-Object -TypeName 'System.Uri' -ArgumentList ([System.Uri]$NSISPage, $Ref)
-}
-
-$InstallerName = (Split-Path -Leaf $Url.LocalPath)
-$InstallerPath = "$TmpDir\$InstallerName"
-
-# Download installer
-if (-not $ForceDownload -and (Test-Path $InstallerPath)) {
- Write-Output "$InstallerName already downloaded, use -ForceDownload to download again"
-}
-else {
- Write-Output "Downloading $Url ..."
- Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $InstallerPath
- if (-not (Test-Path $InstallerPath)) {
- Exit-Script "$Url download failed"
- }
-}
-
-# Install NSIS
-if (-not $NoInstall) {
- Write-Output "Installing $InstallerName"
- Start-Process -FilePath $InstallerPath -ArgumentList @("/S") -Wait
-}
-
-Exit-Script
+#!/usr/bin/env PowerShell
+#-----------------------------------------------------------------------------
+#
+# SRT - Secure, Reliable, Transport
+# Copyright (c) 2021, Thierry Lelegard
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#-----------------------------------------------------------------------------
+
+<#
+ .DESCRIPTION
+
+ Download, expand and install NSIS, the NullSoft Installer Scripting.
+
+ .PARAMETER ForceDownload
+
+ Force a download even if NSIS is already downloaded. By default the
+ installer file is not redownloaded, if already found, although the
+ MD5 sum will still be checked.
+
+ .PARAMETER NoInstall
+
+ Do not install the NSIS package. By default, NSIS is installed.
+
+ .PARAMETER NoPause
+
+ Do not wait for the user to press at end of execution. By default,
+ execute a "pause" instruction at the end of execution, which is useful
+ when the script was run from Windows Explorer.
+#>
+[CmdletBinding(SupportsShouldProcess=$true)]
+param(
+ [switch]$ForceDownload = $false,
+ [switch]$NoInstall = $false,
+ [switch]$NoPause = $false,
+ [switch]$Latest = $false
+)
+
+# A function to exit this script.
+function Exit-Script([string]$Message = "")
+{
+ $Code = 0
+ if ($Message -ne "") {
+ Write-Output "ERROR: $Message"
+ $Code = 1
+ }
+ if (-not $NoPause) {
+ pause
+ }
+ exit $Code
+}
+
+# Local file names.
+$RootDir = $PSScriptRoot
+$TmpDir = "$RootDir\tmp"
+
+# Create the directory for external products when necessary.
+[void] (New-Item -Path $TmpDir -ItemType Directory -Force)
+
+# Without this, Invoke-WebRequest is awfully slow.
+$ProgressPreference = 'SilentlyContinue'
+
+# 1. Define Project URLs
+$ProjectUrl = "https://sourceforge.net/projects/nsis/files/NSIS%203/"
+$UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
+$DownloadHead = "https://prdownloads.sourceforge.net/nsis/"
+
+# Latest download, on-demand only. Default is a hardcoded version.
+if ($Latest) {
+ # 2. Find the Latest Version Folder
+ Write-Host "Checking for the latest NSIS version..."
+ $ProjectPage = Invoke-WebRequest -Uri $ProjectUrl -UserAgent $UserAgent -UseBasicParsing
+ # Matches version folders like "3.10", "3.12", etc.
+ $Versions = [regex]::matches($ProjectPage.Content, 'title="([\d\.]+)"') |
+ ForEach-Object { $_.Groups[1].Value } |
+ Sort-Object {[version]$_} -Descending
+ $LatestVer = $Versions[0]
+ Write-Host "Latest version found: $LatestVer"
+
+ # 3. Build Folder and File Target URLs
+ $FolderUrl = "${ProjectUrl}${LatestVer}/"
+ $FolderPage = Invoke-WebRequest -Uri $FolderUrl -UserAgent $UserAgent -UseBasicParsing
+
+ # Find the exact .exe installer filename (e.g., nsis-3.12-setup.exe)
+ $FileMatch = [regex]::match($FolderPage.Content, "nsis-${LatestVer}-setup\.exe")
+ if (-not $FileMatch.Success) {
+ throw "Could not find the setup.exe file for version $LatestVer"
+ }
+ $FileName = $FileMatch.Value
+
+ # 4. Extract the Official MD5 Checksum
+ Write-Host "Extracting official MD5 checksum..."
+ # SourceForge stores file metadata in a JSON-like 'data-files' attribute or specific table rows
+ # This regex extracts the MD5 hash associated directly with the target filename
+ #$HashPattern = 'tr[^>]*?data-name="' + [regex]::Escape($FileName) + '"[^>]*?md5">([^<]+)'
+ #$MD5Match = [regex]::match($FolderPage.Content, $HashPattern)
+
+ $filedata_inpage = [regex]::match($FolderPage.Content, "net.sf.files = ([^;]+);")
+ if (-not $filedata_inpage.Success) {
+ Exit-Script "net.sf.files not found"
+ }
+
+ $filedata_json = $filedata_inpage.Groups[1].Value.Trim().ToLower()
+
+ $filedata = ConvertFrom-Json $filedata_json
+
+ $ExpectedMD5 = $filedata.$FileName.md5
+
+ if ($ExpectedMD5 -eq $null) {
+ Exit-Script "MD5 not found for $FileName"
+ }
+
+ Write-Host "MD5: $ExpectedMD5"
+} else {
+ Write-Host "USING PREDEFINED VERSION with hardcoded MD5: 3.12"
+ Write-Host "Use -Latest to force latest version; note that this can be prone to MITM attacks."
+
+ $ExpectedMD5 = "d5d54c2a96c1bcb25764adc9f9ff97f2"
+ $FileName = "nsis-3.12-setup.exe"
+}
+
+# 5. Download the File
+$DownloadUrl = "$DownloadHead$FileName" + "?download"
+$OutputPath = "$TmpDir\$FileName"
+
+Write-Host "Installer path: $OutputPath"
+
+if (-not $ForceDownload -and (Test-Path $OutputPath)) {
+ Write-Host "Already downloaded, use -ForceDownload to download anyway."
+} else {
+ Write-Host "Downloading from: $DownloadUrl"
+ Invoke-WebRequest -Uri $DownloadUrl -UserAgent Download -UseBasicParsing -OutFile $OutputPath
+}
+
+# 6. Verify the MD5 Checksum
+Write-Host "Verifying checksum..."
+$LocalMD5 = (Get-FileHash -Path $OutputPath -Algorithm MD5).Hash.ToLower()
+
+if ($LocalMD5 -eq $ExpectedMD5) {
+ Write-Host "Checksum OK." -ForegroundColor Green
+} else {
+ Write-Error "ERROR: Checksum mismatch!"
+ Write-Host "Expected: $ExpectedMD5" -ForegroundColor Red
+ Write-Host "Got: $LocalMD5" -ForegroundColor Red
+ Remove-Item -Path $OutputPath -Force
+ Write-Host "Corrupted file removed."
+ Exit-Script "Installation not possible"
+}
+
+if (-not $NoInstall) {
+ Write-Host "Installing $FileName"
+ Start-Process -FilePath $OutputPath -ArgumentList @("/S") -Wait
+} else {
+ Write-Host "Installation not requested."
+}
+
+Exit-Script
+
+
+
diff --git a/scripts/win-installer/install-openssl.ps1 b/scripts/win-installer/install-openssl.ps1
old mode 100644
new mode 100755
index e24cecc570..d0a95fb94b
--- a/scripts/win-installer/install-openssl.ps1
+++ b/scripts/win-installer/install-openssl.ps1
@@ -1,4 +1,5 @@
-#-----------------------------------------------------------------------------
+#!/usr/bin/env PowerShell
+#-----------------------------------------------------------------------------
#
# SRT - Secure, Reliable, Transport
# Copyright (c) 2021-2024, Thierry Lelegard
@@ -32,29 +33,13 @@
param(
[switch]$ForceDownload = $false,
[switch]$NoInstall = $false,
- [switch]$NoPause = $false
+ [switch]$NoPause = $false,
+ [switch]$Latest = $false
)
Write-Output "OpenSSL download and installation procedure"
-# A bit of history: Where to load OpenSSL binaries from?
-#
-# The packages are built by "slproweb". The binaries are downloadable from
-# their Web page at: http://slproweb.com/products/Win32OpenSSL.html
-#
-# Initially, the HTML for this page was built on server-side. This script
-# grabbed the URL content, parse the HTML and extracted the URL of the
-# binaries for the latest OpenSSL packages from the "href" of the links.
-#
-# At some point in 2024, the site changed policy. The Web page is no longer
-# built on server-side, but on client-side. The downloaded HTML contains some
-# JavaScript which dynamically builds the URL of the binaries for the latest
-# OpenSSL binaries. The previous method now finds no valid package URL from
-# the downloaded page (a full browser is needed to execute the JavaScript).
-# The JavaScript downloads a JSON file which contains all references to
-# the OpenSSL binaries. This script now uses the same method: download that
-# JSON file and parse it.
-
+# The list of OpenSSL packages is available in a JSON file. See more details in README.md.
$PackageList = "https://github.com/slproweb/opensslhashes/raw/master/win32_openssl_hashes.json"
# A function to exit this script.
@@ -81,60 +66,86 @@ $TmpDir = "$RootDir\tmp"
# Without this, Invoke-WebRequest is awfully slow.
$ProgressPreference = 'SilentlyContinue'
-# Get the JSON configuration file for OpenSSL downloads.
-$status = 0
-$message = ""
-try {
- $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $PackageList
- $status = [int] [Math]::Floor($response.StatusCode / 100)
+if ($Latest) {
+
+ # Get the JSON configuration file for OpenSSL downloads.
+ $status = 0
+ $message = ""
+ try {
+ Write-Output "Getting file index: $PackageList"
+ $response = Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $PackageList
+ $status = [int] [Math]::Floor($response.StatusCode / 100)
+ }
+ catch {
+ $message = $_.Exception.Message
+ }
+ if ($status -ne 1 -and $status -ne 2) {
+ if ($message -eq "" -and (Test-Path variable:response)) {
+ Exit-Script "Status code $($response.StatusCode), $($response.StatusDescription)"
+ }
+ else {
+ Exit-Script "#### Error accessing ${PackageList}: $message"
+ }
+ }
+ $config = ConvertFrom-Json $Response.Content
+
+ Write-Output "Getting installer file with .light and .installer =~ 'exe' and .arch =~ 'Universal'"
+
+ # Find the URL of the latest "universal" installer in the JSON config file.
+ #$Url =
+ $have = 0
+ $config.files | Get-Member | ForEach-Object {
+ $name = $_.name
+ $info = $config.files.$($_.name)
+ if (-not $info.light -and $info.installer -like "exe" -and $info.arch -like "universal") {
+ $found_info = $info
+ $have = 1
+ }
+ } # | Select-Object -Last 1
+ if (-not $have) {
+ Exit-Script "#### No universal installer found"
+ }
+
+ $Url = $found_info.url
+ $xsum = $found_info.md5
+} else {
+
+ Write-Host "USING PREDEFINED VERSION with hardcoded MD5: 4.0.1"
+ Write-Host "Use -Latest to force latest version; note that this can be prone to MITM attacks."
+
+ $Url = "https://slproweb.com/download/WinUniversalOpenSSL-4_0_1.exe"
+ $xsum = "34051060e0e9f48e63cfc1f3356191f6"
}
-catch {
- $message = $_.Exception.Message
+
+$ExeName = (Split-Path -Leaf $Url)
+$ExePath = "$TmpDir\$ExeName"
+
+if (-not $ForceDownload -and (Test-Path $ExePath)) {
+ Write-Output "$ExeName already downloaded, use -ForceDownload to download again"
}
-if ($status -ne 1 -and $status -ne 2) {
- if ($message -eq "" -and (Test-Path variable:response)) {
- Exit-Script "Status code $($response.StatusCode), $($response.StatusDescription)"
- }
- else {
- Exit-Script "#### Error accessing ${PackageList}: $message"
- }
+else {
+ Write-Output "Downloading $Url ..."
+ Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $ExePath
}
-$config = ConvertFrom-Json $Response.Content
-
-# Download and install MSI packages for 32 and 64 bit.
-foreach ($bits in @(32, 64)) {
-
- # Get the URL of the MSI installer from the JSON config.
- $Url = $config.files | Get-Member | ForEach-Object {
- $name = $_.name
- $info = $config.files.$($_.name)
- if (-not $info.light -and $info.installer -like "msi" -and $info.bits -eq $bits -and $info.arch -like "intel") {
- $info.url
- }
- } | Select-Object -Last 1
- if (-not $Url) {
- Exit-Script "#### No MSI installer found for Win${bits}"
- }
- $MsiName = (Split-Path -Leaf $Url)
- $MsiPath = "$TmpDir\$MsiName"
+if (-not (Test-Path $ExePath)) {
+ Exit-Script "$Url download failed"
+}
- if (-not $ForceDownload -and (Test-Path $MsiPath)) {
- Write-Output "$MsiName already downloaded, use -ForceDownload to download again"
- }
- else {
- Write-Output "Downloading $Url ..."
- Invoke-WebRequest -UseBasicParsing -UserAgent Download -Uri $Url -OutFile $MsiPath
- }
+Write-Output "Will check tmp\$ExeName for MD5: $xsum ..."
- if (-not (Test-Path $MsiPath)) {
- Exit-Script "$Url download failed"
- }
+$filehash = get-filehash $ExePath -algorithm md5
- if (-not $NoInstall) {
- Write-Output "Installing $MsiName"
- Start-Process msiexec.exe -ArgumentList @("/i", $MsiPath, "/qn", "/norestart") -Wait
- }
+if ($filehash.Hash -ne $xsum) {
+ Exit-Script "$ExePath MD5: $filehash - NOT MATCHING"
+}
+
+if (-not $NoInstall) {
+ Write-Output "CHECKSUM MATCHES. Installing $ExeName"
+ Start-Process -FilePath $ExePath -ArgumentList @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/ALLUSERS") -Wait
+} else {
+ Write-Output "Installation not requested."
}
+
Exit-Script
diff --git a/scripts/win-installer/libsrt.nsi b/scripts/win-installer/libsrt.nsi
index 022ceb617f..66ff147450 100644
--- a/scripts/win-installer/libsrt.nsi
+++ b/scripts/win-installer/libsrt.nsi
@@ -28,9 +28,10 @@ Caption "SRT Libraries Installer"
!include "x64.nsh"
!verbose pop
-!define ProductName "libsrt"
-!define Build32Dir "${BuildRoot}\build.Win32"
-!define Build64Dir "${BuildRoot}\build.x64"
+!define ProductName "libsrt"
+!define BuildWin32Dir "${BuildRoot}\build.Win32"
+!define BuildWin64Dir "${BuildRoot}\build.x64"
+!define BuildArm64Dir "${BuildRoot}\build.ARM64"
; Installer file information.
VIProductVersion ${VersionInfo}
@@ -93,6 +94,7 @@ functionEnd
function un.onInit
; In 64-bit installers, don't use registry redirection.
${If} ${RunningX64}
+ ${OrIf} ${IsNativeARM64}
SetRegView 64
${EndIf}
functionEnd
@@ -128,7 +130,7 @@ Section "Install"
File "${RepoDir}\srtcore\platform_sys.h"
File "${RepoDir}\srtcore\srt.h"
File "${RepoDir}\srtcore\udt.h"
- File "${Build64Dir}\version.h"
+ File "${BuildWin64Dir}\version.h"
CreateDirectory "$INSTDIR\include\win"
SetOutPath "$INSTDIR\include\win"
@@ -139,27 +141,39 @@ Section "Install"
CreateDirectory "$INSTDIR\lib\Release-x64"
SetOutPath "$INSTDIR\lib\Release-x64"
- File /oname=srt.lib "${Build64Dir}\Release\srt_static.lib"
- File /oname=libcrypto.lib "${libcrypto64MD}"
- File /oname=libssl.lib "${libssl64MD}"
+ File /oname=srt.lib "${BuildWin64Dir}\Release\srt_static.lib"
+ File /oname=libcrypto.lib "${libcryptoWin64Release}"
+ File /oname=libssl.lib "${libsslWin64Release}"
CreateDirectory "$INSTDIR\lib\Debug-x64"
SetOutPath "$INSTDIR\lib\Debug-x64"
- File /oname=srt.lib "${Build64Dir}\Debug\srt_static.lib"
- File /oname=libcrypto.lib "${libcrypto64MDd}"
- File /oname=libssl.lib "${libssl64MDd}"
+ File /oname=srt.lib "${BuildWin64Dir}\Debug\srt_static.lib"
+ File /oname=libcrypto.lib "${libcryptoWin64Debug}"
+ File /oname=libssl.lib "${libsslWin64Debug}"
CreateDirectory "$INSTDIR\lib\Release-Win32"
SetOutPath "$INSTDIR\lib\Release-Win32"
- File /oname=srt.lib "${Build32Dir}\Release\srt_static.lib"
- File /oname=libcrypto.lib "${libcrypto32MD}"
- File /oname=libssl.lib "${libssl32MD}"
+ File /oname=srt.lib "${BuildWin32Dir}\Release\srt_static.lib"
+ File /oname=libcrypto.lib "${libcryptoWin32Release}"
+ File /oname=libssl.lib "${libsslWin32Release}"
CreateDirectory "$INSTDIR\lib\Debug-Win32"
SetOutPath "$INSTDIR\lib\Debug-Win32"
- File /oname=srt.lib "${Build32Dir}\Debug\srt_static.lib"
- File /oname=libcrypto.lib "${libcrypto32MDd}"
- File /oname=libssl.lib "${libssl32MDd}"
+ File /oname=srt.lib "${BuildWin32Dir}\Debug\srt_static.lib"
+ File /oname=libcrypto.lib "${libcryptoWin32Debug}"
+ File /oname=libssl.lib "${libsslWin32Debug}"
+
+ CreateDirectory "$INSTDIR\lib\Release-Arm64"
+ SetOutPath "$INSTDIR\lib\Release-Arm64"
+ File /oname=srt.lib "${BuildArm64Dir}\Release\srt_static.lib"
+ File /oname=libcrypto.lib "${libcryptoArm64Release}"
+ File /oname=libssl.lib "${libsslArm64Release}"
+
+ CreateDirectory "$INSTDIR\lib\Debug-Arm64"
+ SetOutPath "$INSTDIR\lib\Debug-Arm64"
+ File /oname=srt.lib "${BuildArm64Dir}\Debug\srt_static.lib"
+ File /oname=libcrypto.lib "${libcryptoArm64Debug}"
+ File /oname=libssl.lib "${libsslArm64Debug}"
; Add an environment variable to installation root.
WriteRegStr HKLM ${EnvironmentKey} "LIBSRT" "$INSTDIR"
diff --git a/sonar-project.properties b/sonar-project.properties
deleted file mode 100644
index d11323df85..0000000000
--- a/sonar-project.properties
+++ /dev/null
@@ -1,36 +0,0 @@
-# must be unique in a given SonarQube instance
-sonar.projectKey=srt
-sonar.organization=haivision
-
-# --- optional properties ---
-
-# This is the name and version displayed in the SonarCloud UI.
-#sonar.projectName=srt
-#sonar.projectVersion=1.0
-
-# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
-#sonar.sources=.
-
-# Encoding of the source code. Default is default system encoding
-#sonar.sourceEncoding=UTF-8
-
-# =====================================================
-# Meta-data for the project
-# =====================================================
-
-sonar.links.homepage=https://github.com/Haivision/srt
-sonar.links.scm=https://github.com/Haivision/srt
-sonar.links.issue=https://github.com/Haivision/srt/issues
-
-
-# =====================================================
-# Properties that will be shared amongst all modules
-# =====================================================
-
-# SQ standard properties
-sonar.sources=srtcore/,apps/,common/,examples/,haicrypt/,scripts/,testing/
-sonar.tests=test/
-
-# Properties specific to the C/C++ analyzer:
-sonar.cfamily.build-wrapper-output=_build/sonar-output
-sonar.cfamily.gcov.reportsPath=.
diff --git a/srtcore/api.cpp b/srtcore/api.cpp
index afc8502dbd..d6c8a48f19 100644
--- a/srtcore/api.cpp
+++ b/srtcore/api.cpp
@@ -100,6 +100,12 @@ srt::CUDTSocket::~CUDTSocket()
releaseMutex(m_ControlLock);
}
+void srt::CUDTSocket::resetAtFork()
+{
+ m_UDT.resetAtFork();
+ resetCond(m_AcceptCond);
+}
+
SRT_SOCKSTATUS srt::CUDTSocket::getStatus()
{
// TTL in CRendezvousQueue::updateConnStatus() will set m_bConnecting to false.
@@ -190,11 +196,23 @@ srt::CUDTUnited::CUDTUnited()
// XXX An unlikely exception thrown from the below calls
// might destroy the application before `main`. This shouldn't
// be a problem in general.
+ setupMutex(m_GCStartLock, "GCStart");
setupMutex(m_GCStopLock, "GCStop");
setupCond(m_GCStopCond, "GCStop");
setupMutex(m_GlobControlLock, "GlobControl");
setupMutex(m_IDLock, "ID");
setupMutex(m_InitLock, "Init");
+ // Global initialization code
+#ifdef _WIN32
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ wVersionRequested = MAKEWORD(2, 2);
+
+ if (0 != WSAStartup(wVersionRequested, &wsaData))
+ throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError());
+#endif
+ CCryptoControl::globalInit();
+ HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR);
}
srt::CUDTUnited::~CUDTUnited()
@@ -202,11 +220,12 @@ srt::CUDTUnited::~CUDTUnited()
// Call it if it wasn't called already.
// This will happen at the end of main() of the application,
// when the user didn't call srt_cleanup().
- if (m_bGCStatus)
- {
- cleanup();
- }
+ enterCS(m_InitLock);
+ stopGarbageCollector();
+ leaveCS(m_InitLock);
+ // DO NOT call closeAllSockets() here; instead rely on that
+ // all sockets have been closed when exiting the GC.
releaseMutex(m_GlobControlLock);
releaseMutex(m_IDLock);
releaseMutex(m_InitLock);
@@ -220,8 +239,11 @@ srt::CUDTUnited::~CUDTUnited()
releaseCond(m_GCStopCond);
#endif
releaseMutex(m_GCStopLock);
-
+ releaseMutex(m_GCStartLock);
delete m_pCache;
+#ifdef _WIN32
+ WSACleanup();
+#endif
}
string srt::CUDTUnited::CONID(SRTSOCKET sock)
@@ -234,38 +256,183 @@ string srt::CUDTUnited::CONID(SRTSOCKET sock)
return os.str();
}
-int srt::CUDTUnited::startup()
+bool srt::CUDTUnited::startGarbageCollector()
{
- ScopedLock gcinit(m_InitLock);
+
+ ScopedLock guard(m_GCStartLock);
+ if (!m_bGCStatus)
+ {
+ m_bClosing = false;
+ m_bGCStatus = StartThread(m_GCThread, garbageCollect, this, "SRT:GC");
+ }
+ return m_bGCStatus;
+}
+
+void srt::CUDTUnited::stopGarbageCollector()
+{
+
+ ScopedLock guard(m_GCStartLock);
if (m_bGCStatus)
- return 1;
+ {
+ m_bGCStatus = false;
+ {
+ CUniqueSync gclock (m_GCStopLock, m_GCStopCond);
+ m_bClosing = true;
+ gclock.notify_all();
+ }
+ m_GCThread.join();
+ }
+}
- if (m_iInstanceCount++ > 0)
- return 1;
+void srt::CUDTUnited::cleanupAllSockets()
+{
+ for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i)
+ {
+ CUDTSocket* s = i->second;
- // Global initialization code
-#ifdef _WIN32
- WORD wVersionRequested;
- WSADATA wsaData;
- wVersionRequested = MAKEWORD(2, 2);
+#if ENABLE_BONDING
+ if (s->m_GroupOf)
+ {
+ s->removeFromGroup(false);
+ }
+#endif
- if (0 != WSAStartup(wVersionRequested, &wsaData))
- throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError());
+ // remove from listener's queue
+ sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket);
+ if (ls == m_Sockets.end())
+ {
+ ls = m_ClosedSockets.find(s->m_ListenSocket);
+ }
+ if (ls != m_ClosedSockets.end())
+ {
+ ls->second->m_QueuedSockets.erase(s->m_SocketID);
+ }
+ s->core().closeAtFork();
+ s->resetAtFork();
+ delete(s);
+ }
+ m_Sockets.clear();
+
+#if ENABLE_BONDING
+ for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j)
+ {
+ delete j->second;
+ }
+ m_Groups.clear();
#endif
+ for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i)
+ {
+ CMultiplexer &multiplexer = i->second;
+ multiplexer.resetAtFork();
+ }
+ m_mMultiplexer.clear();
+}
- CCryptoControl::globalInit();
- PacketFilter::globalInit();
+void srt::CUDTUnited::closeAllSockets()
+{
+ // remove all sockets and multiplexers
+ HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquiring control lock...");
- m_bClosing = false;
+ {
+ // Pre-closing: run over all open sockets and close them.
+ SharedLock glock(m_GlobControlLock);
- if (!StartThread(m_GCThread, garbageCollect, this, "SRT:GC"))
- return -1;
+ for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i)
+ {
+ CUDTSocket* s = i->second;
+ s->breakSocket_LOCKED();
- m_bGCStatus = true;
+#if ENABLE_BONDING
+ if (s->m_GroupOf)
+ {
+ HLOGC(smlog.Debug,
+ log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id()
+ << " (IPE?) - REMOVING FROM GROUP");
+ s->removeFromGroup(false);
+ }
+#endif
+ }
+ }
+
+ {
+ ExclusiveLock glock(m_GlobControlLock);
+
+ for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i)
+ {
+ CUDTSocket* s = i->second;
+
+ m_ClosedSockets[i->first] = s;
+
+ // remove from listener's queue
+ sockets_t::iterator ls = m_Sockets.find(s->m_ListenSocket);
+ if (ls == m_Sockets.end())
+ {
+ ls = m_ClosedSockets.find(s->m_ListenSocket);
+ if (ls == m_ClosedSockets.end())
+ continue;
+ }
+
+ enterCS(ls->second->m_AcceptLock);
+ ls->second->m_QueuedSockets.erase(s->m_SocketID);
+ leaveCS(ls->second->m_AcceptLock);
+ }
+ m_Sockets.clear();
+
+ for (sockets_t::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++j)
+ {
+ j->second->m_tsClosureTimeStamp = steady_clock::time_point();
+ }
+
+#if ENABLE_BONDING
+ for (groups_t::iterator j = m_Groups.begin(); j != m_Groups.end(); ++j)
+ {
+ SRTSOCKET id = j->second->m_GroupID;
+ m_ClosedGroups[id] = j->second;
+ }
+ m_Groups.clear();
+#endif
+ }
+
+ HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets.");
+ while (true)
+ {
+ checkBrokenSockets();
+
+ enterCS(m_GlobControlLock);
+ bool empty = m_ClosedSockets.empty();
+ leaveCS(m_GlobControlLock);
+
+ if (empty)
+ break;
+
+ HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep");
+ srt::sync::this_thread::sleep_for(milliseconds_from(1));
+ }
- HLOGC(inlog.Debug, log << "SRT Clock Type: " << SRT_SYNC_CLOCK_STR);
+}
+
+
+int srt::CUDTUnited::startup()
+{
+ ScopedLock gcinit(m_InitLock);
+ m_iInstanceCount++;
+ if (m_bGCStatus)
+ return (m_iInstanceCount == 1) ? 1 : 0;
+ else
+ return startGarbageCollector() ? 0 : -1;
+}
+
+int srt::CUDTUnited::cleanupAtFork()
+{
+ cleanupAllSockets();
+ resetThread(&m_GCThread);
+ resetCond(m_GCStopCond);
+ m_GCStopLock.unlock();
+ setupCond(m_GCStopCond, "GCStop");
+ m_iInstanceCount=0;
+ m_bGCStatus = false;
return 0;
}
@@ -284,28 +451,8 @@ int srt::CUDTUnited::cleanup()
if (--m_iInstanceCount > 0)
return 0;
- if (!m_bGCStatus)
- return 0;
-
- {
- UniqueLock gclock(m_GCStopLock);
- m_bClosing = true;
- }
- // NOTE: we can do relaxed signaling here because
- // waiting on m_GCStopCond has a 1-second timeout,
- // after which the m_bClosing flag is cheched, which
- // is set here above. Worst case secenario, this
- // pthread_join() call will block for 1 second.
- CSync::notify_one_relaxed(m_GCStopCond);
- m_GCThread.join();
-
- m_bGCStatus = false;
-
- // Global destruction code
-#ifdef _WIN32
- WSACleanup();
-#endif
-
+ stopGarbageCollector();
+ closeAllSockets();
return 0;
}
@@ -448,7 +595,7 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps)
HLOGC(smlog.Debug, log << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID);
// protect the m_Sockets structure.
- ScopedLock cs(m_GlobControlLock);
+ ExclusiveLock cs(m_GlobControlLock);
m_Sockets[ns->m_SocketID] = ns;
}
catch (...)
@@ -459,12 +606,19 @@ SRTSOCKET srt::CUDTUnited::newSocket(CUDTSocket** pps)
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
}
+ startGarbageCollector();
if (pps)
*pps = ns;
return ns->m_SocketID;
}
+// XXX NOTE: TSan reports here false positive against the call
+// to CRcvQueue::removeListener. This here will apply shared
+// lock on m_GlobControlLock in the call of locateSocket, while
+// having applied a shared lock on CRcvQueue::m_pListener in
+// CRcvQueue::worker_ProcessConnectionRequest. As this thread
+// locks both mutexes as shared, it doesn't form a deadlock.
int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
const sockaddr_any& peer,
const CPacket& hspkt,
@@ -529,7 +683,11 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
}
// exceeding backlog, refuse the connection request
- if (ls->m_QueuedSockets.size() >= ls->m_uiBackLog)
+
+ enterCS(ls->m_AcceptLock);
+ size_t backlog = ls->m_QueuedSockets.size();
+ leaveCS(ls->m_AcceptLock);
+ if (backlog >= ls->m_uiBackLog)
{
w_error = SRT_REJ_BACKLOG;
LOGC(cnlog.Note, log << "newConnection: listen backlog=" << ls->m_uiBackLog << " EXCEEDED");
@@ -600,7 +758,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
HLOGC(cnlog.Debug, log <<
"newConnection: incoming " << peer.str() << ", mapping socket " << ns->m_SocketID);
{
- ScopedLock cg(m_GlobControlLock);
+ ExclusiveLock cg(m_GlobControlLock);
m_Sockets[ns->m_SocketID] = ns;
}
@@ -649,7 +807,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
{
// protect the m_PeerRec structure (and group existence)
- ScopedLock glock(m_GlobControlLock);
+ ExclusiveLock glock(m_GlobControlLock);
try
{
HLOGC(cnlog.Debug, log << "newConnection: mapping peer " << ns->m_PeerID
@@ -715,10 +873,10 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
g->m_bConnected = true;
}
- // XXX PROLBEM!!! These events are subscribed here so that this is done once, lazily,
+ // XXX PROBLEM!!! These events are subscribed here so that this is done once, lazily,
// but groupwise connections could be accepted from multiple listeners for the same group!
// m_listener MUST BE A CONTAINER, NOT POINTER!!!
- // ALSO: Maybe checking "the same listener" is not necessary as subscruption may be done
+ // ALSO: Maybe checking "the same listener" is not necessary as subscription may be done
// multiple times anyway?
if (!g->m_listener)
{
@@ -823,7 +981,7 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen,
// connect() in UDT code) may fail, in which case this socket should not be
// further processed and should be removed.
{
- ScopedLock cg(m_GlobControlLock);
+ ExclusiveLock cg(m_GlobControlLock);
#if ENABLE_BONDING
if (ns->m_GroupOf)
@@ -898,7 +1056,7 @@ int srt::CUDTUnited::installConnectHook(const SRTSOCKET u, srt_connect_callback_
SRT_SOCKSTATUS srt::CUDTUnited::getStatus(const SRTSOCKET u)
{
// protects the m_Sockets structure
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
sockets_t::const_iterator i = m_Sockets.find(u);
@@ -989,27 +1147,34 @@ int srt::CUDTUnited::listen(const SRTSOCKET u, int backlog)
// it could have changed the state. It could be also set listen in another
// thread, so check it out.
- // do nothing if the socket is already listening
- if (s->m_Status == SRTS_LISTENING)
- return 0;
-
- // a socket can listen only if is in OPENED status
- if (s->m_Status != SRTS_OPENED)
- throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0);
-
- // [[using assert(s->m_Status == OPENED)]];
-
- // listen is not supported in rendezvous connection setup
if (s->core().m_config.bRendezvous)
throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0);
- s->m_uiBackLog = backlog;
-
- // [[using assert(s->m_Status == OPENED)]]; // (still, unchanged)
+ switch(s->m_Status)
+ {
+ case SRTS_INIT:
+ throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0);
+ break;
+ case SRTS_OPENED:
+ s->m_uiBackLog = backlog;
+ s->core().setListenState(); // propagates CUDTException,
+ s->m_Status = SRTS_LISTENING;
+ break;
+ case SRTS_LISTENING:
+ s->m_uiBackLog = backlog;
+ break;
+ case SRTS_CONNECTING:
+ case SRTS_CONNECTED:
+ throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
+ break;
+ case SRTS_BROKEN:
+ case SRTS_CLOSING:
+ case SRTS_CLOSED:
+ case SRTS_NONEXIST:
+ throw CUDTException(MJ_SETUP, MN_CLOSED, 0);
+ break;
+ }
- s->core().setListenState(); // propagates CUDTException,
- // if thrown, remains in OPENED state if so.
- s->m_Status = SRTS_LISTENING;
return 0;
}
@@ -1172,7 +1337,7 @@ SRTSOCKET srt::CUDTUnited::accept(const SRTSOCKET listen, sockaddr* pw_addr, int
{
// Put a lock to protect the group against accidental deletion
// in the meantime.
- ScopedLock glock(m_GlobControlLock);
+ SharedLock glock(m_GlobControlLock);
// Check again; it's unlikely to happen, but
// it's a theoretically possible scenario
if (s->m_GroupOf)
@@ -1452,13 +1617,13 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i
}
{
- ScopedLock cs(m_GlobControlLock);
+ ExclusiveLock cs(m_GlobControlLock);
if (m_Sockets.count(sid) == 0)
{
HLOGC(aclog.Debug, log << "srt_connect_group: socket @" << sid << " deleted in process");
// Someone deleted the socket in the meantime?
// Unlikely, but possible in theory.
- // Don't delete anyhting - it's alreay done.
+ // Don't delete anything - it's already done.
continue;
}
@@ -1504,7 +1669,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i
}
}
- // XXX This should be reenabled later, this should
+ // XXX This should be re-enabled later, this should
// be probably still in use to exchange information about
// packets asymmetrically lost. But for no other purpose.
/*
@@ -1558,7 +1723,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i
targets[tii].errorcode = e.getErrorCode();
targets[tii].id = CUDT::INVALID_SOCK;
- ScopedLock cl(m_GlobControlLock);
+ ExclusiveLock cl(m_GlobControlLock);
ns->removeFromGroup(false);
m_Sockets.erase(ns->m_SocketID);
// Intercept to delete the socket on failure.
@@ -1570,7 +1735,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i
LOGC(aclog.Fatal, log << "groupConnect: IPE: UNKNOWN EXCEPTION from connectIn");
targets[tii].errorcode = SRT_ESYSOBJ;
targets[tii].id = CUDT::INVALID_SOCK;
- ScopedLock cl(m_GlobControlLock);
+ ExclusiveLock cl(m_GlobControlLock);
ns->removeFromGroup(false);
m_Sockets.erase(ns->m_SocketID);
// Intercept to delete the socket on failure.
@@ -1634,7 +1799,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i
// pending, about to be broken, and srt_connect() is called again?
// SHOULD BLOCK the latter, even though is OPEN.
// Or, OPEN should be removed from here and srt_connect(_group)
- // should block always if the group doesn't have neither 1 conencted link
+ // should block always if the group doesn't have neither 1 connected link
g.m_bOpened = true;
g.m_stats.tsLastSampleTime = steady_clock::now();
@@ -1830,7 +1995,7 @@ int srt::CUDTUnited::groupConnect(CUDTGroup* pg, SRT_SOCKGROUPCONFIG* targets, i
// function will still be polling sockets to determine the last man
// standing. Each one could, however, break by a different reason,
// for example, one by timeout, another by wrong passphrase. Check
- // the `errorcode` field to determine the reaon for particular link.
+ // the `errorcode` field to determine the reason for particular link.
if (retval == -1)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
@@ -1867,7 +2032,12 @@ int srt::CUDTUnited::connectIn(CUDTSocket* s, const sockaddr_any& target_addr, i
}
else
{
- if (s->m_Status != SRTS_OPENED)
+ // Only SRTS_OPENED status is otherwise expected.
+ // Although depending on the state, different type of errors
+ if (int(s->m_Status) >= SRTS_BROKEN) // BROKEN, CLOSING, CLOSED, NONEXIST
+ throw CUDTException(MJ_SETUP, MN_CLOSED, 0);
+
+ if (s->m_Status != SRTS_OPENED) // LISTENING, CONNECTING, CONNECTED
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// status = SRTS_OPENED, so family should be known already.
@@ -1944,7 +2114,7 @@ void srt::CUDTUnited::deleteGroup(CUDTGroup* g)
{
using srt_logging::gmlog;
- srt::sync::ScopedLock cg(m_GlobControlLock);
+ srt::sync::ExclusiveLock cg(m_GlobControlLock);
return deleteGroup_LOCKED(g);
}
@@ -1989,6 +2159,9 @@ void srt::CUDTUnited::deleteGroup_LOCKED(CUDTGroup* g)
int srt::CUDTUnited::close(CUDTSocket* s)
{
+ // Set the closing flag BEFORE you attempt to acquire
+ s->setBreaking();
+
HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSE. Acquiring control lock");
ScopedLock socket_cg(s->m_ControlLock);
HLOGC(smlog.Debug, log << s->core().CONID() << "CLOSING (removing from listening, closing CUDT)");
@@ -2035,7 +2208,7 @@ int srt::CUDTUnited::close(CUDTSocket* s)
HLOGC(smlog.Debug,
log << "@" << u << "U::close done. GLOBAL CLOSE: " << s->core().CONID()
<< "Acquiring GLOBAL control lock");
- ScopedLock manager_cg(m_GlobControlLock);
+ ExclusiveLock manager_cg(m_GlobControlLock);
// since "s" is located before m_GlobControlLock, locate it again in case
// it became invalid
// XXX This is very weird; if we state that the CUDTSocket object
@@ -2106,7 +2279,7 @@ int srt::CUDTUnited::close(CUDTSocket* s)
// Done the other way, but still done. You can stop waiting.
bool isgone = false;
{
- ScopedLock manager_cg(m_GlobControlLock);
+ SharedLock manager_cg(m_GlobControlLock);
isgone = m_ClosedSockets.count(u) == 0;
}
if (!isgone)
@@ -2508,7 +2681,7 @@ int srt::CUDTUnited::epoll_release(const int eid)
srt::CUDTSocket* srt::CUDTUnited::locateSocket(const SRTSOCKET u, ErrorHandling erh)
{
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
CUDTSocket* s = locateSocket_LOCKED(u);
if (!s)
{
@@ -2536,7 +2709,7 @@ srt::CUDTSocket* srt::CUDTUnited::locateSocket_LOCKED(SRTSOCKET u)
#if ENABLE_BONDING
srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling erh)
{
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
const groups_t::iterator i = m_Groups.find(u);
if (i == m_Groups.end())
@@ -2553,7 +2726,7 @@ srt::CUDTGroup* srt::CUDTUnited::locateAcquireGroup(SRTSOCKET u, ErrorHandling e
srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s)
{
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
CUDTGroup* g = s->m_GroupOf;
if (!g)
return NULL;
@@ -2567,7 +2740,7 @@ srt::CUDTGroup* srt::CUDTUnited::acquireSocketsGroup(CUDTSocket* s)
srt::CUDTSocket* srt::CUDTUnited::locateAcquireSocket(SRTSOCKET u, ErrorHandling erh)
{
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
CUDTSocket* s = locateSocket_LOCKED(u);
if (!s)
@@ -2590,7 +2763,7 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s)
// directly from m_Sockets, or even better, has been acquired
// by some other functionality already, which is only about to
// be released earlier than you need.
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
s->apiAcquire();
// Keep the lock so that no one changes anything in the meantime.
// If the socket m_Status == SRTS_CLOSED (set by setClosed()), then
@@ -2606,7 +2779,7 @@ bool srt::CUDTUnited::acquireSocket(CUDTSocket* s)
srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRTSOCKET id, int32_t isn)
{
- ScopedLock cg(m_GlobControlLock);
+ SharedLock cg(m_GlobControlLock);
map >::iterator i = m_PeerRec.find(CUDTSocket::getPeerSpec(id, isn));
if (i == m_PeerRec.end())
@@ -2630,7 +2803,7 @@ srt::CUDTSocket* srt::CUDTUnited::locatePeer(const sockaddr_any& peer, const SRT
void srt::CUDTUnited::checkBrokenSockets()
{
- ScopedLock cg(m_GlobControlLock);
+ ExclusiveLock cg(m_GlobControlLock);
#if ENABLE_BONDING
vector delgids;
@@ -2662,36 +2835,66 @@ void srt::CUDTUnited::checkBrokenSockets()
vector tbc;
vector tbr;
+ bool forced_closing = m_bClosing;
+
for (sockets_t::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++i)
{
CUDTSocket* s = i->second;
if (!s->core().m_bBroken)
- continue;
+ {
+ if (!forced_closing)
+ {
+ continue;
+ }
+ else
+ {
+ // Set forcefully, we are in cleanup and close everything
+ LOGC(smlog.Warn, log << "CLEANUP: Forcefully breaking socket @" << s->m_SocketID);
+ s->core().m_bBroken = true;
+ }
+ }
if (s->m_Status == SRTS_LISTENING)
{
- const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp.load();
- // A listening socket should wait an extra 3 seconds
- // in case a client is connecting.
- if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS))
- continue;
+ if (!forced_closing)
+ {
+ const steady_clock::duration elapsed = steady_clock::now() - s->m_tsClosureTimeStamp.load();
+ // A listening socket should wait an extra 3 seconds
+ // in case a client is connecting.
+ if (elapsed < milliseconds_from(CUDT::COMM_CLOSE_BROKEN_LISTENER_TIMEOUT_MS))
+ continue;
+ }
}
else
{
CUDT& u = s->core();
- enterCS(u.m_RcvBufferLock);
- bool has_avail_packets = u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets();
- leaveCS(u.m_RcvBufferLock);
+ // For decent closing, just keep it as long as it still
+ // has data in the buffer.
+ if (!forced_closing)
+ {
+ enterCS(u.m_RcvBufferLock);
+ bool has_avail_packets = u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets();
+ leaveCS(u.m_RcvBufferLock);
- if (has_avail_packets)
+ if (has_avail_packets)
+ {
+ const int bc = u.m_iBrokenCounter.load();
+ if (bc > 0)
+ {
+ // if there is still data in the receiver buffer, wait longer
+ s->core().m_iBrokenCounter.store(bc - 1);
+ continue;
+ }
+ }
+ }
+ else
{
- const int bc = u.m_iBrokenCounter.load();
- if (bc > 0)
+ // Forced closing: any data still in the buffer - delete them.
+ ScopedLock cgb (u.m_RcvBufferLock);
+ if (u.m_pRcvBuffer && u.m_pRcvBuffer->hasAvailablePackets())
{
- // if there is still data in the receiver buffer, wait longer
- s->core().m_iBrokenCounter.store(bc - 1);
- continue;
+ u.m_pRcvBuffer->dropAll();
}
}
}
@@ -2718,7 +2921,8 @@ void srt::CUDTUnited::checkBrokenSockets()
{
ls = m_ClosedSockets.find(s->m_ListenSocket);
if (ls == m_ClosedSockets.end())
- continue;
+ continue; // END LOOP AS NOT FOUND
+ // OTHERWISE PROCEED with erasing as queued
}
enterCS(ls->second->m_AcceptLock);
@@ -2739,6 +2943,9 @@ void srt::CUDTUnited::checkBrokenSockets()
// other conditions applying on the socket that prevent it from being deleted.
if (ps->isStillBusy())
{
+ // NOTE: you can't use forced_closing to prevent it because isStillBusy
+ // means that some facility has acquired it and is going to use it for
+ // operations; forced deletion may lead to UB/crash.
HLOGC(smlog.Debug, log << "checkBrokenSockets: @" << ps->m_SocketID << " is still busy, SKIPPING THIS CYCLE.");
continue;
}
@@ -2818,7 +3025,7 @@ void srt::CUDTUnited::removeSocket(const SRTSOCKET u)
return;
}
- LOGC(smlog.Note, log << "@" << s->m_SocketID << " busy=" << s->isStillBusy());
+ HLOGC(smlog.Note, log << "@" << s->m_SocketID << " busy=" << s->isStillBusy());
#if ENABLE_BONDING
if (s->m_GroupOf)
@@ -2984,8 +3191,9 @@ bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, cons
void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/)
{
- ScopedLock cg(m_GlobControlLock);
-
+ const int port = reqaddr.hport();
+ ExclusiveLock cg(m_GlobControlLock);
+
// If udpsock is provided, then this socket will be simply
// taken for binding as a good deal. It would be nice to make
// a sanity check to see if this UDP socket isn't already installed
@@ -2995,7 +3203,6 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
{
// If not, we need to see if there exist already a multiplexer bound
// to the same endpoint.
- const int port = reqaddr.hport();
const CSrtConfig& cfgSocket = s->core().m_config;
// This loop is going to check the attempted binding of
@@ -3163,7 +3370,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
{
LOGC(smlog.Error,
log << "bind: Wildcard address: " << reqaddr.str()
- << " conflicts with existting IP binding: " << mux_addr.str());
+ << " conflicts with existing IP binding: " << mux_addr.str());
throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0);
}
// If this is bound to a certain address, AND:
@@ -3213,6 +3420,8 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
}
}
+
+
// a new multiplexer is needed
CMultiplexer m;
configureMuxer((m), s, reqaddr.family());
@@ -3263,7 +3472,11 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
m.m_pSndQueue = new CSndQueue;
m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer);
m.m_pRcvQueue = new CRcvQueue;
- m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer);
+
+ // NOTE: Receiver Queue packet size must be of the maximum possible because you never
+ // know what kind of packet will come over the network, while this must accept any kind
+ // of packet.
+ m.m_pRcvQueue->init(128, s->core().controlPayloadSize(reqaddr.family()), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer);
// If this muxer is designated as an SRTLA demux listener (SRTO_SRTLA), create
// the demux and share it with both queues (ingress classification on the
@@ -3279,7 +3492,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
// Rewrite the port here, as it might be only known upon return
// from CChannel::open.
m.m_iPort = installMuxer((s), m);
- m_mMultiplexer[m.m_iID] = m;
+ swap(m_mMultiplexer[m.m_iID],m);
}
catch (const CUDTException&)
{
@@ -3292,7 +3505,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
}
- HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << m.m_iPort);
+ HLOGC(smlog.Debug, log << "bind: creating new multiplexer for port " << port);
}
// This function is going to find a multiplexer for the port contained
@@ -3302,7 +3515,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
// multiplexer wasn't found by id, the search by port number continues.
bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls)
{
- ScopedLock cg(m_GlobControlLock);
+ ExclusiveLock cg(m_GlobControlLock);
const int port = ls->m_SelfAddr.hport();
HLOGC(smlog.Debug,
@@ -3395,74 +3608,33 @@ void* srt::CUDTUnited::garbageCollect(void* p)
UniqueLock gclock(self->m_GCStopLock);
- while (!self->m_bClosing)
+ for (;;)
{
INCREMENT_THREAD_ITERATIONS();
self->checkBrokenSockets();
- HLOGC(inlog.Debug, log << "GC: sleep 1 s");
- self->m_GCStopCond.wait_for(gclock, seconds_from(1));
- }
-
- // remove all sockets and multiplexers
- HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all pending sockets. Acquring control lock...");
-
- {
- ScopedLock glock(self->m_GlobControlLock);
-
- for (sockets_t::iterator i = self->m_Sockets.begin(); i != self->m_Sockets.end(); ++i)
+ if (self->m_bClosing)
{
- CUDTSocket* s = i->second;
- s->breakSocket_LOCKED();
+ // If GC is requested to close, it means the global cleanup
+ // was requested. But before exiting make sure all sockets
+ // and multiplexers are closed.
-#if ENABLE_BONDING
- if (s->m_GroupOf)
{
- HLOGC(smlog.Debug,
- log << "@" << s->m_SocketID << " IS MEMBER OF $" << s->m_GroupOf->id()
- << " (IPE?) - REMOVING FROM GROUP");
- s->removeFromGroup(false);
- }
-#endif
- self->m_ClosedSockets[i->first] = s;
+ SharedLock glock(self->m_GlobControlLock);
+ if (self->m_Sockets.empty() && self->m_ClosedSockets.empty())
+ break;
- // remove from listener's queue
- sockets_t::iterator ls = self->m_Sockets.find(s->m_ListenSocket);
- if (ls == self->m_Sockets.end())
- {
- ls = self->m_ClosedSockets.find(s->m_ListenSocket);
- if (ls == self->m_ClosedSockets.end())
- continue;
+ HLOGC(smlog.Debug, log << "GC: REQUESTED CLOSE, DELAYING EXIT - still "
+ << self->m_Sockets.size() << " running and "
+ << self->m_ClosedSockets.size() << " closed sockets");
}
-
- enterCS(ls->second->m_AcceptLock);
- ls->second->m_QueuedSockets.erase(s->m_SocketID);
- leaveCS(ls->second->m_AcceptLock);
- }
- self->m_Sockets.clear();
-
- for (sockets_t::iterator j = self->m_ClosedSockets.begin(); j != self->m_ClosedSockets.end(); ++j)
- {
- j->second->m_tsClosureTimeStamp = steady_clock::time_point();
+ self->m_GCStopCond.wait_for(gclock, milliseconds_from(200));
+ continue;
}
- }
-
- HLOGC(inlog.Debug, log << "GC: GLOBAL EXIT - releasing all CLOSED sockets.");
- while (true)
- {
- self->checkBrokenSockets();
- enterCS(self->m_GlobControlLock);
- bool empty = self->m_ClosedSockets.empty();
- leaveCS(self->m_GlobControlLock);
-
- if (empty)
- break;
-
- HLOGC(inlog.Debug, log << "GC: checkBrokenSockets didn't wipe all sockets, repeating after 1s sleep");
- srt::sync::this_thread::sleep_for(milliseconds_from(1));
+ HLOGC(inlog.Debug, log << "GC: sleep 1 s");
+ self->m_GCStopCond.wait_for(gclock, seconds_from(1));
}
-
THREAD_EXIT();
return NULL;
}
@@ -3471,6 +3643,14 @@ void* srt::CUDTUnited::garbageCollect(void* p)
int srt::CUDT::startup()
{
+#if HAVE_PTHREAD_ATFORK
+ static bool registered = false;
+ if (!registered)
+ {
+ pthread_atfork(NULL, NULL, (void (*)()) srt::CUDT::cleanupAtFork);
+ registered = true;
+ }
+#endif
return uglobal().startup();
}
@@ -3479,10 +3659,17 @@ int srt::CUDT::cleanup()
return uglobal().cleanup();
}
-SRTSOCKET srt::CUDT::socket()
+int srt::CUDT::cleanupAtFork()
{
- uglobal().startup();
+ CUDTUnited &context = uglobal();
+ context.cleanupAtFork();
+ new (&context) CUDTUnited();
+
+ return context.startup();
+}
+SRTSOCKET srt::CUDT::socket()
+{
try
{
return uglobal().newSocket();
@@ -3530,15 +3717,12 @@ srt::CUDTGroup& srt::CUDT::newGroup(const int type)
SRTSOCKET srt::CUDT::createGroup(SRT_GROUP_TYPE gt)
{
- // Doing the same lazy-startup as with srt_create_socket()
- uglobal().startup();
-
try
{
- srt::sync::ScopedLock globlock(uglobal().m_GlobControlLock);
+ srt::sync::ExclusiveLock globlock(uglobal().m_GlobControlLock);
return newGroup(gt).id();
// Note: potentially, after this function exits, the group
- // could be deleted, immediately, from a separate thread (tho
+ // could be deleted, immediately, from a separate thread (though
// unlikely because the other thread would need some handle to
// keep it). But then, the first call to any API function would
// return invalid ID error.
@@ -3591,7 +3775,7 @@ SRTSOCKET srt::CUDT::getGroupOfSocket(SRTSOCKET socket)
{
// Lock this for the whole function as we need the group
// to persist the call.
- ScopedLock glock(uglobal().m_GlobControlLock);
+ SharedLock glock(uglobal().m_GlobControlLock);
CUDTSocket* s = uglobal().locateSocket_LOCKED(socket);
if (!s || !s->m_GroupOf)
return APIError(MJ_NOTSUP, MN_INVAL, 0);
diff --git a/srtcore/api.h b/srtcore/api.h
index 48e7827f84..96302f118b 100644
--- a/srtcore/api.h
+++ b/srtcore/api.h
@@ -120,6 +120,7 @@ class CUDTSocket
}
~CUDTSocket();
+ void resetAtFork();
void construct();
@@ -215,6 +216,11 @@ class CUDTSocket
core().m_bClosing = true;
}
+ void setBreaking()
+ {
+ core().m_bBreaking = true;
+ }
+
/// This does the same as setClosed, plus sets the m_bBroken to true.
/// Such a socket can still be read from so that remaining data from
/// the receiver buffer can be read, but no longer sends anything.
@@ -239,6 +245,7 @@ class CUDTUnited
friend class CUDTGroup;
friend class CRendezvousQueue;
friend class CCryptoControl;
+ friend class TestMockCUDT;
public:
CUDTUnited();
@@ -263,6 +270,7 @@ class CUDTUnited
/// release the UDT library.
/// @return 0 if success, otherwise -1 is returned.
int cleanup();
+ int cleanupAtFork();
/// Create a new UDT socket.
/// @param [out] pps Variable (optional) to which the new socket will be written, if succeeded
@@ -407,7 +415,7 @@ class CUDTUnited
groups_t m_Groups;
#endif
- sync::Mutex m_GlobControlLock; // used to synchronize UDT API
+ sync::SharedMutex m_GlobControlLock; // used to synchronize UDT API
sync::Mutex m_IDLock; // used to synchronize ID generation
@@ -462,6 +470,10 @@ class CUDTUnited
CUDTSocket* locateAcquireSocket(SRTSOCKET u, ErrorHandling erh = ERH_RETURN);
bool acquireSocket(CUDTSocket* s);
+ bool startGarbageCollector();
+ void stopGarbageCollector();
+ void cleanupAllSockets();
+ void closeAllSockets();
public:
struct SocketKeeper
@@ -518,7 +530,7 @@ class CUDTUnited
/// @brief Checks if channel configuration matches the socket configuration.
/// @param cfgMuxer multiplexer configuration.
/// @param cfgSocket socket configuration.
- /// @return tru if configurations match, false otherwise.
+ /// @return true if configurations match, false otherwise.
static bool channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket);
static bool inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer,
const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket);
@@ -534,6 +546,7 @@ class CUDTUnited
private:
srt::sync::atomic m_bClosing;
+ sync::Mutex m_GCStartLock;
sync::Mutex m_GCStopLock;
sync::Condition m_GCStopCond;
diff --git a/srtcore/atomic.h b/srtcore/atomic.h
index f391be9416..6f510beaca 100644
--- a/srtcore/atomic.h
+++ b/srtcore/atomic.h
@@ -75,11 +75,11 @@
#define ATOMIC_USE_GCC_INTRINSICS
#elif defined(__GNUC__) \
&& ( (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)) )
- // NOTE: The __atomic_* family of intrisics were introduced in GCC-4.7.0.
+ // NOTE: The __atomic_* family of intrinsics were introduced in GCC-4.7.0.
// NOTE: This follows #if defined(__clang__), because most if, not all,
// versions of Clang define __GNUC__ and __GNUC_MINOR__ but often define
// them to 4.4 or an even earlier version. Most of the newish versions
- // of Clang also support GCC Atomic Intrisics even if they set GCC version
+ // of Clang also support GCC Atomic Intrinsics even if they set GCC version
// macros to <4.7.
#define ATOMIC_USE_GCC_INTRINSICS
#elif defined(__GNUC__) && !defined(ATOMIC_USE_SRT_SYNC_MUTEX)
diff --git a/srtcore/buffer_rcv.cpp b/srtcore/buffer_rcv.cpp
index e74d48c075..88f94103ba 100644
--- a/srtcore/buffer_rcv.cpp
+++ b/srtcore/buffer_rcv.cpp
@@ -276,10 +276,10 @@ int CRcvBuffer::dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, Dro
// Drop by packet seqno range to also wipe those packets that do not exist in the buffer.
const int offset_a = CSeqNo::seqoff(m_iStartSeqNo, seqnolo);
const int offset_b = CSeqNo::seqoff(m_iStartSeqNo, seqnohi);
- if (offset_b < 0)
+ if (offset_b < 0 || offset_a >= (int) m_szSize)
{
LOGC(rbuflog.Debug, log << "CRcvBuffer.dropMessage(): nothing to drop. Requested [" << seqnolo << "; "
- << seqnohi << "]. Buffer start " << m_iStartSeqNo << ".");
+ << seqnohi << "]. Buffer start " << m_iStartSeqNo << " size " << m_szSize << ".");
return 0;
}
@@ -483,7 +483,7 @@ int CRcvBuffer::readMessage(char* data, size_t len, SRT_MSGCTRL* msgctrl)
if (!m_tsbpd.isEnabled())
// We need updateFirstReadableOutOfOrder() here even if we are reading inorder,
- // incase readable inorder packets are all read out.
+ // in case readable inorder packets are all read out.
updateFirstReadableOutOfOrder();
const int bytes_read = int(dst - data);
diff --git a/srtcore/buffer_rcv.h b/srtcore/buffer_rcv.h
index c5fca428b7..eac1c7c47e 100644
--- a/srtcore/buffer_rcv.h
+++ b/srtcore/buffer_rcv.h
@@ -94,7 +94,7 @@ class CRcvBuffer
/// @param seqnolo sequence number of the first packet in the dropping range.
/// @param seqnohi sequence number of the last packet in the dropping range.
/// @param msgno message number to drop (0 if unknown)
- /// @param actionOnExisting Should an exising SOLO packet be dropped from the buffer or preserved?
+ /// @param actionOnExisting Should an existing SOLO packet be dropped from the buffer or preserved?
/// @return the number of packets actually dropped.
int dropMessage(int32_t seqnolo, int32_t seqnohi, int32_t msgno, DropActionIfExists actionOnExisting);
@@ -162,7 +162,7 @@ class CRcvBuffer
int getRcvDataSize() const;
/// Get the number of packets, bytes and buffer timespan.
- /// Differs from getRcvDataSize() that it counts all packets in the buffer, not only continious.
+ /// Differs from getRcvDataSize() that it counts all packets in the buffer, not only continuous.
int getRcvDataSize(int& bytes, int& timespan) const;
struct PacketInfo
@@ -221,7 +221,7 @@ class CRcvBuffer
/// Return buffer capacity.
/// One slot had to be empty in order to tell the difference between "empty buffer" and "full buffer".
- /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continiously.
+ /// E.g. m_iFirstNonreadPos would again point to m_iStartPos if m_szSize entries are added continuously.
/// TODO: Old receiver buffer capacity returns the actual size. Check for conflicts.
size_t capacity() const
{
diff --git a/srtcore/buffer_snd.cpp b/srtcore/buffer_snd.cpp
index ef1bc498cd..7b8c44f999 100644
--- a/srtcore/buffer_snd.cpp
+++ b/srtcore/buffer_snd.cpp
@@ -202,7 +202,7 @@ void CSndBuffer::addBuffer(const char* data, int len, SRT_MSGCTRL& w_mctrl)
s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST);
if (i == iNumBlocks - 1)
s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST);
- // NOTE: if i is neither 0 nor size-1, it resuls with PB_SUBSEQUENT.
+ // NOTE: if i is neither 0 nor size-1, it results with PB_SUBSEQUENT.
// if i == 0 == size-1, it results with PB_SOLO.
// Packets assigned to one message can be:
// [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message
@@ -550,6 +550,13 @@ void CSndBuffer::ackData(int offset)
{
ScopedLock bufferguard(m_BufLock);
+ if (offset > m_iCount)
+ {
+ LOGC(bslog.Warn, log << "ackData: offset=" << offset << " > count=" << m_iCount
+ << " - adjusting");
+ offset = m_iCount;
+ }
+
bool move = false;
for (int i = 0; i < offset; ++i)
{
diff --git a/srtcore/buffer_tools.h b/srtcore/buffer_tools.h
index 88479827df..1c90a56a1e 100644
--- a/srtcore/buffer_tools.h
+++ b/srtcore/buffer_tools.h
@@ -198,6 +198,133 @@ class CSndRateEstimator
int m_iRateBps; //< Rate in Bytes/sec.
};
+// Utility class for bandwidth limitation
+class CShaper
+{
+public:
+ static double SHAPER_RESOLUTION_US() { return 1000000.; } // micro seconds
+ static double BURSTPERIOD_DEFAULT() { return 100; }
+
+ typedef sync::steady_clock::time_point time_point;
+ typedef sync::steady_clock::duration duration;
+ CShaper (double min_tokens_capacity):
+ m_BurstPeriod(sync::milliseconds_from(BURSTPERIOD_DEFAULT())),
+ m_rate_Bps(0),
+ m_tokens(0),
+ m_tokensCapacity(0),
+ m_minTokensCapacity(min_tokens_capacity)
+ {
+
+ }
+
+ // Token capacity should be settable later
+ void setMinimumTokenCapacity(double cap)
+ {
+ m_minTokensCapacity = cap;
+ updateLimits();
+ }
+
+private:
+ duration m_BurstPeriod;
+ double m_rate_Bps; // current_bitrate in Bytes per second
+ double m_tokens; // in bytes
+ double m_tokensCapacity; // in bytes
+ double m_minTokensCapacity;
+ time_point m_UpdateTime;
+ void setTokenCapacity(double tokens)
+ {
+ m_tokensCapacity = std::max(0., tokens);
+ }
+
+ void setTokens(double tokens)
+ {
+ // It is allowed to set the negative number of tokens,
+ // just not less than the negative maximum
+ m_tokens = Bounds(1 - m_tokensCapacity, tokens, m_tokensCapacity);
+ }
+
+ static double periodToTokens(double rate_Bps, duration period)
+ {
+ double seconds = double(sync::count_microseconds(period)) / SHAPER_RESOLUTION_US();
+ return rate_Bps * seconds;
+ }
+
+ static duration tokensToPeriod(double rate_Bps, double tokens)
+ {
+ double seconds = tokens / rate_Bps;
+ return sync::microseconds_from(seconds * SHAPER_RESOLUTION_US());
+ }
+
+ void updateLimits()
+ {
+ double token_cap = periodToTokens(m_rate_Bps, m_BurstPeriod);
+ if (token_cap < m_minTokensCapacity)
+ {
+ // We have a too small value; recalculate the burst period to reach the minimum.
+ duration minperiod = tokensToPeriod(m_rate_Bps, m_minTokensCapacity);
+ token_cap = m_minTokensCapacity;
+ m_BurstPeriod = minperiod;
+ }
+ setTokenCapacity(token_cap);
+ }
+
+public:
+ void setBitrate(double bw_Bps)
+ {
+ // NOTE: comparison on double to check the PREVIOUSLY
+ // SET value (m_rate_Bps), not recalculated value.
+ if (bw_Bps != m_rate_Bps)
+ {
+ m_rate_Bps = bw_Bps;
+ updateLimits();
+ }
+ }
+
+ void setMinimumBurstPeriod(duration hp)
+ {
+ if (m_BurstPeriod < hp)
+ {
+ setBurstPeriod(hp);
+ }
+ }
+
+ void setBurstPeriod(duration bp)
+ {
+ if (bp != m_BurstPeriod)
+ {
+ m_BurstPeriod = bp;
+ updateLimits();
+ }
+ }
+
+ // Note that tick() must be always called after setBitrate.
+ void tick(const time_point& now)
+ {
+ duration delta = now - m_UpdateTime;
+ m_UpdateTime = now;
+
+ double update_tokens = periodToTokens(m_rate_Bps, delta);
+ setTokens(update_tokens + m_tokens);
+ }
+
+ bool enoughTokens(double len) const { return len <= m_tokens; }
+ void consumeTokens(double len) { setTokens(m_tokens - len); }
+
+
+ // For debug purposes
+ int ntokens() const { return m_tokens; }
+ duration burstPeriod() const { return m_BurstPeriod; }
+ int maxTokens() const { return m_tokensCapacity; }
+ // TOKENS = BITRATE * BURST_PERIOD / (SHAPER_UNIT * SHAPER_RESOLUTION_US)
+ // TOKENS * SHAPER_UNIT * SHAPER_RESOLUTION_US = BITRATE * BURST_PERIOD
+ // BITRATE = (TOKENS * SHAPER_UNIT * SHAPER_RESOLUTION_US) / (BURST_PERIOD)
+ double tokenRate_Bps(double tokens) const
+ {
+ return (tokens * SHAPER_RESOLUTION_US()) / (sync::count_microseconds(m_BurstPeriod));
+ }
+ double availRate_Bps() const { return tokenRate_Bps(m_tokens); }
+ double usedRate_Bps() const { return tokenRate_Bps(m_tokensCapacity - m_tokens); }
+};
} // namespace srt
#endif
diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp
index 1868cc4324..851dce8b69 100644
--- a/srtcore/channel.cpp
+++ b/srtcore/channel.cpp
@@ -289,6 +289,20 @@ void srt::CChannel::attach(UDPSOCKET udpsock, const sockaddr_any& udpsocks_addr)
setUDPSockOpt();
}
+static inline string fmt_opt(bool value, const string& label)
+{
+ string out;
+ out.reserve(label.size() + 2);
+ out = value ? "+" : "-";
+ out += label;
+ return out;
+}
+
+static inline string fmt_alt(bool value, const string& label, const string& unlabel)
+{
+ return value ? label : unlabel;
+}
+
void srt::CChannel::setUDPSockOpt()
{
#if defined(SUNOS)
@@ -297,13 +311,13 @@ void srt::CChannel::setUDPSockOpt()
// Retrieve starting SND/RCV Buffer sizes.
int startRCVBUF = 0;
optSize = sizeof(startRCVBUF);
- if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize))
+ if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&startRCVBUF, &optSize))
{
startRCVBUF = -1;
}
int startSNDBUF = 0;
optSize = sizeof(startSNDBUF);
- if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize))
+ if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&startSNDBUF, &optSize))
{
startSNDBUF = -1;
}
@@ -312,13 +326,12 @@ void srt::CChannel::setUDPSockOpt()
// maximum value.
// However, do not reduce the buffer size.
const int maxsize = 64000;
- if (0 !=
- ::setsockopt(
- m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
+ if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF,
+ (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
{
int currentRCVBUF = 0;
optSize = sizeof(currentRCVBUF);
- if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tRCVBUF, &optSize))
+ if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tRCVBUF, &optSize))
{
currentRCVBUF = -1;
}
@@ -327,13 +340,12 @@ void srt::CChannel::setUDPSockOpt()
::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize);
}
}
- if (0 !=
- ::setsockopt(
- m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))
+ if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF,
+ (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))
{
int currentSNDBUF = 0;
optSize = sizeof(currentSNDBUF);
- if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tSNDBUF, &optSize))
+ if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)¤tSNDBUF, &optSize))
{
currentSNDBUF = -1;
}
@@ -346,13 +358,13 @@ void srt::CChannel::setUDPSockOpt()
// Retrieve ending SND/RCV Buffer sizes.
int endRCVBUF = 0;
optSize = sizeof(endRCVBUF);
- if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize))
+ if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (void*)&endRCVBUF, &optSize))
{
endRCVBUF = -1;
}
int endSNDBUF = 0;
optSize = sizeof(endSNDBUF);
- if (0 != ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize))
+ if (-1 == ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (void*)&endSNDBUF, &optSize))
{
endSNDBUF = -1;
}
@@ -368,90 +380,137 @@ void srt::CChannel::setUDPSockOpt()
#elif defined(BSD) || TARGET_OS_MAC
// BSD system will fail setsockopt if the requested buffer size exceeds system maximum value
int maxsize = 64000;
- if (0 != ::setsockopt(
+ if (-1 == ::setsockopt(
m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&maxsize, sizeof maxsize);
- if (0 != ::setsockopt(
+ if (-1 == ::setsockopt(
m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize))
::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&maxsize, sizeof maxsize);
#else
- // for other systems, if requested is greated than maximum, the maximum value will be automactally used
- if ((0 !=
- ::setsockopt(
- m_iSocket, SOL_SOCKET, SO_RCVBUF, (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize)) ||
- (0 != ::setsockopt(
- m_iSocket, SOL_SOCKET, SO_SNDBUF, (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)))
+ // for other systems, if requested is greater than maximum, the maximum value will be automatically used
+ if ((-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF,
+ (const char*)&m_mcfg.iUDPRcvBufSize, sizeof m_mcfg.iUDPRcvBufSize))
+ ||
+ (-1 == ::setsockopt( m_iSocket, SOL_SOCKET, SO_SNDBUF,
+ (const char*)&m_mcfg.iUDPSndBufSize, sizeof m_mcfg.iUDPSndBufSize)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#endif
+ bool is_set = false;
+ bool adr_unspec = false, adr_mapped = false, adr_v6 = false;
+ if (m_BindAddr.family() == AF_INET)
+ {
+ adr_unspec = m_BindAddr.isany();
+ }
+ else
+ {
+ adr_unspec = IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr);
+ adr_mapped = IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr);
+ adr_v6 = true;
+ }
+
if (m_mcfg.iIpTTL != -1)
{
- if (m_BindAddr.family() == AF_INET)
+ if (!adr_v6)
{
- if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
+ if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
+ {
+ LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
+ }
+ is_set = true;
}
else
{
// If IPv6 address is unspecified, set BOTH IP_TTL and IPV6_UNICAST_HOPS.
// For specified IPv6 address, set IPV6_UNICAST_HOPS ONLY UNLESS it's an IPv4-mapped-IPv6
- if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) ||
- !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
+ if (adr_unspec || !adr_mapped)
{
- if (0 !=
- ::setsockopt(
- m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
+ if (-1 == ::setsockopt( m_iSocket, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+ (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
{
+ LOGC(kmlog.Error, log << "setsockopt(IPV6_UNICAST_HOPS): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
+ is_set = true;
}
// For specified IPv6 address, set IP_TTL ONLY WHEN it's an IPv4-mapped-IPv6
- if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
+ if (!is_set) // adr_mapped (because adr_unspec was handled above)
{
- if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
+ if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_mcfg.iIpTTL, sizeof m_mcfg.iIpTTL))
{
+ LOGC(kmlog.Error, log << "setsockopt(IP_TTL): " << SysStrError(NET_ERROR)
+ << fmt_alt(adr_unspec, " (v6 unspec)", " (v6 mapped v4)"));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
+ is_set = true;
}
}
+
+ if (!is_set)
+ {
+ LOGC(kmlog.Error, log << "srt_setsockflag(SRTO_IPTTL): No suitable condition for adr=" << m_BindAddr.str()
+ << " : " << fmt_opt(adr_v6, "v6 ") << fmt_opt(adr_unspec, "unspec ") << fmt_opt(adr_mapped, "mapped"));
+ throw CUDTException(MJ_SETUP, MN_INVAL, 0);
+ }
}
+ is_set = false;
if (m_mcfg.iIpToS != -1)
{
if (m_BindAddr.family() == AF_INET)
{
- if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
+ if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
+ {
+ LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
+ }
+ is_set = true;
}
else
{
// If IPv6 address is unspecified, set BOTH IP_TOS and IPV6_TCLASS.
+ SRT_ATR_UNUSED bool using_tclass = false;
#ifdef IPV6_TCLASS
+ using_tclass = true;
// For specified IPv6 address, set IPV6_TCLASS ONLY UNLESS it's an IPv4-mapped-IPv6
- if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) ||
- !IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
+ if (adr_unspec || !adr_mapped)
{
- if (0 != ::setsockopt(
- m_iSocket, IPPROTO_IPV6, IPV6_TCLASS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
+ if (-1 == ::setsockopt(m_iSocket, IPPROTO_IPV6, IPV6_TCLASS,
+ (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
{
+ LOGC(kmlog.Error, log << "setsockopt(IPV6_TCLASS): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
+ is_set = true;
}
#endif
// For specified IPv6 address, set IP_TOS ONLY WHEN it's an IPv4-mapped-IPv6
- if (IN6_IS_ADDR_UNSPECIFIED(&m_BindAddr.sin6.sin6_addr) || IN6_IS_ADDR_V4MAPPED(&m_BindAddr.sin6.sin6_addr))
+ if (!is_set && (adr_unspec || adr_mapped))
{
- if (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
+ if (-1 == ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_mcfg.iIpToS, sizeof m_mcfg.iIpToS))
{
+ LOGC(kmlog.Error, log << "setsockopt(IP_TOS): " << SysStrError(NET_ERROR)
+ << (adr_unspec ? " (v6 unspecified)" : " (v6 mapped v4)")
+ << (using_tclass ? "(fallback to IP_TOS)" : ""));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
+ is_set = true;
}
}
+
+ if (!is_set)
+ {
+ LOGC(kmlog.Error, log << "srt_setsockflag(SRTO_IPTOS): No suitable condition for adr=" << m_BindAddr.str()
+ << " : " << fmt_opt(adr_v6, "v6 ") << fmt_opt(adr_unspec, "unspec ") << fmt_opt(adr_mapped, "mapped"));
+ throw CUDTException(MJ_SETUP, MN_INVAL, 0);
+ }
}
+
#ifdef SRT_ENABLE_BINDTODEVICE
if (!m_mcfg.sBindToDevice.empty())
{
@@ -461,14 +520,10 @@ void srt::CChannel::setUDPSockOpt()
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
- if (0 != ::setsockopt(
- m_iSocket, SOL_SOCKET, SO_BINDTODEVICE, m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size()))
+ if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_BINDTODEVICE,
+ m_mcfg.sBindToDevice.c_str(), m_mcfg.sBindToDevice.size()))
{
-#if ENABLE_LOGGING
- char buf[255];
- const char* err = SysStrError(NET_ERROR, buf, 255);
- LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << err);
-#endif // ENABLE_LOGGING
+ LOGC(kmlog.Error, log << "setsockopt(SRTO_BINDTODEVICE): " << SysStrError(NET_ERROR));
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
}
}
@@ -482,7 +537,7 @@ void srt::CChannel::setUDPSockOpt()
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#elif defined(_WIN32)
u_long nonBlocking = 1;
- if (0 != ioctlsocket(m_iSocket, FIONBIO, &nonBlocking))
+ if (-1 == ioctlsocket(m_iSocket, FIONBIO, &nonBlocking))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#else
timeval tv;
@@ -495,7 +550,11 @@ void srt::CChannel::setUDPSockOpt()
tv.tv_usec = 100;
#endif
// Set receiving time-out value
- if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval)))
+ if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(timeval)))
+ throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
+ // Set sending time-out, too; O_NONBLOCK sets it in both directions,
+ // so both should be also set here for consistency.
+ if (-1 == ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(timeval)))
throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR);
#endif
@@ -833,7 +892,9 @@ int srt::CChannel::sendto(const sockaddr_any& addr, CPacket& packet, const socka
if (bCompleted)
res = 0;
else
+ {
LOGC(kslog.Warn, log << "CChannel::sendto call on ::WSAGetOverlappedResult failed with error: " << NET_ERROR);
+ }
lEvent.reset();
}
else
diff --git a/srtcore/common.cpp b/srtcore/common.cpp
index 2e94668ae1..b301b80a99 100644
--- a/srtcore/common.cpp
+++ b/srtcore/common.cpp
@@ -67,6 +67,7 @@ modified by
#include "logging.h"
#include "packet.h"
#include "threadname.h"
+#include "logger_defs.h"
using namespace std;
using namespace srt::sync;
@@ -92,6 +93,9 @@ m_iMinor(minor)
m_iErrno = NET_ERROR;
else
m_iErrno = err;
+ // XXX No logging allowed because this constructor can be also used for
+ // initializing global object. That problem should be solved separately.
+ // HLOGC(aclog.Debug, log << "CREATED SRT EXCEPTION: " << (1000*major+minor) << " errno=" << m_iErrno);
}
const char* srt::CUDTException::getErrorMessage() const ATR_NOTHROW
@@ -464,12 +468,12 @@ bool SrtParseConfig(const string& s, SrtConfig& w_config)
return true;
}
-std::string FormatLossArray(const std::vector< std::pair >& lra)
+string FormatLossArray(const vector< pair >& lra)
{
- std::ostringstream os;
+ ostringstream os;
os << "[ ";
- for (std::vector< std::pair >::const_iterator i = lra.begin(); i != lra.end(); ++i)
+ for (vector< pair >::const_iterator i = lra.begin(); i != lra.end(); ++i)
{
int len = CSeqNo::seqoff(i->first, i->second);
os << "%" << i->first;
@@ -482,6 +486,16 @@ std::string FormatLossArray(const std::vector< std::pair >& lr
return os.str();
}
+string FormatValue(int value, int factor, const char* unit)
+{
+ ostringstream out;
+ double showval = value;
+ showval /= factor;
+ out << std::fixed << showval << unit;
+ return out.str();
+}
+
+
ostream& PrintEpollEvent(ostream& os, int events, int et_events)
{
static pair const namemap [] = {
diff --git a/srtcore/common.h b/srtcore/common.h
index ff84c3fafc..7050649e85 100644
--- a/srtcore/common.h
+++ b/srtcore/common.h
@@ -95,6 +95,12 @@ modified by
#define SRT_STATIC_ASSERT(cond, msg)
#endif
+#if HAVE_FULL_CXX11
+#define FUNID() __func__
+#else
+#define FUNID() __FUNCTION__
+#endif
+
#include
namespace srt_logging
@@ -565,7 +571,7 @@ struct EventSlot
// UDT Sequence Number 0 - (2^31 - 1)
-// seqcmp: compare two seq#, considering the wraping
+// seqcmp: compare two seq#, considering the wrapping
// seqlen: length from the 1st to the 2nd seq#, including both
// seqoff: offset from the 2nd to the 1st seq#
// incseq: increase the seq# by 1
@@ -601,7 +607,7 @@ class CSeqNo
return seqcmp(value, other.value) <= 0;
}
- // circular arithmetics
+ // circular arithmetic
friend int operator-(const CSeqNo& c1, const CSeqNo& c2)
{
return seqoff(c2.value, c1.value);
@@ -1435,8 +1441,22 @@ inline bool checkMappedIPv4(const sockaddr_in6& sa)
return checkMappedIPv4(addr);
}
+/// Share of the traffic in a sampled window that was not impaired, in percent.
+/// @param clean packets that made it through on the first attempt
+/// @param impaired packets that were lost, retransmitted or dropped
+/// @return 100 for an empty window, 0 when nothing came through cleanly
+inline double StatsQualityPct(int64_t clean, int64_t impaired)
+{
+ const int64_t total = clean + impaired;
+ if (total <= 0)
+ return 100.0;
+
+ return (100.0 * clean) / total;
+}
+
std::string FormatLossArray(const std::vector< std::pair >& lra);
std::ostream& PrintEpollEvent(std::ostream& os, int events, int et_events = 0);
+std::string FormatValue(int value, int factor, const char* unit);
} // namespace srt
diff --git a/srtcore/congctl.cpp b/srtcore/congctl.cpp
index 85d4cda97d..8de14d8931 100644
--- a/srtcore/congctl.cpp
+++ b/srtcore/congctl.cpp
@@ -60,7 +60,7 @@ void SrtCongestion::Check()
class LiveCC: public SrtCongestionControlBase
{
- int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec)
+ srt::sync::atomic m_llSndMaxBW; //Max bandwidth (bytes/sec)
srt::sync::atomic m_zSndAvgPayloadSize; //Average Payload Size of packets to xmit
size_t m_zMaxPayloadSize;
size_t m_zHeaderSize;
@@ -79,10 +79,10 @@ class LiveCC: public SrtCongestionControlBase
m_llSndMaxBW = BW_INFINITE; // 1 Gbbps in Bytes/sec BW_INFINITE
m_zMaxPayloadSize = parent->OPT_PayloadSize();
if (m_zMaxPayloadSize == 0)
- m_zMaxPayloadSize = parent->maxPayloadSize();
+ m_zMaxPayloadSize = parent->maxDataPayloadSize();
m_zSndAvgPayloadSize = m_zMaxPayloadSize;
- m_zHeaderSize = parent->m_config.iMSS - parent->maxPayloadSize();
+ m_zHeaderSize = parent->m_config.iMSS - parent->maxDataPayloadSize();
m_iMinNakInterval_us = 20000; //Minimum NAK Report Period (usec)
m_iNakReportAccel = 2; //Default NAK Report Period (RTT) accelerator (send periodic NAK every RTT/2)
@@ -319,7 +319,7 @@ class FileCC : public SrtCongestionControlBase
/// and request ACK to be sent immediately.
bool needsQuickACK(const CPacket& pkt) ATR_OVERRIDE
{
- if (pkt.getLength() < m_parent->maxPayloadSize())
+ if (pkt.getLength() < m_parent->maxDataPayloadSize())
{
// This is not a regular fixed size packet...
// an irregular sized packet usually indicates the end of a message, so send an ACK immediately
diff --git a/srtcore/core.cpp b/srtcore/core.cpp
index 949e709fd8..6c1469ee48 100644
--- a/srtcore/core.cpp
+++ b/srtcore/core.cpp
@@ -94,7 +94,7 @@ const SRTSOCKET UDT::INVALID_SOCK = srt::CUDT::INVALID_SOCK;
const int UDT::ERROR = srt::CUDT::ERROR;
//#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */
-#define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */
+#define SRT_CMD_HSREQ_MINSZ 8 /* Minimum Compatible (1.x.x) packet size (bytes) */
#define SRT_CMD_HSREQ_SZ 12 /* Current version packet size */
#if SRT_CMD_HSREQ_SZ > SRT_CMD_MAXSZ
#error SRT_CMD_MAXSZ too small
@@ -107,7 +107,7 @@ const int UDT::ERROR = srt::CUDT::ERROR;
*/
//#define SRT_CMD_HSRSP 2 /* SRT Handshake Response (receiver) */
-#define SRT_CMD_HSRSP_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */
+#define SRT_CMD_HSRSP_MINSZ 8 /* Minimum Compatible (1.x.x) packet size (bytes) */
#define SRT_CMD_HSRSP_SZ 12 /* Current version packet size */
#if SRT_CMD_HSRSP_SZ > SRT_CMD_MAXSZ
#error SRT_CMD_MAXSZ too small
@@ -262,6 +262,162 @@ CUDTUnited& srt::CUDT::uglobal()
#endif
+#ifdef ENABLE_RATE_MEASUREMENT
+void srt::RateMeasurement::pickup(const clock_time& time)
+{
+ // Check if the time has passed the period.
+ clock_interval diff;
+ if (!passedInterval(time, (diff)))
+ {
+ HLOGC(bslog.Debug, log << "RateMeasurement: too early, " << FormatDuration(diff)
+ << " with begin TS=" << FormatTime(m_beginTime) << " now=" << FormatTime(time));
+ // Still collect data for the current period.
+ return;
+ }
+
+ // Doesn't matter what `diff` really is. Enough that we have
+ // this time aligned with 10ms resolution calls 10 times.
+
+ Slice slice;
+ {
+ // Lock for the moment of taking the data
+ sync::ScopedLock lk (m_lock);
+
+ slice.packets = m_nInstaPackets;
+ slice.bytes = m_nInstaBytes;
+ slice.begin_time = m_beginTime;
+
+ m_nInstaPackets = 0;
+ m_nInstaBytes = 0;
+ m_beginTime = time;
+ }
+
+ HLOGC(bslog.Debug, log << "RateMeasurement: passed " << FormatDuration(diff)
+ << " extracted packets=" << slice.packets << " bytes=" << slice.bytes
+ << " now beginTS=" << FormatTime(m_beginTime));
+
+ // Ok, now stickpile the slice.
+ // If the container is empty, do not stockpile empty slices.
+ if (m_slices.empty() && slice.bytes == 0)
+ {
+ HLOGC(bslog.Debug, log << "... NOT COLLECTING packet with " << slice.bytes << " bytes to " << m_slices.size() << " total");
+ // Empty-to-empty: DO NOT stockpile.
+ // NOTE: the container is never cleared when it has
+ // any older data, so this situation is only possible:
+ // - in the beginning
+ // - in the long run if it happened that 10 slices in
+ // a row were empty.
+ // So in this case we simply wait for having the first
+ // packets with something notified. We also know that
+ // the rate was initially 0 and it remains so now.
+ return;
+ }
+
+ // We either have something and have pushed an empty slice
+ // or we have pushed filled slice onto the empty container.
+ // Still, we need at least 5 samples to start measuring.
+ m_slices.push_back(slice);
+
+ // NOTE: this is also a situation that might have happened
+ // in the beginning, or after a long period without sending.
+ // If that period happens, the sending rate is also cleared,
+ // in any case we need to wait for the minimum samples.
+ if (m_slices.size() < MIN_SLICES)
+ {
+ HLOGC(bslog.Debug, log << "... NOT MEASURING: still " << m_slices.size() << " < 5 slices");
+ return;
+ }
+
+ // Keep track of only 10 last slices
+ if (m_slices.size() > MAX_SLICES)
+ {
+ m_slices.pop_front();
+
+ // After a slice was removed, there's already a chance
+ // that all other slices are also 0. Check the minimum
+ // number of slices + 1 and drop all that are empty
+ size_t end_range = 0; // default: nothing to delete
+ for (size_t i = 0; i < m_slices.size(); ++i)
+ {
+ if (i > MIN_SLICES + 1)
+ break;
+
+ if (m_slices[i].bytes > 0)
+ break;
+
+ // This can be skipped and check the next one.
+ end_range = i;
+ }
+
+ // PARANOID
+ if (end_range >= m_slices.size())
+ end_range = m_slices.size() - 1; // at least 1 element must stay
+
+ m_slices.erase(m_slices.begin(), m_slices.begin() + end_range);
+
+ m_beginTime = m_slices[0].begin_time;
+ HLOGC(bslog.Debug, log << "... Dropping 1 oldest + " << end_range << " empty slices, TS=" << FormatTime(m_beginTime));
+ }
+
+ // Check also if the begin time is older than 2* period; if so,
+ // reset it to that value
+ if (m_slices.size() > 1) // with 1 always m_beginTime = m_slices[0].begin_time
+ {
+ clock_time earliest = m_slices[1].begin_time - milliseconds_from(SLICE_INTERVAL_MS*2);
+ if (m_beginTime < earliest)
+ {
+ HLOGC(bslog.Debug, log << "... BEGIN TIME too old TS=" << FormatTime(m_beginTime)
+ << " - fixed to " << FormatTime(earliest));
+ m_beginTime = earliest;
+ m_slices[0].begin_time = earliest;
+ }
+ }
+
+ // Ok, so we have enough samples to record the measurement.
+ // (so we have at least one)
+ Summary sum (m_slices[0].begin_time);
+
+ for (size_t i = 0; i < m_slices.size(); ++i)
+ {
+ sum.consume(m_slices[i]);
+ }
+
+ m_currentPktRate = sum.ratePackets(time);
+ m_currentByteRate = sum.rateBytes(time, m_SysHeaderSize + CPacket::HDR_SIZE);
+
+ HLOGC(bslog.Debug, log << "... CALCULATED from " << m_slices.size() << " slices: total TD=" << FormatDuration(time - m_slices[0].begin_time)
+ << " packets=" << sum.packets << " bytes=" << sum.bytes << " pkt-rate=" << m_currentPktRate << " byte-rate=" << m_currentByteRate);
+}
+#endif
+
+// SRTLA loss-report timing parameters (active only with SRTO_SRTLA).
+namespace {
+const int SRTLA_WITNESS_TTL = 2;
+const uint32_t SRTLA_HOLD_FLOOR_US = 100000;
+const uint32_t SRTLA_HOLD_CAP_US = 500000;
+const uint32_t SRTLA_HOLD_FALLBACK_US = 250000;
+const uint32_t SRTLA_HOLD_EVIDENCE_MARGIN_US = 30000;
+const int64_t SRTLA_HOLD_DECAY_TAU_US = 20000000;
+const int SRTLA_MIN_RCVLATENCY_MS = 1000;
+// Under SRTLA the NAK timer is the only loss-report emitter and the hold already paces the
+// reports, so it runs at a fixed short period: the stock (SRTT + 4 RTTVar) / 2 would turn the
+// inter-link RTT variance of a bond into a hidden reorder tolerance.
+const int64_t SRTLA_NAK_PERIOD_US = 20000;
+const int64_t SRTLA_DEADLINE_MARGIN_US = 30000;
+
+// Sampling window for pctSndQuality / pctRcvQuality.
+const int64_t QUALITY_WINDOW_US = 1000000; // 1 s
+
+inline uint32_t srtlaDecayedFastHold(uint32_t stored_us, int64_t age_us)
+{
+ if (stored_us == 0 || age_us >= SRTLA_HOLD_DECAY_TAU_US)
+ return 0;
+ if (age_us <= 0)
+ return stored_us;
+ return (uint32_t)((int64_t)stored_us * (SRTLA_HOLD_DECAY_TAU_US - age_us) / SRTLA_HOLD_DECAY_TAU_US);
+}
+} // namespace
+
void srt::CUDT::construct()
{
m_pSndBuffer = NULL;
@@ -269,6 +425,8 @@ void srt::CUDT::construct()
m_pSndLossList = NULL;
m_pRcvLossList = NULL;
m_iReorderTolerance = 0;
+ m_uiSrtlaHoldSteadyUs = 0;
+ m_uiSrtlaHoldFastUs = 0;
// How many times so far the packet considered lost has been received
// before TTL expires.
m_iConsecEarlyDelivery = 0;
@@ -289,6 +447,7 @@ void srt::CUDT::construct()
m_bConnected = false;
m_bClosing = false;
m_bShutdown = false;
+ m_bBreaking = false;
m_bBroken = false;
m_bBreakAsUnstable = false;
// TODO: m_iBrokenCounter should be still set to some default.
@@ -306,7 +465,13 @@ void srt::CUDT::construct()
m_bPeerTLPktDrop = false;
m_bBufferWasFull = false;
- // Initilize mutex and condition variables.
+ // Will be updated on first send
+#ifdef ENABLE_MAXREXMITBW
+ m_zSndAveragePacketSize = 0;
+ m_zSndMaxPacketSize = 0;
+#endif
+
+ // Initialize mutex and condition variables.
initSynch();
// TODO: Uncomment when the callback is implemented.
@@ -316,7 +481,8 @@ void srt::CUDT::construct()
srt::CUDT::CUDT(CUDTSocket* parent)
: m_parent(parent)
#ifdef ENABLE_MAXREXMITBW
- , m_SndRexmitRate(sync::steady_clock::now())
+ // , m_SndRexmitRate(sync::steady_clock::now())
+ , m_SndRexmitShaper(CSrtConfig::DEF_MSS)
#endif
, m_iISN(-1)
, m_iPeerISN(-1)
@@ -345,7 +511,8 @@ srt::CUDT::CUDT(CUDTSocket* parent)
srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor)
: m_parent(parent)
#ifdef ENABLE_MAXREXMITBW
- , m_SndRexmitRate(sync::steady_clock::now())
+ // , m_SndRexmitRate(sync::steady_clock::now())
+ , m_SndRexmitShaper(CSrtConfig::DEF_MSS)
#endif
, m_iISN(-1)
, m_iPeerISN(-1)
@@ -380,13 +547,24 @@ srt::CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor)
m_bTLPktDrop = ancestor.m_bTLPktDrop;
m_iReorderTolerance = m_config.iMaxReorderTolerance; // Initialize with maximum value
+ // SRTLA protocol rule: the receiver latency is never below SRTLA_MIN_RCVLATENCY_MS.
+ // The reorder hold may grow to half the latency (capped at 500 ms) and one recovery
+ // round trip has to fit behind it, so anything lower cannot be served. Raise-only; the
+ // raised value reaches the sender through the handshake response as its peer latency.
+ if (m_config.bSRTLA && m_config.iRcvLatency < SRTLA_MIN_RCVLATENCY_MS)
+ {
+ LOGC(cnlog.Note, log << CONID() << "SRTLA: receiver latency " << m_config.iRcvLatency
+ << " ms is below the protocol minimum, raised to " << SRTLA_MIN_RCVLATENCY_MS << " ms");
+ m_config.iRcvLatency = SRTLA_MIN_RCVLATENCY_MS;
+ }
+
// Runtime
m_pCache = ancestor.m_pCache;
}
srt::CUDT::~CUDT()
{
- // release mutex/condtion variables
+ // release mutex/condition variables
destroySynch();
// destroy the data structures
@@ -913,17 +1091,17 @@ string srt::CUDT::getstreamid(SRTSOCKET u)
void srt::CUDT::clearData()
{
const size_t full_hdr_size = CPacket::UDP_HDR_SIZE - CPacket::HDR_SIZE;
- m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size;
- HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
+ m_iMaxDataPayloadSize = m_config.iMSS - full_hdr_size;
+ HLOGC(cnlog.Debug, log << CONID() << "clearData: PAYLOAD SIZE: " << m_iMaxDataPayloadSize);
- m_SndTimeWindow.initialize(full_hdr_size, m_iMaxSRTPayloadSize);
- m_RcvTimeWindow.initialize(full_hdr_size, m_iMaxSRTPayloadSize);
+ m_SndTimeWindow.initialize(full_hdr_size, m_iMaxDataPayloadSize);
+ m_RcvTimeWindow.initialize(full_hdr_size, m_iMaxDataPayloadSize);
m_iEXPCount = 1;
m_iBandwidth = 1; // pkts/sec
// XXX use some constant for this 16
m_iDeliveryRate = 16;
- m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize;
+ m_iByteDeliveryRate = 16 * m_iMaxDataPayloadSize;
m_iAckSeqNo = 0;
m_tsLastAckTime = steady_clock::now();
@@ -939,6 +1117,14 @@ void srt::CUDT::clearData()
m_stats.traceReorderDistance = 0;
m_stats.traceBelatedTime = 0;
m_stats.sndDuration = m_stats.m_sndDurationTotal = 0;
+
+ m_stats.tsQualityWindow = m_stats.tsStartTime;
+ m_stats.qualBaseSndClean = 0;
+ m_stats.qualBaseSndImpaired = 0;
+ m_stats.qualBaseRcvClean = 0;
+ m_stats.qualBaseRcvImpaired = 0;
+ m_stats.pctSndQuality = 100.0;
+ m_stats.pctRcvQuality = 100.0;
}
// Resetting these data because this happens when agent isn't connected.
@@ -1020,8 +1206,6 @@ void srt::CUDT::open()
void srt::CUDT::setListenState()
{
- ScopedLock cg(m_ConnectionLock);
-
if (!m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
@@ -1029,14 +1213,42 @@ void srt::CUDT::setListenState()
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// listen can be called more than once
- if (m_bListening)
- return;
-
- // if there is already another socket listening on the same port
- if (m_pRcvQueue->setListener(this) < 0)
- throw CUDTException(MJ_NOTSUP, MN_BUSY, 0);
-
- m_bListening = true;
+ // If two threads call srt_listen at the same time, only
+ // one will pass this condition; others will be rejected.
+ // If it was called ever once, none will pass.
+ for (;;)
+ {
+ if (m_bListening.compare_exchange(false, true))
+ {
+ // if there is already another socket listening on the same port
+ if (!m_pRcvQueue->setListener(this))
+ {
+ // Failed here, so
+ m_bListening = false;
+ throw CUDTException(MJ_NOTSUP, MN_BUSY, 0);
+ }
+ }
+ else
+ {
+ // Ok, this thread could have been blocked access,
+ // but still the other thread that attempted to set
+ // the listener could have failed. Therefore check
+ // again if the listener was set successfully, and
+ // if the listening point is still free, try again.
+ CUDT* current = m_pRcvQueue->getListener();
+ if (current == NULL)
+ {
+ continue;
+ }
+ else if (current != this)
+ {
+ // Some other listener already set it
+ throw CUDTException(MJ_NOTSUP, MN_BUSY, 0);
+ }
+ // If it was you who set this, just return with no exception.
+ }
+ break;
+ }
}
size_t srt::CUDT::fillSrtHandshake(uint32_t *aw_srtdata, size_t srtlen, int msgtype, int hs_version)
@@ -1473,7 +1685,7 @@ bool srt::CUDT::createSrtHandshake(
// Once you are certain that the version is HSv5, set the enc type flags
// to advertise pbkeylen. Otherwise make sure that the old interpretation
- // will correctly pick up the type field. PBKEYLEN should be advertized
+ // will correctly pick up the type field. PBKEYLEN should be advertised
// regardless of what URQ stage the handshake is (note that in case of rendezvous
// CONCLUSION might be the FIRST MESSAGE EVER RECEIVED by a party).
if (w_hs.m_iVersion > HS_VERSION_UDT4)
@@ -1629,7 +1841,7 @@ bool srt::CUDT::createSrtHandshake(
// it was an abnormal interrupt during the processing so the handshake process should be aborted
// anyway, and that's what will be done.
- // LOCKING INFORMATION: accesing this field just for NULL check doesn't
+ // LOCKING INFORMATION: accessing this field just for NULL check doesn't
// hurt, even if this field could be dangling in the moment. This will be
// followed by an additional check, done this time under lock, and there will
// be no dangling pointers at this time.
@@ -1667,7 +1879,7 @@ bool srt::CUDT::createSrtHandshake(
// Now use the original function to store the actual SRT_HS data
// ra_size after that
- // NOTE: so far, ra_size is m_iMaxSRTPayloadSize expressed in number of elements.
+ // NOTE: so far, ra_size is controlPayloadSize() expressed in number of elements.
// WILL BE CHANGED HERE.
ra_size = fillSrtHandshake((p + offset), total_ra_size - offset, srths_cmd, HS_VERSION_SRT1);
*pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap((uint32_t) ra_size);
@@ -1682,7 +1894,7 @@ bool srt::CUDT::createSrtHandshake(
// Now prepare the string with 4-byte alignment. The string size is limited
// to half the payload size. Just a sanity check to not pack too much into
// the conclusion packet.
- size_t size_limit = m_iMaxSRTPayloadSize / 2;
+ size_t size_limit = controlPayloadSize() / 2;
if (m_config.sStreamName.size() >= size_limit)
{
@@ -1731,7 +1943,7 @@ bool srt::CUDT::createSrtHandshake(
#if ENABLE_BONDING
// Note that this will fire in both cases:
// - When the group has been set by the user on a socket (or socket was created as a part of the group),
- // and the handshake request is to be sent with informing the peer that this conenction belongs to a group
+ // and the handshake request is to be sent with informing the peer that this connection belongs to a group
// - When the agent received a HS request with a group, has created its mirror group on its side, and
// now sends the HS response to the peer, with ITS OWN group id (the mirror one).
//
@@ -1742,7 +1954,7 @@ bool srt::CUDT::createSrtHandshake(
if (have_group)
{
// NOTE: See information about mutex ordering in api.h
- ScopedLock gdrg (uglobal().m_GlobControlLock);
+ SharedLock gdrg (uglobal().m_GlobControlLock);
if (!m_parent->m_GroupOf)
{
// This may only happen if since last check of m_GroupOf pointer the socket was removed
@@ -2029,8 +2241,10 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt)
{
uint32_t srtdata_out[SRTDATA_MAXSIZE];
size_t len_out = 0;
+
res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4, m_uPeerSrtVersion,
- (srtdata_out), (len_out));
+ (srtdata_out), (len_out));
+
if (res == SRT_CMD_KMRSP)
{
if (len_out == 1)
@@ -2053,11 +2267,11 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt)
}
sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out);
}
- // XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now.
- // Please review later.
+ // NOTE: processSrtMsg_KMREQ doesn't return any other value now.
+ // But this may be a result of the size check EPE.
else
{
- LOGC(cnlog.Warn, log << CONID() << "KMREQ failed to process the request - ignoring");
+ LOGC(cnlog.Warn, log << CONID() << "EPE: KMREQ failed to process the request - ignoring");
}
return true; // already done what's necessary
@@ -2066,7 +2280,7 @@ bool srt::CUDT::processSrtMsg(const CPacket *ctrlpkt)
case SRT_CMD_KMRSP:
{
// KMRSP doesn't expect any following action
- m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion);
+ m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, m_uPeerSrtVersion, false);
return true; // nothing to do
}
@@ -2471,7 +2685,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
m_RejectReason = SRT_REJ_VERSION;
// This means that a version with minimum 1.3.0 that features HSv5 is required,
// hence all HSv4 clients should be rejected.
- LOGP(cnlog.Error, "interpretSrtHandshake: minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client");
+ LOGP(cnlog.Error, string(FUNID()) + ": minimum peer version 1.3.0 (HSv5 only), rejecting HSv4 client");
return false;
}
return true; // do nothing
@@ -2509,7 +2723,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ))
{
- HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting HSREQ/RSP type extension");
+ HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": extracting HSREQ/RSP type extension");
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
@@ -2541,7 +2755,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
{
// m_RejectReason already set
LOGC(cnlog.Error,
- log << CONID() << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd);
+ log << CONID() << FUNID() << ": process HSREQ returned unexpected value " << rescmd);
return false;
}
handshakeDone();
@@ -2572,7 +2786,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (m_RejectReason == SRT_REJ_UNKNOWN)
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
- log << CONID() << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd);
+ log << CONID() << FUNID() << ": process HSRSP returned unexpected value " << rescmd);
return false;
}
handshakeDone();
@@ -2582,7 +2796,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Warn,
- log << CONID() << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!");
+ log << CONID() << FUNID() << ": no HSREQ/HSRSP block found in the handshake msg!");
// This means that there can be no more processing done by FindExtensionBlock().
// And we haven't found what we need - otherwise one of the above cases would pass
// and lead to exit this loop immediately.
@@ -2601,7 +2815,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
}
}
- HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: HSREQ done, checking KMREQ");
+ HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": HSREQ done, checking KMREQ");
// Now check the encrypted
@@ -2609,7 +2823,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ))
{
- HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting KMREQ/RSP type extension");
+ HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": extracting KMREQ/RSP type extension");
#ifdef SRT_ENABLE_ENCRYPTION
if (!m_pCryptoControl->hasPassphrase())
@@ -2642,7 +2856,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
HLOGC(cnlog.Debug,
- log << CONID() << "interpretSrtHandshake: found extension: (" << cmd << ") "
+ log << CONID() << FUNID() << ": found extension: (" << cmd << ") "
<< MessageTypeStr(UMSG_EXT, cmd));
size_t bytelen = blocklen * sizeof(uint32_t);
@@ -2662,7 +2876,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
m_RejectReason = SRT_REJ_IPE;
// Something went wrong.
HLOGC(cnlog.Debug,
- log << CONID() << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned "
+ log << CONID() << FUNID() << ": IPE/EPE KMREQ processing failed - returned "
<< res);
return false;
}
@@ -2675,7 +2889,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
m_RejectReason = SRT_REJ_CRYPTO;
LOGC(cnlog.Error,
log << CONID()
- << "interpretSrtHandshake: KMREQ result: Bad crypto mode - rejecting");
+ << FUNID() << ": KMREQ result: Bad crypto mode - rejecting");
return false;
}
#endif
@@ -2694,7 +2908,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
}
LOGC(cnlog.Error,
log << CONID()
- << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption");
+ << FUNID() << ": KMREQ result abnornal - rejecting per enforced encryption");
return false;
}
}
@@ -2702,7 +2916,12 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
}
else if (cmd == SRT_CMD_KMRSP)
{
- int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion);
+ // Normally this is a handshake, but this one can be also called through
+ // the in-connected dispatcher and processCtrlHS(). The is_handshake can be
+ // still potentially set back to true inside when the KMX was detected as
+ // not done for the sake of HSv4.
+ bool is_handshake = m_parent->m_Status != SRTS_CONNECTED;
+ int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, m_uPeerSrtVersion, is_handshake);
if (m_config.bEnforcedEnc && res == -1)
{
if (m_pCryptoControl->m_SndKmState == SRT_KM_S_BADSECRET)
@@ -2727,7 +2946,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
}
else
{
- HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
+ HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
if (NextExtensionBlock((begin), next, (length)))
continue;
}
@@ -2769,7 +2988,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG))
{
- HLOGC(cnlog.Debug, log << CONID() << "interpretSrtHandshake: extracting various CONFIG extensions");
+ HLOGC(cnlog.Debug, log << CONID() << FUNID() << ": extracting various CONFIG extensions");
uint32_t *begin = p;
uint32_t *next = 0;
@@ -2781,7 +3000,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
HLOGC(cnlog.Debug,
- log << CONID() << "interpretSrtHandshake: found extension: (" << cmd << ") "
+ log << CONID() << FUNID() << ": found extension: (" << cmd << ") "
<< MessageTypeStr(UMSG_EXT, cmd));
const size_t bytelen = blocklen * sizeof(uint32_t);
@@ -2790,7 +3009,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH)
{
LOGC(cnlog.Error,
- log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > "
+ log << CONID() << FUNID() << ": STREAMID length " << bytelen << " is 0 or > "
<< +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING");
return false;
}
@@ -2840,7 +3059,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (!bytelen || bytelen > CSrtConfig::MAX_CONG_LENGTH)
{
LOGC(cnlog.Error,
- log << CONID() << "interpretSrtHandshake: CONGESTION-control type length " << bytelen
+ log << CONID() << FUNID() << ": CONGESTION-control type length " << bytelen
<< " is 0 or > " << +CSrtConfig::MAX_CONG_LENGTH << " - PROTOCOL ERROR, REJECTING");
return false;
}
@@ -2883,7 +3102,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
if (!bytelen || bytelen > CSrtConfig::MAX_PFILTER_LENGTH)
{
LOGC(cnlog.Error,
- log << CONID() << "interpretSrtHandshake: packet-filter type length " << bytelen
+ log << CONID() << FUNID() << ": packet-filter type length " << bytelen
<< " is 0 or > " << +CSrtConfig::MAX_PFILTER_LENGTH << " - PROTOCOL ERROR, REJECTING");
return false;
}
@@ -2925,9 +3144,10 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
LOGC(cnlog.Error, log << CONID() << "PEER'S GROUP wrong size: " << (bytelen/GRPD_FIELD_SIZE));
return false;
}
- size_t groupdata_size = bytelen / GRPD_FIELD_SIZE;
+ size_t used_bytelen = std::min(bytelen, sizeof(groupdata));
+ size_t groupdata_size = used_bytelen / GRPD_FIELD_SIZE;
- memcpy(groupdata, begin+1, bytelen);
+ memcpy(groupdata, begin+1, used_bytelen);
if (!interpretGroup(groupdata, groupdata_size, hsreq_type_cmd) )
{
// m_RejectReason handled inside interpretGroup().
@@ -2948,7 +3168,7 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
{
// Found some block that is not interesting here. Skip this and get the next one.
HLOGC(cnlog.Debug,
- log << CONID() << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
+ log << CONID() << FUNID() << ": ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
}
if (!NextExtensionBlock((begin), next, (length)))
@@ -2993,6 +3213,34 @@ bool srt::CUDT::interpretSrtHandshake(const CHandShake& hs,
return false;
}
+ // Check if the filter was configured on agent,
+ // but the peer responded with no filter.
+ if (!m_config.sPacketFilterConfig.empty() && !have_filter)
+ {
+ if (m_SrtHsSide == HSD_INITIATOR)
+ {
+ // IMPORTANT:
+ // If agent didn't set the packet filter in the configuration, the peer
+ // could have responded with its own configuration and enforce the packet filter
+ // setting here locally. In this case the sPacketFilterConfig will not be empty
+ // at the moment, even if it was empty initially.
+
+ // The situation caught here is that the configuration for packet filter was set,
+ // but the peer responded as if it didn't understand it, or simply didn't accept it.
+ // In such a situation the configuration should be rejected because if the
+ // peer doesn't support packet filter, the caller side should not have set it.
+ m_RejectReason = SRT_REJ_FILTER;
+ LOGC(cnlog.Error, log << CONID()
+ << "HS EXT: Agent has configured packetfilter, but peer didn't respond.");
+ return false;
+ }
+
+ // Responder: we are the listener that has configured packetfilter,
+ // but the caller didn't request packetfilter.
+ m_config.sPacketFilterConfig.set("");
+ LOGC(cnlog.Warn, log << CONID() << "HS EXT: Agent has configured packetfilter, but peer didn't request it");
+ }
+
#if ENABLE_BONDING
// m_GroupOf and locking info: NULL check won't hurt here. If the group
// was deleted in the meantime, it will be found out later anyway and result with error.
@@ -3168,10 +3416,19 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A
return false;
}
- ScopedLock guard_group_existence (uglobal().m_GlobControlLock);
-
if (m_SrtHsSide == HSD_INITIATOR)
{
+ // Here we'll be only using a pre-existing group, so shared is enough.
+ SharedLock guard_group_existence (uglobal().m_GlobControlLock);
+
+ // Recheck broken flags after acquisition
+ if (m_bClosing || m_bBroken)
+ {
+ m_RejectReason = SRT_REJ_CLOSE;
+ LOGC(cnlog.Error, log << CONID() << "interpretGroup: closure during handshake, interrupting");
+ return false;
+ }
+
// This is a connection initiator that has requested the peer to make a
// mirror group and join it, then respond its mirror group id. The
// `grpid` variable contains this group ID; map this as your peer
@@ -3190,64 +3447,81 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A
return false;
}
- // Group existence is guarded, so we can now lock the group as well.
- ScopedLock gl(*pg->exp_groupLock());
-
- // Now we know the group exists, but it might still be closed
- if (pg->closing())
{
- LOGC(cnlog.Error, log << CONID() << "HS/RSP: group was closed in the process, can't continue connecting");
- m_RejectReason = SRT_REJ_IPE;
- return false;
- }
+ // XXX Consider moving this to another function to avoid
+ // exposing group lock.
- SRTSOCKET peer = pg->peerid();
- if (peer == -1)
- {
- // This is the first connection within this group, so this group
- // has just been informed about the peer membership. Accept it.
- pg->set_peerid(grpid);
- HLOGC(cnlog.Debug,
- log << CONID() << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid()
- << ", copying characteristic data");
+ // Group existence is guarded, so we can now lock the group as well.
+ ScopedLock gl(*pg->exp_groupLock());
- // The call to syncWithSocket is copying
- // some interesting data from the first connected
- // socket. This should be only done for the first successful connection.
- pg->syncWithSocket(*this, HSD_INITIATOR);
- }
- // Otherwise the peer id must be the same as existing, otherwise
- // this group is considered already bound to another peer group.
- // (Note that the peer group is peer-specific, and peer id numbers
- // may repeat among sockets connected to groups established on
- // different peers).
- else if (peer != grpid)
- {
- LOGC(cnlog.Error,
- log << CONID() << "IPE: HS/RSP: group membership responded for peer $" << grpid
- << " but the current socket's group $" << pg->id() << " has already a peer $" << peer);
- m_RejectReason = SRT_REJ_GROUP;
- return false;
- }
- else
- {
- HLOGC(cnlog.Debug,
- log << CONID() << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $"
- << pg->peerid());
+ // Now we know the group exists, but it might still be closed
+ if (pg->closing())
+ {
+ LOGC(cnlog.Error, log << CONID() << "HS/RSP: group was closed in the process, can't continue connecting");
+ m_RejectReason = SRT_REJ_IPE;
+ return false;
+ }
+
+ SRTSOCKET peer = pg->peerid();
+ if (peer == SRT_INVALID_SOCK)
+ {
+ // This is the first connection within this group, so this group
+ // has just been informed about the peer membership. Accept it.
+ pg->set_peerid(grpid);
+ HLOGC(cnlog.Debug,
+ log << CONID() << "HS/RSP: group $" << pg->id() << " -> peer $" << pg->peerid()
+ << ", copying characteristic data");
+
+ // The call to syncWithSocket is copying
+ // some interesting data from the first connected
+ // socket. This should be only done for the first successful connection.
+ pg->syncWithSocket(*this, HSD_INITIATOR);
+ }
+ // Otherwise the peer id must be the same as existing, otherwise
+ // this group is considered already bound to another peer group.
+ // (Note that the peer group is peer-specific, and peer id numbers
+ // may repeat among sockets connected to groups established on
+ // different peers).
+ else if (peer != grpid)
+ {
+ LOGC(cnlog.Error,
+ log << CONID() << "IPE: HS/RSP: group membership responded for peer $" << grpid
+ << " but the current socket's group $" << pg->id() << " has already a peer $" << peer);
+ m_RejectReason = SRT_REJ_GROUP;
+ return false;
+ }
+ else
+ {
+ HLOGC(cnlog.Debug,
+ log << CONID() << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $"
+ << pg->peerid());
+ }
}
+ m_parent->m_GroupOf->debugGroup();
}
else
{
+ // Here we'll be potentially creating a new group, hence exclusive is needed.
+ ExclusiveLock guard_group_existence (uglobal().m_GlobControlLock);
+
+ // Recheck broken flags after acquisition
+ if (m_bClosing || m_bBroken)
+ {
+ m_RejectReason = SRT_REJ_CLOSE;
+ LOGC(cnlog.Error, log << CONID() << "interpretGroup: closure during handshake, interrupting");
+ return false;
+ }
+
// This is a connection responder that has been requested to make a
// mirror group and join it. Later on, the HS response will be sent
// and its group ID will be added to the HS extensions as mirror group
// ID to the peer.
SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags);
- if (!lgid)
+ if (lgid == 0)
return true; // already done
- if (lgid == -1)
+ if (lgid == SRT_INVALID_SOCK)
{
// NOTE: This error currently isn't reported by makeMePeerOf,
// so this is left to handle a possible error introduced in future.
@@ -3268,10 +3542,9 @@ bool srt::CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_A
f->weight = link_weight;
f->agent = m_parent->m_SelfAddr;
f->peer = m_PeerAddr;
+ m_parent->m_GroupOf->debugGroup();
}
- m_parent->m_GroupOf->debugGroup();
-
// That's all. For specific things concerning group
// types, this will be later.
return true;
@@ -3581,7 +3854,8 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
// Inform the server my configurations.
CPacket reqpkt;
reqpkt.setControl(UMSG_HANDSHAKE);
- reqpkt.allocate(m_iMaxSRTPayloadSize);
+ size_t hs_size = controlPayloadSize(serv_addr.family());
+ reqpkt.allocate(hs_size);
// XXX NOTE: Now the memory for the payload part is allocated automatically,
// and such allocated memory is also automatically deallocated in the
// destructor. If you use CPacket::allocate, remember that you must not:
@@ -3596,7 +3870,6 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
// ID = 0, connection request
reqpkt.set_id(0);
- size_t hs_size = m_iMaxSRTPayloadSize;
m_ConnReq.store_to((reqpkt.m_pcData), (hs_size));
// Note that CPacket::allocate() sets also the size
@@ -3653,7 +3926,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
// next incoming packet.
CPacket response;
response.setControl(UMSG_HANDSHAKE);
- response.allocate(m_iMaxSRTPayloadSize);
+ response.allocate(controlPayloadSize(serv_addr.family()));
CUDTException e;
EConnectStatus cst = CONN_CONTINUE;
@@ -3661,7 +3934,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
// We can't record this address yet until the cookie-confirmation is done, for safety reasons.
sockaddr_any use_source_adr(serv_addr.family());
- while (!m_bClosing)
+ while (!m_bClosing && !m_bBreaking)
{
const steady_clock::time_point local_tnow = steady_clock::now();
const steady_clock::duration tdiff = local_tnow - m_tsLastReqTime.load();
@@ -3705,7 +3978,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
}
cst = CONN_CONTINUE;
- response.setLength(m_iMaxSRTPayloadSize);
+ response.setLength(controlPayloadSize(serv_addr.family()));
if (m_pRcvQueue->recvfrom(m_SocketID, (response)) > 0)
{
use_source_adr = response.udpDestAddr();
@@ -3758,7 +4031,7 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
m_RejectReason = SRT_REJ_ROGUE;
// rejection or erroneous code.
- reqpkt.setLength(m_iMaxSRTPayloadSize);
+ reqpkt.setLength(controlPayloadSize(serv_addr.family()));
reqpkt.setControl(UMSG_HANDSHAKE);
sendRendezvousRejection(serv_addr, (reqpkt));
}
@@ -3786,12 +4059,12 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
// Now serialize the handshake again to the existing buffer so that it's
// then sent later in this loop.
- // First, set the size back to the original size, m_iMaxSRTPayloadSize because
+ // First, set the size back to the original size, controlPayloadSize() because
// this is the size of the originally allocated space. It might have been
// shrunk by serializing the INDUCTION handshake (which was required before
// sending this packet to the output queue) and therefore be too
// small to store the CONCLUSION handshake (with HSv5 extensions).
- reqpkt.setLength(m_iMaxSRTPayloadSize);
+ reqpkt.setLength(controlPayloadSize(serv_addr.family()));
HLOGC(cnlog.Debug,
log << CONID() << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength());
@@ -3859,14 +4132,14 @@ void srt::CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
if (e.getErrorCode() == 0)
{
- if (m_bClosing) // if the socket is closed before connection...
+ if (m_bClosing || m_bBreaking) // if the socket is closed before connection...
e = CUDTException(MJ_SETUP, MN_CLOSED, 0);
else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected
{
m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType);
e = CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
- else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check
+ else if ((!m_config.bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // security check
e = CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
@@ -3921,7 +4194,7 @@ bool srt::CUDT::processAsyncConnectRequest(EReadStatus rst,
CPacket reqpkt;
reqpkt.setControl(UMSG_HANDSHAKE);
- reqpkt.allocate(m_iMaxSRTPayloadSize);
+ reqpkt.allocate(controlPayloadSize(serv_addr.family()));
const steady_clock::time_point now = steady_clock::now();
setPacketTS(reqpkt, now);
@@ -4031,65 +4304,69 @@ void srt::CUDT::sendRendezvousRejection(const sockaddr_any& serv_addr, CPacket&
m_pSndQueue->sendto(serv_addr, r_rsppkt, m_SourceAddr);
}
-void srt::CUDT::cookieContest()
+enum HandshakeSide srt::CUDT::compareCookies(int32_t req, int32_t res)
{
- if (m_SrtHsSide != HSD_DRAW)
- return;
-
- LOGC(cnlog.Debug,
- log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie);
-
- // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer.
- // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request.
- if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0)
+ if (req < res)
{
- // Note that it's virtually impossible that Agent's cookie is not ready, this
- // shall be considered IPE.
- // Not all cookies are ready, don't start the contest.
- return;
+ LOGC(gglog.Note, log << hex << setfill('0') << uppercase
+ << "REQ: " << setw(8) << req
+ << " RES: " << setw(8) << res << " - result: RESPONDER");
+ return HSD_RESPONDER;
+ }
+ else if (req > res)
+ {
+ LOGC(gglog.Note, log << hex << setfill('0') << uppercase
+ << "REQ: " << setw(8) << req
+ << " RES: " << setw(8) << res << " - result: INITIATOR");
+ return HSD_INITIATOR;
}
+ return HSD_DRAW;
+}
- // INITIATOR/RESPONDER role is resolved by COOKIE CONTEST.
- //
- // The cookie contest must be repeated every time because it
- // may change the state at some point.
- //
- // In SRT v1.4.3 and prior the below subtraction was performed in 32-bit arithmetic.
- // The result of subtraction can overflow 32-bits.
- // Example
- // m_ConnReq.m_iCookie = -1480577720;
- // m_ConnRes.m_iCookie = 811599203;
- // int64_t llBetterCookie = -1480577720 - 811599203 = -2292176923 (FFFF FFFF 7760 27E5);
- // int32_t iBetterCookie = 2002790373 (7760 27E5);
- //
- // Now 64-bit arithmetic is used to calculate the actual result of subtraction.
- // The 31-st bit is then checked to check if the resulting is negative in 32-bit aritmetics.
- // This way the old contest behavior is preserved, and potential compiler optimisations are avoided.
- const int64_t contest = int64_t(m_ConnReq.m_iCookie) - int64_t(m_ConnRes.m_iCookie);
+enum HandshakeSide srt::CUDT::backwardCompatibleCookieContest(int32_t req, int32_t res)
+{
+ const int64_t xreq = int64_t(req);
+ const int64_t xres = int64_t(res);
+ const int64_t contest = xreq - xres;
+
+ LOGC(cnlog.Debug, log << "cookieContest: agent=" << req
+ << " peer=" << res
+ << hex << uppercase << setfill('0') // PERSISTENT flags
+ << " X64: "<< setw(16) << xreq
+ << " vs. " << setw(16) << xres
+ << " DIFF: " << setw(16) << contest);
if ((contest & 0xFFFFFFFF) == 0)
{
- // DRAW! The only way to continue would be to force the
- // cookies to be regenerated and to start over. But it's
- // not worth a shot - this is an extremely rare case.
- // This can simply do reject so that it can be started again.
+ return HSD_DRAW;
+ }
+ if (contest & 0x80000000)
+ {
+ const int64_t revert = xres - xreq;
+ if (revert & 0x80000000 && req > res)
+ return HSD_INITIATOR;
+ return HSD_RESPONDER;
+ }
- // Pretend then that the cookie contest wasn't done so that
- // it's done again. Cookies are baked every time anew, however
- // the successful initial contest remains valid no matter how
- // cookies will change.
+ return HSD_INITIATOR;
+}
- m_SrtHsSide = HSD_DRAW;
+void srt::CUDT::cookieContest()
+{
+ if (m_SrtHsSide != HSD_DRAW)
return;
- }
- if (contest & 0x80000000)
+ // Here m_ConnReq.m_iCookie is a local cookie value sent in connection request to the peer.
+ // m_ConnRes.m_iCookie is a cookie value sent by the peer in its connection request.
+ if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0)
{
- m_SrtHsSide = HSD_RESPONDER;
+ LOGC(cnlog.Debug, log << CONID() << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie
+ << " - ERROR: zero not allowed!");
+
return;
}
+ m_SrtHsSide = backwardCompatibleCookieContest(m_ConnReq.m_iCookie, m_ConnRes.m_iCookie);
- m_SrtHsSide = HSD_INITIATOR;
}
// This function should complete the data for KMX needed for an out-of-band
@@ -4235,7 +4512,7 @@ EConnectStatus srt::CUDT::processRendezvous(
m_ConnReq.m_iReqType = rsp_type;
m_ConnReq.m_extension = needs_extension;
- // This must be done before prepareConnectionObjects(), because it sets ISN and m_iMaxSRTPayloadSize needed to create buffers.
+ // This must be done before prepareConnectionObjects(), because it sets ISN needed to create buffers.
if (!applyResponseSettings(pResponse))
{
LOGC(cnlog.Error, log << CONID() << "processRendezvous: peer settings rejected");
@@ -4299,7 +4576,7 @@ EConnectStatus srt::CUDT::processRendezvous(
log << CONID()
<< "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize);
- w_reqpkt.setLength(m_iMaxSRTPayloadSize);
+ w_reqpkt.setLength(controlPayloadSize(serv_addr.family()));
if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP,
kmdata, kmdatasize,
(w_reqpkt), (m_ConnReq)))
@@ -4331,7 +4608,7 @@ EConnectStatus srt::CUDT::processRendezvous(
HLOGC(
cnlog.Debug,
log << CONID()
- << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)");
+ << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on existing data)");
}
else
{
@@ -4376,7 +4653,7 @@ EConnectStatus srt::CUDT::processRendezvous(
// serialization.
m_ConnReq.m_extension = needs_extension;
- w_reqpkt.setLength(m_iMaxSRTPayloadSize);
+ w_reqpkt.setLength(controlPayloadSize(serv_addr.family()));
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
int cst = postConnect(pResponse, true, 0);
@@ -4446,7 +4723,7 @@ EConnectStatus srt::CUDT::processRendezvous(
// the request time must be updated so that the next handshake can be sent out immediately
HLOGC(cnlog.Debug,
log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType)
- << " REQ-TIME: LOW to send immediately, consider yourself conencted");
+ << " REQ-TIME: LOW to send immediately, consider yourself connected");
m_tsLastReqTime = steady_clock::time_point();
}
else
@@ -4719,6 +4996,24 @@ EConnectStatus srt::CUDT::processConnectResponse(const CPacket& response, CUDTEx
return postConnect(&response, false, eout);
}
+static size_t MinimumMSS(int family)
+{
+ const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE;
+ const size_t full_hdr_size_i6 = CPacket::UDP_HDR_SIZE_IPv6 + CPacket::HDR_SIZE;
+
+ size_t min_mss_size = 4; // initial: required for passing any data
+ if (family == AF_INET6)
+ {
+ min_mss_size += full_hdr_size_i6;
+ }
+ else
+ {
+ min_mss_size += full_hdr_size;
+ }
+
+ return min_mss_size;
+}
+
bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) ATR_NOEXCEPT
{
if (!m_ConnRes.valid())
@@ -4732,12 +5027,10 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT
m_config.iMSS = m_ConnRes.m_iMSS;
const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE;
- m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size;
- HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
+ m_iMaxDataPayloadSize = m_config.iMSS - full_hdr_size;
+ HLOGC(cnlog.Debug, log << CONID() << "applyResponseSettings: PAYLOAD SIZE: " << m_iMaxDataPayloadSize);
m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize;
- const int udpsize = m_config.iMSS - CPacket::UDP_HDR_SIZE;
- m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
m_iPeerISN = m_ConnRes.m_iISN;
setInitialRcvSeq(m_iPeerISN);
@@ -4749,7 +5042,7 @@ bool srt::CUDT::applyResponseSettings(const CPacket* pHspkt /*[[nullable]]*/) AT
m_SourceAddr = pHspkt->udpDestAddr();
HLOGC(cnlog.Debug,
- log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize
+ log << CONID() << "applyResponseSettings: HANDSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxDataPayloadSize
<< " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " peer-ISN=" << m_ConnRes.m_iISN
<< " local-ISN=" << m_iISN
<< " peerID=" << m_ConnRes.m_iID
@@ -4823,7 +5116,7 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous,
{
#if ENABLE_BONDING
- ScopedLock cl (uglobal().m_GlobControlLock);
+ SharedLock cl (uglobal().m_GlobControlLock);
CUDTGroup* g = m_parent->m_GroupOf;
if (g)
{
@@ -4930,10 +5223,11 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous,
//int token = -1;
#if ENABLE_BONDING
{
- ScopedLock cl (uglobal().m_GlobControlLock);
+ SharedLock cl (uglobal().m_GlobControlLock);
CUDTGroup* g = m_parent->m_GroupOf;
if (g)
{
+ ScopedLock gl (*g->exp_groupLock());
// XXX this might require another check of group type.
// For redundancy group, at least, update the status in the group.
@@ -4959,6 +5253,11 @@ EConnectStatus srt::CUDT::postConnect(const CPacket* pResponse, bool rendezvous,
s->m_Status = SRTS_CONNECTED;
// acknowledde any waiting epolls to write
+ // This must be done AFTER the group member status is upgraded to IDLE because
+ // this state change will trigger the waiting function in blocking-mode groupConnect
+ // and this may be immediately followed by exit from connect and start sending function,
+ // which must see this very link already as IDLE, not PENDING, which will make this
+ // link unable to be used and therefore the sending call would fail.
uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_CONNECT, true);
CGlobEvent::triggerEvent();
@@ -5549,6 +5848,12 @@ void * srt::CUDT::tsbpd(void* param)
gkeeper.group->updateLatestRcv(self->m_parent);
}
}
+
+ // After re-acquisition of the m_RecvLock, re-check the closing flag
+ if (self->m_bClosing)
+ {
+ break;
+ }
#endif
CGlobEvent::triggerEvent();
tsNextDelivery = steady_clock::time_point(); // Ready to read, nothing to wait for.
@@ -5574,6 +5879,7 @@ void * srt::CUDT::tsbpd(void* param)
THREAD_PAUSED();
bWokeUpOnSignal = tsbpd_cc.wait_until(tsNextDelivery);
THREAD_RESUMED();
+ HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP on " << (bWokeUpOnSignal? "SIGNAL" : "TIMEOUIT") << "!!!");
}
else
{
@@ -5715,16 +6021,17 @@ bool srt::CUDT::prepareBuffers(CUDTException* eout)
try
{
- // CryptoControl has to be initialized and in case of RESPONDER the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced.
+ // CryptoControl has to be initialized and in case of RESPONDER
+ // the KM REQ must be processed (interpretSrtHandshake(..)) for the crypto mode to be deduced.
const int authtag = getAuthTagSize();
- SRT_ASSERT(m_iMaxSRTPayloadSize != 0);
+ SRT_ASSERT(m_iMaxDataPayloadSize != 0);
- HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << m_iMaxSRTPayloadSize
+ HLOGC(rslog.Debug, log << CONID() << "Creating buffers: snd-plsize=" << m_iMaxDataPayloadSize
<< " snd-bufsize=" << 32
<< " authtag=" << authtag);
- m_pSndBuffer = new CSndBuffer(AF_INET, 32, m_iMaxSRTPayloadSize, authtag);
+ m_pSndBuffer = new CSndBuffer(AF_INET, 32, m_iMaxDataPayloadSize, authtag);
SRT_ASSERT(m_iPeerISN != -1);
m_pRcvBuffer = new srt::CRcvBuffer(m_iPeerISN, m_config.iRcvBufSize, m_pRcvQueue->m_pUnitQueue, m_config.bMessageAPI);
// After introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice a space.
@@ -5769,13 +6076,23 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any&
m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS
+ const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE;
// Uses the smaller MSS between the peers
- m_config.iMSS = std::min(m_config.iMSS, w_hs.m_iMSS);
+ size_t mss_size = std::min(m_config.iMSS, w_hs.m_iMSS);
+ if (mss_size < MinimumMSS(peer.family()))
+ {
+ HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: peer's MSS=" << w_hs.m_iMSS
+ << " is too small, can't accept the connection");
+ m_RejectReason = SRT_REJ_ROGUE;
+ w_hs.m_iReqType = URQFailure(m_RejectReason);
+ throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
+ }
- const size_t full_hdr_size = CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE;
- m_iMaxSRTPayloadSize = m_config.iMSS - full_hdr_size;
+ m_config.iMSS = mss_size;
- HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
+ m_iMaxDataPayloadSize = m_config.iMSS - full_hdr_size;
+
+ HLOGC(cnlog.Debug, log << CONID() << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxDataPayloadSize);
// exchange info for maximum flow window size
m_iFlowWindowSize = w_hs.m_iFlightFlagSize;
@@ -5798,7 +6115,6 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any&
rewriteHandshakeData(peer, (w_hs));
-
// Prepare all structures
if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0))
{
@@ -5934,7 +6250,7 @@ void srt::CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any&
// TODO: Here create CONCLUSION RESPONSE with:
// - just the UDT handshake, if HS_VERSION_UDT4,
// - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP.
- size_t size = m_iMaxSRTPayloadSize;
+ size_t size = controlPayloadSize(peer.family());
// Allocate the maximum possible memory for an SRT payload.
// This is a maximum you can send once.
CPacket rsppkt;
@@ -6062,7 +6378,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC()
// XXX Not sure about that. May happen that AGENT wants
// tsbpd mode, but PEER doesn't, even in bidirectional mode.
- // This way, the reception side should get precedense.
+ // This way, the reception side should get precedence.
// if (bidirectional || m_config.bDataSender || m_bTwoWayData)
// m_bPeerTsbPd = m_bTSBPD;
@@ -6122,6 +6438,13 @@ SRT_REJECT_REASON srt::CUDT::setupCC()
m_tsLastRspAckTime = currtime;
m_tsLastSndTime.store(currtime);
+#ifdef ENABLE_RATE_MEASUREMENT
+ // XXX NOTE: use IPv4 or IPv6 as applicable!
+ HLOGC(bslog.Debug, log << CONID() << "RATE-MEASUREMENT: initializing time TS=" << FormatTime(currtime));
+ m_SndRegularMeasurement.init(currtime, CPacket::UDP_HDR_SIZE);
+ m_SndRexmitMeasurement.init(currtime, CPacket::UDP_HDR_SIZE);
+#endif
+
HLOGC(rslog.Debug,
log << CONID() << "setupCC: setting parameters: mss=" << m_config.iMSS << " maxCWNDSize/FlowWindowSize="
<< m_iFlowWindowSize << " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)"
@@ -6129,7 +6452,7 @@ SRT_REJECT_REASON srt::CUDT::setupCC()
if (!updateCC(TEV_INIT, EventVariant(TEV_INIT_RESET)))
{
- LOGC(rslog.Error, log << CONID() << "setupCC: IPE: resrouces not yet initialized!");
+ LOGC(rslog.Error, log << CONID() << "setupCC: IPE: resources not yet initialized!");
return SRT_REJ_IPE;
}
return SRT_REJ_UNKNOWN;
@@ -6344,7 +6667,7 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT
// Inform the threads handler to stop.
m_bClosing = true;
- HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock");
+ HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE (closing=true). Acquiring connection lock");
ScopedLock connectguard(m_ConnectionLock);
@@ -6356,7 +6679,11 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT
if (m_bListening)
{
m_bListening = false;
- m_pRcvQueue->removeListener(this);
+ bool removed SRT_ATR_UNUSED = m_pRcvQueue->removeListener(this);
+ if (!removed)
+ {
+ LOGC(smlog.Error, log << CONID() << "CLOSING: IPE: listening=true but listener removal failed!");
+ }
}
else if (m_bConnecting)
{
@@ -6410,6 +6737,12 @@ bool srt::CUDT::closeInternal() ATR_NOEXCEPT
return true;
}
+bool srt::CUDT::closeAtFork() ATR_NOEXCEPT
+{
+ m_bShutdown = true;
+ return closeInternal();
+}
+
int srt::CUDT::receiveBuffer(char *data, int len)
{
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false))
@@ -6551,7 +6884,7 @@ int srt::CUDT::sndDropTooLate()
// high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms)
// Minimum value must accommodate an I-Frame (~8 x average frame size)
- // >>need picture rate or app to set min treshold
+ // >>need picture rate or app to set min threshold
// >>using 1 sec for worse case 1 frame using all bit budget.
// picture rate would be useful in auto SRT setting for min latency
// XXX Make SRT_TLPKTDROP_MINTHRESHOLD_MS option-configurable
@@ -6697,11 +7030,11 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl)
// out a message of a length that exceeds the total size of the sending
// buffer (configurable by SRTO_SNDBUF).
- if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxSRTPayloadSize))
+ if (m_config.bMessageAPI && len > int(m_config.iSndBufSize * m_iMaxDataPayloadSize))
{
LOGC(aslog.Error,
log << CONID() << "Message length (" << len << ") exceeds the size of sending buffer: "
- << (m_config.iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed.");
+ << (m_config.iSndBufSize * m_iMaxDataPayloadSize) << ". Use SRTO_SNDBUF if needed.");
throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0);
}
@@ -6785,7 +7118,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl)
/*
* The code below is to return ETIMEOUT when blocking mode could not get free buffer in time.
- * If no free buffer available in non-blocking mode, we alredy returned. If buffer available,
+ * If no free buffer available in non-blocking mode, we already returned. If buffer available,
* we test twice if this code is outside the else section.
* This fix move it in the else (blocking-mode) section
*/
@@ -6832,7 +7165,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl)
// Just return how many bytes were actually scheduled for writing.
// XXX May be reasonable to add a flag that requires that the function
// not return until the buffer is sent completely.
- size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize);
+ size = min(len, sndBuffersLeft() * m_iMaxDataPayloadSize);
}
{
@@ -6872,7 +7205,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl)
w_mctrl.pktseq = seqno;
// Now seqno is the sequence to which it was scheduled
- // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurrate.
+ // XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurate.
HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:"
<< (w_mctrl.srctime ? FormatTime(ts_srctime) : "none")
<< " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno
@@ -6907,7 +7240,7 @@ int srt::CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl)
<< " size=" << size << " #" << w_mctrl.msgno << " SCHED %" << orig_seqno
<< "(>> %" << seqno << ") !" << BufferStamp(data, size));
- if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried.
+ if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as required.
{
// write is not available any more
uglobal().m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false);
@@ -7124,7 +7457,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_
// After signaling the tsbpd for ready data, report the bandwidth.
#if ENABLE_HEAVY_LOGGING
- double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxSRTPayloadSize );
+ double bw = Bps2Mbps(int64_t(m_iBandwidth) * m_iMaxDataPayloadSize );
HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth << " buffers per second)");
#endif
}
@@ -7164,7 +7497,7 @@ int srt::CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_
{
// `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be
// only until the time that is now + timeout since the first moment
- // when this started, or sliced-waiting for 1 second, if timtout is
+ // when this started, or sliced-waiting for 1 second, if timeout is
// higher than this.
const steady_clock::time_point exptime = steady_clock::now() + recv_timeout;
@@ -7523,6 +7856,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous)
perf->pktSndLoss = m_stats.sndr.lost.trace.count();
perf->pktRcvLoss = m_stats.rcvr.lost.trace.count();
+ perf->pktRcvLossConfirmed = m_stats.rcvr.lostConfirmed.trace.count();
perf->pktRetrans = m_stats.sndr.sentRetrans.trace.count();
perf->pktRcvRetrans = m_stats.rcvr.recvdRetrans.trace.count();
perf->pktSentACK = m_stats.rcvr.sentAck.trace.count();
@@ -7561,6 +7895,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous)
perf->pktRecvUniqueTotal = m_stats.rcvr.recvdUnique.total.count();
perf->pktSndLossTotal = m_stats.sndr.lost.total.count();
perf->pktRcvLossTotal = m_stats.rcvr.lost.total.count();
+ perf->pktRcvLossConfirmedTotal = m_stats.rcvr.lostConfirmed.total.count();
perf->pktRetransTotal = m_stats.sndr.sentRetrans.total.count();
perf->pktSentACKTotal = m_stats.rcvr.sentAck.total.count();
perf->pktRecvACKTotal = m_stats.sndr.recvdAck.total.count();
@@ -7604,6 +7939,12 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous)
: m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth())
: 0;
+ // Last completed sampling window, maintained by the timer thread. Read
+ // only - independent of @a clear and of the caller's polling cadence, so
+ // repeated reads inside one window all yield the same value.
+ perf->pctSndQuality = m_stats.pctSndQuality;
+ perf->pctRcvQuality = m_stats.pctRcvQuality;
+
if (clear)
{
m_stats.sndr.resetTrace();
@@ -7616,7 +7957,7 @@ void srt::CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous)
const int64_t availbw = m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth.load();
- perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize));
+ perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxDataPayloadSize + pktHdrSize));
if (tryEnterCS(m_ConnectionLock))
{
@@ -7716,7 +8057,7 @@ bool srt::CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg)
// Use the values:
// - if SRTO_MAXBW is >0, use it.
// - if SRTO_MAXBW == 0, use SRTO_INPUTBW + SRTO_OHEADBW
- // - if SRTO_INPUTBW == 0, pass 0 to requst in-buffer sampling
+ // - if SRTO_INPUTBW == 0, pass 0 to request in-buffer sampling
// Bytes/s
const int64_t bw = m_config.llMaxBW != 0 ? m_config.llMaxBW : // When used SRTO_MAXBW
m_config.llInputBW != 0 ? withOverhead(m_config.llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW
@@ -7836,10 +8177,21 @@ void srt::CUDT::destroySynch()
m_RcvTsbPdCond.notify_all();
releaseCond(m_RcvTsbPdCond);
}
+void srt::CUDT::resetAtFork()
+{
+ resetCond(m_SendBlockCond);
+ resetCond(m_RecvDataCond);
+ resetCond(m_RcvTsbPdCond);
+}
void srt::CUDT::releaseSynch()
{
SRT_ASSERT(m_bClosing);
+ if (!m_bClosing)
+ {
+ LOGC(smlog.Error, log << "releaseSynch: IPE: m_bClosing not set to false, TSBPD might hangup!");
+ m_bClosing = true;
+ }
// wake up user calls
CSync::lock_notify_one(m_SendBlockCond, m_SendBlockLock);
@@ -7847,8 +8199,8 @@ void srt::CUDT::releaseSynch()
leaveCS(m_SendLock);
// Awake tsbpd() and srt_recv*(..) threads for them to check m_bClosing.
- CSync::lock_notify_one(m_RecvDataCond, m_RecvLock);
- CSync::lock_notify_one(m_RcvTsbPdCond, m_RecvLock);
+ CSync::lock_notify_all(m_RecvDataCond, m_RecvLock);
+ CSync::lock_notify_all(m_RcvTsbPdCond, m_RecvLock);
// Azquiring m_RcvTsbPdStartupLock protects race in starting
// the tsbpd() thread in CUDT::processData().
@@ -7945,22 +8297,18 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp
// this is periodically NAK report; make sure NAK cannot be sent back too often
// read loss list from the local receiver loss list
- int32_t *data = new int32_t[m_iMaxSRTPayloadSize / 4];
- int losslen;
- m_pRcvLossList->getLossArray(data, losslen, m_iMaxSRTPayloadSize / 4);
-
- if (0 < losslen)
+ FixedArray data (m_iMaxDataPayloadSize / sizeof(int32_t));
+ int losslen = m_pRcvLossList->getLossArray(data);
+ if (losslen > 0)
{
- ctrlpkt.pack(pkttype, NULL, data, losslen * 4);
+ ctrlpkt.pack(pkttype, /*lparam*/ NULL, data.data(), losslen * sizeof(int32_t));
ctrlpkt.set_id(m_PeerID);
- nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr);
+ nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt, m_SourceAddr);
enterCS(m_StatsLock);
m_stats.rcvr.sentNak.count(1);
leaveCS(m_StatsLock);
}
-
- delete[] data;
}
// update next NAK time, which should wait enough time for the retansmission, but not too long
@@ -8004,7 +8352,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp
break;
case UMSG_SHUTDOWN: // 101 - Shutdown
- if (m_PeerID == 0) // Dont't send SHUTDOWN if we don't know peer ID.
+ if (m_PeerID == 0) // Don't send SHUTDOWN if we don't know peer ID.
break;
ctrlpkt.pack(pkttype);
ctrlpkt.set_id(m_PeerID);
@@ -8026,7 +8374,7 @@ void srt::CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rp
break;
- case UMSG_EXT: // 0x7FFF - Resevered for future use
+ case UMSG_EXT: // 0x7FFF - Reserved for future use
break;
default:
@@ -8184,7 +8532,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size)
// can be either never set, already reset, or ever set
// and possibly dangling. The re-check after lock eliminates
// the dangling case.
- ScopedLock glock (uglobal().m_GlobControlLock);
+ SharedLock glock (uglobal().m_GlobControlLock);
// Note that updateLatestRcv will lock m_GroupOf->m_GroupLock,
// but this is an intended order.
@@ -8245,7 +8593,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size)
if (group_read_seq != SRT_SEQNO_NONE && m_parent->m_GroupOf)
{
// See above explanation for double-checking
- ScopedLock glock (uglobal().m_GlobControlLock);
+ SharedLock glock (uglobal().m_GlobControlLock);
if (m_parent->m_GroupOf)
{
@@ -8307,11 +8655,12 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size)
data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed((rcvRate));
data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth();
+ // XXX Likely this can be removed already.
//>>Patch while incompatible (1.0.2) receiver floating around
if (m_uPeerSrtVersion == SrtVersion(1, 0, 2))
{
data[ACKD_RCVRATE] = rcvRate; // bytes/sec
- data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec
+ data[ACKD_XMRATE_VER102_ONLY] = data[ACKD_BANDWIDTH] * m_iMaxDataPayloadSize; // bytes/sec
ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102_ONLY;
}
else if (m_uPeerSrtVersion >= SrtVersion(1, 0, 3))
@@ -8329,6 +8678,7 @@ int srt::CUDT::sendCtrlAck(CPacket& ctrlpkt, int size)
{
ctrlpkt.pack(UMSG_ACK, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL);
}
+ bufflock.unlock();
ctrlpkt.set_id(m_PeerID);
setPacketTS(ctrlpkt, steady_clock::now());
@@ -8401,7 +8751,7 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno)
{
// m_RecvAckLock is ordered AFTER m_GlobControlLock, so this can only
// be done now that m_RecvAckLock is unlocked.
- ScopedLock glock (uglobal().m_GlobControlLock);
+ SharedLock glock (uglobal().m_GlobControlLock);
if (m_parent->m_GroupOf)
{
HLOGC(inlog.Debug, log << CONID() << "ACK: acking group sender buffer for #" << msgno_at_last_acked_seq);
@@ -8437,9 +8787,24 @@ void srt::CUDT::updateSndLossListOnACK(int32_t ackdata_seqno)
leaveCS(m_StatsLock);
}
-void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime)
+bool srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime)
{
+ // Valid ACK payloads are either LITE (just an ack seqno) or at least SMALL
+ // (RCVLASTACK + RTT + RTTVAR + BUFFERLEFT = 16 B). Anything else would OOB-read.
+ const size_t pktlen = ctrlpkt.getLength();
+ const bool isLiteAck = pktlen == size_t(SEND_LITE_ACK);
+ if (!isLiteAck && pktlen < ACKD_TOTAL_SIZE_SMALL * ACKD_FIELD_SIZE)
+ {
+ LOGC(inlog.Warn, log << CONID() << "ACK: EPE: wrong payload size=" << pktlen
+ << " expected 4 or at least SMALL ("
+ << (ACKD_TOTAL_SIZE_SMALL * ACKD_FIELD_SIZE)
+ << ") - rejecting");
+ return false;
+ }
+
const int32_t* ackdata = (const int32_t*)ctrlpkt.m_pcData;
+
+ // Note: minimum of one 4-byte field is granted before the call.
const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK];
// Check the value of ACK in case when it was some rogue peer
@@ -8451,10 +8816,9 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
// This check MUST BE DONE before making any operation on this number.
LOGC(inlog.Error, log << CONID() << "ACK: IPE/EPE: received invalid ACK value: " << ackdata_seqno
<< " " << std::hex << ackdata_seqno << " (IGNORED)");
- return;
+ return false;
}
- const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK;
HLOGC(inlog.Debug,
log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck
<< "]" << (isLiteAck ? "[LITE]" : "[FULL]"));
@@ -8473,7 +8837,16 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
m_tsLastRspAckTime = currtime;
m_iReXmitCount = 1; // Reset re-transmit count since last ACK
}
- return;
+ return true;
+ }
+
+ const size_t acksize = pktlen / ACKD_FIELD_SIZE; // ACTUAL VALUE
+
+ // Check minimum size acceptable. If less, reject it.
+ if (acksize < ACKD_TOTAL_SIZE_SMALL)
+ {
+ LOGC(inlog.Error, log << CONID() << "EPE: ACK msg received with too small size: " << pktlen);
+ return false;
}
// Decide to send ACKACK or not
@@ -8499,39 +8872,44 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
// Protect packet retransmission
{
- ScopedLock ack_lock(m_RecvAckLock);
+ UniqueLock ack_lock(m_RecvAckLock);
// Check the validation of the ack
if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0)
{
+ ack_lock.unlock();
+
// this should not happen: attack or bug
LOGC(gglog.Error,
log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current "
<< m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!");
m_bBroken = true;
m_iBrokenCounter = 0;
- return;
- }
- if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
- {
- const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow);
- const bool bWasStuck = cwnd1<= getFlightSpan();
- // Update Flow Window Size, must update before and together with m_iSndLastAck
- m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT];
- m_iSndLastAck = ackdata_seqno;
- m_tsLastRspAckTime = currtime;
- m_iReXmitCount = 1; // Reset re-transmit count since last ACK
+ updateBrokenConnection();
+ completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS!
+ return false;
+ }
- const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow);
- if (bWasStuck && cwnd > getFlightSpan())
+ if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
{
- m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
- HLOGC(gglog.Debug,
- log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize
- << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno);
+ const int cwnd1 = std::min(m_iFlowWindowSize, m_iCongestionWindow);
+ const bool bWasStuck = cwnd1<= getFlightSpan();
+ // Update Flow Window Size, must update before and together with m_iSndLastAck
+ m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT];
+ m_iSndLastAck = ackdata_seqno;
+ m_tsLastRspAckTime = currtime;
+ m_iReXmitCount = 1; // Reset re-transmit count since last ACK
+
+ const int cwnd = std::min(m_iFlowWindowSize, m_iCongestionWindow);
+ if (bWasStuck && cwnd > getFlightSpan())
+ {
+ m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
+ HLOGC(gglog.Debug,
+ log << CONID() << "processCtrlAck: could reschedule SND. iFlowWindowSize " << m_iFlowWindowSize
+ << " SPAN " << getFlightSpan() << " ackdataseqno %" << ackdata_seqno);
+ }
}
- }
/*
* We must not ignore full ack received by peer
@@ -8539,14 +8917,14 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
* Therefore, a distinct ack state is used for received Ack (iSndLastFullAck)
* and ack position in send buffer (m_iSndLastDataAck).
* Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update)
- * occures, we drop received acks (as duplicates) and do not update stats like RTT,
+ * occurs, we drop received acks (as duplicates) and do not update stats like RTT,
* which may go crazy and stay there, preventing proper stream recovery.
*/
if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0)
{
// discard it if it is a repeated ACK
- return;
+ return true;
}
m_iSndLastFullAck = ackdata_seqno;
}
@@ -8556,7 +8934,7 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
#if ENABLE_BONDING
if (m_parent->m_GroupOf)
{
- ScopedLock glock (uglobal().m_GlobControlLock);
+ SharedLock glock (uglobal().m_GlobControlLock);
if (m_parent->m_GroupOf)
{
// Will apply m_GroupLock, ordered after m_GlobControlLock.
@@ -8566,24 +8944,12 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
}
#endif
- size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING
- bool wrongsize = 0 != (acksize % ACKD_FIELD_SIZE);
- acksize = acksize / ACKD_FIELD_SIZE; // ACTUAL VALUE
-
- if (wrongsize)
- {
- // Issue a log, but don't do anything but skipping the "odd" bytes from the payload.
- LOGC(inlog.Warn,
- log << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to "
- << acksize << " fields");
- }
-
// Start with checking the base size.
if (acksize < ACKD_TOTAL_SIZE_SMALL)
{
LOGC(inlog.Warn, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!");
// Ack is already interpreted, just skip further parts.
- return;
+ return false;
}
// This check covers fields up to ACKD_BUFFERLEFT.
@@ -8672,10 +9038,11 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
/* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE_VER102_ONLY]) and delivery rate (pcData[ACKD_RCVRATE]) in
* bytes/sec instead of pkts/sec */
/* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */
+ // XXX v1.0.2 handling can be likely deleted.
if (acksize > ACKD_TOTAL_SIZE_UDTBASE)
bytesps = ackdata[ACKD_RCVRATE];
else
- bytesps = pktps * m_iMaxSRTPayloadSize;
+ bytesps = pktps * m_iMaxDataPayloadSize;
m_iBandwidth = avg_iir<8>(m_iBandwidth.load(), bandwidth);
m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate.load(), pktps);
@@ -8693,9 +9060,11 @@ void srt::CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_
enterCS(m_StatsLock);
m_stats.sndr.recvdAck.count(1);
leaveCS(m_StatsLock);
+
+ return true;
}
-void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival)
+bool srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival)
{
int32_t ack = 0;
@@ -8721,14 +9090,14 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr
}
#endif
- return;
+ return false;
}
LOGC(inlog.Error,
log << CONID() << "ACK record not found, can't estimate RTT "
<< "(ACK number: " << ctrlpkt.getAckSeqNo() << ", last ACK sent: " << m_iAckSeqNo
<< ", RTT (EWMA): " << m_iSRTT << ")");
- return;
+ return false;
}
if (rtt <= 0)
@@ -8736,7 +9105,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr
LOGC(inlog.Error,
log << CONID() << "IPE: invalid RTT estimate " << rtt
<< ", possible time shift. Clock: " << SRT_SYNC_CLOCK_STR);
- return;
+ return false;
}
// If increasing delay is detected.
@@ -8777,7 +9146,7 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr
if (m_config.bDriftTracer)
{
#if ENABLE_BONDING
- ScopedLock glock(uglobal().m_GlobControlLock); // XXX not too excessive?
+ ExclusiveLock glock(uglobal().m_GlobControlLock); // XXX not too excessive?
const bool drift_updated =
#endif
m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, rtt);
@@ -8791,12 +9160,13 @@ void srt::CUDT::processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsAr
// Update last ACK that has been received by the sender
if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0)
m_iRcvLastAckAck = ack;
+ return true;
}
-void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
+bool srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
{
const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData);
- const size_t losslist_len = ctrlpkt.getLength() / 4;
+ const size_t losslist_len = ctrlpkt.getLength() / sizeof(int32_t);
bool secure = true;
@@ -8814,6 +9184,14 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
// IF the loss is a range
if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST))
{
+ // The range-first marker means the next cell holds HI. Reject if it isn't present
+ // in the body — otherwise losslist[i+1] reads past the wire payload.
+ if (i + 1 >= n)
+ {
+ LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT: trailing range-first word with no HI - DISCARDING");
+ secure = false;
+ break;
+ }
// Then it's this is a specification with HI in a consecutive cell.
const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]);
const int32_t losslist_hi = losslist[i + 1];
@@ -8940,7 +9318,10 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
// this should not happen: attack or bug
m_bBroken = true;
m_iBrokenCounter = 0;
- return;
+
+ updateBrokenConnection();
+ completeBrokenConnectionDependencies(SRT_ESECFAIL); // LOCKS!
+ return false;
}
// the lost packet (retransmission) should be sent out immediately
@@ -8949,16 +9330,22 @@ void srt::CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
enterCS(m_StatsLock);
m_stats.sndr.recvdNak.count(1);
leaveCS(m_StatsLock);
+
+ return true;
}
-void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt)
+bool srt::CUDT::processCtrlHS(const CPacket& ctrlpkt)
{
CHandShake req;
- req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength());
+ if (-1 == req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()))
+ {
+ LOGC(inlog.Error, log << CONID() << "processCtrlHS: EPE: Handshake has wrong size: " << ctrlpkt.getLength());
+ return false;
+ }
HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show());
- if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...???
+ if ((req.m_iReqType > URQ_INDUCTION_TYPES) // actually it catches URQ_INDUCTION and URQ_ERROR_* symbols...???
|| (m_config.bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION
{
// The peer side has not received the handshake message, so it keeps querying
@@ -9040,7 +9427,7 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt)
CPacket rsppkt;
rsppkt.setControl(UMSG_HANDSHAKE);
- rsppkt.allocate(m_iMaxSRTPayloadSize);
+ rsppkt.allocate(controlPayloadSize());
// If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE.
// There is also no possible IPE condition in case of HSv4 - for this version it will always return true.
@@ -9057,17 +9444,61 @@ void srt::CUDT::processCtrlHS(const CPacket& ctrlpkt)
m_tsLastSndTime.store(steady_clock::now());
}
}
+
+ // If a REJECTION HS has been sent, break also the connection locally.
+ if (initdata.m_iReqType >= URQ_FAILURE_TYPES)
+ {
+ processCtrlShutdown(); // always returns true
+ }
}
else
{
HLOGC(inlog.Debug, log << CONID() << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED.");
+ return false;
}
+ return true;
}
-void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt)
+bool srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt)
{
+ // dropdata[0..1] are indexed unconditionally below.
+ if (ctrlpkt.getLength() < 2 * sizeof(int32_t))
+ {
+ LOGC(inlog.Warn, log << CONID() << "DROPREQ: payload " << ctrlpkt.getLength()
+ << " bytes < " << (2 * sizeof(int32_t)) << " - rejecting");
+ return false;
+ }
+
const int32_t* dropdata = (const int32_t*) ctrlpkt.m_pcData;
+ // The wire format carries a (lo, hi) seqno range. Reject reversed
+ // ranges: dropMessage walks a circular buffer from offset(lo) to
+ // offset(hi)+1 via incPos(); when seqcmp(lo, hi) > 0 the loop wraps
+ // and clears nearly the entire receive buffer (DoS primitive). The
+ // analogous LOSSREPORT path already rejects reversed ranges.
+
+ // This is for check only - one packet read will not spoil it
+ m_RcvBufferLock.lock();
+ const int32_t hookseq_begin = m_pRcvBuffer->getStartSeqNo();
+ m_RcvBufferLock.unlock();
+
+ int dist_begin = CSeqNo::seqoff(dropdata[0], hookseq_begin);
+ int dist_rel = CSeqNo::seqoff(dropdata[0], dropdata[1]);
+
+ if (abs(dist_begin) > CSeqNo::m_iSeqNoTH/2)
+ {
+ LOGC(inlog.Warn, log << CONID() << "EPE: rcv DROPREQ low rng %"
+ << dropdata[0] << " - too distant to receiver buffer %" << hookseq_begin
+ << " by " << dist_begin << " (exceeds threshold)");
+ return false;
+ }
+ if (dist_rel < 0 || dist_rel > CSeqNo::m_iSeqNoTH/2)
+ {
+ LOGC(inlog.Warn, log << CONID() << "EPE: rcv DROPREQ rng %"
+ << dropdata[0] << " - %" << dropdata[1] << " - REVERSED RANGE, DISCARDING");
+ return false;
+ }
+
{
CUniqueSync rcvtscc (m_RecvLock, m_RcvTsbPdCond);
// With both TLPktDrop and TsbPd enabled, a message always consists only of one packet.
@@ -9129,9 +9560,11 @@ void srt::CUDT::processCtrlDropReq(const CPacket& ctrlpkt)
HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %"
<< dropdata[0] << "-" << dropdata[1] << " current %" << m_iRcvCurrSeqNo);
}
+
+ return true;
}
-void srt::CUDT::processCtrlShutdown()
+bool srt::CUDT::processCtrlShutdown()
{
m_bShutdown = true;
m_bClosing = true;
@@ -9142,9 +9575,10 @@ void srt::CUDT::processCtrlShutdown()
// just we know about this state prematurely thanks to this message.
updateBrokenConnection();
completeBrokenConnectionDependencies(SRT_ECONNLOST); // LOCKS!
+ return true;
}
-void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt)
+bool srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt)
{
HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:"
<< MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType())
@@ -9172,31 +9606,44 @@ void srt::CUDT::processCtrlUserDefined(const CPacket& ctrlpkt)
{
updateCC(TEV_CUSTOM, EventVariant(&ctrlpkt));
}
+ return true;
}
-void srt::CUDT::processCtrl(const CPacket &ctrlpkt)
+bool srt::CUDT::processCtrl(const CPacket &ctrlpkt)
{
// Just heard from the peer, reset the expiration count.
m_iEXPCount = 1;
const steady_clock::time_point currtime = steady_clock::now();
m_tsLastRspTime = currtime;
+ // Extra check for the payload size:
+ // - must be aligned to int32_t
+ // - cannot be 0 (msgs with no args use 4-byte zero-filled padding).
+ size_t pktlen = ctrlpkt.getLength();
+ if (!pktlen || pktlen % sizeof(int32_t) != 0)
+ {
+ LOGC(inlog.Error, log << CONID() << "EPE: incoming UMSG: " << ctrlpkt.getType() << " INVALID SIZE: " << pktlen
+ << " (expected > 0 and aligned to " << sizeof(int32_t) << " bytes)");
+ return false;
+ }
+
HLOGC(inlog.Debug,
log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " ("
<< MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.id());
+ bool result = false;
switch (ctrlpkt.getType())
{
case UMSG_ACK: // 010 - Acknowledgement
- processCtrlAck(ctrlpkt, currtime);
+ result = processCtrlAck(ctrlpkt, currtime);
break;
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
- processCtrlAckAck(ctrlpkt, currtime);
+ result = processCtrlAckAck(ctrlpkt, currtime);
break;
case UMSG_LOSSREPORT: // 011 - Loss Report
- processCtrlLossReport(ctrlpkt);
+ result = processCtrlLossReport(ctrlpkt);
break;
case UMSG_CGWARNING: // 100 - Delay Warning
@@ -9210,38 +9657,40 @@ void srt::CUDT::processCtrl(const CPacket &ctrlpkt)
break;
case UMSG_KEEPALIVE: // 001 - Keep-alive
- processKeepalive(ctrlpkt, currtime);
+ result = processKeepalive(ctrlpkt, currtime);
break;
case UMSG_HANDSHAKE: // 000 - Handshake
- processCtrlHS(ctrlpkt);
+ result = processCtrlHS(ctrlpkt);
break;
case UMSG_SHUTDOWN: // 101 - Shutdown
- processCtrlShutdown();
+ result = processCtrlShutdown();
break;
case UMSG_DROPREQ: // 111 - Msg drop request
- processCtrlDropReq(ctrlpkt);
+ result = processCtrlDropReq(ctrlpkt);
break;
case UMSG_PEERERROR: // 1000 - An error has happened to the peer side
// int err_type = packet.getAddInfo();
// currently only this error is signalled from the peer side
- // if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately
+ // if recvfile() fails (e.g., due to disk fail), blocked sendfile/send should return immediately
// giving the app a chance to fix the issue
m_bPeerHealth = false;
+ result = true;
break;
case UMSG_EXT: // 0x7FFF - reserved and user defined messages
- processCtrlUserDefined(ctrlpkt);
+ result = processCtrlUserDefined(ctrlpkt);
break;
default:
break;
}
+ return result;
}
void srt::CUDT::updateSrtRcvSettings()
@@ -9320,7 +9769,7 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv)
if (m_parent->m_GroupOf)
{
- ScopedLock glock (uglobal().m_GlobControlLock);
+ SharedLock glock (uglobal().m_GlobControlLock);
grpspec = m_parent->m_GroupOf
? " group=$" + Sprint(m_parent->m_GroupOf->id())
: string();
@@ -9351,80 +9800,157 @@ void srt::CUDT::updateAfterSrtHandshake(int hsv)
}
}
-int srt::CUDT::packLostData(CPacket& w_packet)
+// XXX During sender buffer refax turn this into either returning
+// a sequence number or move it to the sender buffer facility.
+// [[using locked (m_RecvAckLock)]]
+std::pair srt::CUDT::getCleanRexmitOffset()
{
- // protect m_iSndLastDataAck from updating by ACK processing
- UniqueLock ackguard(m_RecvAckLock);
- const steady_clock::time_point time_now = steady_clock::now();
- const steady_clock::time_point time_nak = time_now - microseconds_from(m_iSRTT - 4 * m_iRTTVar);
+ // This function is required to look into the loss list and
+ // drop all sequences that are already revoked from the sender
+ // buffer, send the drop request if needed, and return the sender
+ // buffer offset for the next packet to retransmit, or -1 if
+ // there is no retransmission candidate at the moment.
for (;;)
{
- w_packet.set_seqno(m_pSndLossList->popLostSeq());
- if (w_packet.seqno() == SRT_SEQNO_NONE)
- break;
+ // REPEATABLE because we might need to drop this scheduled seq.
- // XXX See the note above the m_iSndLastDataAck declaration in core.h
- // This is the place where the important sequence numbers for
- // sender buffer are actually managed by this field here.
- const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.seqno());
+ // Pick up the first sequence.
+ int32_t seq = m_pSndLossList->popLostSeq();
+ if (seq == SRT_SEQNO_NONE)
+ {
+ // No loss reports, we are clear here.
+ return std::make_pair(SRT_SEQNO_NONE, -1);
+ }
+
+ int offset = CSeqNo::seqoff(m_iSndLastDataAck, seq);
if (offset < 0)
{
// XXX Likely that this will never be executed because if the upper
// sequence is not in the sender buffer, then most likely the loss
// was completely ignored.
- LOGC(qrlog.Error,
- log << CONID() << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() "
- << w_packet.seqno() << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset
- << ". Continue, request DROP");
+ LOGC(qslog.Error, log << CONID()
+ << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(seqno() "
+ << seq << ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset
+ << ". Continue, request DROP");
+
+ int32_t oldest_outdated_seq = seq;
+ // We have received the first outdated sequence.
+ // Loop on to find out more.
+ // Interrupt on the first non-outdated sequence
+ // or when the loss list gets empty.
+
+ for (;;)
+ {
+ seq = m_pSndLossList->popLostSeq();
+ if (seq == SRT_SEQNO_NONE)
+ {
+ offset = -1; // set it because this will be returned.
+ break;
+ }
+
+ offset = CSeqNo::seqoff(m_iSndLastDataAck, seq);
+ if (offset >= 0)
+ break;
+ }
+
+ // Now we have 'seq' that is either non-sequence
+ // or a valid sequence. We can safely exit with this
+ // value from this branch. We just need to manage
+ // the drop report.
// No matter whether this is right or not (maybe the attack case should be
// considered, and some LOSSREPORT flood prevention), send the drop request
// to the peer.
int32_t seqpair[2] = {
- w_packet.seqno(),
+ oldest_outdated_seq,
CSeqNo::decseq(m_iSndLastDataAck)
};
- HLOGC(qrlog.Debug,
- log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: #"
- << MSGNO_SEQ::unwrap(w_packet.msgflags()) << " SEQ:" << seqpair[0] << " - " << seqpair[1] << "("
- << (-offset) << " packets)");
+ HLOGC(qslog.Debug,
+ log << CONID() << "PEER reported LOSS not from the sending buffer - requesting DROP: %("
+ << seqpair[0] << " - " << seqpair[1] << ") (" << (-offset) << " packets)");
// See interpretation in processCtrlDropReq(). We don't know the message number,
// so we request that the drop be exclusively sequence number based.
int32_t msgno = SRT_MSGNO_CONTROL;
-
sendCtrl(UMSG_DROPREQ, &msgno, seqpair, sizeof(seqpair));
+ }
+
+ // We have exit anyway with the right sequence or no sequence.
+ if (seq == SRT_SEQNO_NONE)
+ return std::make_pair(SRT_SEQNO_NONE, -1);
+
+ // For the right sequence though check also the right time.
+ const steady_clock::time_point time_now = steady_clock::now();
+ if (!checkRexmitRightTime(offset, time_now))
+ {
+ // Ignore this, even though valid, sequence, and pick up
+ // the next from the list.
continue;
}
- if (m_bPeerNakReport && m_config.iRetransmitAlgo != 0)
+ // Ok, we have what we needed one way or another.
+ return std::make_pair(seq, offset);
+ }
+}
+
+// [[using locked (m_RecvAckLock)]]
+bool srt::CUDT::checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time)
+{
+ // If not configured, the time is always right
+ if (!m_bPeerNakReport || m_config.iRetransmitAlgo == 0)
+ return true;
+
+ const steady_clock::time_point time_nak = current_time - microseconds_from(m_iSRTT - 4 * m_iRTTVar);
+ const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset);
+
+ if (tsLastRexmit >= time_nak)
+ {
+#if ENABLE_HEAVY_LOGGING
+ ostringstream last_rextime;
+ if (is_zero(tsLastRexmit))
{
- const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset);
- if (tsLastRexmit >= time_nak)
- {
- HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno "
- << w_packet.seqno() << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit))
- << " RTT=" << m_iSRTT << " RTTVar=" << m_iRTTVar
- << " now=" << FormatTime(time_now));
- continue;
- }
+ last_rextime << "REXMIT FIRST TIME";
+ }
+ else
+ {
+ last_rextime << "REXMITTED " << FormatDuration(current_time - tsLastRexmit)
+ << " ago at " << FormatTime(tsLastRexmit);
}
+ HLOGC(qslog.Debug, log << CONID() << "REXMIT: ignoring %" << CSeqNo::incseq(m_iSndLastDataAck, offset)
+ << " " << last_rextime.str()
+ << ", RTT: ~" << FormatValue(m_iSRTT, 1000, "ms")
+ << " <>" << FormatValue(m_iRTTVar, 1000, "")
+ << " now=" << FormatTime(current_time));
+#endif
+ return false;
+ }
+
+ return true;
+}
- typedef CSndBuffer::DropRange DropRange;
+// [[using locked (m_RecvAckLock)]]
+int srt::CUDT::extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet,
+ srt::sync::steady_clock::time_point& w_tsOrigin)
+{
+ // REPEATABLE BLOCK (not a real loop)
+ for (;;)
+ {
+ typedef CSndBuffer::DropRange DropRange;
DropRange buffer_drop;
- steady_clock::time_point tsOrigin;
- const int payload = m_pSndBuffer->readData(offset, (w_packet), (tsOrigin), (buffer_drop));
+ w_packet.set_seqno(seqno);
+
+ const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_tsOrigin), (buffer_drop));
if (payload == CSndBuffer::READ_DROP)
{
SRT_ASSERT(CSeqNo::seqoff(buffer_drop.seqno[DropRange::BEGIN], buffer_drop.seqno[DropRange::END]) >= 0);
- HLOGC(qrlog.Debug,
- log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #"
- << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - "
- << buffer_drop.seqno[DropRange::END] << ")");
+ HLOGC(qslog.Debug,
+ log << CONID() << "loss-reported packets expired in SndBuf - requesting DROP: #"
+ << buffer_drop.msgno << " %(" << buffer_drop.seqno[DropRange::BEGIN] << " - "
+ << buffer_drop.seqno[DropRange::END] << ")");
sendCtrl(UMSG_DROPREQ, &buffer_drop.msgno, buffer_drop.seqno, sizeof(buffer_drop.seqno));
// skip all dropped packets
@@ -9432,43 +9958,133 @@ int srt::CUDT::packLostData(CPacket& w_packet)
m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, buffer_drop.seqno[DropRange::END]);
continue;
}
- else if (payload == CSndBuffer::READ_NONE)
- continue;
- // The packet has been ecrypted, thus the authentication tag is expected to be stored
- // in the SND buffer as well right after the payload.
- if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM)
+ if (payload == CSndBuffer::READ_NONE)
{
- w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX);
+ LOGC(qslog.Error, log << CONID() << "loss-reported packet %" << w_packet.seqno() << " NOT FOUND in the sender buffer");
+ return 0;
}
- // At this point we no longer need the ACK lock,
- // because we are going to return from the function.
- // Therefore unlocking in order not to block other threads.
- ackguard.unlock();
+ return payload;
+ }
+
+}
- enterCS(m_StatsLock);
- m_stats.sndr.sentRetrans.count(payload);
- leaveCS(m_StatsLock);
+int srt::CUDT::packLostData(CPacket& w_packet)
+{
+#if ENABLE_MAXREXMITBW
- // Despite the contextual interpretation of packet.m_iMsgNo around
- // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular
- // case we can be sure that this is exactly the value of PH_MSGNO as a bitset.
- // So, set here the rexmit flag if the peer understands it.
- if (m_bPeerRexmitFlag)
+#ifdef ENABLE_RATE_MEASUREMENT
+#define IF_RATE_MEA(expr) expr
+#else
+#define IF_RATE_MEA(expr)
+#endif
+
+ // XXX NOTE: If you refactor the sender buffer so that the random access
+ // is possible, you might be able to extract the exact packet size to
+ // check for enough tokens and consume them in one step.
+
+ bool maxrexmitbw_enabled = m_config.llMaxRexmitBW >= 0;
+ if (maxrexmitbw_enabled)
+ {
+ IF_RATE_MEA( IF_HEAVY_LOGGING(const int64_t iRexmitRateMeasured = m_SndRexmitMeasurement.rateBytes()); )
+ size_t granted_size = m_zSndAveragePacketSize;
+ size_t len = granted_size + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE;
+
+ if (!m_SndRexmitShaper.enoughTokens(len))
{
- w_packet.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT);
+ HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED pkt est/len=" << len << " exceeds " << m_SndRexmitShaper.ntokens()
+ << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps()
+ IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" )
+ );
+ return 0;
}
- setDataPacketTS(w_packet, tsOrigin);
-#ifdef ENABLE_MAXREXMITBW
- m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength());
+ HLOGC(qslog.Debug, log << "REXMIT-SH: ALLOWED pkt est/len=" << len << " allowed, budget " << m_SndRexmitShaper.ntokens()
+ << " tokens, rate/Bps:used=" << m_SndRexmitShaper.usedRate_Bps() << ",avail=" << m_SndRexmitShaper.availRate_Bps()
+ IF_RATE_MEA( << " (measured: " << FormatValue(iRexmitRateMeasured, 1024, "kBps") << ")" )
+ );
+ }
+
+#undef IF_RATE_MEA
#endif
- return payload;
+ // Original sending time of the packet, to be stamped after filling.
+ steady_clock::time_point tsOrigin;
+
+ {
+ // protect m_iSndLastDataAck from updating by ACK processing
+ ScopedLock ackguard(m_RecvAckLock);
+ int32_t seqno;
+ int offset;
+
+ // Get the first sequence for retransmission, bypassing and taking care of
+ // those that are in the forgotten region, as well as required to be rejected.
+ Tie2(seqno, offset) = getCleanRexmitOffset();
+
+ if (seqno == SRT_SEQNO_NONE)
+ return 0;
+
+ // Extract the packet from the sender buffer that is mapped to the expected sequence
+ // number, bypassing and taking care of those that are decided to be dropped.
+ const int payload = extractCleanRexmitPacket(seqno, offset, (w_packet), (tsOrigin));
+ if (payload <= 0)
+ return 0;
}
- return 0;
+ HLOGC(qslog.Debug, log << CONID() << "packed REXMIT packet %" << w_packet.seqno() << " size=" << w_packet.getLength()
+ << " - still " << m_pSndLossList->getLossLength() << " LOSS ENTRIES left");
+
+ // POST-EXTRACTION length fixes.
+
+ // The packet has been ecrypted, thus the authentication tag is expected to be stored
+ // in the SND buffer as well right after the payload.
+ if (m_pCryptoControl && m_pCryptoControl->getCryptoMode() == CSrtConfig::CIPHER_MODE_AES_GCM)
+ {
+ w_packet.setLength(w_packet.getLength() + HAICRYPT_AUTHTAG_MAX);
+ }
+
+#if ENABLE_MAXREXMITBW
+ if (maxrexmitbw_enabled)
+ {
+ // Token consumption will only happen when the retransmission
+ // effectively happens.
+ // XXX NOTE: In version 1.6.0 use the IP-version dependent value for UDP_HDR_SIZE
+ size_t network_size = w_packet.getLength() + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE;
+ m_SndRexmitShaper.consumeTokens(network_size);
+ HLOGC(qslog.Debug, log << "REXMIT-SH: consumed " << network_size << " tokens, remain " << m_SndRexmitShaper.ntokens());
+ }
+#endif
+
+ enterCS(m_StatsLock);
+ m_stats.sndr.sentRetrans.count(w_packet.getLength());
+ leaveCS(m_StatsLock);
+
+ // Despite the contextual interpretation of packet.m_iMsgNo around
+ // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular
+ // case we can be sure that this is exactly the value of PH_MSGNO as a bitset.
+ // So, set here the rexmit flag if the peer understands it.
+ if (m_bPeerRexmitFlag)
+ {
+ w_packet.set_msgflags(w_packet.msgflags() | PACKET_SND_REXMIT);
+ }
+ setDataPacketTS(w_packet, tsOrigin);
+
+#ifdef ENABLE_MAXREXMITBW
+ // XXX OLD rexmit measurement
+ // m_SndRexmitRate.addSample(time_now, 1, w_packet.getLength());
+#endif
+
+ // XXX Consider calculating the total packet length once you have a possibility
+ // to get the in-connection used IP version.
+#ifdef ENABLE_RATE_MEASUREMENT
+ m_SndRexmitMeasurement.dataUpdate(1, w_packet.getLength());
+ HLOGC(bslog.Debug, log << "RateMeasurement: REXMIT, pkt-size=" << w_packet.getLength()
+ << " - COLLECTED pkts=" << m_SndRexmitMeasurement.m_nInstaPackets
+ << " bytes=" << m_SndRexmitMeasurement.m_nInstaBytes);
+#endif
+
+ return w_packet.getLength();
}
#if SRT_DEBUG_TRACE_SND
@@ -9593,15 +10209,69 @@ void srt::CUDT::setDataPacketTS(CPacket& p, const time_point& ts)
p.set_timestamp(makeTS(ts, tsStart));
}
-bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED)
+bool srt::CUDT::isRegularSendingPriority()
{
- // Prioritization of original packets only applies to Live CC.
+ // In order to have regular packets take precedence over retransmitted:
+ // - SRTO_TLPKTDROP = true
+ // - SRTO_MESSAGEAPI = true
+ // NOTE:
+ // - tlpktdrop is ignored in stream mode
+ // - messageapi without tlpktdrop is possible in non-live message mode
+ // - Live mode without tlpktdrop is possible and in this mode also the
+ // retransmitted packets should have priority.
if (!m_bPeerTLPktDrop || !m_config.bMessageAPI)
- return true;
+ return false;
- // TODO: lock sender buffer?
+ // XXX NOTE: the current solution is simple - the regular packet takes precedence
+ // over a retransmitted packet, if there is at least one such packet already
+ // scheduled.
+ //
+ // This probably isn't the most wanted solution, some more elaborate condition
+ // might be better in some situations. For example, it should be acceptable
+ // that a packet that has very little time to be recovered is sent before a
+ // regular packet that has still STT + Latency time to deliver. The regular
+ // packets should still be favored, but not necessarily at the expense of
+ // dismissing a recovery chance that wouldn't endanger the delivery of a regular
+ // packet. Criteria might be various, for example, the number of scheduled
+ // packets and their late delivery time might be taken into account.
const time_point tsNextPacket = m_pSndBuffer->peekNextOriginal();
+ if (tsNextPacket != time_point())
+ {
+ // Have regular packet and we decided they have a priority.
+ HLOGC(qslog.Debug, log << "REXMIT-SH: BLOCKED because regular packets have priority");
+ return true;
+ }
+ return false;
+}
+
+namespace srt {
+#ifdef ENABLE_MAXREXMITBW
+static sync::steady_clock::duration optimisticSingleTripDuration(int rttval, int rttvar)
+{
+ int lowrtt = rttval - rttvar;
+ if (lowrtt < 0) // bullshit?
+ lowrtt = rttval;
+
+ int stt = lowrtt/2;
+
+ // Make sure that burst period has at least this value
+ sync::steady_clock::duration stt_td = sync::microseconds_from(stt);
+
+ // Still, prevent from setting enormous STT values
+ if (stt_td < sync::seconds_from(4))
+ {
+ return stt_td;
+ }
+ return sync::steady_clock::duration();
+}
+#endif
+}
+
+void srt::CUDT::updateSenderMeasurements(bool can_rexmit SRT_ATR_UNUSED)
+{
+ const time_point tnow SRT_ATR_UNUSED = steady_clock::now();
+
#if SRT_DEBUG_TRACE_SND
const int buffdelay_ms = count_milliseconds(m_pSndBuffer->getBufferingDelay(tnow));
// If there is a small loss, still better to retransmit. If timespan is already big,
@@ -9617,31 +10287,37 @@ bool srt::CUDT::isRetransmissionAllowed(const time_point& tnow SRT_ATR_UNUSED)
g_snd_logger.state.msTimespanTh = threshold_ms_min;
g_snd_logger.state.msNextUniqueToSend = msNextUniqueToSend;
g_snd_logger.state.usElapsedLastDrop = count_microseconds(tnow - m_tsLastTLDrop);
- g_snd_logger.state.canRexmit = false;
+ g_snd_logger.state.canRexmit = can_rexmit;
#endif
- if (tsNextPacket != time_point())
- {
- // Can send original packet, so just send it
- return false;
- }
-
#ifdef ENABLE_MAXREXMITBW
+ /* OLD rate estimator; CODE for packLostData()!
m_SndRexmitRate.addSample(tnow, 0, 0); // Update the estimation.
const int64_t iRexmitRateBps = m_SndRexmitRate.getRate();
- const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW;
if (iRexmitRateLimitBps >= 0 && iRexmitRateBps > iRexmitRateLimitBps)
{
// Too many retransmissions, so don't send anything.
// TODO: When to wake up next time?
return false;
}
-#endif
+ */
+ const int64_t iRexmitRateLimitBps = m_config.llMaxRexmitBW;
+ if (iRexmitRateLimitBps >= 0)
+ {
+ int b4_tokens SRT_ATR_UNUSED = m_SndRexmitShaper.ntokens();
+ m_SndRexmitShaper.setBitrate(iRexmitRateLimitBps);
+ duration stt_td = optimisticSingleTripDuration(m_iSRTT, m_iRTTVar);
+ if (stt_td != duration())
+ m_SndRexmitShaper.setMinimumBurstPeriod(stt_td);
+ m_SndRexmitShaper.tick(tnow);
+ int a4_tokens SRT_ATR_UNUSED = m_SndRexmitShaper.ntokens();
-#if SRT_DEBUG_TRACE_SND
- g_snd_logger.state.canRexmit = true;
+ HLOGC(qslog.Debug, log << "REXMIT-SW: tick tokens updated: " << b4_tokens << " -> " << a4_tokens
+ << "/" << m_SndRexmitShaper.maxTokens() << " period="
+ << FormatDuration(m_SndRexmitShaper.burstPeriod()));
+ }
#endif
- return true;
+
}
bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime, sockaddr_any& w_src_addr)
@@ -9670,9 +10346,25 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime
if (!m_bOpened)
return false;
- payload = isRetransmissionAllowed(enter_time)
- ? packLostData((w_packet))
- : 0;
+ // This function returns true if there is a regular packet candidate waiting
+ // for being sent and sending this packet has a prioriry over retransmission candidate.
+ if (!isRegularSendingPriority())
+ {
+ payload = packLostData((w_packet));
+#if ENABLE_MAXREXMITBW
+ if (payload == 0)
+ {
+ HLOGC(qslog.Debug, log << "REXMIT-SH: no rexmit required, remain " << m_SndRexmitShaper.ntokens() << " tokens");
+ }
+#endif
+ }
+ else
+ {
+ HLOGC(qslog.Debug, log << "REXMIT: retransmission SUPERSEDED, proceed with regular candidate");
+ }
+
+ // Updates the data that will be next used in packLostData() in next calls.
+ updateSenderMeasurements(payload != 0);
IF_HEAVY_LOGGING(const char* reason); // The source of the data packet (normal/rexmit/filter)
if (payload > 0)
@@ -9698,6 +10390,18 @@ bool srt::CUDT::packData(CPacket& w_packet, steady_clock::time_point& w_nexttime
m_tdSendTimeDiff = steady_clock::duration();
return false;
}
+
+#if ENABLE_MAXREXMITBW
+ if (m_zSndAveragePacketSize > 0)
+ {
+ m_zSndAveragePacketSize = avg_iir<16>(m_zSndAveragePacketSize, w_packet.getLength());
+ }
+ else
+ {
+ m_zSndAveragePacketSize = w_packet.getLength();
+ }
+ m_zSndMaxPacketSize = std::max(m_zSndMaxPacketSize, w_packet.getLength());
+#endif
new_packet_packed = true;
// every 16 (0xF) packets, a packet pair is sent
@@ -9934,6 +10638,11 @@ bool srt::CUDT::packUniqueData(CPacket& w_packet)
g_snd_logger.trace();
#endif
+#ifdef ENABLE_RATE_MEASUREMENT
+ HLOGC(bslog.Debug, log << "RATE-MEASUREMENT: REGULAR, pkt-size=" << w_packet.getLength());
+ m_SndRegularMeasurement.dataUpdate(1, w_packet.getLength());
+#endif
+
return true;
}
@@ -9947,7 +10656,7 @@ void srt::CUDT::processClose()
m_bBroken = true;
m_iBrokenCounter = 60;
- HLOGP(smlog.Debug, "processClose: sent message and set flags");
+ HLOGP(smlog.Debug, "processClose: (closing=true) sent message and set flags");
if (m_bTsbPd)
{
@@ -9964,12 +10673,23 @@ void srt::CUDT::processClose()
CGlobEvent::triggerEvent();
}
+void srt::CUDT::countConfirmedLoss(int pkts)
+{
+ if (pkts <= 0)
+ return;
+
+ ScopedLock lg(m_StatsLock);
+ m_stats.rcvr.lostConfirmed.count(stats::Packets((uint32_t) pkts));
+}
+
void srt::CUDT::sendLossReport(const std::vector > &loss_seqs)
{
+ int confirmed = 0;
vector seqbuffer;
seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic
for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i)
{
+ confirmed += CSeqNo::seqoff(i->first, i->second) + 1;
if (i->first == i->second)
{
seqbuffer.push_back(i->first);
@@ -9987,6 +10707,7 @@ void srt::CUDT::sendLossReport(const std::vector > &
if (!seqbuffer.empty())
{
sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], (int) seqbuffer.size());
+ countConfirmedLoss(confirmed);
}
}
@@ -9994,7 +10715,7 @@ void srt::CUDT::sendLossReport(const std::vector > &
bool srt::CUDT::overrideSndSeqNo(int32_t seq)
{
// This function is intended to be called from the socket
- // group management functions to synchronize the sequnece in
+ // group management functions to synchronize the sequence in
// all sockes in the bonding group. THIS sequence given
// here is the sequence TO BE STAMPED AT THE EXACTLY NEXT
// sent payload. Therefore, screw up the ISN to exactly this
@@ -10179,6 +10900,7 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool&
}
}
+ bool decrypt_successful = false;
const int buffer_add_result = m_pRcvBuffer->insert(u);
if (buffer_add_result < 0)
{
@@ -10239,8 +10961,12 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool&
}
#endif
}
+ else
+ {
+ decrypt_successful = true;
+ }
}
- else if (m_pCryptoControl && m_pCryptoControl->m_RcvKmState == SRT_KM_S_SECURED)
+ else if (m_pCryptoControl && m_pCryptoControl->m_RcvKmState != SRT_KM_S_UNSECURED)
{
// Unencrypted packets are not allowed.
const int iDropCnt = m_pRcvBuffer->dropMessage(u->m_Packet.getSeqNo(), u->m_Packet.getSeqNo(), SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING);
@@ -10319,6 +11045,10 @@ int srt::CUDT::handleSocketPacketReception(const vector& incoming, bool&
if (CSeqNo::seqcmp(rpkt.seqno(), m_iRcvCurrSeqNo) > 0)
{
m_iRcvCurrSeqNo = rpkt.seqno(); // Latest possible received
+ if (decrypt_successful && m_pCryptoControl)
+ {
+ m_pCryptoControl->m_CurrentKey = rpkt.getMsgCryptoFlags();
+ }
}
else
{
@@ -10430,7 +11160,7 @@ int srt::CUDT::processData(CUnit* in_unit)
// always immediately.
int initial_loss_ttl = 0;
if (m_config.bSRTLA && m_bPeerRexmitFlag)
- initial_loss_ttl = m_config.iMaxReorderTolerance;
+ initial_loss_ttl = SRTLA_WITNESS_TTL;
else if (!m_config.bSRTLA && m_bPeerRexmitFlag)
initial_loss_ttl = m_iReorderTolerance;
@@ -10476,7 +11206,7 @@ int srt::CUDT::processData(CUnit* in_unit)
// reception sequence pointer stating that this link is not receiving.
if (m_parent->m_GroupOf)
{
- ScopedLock protect_group_existence (uglobal().m_GlobControlLock);
+ ExclusiveLock protect_group_existence (uglobal().m_GlobControlLock);
groups::SocketData* gi = m_parent->m_GroupMemberData;
// This check is needed as after getting the lock the socket
@@ -10485,7 +11215,7 @@ int srt::CUDT::processData(CUnit* in_unit)
// this socket as member (that is, 'gi' cannot be a dangling pointer).
if (gi != NULL)
{
- if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely
+ if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, though PENDING is unlikely
{
HLOGC(qrlog.Debug,
log << CONID() << "processData: IN-GROUP rcv state transition " << srt_log_grp_state[gi->rcvstate]
@@ -10565,6 +11295,8 @@ int srt::CUDT::processData(CUnit* in_unit)
{
// The LOSSREPORT will be sent after initial_loss_ttl.
m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl));
+ HLOGC(qrlog.Debug, log << CONID() << "SRTLA LOSSTRACE detect %" << i->first << "-%" << i->second
+ << " witness %" << packet.seqno());
}
}
}
@@ -10663,10 +11395,11 @@ int srt::CUDT::processData(CUnit* in_unit)
// If so, send the loss report and remove this element.
// - Since the first element that has TTL>0, iterate until the end of container and decrease TTL.
//
- // This will be efficient becase the loop to increment one field (without any condition check)
+ // This will be efficient because the loop to increment one field (without any condition check)
// can be quite well optimized.
vector lossdata;
+ int confirmed_loss = 0;
{
ScopedLock lg(m_RcvLossLock);
@@ -10676,6 +11409,19 @@ int srt::CUDT::processData(CUnit* in_unit)
{
deque::iterator i = m_FreshLoss.begin();
+ if (m_config.bSRTLA)
+ {
+ // SRTLA: reports are driven from checkNAKTimer(); records
+ // persist until recovered or dropped.
+ for (; i != m_FreshLoss.end(); ++i)
+ {
+ if (i->ttl > 0)
+ --i->ttl;
+ }
+ }
+ else
+ {
+
// Phase 1: take while TTL <= 0.
// There can be more than one record with the same TTL, if it has happened before
// that there was an 'unlost' (@c dropFromLossLists) sequence that has split one detected loss
@@ -10685,6 +11431,7 @@ int srt::CUDT::processData(CUnit* in_unit)
HLOGC(qrlog.Debug, log << "Packet seq " << i->seq[0] << "-" << i->seq[1]
<< " (" << (CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1) << " packets) considered lost - sending LOSSREPORT");
addLossRecord(lossdata, i->seq[0], i->seq[1]);
+ confirmed_loss += CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1;
}
// Remove elements that have been processed and prepared for lossreport.
@@ -10708,12 +11455,15 @@ int srt::CUDT::processData(CUnit* in_unit)
// Phase 2: rest of the records should have TTL decreased.
for (; i != m_FreshLoss.end(); ++i)
--i->ttl;
+
+ }
}
}
if (!lossdata.empty())
{
sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int) lossdata.size());
}
+ countConfirmedLoss(confirmed_loss);
// was_sent_in_order means either of:
// - packet was sent in order (first if branch above)
@@ -10808,6 +11558,31 @@ void srt::CUDT::updateIdleLinkFrom(CUDT* source)
/// do not include the lacking packet.
/// The tolerance is not increased infinitely - it's bordered by iMaxReorderTolerance.
/// This value can be set in options - SRT_LOSSMAXTTL.
+// Caller holds m_RcvLossLock.
+uint32_t srt::CUDT::srtlaReorderHoldUs(const steady_clock::time_point& now)
+{
+ uint32_t steady_us = m_uiSrtlaHoldSteadyUs.load();
+ if (steady_us == 0)
+ steady_us = SRTLA_HOLD_FALLBACK_US;
+
+ const uint32_t fast_us = srtlaDecayedFastHold(m_uiSrtlaHoldFastUs,
+ count_microseconds(now - m_tsSrtlaHoldFastSet));
+ if (fast_us == 0)
+ m_uiSrtlaHoldFastUs = 0;
+
+ uint32_t hold = std::max(steady_us, fast_us);
+ if (hold < SRTLA_HOLD_FLOOR_US)
+ hold = SRTLA_HOLD_FLOOR_US;
+
+ uint32_t cap = SRTLA_HOLD_CAP_US;
+ const uint32_t lat_half_us = (uint32_t)m_iTsbPdDelay_ms * 500;
+ if (lat_half_us > 0 && lat_half_us < cap)
+ cap = lat_half_us;
+ if (hold > cap)
+ hold = cap;
+ return hold;
+}
+
void srt::CUDT::unlose(const CPacket &packet)
{
ScopedLock lg(m_RcvLossLock);
@@ -10863,13 +11638,36 @@ void srt::CUDT::unlose(const CPacket &packet)
// (in this case it's empty anyway)
// - decrease current reorder tolerance based on whether packets come in order
// (current reorder tolerance is 0 anyway)
- if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0)
+ if (m_bPeerRexmitFlag == 0 || (!m_config.bSRTLA && m_iReorderTolerance == 0))
return;
int had_ttl = 0;
- if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl)))
+ steady_clock::time_point detect_time;
+ if (CRcvFreshLoss::removeOne((m_FreshLoss), sequence, (&had_ttl), (&detect_time)))
{
HLOGC(qrlog.Debug, log << "sequence " << sequence << " removed from belated lossreport record");
+ HLOGC(qrlog.Debug, log << CONID() << "SRTLA LOSSTRACE recovered %" << sequence << " after "
+ << (is_zero(detect_time) ? -1 : count_microseconds(steady_clock::now() - detect_time) / 1000) << " ms "
+ << (packet.getRexmitFlag() ? "retransmission" : "original"));
+
+ if (m_config.bSRTLA && was_reordered && !is_zero(detect_time))
+ {
+ const int64_t late_us = count_microseconds(steady_clock::now() - detect_time);
+ if (late_us > 0)
+ {
+ const uint32_t want_us = (uint32_t)std::min(late_us + SRTLA_HOLD_EVIDENCE_MARGIN_US, SRTLA_HOLD_CAP_US);
+ const uint32_t cur_fast_us = srtlaDecayedFastHold(m_uiSrtlaHoldFastUs,
+ count_microseconds(steady_clock::now() - m_tsSrtlaHoldFastSet));
+ if (want_us > cur_fast_us)
+ {
+ HLOGC(qrlog.Debug, log << CONID() << "SRTLA: belated original %" << sequence
+ << " late by " << (late_us / 1000) << "ms - raising reorder hold to "
+ << (want_us / 1000) << "ms");
+ m_uiSrtlaHoldFastUs = want_us;
+ m_tsSrtlaHoldFastSet = steady_clock::now();
+ }
+ }
+ }
}
if (was_reordered)
@@ -10948,7 +11746,9 @@ void srt::CUDT::dropFromLossLists(int32_t from, int32_t to)
<< range.str() << " packets)");
#endif
- if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0)
+ // SRTLA fills m_FreshLoss from a time-based hold, independently of the reorder
+ // tolerance. Same condition as in unlose().
+ if (m_bPeerRexmitFlag == 0 || (!m_config.bSRTLA && m_iReorderTolerance == 0))
return;
// All code below concerns only "belated lossreport" feature.
@@ -11086,7 +11886,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet)
return m_RejectReason;
}
- // Dunno why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE.
+ // Don't know why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE.
// It doesn't seem to make sense to deserialize it into the handshake structure if we are not
// sure that the packet contains the handshake at all!
if (!packet.isControl(UMSG_HANDSHAKE))
@@ -11316,7 +12116,7 @@ int srt::CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet)
if (conn != CONN_ACCEPT)
return conn;
- packet.setLength(m_iMaxSRTPayloadSize);
+ packet.setLength(controlPayloadSize(addr.family()));
if (!acpu->createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP,
kmdata, kmdatasize,
(packet), (hs)))
@@ -11448,7 +12248,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime)
// by the filter. By this reason they appear often out of order
// and for adding them properly the loss list container wasn't
// prepared. This then requires some more effort to implement.
- if (!m_config.bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS)
+ // Under SRTLA this is the only loss-report emitter, so SRTO_NAKREPORT must not
+ // disable it - the hold is the pacing. The packet-filter condition still applies.
+ const bool srtla_emitter = m_config.bSRTLA && m_bPeerRexmitFlag;
+ if ((!m_config.bRcvNakReport && !srtla_emitter) || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS)
return BECAUSE_NO_REASON;
/*
@@ -11469,65 +12272,71 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime)
if (currtime <= m_tsNextNAKTime.load())
return BECAUSE_NO_REASON; // wait for next NAK time
- if (m_config.bSRTLA)
+ if (srtla_emitter)
{
+ // SRTLA: time-gated loss reporting (single emitter for first
+ // reports and repeats).
+ if (count_microseconds(currtime - m_tsSrtlaHoldPollTime) >= 1000000)
+ {
+ m_tsSrtlaHoldPollTime = currtime;
+ if (m_pRcvQueue && m_pRcvQueue->m_pSrtlaRec)
+ m_uiSrtlaHoldSteadyUs.store(m_pRcvQueue->m_pSrtlaRec->holdSteadyUs(m_SocketID));
+ }
+
vector lossdata;
+ int confirmed_loss = 0;
+ int64_t dbg_hold_us = 0, dbg_spacing_us = 0; size_t dbg_records = 0; // for the debug log below
{
ScopedLock lk(m_RcvLossLock);
- const int cap = m_iMaxSRTPayloadSize / 4;
- int32_t* arr = new int32_t[cap];
- int arrlen = 0;
- m_pRcvLossList->getLossArray(arr, arrlen, cap);
- const steady_clock::duration max_age = milliseconds_from(250);
- for (int n = 0; n < arrlen; )
+
+ const int64_t srtt_us = m_iSRTT.load();
+
+ SrtlaNakParams np;
+ np.hold_us = srtlaReorderHoldUs(currtime);
+ // A round trip plus the measured link spread: SRTT tracks the fastest path, the
+ // retransmission may travel the slowest. The floored, evidence-raised hold is
+ // deliberately not used here (it doubled the wait under load).
+ np.spacing_us = std::max(srtt_us + std::max(m_uiSrtlaHoldSteadyUs.load(), 20000),
+ count_microseconds(m_tdNAKInterval));
+ // No TSBPD means no play deadline and no TLPKTDROP: deadline handling off.
+ np.budget_us = m_bTsbPd ? (int64_t)m_iTsbPdDelay_ms * 1000 : 0;
+ np.rtt_us = srtt_us;
+ np.margin_us = SRTLA_DEADLINE_MARGIN_US;
+ np.cap = (size_t)m_iMaxDataPayloadSize / sizeof(int32_t);
+
+ const SrtlaNakPlan plan = srtlaPlanNak(m_FreshLoss, currtime, np);
+
+ lossdata.reserve(2 * plan.report.size());
+ for (size_t k = 0; k < plan.report.size(); ++k)
{
- int32_t lo, hi;
- if (arr[n] & LOSSDATA_SEQNO_RANGE_FIRST)
- {
- lo = arr[n] & ~LOSSDATA_SEQNO_RANGE_FIRST;
- hi = arr[n + 1];
- n += 2;
- }
- else
- {
- lo = hi = arr[n];
- n += 1;
- }
- int32_t runStart = SRT_SEQNO_NONE;
- for (int32_t s = lo; ; s = CSeqNo::incseq(s))
- {
- bool fresh = false;
- for (size_t k = 0; k < m_FreshLoss.size(); ++k)
- {
- if (CSeqNo::seqcmp(s, m_FreshLoss[k].seq[0]) >= 0 && CSeqNo::seqcmp(s, m_FreshLoss[k].seq[1]) <= 0)
- {
- if (currtime - m_FreshLoss[k].timestamp < max_age)
- fresh = true;
- break;
- }
- }
- if (!fresh)
- {
- if (runStart == SRT_SEQNO_NONE)
- runStart = s;
- }
- else if (runStart != SRT_SEQNO_NONE)
- {
- addLossRecord(lossdata, runStart, CSeqNo::decseq(s));
- runStart = SRT_SEQNO_NONE;
- }
- if (s == hi)
- break;
- }
- if (runStart != SRT_SEQNO_NONE)
- addLossRecord(lossdata, runStart, hi);
+ CRcvFreshLoss& rec = m_FreshLoss[plan.report[k]];
+ addLossRecord(lossdata, rec.seq[0], rec.seq[1]);
+ HLOGC(qrlog.Debug, log << CONID() << "SRTLA LOSSTRACE request " << (is_zero(rec.report_time) ? "first" : "repeat")
+ << " %" << rec.seq[0] << "-%" << rec.seq[1] << " age " << (count_microseconds(currtime - rec.timestamp) / 1000)
+ << " ms remaining " << ((np.budget_us - count_microseconds(currtime - rec.timestamp) - np.rtt_us - np.margin_us) / 1000)
+ << " ms hold " << (np.hold_us / 1000) << " ms spacing " << (np.spacing_us / 1000) << " ms");
+ rec.report_time = currtime;
+ }
+ confirmed_loss = plan.confirmed;
+ dbg_hold_us = np.hold_us; dbg_spacing_us = np.spacing_us; dbg_records = plan.report.size();
+
+ // The indices above address the untouched container, so retire only now.
+ if (plan.retire > 0)
+ {
+ HLOGC(qrlog.Debug,
+ log << CONID() << "SRTLA: retiring " << plan.retire << " loss record(s) past the "
+ << m_iTsbPdDelay_ms << "ms play budget (" << m_FreshLoss.size() << " held)");
+ m_FreshLoss.erase(m_FreshLoss.begin(), m_FreshLoss.begin() + plan.retire);
}
- delete[] arr;
}
if (!lossdata.empty())
{
sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], (int)lossdata.size());
+ countConfirmedLoss(confirmed_loss);
debug_decision = BECAUSE_NAKREPORT;
+ HLOGC(qrlog.Debug, log << CONID() << "SRTLA: loss report with " << dbg_records << " record(s), hold "
+ << (dbg_hold_us / 1000) << " ms, spacing " << (dbg_spacing_us / 1000) << " ms, timer period "
+ << count_milliseconds(m_tdNAKInterval) << " ms");
}
}
else
@@ -11537,7 +12346,10 @@ int srt::CUDT::checkNAKTimer(const steady_clock::time_point& currtime)
}
}
- m_tsNextNAKTime.store(currtime + m_tdNAKInterval);
+ steady_clock::duration next = m_tdNAKInterval;
+ if (srtla_emitter && next > microseconds_from(SRTLA_NAK_PERIOD_US))
+ next = microseconds_from(SRTLA_NAK_PERIOD_US);
+ m_tsNextNAKTime.store(currtime + next);
return debug_decision;
}
@@ -11715,6 +12527,31 @@ void srt::CUDT::checkRexmitTimer(const steady_clock::time_point& currtime)
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
}
+void srt::CUDT::updateQualityWindow(const steady_clock::time_point& currtime)
+{
+ ScopedLock stat_lock(m_StatsLock);
+
+ if (count_microseconds(currtime - m_stats.tsQualityWindow) < QUALITY_WINDOW_US)
+ return;
+
+ const int64_t snd_clean = m_stats.sndr.sentUnique.total.count();
+ const int64_t snd_impaired = m_stats.sndr.sentRetrans.total.count()
+ + m_stats.sndr.dropped.total.count();
+ const int64_t rcv_clean = m_stats.rcvr.recvdUnique.total.count();
+ const int64_t rcv_impaired = m_stats.rcvr.lostConfirmed.total.count();
+
+ m_stats.pctSndQuality = StatsQualityPct(snd_clean - m_stats.qualBaseSndClean,
+ snd_impaired - m_stats.qualBaseSndImpaired);
+ m_stats.pctRcvQuality = StatsQualityPct(rcv_clean - m_stats.qualBaseRcvClean,
+ rcv_impaired - m_stats.qualBaseRcvImpaired);
+
+ m_stats.qualBaseSndClean = snd_clean;
+ m_stats.qualBaseSndImpaired = snd_impaired;
+ m_stats.qualBaseRcvClean = rcv_clean;
+ m_stats.qualBaseRcvImpaired = rcv_impaired;
+ m_stats.tsQualityWindow = currtime;
+}
+
void srt::CUDT::checkTimers()
{
// update CC parameters
@@ -11722,6 +12559,8 @@ void srt::CUDT::checkTimers()
const steady_clock::time_point currtime = steady_clock::now();
+ updateQualityWindow(currtime);
+
// This is a very heavy log, unblock only for temporary debugging!
#if 0
HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_tsNextACKTime)
@@ -11729,6 +12568,11 @@ void srt::CUDT::checkTimers()
<< " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount);
#endif
+#ifdef ENABLE_RATE_MEASUREMENT
+ m_SndRegularMeasurement.pickup(currtime);
+ m_SndRexmitMeasurement.pickup(currtime);
+#endif
+
// Check if it is time to send ACK
int debug_decision = checkACKTimer(currtime);
@@ -11748,7 +12592,7 @@ void srt::CUDT::checkTimers()
#if ENABLE_BONDING
if (m_parent->m_GroupOf)
{
- ScopedLock glock (uglobal().m_GlobControlLock);
+ SharedLock glock (uglobal().m_GlobControlLock);
if (m_parent->m_GroupOf)
{
// Pass socket ID because it's about changing group socket data
@@ -11765,6 +12609,7 @@ void srt::CUDT::checkTimers()
void srt::CUDT::updateBrokenConnection()
{
+ HLOGC(smlog.Debug, log << "updateBrokenConnection: setting closing=true and taking out epoll events");
m_bClosing = true;
releaseSynch();
// app can call any UDT API to learn the connection_broken error
@@ -11779,7 +12624,7 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode)
#if ENABLE_BONDING
bool pending_broken = false;
{
- ScopedLock guard_group_existence (uglobal().m_GlobControlLock);
+ SharedLock guard_group_existence (uglobal().m_GlobControlLock);
if (m_parent->m_GroupOf)
{
token = m_parent->m_GroupMemberData->token;
@@ -11814,7 +12659,7 @@ void srt::CUDT::completeBrokenConnectionDependencies(int errorcode)
// existence of the group will not be changed during
// the operation. The attempt of group deletion will
// have to wait until this operation completes.
- ScopedLock lock(uglobal().m_GlobControlLock);
+ ExclusiveLock lock(uglobal().m_GlobControlLock);
CUDTGroup* pg = m_parent->m_GroupOf;
if (pg)
{
@@ -11951,7 +12796,7 @@ int64_t srt::CUDT::socketStartTime(SRTSOCKET u)
if (!s)
return APIError(MJ_NOTSUP, MN_SIDINVAL);
- return count_microseconds(s->core().m_stats.tsStartTime.time_since_epoch());
+ return count_microseconds(s->core().socketStartTime().time_since_epoch());
}
bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt)
@@ -11994,7 +12839,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak
if (!bytelen || bytelen > CSrtConfig::MAX_SID_LENGTH)
{
LOGC(cnlog.Error,
- log << CONID() << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > "
+ log << CONID() << FUNID() << ": STREAMID length " << bytelen << " is 0 or > "
<< +CSrtConfig::MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING");
return false;
}
@@ -12059,7 +12904,7 @@ bool srt::CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShak
return true;
}
-void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival)
+bool srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival)
{
// Here can be handled some protocol definition
// for extra data sent through keepalive.
@@ -12072,7 +12917,7 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr
// existence of the group will not be changed during
// the operation. The attempt of group deletion will
// have to wait until this operation completes.
- ScopedLock lock(uglobal().m_GlobControlLock);
+ ExclusiveLock lock(uglobal().m_GlobControlLock);
CUDTGroup* pg = m_parent->m_GroupOf;
if (pg)
{
@@ -12088,4 +12933,21 @@ void srt::CUDT::processKeepalive(const CPacket& ctrlpkt, const time_point& tsArr
m_pRcvBuffer->updateTsbPdTimeBase(ctrlpkt.getMsgTimeStamp());
if (m_config.bDriftTracer)
m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), tsArrival, -1);
+
+ return true;
+}
+
+namespace srt {
+HandshakeSide getHandshakeSide(SRTSOCKET u)
+{
+ return CUDT::handshakeSide(u);
+}
+
+HandshakeSide CUDT::handshakeSide(SRTSOCKET u)
+{
+ CUDTSocket *s = uglobal().locateSocket(u);
+ return s ? s->core().handshakeSide() : HSD_DRAW;
+}
}
+
+
diff --git a/srtcore/core.h b/srtcore/core.h
index 1bfcb76fbb..9b97e3d3d9 100644
--- a/srtcore/core.h
+++ b/srtcore/core.h
@@ -149,16 +149,142 @@ enum SeqPairItems
};
-// Extended SRT Congestion control class - only an incomplete definition required
-class CCryptoControl;
namespace srt {
+class CCryptoControl;
class CUDTUnited;
class CUDTSocket;
#if ENABLE_BONDING
class CUDTGroup;
#endif
+#ifdef ENABLE_RATE_MEASUREMENT
+struct RateMeasurement
+{
+ typedef sync::steady_clock clock_type;
+ typedef clock_type::time_point clock_time;
+ typedef clock_type::duration clock_interval;
+
+ static const int SLICE_INTERVAL_MS = 20;
+ static const size_t MIN_SLICES = 5; // min
+ static const size_t MAX_SLICES = 10;
+
+ sync::Mutex m_lock;
+
+ size_t m_SysHeaderSize;
+
+ // Instantaneous data updated by sending packets
+ // AFFINITY: Snd:worker thread
+ // MODE: write
+ sync::atomic m_nInstaPackets;
+ sync::atomic m_nInstaBytes;
+
+ void dataUpdate(int packets, int bytes)
+ {
+ sync::ScopedLock lk (m_lock);
+ m_nInstaPackets = m_nInstaPackets + packets;
+ m_nInstaBytes = m_nInstaBytes + bytes;
+ }
+
+ // Cached last measurement
+ // AFFINITY: Snd:worker thread MODE: read
+ // AFFINITY: Rcv:worker thread MODE: write
+ // (not locking because one data is updated at a time).
+ sync::atomic m_currentPktRate;
+ sync::atomic m_currentByteRate;
+ int64_t rateBytes() const
+ {
+ return m_currentByteRate;
+ }
+ int64_t ratePackets() const
+ {
+ return m_currentPktRate;
+ }
+
+ // Time of the last checkpoint or initialization
+ clock_time m_beginTime;
+
+ struct Slice
+ {
+ int packets;
+ int bytes;
+ clock_time begin_time;
+
+ Slice(): packets(0), bytes(0) {} // clock_time is non-POD
+ Slice(const clock_time& t): packets(0), bytes(0), begin_time(t) {}
+
+ };
+
+ struct Summary: Slice
+ {
+ Summary(const clock_time& earliest_time): Slice(earliest_time), nsamples(0) {}
+
+ unsigned char nsamples;
+
+ void consume(const Slice& another)
+ {
+ if (is_zero(begin_time))
+ begin_time = another.begin_time;
+ packets += another.packets;
+ bytes += another.bytes;
+ nsamples++;
+ }
+
+ int64_t ratePackets(const clock_time& end_time) const
+ {
+ double packets_per_micro = double(packets) * 1000 * 1000;
+ double rate = packets_per_micro / count_microseconds(end_time - begin_time);
+ return rate;
+ }
+
+ int64_t rateBytes(const clock_time& end_time, size_t hdr_size) const
+ {
+ double data_bytes = bytes + (packets * hdr_size);
+ double bytes_per_micro = data_bytes * 1000 * 1000;
+ double rate = bytes_per_micro / count_microseconds(end_time - begin_time);
+ return rate;
+ }
+ };
+
+ std::deque m_slices;
+
+ RateMeasurement():
+ m_SysHeaderSize(CPacket::UDP_HDR_SIZE), // XXX NOTE: IPv4 !
+ m_nInstaPackets(0),
+ m_nInstaBytes(0),
+ m_currentPktRate(0),
+ m_currentByteRate(0)
+ {
+ }
+
+ bool passedInterval(const clock_time& this_time, clock_interval& w_interval)
+ {
+ if (is_zero(m_beginTime))
+ {
+ sync::ScopedLock lk (m_lock);
+ m_beginTime = this_time;
+ w_interval = clock_interval(0);
+ return false;
+ }
+ w_interval = this_time - m_beginTime;
+ return (sync::count_milliseconds(w_interval) > SLICE_INTERVAL_MS);
+ }
+
+ // This is to be called in constructor, only once.
+ void init(const clock_time& time, size_t sys_hdr_size)
+ {
+ // Just formally.
+ sync::ScopedLock lk (m_lock);
+ m_beginTime = time;
+ m_SysHeaderSize = sys_hdr_size;
+ }
+
+ // AFFINITY: Rcv:worker thread (update thread)
+ // This function should be called in regular time periods.
+ void pickup(const clock_time& time);
+};
+#endif
+
// XXX REFACTOR: The 'CUDT' class is to be merged with 'CUDTSocket'.
// There's no reason for separating them, there's no case of having them
// anyhow managed separately. After this is done, with a small help with
@@ -181,13 +307,14 @@ class CUDT
friend class PacketFilter;
friend class CUDTGroup;
friend class TestMockCUDT; // unit tests
+ friend class TestMockControlPackets; // unit tests
typedef sync::steady_clock::time_point time_point;
typedef sync::steady_clock::duration duration;
typedef sync::AtomicClock atomic_time_point;
typedef sync::AtomicDuration atomic_duration;
-private: // constructor and desctructor
+private: // constructor and destructor
void construct();
void clearData();
CUDT(CUDTSocket* parent);
@@ -198,6 +325,7 @@ class CUDT
public: //API
static int startup();
static int cleanup();
+ static int cleanupAtFork();
static SRTSOCKET socket();
#if ENABLE_BONDING
static SRTSOCKET createGroup(SRT_GROUP_TYPE);
@@ -295,6 +423,17 @@ class CUDT
return m_ConnRes.m_iVersion;
}
+ int32_t handshakeCookie()
+ {
+ return m_ConnReq.m_iCookie;
+ }
+
+ static HandshakeSide handshakeSide(SRTSOCKET u);
+ HandshakeSide handshakeSide()
+ {
+ return m_SrtHsSide;
+ }
+
std::string CONID() const
{
#if ENABLE_LOGGING
@@ -338,7 +477,7 @@ class CUDT
uint32_t peerLatency_us() const { return m_iPeerTsbPdDelay_ms * 1000; }
int peerIdleTimeout_ms() const { return m_config.iPeerIdleTimeout_ms; }
- size_t maxPayloadSize() const { return m_iMaxSRTPayloadSize; }
+ size_t maxDataPayloadSize() const { return m_iMaxDataPayloadSize; }
size_t OPT_PayloadSize() const { return m_config.zExpPayloadSize; }
size_t payloadSize() const
{
@@ -351,7 +490,7 @@ class CUDT
// If SRTO_PAYLOADSIZE was remaining with 0 (default for FILE mode)
// then return the maximum payload size per packet.
- return m_iMaxSRTPayloadSize;
+ return m_iMaxDataPayloadSize;
}
int sndLossLength() { return m_pSndLossList->getLossLength(); }
@@ -399,12 +538,21 @@ class CUDT
int minSndSize(int len = 0) const
{
- const int ps = (int) maxPayloadSize();
+ const int ps = (int) maxDataPayloadSize();
if (len == 0) // weird, can't use non-static data member as default argument!
len = ps;
return m_config.bMessageAPI ? (len+ps-1)/ps : 1;
}
+ // This returns the biggest possible size for an SRT payload with
+ // the default 1500 MTU size. When in doubt, use AF_INET, which returns
+ // bigger size, if needed for a safe allocation.
+ static int controlPayloadSize(int family = AF_INET)
+ {
+ int header = family == AF_INET6 ? CPacket::UDP_HDR_SIZE_IPv6 : CPacket::UDP_HDR_SIZE;
+ return CPacket::ETH_MAX_MTU_SIZE - header;
+ }
+
static int32_t makeTS(const time_point& from_time, const time_point& tsStartTime)
{
// NOTE:
@@ -438,7 +586,6 @@ class CUDT
// immediately to free the socket
void notListening()
{
- sync::ScopedLock cg(m_ConnectionLock);
m_bListening = false;
m_pRcvQueue->removeListener(this);
}
@@ -584,11 +731,14 @@ class CUDT
SRT_ATTR_REQUIRES2(m_RecvAckLock, m_StatsLock)
int sndDropTooLate();
- /// @bried Allow packet retransmission.
- /// Depending on the configuration mode (live / file), retransmission
- /// can be blocked if e.g. there are original packets pending to be sent.
- /// @return true if retransmission is allowed; false otherwise.
- bool isRetransmissionAllowed(const time_point& tnow);
+ // Returns true if there is a regular packet waiting for sending
+ // and sending regular packets has priority over retransmitted ones.
+ bool isRegularSendingPriority();
+
+ // Performs updates in the measurement variables after possible
+ // extraction of a retransmission packet. Some are for debug purposes,
+ // others for retransmission rate measurement.
+ void updateSenderMeasurements(bool can_rexmit);
/// Connect to a UDT entity as per hs request. This will update
/// required data in the entity, then update them also in the hs structure,
@@ -607,6 +757,7 @@ class CUDT
/// Close the opened UDT entity.
bool closeInternal() ATR_NOEXCEPT;
+ bool closeAtFork() ATR_NOEXCEPT;
void updateBrokenConnection();
void completeBrokenConnectionDependencies(int errorcode);
@@ -739,7 +890,7 @@ class CUDT
int sndSpaceLeft()
{
- return static_cast(sndBuffersLeft() * maxPayloadSize());
+ return static_cast(sndBuffersLeft() * maxDataPayloadSize());
}
int sndBuffersLeft()
@@ -749,6 +900,7 @@ class CUDT
time_point socketStartTime()
{
+ sync::ScopedLock lk (m_StatsLock);
return m_stats.tsStartTime;
}
@@ -805,7 +957,7 @@ class CUDT
#endif
private:
- int m_iMaxSRTPayloadSize; // Maximum/regular payload size, in bytes
+ int m_iMaxDataPayloadSize; // Maximum data payload size, in bytes
int m_iTsbPdDelay_ms; // Rx delay to absorb burst, in milliseconds
int m_iPeerTsbPdDelay_ms; // Tx delay that the peer uses to absorb burst, in milliseconds
bool m_bTLPktDrop; // Enable Too-late Packet Drop
@@ -831,6 +983,7 @@ class CUDT
sync::atomic m_bConnected; // Whether the connection is on or off
sync::atomic m_bClosing; // If the UDT entity is closing
sync::atomic m_bShutdown; // If the peer side has shutdown the connection
+ sync::atomic m_bBreaking; // The flag that declares interrupt of the connecting process
sync::atomic m_bBroken; // If the connection has been broken
sync::atomic m_bBreakAsUnstable; // A flag indicating that the socket should become broken because it has been unstable for too long.
sync::atomic m_bPeerHealth; // If the peer status is normal
@@ -862,7 +1015,16 @@ class CUDT
CSndLossList* m_pSndLossList; // Sender loss list
CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window
#ifdef ENABLE_MAXREXMITBW
- CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation.
+ size_t m_zSndAveragePacketSize;
+ size_t m_zSndMaxPacketSize;
+ // XXX Old rate estimator for rexmit
+ // CSndRateEstimator m_SndRexmitRate; // Retransmission rate estimation.
+ CShaper m_SndRexmitShaper;
+
+#ifdef ENABLE_RATE_MEASUREMENT
+ RateMeasurement m_SndRegularMeasurement; // Regular rate measurement
+ RateMeasurement m_SndRexmitMeasurement; // Retransmission rate measurement
+#endif
#endif
atomic_duration m_tdSendInterval; // Inter-packet time, in CPU clock cycles
@@ -974,6 +1136,16 @@ class CUDT
SRT_ATTR_GUARDED_BY(m_RcvLossLock)
std::deque m_FreshLoss; //< Lost sequence already added to m_pRcvLossList, but not yet sent UMSG_LOSSREPORT for.
+ // SRTLA loss-report timing state (bSRTLA only).
+ sync::atomic m_uiSrtlaHoldSteadyUs;
+ SRT_ATTR_GUARDED_BY(m_RcvLossLock)
+ uint32_t m_uiSrtlaHoldFastUs;
+ SRT_ATTR_GUARDED_BY(m_RcvLossLock)
+ sync::steady_clock::time_point m_tsSrtlaHoldFastSet;
+ sync::steady_clock::time_point m_tsSrtlaHoldPollTime;
+
+ uint32_t srtlaReorderHoldUs(const sync::steady_clock::time_point& now);
+
int m_iReorderTolerance; //< Current value of dynamic reorder tolerance
int m_iConsecEarlyDelivery; //< Increases with every OOO packet that came >& losslist);
- void processCtrl(const CPacket& ctrlpkt);
+ /// Record packets that are being requested back for the first time, that is,
+ /// that were still missing once the reorder grace period was over. Feeds
+ /// pctRcvQuality. NAK repeats must not be passed here.
+ /// Takes m_StatsLock, so the caller must not hold it.
+ /// @param pkts number of sequence numbers confirmed as lost
+ void countConfirmedLoss(int pkts);
+
+ bool processCtrl(const CPacket& ctrlpkt);
/// @brief Process incoming control ACK packet.
/// @param ctrlpkt incoming ACK packet
/// @param currtime current clock time
- void processCtrlAck(const CPacket& ctrlpkt, const time_point& currtime);
+ bool processCtrlAck(const CPacket& ctrlpkt, const time_point& currtime);
/// @brief Process incoming control ACKACK packet.
/// @param ctrlpkt incoming ACKACK packet
/// @param tsArrival time when packet has arrived (used to calculate RTT)
- void processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival);
+ bool processCtrlAckAck(const CPacket& ctrlpkt, const time_point& tsArrival);
/// @brief Process incoming loss report (NAK) packet.
/// @param ctrlpkt incoming NAK packet
- void processCtrlLossReport(const CPacket& ctrlpkt);
+ bool processCtrlLossReport(const CPacket& ctrlpkt);
/// @brief Process incoming handshake control packet
/// @param ctrlpkt incoming HS packet
- void processCtrlHS(const CPacket& ctrlpkt);
+ bool processCtrlHS(const CPacket& ctrlpkt);
/// @brief Process incoming drop request control packet
/// @param ctrlpkt incoming drop request packet
- void processCtrlDropReq(const CPacket& ctrlpkt);
+ bool processCtrlDropReq(const CPacket& ctrlpkt);
/// @brief Process incoming shutdown control packet
- void processCtrlShutdown();
+ bool processCtrlShutdown();
/// @brief Process incoming user defined control packet
/// @param ctrlpkt incoming user defined packet
- void processCtrlUserDefined(const CPacket& ctrlpkt);
+ bool processCtrlUserDefined(const CPacket& ctrlpkt);
/// @brief Update sender's loss list on an incoming acknowledgement.
/// @param ackdata_seqno sequence number of a data packet being acknowledged
@@ -1117,6 +1299,11 @@ class CUDT
/// @return payload size on success, <=0 on failure
int packLostData(CPacket &packet);
+ std::pair getCleanRexmitOffset();
+ bool checkRexmitRightTime(int offset, const srt::sync::steady_clock::time_point& current_time);
+ int extractCleanRexmitPacket(int32_t seqno, int offset, CPacket& w_packet,
+ srt::sync::steady_clock::time_point& w_tsOrigin);
+
/// Pack a unique data packet (never sent so far) in CPacket for sending.
/// @param packet [in, out] a CPacket structure to fill.
///
@@ -1175,7 +1362,7 @@ class CUDT
static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi);
int32_t bake(const sockaddr_any& addr, int32_t previous_cookie = 0, int correction = 0);
- void processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival);
+ bool processKeepalive(const CPacket& ctrlpkt, const time_point& tsArrival);
SRT_ATTR_REQUIRES(m_RcvBufferLock)
@@ -1199,8 +1386,24 @@ class CUDT
int64_t sndDuration; // real time for sending
time_point sndDurationCounter; // timers to record the sending Duration
+ // Quality sampling window. Rolled on a fixed cadence by the timer thread
+ // (not by a reader), so the reported percentages describe the last
+ // completed window and stay identical for every read within it.
+ time_point tsQualityWindow; // start of the window currently filling
+ int64_t qualBaseSndClean; // cumulative counters snapshotted at window start
+ int64_t qualBaseSndImpaired;
+ int64_t qualBaseRcvClean;
+ int64_t qualBaseRcvImpaired;
+ double pctSndQuality; // last completed window, percent
+ double pctRcvQuality;
+
} m_stats;
+ /// Roll the quality sampling window if it is due, and recompute the
+ /// percentages from what accumulated inside it. Called from the timer
+ /// thread; takes m_StatsLock.
+ void updateQualityWindow(const time_point& currtime);
+
public:
static const int SELF_CLOCK_INTERVAL = 64; // ACK interval for self-clocking
static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq
@@ -1247,6 +1450,9 @@ class CUDT
void removeEPollID(const int eid);
};
+// DEBUG SUPPORT
+HandshakeSide getHandshakeSide(SRTSOCKET s);
+
} // namespace srt
#endif
diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp
index e7fe0be895..c6b18911a1 100644
--- a/srtcore/crypto.cpp
+++ b/srtcore/crypto.cpp
@@ -81,7 +81,7 @@ void srt::CCryptoControl::globalInit()
{
#ifdef SRT_ENABLE_ENCRYPTION
// We need to force the Cryspr to be initialized during startup to avoid the
- // possibility of multiple threads initialzing the same static data later on.
+ // possibility of multiple threads initializing the same static data later on.
HaiCryptCryspr_Get_Instance();
#endif
}
@@ -135,250 +135,302 @@ void srt::CCryptoControl::createFakeSndContext()
}
}
+#ifdef SRT_ENABLE_ENCRYPTION
+
int srt::CCryptoControl::processSrtMsg_KMREQ(
- const uint32_t* srtdata SRT_ATR_UNUSED,
- size_t bytelen SRT_ATR_UNUSED,
- int hsv SRT_ATR_UNUSED, unsigned srtv SRT_ATR_UNUSED,
+ const uint32_t* srtdata, size_t bytelen, int hsv, unsigned srtv,
uint32_t pw_srtdata_out[], size_t& w_srtlen)
{
- //Receiver
- /* All 32-bit msg fields swapped on reception
- * But HaiCrypt expect network order message
- * Re-swap to cancel it.
- */
-#ifdef SRT_ENABLE_ENCRYPTION
- w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]);
- HtoNLA((pw_srtdata_out), srtdata, w_srtlen);
unsigned char* kmdata = reinterpret_cast(pw_srtdata_out);
// The side that has received KMREQ is always an HSD_RESPONDER, regardless of
// what has called this function. The HSv5 handshake only enforces bidirectional
// connection.
+ // They actually mean the same thing in HSv5, but removal of bidirectional can be
+ // only done when the compat with <1.3.0 is allowed to be broken.
const bool bidirectional = hsv > CUDT::HS_VERSION_UDT4;
+ const bool kmx_update = m_hRcvCrypto;
+ SRT_KM_STATE failure_state = m_KmSecret.len == 0 ? SRT_KM_S_NOSECRET : SRT_KM_S_BADSECRET;
+ bool bUseGCM = false;
- // Local macro to return rejection appropriately.
- // CHANGED. The first version made HSv5 reject the connection.
- // This isn't well handled by applications, so the connection is
- // still established, but unable to handle any transport.
-//#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { w_srtlen = 1; goto HSv4_ErrorReport; }
-#define KMREQ_RESULT_REJECTION() { w_srtlen = 1; goto HSv4_ErrorReport; }
-
- int rc = HAICRYPT_OK; // needed before 'goto' run from KMREQ_RESULT_REJECTION macro
- bool wasb4 SRT_ATR_UNUSED = false;
- size_t sek_len = 0;
-
- const bool bUseGCM =
- (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM) ||
- (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM);
-
- m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3);
-
- // What we have to do:
- // If encryption is on (we know that by having m_KmSecret nonempty), create
- // the crypto context (if bidirectional, create for both sending and receiving).
- // Both crypto contexts should be set with the same length of the key.
- // The problem with interpretinting this should be reported as SRT_CMD_NONE,
- // should be appropriately handled by the caller, as it expects that this
- // function normally return SRT_CMD_KMRSP.
- if ( bytelen <= HCRYPT_MSG_KM_OFS_SALT ) //Sanity on message
+ // TRY-BLOCK, with THROW done by "goto Error".
{
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT);
- m_RcvKmState = SRT_KM_S_BADSECRET;
- KMREQ_RESULT_REJECTION();
- }
+ if (bytelen % sizeof(uint32_t) != 0 || bytelen > HCRYPT_MSG_KM_MAX_SZ)
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too high, must be < " << HCRYPT_MSG_KM_MAX_SZ);
+ goto Error;
+ }
- HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto");
- sek_len = hcryptMsg_KM_GetSekLen(kmdata);
- if ( sek_len == 0 )
- {
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!");
- m_RcvKmState = SRT_KM_S_BADSECRET;
- KMREQ_RESULT_REJECTION();
- }
+ // Default successful settings: original length and contents
+ w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]);
- // Write the key length
- m_iRcvKmKeyLen = sek_len;
- // Overwrite the key length anyway - it doesn't make sense to somehow
- // keep the original setting because it will only make KMX impossible.
-#if ENABLE_HEAVY_LOGGING
- if (m_iSndKmKeyLen != m_iRcvKmKeyLen)
- {
- LOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen
- << " overwritten by Peer's PBKEYLEN=" << m_iRcvKmKeyLen);
- }
-#endif
- m_iSndKmKeyLen = m_iRcvKmKeyLen;
+ /* All 32-bit msg fields swapped on reception
+ * But HaiCrypt expect network order message
+ * Re-swap to cancel it.
+ */
+ HtoNLA((pw_srtdata_out), srtdata, w_srtlen);
- // This is checked only now so that the SRTO_PBKEYLEN return always the correct value,
- // even if encryption is not possible because Agent didn't set a password, or supplied
- // a wrong password.
- if (m_KmSecret.len == 0) //We have a shared secret <==> encryption is on
- {
- LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- KMREQ_RESULT_REJECTION();
- }
- wasb4 = m_hRcvCrypto;
+ if (!kmx_update) // Only in initial/handshake
+ {
+ // If this is NOT changed anywhere later, failure_state value will be used
+ m_RcvKmState = SRT_KM_S_SECURING;
+ }
- if (!createCryptoCtx((m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM))
- {
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject...");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- KMREQ_RESULT_REJECTION();
- }
+ if ((m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM)
+ || (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM))
+ bUseGCM = true;
- // Deduce resulting mode.
- m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR;
+ m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3);
- if (!wasb4)
- {
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen);
- }
- // We have both sides set with password, so both are pending for security
- m_RcvKmState = SRT_KM_S_SECURING;
- // m_SndKmState is set to SECURING or UNSECURED in init(),
- // or it might have been set to SECURED, NOSECRET or BADSECRET in the previous
- // handshake iteration (handshakes may be sent multiple times for the same connection).
-
- rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0);
- switch(rc >= 0 ? HAICRYPT_OK : rc)
- {
- case HAICRYPT_OK:
- m_RcvKmState = SRT_KM_S_SECURED;
- HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED.");
- //Send back the whole message to confirm
- break;
- case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key
- m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET;
- //Send status KMRSP message to tel error
- w_srtlen = 1;
- LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET");
- break;
- case HAICRYPT_ERROR_CIPHER:
+ // INITIAL ACTIONS (first time KMREQ received):
+ // If encryption is on (we know that by having m_KmSecret nonempty), create
+ // the crypto context (if bidirectional, create for both sending and receiving).
+ // Both crypto contexts should be set with the same length of the key.
+ // Report SRT_CMD_NONE if no response is to be sent or SRT_CMD_KMRSP if the
+ // response is filled and ready (including erroneous).
+ if (bytelen <= HCRYPT_MSG_KM_OFS_SALT) //Sanity on message
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT);
+ goto Error;
+ }
+
+ size_t sek_len = hcryptMsg_KM_GetSekLen(kmdata);
+ if (sek_len == 0)
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!");
+ goto Error;
+ }
+
+ bool new_keylen = m_iSndKmKeyLen != sek_len;
+
+ if (kmx_update && new_keylen)
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: KMX refresh came in with a DIFFERENT KEY LEN: " << sek_len);
+ goto Error;
+ }
+
+ HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto sek_len=" << sek_len
+ << " local=" << m_iSndKmKeyLen
+ << (new_keylen ? " - OVERRIDE with received SEK len" : ""));
+
+ // Write the key length.
+ // Overwrite the key length anyway - it doesn't make sense to somehow
+ // keep the original setting because it will only make KMX impossible.
+ m_iSndKmKeyLen = m_iRcvKmKeyLen = sek_len;
+
+ // This is checked only now so that the SRTO_PBKEYLEN return always the correct value,
+ // even if encryption is not possible because Agent didn't set a password, or supplied
+ // a wrong password.
+ if (!hasPassphrase()) //We have a shared secret <==> encryption is on
+ {
+ LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!");
+ goto Error;
+ }
+
+ if (!createCryptoCtx((m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM))
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject...");
+ failure_state = SRT_KM_S_NOSECRET;
+ goto Error;
+ }
+
+ if (!kmx_update)
+ {
+ // Deduce resulting mode.
+ m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR;
+ HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen);
+ }
+
+ // We have both sides set with password, so both are pending for security
+ int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0);
+ LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen) << " result: " << rc);
+
+ // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE,
+ // until the next KMREQ is received as a key regeneration.
+ m_bErrorReported = false;
+
+ if (rc >= HAICRYPT_OK)
+ {
+ m_RcvKmState = SRT_KM_S_SECURED;
+ HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED.");
+ }
+ else
+ {
+ switch(rc)
+ {
+ case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key
+ failure_state = SRT_KM_S_BADSECRET;
+ //Send status KMRSP message to tel error
+ LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET");
+ break;
+ case HAICRYPT_ERROR_CIPHER:
#ifdef ENABLE_AEAD_API_PREVIEW
- m_RcvKmState = m_SndKmState = SRT_KM_S_BADCRYPTOMODE;
+ failure_state = SRT_KM_S_BADCRYPTOMODE;
#else
- m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback.
+ failure_state = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback.
#endif
- w_srtlen = 1;
- LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADCRYPTOMODE");
- break;
- case HAICRYPT_ERROR: //Other errors
- default:
- m_RcvKmState = m_SndKmState = SRT_KM_S_NOSECRET;
- w_srtlen = 1;
- LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET");
- break;
- }
-
- LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen));
-
- // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE,
- // until the next KMREQ is received as a key regeneration.
- m_bErrorReported = false;
+ LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADCRYPTOMODE");
+ break;
+ case HAICRYPT_ERROR: //Other errors
+ default:
+ failure_state = SRT_KM_S_NOSECRET;
+ LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET");
+ break;
+ }
- if (w_srtlen == 1)
- goto HSv4_ErrorReport;
+ if (!kmx_update)
+ {
+ // Only initially, set this also to SND state.
+ m_SndKmState = failure_state;
+ }
+ goto Error;
+ }
- // Configure the sender context also, if it succeeded to configure the
- // receiver context and we are using bidirectional mode.
- if (bidirectional)
- {
- // Note: 'bidirectional' means that we want a bidirectional key update,
- // which happens only and exclusively with HSv5 handshake - not when the
- // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used
- // in HSv4 versions also to initialize the first key, unlike HSv5).
- if (m_RcvKmState == SRT_KM_S_SECURED)
+ // Configure the sender context also, if it succeeded to configure the
+ // receiver context and we are using bidirectional mode.
+ if (bidirectional)
{
- if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto)
+ // Note: 'bidirectional' means that we want a bidirectional key update,
+ // which happens only and exclusively with HSv5 handshake - not when the
+ // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used
+ // in HSv4 versions also to initialize the first key, unlike HSv5).
+ if (m_RcvKmState == SRT_KM_S_SECURED)
{
- m_iSndKmKeyLen = m_iRcvKmKeyLen;
- if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK)
+ if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto)
{
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!");
- if (hasPassphrase())
- m_SndKmState = SRT_KM_S_BADSECRET;
+ m_iSndKmKeyLen = m_iRcvKmKeyLen;
+ if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK)
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!");
+ m_SndKmState = failure_state;
+ }
else
- m_SndKmState = SRT_KM_S_NOSECRET;
+ {
+ m_SndKmState = SRT_KM_S_SECURED;
+ }
+
+ LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)
+ << " SndKeyLen=" << m_iSndKmKeyLen
+ << " TX CRYPTO CTX CLONED FROM RX"
+ );
+
+ // Write the KM message into the field from which it will be next sent.
+ memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen);
+ m_SndKmMsg[0].MsgLen = bytelen;
+ m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :)
}
else
{
- m_SndKmState = SRT_KM_S_SECURED;
+ HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in "
+ << KmStateStr(m_SndKmState) << " state");
}
-
- LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)
- << " SndKeyLen=" << m_iSndKmKeyLen
- << " TX CRYPTO CTX CLONED FROM RX"
- );
-
- // Write the KM message into the field from which it will be next sent.
- memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen);
- m_SndKmMsg[0].MsgLen = bytelen;
- m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :)
}
else
{
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in "
- << KmStateStr(m_SndKmState) << " state");
+ HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX");
}
}
else
{
- HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX");
+ HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX.");
}
+
+ if (m_hRcvCrypto != NULL)
+ HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153);
+ if (m_hSndCrypto != NULL)
+ HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153);
+
+ // NOTE: The "loop" gets exit with return HERE.
+ return SRT_CMD_KMRSP;
}
- else
- {
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX.");
- }
+Error: // CATCH POINT
-#ifdef SRT_ENABLE_ENCRYPTION
- if (m_hRcvCrypto != NULL)
- HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153);
- if (m_hSndCrypto != NULL)
- HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153);
-#endif
+ // NOTE: This is set, but if we return NONE, this will not be
+ // taken into account anyhow. The state is returned this way only for
+ // unit tests so that they recognize the result of the call.
+ w_srtlen = 1;
+ pw_srtdata_out[SRT_KMR_KMSTATE] = failure_state;
- return SRT_CMD_KMRSP;
+ if (!kmx_update)
+ {
+ // Set the appropriate error, if it wasn't already set before
+ m_RcvKmState = failure_state;
+ if (bidirectional && hasPassphrase())
+ {
+ // If the Forward KMX process has failed, the reverse-KMX process was not done at all.
+ // This will lead to incorrect object configuration and will fail to properly declare
+ // the transmission state.
+ // Create the "fake crypto" with the passphrsae you currently have.
+ createFakeSndContext();
+ }
-HSv4_ErrorReport:
+ return SRT_CMD_KMRSP;
+ }
+ return SRT_CMD_NONE;
+}
- if (bidirectional && hasPassphrase())
+// RETURNS:
+// first: the converted state_value, or fallback if it was wrong
+// second: the value to be set in the opposite direction, if different
+inline std::pair ErraticKMState(uint32_t state_value)
+{
+ if (state_value >= uint32_t(SRT_KM_S_E_SIZE))
{
- // If the Forward KMX process has failed, the reverse-KMX process was not done at all.
- // This will lead to incorrect object configuration and will fail to properly declare
- // the transmission state.
- // Create the "fake crypto" with the passphrsae you currently have.
- createFakeSndContext();
+ LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer state value: " << state_value);
+ return std::make_pair(SRT_KM_S_BADSECRET, SRT_KM_S_BADSECRET);
}
-#undef KMREQ_RESULT_REJECTION
-#else
- // It's ok that this is reported as error because this happens in a scenario,
- // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT
- // application which tries to make a security association.
- LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject...");
- m_RcvKmState = SRT_KM_S_NOSECRET;
-#endif
+ SRT_KM_STATE state = SRT_KM_STATE(state_value);
+ switch (state)
+ {
+ case SRT_KM_S_NOSECRET:
+ return std::make_pair(state, SRT_KM_S_UNSECURED);
- w_srtlen = 1;
+ case SRT_KM_S_UNSECURED:
+ return std::make_pair(state, SRT_KM_S_NOSECRET);
- pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState;
- return SRT_CMD_KMRSP;
+ default: ; // do nothing
+ }
+ return std::make_pair(state, state);
}
-int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv)
+int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv, bool is_handshake)
{
+ uint32_t srtd[SRTDATA_MAXSIZE];
+ size_t srtlen = len/sizeof(uint32_t);
+ // Validate the wire-supplied length before using it:
+ // - oversize would overflow the fixed-size stack buffer below;
+ // - non-word-aligned or too-small payloads are malformed by protocol and would
+ // feed uninitialised stack into downstream key-matching logic.
+ if (srtlen > SRTDATA_MAXSIZE)
+ {
+ LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: malformed len " << len
+ << " (must be up to " << SRT_CMD_MAXSZ << ") - rejecting");
+ return SRT_CMD_NONE;
+ }
+
+ // Still handle HSv4 post-handshake-handshake. XXX: DEPRECATED. Remove in next major version.
+ if (!is_handshake)
+ {
+ // HSv4 version is unidirectional and encryption is only set in one direction.
+ // So, as KMRSP handler, the agent should be expected to be a sender, hence m_hSndCrypto
+ // should be created, but m_hRcvCrypto not. In case of HSv5, both should be simultaneously
+ // either NULL or valid pointers.
+ if (!m_hRcvCrypto && m_SndKmState != SRT_KM_S_SECURED)
+ is_handshake = true;
+
+ if (!m_hSndCrypto)
+ return SRT_CMD_NONE;
+ }
+
/* All 32-bit msg fields (if present) swapped on reception
* But HaiCrypt expect network order message
* Re-swap to cancel it.
*/
- uint32_t srtd[SRTDATA_MAXSIZE];
- size_t srtlen = len/sizeof(uint32_t);
HtoNLA(srtd, srtdata, srtlen);
- int retstatus = -1;
+ int retstatus = -1; // Error by default, unless all is confirmed
// Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE,
// until the next KMREQ is received as a key regeneration.
@@ -386,55 +438,30 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len
if (srtlen == 1) // Error report. Set accordingly.
{
- SRT_KM_STATE peerstate = SRT_KM_STATE(srtd[SRT_KMR_KMSTATE]); /* Bad or no passphrase */
- m_SndKmMsg[0].iPeerRetry = 0;
- m_SndKmMsg[1].iPeerRetry = 0;
-
- switch (peerstate)
- {
- case SRT_KM_S_BADSECRET:
- m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET;
- retstatus = -1;
- break;
-
- // Default embraces two cases:
- // NOSECRET: this KMRSP was sent by secured Peer, but Agent supplied no password.
- // UNSECURED: this KMRSP was sent by unsecure Peer because Agent sent KMREQ.
-
- case SRT_KM_S_NOSECRET:
- // This means that the peer did not set the password, while Agent did.
- m_RcvKmState = SRT_KM_S_UNSECURED;
- m_SndKmState = SRT_KM_S_NOSECRET;
- retstatus = -1;
- break;
-
- case SRT_KM_S_UNSECURED:
- // This means that KMRSP was sent without KMREQ, to inform the Agent,
- // that the Peer, unlike Agent, does use password. Agent can send then,
- // but can't decrypt what Peer would send.
- m_RcvKmState = SRT_KM_S_NOSECRET;
- m_SndKmState = SRT_KM_S_UNSECURED;
+ SRT_KM_STATE peerstate, revstate;
+ Tie2(peerstate, revstate) = ErraticKMState(srtd[SRT_KMR_KMSTATE]);
+ if (peerstate == SRT_KM_S_UNSECURED)
retstatus = 0;
- break;
-#ifdef ENABLE_AEAD_API_PREVIEW
- case SRT_KM_S_BADCRYPTOMODE:
- // The peer expects to use a different cryptographic mode (e.g. AES-GCM, not AES-CTR).
- m_RcvKmState = SRT_KM_S_BADCRYPTOMODE;
- m_SndKmState = SRT_KM_S_BADCRYPTOMODE;
- retstatus = -1;
- break;
-#endif
- default:
- LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer error state: "
- << KmStateStr(peerstate) << " (" << int(peerstate) << ")");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- m_SndKmState = SRT_KM_S_NOSECRET;
- retstatus = -1; //This is IPE
- break;
+ // If the erroneous KMRSP was received while the connection is established, we state
+ // the connection should be SECURED already, so no change of the state is done.
+ if (!is_handshake)
+ {
+ LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: rogue KMRSP received as KMX update - ignoring");
+ // Still proceed with the summary logging.
}
-
- LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState));
+ else
+ {
+ m_SndKmMsg[0].iPeerRetry = 0;
+ m_SndKmMsg[1].iPeerRetry = 0;
+ m_SndKmState = peerstate;
+ m_RcvKmState = revstate;
+ LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState));
+ }
+ }
+ else if (srtlen == 0) // 0-size is a special value for MsgLen, so avoid checks!
+ {
+ LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: IPE/EPE KM response key is empty - ignoring");
}
else
{
@@ -454,25 +481,27 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len
else
{
retstatus = -1;
- LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE??? KM response key matches no key");
+ LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE/EPE KM response key matches no key");
/* XXX INSECURE
LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len)
<< "] matches no key 0=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[0].Msg, m_SndKmMsg[0].MsgLen)
<< "] 1=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[1].Msg, m_SndKmMsg[1].MsgLen) << "]");
*/
- m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET;
+ // DO NOT change the state in case of KMX update with secured sender.
+ if (is_handshake)
+ {
+ m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET;
+ }
}
HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry
<< "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry);
m_bUseGcm153 = srtv <= SrtVersion(1, 5, 3);
-#ifdef SRT_ENABLE_ENCRYPTION
if (m_hRcvCrypto != NULL)
HaiCrypt_UpdateGcm153(m_hRcvCrypto, m_bUseGcm153);
if (m_hSndCrypto != NULL)
HaiCrypt_UpdateGcm153(m_hSndCrypto, m_bUseGcm153);
-#endif
}
LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len));
@@ -480,6 +509,30 @@ int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len
return retstatus;
}
+#else
+
+int srt::CCryptoControl::processSrtMsg_KMREQ(
+ const uint32_t*, size_t, int, unsigned, // ignore input
+ uint32_t pw_srtdata_out[], size_t& w_srtlen)
+{
+ // It's ok that this is reported as error because this happens in a scenario,
+ // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT
+ // application which tries to make a security association.
+ LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject...");
+ m_RcvKmState = SRT_KM_S_NOSECRET;
+ pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState;
+ w_srtlen = 1;
+
+ return SRT_CMD_KMRSP;
+}
+
+int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t*, size_t, unsigned, bool)
+{
+ LOGP(cnlog.Error, "processSrtMsg_KMRSP: Encryption not enabled at compile time; not expected to receive SRT_CMD_KMRSP");
+ return SRT_CMD_NONE;
+}
+#endif
+
void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SRT_ATR_UNUSED)
{
sync::ScopedLock lck(m_mtxLock);
@@ -604,6 +657,7 @@ srt::CCryptoControl::CCryptoControl(SRTSOCKET id)
, m_iRcvKmKeyLen(0)
, m_SndKmState(SRT_KM_S_UNSECURED)
, m_RcvKmState(SRT_KM_S_UNSECURED)
+ , m_CurrentKey(EK_NOENC)
, m_KmRefreshRatePkt(0)
, m_KmPreAnnouncePkt(0)
, m_iCryptoMode(CSrtConfig::CIPHER_MODE_AUTO)
@@ -870,9 +924,9 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU
if (m_RcvKmState != SRT_KM_S_SECURED)
{
// If not "secured", it means that it won't be able to decrypt packets,
- // so there's no point to even try to send them to HaiCrypt_Rx_Data.
+ // so there's no point in even trying to send them to HaiCrypt_Rx_Data.
// Actually the current conditions concerning m_hRcvCrypto are such that this object
- // is cretaed in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt,
+ // is created in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt,
// but with SRT_KM_S_NOSECRET m_hRcvCrypto is not even created (is NULL), which
// will then cause an error to be reported, misleadingly. Simply don't try to
// decrypt anything as long as you are not sure that the connection is secured.
@@ -907,6 +961,7 @@ srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNU
HLOGC(cnlog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc);
return ENCS_CLEAR;
#else
+ (void)m_bErrorReported; // otherwise warning!
return ENCS_NOTSUP;
#endif
}
diff --git a/srtcore/crypto.h b/srtcore/crypto.h
index 613ded8dde..4a0d6aa02b 100644
--- a/srtcore/crypto.h
+++ b/srtcore/crypto.h
@@ -22,6 +22,7 @@ written by
// UDT
#include "udt.h"
#include "packet.h"
+#include "common.h"
#include "utilities.h"
#include "logging.h"
@@ -61,6 +62,7 @@ class CCryptoControl
public:
SRT_KM_STATE m_SndKmState; //Sender Km State (imposed by agent)
SRT_KM_STATE m_RcvKmState; //Receiver Km State (informed by peer)
+ EncryptionKeySpec m_CurrentKey;
private:
// Partial haicrypt configuration, consider
@@ -139,7 +141,7 @@ class CCryptoControl
/// 1 - the given payload is the same as the currently used key
/// 0 - there's no key in agent or the payload is error message with agent NOSECRET.
/// -1 - the payload is error message with other state or it doesn't match the key
- int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv);
+ int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, unsigned srtv, bool is_handshake);
void createFakeSndContext();
const unsigned char* getKmMsg_data(size_t ki) const { return m_SndKmMsg[ki].Msg; }
diff --git a/srtcore/epoll.cpp b/srtcore/epoll.cpp
index 8cd8440c7c..e3545b7380 100644
--- a/srtcore/epoll.cpp
+++ b/srtcore/epoll.cpp
@@ -486,7 +486,7 @@ int srt::CEPoll::setflags(const int eid, int32_t flags)
int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int64_t msTimeOut)
{
- // It is allowed to call this function witn fdsSize == 0
+ // It is allowed to call this function with fdsSize == 0
// and therefore also NULL fdsSet. This will then only report
// the number of ready sockets, just without information which.
if (fdsSize < 0 || (fdsSize > 0 && !fdsSet))
@@ -522,22 +522,17 @@ int srt::CEPoll::uwait(const int eid, SRT_EPOLL_EVENT* fdsSet, int fdsSize, int6
throw CUDTException(MJ_NOTSUP, MN_INVAL);
}
- int total = 0; // This is a list, so count it during iteration
- CEPollDesc::enotice_t::iterator i = ed.enotice_begin();
- while (i != ed.enotice_end())
+ CEPollDesc::enotice_t::iterator i = ed.enotice_begin(), inext;
+ int pos = 0; // This is a list, so count it during iteration
+ for (inext = i ; i != ed.enotice_end() && pos < fdsSize ; ++pos, i = inext)
{
- int pos = total; // previous past-the-end position
- ++total;
-
- if (total > fdsSize)
- break;
+ ++inext; // deletion-safe list loop
fdsSet[pos] = *i;
-
- ed.checkEdge(i++); // NOTE: potentially deletes `i`
+ ed.checkEdge(i); // NOTE: potentially deletes `i`
}
- if (total)
- return total;
+ if (pos) // pos is increased by 1 towards the last used position
+ return pos;
}
if ((msTimeOut >= 0) && (count_microseconds(srt::sync::steady_clock::now() - entertime) >= msTimeOut * int64_t(1000)))
diff --git a/srtcore/epoll.h b/srtcore/epoll.h
index 00d46ceb41..09e0f0ec73 100644
--- a/srtcore/epoll.h
+++ b/srtcore/epoll.h
@@ -157,7 +157,7 @@ std::string DisplayEpollWatch();
ewatch_t m_USockWatchState;
/// Objects representing changes in SRT sockets.
- /// Objects are removed from here when an event is registerred as edge-triggered.
+ /// Objects are removed from here when an event is registered as edge-triggered.
/// Otherwise it is removed only when all events as per subscription
/// are no longer on.
enotice_t m_USockEventNotice;
@@ -429,7 +429,7 @@ friend class srt::CRendezvousQueue;
/// @param d the internal structure of the epoll container
/// @param st output container for the results: { socket_type, event }
/// @param msTimeOut timeout after which return with empty output is allowed
- /// @param report_by_exception if true, errors will result in exception intead of returning -1
+ /// @param report_by_exception if true, errors will result in exception instead of returning -1
/// @retval -1 error occurred
/// @retval >=0 number of ready sockets (actually size of `st`)
int swait(CEPollDesc& d, fmap_t& st, int64_t msTimeOut, bool report_by_exception = true);
@@ -438,7 +438,7 @@ friend class srt::CRendezvousQueue;
bool empty(const CEPollDesc& d) const;
/// Reports which events are ready on the given socket.
- /// @param mp socket event map retirned by `swait`
+ /// @param mp socket event map returned by `swait`
/// @param sock which socket to ask
/// @return event flags for given socket, or 0 if none
static int ready(const fmap_t& mp, SRTSOCKET sock)
@@ -450,7 +450,7 @@ friend class srt::CRendezvousQueue;
}
/// Reports whether socket is ready for given event.
- /// @param mp socket event map retirned by `swait`
+ /// @param mp socket event map returned by `swait`
/// @param sock which socket to ask
/// @param event which events it should be ready for
/// @return true if the given socket is ready for given event
diff --git a/srtcore/fec.cpp b/srtcore/fec.cpp
index fd762b88bc..498c521830 100644
--- a/srtcore/fec.cpp
+++ b/srtcore/fec.cpp
@@ -59,11 +59,11 @@ bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error)
string colspec = map_get(cfg.parameters, "cols"), rowspec = map_get(cfg.parameters, "rows");
- int out_rows = 1;
+ int out_rows = 1, out_cols = 2;
if (colspec != "")
{
- int out_cols = atoi(colspec.c_str());
+ out_cols = atoi(colspec.c_str());
if (out_cols < 2)
{
w_error = "at least 'cols' must be specified and > 1";
@@ -81,6 +81,12 @@ bool FECFilterBuiltin::verifyConfig(const SrtFilterConfig& cfg, string& w_error)
}
}
+ if (out_cols > MAX_GROUP_SIZE || out_rows > MAX_GROUP_SIZE || out_rows < -MAX_GROUP_SIZE)
+ {
+ w_error = "rows/cols size must not exceed 16-bit max value";
+ return false;
+ }
+
// Extra interpret level, if found, default never.
// Check only those that are managed.
string level = map_get(cfg.parameters, "arq");
@@ -213,74 +219,85 @@ FECFilterBuiltin::FECFilterBuiltin(const SrtFilterInitializer &init, std::vector
// Required to store in the header when rebuilding
rcv.id = socketID();
- // Setup the bit matrix, initialize everything with false.
-
- // Vertical size (y)
- rcv.cells.resize(sizeCol() * sizeRow(), false);
-
- // These sequence numbers are both the value of ISN-1 at the moment
- // when the handshake is done. The sender ISN is generated here, the
- // receiver ISN by the peer. Both should be known after the handshake.
- // Later they will be updated as packets are transmitted.
-
- int32_t snd_isn = CSeqNo::incseq(sndISN());
- int32_t rcv_isn = CSeqNo::incseq(rcvISN());
-
- // Alright, now we need to get the ISN from m_parent
- // to extract the sequence number allowing qualification to the group.
- // The base values must be prepared so that feedSource can qualify them.
-
- // SEPARATE FOR SENDING AND RECEIVING!
-
- // Now, assignment of the groups requires:
- // For row groups, simply the size of the group suffices.
- // For column groups, you need a whole matrix of all sequence
- // numbers that are base sequence numbers for the group.
- // Sequences that belong to this group are:
- // 1. First packet has seq+1 towards the base.
- // 2. Every next packet has this value + the size of the row group.
- // So: group dispatching is:
- // - get the column number
- // - extract the group data for that column
- // - check if the sequence is later than the group base sequence, if not, report no group for the packet
- // - sanity check, if the seqdiff divided by row size gets 0 remainder
- // - The result from the above division can't exceed the column size, otherwise
- // it's another group. The number of currently collected data should be in 'collected'.
+ // This will catch any memory allocation error and translate
+ // to MJ_SETUP / MN_NORES.
+ try
+ {
+ // Setup the bit matrix, initialize everything with false.
+
+ // Vertical size (y)
+ rcv.cells.resize(sizeCol() * sizeRow(), false);
+
+ // These sequence numbers are both the value of ISN-1 at the moment
+ // when the handshake is done. The sender ISN is generated here, the
+ // receiver ISN by the peer. Both should be known after the handshake.
+ // Later they will be updated as packets are transmitted.
+
+ int32_t snd_isn = CSeqNo::incseq(sndISN());
+ int32_t rcv_isn = CSeqNo::incseq(rcvISN());
+
+ // Alright, now we need to get the ISN from m_parent
+ // to extract the sequence number allowing qualification to the group.
+ // The base values must be prepared so that feedSource can qualify them.
+
+ // SEPARATE FOR SENDING AND RECEIVING!
+
+ // Now, assignment of the groups requires:
+ // For row groups, simply the size of the group suffices.
+ // For column groups, you need a whole matrix of all sequence
+ // numbers that are base sequence numbers for the group.
+ // Sequences that belong to this group are:
+ // 1. First packet has seq+1 towards the base.
+ // 2. Every next packet has this value + the size of the row group.
+ // So: group dispatching is:
+ // - get the column number
+ // - extract the group data for that column
+ // - check if the sequence is later than the group base sequence, if not, report no group for the packet
+ // - sanity check, if the seqdiff divided by row size gets 0 remainder
+ // - The result from the above division can't exceed the column size, otherwise
+ // it's another group. The number of currently collected data should be in 'collected'.
+
+ // Now set up the group starting sequences.
+ // The very first group in both dimensions will have the value of ISN in particular direction.
+
+ // Set up sender part.
+ //
+ // Size: rows
+ // Step: 1 (next packet in group is 1 past the previous one)
+ // Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows')
+ HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row");
+ ConfigureGroup(snd.row, snd_isn, 1, sizeRow());
+
+ // In the beginning we need just one reception group. New reception
+ // groups will be created in tact with receiving packets outside this one.
+ // The value of rcv.row[0].base will be used as an absolute base for calculating
+ // the index of the group for a given received packet.
+ rcv.rowq.resize(1);
+ HLOGP(pflog.Debug, "FEC: INIT: receiver first row");
+ ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow());
- // Now set up the group starting sequences.
- // The very first group in both dimensions will have the value of ISN in particular direction.
+ if (sizeCol() > 1)
+ {
+ // Size: cols
+ // Step: rows (the next packet in the group is one row later)
+ // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down)
+
+ HLOGP(pflog.Debug, "FEC: INIT: sender first N columns");
+ ConfigureColumns(snd.cols, snd_isn);
+ HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns");
+ ConfigureColumns(rcv.colq, rcv_isn);
+ }
- // Set up sender part.
- //
- // Size: rows
- // Step: 1 (next packet in group is 1 past the previous one)
- // Slip: rows (first packet in the next group is distant to first packet in the previous group by 'rows')
- HLOGC(pflog.Debug, log << "FEC: INIT: ISN { snd=" << snd_isn << " rcv=" << rcv_isn << " }; sender single row");
- ConfigureGroup(snd.row, snd_isn, 1, sizeRow());
-
- // In the beginning we need just one reception group. New reception
- // groups will be created in tact with receiving packets outside this one.
- // The value of rcv.row[0].base will be used as an absolute base for calculating
- // the index of the group for a given received packet.
- rcv.rowq.resize(1);
- HLOGP(pflog.Debug, "FEC: INIT: receiver first row");
- ConfigureGroup(rcv.rowq[0], rcv_isn, 1, sizeRow());
-
- if (sizeCol() > 1)
+ // The bit markers that mark the received/lost packets will be expanded
+ // as packets come in.
+ rcv.cell_base = rcv_isn;
+ }
+ catch (std::bad_alloc& be)
{
- // Size: cols
- // Step: rows (the next packet in the group is one row later)
- // Slip: rows+1 (the first packet in the next group is later by 1 column + one whole row down)
-
- HLOGP(pflog.Debug, "FEC: INIT: sender first N columns");
- ConfigureColumns(snd.cols, snd_isn);
- HLOGP(pflog.Debug, "FEC: INIT: receiver first N columns");
- ConfigureColumns(rcv.colq, rcv_isn);
+ LOGC(pflog.Error, log << "FEC: INIT: Memory allocation error with cols="
+ << numberCols() << " rows=" << numberRows());
+ throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
}
-
- // The bit markers that mark the received/lost packets will be expanded
- // as packets come in.
- rcv.cell_base = rcv_isn;
}
template
@@ -297,7 +314,7 @@ void FECFilterBuiltin::ConfigureColumns(Container& which, int32_t isn)
size_t zero = which.size();
// The first series of initialization should embrace:
- // - if multiplyer == 1, EVERYTHING (also the case of SOLID matrix)
+ // - if multiplayer == 1, EVERYTHING (also the case of SOLID matrix)
// - if more, ONLY THE FIRST SQUARE.
which.resize(zero + numberCols());
@@ -544,11 +561,16 @@ void FECFilterBuiltin::ClipPacket(Group& g, const CPacket& pkt)
// Clipping a control packet does merely the same, just the packet has
// different contents, so it must be differetly interpreted.
-void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt)
+bool FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt)
{
// Both length and timestamp must be taken as NETWORK ORDER
// before applying the clip.
+ if (pkt.size() < 4) // paranoid check
+ {
+ return false;
+ }
+
const char* fec_header = pkt.data();
const char* payload = fec_header + 4;
size_t payload_clip_len = pkt.size() - 4;
@@ -567,6 +589,8 @@ void FECFilterBuiltin::ClipControlPacket(Group& g, const CPacket& pkt)
<< " LENGTH[ne]=" << g.length_clip
<< " TS[he]=" << g.timestamp_clip
<< " PL4=" << (*(uint32_t*)&g.payload_clip[0]));
+
+ return true;
}
void FECFilterBuiltin::ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt)
@@ -601,6 +625,10 @@ void FECFilterBuiltin::ClipData(Group& g, uint16_t length_net, uint8_t kflg,
HLOGC(pflog.Debug, log << "FEC CLIP: data pkt.size=" << payload_size
<< " to a clip buffer size=" << payloadSize());
+ // Paranoid check
+ if (payload_size > payloadSize())
+ payload_size = payloadSize();
+
// Payload goes "as is".
for (size_t i = 0; i < payload_size; ++i)
{
@@ -887,11 +915,11 @@ bool FECFilterBuiltin::receive(const CPacket& rpkt, loss_seqs_t& loss_seqs)
// - Both HangVertical and HangHorizontal
okv = HangVertical(rpkt, isfec.colx, irrecover_col);
- IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? int(okh) < HANG_CRAZY : false);
+ IF_HEAVY_LOGGING(bool discrep = (okv == HANG_CRAZY) ? okh < HANG_CRAZY : false);
HLOGC(pflog.Debug, log << "FEC: HangVertical %" << rpkt.getSeqNo()
<< " msgno=" << rpkt.getMsgSeq()
<< " RESULT=" << hangname[okh]
- << (discrep ? " IPE: H successul and V failed!" : "")
+ << (discrep ? " IPE: H successful and V failed!" : "")
<< " IRRECOVERABLE: " << Printable(irrecover_col));
}
@@ -1162,7 +1190,11 @@ FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangHorizontal(const CPacket& rp
{
if (!rowg.fec)
{
- ClipControlPacket(rowg, rpkt);
+ if (!ClipControlPacket(rowg, rpkt))
+ {
+ LOGC(pflog.Error, log << "FEC/H: Rogue control packet received");
+ return HANG_CRAZY; // That's the only "complete failure" statement
+ }
rowg.fec = true;
HLOGC(pflog.Debug, log << "FEC/H: FEC/CTL packet clipped, %" << seq << " base=%" << rowg.base);
}
@@ -1863,7 +1895,11 @@ FECFilterBuiltin::EHangStatus FECFilterBuiltin::HangVertical(const CPacket& rpkt
{
if (!colg.fec)
{
- ClipControlPacket(colg, rpkt);
+ if (!ClipControlPacket(colg, rpkt))
+ {
+ LOGC(pflog.Error, log << "FEC/H: Rogue control packet received");
+ return HANG_CRAZY; // That's the only "complete failure" statement
+ }
colg.fec = true;
HLOGC(pflog.Debug, log << "FEC/V: FEC/CTL packet clipped, %" << seq << " FOR COLUMN " << int(fec_col)
<< " base=%" << colg.base);
diff --git a/srtcore/fec.h b/srtcore/fec.h
index 0297094752..c5715786e4 100644
--- a/srtcore/fec.h
+++ b/srtcore/fec.h
@@ -34,6 +34,12 @@ class FECFilterBuiltin: public SrtPacketFilterBase
public:
+ // rows/cols size can have this size at maximum, otherwise
+ // there's a risk to have an overflow memory size value on
+ // 32-bit systems, which may result in a false success followed
+ // by a crash.
+ static const int MAX_GROUP_SIZE = 0xFFFF;
+
size_t numberCols() const { return m_number_cols; }
size_t numberRows() const { return m_number_rows; }
@@ -132,10 +138,10 @@ class FECFilterBuiltin: public SrtPacketFilterBase
// Base index at the oldest column platform determines
// the base index of the queue. Meaning, first you need
- // to determnine the column index, where the index 0 is
+ // to determine the column index, where the index 0 is
// the fistmost element of this queue. After determining
// the column index, there must be also a second factor
- // deteremined - which column series it is. So, this can
+ // determined - which column series it is. So, this can
// start by extracting the base sequence of the element
// at the index column. This is the series 0. Now, the
// distance between these two sequences, divided by
@@ -217,7 +223,7 @@ class FECFilterBuiltin: public SrtPacketFilterBase
EHangStatus HangHorizontal(const CPacket& pkt, bool fec_ctl, loss_seqs_t& irrecover);
EHangStatus HangVertical(const CPacket& pkt, signed char fec_colx, loss_seqs_t& irrecover);
- void ClipControlPacket(Group& g, const CPacket& pkt);
+ SRT_ATR_NODISCARD bool ClipControlPacket(Group& g, const CPacket& pkt);
void ClipRebuiltPacket(Group& g, Receive::PrivPacket& pkt);
void RcvRebuild(Group& g, int32_t seqno, Group::Type tp);
int32_t RcvGetLossSeqHoriz(Group& g);
diff --git a/srtcore/group.cpp b/srtcore/group.cpp
index 6b0b16484b..54d9bd74ce 100644
--- a/srtcore/group.cpp
+++ b/srtcore/group.cpp
@@ -90,7 +90,7 @@ bool CUDTGroup::applyGroupSequences(SRTSOCKET target, int32_t& w_snd_isn, int32_
// NOTE: the groupwise scheduling sequence might have been set
// already. If so, it means that it was set by either:
- // - the call of this function on the very first conencted socket (see below)
+ // - the call of this function on the very first connected socket (see below)
// - the call to `sendBroadcast` or `sendBackup`
// In both cases, we want THIS EXACTLY value to be reported
if (m_iLastSchedSeqNo != -1)
@@ -205,7 +205,7 @@ void CUDTGroup::debugMasterData(SRTSOCKET slave)
else
{
// The returned master_st is the master's start time. Calculate the
- // differene time.
+ // difference time.
IF_LOGGING(steady_clock::duration master_tdiff = m_tsStartTime - start_time);
LOGC(cnlog.Debug, log << CONID() << "FOUND GROUP MASTER LINK: peer=$" << mpeer
<< " - start time diff: " << FormatDuration(master_tdiff));
@@ -347,6 +347,36 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen)
switch (optName)
{
+ // First go options that are NOT ALLOWED to be modified on the group.
+ // (socket-only), or are read-only.
+
+ case SRTO_ISN: // read-only
+ case SRTO_STATE: // read-only
+ case SRTO_EVENT: // read-only
+ case SRTO_SNDDATA: // read-only
+ case SRTO_RCVDATA: // read-only
+ case SRTO_KMSTATE: // read-only
+ case SRTO_VERSION: // read-only
+ case SRTO_PEERVERSION: // read-only
+ case SRTO_SNDKMSTATE: // read-only
+ case SRTO_RCVKMSTATE: // read-only
+ case SRTO_GROUPTYPE: // read-only
+ LOGC(gmlog.Error, log << "group option setter: this option ("<< int(optName) << ") is read-only");
+ throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
+
+ case SRTO_SENDER: // deprecated (1.2.0 version legacy)
+ case SRTO_IPV6ONLY: // link-type specific
+ case SRTO_RENDEZVOUS: // socket-only
+ case SRTO_BINDTODEVICE: // socket-specific
+ case SRTO_GROUPCONNECT: // listener-specific
+ LOGC(gmlog.Error, log << "group option setter: this option ("<< int(optName) << ") is socket- or link-specific");
+ throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
+
+ case SRTO_TRANSTYPE:
+ case SRTO_TSBPDMODE:
+ case SRTO_CONGESTION:
+ LOGC(gmlog.Error, log << "group option setter: this option (" << int(optName) << ") removes live mode, which is the only supported for groups");
+ throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
case SRTO_RCVSYN:
m_bSynRecving = cast_optval(optval, optlen);
return;
@@ -369,7 +399,7 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen)
const int min_timeo_ms = (int) CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS;
if (val_ms < min_timeo_ms)
{
- LOGC(qmlog.Error,
+ LOGC(gmlog.Error,
log << "group option: SRTO_GROUPMINSTABLETIMEO min allowed value is " << min_timeo_ms << " ms.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
@@ -385,7 +415,7 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen)
if (val_ms > idletmo)
{
- LOGC(qmlog.Error,
+ LOGC(gmlog.Error,
log << "group option: SRTO_GROUPMINSTABLETIMEO=" << val_ms << " exceeds SRTO_PEERIDLETIMEO=" << idletmo);
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
@@ -431,6 +461,14 @@ void CUDTGroup::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen)
}
}
+ // Before possibly storing the option, check if it is settable on a socket.
+ CSrtConfig testconfig;
+
+ // Note: this call throws CUDTException by itself.
+ int result = testconfig.set(optName, optval, optlen);
+ if (result == -1) // returned in case of unknown option
+ throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
+
// Store the option regardless if pre or post. This will apply
m_config.push_back(ConfigItem(optName, optval, optlen));
}
@@ -444,23 +482,45 @@ static bool operator!=(const struct linger& l1, const struct linger& l2)
return l1.l_onoff != l2.l_onoff || l1.l_linger != l2.l_linger;
}
+/// A function template to import socket option of a trivial type.
+/// This includes linger, bool, int8_t, int32_t, int64_t, double, etc.
+/// Potentially can be extended to trivially_copyable if needed.
template
-static void importOption(vector& storage, SRT_SOCKOPT optname, const ValueType& field)
+static void importTrivialOption(vector& storage, SRT_SOCKOPT optname, const ValueType& optval, const int optsize = sizeof(ValueType))
{
- ValueType default_opt = ValueType();
- static const int default_opt_size = sizeof(ValueType);
- ValueType opt = field;
- int opt_size = default_opt_size;
- if (!getOptDefault(optname, (&default_opt), (opt_size)) || default_opt != opt)
+ SRT_STATIC_ASSERT(std::is_trivial::value, "ValueType must be a trivial type.");
+ ValueType optval_dflt = ValueType();
+ int optsize_dflt = sizeof(ValueType);
+ if (!getOptDefault(optname, (&optval_dflt), (optsize_dflt)) || optval_dflt != optval)
{
- SRT_ASSERT(opt_size == default_opt_size);
+ SRT_ASSERT(optsize == sizeof(ValueType));
// Store the option when:
// - no default for this option is found
- // - the option value retrieved from the field is different than default
- storage.push_back(CUDTGroup::ConfigItem(optname, &opt, default_opt_size));
+ // - the option value retrieved from the field is different from the default one
+#if HAVE_FULL_CXX11
+ storage.emplace_back(optname, &optval, optsize);
+#else
+ storage.push_back(CUDTGroup::ConfigItem(optname, &optval, optsize));
+#endif
}
}
+/// A function template to import a StringStorage option.
+template
+static void importStringOption(vector& storage, SRT_SOCKOPT optname, const StringStorage& optval)
+{
+ if (optval.empty())
+ return;
+
+ // Store the option when:
+ // - option has a value (default is empty).
+#if HAVE_FULL_CXX11
+ storage.emplace_back(optname, optval.c_str(),(int) optval.size());
+#else
+ storage.push_back(CUDTGroup::ConfigItem(optname, optval.c_str(), (int) optval.size()));
+#endif
+}
+
// This function is called by the same premises as the CUDT::CUDT(const CUDT&) (copy constructor).
// The intention is to rewrite the part that comprises settings from the socket
// into the group. Note that some of the settings concern group, some others concern
@@ -502,17 +562,18 @@ void CUDTGroup::deriveSettings(CUDT* u)
// to be potentially replicated on the socket. So both pre
// and post options apply.
-#define IM(option, field) importOption(m_config, option, u->m_config.field)
-#define IMF(option, field) importOption(m_config, option, u->field)
+#define IM(option, field) importTrivialOption(m_config, option, u->m_config.field)
+#define IMF(option, field) importTrivialOption(m_config, option, u->field)
IM(SRTO_MSS, iMSS);
IM(SRTO_FC, iFlightFlagSize);
// Nonstandard
- importOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE));
- importOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE));
+ importTrivialOption(m_config, SRTO_SNDBUF, u->m_config.iSndBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE));
+ importTrivialOption(m_config, SRTO_RCVBUF, u->m_config.iRcvBufSize * (u->m_config.iMSS - CPacket::UDP_HDR_SIZE));
IM(SRTO_LINGER, Linger);
+
IM(SRTO_UDP_SNDBUF, iUDPSndBufSize);
IM(SRTO_UDP_RCVBUF, iUDPRcvBufSize);
// SRTO_RENDEZVOUS: impossible to have it set on a listener socket.
@@ -520,7 +581,7 @@ void CUDTGroup::deriveSettings(CUDT* u)
// SRTO_CONNTIMEO requires a special handling, because API stores the value in integer milliseconds,
// but the type of the variable is srt::sync::duration.
- importOption(m_config, SRTO_CONNTIMEO, (int) count_milliseconds(u->m_config.tdConnTimeOut));
+ importTrivialOption(m_config, SRTO_CONNTIMEO, (int) count_milliseconds(u->m_config.tdConnTimeOut));
IM(SRTO_DRIFTTRACER, bDriftTracer);
// Reuseaddr: true by default and should only be true.
@@ -534,12 +595,12 @@ void CUDTGroup::deriveSettings(CUDT* u)
IM(SRTO_RCVLATENCY, iRcvLatency);
IM(SRTO_PEERLATENCY, iPeerLatency);
IM(SRTO_SNDDROPDELAY, iSndDropDelay);
- // Special handling of SRTO_PAYLOADSIZE becuase API stores the value as int32_t,
+ // Special handling of SRTO_PAYLOADSIZE because API stores the value as int32_t,
// while the config structure stores it as size_t.
- importOption(m_config, SRTO_PAYLOADSIZE, (int)u->m_config.zExpPayloadSize);
+ importTrivialOption(m_config, SRTO_PAYLOADSIZE, (int)u->m_config.zExpPayloadSize);
IMF(SRTO_TLPKTDROP, m_bTLPktDrop);
- importOption(m_config, SRTO_STREAMID, u->m_config.sStreamName.str());
+ importStringOption(m_config, SRTO_STREAMID, u->m_config.sStreamName);
IM(SRTO_MESSAGEAPI, bMessageAPI);
IM(SRTO_NAKREPORT, bRcvNakReport);
@@ -548,22 +609,22 @@ void CUDTGroup::deriveSettings(CUDT* u)
IM(SRTO_IPV6ONLY, iIpV6Only);
IM(SRTO_PEERIDLETIMEO, iPeerIdleTimeout_ms);
- importOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig.str());
+ importStringOption(m_config, SRTO_PACKETFILTER, u->m_config.sPacketFilterConfig);
- importOption(m_config, SRTO_PBKEYLEN, (int) u->m_pCryptoControl->KeyLen());
+ importTrivialOption(m_config, SRTO_PBKEYLEN, (int) u->m_pCryptoControl->KeyLen());
// Passphrase is empty by default. Decipher the passphrase and
// store as passphrase option
if (u->m_config.CryptoSecret.len)
{
- string password((const char*)u->m_config.CryptoSecret.str, u->m_config.CryptoSecret.len);
- m_config.push_back(ConfigItem(SRTO_PASSPHRASE, password.c_str(), (int)password.size()));
+ const StringStorage password((const char*)u->m_config.CryptoSecret.str, u->m_config.CryptoSecret.len);
+ importStringOption(m_config, SRTO_PASSPHRASE, password);
}
IM(SRTO_KMREFRESHRATE, uKmRefreshRatePkt);
IM(SRTO_KMPREANNOUNCE, uKmPreAnnouncePkt);
- string cc = u->m_CongCtl.selected_name();
+ const string cc = u->m_CongCtl.selected_name();
if (cc != "live")
{
m_config.push_back(ConfigItem(SRTO_CONGESTION, cc.c_str(), (int)cc.size()));
@@ -577,6 +638,7 @@ void CUDTGroup::deriveSettings(CUDT* u)
#undef IMF
}
+// XXX This function is likely of no use now.
bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide)
{
const bool synconmsg = IsSet(flags, SRT_GFLAG_SYNCONMSG);
@@ -592,16 +654,18 @@ bool CUDTGroup::applyFlags(uint32_t flags, HandshakeSide)
template
struct Value
{
- static int fill(void* optval, int, Type value)
+ static int fill(void* optval, int len, const Type& value)
{
- // XXX assert size >= sizeof(Type) ?
+ if (size_t(len) < sizeof(Type))
+ return 0;
+
*(Type*)optval = value;
return sizeof(Type);
}
};
template <>
-inline int Value::fill(void* optval, int len, std::string value)
+inline int Value::fill(void* optval, int len, const std::string& value)
{
if (size_t(len) < value.size())
return 0;
@@ -648,7 +712,7 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
case SRTO_SNDBUF:
case SRTO_RCVBUF:
- w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::UDP_HDR_SIZE));
+ w_optlen = fillValue((pw_optval), w_optlen, CSrtConfig::DEF_BUFFER_SIZE * (CSrtConfig::DEF_MSS - CPacket::UDP_HDR_SIZE));
break;
case SRTO_LINGER:
@@ -666,10 +730,16 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
RD(true);
case SRTO_MAXBW:
RD(int64_t(-1));
+#ifdef ENABLE_MAXREXMITBW
+ case SRTO_MAXREXMITBW:
+ RD(int64_t(-1));
+#endif
case SRTO_INPUTBW:
RD(int64_t(-1));
+ case SRTO_MININPUTBW:
+ RD(int64_t(0));
case SRTO_OHEADBW:
- RD(0);
+ RD(SRT_OHEAD_DEFAULT_P100);
case SRTO_STATE:
RD(SRTS_INIT);
case SRTO_EVENT:
@@ -690,8 +760,9 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
RD(false);
case SRTO_LATENCY:
case SRTO_RCVLATENCY:
- case SRTO_PEERLATENCY:
RD(SRT_LIVE_DEF_LATENCY_MS);
+ case SRTO_PEERLATENCY:
+ RD(0);
case SRTO_TLPKTDROP:
RD(true);
case SRTO_SNDDROPDELAY:
@@ -702,14 +773,15 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
RD(SRT_DEF_VERSION);
case SRTO_PEERVERSION:
RD(0);
-
+ case SRTO_PEERIDLETIMEO:
+ RD(CSrtConfig::COMM_RESPONSE_TIMEOUT_MS);
case SRTO_CONNTIMEO:
- RD(-1);
+ RD(CSrtConfig::DEF_CONNTIMEO_S * 1000); // required milliseconds
case SRTO_DRIFTTRACER:
RD(true);
case SRTO_MINVERSION:
- RD(0);
+ RD(SRT_VERSION_MAJ1);
case SRTO_STREAMID:
RD(std::string());
case SRTO_CONGESTION:
@@ -720,6 +792,10 @@ static bool getOptDefault(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
RD(0);
case SRTO_GROUPMINSTABLETIMEO:
RD(CSrtConfig::COMM_DEF_MIN_STABILITY_TIMEOUT_MS);
+ case SRTO_LOSSMAXTTL:
+ RD(0);
+ case SRTO_RETRANSMITALGO:
+ RD(1);
}
#undef RD
@@ -790,27 +866,33 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
{
// Can't have m_GroupLock locked while calling getOpt on a member socket
// because the call will acquire m_ControlLock leading to a lock-order-inversion.
+ SRTSOCKET firstsocket = SRT_INVALID_SOCK;
enterCS(m_GroupLock);
gli_t gi = m_Group.begin();
- CUDTSocket* const ps = (gi != m_Group.end()) ? gi->ps : NULL;
- CUDTUnited::SocketKeeper sk(CUDT::uglobal(), ps);
+ if (gi != m_Group.end())
+ firstsocket = gi->ps->core().id();
leaveCS(m_GroupLock);
- if (sk.socket)
+ // CUDTUnited::m_GlobControlLock can't be acquired with m_GroupLock either.
+ // We have also no guarantee that after leaving m_GroupLock the socket isn't
+ // going to be deleted. Hence use the safest method by extracting through the id.
+ if (firstsocket != SRT_INVALID_SOCK)
{
- // Return the value from the first member socket, if any is present
- // Note: Will throw exception if the request is wrong.
- sk.socket->core().getOpt(optname, (pw_optval), (w_optlen));
- is_set_on_socket = true;
+ CUDTUnited::SocketKeeper sk(CUDT::uglobal(), firstsocket);
+ if (sk.socket)
+ {
+ // Return the value from the first member socket, if any is present
+ // Note: Will throw exception if the request is wrong.
+ sk.socket->core().getOpt(optname, (pw_optval), (w_optlen));
+ is_set_on_socket = true;
+ }
}
}
// Check if the option is in the storage, which means that
// it was modified on the group.
+ vector::const_iterator i = find_if(m_config.begin(), m_config.end(), FOptionValue(optname));
- vector::const_iterator i = find_if(m_config.begin(), m_config.end(),
- FOptionValue(optname));
-
- if (i == m_config.end())
+ if (i == m_config.end() || i->value.empty())
{
// Already written to the target variable.
if (is_set_on_socket)
@@ -822,15 +904,14 @@ void CUDTGroup::getOpt(SRT_SOCKOPT optname, void* pw_optval, int& w_optlen)
return;
}
- // NOTE: even if is_set_on_socket, if it was also found in the group
- // settings, overwrite with the value from the group.
- // Found, return the value from the storage.
+ // Found a value set on or derived by a group. Prefer returning it over the one taken from a member socket.
// Check the size first.
if (w_optlen < int(i->value.size()))
throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0);
- w_optlen = (int) i->value.size();
+ SRT_ASSERT(!i->value.empty());
+ w_optlen = (int)i->value.size();
memcpy((pw_optval), &i->value[0], i->value.size());
}
@@ -854,7 +935,7 @@ SRT_KM_STATE CUDTGroup::getGroupEncryptionState()
// no password, but peer did, and ENFORCEDENCRYPTION=false allowed
// this connection to be established. UNSECURED can't be taken in this
// case because this would suggest that BOTH are unsecured, that is,
- // we have established an unsecured connection (which ain't true).
+ // we have established an unsecured connection (which is not true).
if (gst == SRT_KM_S_UNSECURED && cc->m_SndKmState == SRT_KM_S_NOSECRET)
gst = SRT_KM_S_NOSECRET;
kmstates.insert(gst);
@@ -967,7 +1048,7 @@ void CUDTGroup::close()
vector ids;
{
- ScopedLock glob(CUDT::uglobal().m_GlobControlLock);
+ ExclusiveLock glob(CUDT::uglobal().m_GlobControlLock);
ScopedLock g(m_GroupLock);
m_bClosing = true;
@@ -1069,7 +1150,7 @@ void CUDTGroup::close()
// Release blocked clients
// XXX This looks like a dead code. Group receiver functions
// do not use any lock on m_RcvDataLock, it is likely a remainder
- // of the old, internal impementation.
+ // of the old, internal implementation.
// CSync::lock_notify_one(m_RcvDataCond, m_RcvDataLock);
}
@@ -1396,9 +1477,28 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc)
// { send_CheckBrokenSockets()
- if (!pendingSockets.empty())
+ // Make an extra loop check to see if we could be
+ // in a condition of "all sockets either blocked or pending"
+
+ int nsuccessful = 0; // number of successfully connected sockets
+ int nblocked = 0; // number of sockets blocked in connection
+ bool is_pending_blocked = false;
+ for (vector::iterator is = sendstates.begin(); is != sendstates.end(); ++is)
{
- HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets, polling them.");
+ if (is->stat != -1)
+ {
+ nsuccessful++;
+ }
+ // is->stat == -1
+ else if (is->code == SRT_EASYNCSND)
+ {
+ ++nblocked;
+ }
+ }
+
+ if (!pendingSockets.empty() || nblocked)
+ {
+ HLOGC(gslog.Debug, log << "grp/sendBroadcast: found pending sockets (blocked: " << nblocked << "), polling them.");
// These sockets if they are in pending state, they should be added to m_SndEID
// at the connecting stage.
@@ -1413,12 +1513,24 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc)
}
else
{
+ int swait_timeout = 0;
+
+ // There's also a hidden condition here that is the upper if condition.
+ is_pending_blocked = (nsuccessful == 0);
+
+ // If this is the case when
+ if (m_bSynSending && is_pending_blocked)
+ {
+ HLOGC(gslog.Debug, log << "grp/sendBroadcast: will block for " << m_iSndTimeOut << " - waiting for any writable in blocking mode");
+ swait_timeout = m_iSndTimeOut;
+ }
+
{
InvertedLock ug(m_GroupLock);
THREAD_PAUSED();
m_Global.m_EPoll.swait(
- *m_SndEpolld, sready, 0, false /*report by retval*/); // Just check if anything happened
+ *m_SndEpolld, (sready), swait_timeout, false /*report by retval*/); // Just check if anything happened
THREAD_RESUMED();
}
@@ -1431,6 +1543,10 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc)
HLOGC(gslog.Debug, log << "grp/sendBroadcast: RDY: " << DisplayEpollResults(sready));
// sockets in EX: should be moved to wipeme.
+ // IMPORTANT: we check only PENDING sockets (not blocked) because only
+ // pending sockets might report ERR epoll without being explicitly broken.
+ // Sockets that did connect and just have buffer full will be always broken,
+ // if they're going to report ERR in epoll.
for (vector::iterator i = pendingSockets.begin(); i != pendingSockets.end(); ++i)
{
if (CEPoll::isready(sready, *i, SRT_EPOLL_ERR))
@@ -1442,6 +1558,9 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc)
int no_events = 0;
m_Global.m_EPoll.update_usock(m_SndEID, *i, &no_events);
}
+
+ if (CEPoll::isready(sready, *i, SRT_EPOLL_OUT))
+ is_pending_blocked = false;
}
// After that, all sockets that have been reported
@@ -1458,7 +1577,10 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc)
if (m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
- send_CloseBrokenSockets(wipeme);
+ // Just for a case, when a socket that was blocked or pending
+ // had switched to write-enabled,
+
+ send_CloseBrokenSockets((wipeme)); // wipeme will be cleared by this function
// Re-check after the waiting lock has been reacquired
if (m_bClosing)
@@ -1720,9 +1842,18 @@ int CUDTGroup::sendBroadcast(const char* buf, int len, SRT_MSGCTRL& w_mc)
if (none_succeeded)
{
- HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)");
m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_OUT, false);
- m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true);
+ if (!m_bSynSending && (is_pending_blocked || was_blocked))
+ {
+ HLOGC(gslog.Debug, log << "grp/sendBroadcast: no links are ready for sending");
+ ercode = SRT_EASYNCSND;
+ }
+ else
+ {
+ HLOGC(gslog.Debug, log << "grp/sendBroadcast: all links broken (none succeeded to send a payload)");
+ m_Global.m_EPoll.update_events(id(), m_sPollID, SRT_EPOLL_ERR, true);
+ }
+
// Reparse error code, if set.
// It might be set, if the last operation was failed.
// If any operation succeeded, this will not be executed anyway.
@@ -2549,6 +2680,12 @@ void CUDTGroup::bstatsSocket(CBytePerfMon* perf, bool clear)
perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval;
perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval;
+ // The group keeps no loss counters of its own - loss is a per-member property,
+ // and so is the sampling window that backs these. Report a clean window and
+ // let the caller query the member sockets for the real thing.
+ perf->pctSndQuality = 100.0;
+ perf->pctRcvQuality = 100.0;
+
if (clear)
{
m_stats.reset();
@@ -2845,7 +2982,7 @@ CUDTGroup::BackupMemberState CUDTGroup::sendBackup_QualifyActiveState(const gli_
const int64_t probing_period_us = initial_stabtout_us + 5 * CUDT::COMM_SYN_INTERVAL_US;
// RTT and RTTVar values are still being refined during the probing period,
- // therefore the dymanic timeout should not be used during the probing period.
+ // therefore the dynamic timeout should not be used during the probing period.
const bool is_activation_phase = !is_zero(u.freshActivationStart())
&& (count_microseconds(currtime - u.freshActivationStart()) <= probing_period_us);
@@ -3282,7 +3419,7 @@ void CUDTGroup::send_CloseBrokenSockets(vector& w_wipeme)
// With unlocked GroupLock, we can now lock GlobControlLock.
// This is needed to prevent any of them deleted from the container
// at the same time.
- ScopedLock globlock(CUDT::uglobal().m_GlobControlLock);
+ SharedLock globlock(CUDT::uglobal().m_GlobControlLock);
for (vector::iterator p = w_wipeme.begin(); p != w_wipeme.end(); ++p)
{
@@ -3319,7 +3456,7 @@ void CUDTGroup::sendBackup_CloseBrokenSockets(SendBackupCtx& w_sendBackupCtx)
// With unlocked GroupLock, we can now lock GlobControlLock.
// This is needed prevent any of them be deleted from the container
// at the same time.
- ScopedLock globlock(CUDT::uglobal().m_GlobControlLock);
+ SharedLock globlock(CUDT::uglobal().m_GlobControlLock);
typedef vector::const_iterator const_iter_t;
for (const_iter_t member = w_sendBackupCtx.memberStates().begin(); member != w_sendBackupCtx.memberStates().end(); ++member)
@@ -3376,11 +3513,13 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx
// Note: A link is added in unstableLinks if sending has failed with SRT_ESYNCSND.
const unsigned num_unstable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE);
const unsigned num_wary = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_UNSTABLE_WARY);
- if ((num_unstable + num_wary == 0) || !w_none_succeeded)
+ const unsigned num_pending = w_sendBackupCtx.countMembersByState(BKUPST_PENDING);
+ if ((num_unstable + num_wary + num_pending == 0) || !w_none_succeeded)
return;
- HLOGC(gslog.Debug, log << "grp/sendBackup: no successfull sending: "
- << (num_unstable + num_wary) << " unstable links - waiting to retry sending...");
+ HLOGC(gslog.Debug, log << "grp/sendBackup: no successful sending: "
+ << (num_unstable + num_wary) << " unstable links, "
+ << num_pending << " pending - waiting to retry sending...");
// Note: GroupLock is set already, skip locks and checks
getGroupData_LOCKED((w_mc.grpdata), (&w_mc.grpdata_size));
@@ -3454,9 +3593,8 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx
CUDTSocket* s = m_Global.locateSocket(id, CUDTUnited::ERH_RETURN); // << LOCKS m_GlobControlLock!
if (s)
{
- HLOGC(gslog.Debug,
- log << "grp/sendBackup: swait/ex on @" << (id)
- << " while waiting for any writable socket - CLOSING");
+ HLOGC(gslog.Debug, log << "grp/sendBackup: swait/ex on @" << id
+ << " while waiting for any writable socket - CLOSING");
CUDT::uglobal().close(s); // << LOCKS m_GlobControlLock, then GroupLock!
}
else
@@ -3488,6 +3626,32 @@ void CUDTGroup::sendBackup_RetryWaitBlocked(SendBackupCtx& w_sendBackupCtx
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
+ // IMPORTANT!
+ // There was a socket deletion possibly done above, and as well there
+ // was the m_GroupLock lifted for that check activity, so potentially any socket
+ // from m_Group container could be deleted; review them and remove any dangling
+ // objects from w_sendBackupCtx.
+ //
+ // Due to the nature of the m_Group container, use mark-and-sweep method.
+
+ set remain;
+ w_sendBackupCtx.getSocketIds( (remain) );
+
+ // MARK
+ for (gli_t d = m_Group.begin(); d != m_Group.end(); ++d)
+ {
+ remain.erase(d->id);
+ }
+
+ HLOGC(gslog.Debug, log << "grp/sendBackup: RE-LOCK, checking members deleted in the meantime: "
+ << Printable(remain));
+
+ // SWEEP
+ for (set::iterator i = remain.begin(); i != remain.end(); ++i)
+ {
+ w_sendBackupCtx.deleteById(*i);
+ }
+
// Ok, now check if we have at least one write-ready.
// Note that the procedure of activation of a new link in case of
// no stable links found embraces also rexmit-sending and status
@@ -3566,7 +3730,7 @@ void CUDTGroup::sendBackup_SilenceRedundantLinks(SendBackupCtx& w_sendBackupCtx,
{
// The most important principle is to keep the data being sent constantly,
// even if it means temporarily full redundancy.
- // A member can be silenced only if there is at least one stable memebr.
+ // A member can be silenced only if there is at least one stable member.
const unsigned num_stable = w_sendBackupCtx.countMembersByState(BKUPST_ACTIVE_STABLE);
if (num_stable == 0)
return;
@@ -3636,6 +3800,7 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc)
{
if (len <= 0)
{
+ LOGC(gslog.Error, log << "grp/send(backup): negative length: " << len);
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
@@ -3655,6 +3820,7 @@ int CUDTGroup::sendBackup(const char* buf, int len, SRT_MSGCTRL& w_mc)
if (m_bClosing)
{
leaveCS(m_Global.m_GlobControlLock);
+ LOGC(gslog.Error, log << "grp/send(backup): Cannot send, connection lost!");
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
@@ -4048,7 +4214,7 @@ void CUDTGroup::processKeepalive(CUDTGroup::SocketData* gli)
// the sequence per being IDLE and empty buffer), so a large portion of initial
// series of packets may come with past sequence, delaying this way with ACK,
// which may result not only with exceeded stability timeout (which fortunately
- // isn't being measured in this case), but also with receiveing keepalive
+ // isn't being measured in this case), but also with receiving keepalive
// (therefore we also don't reset the link to IDLE in the temporary activation period).
if (gli->sndstate == SRT_GST_RUNNING && is_zero(gli->ps->core().m_tsFreshActivation))
{
diff --git a/srtcore/group.h b/srtcore/group.h
index 56f1456f8c..e47f1243a5 100644
--- a/srtcore/group.h
+++ b/srtcore/group.h
@@ -223,7 +223,7 @@ class CUDTGroup
bool send_CheckIdle(const gli_t d, std::vector& w_wipeme, std::vector& w_pendingLinks);
- /// This function checks if the member has just become idle (check if sender buffer is empty) to send a KEEPALIVE immidiatelly.
+ /// This function checks if the member has just become idle (check if sender buffer is empty) to send a KEEPALIVE immediately.
/// @todo Check it is some abandoned logic.
void sendBackup_CheckIdleTime(gli_t w_d);
@@ -247,11 +247,11 @@ class CUDTGroup
/// @param[in,out] w_mc message control
/// @param[in] currtime current time
/// @param[in] currseq current packet sequence number
- /// @param[out] w_nsuccessful number of members with successfull sending.
+ /// @param[out] w_nsuccessful number of members with successful sending.
/// @param[in,out] maxActiveWeight
/// @param[in,out] sendBackupCtx context
/// @param[in,out] w_cx error
- /// @return group send result: -1 if sending over all members has failed; number of bytes sent overwise.
+ /// @return group send result: -1 if sending over all members has failed; number of bytes sent otherwise.
int sendBackup_SendOverActive(const char* buf, int len, SRT_MSGCTRL& w_mc, const steady_clock::time_point& currtime, int32_t& w_curseq,
size_t& w_nsuccessful, uint16_t& w_maxActiveWeight, SendBackupCtx& w_sendBackupCtx, CUDTException& w_cx);
diff --git a/srtcore/group_backup.cpp b/srtcore/group_backup.cpp
index 9adb19607b..825791dab2 100644
--- a/srtcore/group_backup.cpp
+++ b/srtcore/group_backup.cpp
@@ -144,6 +144,30 @@ unsigned SendBackupCtx::countMembersByState(BackupMemberState st) const
return m_stateCounter[st];
}
+void SendBackupCtx::getSocketIds(std::set& ids) const
+{
+ typedef vector::const_iterator const_iter_t;
+ for (const_iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i)
+ {
+ ids.insert(i->socketID);
+ }
+}
+
+bool SendBackupCtx::deleteById(SRTSOCKET id)
+{
+ typedef vector::iterator iter_t;
+ for (iter_t i = m_memberStates.begin(); i != m_memberStates.end(); ++i)
+ {
+ if (i->socketID == id)
+ {
+ m_memberStates.erase(i);
+ return true;
+ }
+ }
+
+ return false;
+}
+
std::string SendBackupCtx::printMembers() const
{
stringstream ss;
diff --git a/srtcore/group_backup.h b/srtcore/group_backup.h
index ed2fb29ffb..4705cc3ad3 100644
--- a/srtcore/group_backup.h
+++ b/srtcore/group_backup.h
@@ -92,13 +92,13 @@ namespace groups
/// @brief Adds or updates a record of the member socket state.
/// @param pSocketDataIt Iterator to a socket
- /// @param st State of the memmber socket
+ /// @param st State of the member socket
/// @todo Implement updating member state
void recordMemberState(SocketData* pSocketDataIt, BackupMemberState st);
/// @brief Updates a record of the member socket state.
/// @param pSocketDataIt Iterator to a socket
- /// @param st State of the memmber socket
+ /// @param st State of the member socket
/// @todo To be replaced by recordMemberState
/// @todo Update max weights?
void updateMemberState(const SocketData* pSocketDataIt, BackupMemberState st);
@@ -107,6 +107,8 @@ namespace groups
/// Higher weight comes first, same weight: stable first, then fresh active.
void sortByWeightAndState();
+ bool deleteById(SRTSOCKET id);
+
BackupMemberState getMemberState(const SocketData* pSocketDataIt) const;
unsigned countMembersByState(BackupMemberState st) const;
@@ -122,6 +124,8 @@ namespace groups
const CRateEstimator& getRateEstimate() const { return m_rateEstimate; }
+ void getSocketIds(std::set& out) const;
+
private:
std::vector m_memberStates; // TODO: consider std::map here?
unsigned m_stateCounter[BKUPST_E_SIZE];
diff --git a/srtcore/handshake.cpp b/srtcore/handshake.cpp
index c97b4e2a34..878b90452a 100644
--- a/srtcore/handshake.cpp
+++ b/srtcore/handshake.cpp
@@ -51,10 +51,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include
#include
+#include "handshake.h"
+#include "packet.h"
#include "udt.h"
#include "api.h"
#include "core.h"
-#include "handshake.h"
#include "utilities.h"
using namespace std;
@@ -198,7 +199,7 @@ bool srt::CHandShake::valid()
{
if (m_iVersion < CUDT::HS_VERSION_UDT4
|| m_iISN < 0 || m_iISN >= CSeqNo::m_iMaxSeqNo
- || m_iMSS < 32
+ || m_iMSS < int(CPacket::UDP_HDR_SIZE_IPv6 + CPacket::HDR_SIZE)
|| m_iFlightFlagSize < 2)
return false;
diff --git a/srtcore/list.cpp b/srtcore/list.cpp
index 0fe7d2b4b7..6096284560 100644
--- a/srtcore/list.cpp
+++ b/srtcore/list.cpp
@@ -52,6 +52,8 @@ modified by
#include "platform_sys.h"
+#include
+
#include "list.h"
#include "packet.h"
#include "logging.h"
@@ -740,7 +742,7 @@ int32_t srt::CRcvLossList::removeUpTo(int32_t seqno_last)
if (CSeqNo::seqcmp(seqno_last, first) < 0)
{
- //HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " - first %" << first << " is newer, exitting");
+ //HLOGC(tslog.Debug, log << "rcv-loss: DROP to %" << seqno_last << " - first %" << first << " is newer, exiting");
return first; // seqno_last older than first - nothing to remove
}
@@ -791,13 +793,12 @@ int32_t srt::CRcvLossList::getFirstLostSeq() const
return m_caSeq[m_iHead].seqstart;
}
-void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit)
+int srt::CRcvLossList::getLossArray(FixedArray& array)
{
- len = 0;
-
+ int len = 0;
int i = m_iHead;
- while ((len < limit - 1) && (-1 != i))
+ while ((len < int(array.size()) - 1) && (i != -1))
{
array[len] = m_caSeq[i].seqstart;
if (SRT_SEQNO_NONE != m_caSeq[i].seqend)
@@ -812,11 +813,13 @@ void srt::CRcvLossList::getLossArray(int32_t* array, int& len, int limit)
i = m_caSeq[i].inext;
}
+ return len;
}
srt::CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age)
: ttl(initial_age)
, timestamp(steady_clock::now())
+ , report_time() // zero: never reported yet
{
seq[0] = seqlo;
seq[1] = seqhi;
@@ -897,7 +900,8 @@ srt::CRcvFreshLoss::Emod srt::CRcvFreshLoss::revoke(int32_t lo, int32_t hi)
return DELETE;
}
-bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32_t sequence, int* pw_had_ttl)
+bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32_t sequence, int* pw_had_ttl,
+ srt::sync::steady_clock::time_point* pw_detect_time)
{
for (size_t i = 0; i < w_container.size(); ++i)
{
@@ -907,6 +911,10 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32
if (wh == NONE)
continue; // Not found. Search again.
+ // Read before a possible erase below; revoke() never touches the timestamp.
+ if (pw_detect_time)
+ *pw_detect_time = w_container[i].timestamp;
+
if (wh == DELETE) // ... oo ... x ... o ... => ... oo ... o ...
{
// Removed the only element in the record - remove the record.
@@ -927,8 +935,11 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32
// Use position of the NEXT element because insertion happens BEFORE pointed element.
// Use the same TTL (will stay the same in the other one).
- w_container.insert(w_container.begin() + i + 1,
- CRcvFreshLoss(next_begin, next_end, w_container[i].ttl));
+ // Both halves keep the original time history.
+ CRcvFreshLoss upper(next_begin, next_end, w_container[i].ttl);
+ upper.timestamp = w_container[i].timestamp;
+ upper.report_time = w_container[i].report_time;
+ w_container.insert(w_container.begin() + i + 1, upper);
}
// For STRIPPED: ... xooo ... => ... ooo ...
// i.e. there's nothing to do.
@@ -946,3 +957,97 @@ bool srt::CRcvFreshLoss::removeOne(std::deque& w_container, int32
}
+namespace
+{
+// Whether a record belongs in this report, disregarding the payload cap.
+// @a want_first selects the pass: true = records never reported before.
+bool srtlaNakEligible(const srt::CRcvFreshLoss& rec,
+ const srt::sync::steady_clock::time_point& now,
+ const srt::SrtlaNakParams& params,
+ bool want_first)
+{
+ using namespace srt::sync;
+
+ const bool first_report = is_zero(rec.report_time);
+ if (first_report != want_first)
+ return false;
+
+ if (rec.ttl > 0)
+ return false; // not yet witnessed by enough subsequent packets
+
+ const int64_t age_us = count_microseconds(now - rec.timestamp);
+ if (age_us < params.hold_us)
+ return false; // still within the reordering grace period
+
+ // What is left of the play budget for one more round trip.
+ const int64_t remaining_us = params.budget_us > 0 ? params.budget_us - age_us - params.rtt_us - params.margin_us : 1;
+ if (remaining_us < 0)
+ return false; // no round trip fits any more
+
+ int64_t spacing_us = params.spacing_us;
+ // The closer the play deadline, the sooner a repeat. With plenty of budget left the normal
+ // spacing applies (few duplicates); once only a couple of round trips fit, repeat at half the
+ // remaining time so at least two more attempts can be made before the deadline.
+ if (remaining_us > 0 && remaining_us / 2 < spacing_us)
+ spacing_us = std::max(remaining_us / 2, 50000);
+ if (!first_report && count_microseconds(now - rec.report_time) < spacing_us)
+ return false; // repeat not due yet
+
+ return true;
+}
+} // namespace
+
+srt::SrtlaNakPlan srt::srtlaPlanNak(const std::deque& fresh,
+ const srt::sync::steady_clock::time_point& now,
+ const SrtlaNakParams& params)
+{
+ using namespace srt::sync;
+
+ SrtlaNakPlan plan;
+
+ // Records are appended in detection order, and a split in removeOne() copies the
+ // original timestamp into both halves, so the container is sorted by age: everything
+ // past the play budget is a prefix.
+ if (params.budget_us > 0)
+ {
+ while (plan.retire < fresh.size()
+ && count_microseconds(now - fresh[plan.retire].timestamp) > params.budget_us)
+ {
+ ++plan.retire;
+ }
+
+ }
+
+ size_t used = 0; // 32-bit words already claimed in the report
+
+ // Two passes so a never-reported loss cannot be crowded out of a full report by
+ // repeats of older ones.
+ for (int pass = 0; pass < 2; ++pass)
+ {
+ const bool want_first = (pass == 0);
+
+ for (size_t i = plan.retire; i < fresh.size(); ++i)
+ {
+ const CRcvFreshLoss& rec = fresh[i];
+
+ if (!srtlaNakEligible(rec, now, params, want_first))
+ continue;
+
+ const size_t cost = (rec.seq[0] == rec.seq[1]) ? 1 : 2;
+ if (used + cost > params.cap)
+ break; // report is full, the remaining records get their turn next cycle
+
+ plan.report.push_back(i);
+ used += cost;
+
+ if (want_first)
+ plan.confirmed += CSeqNo::seqoff(rec.seq[0], rec.seq[1]) + 1;
+ }
+ }
+
+ // Selection is fresh-first, transmission stays in sequence order.
+ std::sort(plan.report.begin(), plan.report.end());
+
+ return plan;
+}
+
diff --git a/srtcore/list.h b/srtcore/list.h
index 40b8ee0c9b..b6086a91df 100644
--- a/srtcore/list.h
+++ b/srtcore/list.h
@@ -54,9 +54,11 @@ modified by
#define INC_SRT_LIST_H
#include
+#include
#include "udt.h"
#include "common.h"
+#include "utilities.h"
namespace srt {
@@ -162,7 +164,7 @@ class CRcvLossList
/// Insert a series of loss seq. no. between "seqno1" and "seqno2" into the receiver's loss list.
/// @param [in] seqno1 sequence number starts.
- /// @param [in] seqno2 seqeunce number ends.
+ /// @param [in] seqno2 sequence number ends.
/// @return length of the loss record inserted (seqlen(seqno1, seqno2)), -1 on error.
int insert(int32_t seqno1, int32_t seqno2);
@@ -203,11 +205,9 @@ class CRcvLossList
int32_t getFirstLostSeq() const;
/// Get a encoded loss array for NAK report.
- /// @param [out] array the result list of seq. no. to be included in NAK.
- /// @param [out] len physical length of the result array.
- /// @param [in] limit maximum length of the array.
-
- void getLossArray(int32_t* array, int& len, int limit);
+ /// @param [inout] array the result list of seq. no. to be included in NAK.
+ /// @return physical length of the result array.
+ int getLossArray(FixedArray& array);
private:
struct Seq
@@ -281,6 +281,7 @@ struct CRcvFreshLoss
int32_t seq[2];
int ttl;
srt::sync::steady_clock::time_point timestamp;
+ srt::sync::steady_clock::time_point report_time; // last report time (zero = never); unused in plain SRT
CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl);
@@ -300,9 +301,64 @@ struct CRcvFreshLoss
Emod revoke(int32_t sequence);
Emod revoke(int32_t lo, int32_t hi);
- static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL);
+ static bool removeOne(std::deque& w_container, int32_t sequence, int* had_ttl = NULL,
+ srt::sync::steady_clock::time_point* w_detect_time = NULL);
+};
+
+/// Inputs for one SRTLA loss-report decision (see @c srtlaPlanNak). All durations
+/// are in microseconds. Only used when SRTO_SRTLA is set.
+struct SrtlaNakParams
+{
+ int64_t hold_us; //< reordering grace period before a record may be reported
+ int64_t spacing_us; //< minimum distance between two reports of the same record
+ int64_t budget_us; //< TSBPD play budget; 0 switches all deadline handling off
+ int64_t rtt_us; //< round trip a retransmission is expected to take
+ int64_t margin_us; //< safety margin kept free before the play deadline
+ size_t cap; //< capacity of the loss report payload, in 32-bit words
+
+ SrtlaNakParams()
+ : hold_us(0)
+ , spacing_us(0)
+ , budget_us(0)
+ , rtt_us(0)
+ , margin_us(0)
+ , cap(0)
+ {
+ }
};
+/// What to do with the fresh-loss records in this NAK cycle.
+struct SrtlaNakPlan
+{
+ /// Indices into the container passed to @c srtlaPlanNak, in report order. They are
+ /// only valid as long as that container is unmodified, so stamp @c report_time on
+ /// these records FIRST and erase the @c retire prefix afterwards.
+ std::vector report;
+ int confirmed; //< packets in @c report that are being reported for the first time
+ size_t retire; //< number of leading records that outlived the play budget
+
+ SrtlaNakPlan()
+ : confirmed(0)
+ , retire(0)
+ {
+ }
+};
+
+/// Decide which fresh-loss records belong in the next SRTLA loss report.
+///
+/// Unlike plain SRT - where a record is reported once, as soon as its TTL expires, and
+/// then dropped - SRTLA keeps a record until it is either recovered or dropped, and
+/// repeats the request on a timer. This applies the reordering hold, the repeat spacing
+/// and the play deadline, and caps the report at the payload size.
+///
+/// @param fresh loss records, ordered by detection time (oldest first)
+/// @param now current time
+/// @param params timing and capacity limits
+/// @return indices to report, the first-report packet count, and the prefix to retire
+SrtlaNakPlan srtlaPlanNak(const std::deque& fresh,
+ const srt::sync::steady_clock::time_point& now,
+ const SrtlaNakParams& params);
+
} // namespace srt
#endif
diff --git a/srtcore/logging.h b/srtcore/logging.h
index 7782245a25..dd9be95a81 100644
--- a/srtcore/logging.h
+++ b/srtcore/logging.h
@@ -189,6 +189,7 @@ struct SRT_API LogDispatcher
~LogDispatcher()
{
+ src_config = NULL; // Allow to call CheckEnabled after destruction
}
bool CheckEnabled();
@@ -390,6 +391,12 @@ struct LogDispatcher::Proxy
#endif
+
+// IMPORTANT:
+// 1. Logger objects are only allowed to be global. The CheckEnabled
+// method RELIES ON THAT it may run before the Logger() constructor is run.
+// 2. Destructor resets src_config to NULL to prevent any logging instruction
+// to access the facility after destruction.
class Logger
{
int m_fa;
@@ -425,12 +432,16 @@ inline bool LogDispatcher::CheckEnabled()
// when the enabler check is tested here. Worst case, the log
// will be printed just a moment after it was turned off.
const LogConfig* config = src_config; // to enforce using const operator[]
- config->lock();
- int configured_enabled_fa = config->enabled_fa[fa];
- int configured_maxlevel = config->max_level;
- config->unlock();
+ if (config)
+ {
+ config->lock();
+ int configured_enabled_fa = config->enabled_fa[fa];
+ int configured_maxlevel = config->max_level;
+ config->unlock();
- return configured_enabled_fa && level <= configured_maxlevel;
+ return configured_enabled_fa && level <= configured_maxlevel;
+ }
+ return false;
}
diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp
index 1806230399..e0b1edfb38 100644
--- a/srtcore/packet.cpp
+++ b/srtcore/packet.cpp
@@ -110,7 +110,7 @@ modified by
// Control Info: None
// 2: Acknowledgement (UMSG_ACK)
// Add. Info: The ACK sequence number
-// Control Info: The sequence number to which (but not include) all the previous packets have beed received
+// Control Info: The sequence number to which (but not include) all the previous packets have been received
// Optional: RTT
// RTT Variance
// available receiver buffer size (in bytes)
@@ -131,7 +131,7 @@ modified by
// 7: Message Drop Request (UMSG_DROPREQ)
// Add. Info: Message ID
// Control Info: first sequence number of the message
-// last seqeunce number of the message
+// last sequence number of the message
// 8: Error Signal from the Peer Side (UMSG_PEERERROR)
// Add. Info: Error code
// Control Info: None
@@ -151,7 +151,7 @@ modified by
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Loss List Field Coding:
-// For any consecutive lost seqeunce numbers that the differnece between
+// For any consecutive lost sequence numbers that the difference between
// the last and first is more than 1, only record the first (a) and the
// the last (b) sequence numbers in the loss list field, and modify the
// the first bit of a to 1.
@@ -174,7 +174,7 @@ using namespace srt_logging;
namespace srt {
-// Set up the aliases in the constructure
+// Set up the aliases in the constructor
CPacket::CPacket()
: m_nHeader() // Silences GCC 12 warning "used uninitialized".
, m_extra_pad()
diff --git a/srtcore/packet.h b/srtcore/packet.h
index 5a6d6eb152..a735c4cb0d 100644
--- a/srtcore/packet.h
+++ b/srtcore/packet.h
@@ -64,7 +64,7 @@ namespace srt
//////////////////////////////////////////////////////////////////////////////
// The purpose of the IOVector class is to proide a platform-independet interface
-// to the WSABUF on Windows and iovec on Linux, that can be easilly converted
+// to the WSABUF on Windows and iovec on Linux, that can be easily converted
// to the native structure for use in WSARecvFrom() and recvmsg(...) functions
class IOVector
#ifdef _WIN32
@@ -372,7 +372,12 @@ class CPacket
static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size = SRT_PH_E_SIZE * sizeof(uint32_t)
// Can also be calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr).
- static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }.
+ static const size_t BARE_UDP_HDR_SIZE = 8; // 8 bytes of UDP { u16 sport, dport, len, csum }.
+ static const size_t IPv4_HDR_SIZE = 20; // 20 bytes IPv4
+ static const size_t IPv6_HDR_SIZE = 40; // 40 bytes IPv6
+
+ static const size_t UDP_HDR_SIZE = BARE_UDP_HDR_SIZE + IPv4_HDR_SIZE;
+ static const size_t UDP_HDR_SIZE_IPv6 = BARE_UDP_HDR_SIZE + IPv6_HDR_SIZE;
static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE;
diff --git a/srtcore/packetfilter.cpp b/srtcore/packetfilter.cpp
index dc7e5b422b..e8b2a6102a 100644
--- a/srtcore/packetfilter.cpp
+++ b/srtcore/packetfilter.cpp
@@ -26,12 +26,13 @@ using namespace std;
using namespace srt_logging;
using namespace srt::sync;
-bool srt::ParseFilterConfig(const string& s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf)
+namespace srt {
+bool PacketFilter::Internal::ParseConfig(const string& s, SrtFilterConfig& w_config, PacketFilter::Factory** ppf)
{
if (!SrtParseConfig(s, (w_config)))
return false;
- PacketFilter::Factory* fac = PacketFilter::find(w_config.type);
+ PacketFilter::Factory* fac = find(w_config.type);
if (!fac)
return false;
@@ -43,24 +44,26 @@ bool srt::ParseFilterConfig(const string& s, SrtFilterConfig& w_config, PacketFi
return true;
}
-bool srt::ParseFilterConfig(const string& s, SrtFilterConfig& w_config)
+bool ParseFilterConfig(const std::string& s, SrtFilterConfig& w_config)
{
- return ParseFilterConfig(s, (w_config), NULL);
+ return PacketFilter::internal().ParseConfig(s, (w_config), NULL);
}
// Parameters are passed by value because they need to be potentially modicied inside.
-bool srt::CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer)
+bool PacketFilter::Internal::CheckFilterCompat(SrtFilterConfig& w_agent, const SrtFilterConfig& peer_in)
{
- PacketFilter::Factory* fac = PacketFilter::find(w_agent.type);
+ PacketFilter::Factory* fac = find(w_agent.type);
if (!fac)
return false;
SrtFilterConfig defaults;
- if (!ParseFilterConfig(fac->defaultConfig(), (defaults)))
+ if (!ParseConfig(fac->defaultConfig(), (defaults)))
{
return false;
}
+ // Make a copy so that modifications can be done. This is only required for internal checks.
+ SrtFilterConfig peer = peer_in;
set keys;
// Extract all keys to identify also unspecified parameters on both sides
// Note that theoretically for FEC it could simply check for the "cols" parameter
@@ -109,20 +112,18 @@ bool srt::CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer)
return true;
}
-namespace srt {
- struct SortBySequence
+struct SortBySequence
+{
+ bool operator()(const CUnit* u1, const CUnit* u2)
{
- bool operator()(const CUnit* u1, const CUnit* u2)
- {
- int32_t s1 = u1->m_Packet.getSeqNo();
- int32_t s2 = u2->m_Packet.getSeqNo();
+ int32_t s1 = u1->m_Packet.getSeqNo();
+ int32_t s2 = u2->m_Packet.getSeqNo();
- return CSeqNo::seqcmp(s1, s2) < 0;
- }
- };
-} // namespace srt
+ return CSeqNo::seqcmp(s1, s2) < 0;
+ }
+};
-void srt::PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs)
+void PacketFilter::receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs)
{
const CPacket& rpkt = unit->m_Packet;
@@ -205,7 +206,7 @@ void srt::PacketFilter::receive(CUnit* unit, std::vector& w_incoming, lo
}
-bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet)
+bool PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_packet)
{
bool have = m_filter->packControlPacket(m_sndctlpkt, seq);
if (!have)
@@ -237,7 +238,7 @@ bool srt::PacketFilter::packControlPacket(int32_t seq, int kflg, CPacket& w_pack
}
-void srt::PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq)
+void PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq)
{
if (m_provided.empty())
return;
@@ -272,42 +273,61 @@ void srt::PacketFilter::InsertRebuilt(vector& incoming, CUnitQueue* uq)
m_provided.clear();
}
-bool srt::PacketFilter::IsBuiltin(const string& s)
+// Placement here is necessary in order to mark the location to
+// store the PacketFilter::Factory class characteristic object.
+PacketFilter::Factory::~Factory()
{
- return builtin_filters.count(s);
}
-namespace srt {
-std::set PacketFilter::builtin_filters;
-PacketFilter::filters_map_t PacketFilter::filters;
+#if HAVE_CXX11
+
+PacketFilter::Internal& PacketFilter::internal()
+{
+ static PacketFilter::Internal instance;
+ return instance;
+}
+
+#else // !HAVE_CXX11
+
+static pthread_once_t s_PacketFactoryOnce = PTHREAD_ONCE_INIT;
+
+static PacketFilter::Internal *getInstance()
+{
+ static PacketFilter::Internal instance;
+ return &instance;
}
-srt::PacketFilter::Factory::~Factory()
+PacketFilter::Internal& PacketFilter::internal()
{
+ // We don't want lock each time, pthread_once can be faster than mutex.
+ pthread_once(&s_PacketFactoryOnce, reinterpret_cast(getInstance));
+ return *getInstance();
}
-void srt::PacketFilter::globalInit()
+#endif
+
+PacketFilter::Internal::Internal()
{
// Add here builtin packet filters and mark them
// as builtin. This will disallow users to register
// external filters with the same name.
- filters["fec"] = new Creator;
- builtin_filters.insert("fec");
+ m_filters["fec"] = new PacketFilter::Creator;
+ m_builtin_filters.insert("fec");
}
-bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr)
+bool PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::string& confstr)
{
m_parent = parent;
SrtFilterConfig cfg;
- if (!ParseFilterConfig(confstr, (cfg)))
+ if (!internal().ParseConfig(confstr, (cfg)))
return false;
// Extract the "type" key from parameters, or use
// builtin if lacking.
- filters_map_t::iterator selector = filters.find(cfg.type);
- if (selector == filters.end())
+ PacketFilter::Factory *factory = internal().find(cfg.type);
+ if (factory == NULL)
return false;
SrtFilterInitializer init;
@@ -324,7 +344,7 @@ bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::strin
<< init.payload_size << " rcvbuf size=" << init.rcvbuf_size);
// Found a filter, so call the creation function
- m_filter = selector->second->Create(init, m_provided, confstr);
+ m_filter = factory->Create(init, m_provided, confstr);
if (!m_filter)
return false;
@@ -336,7 +356,7 @@ bool srt::PacketFilter::configure(CUDT* parent, CUnitQueue* uq, const std::strin
return true;
}
-bool srt::PacketFilter::correctConfig(const SrtFilterConfig& conf)
+bool PacketFilter::correctConfig(const SrtFilterConfig& conf)
{
const string* pname = map_getp(conf.parameters, "type");
@@ -346,15 +366,16 @@ bool srt::PacketFilter::correctConfig(const SrtFilterConfig& conf)
if (*pname == "adaptive")
return true;
- filters_map_t::iterator x = filters.find(*pname);
- if (x == filters.end())
+ PacketFilter::Factory *factory = internal().find(*pname);
+ if (factory == NULL)
return false;
return true;
}
-srt::PacketFilter::~PacketFilter()
+PacketFilter::~PacketFilter()
{
delete m_filter;
}
+} // namespace srt
diff --git a/srtcore/packetfilter.h b/srtcore/packetfilter.h
index 429e81e795..8d4dc6369a 100644
--- a/srtcore/packetfilter.h
+++ b/srtcore/packetfilter.h
@@ -51,7 +51,6 @@ class PacketFilter
virtual ~Factory();
};
private:
- friend bool ParseFilterConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf);
template
class Creator: public Factory
@@ -74,7 +73,6 @@ class PacketFilter
virtual ~Creator() {}
};
-
// We need a private wrapper for the auto-pointer, can't use
// std::unique_ptr here due to no C++11.
struct ManagedPtr
@@ -114,14 +112,6 @@ class PacketFilter
Factory* get() { return f; }
};
- // The list of builtin names that are reserved.
- static std::set builtin_filters;
-
- // Temporarily changed to linear searching, until this is exposed
- // for a user-defined filter.
- typedef std::map filters_map_t;
- static filters_map_t filters;
-
// This is a filter container.
SrtPacketFilterBase* m_filter;
void Check()
@@ -133,29 +123,52 @@ class PacketFilter
// Don't do any check for now.
}
+ friend class Internal;
+
public:
+ class Internal
+ {
+ public:
- static void globalInit();
+ // The list of builtin names that are reserved.
+ std::set m_builtin_filters;
- static bool IsBuiltin(const std::string&);
+ // Temporarily changed to linear searching, until this is exposed
+ // for a user-defined filter.
+ typedef std::map filters_map_t;
+ filters_map_t m_filters;
- template
- static bool add(const std::string& name)
- {
- if (IsBuiltin(name))
- return false;
- filters[name] = new Creator;
- return true;
- }
+ public:
- static Factory* find(const std::string& type)
- {
- filters_map_t::iterator i = filters.find(type);
- if (i == filters.end())
- return NULL; // No matter what to return - this is "undefined behavior" to be prevented
- return i->second.get();
- }
+ bool IsBuiltin(const std::string& s)
+ {
+ return m_builtin_filters.count(s);
+ }
+
+ template
+ bool add(const std::string& name)
+ {
+ if (IsBuiltin(name))
+ return false;
+
+ m_filters[name] = new Creator;
+ return true;
+ }
+
+ Factory* find(const std::string& type)
+ {
+ filters_map_t::iterator i = m_filters.find(type);
+ if (i == m_filters.end())
+ return NULL; // No matter what to return - this is "undefined behavior" to be prevented
+ return i->second.get();
+ }
+ bool ParseConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf = NULL);
+ bool CheckFilterCompat(srt::SrtFilterConfig& w_agent, const srt::SrtFilterConfig& peer);
+ public:
+ Internal();
+ };
+ static Internal& internal(); // Singleton accessor
// Filter is optional, so this check should be done always
// manually.
@@ -171,7 +184,7 @@ class PacketFilter
// Copy constructor - important when listener-spawning
// Things being done:
// 1. The filter is individual, so don't copy it. Set NULL.
- // 2. This will be configued anyway basing on possibly a new rule set.
+ // 2. This will be configured anyway basing on possibly a new rule set.
PacketFilter(const PacketFilter& source SRT_ATR_UNUSED): m_filter(), m_parent(), m_sndctlpkt(0), m_unitq() {}
// This function will be called by the parent CUDT
@@ -188,8 +201,8 @@ class PacketFilter
~PacketFilter();
// Simple wrappers
- void feedSource(CPacket& w_packet);
- SRT_ARQLevel arqLevel();
+ void feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); }
+ SRT_ARQLevel arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); }
bool packControlPacket(int32_t seq, int kflg, CPacket& w_packet);
void receive(CUnit* unit, std::vector& w_incoming, loss_seqs_t& w_loss_seqs);
@@ -207,12 +220,10 @@ class PacketFilter
std::vector m_provided;
};
-bool CheckFilterCompat(SrtFilterConfig& w_agent, SrtFilterConfig peer);
-
-inline void PacketFilter::feedSource(CPacket& w_packet) { SRT_ASSERT(m_filter); return m_filter->feedSource((w_packet)); }
-inline SRT_ARQLevel PacketFilter::arqLevel() { SRT_ASSERT(m_filter); return m_filter->arqLevel(); }
-
-bool ParseFilterConfig(const std::string& s, SrtFilterConfig& out, PacketFilter::Factory** ppf);
+inline bool CheckFilterCompat(SrtFilterConfig& w_agent, const SrtFilterConfig& peer)
+{
+ return PacketFilter::internal().CheckFilterCompat((w_agent), peer);
+}
} // namespace srt
diff --git a/srtcore/platform_sys.h b/srtcore/platform_sys.h
index 83763e5ea5..5dd2b928d4 100644
--- a/srtcore/platform_sys.h
+++ b/srtcore/platform_sys.h
@@ -68,7 +68,9 @@
// also other macros, like TARGET_OS_IOS etc.
#include "TargetConditionals.h"
+#ifndef __APPLE_USE_RFC_3542
#define __APPLE_USE_RFC_3542 /* IPV6_PKTINFO */
+#endif
#ifdef SRT_IMPORT_TIME
#include
diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp
index 58522402ad..523bbfefc0 100644
--- a/srtcore/queue.cpp
+++ b/srtcore/queue.cpp
@@ -221,6 +221,11 @@ srt::CSndUList::~CSndUList()
delete[] m_pHeap;
}
+void srt::CSndUList::resetAtFork()
+{
+ resetCond(m_ListCond);
+}
+
void srt::CSndUList::update(const CUDT* u, EReschedule reschedule, sync::steady_clock::time_point ts)
{
ScopedLock listguard(m_ListLock);
@@ -416,6 +421,17 @@ srt::CSndQueue::CSndQueue()
}
srt::CSndQueue::~CSndQueue()
+{
+ delete m_pSndUList;
+}
+
+void srt::CSndQueue::resetAtFork()
+{
+ resetThread(&m_WorkerThread);
+ m_pSndUList->resetAtFork();
+}
+
+void srt::CSndQueue::stop()
{
m_bClosing = true;
@@ -432,8 +448,6 @@ srt::CSndQueue::~CSndQueue()
HLOGC(rslog.Debug, log << "SndQueue: EXIT");
m_WorkerThread.join();
}
-
- delete m_pSndUList;
}
int srt::CSndQueue::ioctlQuery(int type) const
@@ -922,7 +936,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst
return;
HLOGC(cnlog.Debug,
- log << "updateConnStatus: collected " << toProcess.size() << " for processing, " << toRemove.size()
+ log << FUNID() << ": collected " << toProcess.size() << " for processing, " << toRemove.size()
<< " to close");
// Repeat (resend) connection request.
@@ -942,43 +956,45 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst
// to interpret these data (for caller-listener this was already done by `processConnectRequest`
// before calling this function), and it checks for the data presence.
- EReadStatus read_st = rst;
- EConnectStatus conn_st = cst;
-
CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id);
if (!sk.socket)
{
// Socket deleted already, so stop this and proceed to the next loop.
- LOGC(cnlog.Error, log << "updateConnStatus: IPE: socket @" << i->id << " already closed, proceed to only removal from lists");
+ LOGC(cnlog.Error, log << FUNID() << ": IPE: socket @" << i->id << " already closed, proceed to only removal from lists");
toRemove.push_back(*i);
continue;
}
+ EReadStatus read_st = rst;
+ EConnectStatus conn_st = cst;
- if (cst != CONN_RENDEZVOUS && dest_id != 0)
- {
- if (i->id != dest_id)
- {
- HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " but for RID @" << i->id
- << " dest_id=@" << dest_id << " - resetting to AGAIN");
+ // Ok, we should have 3 cases here:
+ // 1. id == 0 ==> conn_st cannot be == CONN_RENDEZVOUS; reset to AGAIN always
+ // 2. conn_st == CONN_RENDEZVOUS -> id > 0 and no "alien" sockets are expected to be in the loop -> never reset to AGAIN
+ // 3. id > 0 and no rendezvous -> reset to AGAIN, unless id == dest_id.
- read_st = RST_AGAIN;
- conn_st = CONN_AGAIN;
- }
- else
- {
- HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " for @"
- << i->id);
- }
+ // Condition:
+ // IF CONN_RENDEZVOUS -> never reset to AGAIN.
+ // ELSE IF dest_id == id -> don't reset to AGAIN
+ // ELSE: reset to again.
+
+ if (cst == CONN_RENDEZVOUS || i->id == dest_id)
+ {
+ HLOGC(cnlog.Debug, log << FUNID() << ": applied to @" << i->id
+ << (cst == CONN_RENDEZVOUS ? "[RDV] " : "")
+ << " with target @" << dest_id << " -- remains: cst=" << ConnectStatusStr(cst));
}
else
{
- HLOGC(cnlog.Debug, log << "updateConnStatus: cst=" << ConnectStatusStr(cst) << " and dest_id=@" << dest_id
- << " - NOT checking against RID @" << i->id);
+ HLOGC(cnlog.Debug, log << FUNID() << ": applied to @" << i->id
+ << " with target @" << dest_id << " -- resetting to AGAIN");
+
+ read_st = RST_AGAIN;
+ conn_st = CONN_AGAIN;
}
HLOGC(cnlog.Debug,
- log << "updateConnStatus: processing async conn for @" << i->id << " FROM " << i->peeraddr.str());
+ log << FUNID() << ": processing async conn for @" << i->id << " FROM " << i->peeraddr.str());
if (!i->u->processAsyncConnectRequest(read_st, conn_st, pkt, i->peeraddr))
{
@@ -999,14 +1015,14 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst
for (vector::iterator i = toRemove.begin(); i != toRemove.end(); ++i)
{
- HLOGC(cnlog.Debug, log << "updateConnStatus: COMPLETING dep objects update on failed @" << i->id);
+ HLOGC(cnlog.Debug, log << FUNID() << ": COMPLETING dep objects update on failed @" << i->id);
remove(i->id);
CUDTUnited::SocketKeeper sk (CUDT::uglobal(), i->id);
if (!sk.socket)
{
// This actually shall never happen, so it's a kind of paranoid check.
- LOGC(cnlog.Error, log << "updateConnStatus: IPE: socket @" << i->id << " already closed, NOT ACCESSING its contents");
+ LOGC(cnlog.Error, log << FUNID() << ": IPE: socket @" << i->id << " already closed, NOT ACCESSING its contents");
continue;
}
@@ -1043,7 +1059,7 @@ void srt::CRendezvousQueue::updateConnStatus(EReadStatus rst, EConnectStatus cst
if (find_if(toRemove.begin(), toRemove.end(), LinkStatusInfo::HasID(i->m_iID)) != toRemove.end())
{
LOGC(cnlog.Error,
- log << "updateConnStatus: processAsyncConnectRequest FAILED on @" << i->m_iID
+ log << FUNID() << ": processAsyncConnectRequest FAILED on @" << i->m_iID
<< ". Setting TTL as EXPIRED.");
i->m_tsTTL =
steady_clock::time_point(); // Make it expire right now, will be picked up at the next iteration
@@ -1064,7 +1080,7 @@ bool srt::CRendezvousQueue::qualifyToHandle(EReadStatus rst,
return false; // nothing to process.
HLOGC(cnlog.Debug,
- log << "updateConnStatus: updating after getting pkt with DST socket ID @" << iDstSockID
+ log << FUNID() << ": updating after getting pkt with DST socket ID @" << iDstSockID
<< " status: " << ConnectStatusStr(cst));
for (list::iterator i = m_lRendezvousID.begin(), i_next = i; i != m_lRendezvousID.end(); i = i_next)
@@ -1178,15 +1194,6 @@ srt::CRcvQueue::CRcvQueue()
srt::CRcvQueue::~CRcvQueue()
{
- m_bClosing = true;
-
- if (m_WorkerThread.joinable())
- {
- HLOGC(rslog.Debug, log << "RcvQueue: EXIT");
- m_WorkerThread.join();
- }
- releaseCond(m_BufferCond);
-
delete m_pUnitQueue;
delete m_pRcvUList;
delete m_pHash;
@@ -1204,6 +1211,24 @@ srt::CRcvQueue::~CRcvQueue()
}
}
+void srt::CRcvQueue::resetAtFork()
+{
+ resetThread(&m_WorkerThread);
+}
+
+void srt::CRcvQueue::stop()
+{
+ m_bClosing = true;
+
+ if (m_WorkerThread.joinable())
+ {
+ HLOGC(rslog.Debug, log << "RcvQueue: EXIT");
+ m_WorkerThread.join();
+ }
+ releaseCond(m_BufferCond);
+}
+
+
#if ENABLE_LOGGING
srt::sync::atomic srt::CRcvQueue::m_counter(0);
#endif
@@ -1238,7 +1263,7 @@ void srt::CRcvQueue::init(int qsize, size_t payload, int version, int hsize, CCh
}
}
-void* srt::CRcvQueue::worker(void* param)
+void* srt::CRcvQueue::worker(void* param) ATR_NOEXCEPT
{
CRcvQueue* self = (CRcvQueue*)param;
sockaddr_any sa(self->getIPversion());
@@ -1323,7 +1348,11 @@ void* srt::CRcvQueue::worker(void* param)
<< "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway.");
}
cst = CONN_REJECT;
- break;
+
+ // DO NOT interrupt though - the worker thread must run until all
+ // sockets are removed from the multiplexer. Alternatively you can forcefully
+ // remove all sockets from the receive U list.
+ continue;
}
// OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue.
@@ -1350,10 +1379,7 @@ void* srt::CRcvQueue::worker(void* param)
{
HLOGC(qrlog.Debug,
log << CUDTUnited::CONID(u->m_SocketID) << " SOCKET broken, REMOVING FROM RCV QUEUE/MAP.");
- // the socket must be removed from Hash table first, then RcvUList
- self->m_pHash->remove(u->m_SocketID);
- self->m_pRcvUList->remove(u);
- u->m_pRNode->m_bOnList = false;
+ self->removeFromLists(u);
}
ul = self->m_pRcvUList->m_pUList;
@@ -1452,7 +1478,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit,
bool have_listener = false;
{
SharedLock shl(m_pListener);
- CUDT* pListener = m_pListener.getPtrNoLock();
+ CUDT* pListener = m_pListener.get_locked(shl);
if (pListener)
{
@@ -1562,7 +1588,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUni
// side just started sending its handshake packets, the local side has already
// run the CRcvQueue::worker thread, and this worker thread is trying to dispatch
// the handshake packet too early, before the dispatcher has a chance to see
- // this socket registerred in the RendezvousQueue, which causes the packet unable
+ // this socket registered in the RendezvousQueue, which causes the packet unable
// to be dispatched. Therefore simply treat every "out of band" packet (with socket
// not belonging to the connection and not registered as rendezvous) as "possible
// attack" and ignore it. This also should better protect the rendezvous socket
@@ -1623,7 +1649,7 @@ srt::EConnectStatus srt::CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUni
// that we KNOW (by the cst == CONN_ACCEPT result) that the socket should be inserted
// into the pending anteroom.
- CUDT* ne = getNewEntry(); // This function actuall removes the entry and returns it.
+ CUDT* ne = getNewEntry(); // This function actually removes the entry and returns it.
// This **should** now always return a non-null value, but check it first
// because if this accidentally isn't true, the call to worker_ProcessAddressedPacket will
// result in redirecting it to here and so on until the call stack overflow. In case of
@@ -1746,17 +1772,25 @@ int srt::CRcvQueue::recvfrom(int32_t id, CPacket& w_packet)
return (int)w_packet.getLength();
}
-int srt::CRcvQueue::setListener(CUDT* u)
+bool srt::CRcvQueue::setListener(CUDT* u)
{
- if (!m_pListener.set(u))
- return -1;
+ return m_pListener.compare_exchange(NULL, u);
+}
- return 0;
+srt::CUDT* srt::CRcvQueue::getListener()
+{
+ SharedLock lkl (m_pListener);
+ return m_pListener.get_locked(lkl);
}
-void srt::CRcvQueue::removeListener(const CUDT* u)
+// XXX NOTE: TSan reports here false positive against the call
+// to locateSocket in CUDTUnited::newConnection. This here will apply
+// exclusive lock on m_pListener, while keeping shared lock on
+// CUDTUnited::m_GlobControlLock in CUDTUnited::closeAllSockets.
+// As the other thread locks both as shared, this is no deadlock risk.
+bool srt::CRcvQueue::removeListener(CUDT* u)
{
- m_pListener.clearIf(u);
+ return m_pListener.compare_exchange(u, NULL);
}
void srt::CRcvQueue::registerConnector(const SRTSOCKET& id,
@@ -1837,19 +1871,55 @@ void srt::CRcvQueue::storePktClone(int32_t id, const CPacket& pkt)
}
}
-void srt::CMultiplexer::destroy()
+void srt::CRcvQueue::removeFromLists(CUDT* u)
{
- // Reverse order of the assigned.
- delete m_pRcvQueue;
- delete m_pSndQueue;
- // The demux is borrowed by both queues; delete it only after their workers have
- // joined (queue destructors above) and before the channel it sends through.
- delete m_pSrtlaRec;
- delete m_pTimer;
+ // the socket must be removed from Hash table first, then RcvUList
+ m_pHash->remove(u->m_SocketID);
+ // SRTLA: a group bound to this connection was kept while it lived; let it go.
+ if (m_pSrtlaRec)
+ m_pSrtlaRec->unbindSocket(u->m_SocketID);
+ m_pRcvUList->remove(u);
+ u->m_pRNode->m_bOnList = false;
+}
+
+srt::CMultiplexer::~CMultiplexer()
+{
+ if (m_pRcvQueue != NULL) delete m_pRcvQueue;
+ if (m_pSndQueue != NULL) delete m_pSndQueue;
+ if (m_pSrtlaRec != NULL) delete m_pSrtlaRec;
+ if (m_pTimer != NULL) delete m_pTimer;
+ close();
+}
+
+void srt::CMultiplexer::resetAtFork()
+{
+ if (m_pRcvQueue != NULL)
+ m_pRcvQueue->resetAtFork();
+ if (m_pSndQueue != NULL)
+ m_pSndQueue->resetAtFork();
+}
+void srt::CMultiplexer::close()
+{
if (m_pChannel)
{
m_pChannel->close();
delete m_pChannel;
+ m_pChannel = NULL;
}
}
+
+void srt::CMultiplexer::stop()
+{
+ if (m_pRcvQueue != NULL)
+ m_pRcvQueue->stop();
+ if (m_pSndQueue != NULL)
+ m_pSndQueue->stop();
+}
+
+void srt::CMultiplexer::destroy()
+{
+ // Reverse order of the assigned.
+ stop();
+ close();
+}
diff --git a/srtcore/queue.h b/srtcore/queue.h
index d2cb118703..a2c12a4670 100644
--- a/srtcore/queue.h
+++ b/srtcore/queue.h
@@ -158,6 +158,7 @@ class CSndUList
};
static EReschedule rescheduleIf(bool cond) { return cond ? DO_RESCHEDULE : DONT_RESCHEDULE; }
+ void resetAtFork();
/// Update the timestamp of the UDT instance on the list.
/// @param [in] u pointer to the UDT instance
@@ -339,7 +340,7 @@ class CRendezvousQueue
/// @brief Update status of connections in the pending queue.
/// Stop connecting if TTL expires. Resend handshake request every 250 ms if no response from the peer.
- /// @param rst result of reading from a UDP socket: received packet / nothin read / read error.
+ /// @param rst result of reading from a UDP socket: received packet / nothing read / read error.
/// @param cst target status for pending connection: reject or proceed.
/// @param pktIn packet received from the UDP socket.
void updateConnStatus(EReadStatus rst, EConnectStatus cst, CUnit* unit);
@@ -368,7 +369,7 @@ class CRendezvousQueue
/// - Sockets with expired TTL go to the 'to_remove' list and removed from the queue straight away.
/// - If HS request is to be resent (resend 250 ms if no response from the peer) go to the 'to_process' list.
///
- /// @param rst result of reading from a UDP socket: received packet / nothin read / read error.
+ /// @param rst result of reading from a UDP socket: received packet / nothing read / read error.
/// @param cst target status for pending connection: reject or proceed.
/// @param iDstSockID destination socket ID of the received packet.
/// @param[in,out] toRemove stores sockets with expired TTL.
@@ -402,6 +403,7 @@ class CSndQueue
~CSndQueue();
public:
+ void resetAtFork();
// XXX There's currently no way to access the socket ID set for
// whatever the queue is currently working for. Required to find
// some way to do this, possibly by having a "reverse pointer".
@@ -440,6 +442,7 @@ class CSndQueue
int sockoptQuery(int level, int type) const;
void setClosing() { m_bClosing = true; }
+ void stop();
private:
static void* worker(void* param);
@@ -491,6 +494,7 @@ class CRcvQueue
~CRcvQueue();
public:
+ void resetAtFork();
// XXX There's currently no way to access the socket ID set for
// whatever the queue is currently working. Required to find
// some way to do this, possibly by having a "reverse pointer".
@@ -518,8 +522,11 @@ class CRcvQueue
int getIPversion() { return m_iIPversion; }
+ void removeFromLists(CUDT* u);
+
+ void stop();
private:
- static void* worker(void* param);
+ static void* worker(void* param) ATR_NOEXCEPT;
sync::CThread m_WorkerThread;
// Subroutines of worker
EReadStatus worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr_any& sa);
@@ -547,8 +554,9 @@ class CRcvQueue
#endif
private:
- int setListener(CUDT* u);
- void removeListener(const CUDT* u);
+ bool setListener(CUDT* u);
+ CUDT* getListener();
+ bool removeListener(CUDT* u);
void registerConnector(const SRTSOCKET& id,
CUDT* u,
@@ -609,6 +617,10 @@ struct CMultiplexer
{
}
+ ~CMultiplexer();
+ void resetAtFork();
+ void close();
+ void stop();
void destroy();
};
diff --git a/srtcore/socketconfig.cpp b/srtcore/socketconfig.cpp
index f1a0ae69f6..26c826d050 100644
--- a/srtcore/socketconfig.cpp
+++ b/srtcore/socketconfig.cpp
@@ -56,7 +56,7 @@ namespace srt
{
int RcvBufferSizeOptionToValue(int val, int flightflag, int mss)
{
- // Mimimum recv buffer size is 32 packets
+ // Minimum recv buffer size is 32 packets
const int mssin_size = mss - CPacket::UDP_HDR_SIZE;
int bufsize;
@@ -811,7 +811,7 @@ struct CSrtConfigSetter
// Parse the configuration string prematurely
SrtFilterConfig fc;
PacketFilter::Factory* fax = 0;
- if (!ParseFilterConfig(arg, (fc), (&fax)))
+ if (!PacketFilter::internal().ParseConfig(arg, (fc), (&fax)))
{
LOGC(aclog.Error,
log << "SRTO_PACKETFILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. "
@@ -1035,7 +1035,7 @@ bool CSrtConfig::payloadSizeFits(size_t val, int /*ip_family*/, std::string& w_e
}
}
- // Not checking AUTO to allow defaul 1456 bytes.
+ // Not checking AUTO to allow default 1456 bytes.
if ((this->iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM)
&& (val > (SRT_LIVE_MAX_PLSIZE - HAICRYPT_AUTHTAG_MAX)))
{
@@ -1099,10 +1099,10 @@ bool SRT_SocketOptionObject::add(SRT_SOCKOPT optname, const void* optval, size_t
// Header size will get the size likely aligned, but it won't
// hurt if the memory size will be up to 4 bytes more than
- // needed - and it's better to not risk that alighment rules
+ // needed - and it's better to not risk that alignment rules
// will make these calculations result in less space than needed.
const size_t headersize = sizeof(SingleOption);
- const size_t payload = std::min(sizeof(uint32_t), optlen);
+ const size_t payload = std::max(sizeof(uint32_t), optlen);
unsigned char* mem = new unsigned char[headersize + payload];
SingleOption* option = reinterpret_cast(mem);
option->option = optname;
diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h
index 1773b463a7..d3593ff827 100644
--- a/srtcore/socketconfig.h
+++ b/srtcore/socketconfig.h
@@ -70,6 +70,8 @@ written by
#define SRT_VERSION_MIN(v) (0x00FF00 & (v))
#define SRT_VERSION_PCH(v) (0x0000FF & (v))
+static const int SRT_OHEAD_DEFAULT_P100 = 25;
+
// NOTE: SRT_VERSION is primarily defined in the build file.
extern const int32_t SRT_DEF_VERSION;
@@ -94,7 +96,7 @@ struct CSrtMuxerConfig
int iUDPSndBufSize; // UDP sending buffer size
int iUDPRcvBufSize; // UDP receiving buffer size
- // NOTE: this operator is not reversable. The syntax must use:
+ // NOTE: this operator is not reversible. The syntax must use:
// muxer_entry == socket_entry
bool isCompatWith(const CSrtMuxerConfig& other) const
{
@@ -148,6 +150,12 @@ class StringStorage
memset(stor, 0, sizeof stor);
}
+ StringStorage(const char* s, size_t length)
+ : len(0)
+ {
+ set(s, length);
+ }
+
bool set(const char* s, size_t length)
{
if (length > SIZE)
@@ -210,7 +218,7 @@ struct CSrtConfig: CSrtMuxerConfig
static const int COMM_RESPONSE_TIMEOUT_MS = 5 * 1000; // 5 seconds
static const uint32_t COMM_DEF_MIN_STABILITY_TIMEOUT_MS = 60; // 60 ms
- // Mimimum recv flight flag size is 32 packets
+ // Minimum recv flight flag size is 32 packets
static const int DEF_MIN_FLIGHT_PKT = 32;
static const size_t MAX_SID_LENGTH = 512;
static const size_t MAX_PFILTER_LENGTH = 64;
@@ -316,7 +324,7 @@ struct CSrtConfig: CSrtMuxerConfig
, iCryptoMode(CIPHER_MODE_AUTO)
, llInputBW(0)
, llMinInputBW(0)
- , iOverheadBW(25)
+ , iOverheadBW(SRT_OHEAD_DEFAULT_P100)
, bRcvNakReport(true)
, iMaxReorderTolerance(0) // Sensible optimal value is 10, 0 preserves old behavior
, uKmRefreshRatePkt(0)
@@ -337,7 +345,7 @@ struct CSrtConfig: CSrtMuxerConfig
// Default congestion is "live".
// Available builtin congestions: "file".
- // Others can be registerred.
+ // Others can be registered.
sCongestion.set("live", 4);
}
diff --git a/srtcore/srt.h b/srtcore/srt.h
index a28fb1411e..c9cce08642 100644
--- a/srtcore/srt.h
+++ b/srtcore/srt.h
@@ -109,17 +109,21 @@ written by
#define SRT_ATR_DEPRECATED
#define SRT_ATR_DEPRECATED_PX [[deprecated]]
+#define SRT_ATR_NODISCARD [[nodiscard]]
// GNUG is GNU C/C++; this syntax is also supported by Clang
#elif defined(__GNUC__)
#define SRT_ATR_DEPRECATED_PX
#define SRT_ATR_DEPRECATED __attribute__((deprecated))
+#define SRT_ATR_NODISCARD __attribute__((warn_unused_result))
#elif defined(_MSC_VER)
#define SRT_ATR_DEPRECATED_PX __declspec(deprecated)
#define SRT_ATR_DEPRECATED // no postfix-type modifier
+#define SRT_ATR_NODISCARD _Check_return_
#else
#define SRT_ATR_DEPRECATED_PX
#define SRT_ATR_DEPRECATED
+#define SRT_ATR_NODISCARD
#endif
#ifdef __cplusplus
@@ -138,8 +142,10 @@ static const int32_t SRTGROUP_MASK = (1 << 30);
#ifdef _WIN32
typedef SOCKET SYSSOCKET;
+ static const SYSSOCKET SYSSOCKET_INVALID = INVALID_SOCKET;
#else
typedef int SYSSOCKET;
+ static const int SYSSOCKET_INVALID = -1;
#endif
#ifndef ENABLE_BONDING
@@ -182,7 +188,7 @@ typedef enum SRT_SOCKOPT {
SRTO_RCVTIMEO = 14, // recv() timeout
SRTO_REUSEADDR = 15, // reuse an existing port or create a new one
SRTO_MAXBW = 16, // maximum bandwidth (bytes per second) that the connection can use
- SRTO_STATE = 17, // current socket state, see UDTSTATUS, read only
+ SRTO_STATE = 17, // current socket state, see SRT_SOCKSTATUS, read only
SRTO_EVENT = 18, // current available events associated with the socket
SRTO_SNDDATA = 19, // size of data in the sending buffer
SRTO_RCVDATA = 20, // size of data available for recv
@@ -217,7 +223,7 @@ typedef enum SRT_SOCKOPT {
SRTO_PAYLOADSIZE, // Maximum payload size sent in one UDP packet (0 if unlimited)
SRTO_TRANSTYPE = 50, // Transmission type (set of options required for given transmission type)
SRTO_KMREFRESHRATE, // After sending how many packets the encryption key should be flipped to the new key
- SRTO_KMPREANNOUNCE, // How many packets before key flip the new key is annnounced and after key flip the old one decommissioned
+ SRTO_KMPREANNOUNCE, // How many packets before key flip the new key is announced and after key flip the old one decommissioned
SRTO_ENFORCEDENCRYPTION, // Connection to be rejected or quickly broken when one side encryption set or bad password
SRTO_IPV6ONLY, // IPV6_V6ONLY mode
SRTO_PEERIDLETIMEO, // Peer-idle timeout (max time of silence heard from peer) in [ms]
@@ -236,12 +242,18 @@ typedef enum SRT_SOCKOPT {
SRTO_SRTLA = 120, // On a listener: designate an SRTLA (link-aggregation) demux listener.
// Inherited by accepted connections, where it also enables the SRTLA
- // multipath delivery tuning (retransmit-flag / ordered-delivery heuristics).
- // (Formerly SRTO_SRTLAPATCHES.)
+ // multipath delivery tuning (retransmit-flag / ordered-delivery heuristics)
+ // and enforces the SRTLA minimum receiver latency of 1000 ms: a lower
+ // SRTO_RCVLATENCY / SRTO_LATENCY is raised on accept and the raised value
+ // is negotiated to the sender. (Formerly SRTO_SRTLAPATCHES.)
SRTO_E_SIZE // Always last element, not a valid option.
} SRT_SOCKOPT;
+#ifndef SRTO_SRTLAPATCHES
+#define SRTO_SRTLAPATCHES SRTO_SRTLA
+#endif
+
#ifdef __cplusplus
@@ -406,6 +418,21 @@ struct CBytePerfMon
int64_t pktRecvUnique; // number of packets to be received by the application
uint64_t byteSentUnique; // number of data bytes, sent by the application
uint64_t byteRecvUnique; // number of data bytes to be received by the application
+
+ // Packets that were still missing once the reorder grace period was over and were
+ // therefore requested back. Unlike pktRcvLoss (a sequence discontinuity metric per
+ // RFC 4737, which counts reordering as loss and never takes it back), this counts
+ // only losses the receiver itself concluded were real. NAK repeats are not counted.
+ int pktRcvLossConfirmedTotal; // total number of packets confirmed as lost (receiver side)
+ int pktRcvLossConfirmed; // number of packets confirmed as lost (receiver side)
+
+ // Share of the traffic that was not impaired, in percent (100 = clean, 0 = nothing
+ // got through), over the last completed one-second sampling window. The window is
+ // rolled internally on its own cadence, so these are independent of @a clear and of
+ // how often the caller polls: every read inside the same window returns the same
+ // value. Both are 100 for an idle window.
+ double pctSndQuality; // 100 * pktSentUnique / (pktSentUnique + pktRetrans + pktSndDrop)
+ double pctRcvQuality; // 100 * pktRecvUnique / (pktRecvUnique + pktRcvLossConfirmed)
};
////////////////////////////////////////////////////////////////////////////////
@@ -600,7 +627,7 @@ enum SRT_REJECT_REASON
#define SRT_LOGFA_CONGEST 7 // cclog: Congestion control module
#define SRT_LOGFA_PFILTER 8 // pflog: Packet filter module
-#define SRT_LOGFA_API_CTRL 11 // aclog: API part for socket and library managmenet
+#define SRT_LOGFA_API_CTRL 11 // aclog: API part for socket and library management
#define SRT_LOGFA_QUE_CTRL 13 // qclog: Queue control activities
@@ -640,10 +667,9 @@ enum SRT_KM_STATE
SRT_KM_S_SECURING = 1, // Stream encrypted, exchanging Keying Material
SRT_KM_S_SECURED = 2, // Stream encrypted, keying Material exchanged, decrypting ok.
SRT_KM_S_NOSECRET = 3, // Stream encrypted and no secret to decrypt Keying Material
- SRT_KM_S_BADSECRET = 4 // Stream encrypted and wrong secret is used, cannot decrypt Keying Material
-#ifdef ENABLE_AEAD_API_PREVIEW
- ,SRT_KM_S_BADCRYPTOMODE = 5 // Stream encrypted but wrong cryptographic mode is used, cannot decrypt. Since v1.5.2.
-#endif
+ SRT_KM_S_BADSECRET = 4, // Stream encrypted and wrong secret is used, cannot decrypt Keying Material
+ SRT_KM_S_BADCRYPTOMODE = 5, // Stream encrypted but wrong cryptographic mode is used, cannot decrypt. Since v1.5.2.
+ SRT_KM_S_E_SIZE
};
enum SRT_EPOLL_OPT
@@ -1006,6 +1032,13 @@ typedef struct SRT_SRTLA_PEER_STATS_ {
int32_t transitUs; // smoothed relative one-way transit (consumer anchors RTT to msRTT)
uint32_t usJitter; // smoothed jitter, microseconds (RFC 3550)
uint64_t establishedMs; // link start, demux monotonic milliseconds
+
+ // Legacy OpenIRL source compatibility for srt-live-server 1.5.2.
+ uint32_t bitrate;
+ uint32_t jitter;
+ uint64_t bytesReceived;
+ uint32_t uptime;
+ uint32_t throughput;
} SRT_SRTLA_PEER_STATS;
typedef struct SRT_SRTLA_STATS_ {
@@ -1014,6 +1047,10 @@ typedef struct SRT_SRTLA_STATS_ {
uint8_t numPeers;
uint64_t nowMs; // demux monotonic "now" (derive uptime = nowMs - establishedMs)
SRT_SRTLA_PEER_STATS peers[SRT_SRTLA_MAX_PEERS];
+
+ // Legacy OpenIRL source compatibility for srt-live-server 1.5.2.
+ uint32_t totalBitrate;
+ uint64_t timestamp;
} SRT_SRTLA_STATS;
SRT_API int srt_srtla_stats(SRTSOCKET u, SRT_SRTLA_STATS* stats);
diff --git a/srtcore/srt_attr_defs.h b/srtcore/srt_attr_defs.h
index 726c4a03b4..1c489658f6 100644
--- a/srtcore/srt_attr_defs.h
+++ b/srtcore/srt_attr_defs.h
@@ -17,26 +17,17 @@ used by SRT library internally.
// ATTRIBUTES:
//
-// SRT_ATR_UNUSED: declare an entity ALLOWED to be unused (prevents warnings)
-// ATR_DEPRECATED: declare an entity deprecated (compiler should warn when used)
// ATR_NOEXCEPT: The true `noexcept` from C++11, or nothing if compiling in pre-C++11 mode
// ATR_NOTHROW: In C++11: `noexcept`. In pre-C++11: `throw()`. Required for GNU libstdc++.
// ATR_CONSTEXPR: In C++11: `constexpr`. Otherwise empty.
// ATR_OVERRIDE: In C++11: `override`. Otherwise empty.
// ATR_FINAL: In C++11: `final`. Otherwise empty.
-#ifdef __GNUG__
-#define ATR_DEPRECATED __attribute__((deprecated))
-#else
-#define ATR_DEPRECATED
-#endif
-#if HAVE_CXX11
-#define SRT_ATR_ALIGNAS(n) alignas(n)
-#elif HAVE_GCC
-#define SRT_ATR_ALIGNAS(n) __attribute__((aligned(n)))
+#ifdef __GNUG__
+#define HAVE_GCC 1
#else
-#define SRT_ATR_ALIGNAS(n)
+#define HAVE_GCC 0
#endif
#if defined(__cplusplus) && __cplusplus > 199711L
@@ -44,18 +35,15 @@ used by SRT library internally.
// For gcc 4.7, claim C++11 is supported, as long as experimental C++0x is on,
// however it's only the "most required C++11 support".
#if defined(__GXX_EXPERIMENTAL_CXX0X__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 // 4.7 only!
-#define ATR_NOEXCEPT
-#define ATR_NOTHROW throw()
-#define ATR_CONSTEXPR
-#define ATR_OVERRIDE
-#define ATR_FINAL
#else
#define HAVE_FULL_CXX11 1
-#define ATR_NOEXCEPT noexcept
-#define ATR_NOTHROW noexcept
-#define ATR_CONSTEXPR constexpr
-#define ATR_OVERRIDE override
-#define ATR_FINAL final
+
+#if __cplusplus >= 201703L
+#define HAVE_CXX17 1
+#else
+#define HAVE_CXX17 0
+#endif
+
#endif
#elif defined(_MSC_VER) && _MSC_VER >= 1800
// Microsoft Visual Studio supports C++11, but not fully,
@@ -65,26 +53,41 @@ used by SRT library internally.
#define HAVE_CXX11 1
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023026
#define HAVE_FULL_CXX11 1
+
+#if __cplusplus >= 201703L
+#define HAVE_CXX17 1
+#else
+#define HAVE_CXX17 0
+#endif
+
+#endif
+#else
+#define HAVE_CXX11 0
+#define HAVE_CXX17 0
+#endif // __cplusplus
+
+#if HAVE_FULL_CXX11
#define ATR_NOEXCEPT noexcept
#define ATR_NOTHROW noexcept
#define ATR_CONSTEXPR constexpr
#define ATR_OVERRIDE override
#define ATR_FINAL final
#else
+// These are both for HAVE_CXX11 == 1 and 0.
#define ATR_NOEXCEPT
#define ATR_NOTHROW throw()
#define ATR_CONSTEXPR
#define ATR_OVERRIDE
#define ATR_FINAL
#endif
+
+#if HAVE_CXX11
+#define SRT_ATR_ALIGNAS(n) alignas(n)
+#elif HAVE_GCC
+#define SRT_ATR_ALIGNAS(n) __attribute__((aligned(n)))
#else
-#define HAVE_CXX11 0
-#define ATR_NOEXCEPT
-#define ATR_NOTHROW throw()
-#define ATR_CONSTEXPR
-#define ATR_OVERRIDE
-#define ATR_FINAL
-#endif // __cplusplus
+#define SRT_ATR_ALIGNAS(n)
+#endif
#if !HAVE_CXX11 && defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1
#error "The currently compiled application required C++11, but your compiler doesn't support it."
diff --git a/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp
index a3c0e688f8..136b436642 100644
--- a/srtcore/srt_c_api.cpp
+++ b/srtcore/srt_c_api.cpp
@@ -26,6 +26,14 @@ written by
using namespace std;
using namespace srt;
+namespace srt
+{
+// This is provided in strerror_defs.cpp, which doesn't have
+// its header file.
+// XXX Consider adding some static function to CUDTException.
+const char* strerror_get_message(size_t major, size_t minor);
+}
+
extern "C" {
@@ -248,11 +256,9 @@ int srt_getlasterror(int* loc_errno)
return CUDT::getlasterror().getErrorCode();
}
-const char* srt_strerror(int code, int err)
+const char* srt_strerror(int code, int /*err ignored*/)
{
- static srt::CUDTException e;
- e = srt::CUDTException(CodeMajor(code/1000), CodeMinor(code%1000), err);
- return(e.getErrorMessage());
+ return strerror_get_message(CodeMajor(code/1000), CodeMinor(code%1000));
}
diff --git a/srtcore/srt_compat.c b/srtcore/srt_compat.c
index bbdf7f795e..819fe41f55 100644
--- a/srtcore/srt_compat.c
+++ b/srtcore/srt_compat.c
@@ -108,7 +108,7 @@ extern const char * SysStrError(int errnum, char * buf, size_t buflen)
// 0 for success, otherwise it's:
// - possibly -1 and the error code is in ::errno
// - possibly the error code itself
- // The details of the errror are not interesting; simply
+ // The details of the error are not interesting; simply
// craft a fallback message in this case.
if (strerror_r(errnum, buf, buflen) != 0)
{
diff --git a/srtcore/srt_compat.h b/srtcore/srt_compat.h
index d4fd2361e0..46fe6aa885 100644
--- a/srtcore/srt_compat.h
+++ b/srtcore/srt_compat.h
@@ -24,7 +24,7 @@ written by
extern "C" {
#endif
-/* Ensures that we store the error in the buffer and return the bufer. */
+/* Ensures that we store the error in the buffer and return the buffer. */
const char * SysStrError(int errnum, char * buf, size_t buflen);
#ifdef __cplusplus
diff --git a/srtcore/srt_shared.rc b/srtcore/srt_shared.rc
index 4481928781..5639a02599 100644
--- a/srtcore/srt_shared.rc
+++ b/srtcore/srt_shared.rc
@@ -1,45 +1,45 @@
-// Microsoft Visual C++ generated resource script.
-//
-#include "version.h"
-#include "winres.h"
-
-/////////////////////////////////////////////////////////////////////////////
-//
-// Version
-//
-
-VS_VERSION_INFO VERSIONINFO
-#ifdef SRT_VERSION_BUILD
- FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH, SRT_VERSION_BUILD
-#else
- FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH
-#endif
- FILEFLAGSMASK 0x3fL
-#ifdef _DEBUG
- FILEFLAGS 0x1L
-#else
- FILEFLAGS 0x0L
-#endif
- FILEOS 0x40004L
- FILETYPE 0x2L
- FILESUBTYPE 0x0L
-BEGIN
- BLOCK "StringFileInfo"
- BEGIN
- BLOCK "080904b0"
- BEGIN
- VALUE "CompanyName", "SRT Alliance"
- VALUE "FileDescription", "SRT Local Build"
- VALUE "InternalName", "srt.dll"
- VALUE "LegalCopyright", ""
- VALUE "OriginalFilename", "srt.dll"
- VALUE "ProductName", "SRT"
- VALUE "ProductVersion", SRT_VERSION_STRING
- END
- END
- BLOCK "VarFileInfo"
- BEGIN
- VALUE "Translation", 0x809, 1200
- END
-END
-
+// Microsoft Visual C++ generated resource script.
+//
+#include "version.h"
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+#ifdef SRT_VERSION_BUILD
+ FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH, SRT_VERSION_BUILD
+#else
+ FILEVERSION SRT_VERSION_MAJOR, SRT_VERSION_MINOR, SRT_VERSION_PATCH
+#endif
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "080904b0"
+ BEGIN
+ VALUE "CompanyName", "SRT Alliance"
+ VALUE "FileDescription", "SRT Local Build"
+ VALUE "InternalName", "srt.dll"
+ VALUE "LegalCopyright", ""
+ VALUE "OriginalFilename", "srt.dll"
+ VALUE "ProductName", "SRT"
+ VALUE "ProductVersion", SRT_VERSION_STRING
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x809, 1200
+ END
+END
+
diff --git a/srtcore/srtla_rec.cpp b/srtcore/srtla_rec.cpp
index ccf38c0855..13d379f419 100644
--- a/srtcore/srtla_rec.cpp
+++ b/srtcore/srtla_rec.cpp
@@ -11,6 +11,8 @@
#include "platform_sys.h"
+#include
+#include
#include
#include "srtla_rec.h"
@@ -240,12 +242,17 @@ void srt::SrtlaRec::sendKeepalive(const sockaddr_any& dst)
// SRT data sequence numbers (big-endian, as received).
void srt::SrtlaRec::sendSrtlaAck(const sockaddr_any& dst, Link& l)
{
+ if (l.ack_count <= 0)
+ return;
+
+ // Variable-length ACK: header word + one word per pending SN.
uint8_t buf[SRTLA_ACK_LEN];
- memset(buf, 0, sizeof buf);
store_be32(buf, uint32_t(T_SRTLA_ACK) << 16); // 0x91000000
- for (int i = 0; i < RECV_ACK_INT; ++i)
+ const int n = l.ack_count;
+ for (int i = 0; i < n; ++i)
store_be32(buf + 4 + 4 * i, l.ack_log[i]);
- m_pChannel->sendtoRaw(dst, (const char*)buf, SRTLA_ACK_LEN);
+ m_pChannel->sendtoRaw(dst, (const char*)buf, 4 + 4 * (size_t)n);
+ l.ack_count = 0;
}
// Echo a received keepalive verbatim, padded to at least MIN_PAD (spec §3.4).
@@ -521,12 +528,20 @@ srt::SrtlaRec::Ingress srt::SrtlaRec::onIngress(const sockaddr_any& src, CUnit*
// Count first-seen (group-wide) sequence numbers as unique. First-seen tracking,
// not the rexmit flag: a retransmit the receiver never got is still new.
const int32_t sn = int32_t(w0 & 0x7FFFFFFF);
- const bool is_new = trackSn(*g, sn);
+ // Accounting covers the bound connection only: packets for another socket on the
+ // same group (stragglers of the previous connection, a reconnect being refused) run
+ // in their own sequence-number and timestamp space and would corrupt the window.
+ const bool ours = g->bound && SRTSOCKET(pkt.getHeader()[SRT_PH_ID]) == g->socket_id;
+ const bool is_new = ours && trackSn(*g, sn);
+ // A retransmission carries the original packet's timestamp, so it would measure the age
+ // of the loss, not the link: under 20 % loss that inflated the spread to seconds and
+ // pinned the hold at its cap. Retransmissions count as unique data but not as transit samples.
+ const bool rexmit = (pkt.getHeader()[SRT_PH_MSGNO] & MSGNO_REXMIT::mask) != 0;
if (is_new)
- {
l->unique_acc += double(total); // wire bytes, same basis as recv_acc (decayed above)
-
- // Relative one-way transit (new packets only). rel = (arrival - arrival_ref)
+ if (is_new && !rexmit)
+ {
+ // Relative one-way transit (new original packets only). rel = (arrival - arrival_ref)
// - (sender_ts - ts_ref); the sender clock offset cancels. Sender timestamp
// unwrapped with signed 32-bit deltas (wrap- and reorder-safe).
const uint32_t sender_ts = pkt.getHeader()[SRT_PH_TIMESTAMP];
@@ -569,11 +584,14 @@ srt::SrtlaRec::Ingress srt::SrtlaRec::onIngress(const sockaddr_any& src, CUnit*
l->have_prev_transit = true;
}
+ if (l->ack_count == 0)
+ l->ack_first_pending = now;
l->ack_log[l->ack_count++] = uint32_t(sn);
- if (l->ack_count >= RECV_ACK_INT)
+ // Flush on a full batch or when the oldest pending entry has aged out.
+ if (l->ack_count >= RECV_ACK_INT
+ || count_microseconds(now - l->ack_first_pending) >= ACK_FLUSH_US)
{
sendSrtlaAck(src, *l);
- l->ack_count = 0;
}
}
@@ -592,24 +610,52 @@ bool srt::SrtlaRec::onEgress(const sockaddr_any& peer, CPacket& pkt, const socka
if (!g)
return false; // not an SRTLA group destination: caller sends normally
+ if (g->links.empty())
+ {
+ // Registration gone but the CUDT still alive: fall back to its peer address.
+ m_pChannel->sendto(peer, pkt, src);
+ return true;
+ }
+
if (pkt.isControl())
{
const UDTMessageType mt = pkt.getType();
- if (mt == UMSG_ACK || mt == UMSG_LOSSREPORT)
+
+ if (mt == UMSG_ACK)
{
- // Fan SRT ACK / NAK out to every link so the sender can balance and
- // recover on all paths. sendto() round-trips byte order per call.
+ // Every link: the sender rebuilds its per-link in-flight counts from these.
for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it)
m_pChannel->sendto(it->addr, pkt, src);
- if (g->links.empty())
- m_pChannel->sendto(peer, pkt, src);
+ return true;
+ }
+
+ if (mt == UMSG_LOSSREPORT)
+ {
+ // NAK_FANOUT links only, starting with the one that most recently delivered.
+ size_t sent = 0;
+ for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it)
+ {
+ if (g->has_last_addr && it->addr == g->last_addr)
+ {
+ m_pChannel->sendto(it->addr, pkt, src);
+ ++sent;
+ break;
+ }
+ }
+ for (std::list::iterator it = g->links.begin();
+ it != g->links.end() && sent < NAK_FANOUT; ++it)
+ {
+ if (g->has_last_addr && it->addr == g->last_addr)
+ continue;
+ m_pChannel->sendto(it->addr, pkt, src);
+ ++sent;
+ }
return true;
}
}
- // Everything else goes to the group's most recently active link.
- const sockaddr_any& dst = g->has_last_addr ? g->last_addr : peer;
- m_pChannel->sendto(dst, pkt, src);
+ // Everything else goes over the most recently active link.
+ m_pChannel->sendto(g->has_last_addr ? g->last_addr : peer, pkt, src);
return true;
}
@@ -630,7 +676,13 @@ void srt::SrtlaRec::bindGroup(const sockaddr_any& peer, CUDT* u)
return;
}
- Group* g = ref.g;
+ Group* g = ref.g;
+ // A new connection on a surviving group (the sender's SRT reconnected while its links
+ // stayed registered) starts a new sequence-number and timestamp space. Accounting kept
+ // from the previous one would class its packets as old: per-link bitrate stuck at 0,
+ // transit and jitter frozen or offset by the restarted sender clock.
+ if (g->socket_id != u->socketID())
+ resetAccounting(*g);
g->socket_id = u->socketID();
g->bound = true;
if (!g->has_last_addr)
@@ -643,6 +695,38 @@ void srt::SrtlaRec::bindGroup(const sockaddr_any& peer, CUDT* u)
HLOGC(cnlog.Debug, log << "SRTLA bindGroup: peer " << peer.str() << " -> CUDT @" << u->socketID());
}
+void srt::SrtlaRec::unbindSocket(SRTSOCKET socket_id)
+{
+ ScopedLock lock(m_Lock);
+
+ for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g)
+ {
+ if (g->bound && g->socket_id == socket_id)
+ {
+ HLOGC(cnlog.Debug, log << "SRTLA unbindSocket: @" << socket_id << " gone, group released");
+ g->bound = false;
+ g->socket_id = SRT_INVALID_SOCK;
+ }
+ }
+}
+
+void srt::SrtlaRec::resetAccounting(Group& g)
+{
+ g.sn_valid = false; // trackSn clears the window on the next packet
+ g.sn_max = 0;
+ g.ts_init = false;
+ g.ts_last_raw = 0;
+ g.sender_ts_acc = 0;
+ for (std::list::iterator it = g.links.begin(); it != g.links.end(); ++it)
+ {
+ it->unique_acc = 0.0;
+ it->transit_ewma = 0.0;
+ it->transit_valid = false;
+ it->jitter_ewma = 0.0;
+ it->have_prev_transit = false;
+ }
+}
+
// --------------------------------------------------------------------------
// Periodic: timeouts, recovery keepalives, per-second stats (spec §6, §3.6)
// --------------------------------------------------------------------------
@@ -653,11 +737,28 @@ void srt::SrtlaRec::onPeriodic(const time_point& now)
if (!m_HaveTimers)
{
- m_LastCleanup = now;
- m_HaveTimers = true;
+ m_LastCleanup = now;
+ m_LastLinkPass = now;
+ m_HaveTimers = true;
return;
}
+ // Flush aged partial SRTLA-ACK batches (covers links that went quiet). Throttled:
+ // the pass is O(groups x links) and the receive worker reaches it once per datagram.
+ if (count_microseconds(now - m_LastLinkPass) >= LINK_PASS_US)
+ {
+ m_LastLinkPass = now;
+
+ for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g)
+ {
+ for (std::list::iterator it = g->links.begin(); it != g->links.end(); ++it)
+ {
+ if (it->ack_count > 0 && count_microseconds(now - it->ack_first_pending) >= ACK_FLUSH_US)
+ sendSrtlaAck(it->addr, *it);
+ }
+ }
+ }
+
if (count_milliseconds(now - m_LastCleanup) >= 3000) // CLEANUP_PERIOD
{
doCleanup(now);
@@ -715,8 +816,11 @@ void srt::SrtlaRec::doCleanup(const time_point& now)
}
}
- // Remove empty groups past the initial grace period (GROUP_TIMEOUT 4 s).
- if (g->links.empty() && count_milliseconds(now - g->created_at) > 4000)
+ // Remove empty groups past the initial grace period (GROUP_TIMEOUT 4 s). A group bound
+ // to a live connection stays: when every link drops at once (all modems out for a few
+ // seconds) the sender re-registers with REG2 and the same group id, and must find the
+ // group again rather than get REG_NGP while its SRT connection is still up.
+ if (g->links.empty() && !g->bound && count_milliseconds(now - g->created_at) > 4000)
{
HLOGC(cnlog.Debug, log << "SRTLA group timeout (empty)");
std::list::iterator dead = g++;
@@ -729,6 +833,52 @@ void srt::SrtlaRec::doCleanup(const time_point& now)
}
}
+uint32_t srt::SrtlaRec::holdSteadyUs(SRTSOCKET socket_id)
+{
+ ScopedLock lock(m_Lock);
+
+ Group* grp = NULL;
+ for (std::list::iterator g = m_Groups.begin(); g != m_Groups.end(); ++g)
+ {
+ if (g->bound && g->socket_id == socket_id)
+ {
+ grp = &*g;
+ break;
+ }
+ }
+ if (!grp)
+ return 0;
+
+ const time_point now = steady_clock::now();
+
+ std::vector transit, jitter;
+ for (std::list::iterator it = grp->links.begin(); it != grp->links.end(); ++it)
+ {
+ // transit_ewma only advances on arriving data, so a stale one is not a current
+ // measurement and must not widen the spread.
+ if (!it->transit_valid || count_microseconds(now - it->last_transit) > LINK_FRESH_US)
+ continue;
+ transit.push_back(it->transit_ewma);
+ jitter.push_back(it->jitter_ewma);
+ }
+ if (transit.empty())
+ return 0;
+ // The slowest link the hold waits for: a link that is late by more than the cap is not
+ // covered and gets starved by the sender's own loss penalties, links inside it stay in the
+ // bond. (A median-of-links policy was measured end to end and rejected.)
+ const double tmin = *std::min_element(transit.begin(), transit.end());
+ const double tmax = *std::max_element(transit.begin(), transit.end());
+ const double jmax = *std::max_element(jitter.begin(), jitter.end());
+ double hold = (tmax - tmin) + 4.0 * jmax;
+ HLOGC(qrlog.Debug, log << "SRTLA hold: " << transit.size() << " fresh links, spread " << ((tmax - tmin) / 1000.0)
+ << " ms, jitter " << (jmax / 1000.0) << " ms -> steady " << (hold / 1000.0) << " ms");
+ if (hold < 0.0)
+ hold = 0.0;
+ if (hold > 10e6)
+ hold = 10e6;
+ return (uint32_t)hold;
+}
+
// Fill @a out with the group's live per-link EWMA values (read fresh); rates get a
// read-time decay so they are current at this instant.
bool srt::SrtlaRec::fillStats(SRTSOCKET socket_id, SRT_SRTLA_STATS* out)
@@ -753,6 +903,8 @@ bool srt::SrtlaRec::fillStats(SRTSOCKET socket_id, SRT_SRTLA_STATS* out)
const time_point now = steady_clock::now();
out->valid = 1;
out->nowMs = uint64_t(count_milliseconds(now.time_since_epoch()));
+ out->timestamp = out->nowMs;
+ out->totalBitrate = 0;
int i = 0;
for (std::list::iterator it = grp->links.begin();
@@ -770,6 +922,14 @@ bool srt::SrtlaRec::fillStats(SRTSOCKET socket_id, SRT_SRTLA_STATS* out)
out->peers[i].transitUs = int32_t(it->transit_ewma);
out->peers[i].usJitter = uint32_t(it->jitter_ewma);
out->peers[i].establishedMs = uint64_t(count_milliseconds(it->established.time_since_epoch()));
+
+ out->peers[i].bitrate = out->peers[i].kbpsRecvUnique;
+ out->peers[i].throughput = out->peers[i].kbpsRecvRate;
+ out->peers[i].jitter = out->peers[i].usJitter;
+ out->peers[i].bytesReceived = 0;
+ const uint64_t uptime_ms = out->nowMs > out->peers[i].establishedMs ? out->nowMs - out->peers[i].establishedMs : 0;
+ out->peers[i].uptime = uint32_t((uptime_ms / 1000) > 0xffffffffu ? 0xffffffffu : (uptime_ms / 1000));
+ out->totalBitrate += out->peers[i].bitrate;
}
out->numPeers = uint8_t(i);
return true;
diff --git a/srtcore/srtla_rec.h b/srtcore/srtla_rec.h
index 59422f6bd3..0c73789255 100644
--- a/srtcore/srtla_rec.h
+++ b/srtcore/srtla_rec.h
@@ -60,9 +60,14 @@ class SrtlaRec
static const size_t SRTLA_ACK_LEN = 44; // header word + 10 SNs
static const size_t SRT_MIN_LEN = 16; // min length treated as an SRT packet
static const size_t MIN_PAD = 32; // short control packets padded to this
- static const int RECV_ACK_INT = 10; // SRT data packets per SRTLA ACK
+ static const int RECV_ACK_INT = 10; // SRT data packets per SRTLA ACK (batch cap)
+ static const int64_t ACK_FLUSH_US = 10000; // partial-batch flush age
static const size_t MAX_CONNS_PER_GROUP = 16; // == SRT_SRTLA_MAX_PEERS
static const size_t MAX_GROUPS = 200;
+
+ static const size_t NAK_FANOUT = 2; // links a loss report is duplicated over
+ static const int64_t LINK_FRESH_US = 1000000; // age up to which a transit measurement counts
+ static const int64_t LINK_PASS_US = 2000; // period of the per-link pass in onPeriodic()
static const int32_t SN_WINDOW_SIZE = 65536; // retransmission-tracking bitmap
static const int32_t SN_WINDOW_MASK = SN_WINDOW_SIZE - 1;
@@ -103,6 +108,10 @@ class SrtlaRec
/// reverse path can find the group.
void bindGroup(const sockaddr_any& peer, CUDT* u);
+ /// The connection @a socket_id has left the receive queue (closed or broken): release
+ /// its group, which from then on times out like any other once its links are gone.
+ void unbindSocket(SRTSOCKET socket_id);
+
/// Periodic pass (receive worker timing section): link/group timeouts and recovery
/// keepalives. Internally throttled.
void onPeriodic(const sync::steady_clock::time_point& now);
@@ -112,6 +121,10 @@ class SrtlaRec
/// Returns true (out.valid = 1) if such a group exists.
bool fillStats(SRTSOCKET socket_id, SRT_SRTLA_STATS* out);
+ /// Link-timing summary for the group bound to @a socket_id, in
+ /// microseconds. Returns 0 while no valid measurement exists.
+ uint32_t holdSteadyUs(SRTSOCKET socket_id);
+
private:
typedef sync::steady_clock::time_point time_point;
@@ -125,8 +138,9 @@ class SrtlaRec
bool recovering;
// SRTLA-ACK batch: raw 31-bit SNs in receive order (spec §3.5).
- uint32_t ack_log[RECV_ACK_INT];
- int ack_count;
+ uint32_t ack_log[RECV_ACK_INT];
+ int ack_count;
+ time_point ack_first_pending; // when the oldest unsent entry was queued
uint32_t connectionId; // FNV-1a of addr+port
@@ -166,7 +180,7 @@ class SrtlaRec
SRTSOCKET socket_id; // bound CUDT socket id, or SRT_INVALID_SOCK
bool bound;
time_point created_at;
- sockaddr_any last_addr; // most recently active link (reverse path primary)
+ sockaddr_any last_addr; // most recently active link (reverse path, REG1 idempotency)
bool has_last_addr;
bool data_seen;
@@ -248,6 +262,7 @@ class SrtlaRec
// --- accounting helpers ---
bool trackSn(Group& g, int32_t sn); // returns true if unique (first seen)
+ void resetAccounting(Group& g); // new connection bound: forget the old one's
// --- cleanup ---
void doCleanup(const time_point& now);
@@ -268,10 +283,13 @@ class SrtlaRec
std::map m_EgressMap; // CUDT peer addr → group (bind lifetime)
time_point m_LastCleanup;
+ time_point m_LastLinkPass;
bool m_HaveTimers;
SrtlaRec(const SrtlaRec&);
SrtlaRec& operator=(const SrtlaRec&);
+
+ friend class SrtlaRecTestAccess; // unit tests
};
} // namespace srt
diff --git a/srtcore/stats.h b/srtcore/stats.h
index 947489eb16..1b6a585bd4 100644
--- a/srtcore/stats.h
+++ b/srtcore/stats.h
@@ -176,6 +176,7 @@ struct Receiver
Metric recvdUnique;
Metric recvdRetrans; // The number of retransmitted data packets received by the receiver.
Metric lost; // The number of packets detected by the receiver as lost.
+ Metric lostConfirmed; // The number of packets the receiver requested back, i.e. still missing once the reorder grace period was over. Counts a sequence once; NAK repeats are not counted.
Metric dropped; // The number of packets dropped by the receiver (as too-late to be delivered).
Metric recvdBelated; // The number of belated packets received (dropped as too late but eventually received).
Metric undecrypted; // The number of packets received by the receiver that failed to be decrypted.
@@ -193,6 +194,7 @@ struct Receiver
recvdUnique.reset();
recvdRetrans.reset();
lost.reset();
+ lostConfirmed.reset();
dropped.reset();
recvdBelated.reset();
undecrypted.reset();
@@ -209,6 +211,7 @@ struct Receiver
recvdUnique.resetTrace();
recvdRetrans.resetTrace();
lost.resetTrace();
+ lostConfirmed.resetTrace();
dropped.resetTrace();
recvdBelated.resetTrace();
undecrypted.resetTrace();
diff --git a/srtcore/strerror_defs.cpp b/srtcore/strerror_defs.cpp
index e99c8e2778..4a93d6a2fe 100644
--- a/srtcore/strerror_defs.cpp
+++ b/srtcore/strerror_defs.cpp
@@ -66,7 +66,7 @@ const char* strerror_msgs_filesystem [] = {
const char* strerror_msgs_notsup [] = {
"Operation not supported", // MN_NONE = 0
"Operation not supported: Cannot do this operation on a BOUND socket", // MN_ISBOUND = 1
- "Operation not supported: Cannot do this operation on a CONNECTED socket", // MN_ISCONNECTED = 2
+ "Operation not supported: Cannot do this operation on a CONNECTED or LISTENING socket", // MN_ISCONNECTED = 2
"Operation not supported: Bad parameters", // MN_INVAL = 3
"Operation not supported: Invalid socket ID", // MN_SIDINVAL = 4
"Operation not supported: Cannot do this operation on an UNBOUND socket", // MN_ISUNBOUND = 5
@@ -76,7 +76,7 @@ const char* strerror_msgs_notsup [] = {
"Operation not supported: Incorrect use of Message API (sendmsg/recvmsg)", // MN_INVALMSGAPI = 9
"Operation not supported: Incorrect use of Buffer API (send/recv) or File API (sendfile/recvfile)", // MN_INVALBUFFERAPI = 10
"Operation not supported: Another socket is already listening on the same port", // MN_BUSY = 11
- "Operation not supported: Message is too large to send", // MN_XSIZE = 12
+ "Operation not supported: Message too large to send (exceeds send buffer size)", // MN_XSIZE = 12
"Operation not supported: Invalid epoll ID", // MN_EIDINVAL = 13
"Operation not supported: All sockets removed from epoll, waiting would deadlock", // MN_EEMPTY = 14
"Operation not supported: Another socket is bound to that port and is not reusable for requested settings", // MN_BUSYPORT = 15
diff --git a/srtcore/sync.cpp b/srtcore/sync.cpp
index bfe1536577..ff16a7146a 100644
--- a/srtcore/sync.cpp
+++ b/srtcore/sync.cpp
@@ -347,7 +347,7 @@ int srt::sync::genRandomInt(int minVal, int maxVal)
#endif
// Map onto [minVal, maxVal].
- // Note. There is a minuscule probablity to get maxVal+1 as the result.
+ // Note. There is a minuscule probability to get maxVal+1 as the result.
// So we have to use long long to handle cases when maxVal = INT32_MAX.
// Also we must check 'res' does not exceed maxVal,
// which may happen if rand_0_1 = 1, even though the chances are low.
@@ -357,6 +357,11 @@ int srt::sync::genRandomInt(int minVal, int maxVal)
#endif // HAVE_CXX11
}
+#if defined(ENABLE_STDCXX_SYNC) && HAVE_CXX17
+
+// Shared mutex imp not required - aliased from C++17
+
+#else
////////////////////////////////////////////////////////////////////////////////
//
@@ -451,4 +456,6 @@ int srt::sync::SharedMutex::getReaderCount() const
{
ScopedLock lk(m_Mutex);
return m_iCountRead;
-}
\ No newline at end of file
+}
+#endif // C++17 for shared_mutex
+
diff --git a/srtcore/sync.h b/srtcore/sync.h
index 45285828cb..a5ac6d6840 100644
--- a/srtcore/sync.h
+++ b/srtcore/sync.h
@@ -12,6 +12,7 @@
#define INC_SRT_SYNC_H
#include "platform_sys.h"
+#include "srt_attr_defs.h"
#include
#include
@@ -21,6 +22,9 @@
#include
#include
#include
+#if HAVE_CXX17
+#include
+#endif
#define SRT_SYNC_CLOCK SRT_SYNC_CLOCK_STDCXX_STEADY
#define SRT_SYNC_CLOCK_STR "STDCXX_STEADY"
#else
@@ -54,7 +58,6 @@
#include "srt.h"
#include "utilities.h"
-#include "srt_attr_defs.h"
namespace srt
@@ -426,11 +429,11 @@ class Condition
public:
Condition();
~Condition();
-
public:
/// These functions do not align with C++11 version. They are here hopefully as a temporal solution
- /// to avoud issues with static initialization of CV on windows.
+ /// to avoid issues with static initialization of CV on windows.
void init();
+ void reset();
void destroy();
public:
@@ -483,6 +486,7 @@ class Condition
};
inline void setupCond(Condition& cv, const char*) { cv.init(); }
+inline void resetCond(Condition& cv) { cv.reset(); }
inline void releaseCond(Condition& cv) { cv.destroy(); }
///////////////////////////////////////////////////////////////////////////////
@@ -491,11 +495,15 @@ inline void releaseCond(Condition& cv) { cv.destroy(); }
//
///////////////////////////////////////////////////////////////////////////////
+#if defined(ENABLE_STDCXX_SYNC) && HAVE_CXX17
+using SharedMutex = std::shared_mutex;
+#else
+
/// Implementation of a read-write mutex.
/// This allows multiple readers at a time, or a single writer.
/// TODO: The class can be improved if needed to give writer a preference
/// by adding additional m_iWritersWaiting member variable (counter).
-/// TODO: The m_iCountRead could be made atomic to make unlok_shared() faster and lock-free.
+/// TODO: The m_iCountRead could be made atomic to make unlock_shared() faster and lock-free.
class SharedMutex
{
public:
@@ -503,13 +511,13 @@ class SharedMutex
~SharedMutex();
public:
- /// Acquire the lock for writting purposes. Only one thread can acquire this lock at a time
+ /// Acquire the lock for writing purposes. Only one thread can acquire this lock at a time
/// Once it is locked, no reader can acquire it
void lock();
bool try_lock();
void unlock();
- /// Acquire the lock if no writter already has it. For read purpose only
+ /// Acquire the lock if no writer already has it. For read purpose only
/// Several readers can lock this at the same time.
void lock_shared();
bool try_lock_shared();
@@ -526,10 +534,20 @@ class SharedMutex
int m_iCountRead;
bool m_bWriterLocked;
};
+#endif
+
+inline void enterCS(SharedMutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_ACQUIRE(m) { m.lock(); }
+
+inline bool tryEnterCS(SharedMutex& m) SRT_ATTR_EXCLUDES(m) SRT_ATTR_TRY_ACQUIRE(true, m) { return m.try_lock(); }
+
+inline void leaveCS(SharedMutex& m) SRT_ATTR_REQUIRES(m) SRT_ATTR_RELEASE(m) { m.unlock(); }
+
+inline void setupMutex(SharedMutex&, const char*) {}
+inline void releaseMutex(SharedMutex&) {}
/// A version of std::scoped_lock (or lock_guard for C++11).
/// We could have used the srt::sync::ScopedLock making it a template-based class.
-/// But in that case all usages would have to be specificed like ScopedLock in C++03.
+/// But in that case all usages would have to be specified like ScopedLock in C++03.
class SRT_ATTR_SCOPED_CAPABILITY ExclusiveLock
{
public:
@@ -577,26 +595,21 @@ class CSharedObjectPtr : public SharedMutex
{
}
- bool set(T* pObj)
+ bool compare_exchange(T* expected, T* newobj)
{
ExclusiveLock lock(*this);
- if (m_pObj)
+ if (m_pObj != expected)
return false;
- m_pObj = pObj;
+ m_pObj = newobj;
return true;
}
- bool clearIf(const T* pObj)
+ T* get_locked(SharedLock& /*wholocked*/)
{
- ExclusiveLock lock(*this);
- if (m_pObj != pObj)
- return false;
- m_pObj = NULL;
- return true;
+ // XXX Here you can assert that `wholocked` locked *this.
+ return m_pObj;
}
- T* getPtrNoLock() const { return m_pObj; }
-
private:
T* m_pObj;
};
@@ -843,7 +856,7 @@ class CTimer
/// Wakes up waiting thread (sleep_until(..)) without
/// changing the target waiting time to force a recheck
- /// of the current time in comparisson to the target time.
+ /// of the current time in comparison to the target time.
void tick();
private:
@@ -990,6 +1003,7 @@ class CThread
private:
pthread_t m_thread;
+ pid_t m_pid;
};
template
@@ -1021,6 +1035,8 @@ namespace this_thread
#endif
+inline void resetThread(CThread* th) { (void)new (th) CThread; }
+
/// StartThread function should be used to do CThread assignments:
/// @code
/// CThread a();
diff --git a/srtcore/sync_cxx11.cpp b/srtcore/sync_cxx11.cpp
index bdfbf9a4e2..c376b0adfa 100644
--- a/srtcore/sync_cxx11.cpp
+++ b/srtcore/sync_cxx11.cpp
@@ -58,6 +58,24 @@ srt::sync::Condition::~Condition() {}
void srt::sync::Condition::init() {}
+void srt::sync::Condition::reset()
+{
+ // SRT attempts to safely handle `fork()` in multithreaded environments,
+ // even though using `fork()` in such contexts is strongly discouraged.
+ // This is because `fork()` only duplicates the calling thread, leaving
+ // synchronization primitives (like condition variables) in an
+ // undefined or inconsistent state in the child process.
+ //
+ // To mitigate this, SRT forcefully reinitializes these synchronization
+ // primitives post-fork. In POSIX, this is done by overwriting the object
+ // with its default-initialized state. In C++11, we achieve the same effect
+ // using *placement new* to reconstruct the object in place. This ensures
+ // the condition variable is returned to a fresh, "neutral" state,
+ // as if it was just created.
+
+ new (&m_cv) std::condition_variable;
+}
+
void srt::sync::Condition::destroy() {}
void srt::sync::Condition::wait(UniqueLock& lock)
diff --git a/srtcore/sync_posix.cpp b/srtcore/sync_posix.cpp
index 8d7561a19d..cfbd2ab6d2 100644
--- a/srtcore/sync_posix.cpp
+++ b/srtcore/sync_posix.cpp
@@ -288,17 +288,35 @@ Condition::~Condition() {}
void Condition::init()
{
pthread_condattr_t* attr = NULL;
-#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC
+#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
pthread_condattr_t CondAttribs;
pthread_condattr_init(&CondAttribs);
- pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC);
+ if (pthread_condattr_setclock(&CondAttribs, CLOCK_MONOTONIC) != 0)
+ {
+ pthread_condattr_destroy(&CondAttribs);
+ LOGC(inlog.Fatal, log << "IPE: pthread_condattr_setclock failed to set up a monotonic clock for a CV");
+ }
attr = &CondAttribs;
#endif
const int res = pthread_cond_init(&m_cv, attr);
+#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
+ if (attr != NULL)
+ {
+ pthread_condattr_destroy(attr);
+ }
+#endif
if (res != 0)
throw std::runtime_error("pthread_cond_init monotonic failed");
}
+void Condition::reset()
+{
+
+ pthread_cond_t temp = PTHREAD_COND_INITIALIZER;
+ memcpy(&m_cv, (void *) &temp, sizeof(m_cv));
+ init();
+}
+
void Condition::destroy()
{
pthread_cond_destroy(&m_cv);
@@ -312,7 +330,7 @@ void Condition::wait(UniqueLock& lock)
bool Condition::wait_for(UniqueLock& lock, const steady_clock::duration& rel_time)
{
timespec timeout;
-#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC
+#if SRT_SYNC_CLOCK == SRT_SYNC_CLOCK_GETTIME_MONOTONIC && HAVE_PTHREAD_CONDATTR_SETCLOCK
clock_gettime(CLOCK_MONOTONIC, &timeout);
const uint64_t now_us = timeout.tv_sec * uint64_t(1000000) + (timeout.tv_nsec / 1000);
#else
@@ -360,6 +378,7 @@ void Condition::notify_all()
srt::sync::CThread::CThread()
{
m_thread = pthread_t();
+ m_pid = getpid();
}
srt::sync::CThread::CThread(void *(*start_routine) (void *), void *arg)
@@ -381,20 +400,20 @@ srt::sync::CThread& srt::sync::CThread::operator=(CThread& other)
LOGC(inlog.Error, log << "IPE: Assigning to a thread that is not terminated!");
#ifndef DEBUG
-#ifndef __ANDROID__
+#if !defined(__ANDROID__) && !defined(__OHOS__)
// In case of production build the hanging thread should be terminated
// to avoid hang ups and align with C++11 implementation.
// There is no pthread_cancel on Android. See #1476. This error should not normally
// happen, but if it happen, then detaching the thread.
pthread_cancel(m_thread);
-#endif // __ANDROID__
+#endif // __ANDROID__ __OHOS__
#else
join();
#endif
}
-
// Move thread handler from other
m_thread = other.m_thread;
+ m_pid = other.m_pid;
other.m_thread = pthread_t();
return *this;
}
@@ -409,24 +428,27 @@ void srt::sync::CThread::create_thread(void *(*start_routine) (void *), void *ar
bool srt::sync::CThread::joinable() const
{
- return !pthread_equal(m_thread, pthread_t());
+ return m_pid == getpid() && !pthread_equal(m_thread, pthread_t());
}
void srt::sync::CThread::join()
{
- void *retval;
- const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval);
- if (ret != 0)
- {
- LOGC(inlog.Error, log << "pthread_join failed with " << ret);
- }
+ if (joinable())
+ {
+ void *retval;
+ const int ret SRT_ATR_UNUSED = pthread_join(m_thread, &retval);
+ if (ret != 0)
+ {
+ LOGC(inlog.Error, log << "pthread_join failed with " << ret << " (" << m_thread << ")");
+ }
#ifdef HEAVY_LOGGING
- else
- {
- HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED");
- }
+ else
+ {
+ HLOGC(inlog.Debug, log << "pthread_join SUCCEEDED");
+ }
#endif
- // After joining, joinable should be false
+ // After joining, joinable should be false
+ }
m_thread = pthread_t();
return;
}
@@ -439,6 +461,7 @@ void srt::sync::CThread::create(void *(*start_routine) (void *), void *arg)
LOGC(inlog.Error, log << "pthread_create failed with " << st);
throw CThreadException(MJ_SYSTEMRES, MN_THREAD, 0);
}
+ m_pid = getpid();
}
@@ -544,7 +567,7 @@ CUDTException& GetThreadLocalError()
{
// In POSIX version we take into account the possibility
// of having an allocation error here. Therefore we need to
- // allow thie value to return NULL and have some fallback
+ // allow this value to return NULL and have some fallback
// for that case. The dynamic memory allocation failure should
// be the only case as to why it is unable to get the pointer
// to the error description.
diff --git a/srtcore/tsbpd_time.cpp b/srtcore/tsbpd_time.cpp
index d45215a011..b85aa5060f 100644
--- a/srtcore/tsbpd_time.cpp
+++ b/srtcore/tsbpd_time.cpp
@@ -266,7 +266,7 @@ void CTsbpdTime::updateBaseTime(uint32_t usPktTimestamp)
// Check if timestamp is within the TSBPD_WRAP_PERIOD before reaching the MAX_TIMESTAMP.
if (usPktTimestamp > (CPacket::MAX_TIMESTAMP - TSBPD_WRAP_PERIOD))
{
- // Approching wrap around point, start wrap check period (if for packet delivery head)
+ // Approaching wrap around point, start wrap check period (if for packet delivery head)
m_bTsbPdWrapCheck = true;
LOGC(tslog.Debug,
log << "tsbpd wrap period begins with ts=" << usPktTimestamp
diff --git a/srtcore/utilities.h b/srtcore/utilities.h
index ca8c365a3c..9f0ab78aa3 100644
--- a/srtcore/utilities.h
+++ b/srtcore/utilities.h
@@ -285,7 +285,7 @@ inline void ItoHLA(uint32_t* dst, const uint32_t* src, size_t size)
// Usage: typedef Bits MASKTYPE; // MASKTYPE is a name of your choice.
//
// With this defined, you can use the following members:
-// - MASKTYPE::mask - to get the int32_t value with bimask (used bits set to 1, others to 0)
+// - MASKTYPE::mask - to get the int32_t value with bitmask (used bits set to 1, others to 0)
// - MASKTYPE::offset - to get the lowermost bit number, or number of bits to shift
// - MASKTYPE::wrap(int value) - to create a bitset where given value is encoded in given bits
// - MASKTYPE::unwrap(int bitset) - to extract an integer value from the bitset basing on mask definition
@@ -789,12 +789,6 @@ inline void insert_uniq(std::vector& v, const ArgValue& val)
v.push_back(val);
}
-template
-inline std::pair Tie(Type1& var1, Type2& var2)
-{
- return std::pair(var1, var2);
-}
-
// This can be used in conjunction with Tie to simplify the code
// in loops around a whole container:
// list::const_iterator it, end;
@@ -828,17 +822,14 @@ struct CallbackHolder
// Test if the pointer is a pointer to function. Don't let
// other type of pointers here.
#if HAVE_CXX11
+ // NOTE: No poor-man's replacement can be done for C++03 because it's
+ // not possible to fake calling a function without calling it and no
+ // other operation can be done without extensive transformations on
+ // the Signature type, still in C++03 possible only on functions up to
+ // 2 arguments (callbacks in SRT usually have more).
static_assert(std::is_function::value, "CallbackHolder is for functions only!");
-#else
- // This is a poor-man's replacement, which should in most compilers
- // generate a warning, if `Signature` resolves to a value type.
- // This would make an illegal pointer cast from a value to a function type.
- // Casting function-to-function, however, should not. Unfortunately
- // newer compilers disallow that, too (when a signature differs), but
- // then they should better use the C++11 way, much more reliable and safer.
- void* (*testfn)(void*) = (void*(*)(void*))f;
- (void)(testfn);
#endif
+
opaque = o;
fn = f;
}
@@ -975,6 +966,16 @@ inline void AccumulatePassFilterParallel(const int* p, size_t size, PassFilter
+inline Type Bounds(Type lower, Type value, Type upper)
+{
+ if (value < lower)
+ return lower;
+ if (value > upper)
+ return upper;
+ return value;
+}
+
inline std::string FormatBinaryString(const uint8_t* bytes, size_t size)
{
@@ -1071,7 +1072,7 @@ class DriftTracer
// overdrift() should be read only immediately after update() returned
// true. It will stay available with this value until the next time when
// update() returns true, in which case the value will be cleared.
- // Therefore, after calling update() if it retuns true, you should read
+ // Therefore, after calling update() if it returns true, you should read
// overdrift() immediately an make some use of it. Next valid overdrift
// will be then relative to every previous overdrift.
//
@@ -1111,7 +1112,7 @@ struct MapProxy
{
typename std::map::const_iterator p = find();
if (p == mp.end())
- return "";
+ return ValueType();
return p->second;
}
@@ -1214,7 +1215,7 @@ inline size_t safe_advance(It& it, size_t num, It end)
return num; // will be effectively 0, if reached the required point, or >0, if end was by that number earlier
}
-// This is available only in C++17, dunno why not C++11 as it's pretty useful.
+// This is available only in C++17, don't know why not C++11 as it's pretty useful.
template inline
ATR_CONSTEXPR size_t Size(const V (&)[N]) ATR_NOEXCEPT { return N; }
diff --git a/srtcore/window.cpp b/srtcore/window.cpp
index 1950f7e166..5a926c7212 100644
--- a/srtcore/window.cpp
+++ b/srtcore/window.cpp
@@ -154,13 +154,13 @@ void srt::CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_p
r_probeWindow[k] = 1000; //1 msec -> 1000 pkts/sec
for (size_t i = 0; i < asize; ++ i)
- r_bytesWindow[i] = max_payload_size; //based on 1 pkt/sec set in r_pktWindow[i]
+ r_bytesWindow[i] = int(max_payload_size); //based on 1 pkt/sec set in r_pktWindow[i]
}
int srt::CPktTimeWindowTools::ceilPerMega(double value, double count)
{
static const double MEGA = 1000.0 * 1000.0;
- return ::ceil(MEGA / (value / count));
+ return int(::ceil(MEGA / (value / count)));
}
int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, size_t asize, size_t hdr_size, int& w_bytesps)
@@ -176,14 +176,14 @@ int srt::CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica,
AccumulatePassFilterParallel(window, asize, filter, abytes,
(sum), (count), (bytes));
- // claculate speed, or return 0 if not enough valid value
+ // calculate speed, or return 0 if not enough valid value
if (count <= (asize/2))
{
w_bytesps = 0;
return 0;
}
- bytes += (hdr_size * count); //Add protocol headers to bytes received
+ bytes += (unsigned long)(hdr_size * count); //Add protocol headers to bytes received
w_bytesps = ceilPerMega(sum, bytes);
return ceilPerMega(sum, count);
}
diff --git a/srtcore/window.h b/srtcore/window.h
index dbe4b7179c..4e548d3ef0 100644
--- a/srtcore/window.h
+++ b/srtcore/window.h
@@ -322,7 +322,7 @@ class CPktTimeWindow: CPktTimeWindowTools
// Meaning: if the packet is fully packed, probe_case = timediff.
// Otherwise the timediff will be "converted" to a time that a fully packed packet "would take",
// provided the arrival time is proportional to the payload size and skipping
- // the ETH+IP+UDP+SRT header part elliminates the constant packet delivery time influence.
+ // the ETH+IP+UDP+SRT header part eliminates the constant packet delivery time influence.
//
const size_t pktsz = pkt.getLength();
m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? int(timediff_times_pl_size / pktsz) : int(timediff);
diff --git a/submodules/abi-compliance-checker b/submodules/abi-compliance-checker
new file mode 160000
index 0000000000..7c175c45a8
--- /dev/null
+++ b/submodules/abi-compliance-checker
@@ -0,0 +1 @@
+Subproject commit 7c175c45a8ba9ac41b8e47d8ebbab557b623b18e
diff --git a/test/any.hpp b/test/any.hpp
index 9ecb96cc59..afe3fc7915 100644
--- a/test/any.hpp
+++ b/test/any.hpp
@@ -209,11 +209,11 @@ class any final
/// The state of the union after this call is unspecified, caller must ensure not to use src anymore.
void(*destroy)(storage_union&) noexcept;
- /// Copies the **inner** content of the src union into the yet unitialized dest union.
+ /// Copies the **inner** content of the src union into the yet uninitialized dest union.
/// As such, both inner objects will have the same state, but on separate memory locations.
void(*copy)(const storage_union& src, storage_union& dest);
- /// Moves the storage from src to the yet unitialized dest union.
+ /// Moves the storage from src to the yet uninitialized dest union.
/// The state of src after this call is unspecified, caller must ensure not to use src anymore.
void(*move)(storage_union& src, storage_union& dest) noexcept;
@@ -251,7 +251,7 @@ class any final
static void swap(storage_union& lhs, storage_union& rhs) noexcept
{
- // just exchage the storage pointers.
+ // just exchange the storage pointers.
std::swap(lhs.dynamic, rhs.dynamic);
}
};
diff --git a/test/filelist.maf b/test/filelist.maf
index 7ae5693b19..a8e94f5c59 100644
--- a/test/filelist.maf
+++ b/test/filelist.maf
@@ -7,6 +7,7 @@ test_main.cpp
test_buffer_rcv.cpp
test_common.cpp
test_connection_timeout.cpp
+test_control_packets.cpp
test_crypto.cpp
test_cryspr.cpp
test_enforced_encryption.cpp
@@ -29,6 +30,7 @@ test_utilities.cpp
test_reuseaddr.cpp
test_socketdata.cpp
test_snd_rate_estimator.cpp
+test_srtla_rec.cpp
# Tests for bonding only - put here!
diff --git a/test/test_bonding.cpp b/test/test_bonding.cpp
index 0e48c8a048..bc870ce58d 100644
--- a/test/test_bonding.cpp
+++ b/test/test_bonding.cpp
@@ -1,14 +1,18 @@
+#include
#include
#include
#include
#include
-#include
-
#include "gtest/gtest.h"
#include "test_env.h"
#include "srt.h"
+#include "udt.h"
+#include "common.h"
#include "netinet_any.h"
+#include "socketconfig.h"
+
+#include "apputil.hpp"
TEST(Bonding, SRTConnectGroup)
{
@@ -23,7 +27,7 @@ TEST(Bonding, SRTConnectGroup)
{
sa.sin_family = AF_INET;
sa.sin_port = htons(4200 + i);
- ASSERT_EQ(inet_pton(AF_INET, "192.168.1.237", &sa.sin_addr), 1);
+ EXPECT_EQ(inet_pton(AF_INET, "192.168.1.237", &sa.sin_addr), 1);
const SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(NULL, (struct sockaddr*)&sa, sizeof sa);
targets.push_back(gd);
@@ -56,7 +60,7 @@ TEST(Bonding, SRTConnectGroup)
}
}
-#define ASSERT_SRT_SUCCESS(callform) ASSERT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str()
+#define EXPECT_SRT_SUCCESS(callform) EXPECT_NE(callform, -1) << "SRT ERROR: " << srt_getlasterror_str()
void listening_thread(bool should_read)
{
@@ -64,37 +68,37 @@ void listening_thread(bool should_read)
sockaddr_in bind_sa;
memset(&bind_sa, 0, sizeof bind_sa);
bind_sa.sin_family = AF_INET;
- ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1);
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1);
bind_sa.sin_port = htons(4200);
- ASSERT_SRT_SUCCESS(srt_bind(server_sock, (sockaddr*)&bind_sa, sizeof bind_sa));
+ EXPECT_SRT_SUCCESS(srt_bind(server_sock, (sockaddr*)&bind_sa, sizeof bind_sa));
const int yes = 1;
- ASSERT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &yes, sizeof yes));
+ EXPECT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_GROUPCONNECT, &yes, sizeof yes));
const int no = 1;
- ASSERT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_RCVSYN, &no, sizeof no));
+ EXPECT_SRT_SUCCESS(srt_setsockflag(server_sock, SRTO_RCVSYN, &no, sizeof no));
const int eid = srt_epoll_create();
const int listen_event = SRT_EPOLL_IN | SRT_EPOLL_ERR;
- ASSERT_SRT_SUCCESS(srt_epoll_add_usock(eid, server_sock, &listen_event));
+ EXPECT_SRT_SUCCESS(srt_epoll_add_usock(eid, server_sock, &listen_event));
- ASSERT_SRT_SUCCESS(srt_listen(server_sock, 5));
+ EXPECT_SRT_SUCCESS(srt_listen(server_sock, 5));
std::cout << "Listen: wait for acceptability\n";
int fds[2];
int fds_len = 2;
int ers[2];
int ers_len = 2;
- ASSERT_SRT_SUCCESS(srt_epoll_wait(eid, fds, &fds_len, ers, &ers_len, 5000,
+ EXPECT_SRT_SUCCESS(srt_epoll_wait(eid, fds, &fds_len, ers, &ers_len, 5000,
0, 0, 0, 0));
std::cout << "Listen: reported " << fds_len << " acceptable and " << ers_len << " errors\n";
- ASSERT_GT(fds_len, 0);
- ASSERT_EQ(fds[0], server_sock);
+ EXPECT_GT(fds_len, 0);
+ EXPECT_EQ(fds[0], server_sock);
srt::sockaddr_any scl;
int acp = srt_accept(server_sock, (scl.get()), (&scl.len));
- ASSERT_SRT_SUCCESS(acp);
- ASSERT_NE(acp & SRTGROUP_MASK, 0);
+ EXPECT_SRT_SUCCESS(acp);
+ EXPECT_NE(acp & SRTGROUP_MASK, 0);
if (should_read)
{
@@ -122,11 +126,22 @@ void listening_thread(bool should_read)
// srt_accept..
}
-void ConnectCallback(void* /*opaq*/, SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token)
+SRTSOCKET g_listen_socket = -1;
+int g_nconnected = 0;
+int g_nfailed = 0;
+
+// This ConnectCallback is mainly informative, but it also collects the
+// number of succeeded and failed links.
+void ConnectCallback(void* , SRTSOCKET sock, int error, const sockaddr* /*peer*/, int token)
{
std::cout << "Connect callback. Socket: " << sock
<< ", error: " << error
<< ", token: " << token << '\n';
+
+ if (error == SRT_SUCCESS)
+ ++g_nconnected;
+ else
+ ++g_nfailed;
}
TEST(Bonding, NonBlockingGroupConnect)
@@ -138,20 +153,20 @@ TEST(Bonding, NonBlockingGroupConnect)
std::cout << "Created group socket: " << ss << '\n';
int no = 0;
- ASSERT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode
- ASSERT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode
+ EXPECT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode
+ EXPECT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode
const int poll_id = srt_epoll_create();
// Will use this epoll to wait for srt_accept(...)
const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
- ASSERT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR);
+ EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR);
srt_connect_callback(ss, &ConnectCallback, this);
sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(4200);
- ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1);
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1);
sockaddr_in safail = sa;
safail.sin_port = htons(4201); // port where we have no listener
@@ -164,14 +179,14 @@ TEST(Bonding, NonBlockingGroupConnect)
EXPECT_GT(sockid, 0) << "Socket " << 1;
sa.sin_port = htons(4201); // Changing port so that second connect fails
std::cout << "Socket created: " << sockid << '\n';
- ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR);
+ EXPECT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR);
}
{
const int sockid = srt_connect(ss, (sockaddr*) &safail, sizeof safail);
EXPECT_GT(sockid, 0) << "Socket " << 2;
safail.sin_port = htons(4201); // Changing port so that second connect fails
std::cout << "Socket created: " << sockid << '\n';
- ASSERT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR);
+ EXPECT_NE(srt_epoll_add_usock(poll_id, sockid, &epoll_out), SRT_ERROR);
}
std::cout << "Returned from connecting two sockets " << std::endl;
@@ -224,8 +239,8 @@ TEST(Bonding, CloseGroupAndSocket)
{
srt::TestInit srtinit;
- const int ss = srt_create_group(SRT_GTYPE_BROADCAST);
- ASSERT_NE(ss, SRT_ERROR);
+ MAKE_UNIQUE_SOCK(ss, "broadcast group", srt_create_group(SRT_GTYPE_BROADCAST));
+
std::cout << "Created group socket: " << ss << '\n';
int no = 0;
@@ -311,7 +326,7 @@ TEST(Bonding, CloseGroupAndSocket)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (srt_send(ss, buf, 1316) == -1)
{
- std::cout << "[Sender] sending failure, exitting after sending " << n << " packets\n";
+ std::cout << "[Sender] sending failure, exiting after sending " << n << " packets\n";
break;
}
@@ -323,13 +338,25 @@ TEST(Bonding, CloseGroupAndSocket)
std::this_thread::sleep_for(std::chrono::milliseconds(300));
- EXPECT_EQ(srt_close(ss), 0) << "srt_close: %s\n" << srt_getlasterror_str();
+ ss.close();
std::cout << "CLOSED GROUP. Now waiting for sender to exit...\n";
sender.join();
listen_promise.wait();
}
+// (void* opaq, SRTSOCKET ns, int hsversion, const struct sockaddr* peeraddr, const char* streamid);
+int ListenCallbackFn(void* expected_sid, SRTSOCKET, int /*hsversion*/, const sockaddr* /*peer*/, const char* streamid)
+{
+ const auto* p = (std::pair*) expected_sid;
+ // Note: It is not safe to access the streamid pointer by the expected size,
+ // but there is no way to know the real size apart from finding the first null terminator.
+ // See FR #3073.
+ EXPECT_EQ(std::memcmp(streamid, p->first, p->second), 0);
+
+ return 0;
+}
+
TEST(Bonding, Options)
{
using namespace std;
@@ -338,7 +365,7 @@ TEST(Bonding, Options)
TestInit srtinit;
// Create a group
- const SRTSOCKET grp = srt_create_group(SRT_GTYPE_BROADCAST);
+ MAKE_UNIQUE_SOCK(grp, "broadcast group", srt_create_group(SRT_GTYPE_BROADCAST));
// rendezvous shall not be allowed to be set on the group
// XXX actually it is possible, but no one tested it. POSTPONE.
@@ -346,12 +373,15 @@ TEST(Bonding, Options)
//EXPECT_EQ(srt_setsockflag(grp, SRTO_RENDEZVOUS, &yes, sizeof yes), SRT_ERROR);
#ifdef SRT_ENABLE_ENCRYPTION
- string pass = "longenoughpassword";
+ const string pass = "longenoughpassword";
// passphrase should be ok.
- EXPECT_NE(srt_setsockflag(grp, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR);
+ EXPECT_NE(srt_setsockflag(grp, SRTO_PASSPHRASE, pass.c_str(), (int) pass.size()), SRT_ERROR);
uint32_t val = 16;
- EXPECT_NE(srt_setsockflag(grp, SRTO_PBKEYLEN, &val, sizeof val), SRT_ERROR);
+ EXPECT_NE(srt_setsockflag(grp, SRTO_PBKEYLEN, &val, (int) sizeof val), SRT_ERROR);
+
+ const bool bfalse = true;
+ EXPECT_EQ(srt_setsockflag(grp, SRTO_RENDEZVOUS, &bfalse, (int)sizeof bfalse), SRT_ERROR);
#ifdef ENABLE_AEAD_API_PREVIEW
val = 1;
@@ -359,6 +389,38 @@ TEST(Bonding, Options)
#endif
#endif
+ const string packet_filter = "fec,cols:10,rows:5";
+ EXPECT_NE(srt_setsockflag(grp, SRTO_PACKETFILTER, packet_filter.c_str(), (int)packet_filter.size()), SRT_ERROR);
+
+ // ================
+ // Linger is an option of a trivial type, but differs from other integer-typed options.
+ // Therefore checking it specifically.
+ const linger l = {1, 10};
+ srt_setsockflag(grp, SRTO_LINGER, &l, sizeof l);
+
+ {
+ linger l2;
+ int optsize = sizeof l2;
+ EXPECT_NE(srt_getsockflag(grp, SRTO_LINGER, &l2, &optsize), SRT_ERROR);
+ EXPECT_EQ(optsize, (int)sizeof l2);
+ EXPECT_EQ(l2.l_onoff, l.l_onoff);
+ EXPECT_EQ(l2.l_linger, l.l_linger);
+ }
+ // ================
+
+ const std::array streamid = { 's', 't', 'r', 'e', 0, 'm', 'i', 'd', '%', '&'};
+ EXPECT_NE(srt_setsockflag(grp, SRTO_STREAMID, &streamid, streamid.size()), SRT_ERROR);
+
+ auto check_streamid = [&streamid](SRTSOCKET sock) {
+ std::array tmpbuf;
+ auto opt_len = (int)tmpbuf.size();
+ EXPECT_EQ(srt_getsockflag(sock, SRTO_STREAMID, tmpbuf.data(), &opt_len), SRT_SUCCESS);
+ EXPECT_EQ(size_t(opt_len), streamid.size());
+ EXPECT_EQ(std::memcmp(tmpbuf.data(), streamid.data(), opt_len), 0);
+ };
+
+ check_streamid(grp);
+
int lat = 500;
EXPECT_NE(srt_setsockflag(grp, SRTO_RCVLATENCY, &lat, sizeof lat), SRT_ERROR);
@@ -371,22 +433,34 @@ TEST(Bonding, Options)
unique_lock ux(mx);
SRTSOCKET lsn = srt_create_socket();
+
+ auto expected_sid = std::make_pair(streamid.data(), streamid.size());
+ srt_listen_callback(lsn, &ListenCallbackFn, (void*) &expected_sid);
+
#ifdef SRT_ENABLE_ENCRYPTION
EXPECT_NE(srt_setsockflag(lsn, SRTO_PASSPHRASE, pass.c_str(), pass.size()), SRT_ERROR);
#endif
int allow = 1;
- ASSERT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR);
- sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET);
- ASSERT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR);
- ASSERT_NE(srt_listen(lsn, 1), SRT_ERROR);
+ EXPECT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR);
+ sockaddr_any sa = srt::CreateAddr("127.0.0.1", 5555, AF_INET);
+ EXPECT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR);
+ EXPECT_NE(srt_listen(lsn, 1), SRT_ERROR);
started = true;
// First wait - until it's let go with accepting
latch.wait(ux);
- sockaddr_any revsa;
- SRTSOCKET gs = srt_accept(lsn, revsa.get(), &revsa.len);
- ASSERT_NE(gs, SRT_ERROR);
+ //sockaddr_any revsa;
+ SRTSOCKET lsna [1] = { lsn };
+ SRTSOCKET gs = srt_accept_bond(lsna, 1, 1000);
+ ASSERT_NE(gs, SRT_INVALID_SOCK);
+
+ check_streamid(gs);
+
+ std::array tmpbuf;
+ auto opt_len = (int)tmpbuf.size();
+ EXPECT_EQ(srt_getsockflag(gs, SRTO_PACKETFILTER, tmpbuf.data(), &opt_len), SRT_SUCCESS);
+ std::cout << "Packet filter: " << std::string(tmpbuf.data(), opt_len) << '\n';
// Connected, wait to close
latch.wait(ux);
@@ -413,7 +487,7 @@ TEST(Bonding, Options)
}
// Now the thread is accepting, so we call the connect.
- sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET);
+ sockaddr_any sa = srt::CreateAddr("127.0.0.1", 5555, AF_INET);
SRTSOCKET member = srt_connect(grp, sa.get(), sa.size());
// We've released the mutex and signaled the CV, so accept should proceed now.
@@ -456,20 +530,20 @@ TEST(Bonding, Options)
#if SRT_ENABLE_ENCRYPTION
- uint32_t kms = -1;
+ int32_t kms = -1;
EXPECT_NE(srt_getsockflag(grp, SRTO_KMSTATE, &kms, &optsize), SRT_ERROR);
EXPECT_EQ(optsize, (int) sizeof kms);
- EXPECT_EQ(kms, int(SRT_KM_S_SECURED));
+ EXPECT_EQ(kms, uint32_t(SRT_KM_S_SECURED));
EXPECT_NE(srt_getsockflag(grp, SRTO_PBKEYLEN, &kms, &optsize), SRT_ERROR);
EXPECT_EQ(optsize, (int) sizeof kms);
- EXPECT_EQ(kms, 16);
+ EXPECT_EQ(kms, uint32_t(16));
#ifdef ENABLE_AEAD_API_PREVIEW
EXPECT_NE(srt_getsockflag(grp, SRTO_CRYPTOMODE, &kms, &optsize), SRT_ERROR);
- EXPECT_EQ(optsize, sizeof kms);
- EXPECT_EQ(kms, 1);
+ EXPECT_EQ(optsize, (int) sizeof kms);
+ EXPECT_EQ(kms, uint32_t(SRT_KM_S_SECURING));
#endif
#endif
@@ -481,7 +555,6 @@ TEST(Bonding, Options)
}
accept_and_close.join();
- srt_close(grp);
}
inline SRT_SOCKGROUPCONFIG PrepareEndpoint(const std::string& host, int port)
@@ -507,7 +580,7 @@ TEST(Bonding, InitialFailure)
int allow = 1;
ASSERT_NE(srt_setsockflag(lsn, SRTO_GROUPCONNECT, &allow, sizeof allow), SRT_ERROR);
- sockaddr_any sa = CreateAddr("127.0.0.1", 5555, AF_INET);
+ sockaddr_any sa = srt::CreateAddr("127.0.0.1", 5555, AF_INET);
ASSERT_NE(srt_bind(lsn, sa.get(), sa.size()), SRT_ERROR);
ASSERT_NE(srt_listen(lsn, 5), SRT_ERROR);
@@ -564,3 +637,976 @@ TEST(Bonding, InitialFailure)
srt_close(lsn);
}
+
+
+// General idea:
+// This should try to connect to two nonexistent links,
+// the connecting function (working in blocking mode)
+// should exit with error, after the group has been closed
+// in a separate thread.
+//
+// Steps:
+// 1. Create group
+// 2. Use a nonexistent endpoints 192.168.1.237:4200 and *:4201
+// 3. Close the group in a thread
+// 4. Wait for error
+TEST(Bonding, ConnectBlind)
+{
+ using namespace srt;
+
+ TestInit srtinit;
+ MAKE_UNIQUE_SOCK(ss, "GrpCaller", srt_create_group(SRT_GTYPE_BROADCAST));
+
+ std::vector targets;
+ for (int i = 0; i < 2; ++i)
+ {
+ sockaddr_any sa = srt::CreateAddr("192.168.1.237", 4200 + i, AF_INET);
+ const SRT_SOCKGROUPCONFIG gd = srt_prepare_endpoint(NULL, sa.get(), sa.size());
+ targets.push_back(gd);
+ }
+
+ std::future closing_promise = std::async(std::launch::async, [](int s) {
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+ std::cerr << "Closing group" << std::endl;
+ srt_close(s);
+ }, ss);
+
+ std::cout << "srt_connect_group calling " << std::endl;
+ const int st = srt_connect_group(ss, targets.data(), targets.size());
+ std::cout << "srt_connect_group returned " << st << std::endl;
+
+ closing_promise.wait();
+ EXPECT_EQ(st, -1);
+
+ // Delete config objects before prospective exception
+ for (auto& gd: targets)
+ srt_delete_config(gd.config);
+
+ int res = srt_close(ss);
+ if (res == SRT_ERROR)
+ {
+ std::cerr << "srt_close: " << srt_getlasterror_str() << std::endl;
+ }
+}
+
+// TEST IDEA:
+// This uses srt_connect_group in non-blocking mode. The listener
+// is also created to respond to the connection. Expected is to
+// continue the connecting in background and report a success,
+// and report the epoll IN on listener for the first connection,
+// and UPDATE For the second one.
+//
+// TEST STEPS:
+// 1. Create a listener socket and a group.
+// 2. Set the group and the listener socket non-blocking mode
+// 3. Start the accepting thread
+// - wait for IN event ready on the listener socket
+// - accept a connection
+// - wait for UPDATE event ready on the listener socket
+// - wait for any event up to 5s (possibly ERR)
+// - close the listener socket
+// 4. Prepare two connections and start connecting
+// 5. Wait for the OUT readiness event on the group
+// 6. Close the group.
+// 7. Join the thread
+TEST(Bonding, ConnectNonBlocking)
+{
+ using namespace std;
+ using namespace std::chrono;
+ using namespace srt;
+
+ TestInit srtinit;
+
+ const string ADDR = "127.0.0.1";
+ const int PORT = 4209;
+
+ // NOTE: Add more group types, if implemented!
+ vector types { SRT_GTYPE_BROADCAST, SRT_GTYPE_BACKUP };
+
+ for (const auto GTYPE: types)
+ {
+ g_listen_socket = srt_create_socket();
+
+ sockaddr_any sa = srt::CreateAddr(ADDR, PORT, AF_INET);
+
+ EXPECT_NE(srt_bind(g_listen_socket, sa.get(), sa.size()), -1);
+ const int yes = 1;
+ srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes);
+
+ int lsn_eid = srt_epoll_create();
+ int lsn_events = SRT_EPOLL_IN | SRT_EPOLL_ERR | SRT_EPOLL_UPDATE;
+ srt_epoll_add_usock(lsn_eid, g_listen_socket, &lsn_events);
+
+ // Caller part
+
+ const int ss = srt_create_group(GTYPE);
+ EXPECT_NE(ss, SRT_ERROR);
+ std::cout << "Created group socket: " << ss << '\n';
+
+ int no = 0;
+ EXPECT_NE(srt_setsockopt(ss, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode
+ EXPECT_NE(srt_setsockopt(ss, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // non-blocking mode
+
+ const int poll_id = srt_epoll_create();
+ // Will use this epoll to wait for srt_accept(...)
+ const int epoll_out = SRT_EPOLL_OUT | SRT_EPOLL_ERR;
+ EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR);
+
+ srt_connect_callback(ss, &ConnectCallback, this);
+
+ cout << "TEST: Group type: " << GTYPE << endl;
+
+ // synchronizers
+ std::promise
+ connect_passed,
+ accept_passed,
+ checks_done;
+
+ //srt_setloglevel(LOG_DEBUG);
+
+ auto acthr = std::thread([&lsn_eid, &connect_passed, &accept_passed, &checks_done]() {
+ SRT_EPOLL_EVENT ev[3];
+
+ ThreadName::set("TEST_A");
+
+ cout << "[A] Waiting for main thread to pass connect()\n";
+
+ // Delay with executing accept to keep the peer in "in progress"
+ // connection state.
+ connect_passed.get_future().get();
+ EXPECT_NE(srt_listen(g_listen_socket, 5), SRT_ERROR);
+
+ cout << "[A] Waiting for accept\n";
+ // This can wait in infinity; worst case it will be killed in process.
+ int uwait_res = srt_epoll_uwait(lsn_eid, ev, 3, -1);
+ EXPECT_EQ(uwait_res, 1);
+ EXPECT_EQ(ev[0].fd, g_listen_socket);
+
+ // Check if the IN event is set, even if it's not the only event
+ const int ev_in_bit = SRT_EPOLL_IN;
+ EXPECT_NE(ev[0].events & ev_in_bit, 0);
+ bool have_also_update = ev[0].events & SRT_EPOLL_UPDATE;
+
+ cout << "[A] Accept delay until connect done...\n";
+
+ cout << "[A] Accept: go on\n";
+
+ sockaddr_any adr;
+ SRTSOCKET accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len);
+
+ // Expected: group reporting
+ EXPECT_NE(accept_id & SRTGROUP_MASK, 0);
+
+ if (have_also_update)
+ {
+ cout << "[A] NOT waiting for update - already reported previously\n";
+ }
+ else
+ {
+ cout << "[A] Waiting for update\n";
+ // Now another waiting is required and expected the update event
+ // Wait up to 5s to avoid hangup in case of error
+ uwait_res = srt_epoll_uwait(lsn_eid, ev, 3, 5000);
+ EXPECT_EQ(uwait_res, 1);
+ EXPECT_EQ(ev[0].fd, g_listen_socket);
+ EXPECT_EQ(ev[0].events, (int)SRT_EPOLL_UPDATE);
+ }
+
+ // As accept is expected to be finished and two connections were
+ // established, make sure that both connections are established.
+ // Wait until you have at least two members
+ SRT_SOCKGROUPDATA members[3];
+ size_t grouplen = 3;
+ cout << "[A] Waiting until having 2 members in the group\n";
+ while (srt_group_data(accept_id, members, &grouplen) != SRT_ERROR
+ && grouplen < 2)
+ {
+ grouplen = 3;
+ this_thread::sleep_for(milliseconds(250));
+ }
+ accept_passed.set_value();
+
+
+ cout << "[A] Waitig on epoll for close (up to 5s)\n";
+ // Wait up to 5s for an error
+ srt_epoll_uwait(lsn_eid, ev, 3, 5000);
+ srt_close(accept_id);
+ checks_done.set_value();
+
+ cout << "[A] thread finished\n";
+ });
+
+ cout << "Connecting two sockets\n";
+
+ SRT_SOCKGROUPCONFIG cc[2];
+ cc[0] = srt_prepare_endpoint(NULL, sa.get(), sa.size());
+ cc[1] = srt_prepare_endpoint(NULL, sa.get(), sa.size());
+
+ EXPECT_NE(srt_epoll_add_usock(poll_id, ss, &epoll_out), SRT_ERROR);
+
+ int result = srt_connect_group(ss, cc, 2);
+ EXPECT_NE(result, -1);
+ char data[4] = { 1, 2, 3, 4};
+ cout << "Sending...\n";
+ int wrong_send = srt_send(ss, data, sizeof data);
+
+ // Only now we allow the other thread to go on with accept
+ connect_passed.set_value();
+ cout << "Getting error...\n";
+ int errorcode = srt_getlasterror(NULL);
+ EXPECT_EQ(wrong_send, -1);
+ EXPECT_EQ(errorcode, SRT_EASYNCSND) << "REAL ERROR: " << srt_getlasterror_str();
+
+ // Wait to make sure that both links are connected.
+ accept_passed.get_future().get();
+
+ // Wait up to 2s
+ SRT_EPOLL_EVENT ev[3];
+ const int uwait_result = srt_epoll_uwait(poll_id, ev, 3, 2000);
+ std::cout << "Returned from connecting two sockets, waiting for all checks from [A]" << std::endl;
+
+ checks_done.get_future().get();
+
+ EXPECT_EQ(uwait_result, 1); // Expect the group reported
+ EXPECT_EQ(ev[0].fd, ss);
+
+ std::cout << "Closing group and releasing resources\n";
+
+ EXPECT_EQ(srt_close(ss), 0);
+ acthr.join();
+
+ srt_epoll_release(lsn_eid);
+ srt_epoll_release(poll_id);
+
+ srt_close(g_listen_socket);
+ }
+
+}
+
+// TEST IDEA:
+// In this test there is created a working listener socket to
+// accept the connection, and we use a Backup-type group with
+// two links, but different weights. We connect them both and
+// make sure that both are ready for use. Then we send a packet
+// over the group and see, which link got activated and which
+// remained idle. Expected is to have the link with higher
+// priority (greater weight) to be activated.
+//
+// TEST STEPS:
+// 1. Create a listener socket and a group.
+// 3. Start the accepting thread
+// - accept a connection
+// - read one packet from the accepted entity
+// - close the listener socket
+// 4. Prepare two connections (one with weight=1) and connect the group
+// 5. Wait for having all links connected
+// 6. Send one packet and check which link was activated
+// 6. Close the group.
+// 7. Join the thread
+TEST(Bonding, BackupPriorityBegin)
+{
+ using namespace std;
+ using namespace std::chrono;
+ using namespace srt;
+
+ TestInit srtinit;
+
+ g_nconnected = 0;
+ g_nfailed = 0;
+
+ g_listen_socket = srt_create_socket();
+ ASSERT_NE(g_listen_socket, SRT_INVALID_SOCK);
+ sockaddr_in bind_sa;
+ memset(&bind_sa, 0, sizeof bind_sa);
+ bind_sa.sin_family = AF_INET;
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1);
+ bind_sa.sin_port = htons(4200);
+
+ EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1);
+ const int yes = 1;
+ srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes);
+ EXPECT_NE(srt_listen(g_listen_socket, 5), -1);
+
+ // Caller part
+
+ const int ss = srt_create_group(SRT_GTYPE_BACKUP);
+ EXPECT_NE(ss, SRT_ERROR);
+
+ srt_connect_callback(ss, &ConnectCallback, this);
+
+ sockaddr_in sa;
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(4200);
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1);
+
+ auto acthr = std::thread([]() {
+ sockaddr_any adr;
+ cout << "[A] Accepting a connection...\n";
+ int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len);
+
+ // Expected: group reporting
+ EXPECT_NE(accept_id & SRTGROUP_MASK, 0);
+
+ SRT_SOCKGROUPDATA gdata[2];
+ SRT_MSGCTRL mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+ long long data[1320/8];
+
+ cout << "[A] Receiving...\n";
+ int ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc));
+ EXPECT_EQ(ds, 8);
+
+ cout << "[A] Closing\n";
+ srt_close(accept_id);
+ cout << "[A] thread finished\n";
+ });
+
+ cout << "Connecting two sockets\n";
+
+ SRT_SOCKGROUPCONFIG cc[2];
+ cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa);
+ cc[0].token = 0;
+ cc[1] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa);
+ cc[1].token = 1;
+ cc[1].weight = 1; // higher than the default 0
+
+ int result = srt_connect_group(ss, cc, 2);
+ EXPECT_GT(result, 0); // blocking mode, first connection = returns Socket ID
+
+ // Make sure both links are connected
+ SRT_SOCKGROUPDATA gdata[2];
+ size_t psize = 2;
+ size_t nwait = 10;
+ cout << "Waiting for getting 2 links:\n";
+ while (--nwait)
+ {
+ srt_group_data(ss, gdata, &psize);
+ if (psize == 2)
+ {
+ int l1, l2;
+ l1 = gdata[0].memberstate;
+ l2 = gdata[1].memberstate;
+
+ if (l1 > SRT_GST_PENDING && l2 > SRT_GST_PENDING)
+ {
+ cout << "Both up: [0]=" << l1 << " [1]=" << l2 << "\n";
+ break;
+ }
+ else
+ {
+ cout << "Still link states [0]=" << l1 << " [1]=" << l2 << "\n";
+ }
+ }
+ else
+ {
+ cout << "Still " << psize << endl;
+ }
+ this_thread::sleep_for(milliseconds(500));
+ }
+ EXPECT_NE(nwait, size_t());
+
+ // Now send one packet
+ long long data = 0x1234123412341234;
+
+ SRT_MSGCTRL mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+
+ // This call should retrieve the group information
+ // AFTER the transition has happened
+ int sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc));
+ EXPECT_EQ(sendret, int(sizeof data));
+
+ // So, let's check which link is in RUNNING state
+ // TOKEN value is the index in cc array, and we should
+ // also have the weight value there.
+
+ SRT_SOCKGROUPDATA* mane, * backup;
+ if (gdata[0].weight == 0)
+ {
+ backup = &gdata[0];
+ mane = &gdata[1];
+ }
+ else
+ {
+ mane = &gdata[0];
+ backup = &gdata[1];
+ }
+
+ cout << "MAIN:[" << mane->token << "] weight=" << mane->weight << endl;
+ cout << "BACKUP:[" << backup->token << "] weight=" << backup->weight << endl;
+
+ // Ok, now mane link should be active, backup idle
+ EXPECT_EQ(mane->memberstate, SRT_GST_RUNNING);
+ EXPECT_EQ(backup->memberstate, SRT_GST_IDLE);
+
+ acthr.join();
+
+ srt_close(g_listen_socket);
+ srt_close(ss);
+}
+
+
+// TEST IDEA:
+// In this test there is created a working listener socket to
+// accept the connection, and we use a Backup-type group with
+// two links, but different weights. We connect the first link
+// with less weight and send one packet to make sure this only
+// link was activated. Then we connect a second link with weight=1.
+// Then we send the packet again and see if the new link was
+// immediately activated. The first link should be silenced after
+// time, but there's no possibility to check this in such a
+// simple test.
+//
+// TEST STEPS:
+// 1. Create a listener socket and a group.
+// 3. Start the accepting thread
+// - accept a connection
+// - read one packet from the accepted entity
+// - read the second packet from the accepted entity
+// - close the listener socket
+// 4. Prepare one connection with weight=0 and connect the group
+// 5. Send a packet to enforce activation of one link
+// 6. Prepare another connection with weight=1 and connect the group
+// 7. Send a packet
+// 8. Check member status - both links should be running.
+// 9. Close the group.
+// 10. Join the thread
+TEST(Bonding, BackupPriorityTakeover)
+{
+ using namespace std;
+ using namespace std::chrono;
+ using namespace srt;
+
+ TestInit srtinit;
+
+ g_nconnected = 0;
+ g_nfailed = 0;
+
+ g_listen_socket = srt_create_socket();
+ ASSERT_NE(g_listen_socket, SRT_INVALID_SOCK);
+ sockaddr_in bind_sa;
+ memset(&bind_sa, 0, sizeof bind_sa);
+ bind_sa.sin_family = AF_INET;
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1);
+ bind_sa.sin_port = htons(4200);
+
+ EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1);
+ const int yes = 1;
+ srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes);
+ EXPECT_NE(srt_listen(g_listen_socket, 5), -1);
+
+ // Caller part
+
+ const int ss = srt_create_group(SRT_GTYPE_BACKUP);
+ EXPECT_NE(ss, SRT_ERROR);
+
+ srt_connect_callback(ss, &ConnectCallback, this);
+
+ sockaddr_in sa;
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(4200);
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1);
+
+ auto acthr = std::thread([]() {
+ sockaddr_any adr;
+ cout << "[A] Accepting a connection...\n";
+ int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len);
+
+ // Expected: group reporting
+ EXPECT_NE(accept_id & SRTGROUP_MASK, 0);
+
+ SRT_SOCKGROUPDATA gdata[2];
+ SRT_MSGCTRL mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+ long long data[1320/8];
+
+ cout << "[A] Receiving 1...\n";
+ int ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc));
+ EXPECT_EQ(ds, 8);
+
+ cout << "[A] Receiving 2...\n";
+ ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc));
+ EXPECT_EQ(ds, 8);
+
+ // To make it possible that the state is checked before it is closed.
+ this_thread::sleep_for(seconds(1));
+
+ cout << "[A] Closing\n";
+ srt_close(accept_id);
+ cout << "[A] thread finished\n";
+ });
+
+ cout << "Connecting first link weight=0:\n";
+
+ SRT_SOCKGROUPCONFIG cc[2];
+ cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa);
+ cc[0].token = 0;
+
+ int result = srt_connect_group(ss, cc, 1);
+ EXPECT_GT(result, 0); // connect with only one element returns socket ID
+
+ // As we have one link, after `srt_connect_group` returns, we have
+ // this link now connected. Send one data portion.
+
+ SRT_SOCKGROUPDATA gdata[2];
+
+ long long data = 0x1234123412341234;
+ SRT_MSGCTRL mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+
+ cout << "Sending (1)\n";
+ // This call should retrieve the group information
+ // AFTER the transition has happened
+ int sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc));
+ EXPECT_EQ(sendret, int(sizeof data));
+ EXPECT_EQ(mc.grpdata_size, size_t(1));
+ EXPECT_EQ(gdata[0].memberstate, SRT_GST_RUNNING);
+
+ cout << "Connecting second link weight=1:\n";
+ // Now prepare the second connection
+ cc[0].token = 1;
+ cc[0].weight = 1; // higher than the default 0
+ result = srt_connect_group(ss, cc, 1);
+ EXPECT_GT(result, 0); // connect with only one element returns socket ID
+
+ // Make sure both links are connected
+ size_t psize = 2;
+ size_t nwait = 10;
+ cout << "Waiting for getting 2 links:\n";
+ while (--nwait)
+ {
+ srt_group_data(ss, gdata, &psize);
+ if (psize == 2)
+ {
+ int l1, l2;
+ l1 = gdata[0].memberstate;
+ l2 = gdata[1].memberstate;
+
+ if (l1 > SRT_GST_PENDING && l2 > SRT_GST_PENDING)
+ {
+ cout << "Both up: [0]=" << l1 << " [1]=" << l2 << "\n";
+ break;
+ }
+ else
+ {
+ cout << "Still link states [0]=" << l1 << " [1]=" << l2 << "\n";
+ }
+ }
+ else
+ {
+ cout << "Still " << psize << endl;
+ }
+ this_thread::sleep_for(milliseconds(500));
+ }
+ EXPECT_NE(nwait, size_t(0));
+
+ // Now send one packet (again)
+ mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+
+ cout << "Sending (2)\n";
+ // This call should retrieve the group information
+ // AFTER the transition has happened
+ sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc));
+ EXPECT_EQ(sendret, int(sizeof data));
+
+ // So, let's check which link is in RUNNING state
+ // TOKEN value is the index in cc array, and we should
+ // also have the weight value there.
+
+ SRT_SOCKGROUPDATA* mane, * backup;
+ if (gdata[0].weight == 0)
+ {
+ backup = &gdata[0];
+ mane = &gdata[1];
+ }
+ else
+ {
+ mane = &gdata[0];
+ backup = &gdata[1];
+ }
+
+ cout << "MAIN:[" << mane->token << "] weight=" << mane->weight << endl;
+ cout << "BACKUP:[" << backup->token << "] weight=" << backup->weight << endl;
+
+ // Ok, now both links should be running (this state lasts
+ // for the "temporary activation" period.
+ EXPECT_EQ(mane->memberstate, SRT_GST_RUNNING);
+ EXPECT_EQ(backup->memberstate, SRT_GST_RUNNING);
+
+ acthr.join();
+
+ srt_close(g_listen_socket);
+ srt_close(ss);
+}
+
+
+// TEST IDEA:
+// In this test there is created a working listener socket to
+// accept the connection, and we use a Backup-type group with
+// two links, but different weights. We connect then two links
+// both with weight=1. Then we send a packet to make sure that
+// exactly one of them got activated. Then we connect another
+// link with weight=0. Then we send a packet again, which should
+// not change the link usage. Then we check which link was
+// active so far, and we close the socket for that link to make
+// it broken, then we wait for having only two links connected.
+// Then a packet is sent to activate a link. We expect the link
+// with higher weight is activated.
+//
+// TEST STEPS:
+// 1. Create a listener socket.
+// 2. Create and setup a group.
+// 3. Start the accepting thread
+// A1. accept a connection
+// A2. read one packet from the accepted entity
+// A3. read the second packet from the accepted entity
+// A4. read the third packet from the accepted entity
+// A5. close the listener socket
+// 4. Prepare two connections with weight=1 and connect the group
+// 5. Send a packet to enforce activation of one link
+// 6. Prepare another connection with weight=0 and connect the group
+// 7. Wait for having all 3 links connected.
+// 8. Send a packet
+// 9. Find which link is currently active and close it
+// 10. Wait for having only two links.
+// 11. Send a packet.
+// 12. Find one link active and one idle
+// 13. Check if the link with weight=1 is active and the one with weight=0 is idle.
+// 14. Close the group.
+// 15. Join the thread
+TEST(Bonding, BackupPrioritySelection)
+{
+ using namespace std;
+ using namespace std::chrono;
+ using namespace srt;
+
+ TestInit srtinit;
+
+ g_nconnected = 0;
+ g_nfailed = 0;
+ sync::atomic recvd { false };
+
+ // 1.
+ sockaddr_in bind_sa;
+ memset(&bind_sa, 0, sizeof bind_sa);
+ bind_sa.sin_family = AF_INET;
+ ASSERT_EQ(inet_pton(AF_INET, "127.0.0.1", &bind_sa.sin_addr), 1);
+ bind_sa.sin_port = htons(4200);
+
+ g_listen_socket = srt_create_socket();
+ ASSERT_NE(g_listen_socket, SRT_INVALID_SOCK);
+ EXPECT_NE(srt_bind(g_listen_socket, (sockaddr*)&bind_sa, sizeof bind_sa), -1);
+ const int yes = 1;
+ srt_setsockflag(g_listen_socket, SRTO_GROUPCONNECT, &yes, sizeof yes);
+ EXPECT_NE(srt_listen(g_listen_socket, 5), -1);
+
+ // Caller part
+ // 2.
+ const int ss = srt_create_group(SRT_GTYPE_BACKUP);
+ EXPECT_NE(ss, SRT_ERROR);
+
+ srt_connect_callback(ss, &ConnectCallback, this);
+
+ // Set the group's stability timeout to 1s, otherwise it will
+ // declare the links unstable by not receiving ACKs.
+ int stabtimeo = 1000;
+ srt_setsockflag(ss, SRTO_GROUPMINSTABLETIMEO, &stabtimeo, sizeof stabtimeo);
+
+ //srt_setloglevel(LOG_DEBUG);
+ srt::resetlogfa( std::set {
+ SRT_LOGFA_GRP_SEND,
+ SRT_LOGFA_GRP_MGMT,
+ SRT_LOGFA_CONN
+ });
+
+ sockaddr_in sa;
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(4200);
+ EXPECT_EQ(inet_pton(AF_INET, "127.0.0.1", &sa.sin_addr), 1);
+
+ // 3.
+ auto acthr = std::thread([&recvd]() {
+ sockaddr_any adr;
+ cout << "[A1] Accepting a connection...\n";
+
+ // A1
+ int accept_id = srt_accept(g_listen_socket, adr.get(), &adr.len);
+
+ // Expected: group reporting
+ EXPECT_NE(accept_id & SRTGROUP_MASK, 0);
+
+ SRT_SOCKGROUPDATA gdata[2];
+ SRT_MSGCTRL mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+ long long data[1320/8];
+
+ // A2
+ cout << "[A2] Receiving 1...\n";
+ int ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc));
+ if (ds == -1) { cout << "[A2] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; }
+ EXPECT_EQ(ds, 8);
+
+ // A3
+ cout << "[A3] Receiving 2...\n";
+ ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc));
+ if (ds == -1) { cout << "[A3] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; }
+ EXPECT_EQ(ds, 8);
+ recvd = true;
+
+ // A4
+ cout << "[A4] Receiving 3...\n";
+ ds = srt_recvmsg2(accept_id, (char*)data, sizeof data, (&mc));
+ if (ds == -1) { cout << "[A4] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; }
+ EXPECT_EQ(ds, 8);
+
+ cout << "[A] Waiting 5s...\n";
+ // To make it possible that the state is checked before it is closed.
+ this_thread::sleep_for(seconds(5));
+
+ // A5
+ cout << "[A5] Closing\n";
+ srt_close(accept_id);
+ cout << "[A] thread finished\n";
+ });
+
+
+ cout << "(4) Connecting first 2 links weight=1:\n";
+
+ SRT_SOCKGROUPCONFIG cc[2];
+ cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa);
+ cc[0].token = 0;
+ cc[0].weight = 1;
+ cc[1] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa);
+ cc[1].token = 1;
+ cc[1].weight = 1;
+
+ // 4.
+ int result = srt_connect_group(ss, cc, 2);
+ EXPECT_GT(result, 0); // BLOCKING MODE, always returns the socket value
+
+ // As we have one link, after `srt_connect_group` returns, we have
+ // this link now connected. Send one data portion.
+
+ SRT_SOCKGROUPDATA gdata[3];
+ memset(gdata, 0, sizeof(gdata));
+
+ long long data = 0x1234123412341234;
+ SRT_MSGCTRL mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 3;
+
+ // We can send now. We know that we have at least one
+ // link connected and it already has the same priority
+ // as the other.
+
+ //srt_setloglevel(LOG_DEBUG);
+ // 5.
+ cout << "(5) Sending (1)\n";
+ // This call should retrieve the group information
+ // AFTER the transition has happened
+ int sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc));
+ // In case when this was an error, display the code
+ if (sendret == -1) { cout << "[A4] ERROR: " << srt_getlasterror(NULL) << " " << srt_getlasterror_str() << endl; }
+
+ EXPECT_EQ(sendret, int(sizeof data));
+
+ EXPECT_EQ(mc.grpdata_size, size_t(2));
+
+ int state0 = gdata[0].memberstate;
+ int state1 = gdata[1].memberstate;
+
+ cout << "States: [0]=" << state0 << " [1]=" << state1 << endl;
+ EXPECT_TRUE(state0 == SRT_GST_RUNNING || state1 == SRT_GST_RUNNING);
+
+ // 6.
+ cout << "(6) Connecting third link weight=0:\n";
+ // Now prepare the third connection
+ cc[0] = srt_prepare_endpoint(NULL, (sockaddr*)&sa, sizeof sa);
+ cc[0].token = 2;
+ cc[0].weight = 0; // higher than the default 0
+ result = srt_connect_group(ss, cc, 1);
+ EXPECT_GE(result, 0); // ONE connection only - will return socket id
+
+ // Make sure all 3 links are connected
+ size_t psize = 3;
+ size_t nwait = 10;
+ set states;
+
+ // 7.
+ cout << "(7) Waiting for getting 3 links:\n";
+ while (--nwait)
+ {
+ srt_group_data(ss, gdata, &psize);
+ if (psize == 3)
+ {
+ states.clear();
+ for (int i = 0; i < 3; ++i)
+ states.insert(gdata[i].memberstate);
+
+ if (states.count(SRT_GST_PENDING))
+ {
+ cout << "Still not all links...\n";
+ }
+ else
+ {
+ cout << "All links up\n";
+ break;
+ }
+ }
+ else
+ {
+ cout << "Still " << psize << endl;
+ }
+ this_thread::sleep_for(milliseconds(500));
+ }
+ EXPECT_NE(nwait, size_t(0));
+
+ // Now send one packet (again)
+ mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 3;
+
+ // 8.
+ cout << "(8) Sending (2)\n";
+ // This call should retrieve the group information
+ // AFTER the transition has happened
+ sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc));
+ EXPECT_EQ(sendret, int(sizeof data));
+ EXPECT_EQ(mc.grpdata_size, size_t(3));
+
+ // So, let's check which link is in RUNNING state
+ // TOKEN value is the index in cc array, and we should
+ // also have the weight value there.
+
+ SRT_SOCKGROUPDATA* mane = nullptr;
+
+ for (size_t i = 0; i < mc.grpdata_size; ++i)
+ {
+ if (gdata[i].memberstate == SRT_GST_RUNNING)
+ {
+ mane = &gdata[i];
+ break;
+ }
+ }
+
+ EXPECT_NE(mane, nullptr);
+ EXPECT_EQ(mane->weight, 1);
+
+ // Spin-wait for making sure the reception succeeded before
+ // closing. This shouldn't be a problem in general, but
+ int ntry = 100;
+ while (!recvd && --ntry)
+ this_thread::sleep_for(milliseconds(200));
+ EXPECT_NE(ntry, 0);
+
+ cout << "(9) Found activated link: [" << mane->token << "] - closing after 0.5s...\n";
+
+ // Waiting is to make sure that the listener thread has received packet 3.
+ this_thread::sleep_for(milliseconds(500));
+ EXPECT_NE(srt_close(mane->id), -1);
+
+ // Now expect to have only 2 links, wait for it if needed.
+ psize = 2;
+ nwait = 10;
+
+ cout << "(10) Waiting for ONLY 2 links:\n";
+ while (--nwait)
+ {
+ srt_group_data(ss, gdata, &psize);
+ if (psize == 2)
+ {
+ break;
+ }
+ else
+ {
+ cout << "Still " << psize << endl;
+ }
+ this_thread::sleep_for(milliseconds(500));
+ }
+ EXPECT_NE(nwait, size_t(0));
+
+ // Now send one packet (again)
+ mc = srt_msgctrl_default;
+ mc.grpdata = gdata;
+ mc.grpdata_size = 2;
+
+ cout << "(11) Sending (3)\n";
+ // This call should retrieve the group information
+ // AFTER the transition has happened
+ sendret = srt_sendmsg2(ss, (char*)&data, sizeof data, (&mc));
+ EXPECT_EQ(sendret, int(sizeof data));
+
+ cout << "(sleep)\n";
+ this_thread::sleep_for(seconds(1));
+
+ mane = nullptr;
+ SRT_SOCKGROUPDATA* backup = nullptr;
+ cout << "(12) Checking main/backup:";
+ int repeat_check = 1; // 50;
+CheckLinksAgain:
+ for (size_t i = 0; i < mc.grpdata_size; ++i)
+ {
+ cout << "[" << i << "]" << srt_logging::MemberStatusStr(gdata[i].memberstate)
+ << " weight=" << gdata[i].weight;
+ if (gdata[i].memberstate == SRT_GST_RUNNING)
+ {
+ cout << " (main) ";
+ mane = &gdata[i];
+ }
+ else
+ {
+ cout << " (backup) ";
+ backup = &gdata[i];
+ }
+ }
+ if (backup == nullptr)
+ {
+ if (--repeat_check)
+ {
+ cout << "BACKUP STILL RUNNING. AGAIN\n";
+ this_thread::sleep_for(milliseconds(250));
+ goto CheckLinksAgain;
+ }
+ }
+ cout << endl;
+
+ EXPECT_NE(mane, nullptr);
+ EXPECT_NE(backup, nullptr);
+ EXPECT_EQ(mane->weight, 1);
+ EXPECT_EQ(backup->weight, 0);
+
+ cout << "MAIN (expected active):[" << mane->token << "] weight=" << mane->weight << endl;
+ cout << "BACKUP (expected idle):[" << backup->token << "] weight=" << backup->weight << endl;
+
+ // Ok, now both links should be running (this state lasts
+ // for the "temporary activation" period.
+ EXPECT_EQ(mane->memberstate, SRT_GST_RUNNING);
+ EXPECT_EQ(backup->memberstate, SRT_GST_IDLE);
+
+ this_thread::sleep_for(seconds(1));
+
+ cout << "Closing receiver thread [A]\n";
+
+ acthr.join();
+
+ srt_close(g_listen_socket);
+ srt_close(ss);
+}
+
+
+TEST(Bonding, ApiConfig)
+{
+ using namespace std;
+ SRT_SOCKOPT_CONFIG config;
+
+ string example = "example_long_excessively";
+
+ EXPECT_EQ(srt_config_add(&config, SRTO_BINDTODEVICE, (void*)example.data(), example.size()), 0);
+}
diff --git a/test/test_buffer_rcv.cpp b/test/test_buffer_rcv.cpp
index 511c2dcb1d..5e766de46f 100644
--- a/test/test_buffer_rcv.cpp
+++ b/test/test_buffer_rcv.cpp
@@ -25,7 +25,7 @@ class CRcvBufferReadMsg
// SetUp() is run immediately before a test starts.
void SetUp() override
{
- // make_unique is unfortunatelly C++14
+ // make_unique is unfortunately C++14
m_unit_queue.reset(new CUnitQueue(m_buff_size_pkts, 1500));
ASSERT_NE(m_unit_queue.get(), nullptr);
@@ -296,6 +296,34 @@ TEST_F(CRcvBufferReadMsg, PacketDropBySeqNo)
EXPECT_EQ(m_unit_queue->size(), m_unit_queue->capacity());
}
+// Drop ranges whose low seqno is already past the buffer end must be a
+// no-op. Without the offset_a >= m_szSize guard, incPos() wraps modulo
+// m_szSize and the loop walks legitimate entries -- mirroring the DoS
+// shape of a reversed range, just triggered from the other side.
+TEST_F(CRcvBufferReadMsg, PacketDropLoPastBufferEnd)
+{
+ EXPECT_EQ(addMessage(1, 1, m_init_seqno), 0);
+ EXPECT_EQ(addMessage(1, 2, CSeqNo::incseq(m_init_seqno)), 0);
+
+ auto& rcv_buffer = *m_rcv_buffer.get();
+ EXPECT_TRUE(hasAvailablePackets());
+
+ // Buffer size is m_buff_size_pkts (16); pick a range that begins
+ // well past the last valid slot.
+ const int32_t lo = m_init_seqno + 2 * m_buff_size_pkts;
+ const int32_t hi = lo + 4;
+ EXPECT_EQ(rcv_buffer.dropMessage(lo, hi, SRT_MSGNO_NONE, CRcvBuffer::DROP_EXISTING), 0);
+
+ // The two packets we inserted must still be present and readable.
+ EXPECT_TRUE(hasAvailablePackets());
+ EXPECT_TRUE(rcv_buffer.isRcvDataReady());
+ array