1
0
Fork 0
mirror of https://github.com/ossrs/srs.git synced 2025-02-13 20:01:56 +00:00
srs/trunk/3rdparty/srt-1-fit/srtcore/core.cpp
2021-05-16 16:14:00 +08:00

9705 lines
367 KiB
C++

/*
* SRT - Secure, Reliable, Transport
* Copyright (c) 2018 Haivision Systems Inc.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/
/*****************************************************************************
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the
above copyright notice, this list of conditions
and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the University of Illinois
nor the names of its contributors may be used to
endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
written by
Yunhong Gu, last updated 02/28/2012
modified by
Haivision Systems Inc.
*****************************************************************************/
#ifndef _WIN32
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <cmath>
#include <sstream>
#include "srt.h"
#include "queue.h"
#include "core.h"
#include "logging.h"
#include "crypto.h"
#include "logging_api.h" // Required due to containing extern srt_logger_config
// Again, just in case when some "smart guy" provided such a global macro
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
using namespace std;
namespace srt_logging
{
struct AllFaOn
{
LogConfig::fa_bitset_t allfa;
AllFaOn()
{
// allfa.set(SRT_LOGFA_BSTATS, true);
allfa.set(SRT_LOGFA_CONTROL, true);
allfa.set(SRT_LOGFA_DATA, true);
allfa.set(SRT_LOGFA_TSBPD, true);
allfa.set(SRT_LOGFA_REXMIT, true);
allfa.set(SRT_LOGFA_CONGEST, true);
#if ENABLE_HAICRYPT_LOGGING
allfa.set(SRT_LOGFA_HAICRYPT, true);
#endif
}
} logger_fa_all;
} // namespace srt_logging
// We need it outside the namespace to preserve the global name.
// It's a part of "hidden API" (used by applications)
SRT_API srt_logging::LogConfig srt_logger_config(srt_logging::logger_fa_all.allfa);
namespace srt_logging
{
Logger glog(SRT_LOGFA_GENERAL, srt_logger_config, "SRT.g");
// Unused. If not found useful, maybe reuse for another FA.
// Logger blog(SRT_LOGFA_BSTATS, srt_logger_config, "SRT.b");
Logger mglog(SRT_LOGFA_CONTROL, srt_logger_config, "SRT.c");
Logger dlog(SRT_LOGFA_DATA, srt_logger_config, "SRT.d");
Logger tslog(SRT_LOGFA_TSBPD, srt_logger_config, "SRT.t");
Logger rxlog(SRT_LOGFA_REXMIT, srt_logger_config, "SRT.r");
Logger cclog(SRT_LOGFA_CONGEST, srt_logger_config, "SRT.cc");
} // namespace srt_logging
using namespace srt_logging;
CUDTUnited CUDT::s_UDTUnited;
const SRTSOCKET UDT::INVALID_SOCK = CUDT::INVALID_SOCK;
const int UDT::ERROR = CUDT::ERROR;
// SRT Version constants
#define SRT_VERSION_UNK 0
#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */
#define SRT_VERSION_MAJ(v) (0xFF0000 & (v)) /* Major number ensuring backward compatibility */
#define SRT_VERSION_MIN(v) (0x00FF00 & (v))
#define SRT_VERSION_PCH(v) (0x0000FF & (v))
// NOTE: SRT_VERSION is primarily defined in the build file.
const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION);
//#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_SZ 12 /* Current version packet size */
#if SRT_CMD_HSREQ_SZ > SRT_CMD_MAXSZ
#error SRT_CMD_MAXSZ too small
#endif
/* Handshake Request (Network Order)
0[31..0]: SRT version SRT_DEF_VERSION
1[31..0]: Options 0 [ | SRT_OPT_TSBPDSND ][ | SRT_OPT_HAICRYPT ]
2[31..16]: TsbPD resv 0
2[15..0]: TsbPD delay [0..60000] msec
*/
//#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_SZ 12 /* Current version packet size */
#if SRT_CMD_HSRSP_SZ > SRT_CMD_MAXSZ
#error SRT_CMD_MAXSZ too small
#endif
/* Handshake Response (Network Order)
0[31..0]: SRT version SRT_DEF_VERSION
1[31..0]: Options 0 [ | SRT_OPT_TSBPDRCV [| SRT_OPT_TLPKTDROP ]][ | SRT_OPT_HAICRYPT]
[ | SRT_OPT_NAKREPORT ] [ | SRT_OPT_REXMITFLG ]
2[31..16]: TsbPD resv 0
2[15..0]: TsbPD delay [0..60000] msec
*/
void CUDT::construct()
{
m_pSndBuffer = NULL;
m_pRcvBuffer = NULL;
m_pSndLossList = NULL;
m_pRcvLossList = NULL;
m_iReorderTolerance = 0;
m_iMaxReorderTolerance = 0; // Sensible optimal value is 10, 0 preserves old behavior
m_iConsecEarlyDelivery = 0; // how many times so far the packet considered lost has been received before TTL expires
m_iConsecOrderedDelivery = 0;
m_pSndQueue = NULL;
m_pRcvQueue = NULL;
m_pPeerAddr = NULL;
m_pSNode = NULL;
m_pRNode = NULL;
m_ullSndHsLastTime_us = 0;
m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; // Will be reset to 0 for HSv5, this value is important for HSv4
// Initial status
m_bOpened = false;
m_bListening = false;
m_bConnecting = false;
m_bConnected = false;
m_bClosing = false;
m_bShutdown = false;
m_bBroken = false;
m_bPeerHealth = true;
m_RejectReason = SRT_REJ_UNKNOWN;
m_ullLingerExpiration = 0;
m_llLastReqTime = 0;
m_lSrtVersion = SRT_DEF_VERSION;
m_lPeerSrtVersion = 0; // not defined until connected.
m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1;
m_iTsbPdDelay_ms = 0;
m_iPeerTsbPdDelay_ms = 0;
m_bPeerTsbPd = false;
m_iPeerTsbPdDelay_ms = 0;
m_bTsbPd = false;
m_bTsbPdAckWakeup = false;
m_bPeerTLPktDrop = false;
m_uKmRefreshRatePkt = 0;
m_uKmPreAnnouncePkt = 0;
// Initilize mutex and condition variables
initSynch();
}
CUDT::CUDT()
{
construct();
(void)SRT_DEF_VERSION;
// Default UDT configurations
m_iMSS = 1500;
m_bSynSending = true;
m_bSynRecving = true;
m_iFlightFlagSize = 25600;
m_iSndBufSize = 8192;
m_iRcvBufSize = 8192; // Rcv buffer MUST NOT be bigger than Flight Flag size
// Linger: LIVE mode defaults, please refer to `SRTO_TRANSTYPE` option
// for other modes.
m_Linger.l_onoff = 0;
m_Linger.l_linger = 0;
m_iUDPSndBufSize = 65536;
m_iUDPRcvBufSize = m_iRcvBufSize * m_iMSS;
m_iSockType = UDT_DGRAM;
m_iIPversion = AF_INET;
m_bRendezvous = false;
#ifdef SRT_ENABLE_CONNTIMEO
m_iConnTimeOut = 3000;
#endif
m_iSndTimeOut = -1;
m_iRcvTimeOut = -1;
m_bReuseAddr = true;
m_llMaxBW = -1;
#ifdef SRT_ENABLE_IPOPTS
m_iIpTTL = -1;
m_iIpToS = -1;
#endif
m_CryptoSecret.len = 0;
m_iSndCryptoKeyLen = 0;
// Cfg
m_bDataSender = false; // Sender only if true: does not recv data
m_bOPT_TsbPd = true; // Enable TsbPd on sender
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
m_iOPT_PeerTsbPdDelay = 0; // Peer's TsbPd delay as receiver (here is its minimum value, if used)
m_bOPT_TLPktDrop = true;
m_iOPT_SndDropDelay = 0;
m_bOPT_StrictEncryption = true;
m_iOPT_PeerIdleTimeout = COMM_RESPONSE_TIMEOUT_MS;
m_bTLPktDrop = true; // Too-late Packet Drop
m_bMessageAPI = true;
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
m_iIpV6Only = -1;
// Runtime
m_bRcvNakReport = true; // Receiver's Periodic NAK Reports
m_llInputBW = 0; // Application provided input bandwidth (internal input rate sampling == 0)
m_iOverheadBW = 25; // Percent above input stream rate (applies if m_llMaxBW == 0)
m_OPT_PktFilterConfigString = "";
m_pCache = NULL;
// Default congctl is "live".
// Available builtin congctl: "file".
// Other congctls can be registerred.
// Note that 'select' returns false if there's no such congctl.
// If so, congctl becomes unselected. Calling 'configure' on an
// unselected congctl results in exception.
m_CongCtl.select("live");
}
CUDT::CUDT(const CUDT &ancestor)
{
construct();
// XXX Consider all below fields (except m_bReuseAddr) to be put
// into a separate class for easier copying.
// Default UDT configurations
m_iMSS = ancestor.m_iMSS;
m_bSynSending = ancestor.m_bSynSending;
m_bSynRecving = ancestor.m_bSynRecving;
m_iFlightFlagSize = ancestor.m_iFlightFlagSize;
m_iSndBufSize = ancestor.m_iSndBufSize;
m_iRcvBufSize = ancestor.m_iRcvBufSize;
m_Linger = ancestor.m_Linger;
m_iUDPSndBufSize = ancestor.m_iUDPSndBufSize;
m_iUDPRcvBufSize = ancestor.m_iUDPRcvBufSize;
m_iSockType = ancestor.m_iSockType;
m_iIPversion = ancestor.m_iIPversion;
m_bRendezvous = ancestor.m_bRendezvous;
#ifdef SRT_ENABLE_CONNTIMEO
m_iConnTimeOut = ancestor.m_iConnTimeOut;
#endif
m_iSndTimeOut = ancestor.m_iSndTimeOut;
m_iRcvTimeOut = ancestor.m_iRcvTimeOut;
m_bReuseAddr = true; // this must be true, because all accepted sockets share the same port with the listener
m_llMaxBW = ancestor.m_llMaxBW;
#ifdef SRT_ENABLE_IPOPTS
m_iIpTTL = ancestor.m_iIpTTL;
m_iIpToS = ancestor.m_iIpToS;
#endif
m_llInputBW = ancestor.m_llInputBW;
m_iOverheadBW = ancestor.m_iOverheadBW;
m_bDataSender = ancestor.m_bDataSender;
m_bOPT_TsbPd = ancestor.m_bOPT_TsbPd;
m_iOPT_TsbPdDelay = ancestor.m_iOPT_TsbPdDelay;
m_iOPT_PeerTsbPdDelay = ancestor.m_iOPT_PeerTsbPdDelay;
m_bOPT_TLPktDrop = ancestor.m_bOPT_TLPktDrop;
m_iOPT_SndDropDelay = ancestor.m_iOPT_SndDropDelay;
m_bOPT_StrictEncryption = ancestor.m_bOPT_StrictEncryption;
m_iOPT_PeerIdleTimeout = ancestor.m_iOPT_PeerIdleTimeout;
m_zOPT_ExpPayloadSize = ancestor.m_zOPT_ExpPayloadSize;
m_bTLPktDrop = ancestor.m_bTLPktDrop;
m_bMessageAPI = ancestor.m_bMessageAPI;
m_iIpV6Only = ancestor.m_iIpV6Only;
m_iReorderTolerance = ancestor.m_iMaxReorderTolerance; // Initialize with maximum value
m_iMaxReorderTolerance = ancestor.m_iMaxReorderTolerance;
// Runtime
m_bRcvNakReport = ancestor.m_bRcvNakReport;
m_OPT_PktFilterConfigString = ancestor.m_OPT_PktFilterConfigString;
m_CryptoSecret = ancestor.m_CryptoSecret;
m_iSndCryptoKeyLen = ancestor.m_iSndCryptoKeyLen;
m_uKmRefreshRatePkt = ancestor.m_uKmRefreshRatePkt;
m_uKmPreAnnouncePkt = ancestor.m_uKmPreAnnouncePkt;
m_pCache = ancestor.m_pCache;
// SrtCongestion's copy constructor copies the selection,
// but not the underlying congctl object. After
// copy-constructed, the 'configure' must be called on it again.
m_CongCtl = ancestor.m_CongCtl;
}
CUDT::~CUDT()
{
// release mutex/condtion variables
destroySynch();
// Wipeout critical data
memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret));
// destroy the data structures
delete m_pSndBuffer;
delete m_pRcvBuffer;
delete m_pSndLossList;
delete m_pRcvLossList;
delete m_pPeerAddr;
delete m_pSNode;
delete m_pRNode;
}
// This function is to make it possible for both C and C++
// API to accept both bool and int types for boolean options.
// (it's not that C couldn't use <stdbool.h>, it's that people
// often forget to use correct type).
static bool bool_int_value(const void *optval, int optlen)
{
if (optlen == sizeof(bool))
{
return *(bool *)optval;
}
if (optlen == sizeof(int))
{
return 0 != *(int *)optval; // 0!= is a windows warning-killer int-to-bool conversion
}
return false;
}
void CUDT::setOpt(SRT_SOCKOPT optName, const void *optval, int optlen)
{
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
CGuard cg(m_ConnectionLock);
CGuard sendguard(m_SendLock);
CGuard recvguard(m_RecvLock);
switch (optName)
{
case SRTO_MSS:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (*(int *)optval < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iMSS = *(int *)optval;
// Packet size cannot be greater than UDP buffer size
if (m_iMSS > m_iUDPSndBufSize)
m_iMSS = m_iUDPSndBufSize;
if (m_iMSS > m_iUDPRcvBufSize)
m_iMSS = m_iUDPRcvBufSize;
break;
case SRTO_SNDSYN:
m_bSynSending = bool_int_value(optval, optlen);
break;
case SRTO_RCVSYN:
m_bSynRecving = bool_int_value(optval, optlen);
break;
case SRTO_FC:
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (*(int *)optval < 1)
throw CUDTException(MJ_NOTSUP, MN_INVAL);
// Mimimum recv flight flag size is 32 packets
if (*(int *)optval > 32)
m_iFlightFlagSize = *(int *)optval;
else
m_iFlightFlagSize = 32;
break;
case SRTO_SNDBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (*(int *)optval <= 0)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iSndBufSize = *(int *)optval / (m_iMSS - CPacket::UDP_HDR_SIZE);
break;
case SRTO_RCVBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (*(int *)optval <= 0)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
{
// This weird cast through int is required because
// API requires 'int', and internals require 'size_t';
// their size is different on 64-bit systems.
size_t val = size_t(*(int *)optval);
// Mimimum recv buffer size is 32 packets
size_t mssin_size = m_iMSS - CPacket::UDP_HDR_SIZE;
// XXX This magic 32 deserves some constant
if (val > mssin_size * 32)
m_iRcvBufSize = val / mssin_size;
else
m_iRcvBufSize = 32;
// recv buffer MUST not be greater than FC size
if (m_iRcvBufSize > m_iFlightFlagSize)
m_iRcvBufSize = m_iFlightFlagSize;
}
break;
case SRTO_LINGER:
m_Linger = *(linger *)optval;
break;
case SRTO_UDP_SNDBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iUDPSndBufSize = *(int *)optval;
if (m_iUDPSndBufSize < m_iMSS)
m_iUDPSndBufSize = m_iMSS;
break;
case SRTO_UDP_RCVBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iUDPRcvBufSize = *(int *)optval;
if (m_iUDPRcvBufSize < m_iMSS)
m_iUDPRcvBufSize = m_iMSS;
break;
case SRTO_RENDEZVOUS:
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bRendezvous = bool_int_value(optval, optlen);
break;
case SRTO_SNDTIMEO:
m_iSndTimeOut = *(int *)optval;
break;
case SRTO_RCVTIMEO:
m_iRcvTimeOut = *(int *)optval;
break;
case SRTO_REUSEADDR:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bReuseAddr = bool_int_value(optval, optlen);
break;
case SRTO_MAXBW:
m_llMaxBW = *(int64_t *)optval;
// This can be done on both connected and unconnected socket.
// When not connected, this will do nothing, however this
// event will be repeated just after connecting anyway.
if (m_bConnected)
updateCC(TEV_INIT, TEV_INIT_RESET);
break;
#ifdef SRT_ENABLE_IPOPTS
case SRTO_IPTTL:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (!(*(int *)optval == -1) && !((*(int *)optval >= 1) && (*(int *)optval <= 255)))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iIpTTL = *(int *)optval;
break;
case SRTO_IPTOS:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iIpToS = *(int *)optval;
break;
#endif
case SRTO_INPUTBW:
m_llInputBW = *(int64_t *)optval;
// (only if connected; if not, then the value
// from m_iOverheadBW will be used initially)
if (m_bConnected)
updateCC(TEV_INIT, TEV_INIT_INPUTBW);
break;
case SRTO_OHEADBW:
if ((*(int *)optval < 5) || (*(int *)optval > 100))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iOverheadBW = *(int *)optval;
// Changed overhead BW, so spread the change
// (only if connected; if not, then the value
// from m_iOverheadBW will be used initially)
if (m_bConnected)
updateCC(TEV_INIT, TEV_INIT_OHEADBW);
break;
case SRTO_SENDER:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bDataSender = bool_int_value(optval, optlen);
break;
case SRTO_TSBPDMODE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bOPT_TsbPd = bool_int_value(optval, optlen);
break;
case SRTO_LATENCY:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iOPT_TsbPdDelay = *(int *)optval;
m_iOPT_PeerTsbPdDelay = *(int *)optval;
break;
case SRTO_RCVLATENCY:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iOPT_TsbPdDelay = *(int *)optval;
break;
case SRTO_PEERLATENCY:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iOPT_PeerTsbPdDelay = *(int *)optval;
break;
case SRTO_TLPKTDROP:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bOPT_TLPktDrop = bool_int_value(optval, optlen);
break;
case SRTO_SNDDROPDELAY:
// Surprise: you may be connected to alter this option.
// The application may manipulate this option on sender while transmitting.
m_iOPT_SndDropDelay = *(int *)optval;
break;
case SRTO_PASSPHRASE:
// For consistency, throw exception when connected,
// no matter if otherwise the password can be set.
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
#ifdef SRT_ENABLE_ENCRYPTION
// Password must be 10-80 characters.
// Or it can be empty to clear the password.
if ((optlen != 0) && (optlen < 10 || optlen > HAICRYPT_SECRET_MAX_SZ))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret));
m_CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE;
m_CryptoSecret.len = (optlen <= (int)sizeof(m_CryptoSecret.str) ? optlen : (int)sizeof(m_CryptoSecret.str));
memcpy(m_CryptoSecret.str, optval, m_CryptoSecret.len);
#else
if (optlen == 0)
break;
LOGC(mglog.Error, log << "SRTO_PASSPHRASE: encryption not enabled at compile time");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
#endif
break;
case SRTO_PBKEYLEN:
case _DEPRECATED_SRTO_SNDPBKEYLEN:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
#ifdef SRT_ENABLE_ENCRYPTION
{
int v = *(int *)optval;
int allowed[4] = {
0, // Default value, if this results for initiator, defaults to 16. See below.
16, // AES-128
24, // AES-192
32 // AES-256
};
int *allowed_end = allowed + 4;
if (find(allowed, allowed_end, v) == allowed_end)
{
LOGC(mglog.Error,
log << "Invalid value for option SRTO_PBKEYLEN: " << v << "; allowed are: 0, 16, 24, 32");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
// Note: This works a little different in HSv4 and HSv5.
// HSv4:
// The party that is set SRTO_SENDER will send KMREQ, and it will
// use default value 16, if SRTO_PBKEYLEN is the default value 0.
// The responder that receives KMRSP has nothing to say about
// PBKEYLEN anyway and it will take the length of the key from
// the initiator (sender) as a good deal.
//
// HSv5:
// The initiator (independently on the sender) will send KMREQ,
// and as it should be the sender to decide about the PBKEYLEN.
// Your application should do the following then:
// 1. The sender should set PBKEYLEN to the required value.
// 2. If the sender is initiator, it will create the key using
// its preset PBKEYLEN (or default 16, if not set) and the
// receiver-responder will take it as a good deal.
// 3. Leave the PBKEYLEN value on the receiver as default 0.
// 4. If sender is responder, it should then advertise the PBKEYLEN
// value in the initial handshake messages (URQ_INDUCTION if
// listener, and both URQ_WAVEAHAND and URQ_CONCLUSION in case
// of rendezvous, as it is the matter of luck who of them will
// eventually become the initiator). This way the receiver
// being an initiator will set m_iSndCryptoKeyLen before setting
// up KMREQ for sending to the sender-responder.
//
// Note that in HSv5 if both sides set PBKEYLEN, the responder
// wins, unless the initiator is a sender (the effective PBKEYLEN
// will be the one advertised by the responder). If none sets,
// PBKEYLEN will default to 16.
m_iSndCryptoKeyLen = v;
}
#else
LOGC(mglog.Error, log << "SRTO_PBKEYLEN: encryption not enabled at compile time");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
#endif
break;
case SRTO_NAKREPORT:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bRcvNakReport = bool_int_value(optval, optlen);
break;
#ifdef SRT_ENABLE_CONNTIMEO
case SRTO_CONNTIMEO:
m_iConnTimeOut = *(int *)optval;
break;
#endif
case SRTO_LOSSMAXTTL:
m_iMaxReorderTolerance = *(int *)optval;
if (!m_bConnected)
m_iReorderTolerance = m_iMaxReorderTolerance;
break;
case SRTO_VERSION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_lSrtVersion = *(uint32_t *)optval;
break;
case SRTO_MINVERSION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_lMinimumPeerSrtVersion = *(uint32_t *)optval;
break;
case SRTO_STREAMID:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (size_t(optlen) > MAX_SID_LENGTH)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_sStreamName.assign((const char *)optval, optlen);
break;
case SRTO_CONGESTION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
{
string val;
if (optlen == -1)
val = (const char *)optval;
else
val.assign((const char *)optval, optlen);
// Translate alias
if (val == "vod")
val = "file";
bool res = m_CongCtl.select(val);
if (!res)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
break;
case SRTO_MESSAGEAPI:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bMessageAPI = bool_int_value(optval, optlen);
break;
case SRTO_PAYLOADSIZE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (*(int *)optval > SRT_LIVE_MAX_PLSIZE)
{
LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE, maximum payload per MTU.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (m_OPT_PktFilterConfigString != "")
{
// This means that the filter might have been installed before,
// and the fix to the maximum payload size was already applied.
// This needs to be checked now.
SrtFilterConfig fc;
if (!ParseFilterConfig(m_OPT_PktFilterConfigString, fc))
{
// Break silently. This should not happen
LOGC(mglog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size;
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
{
LOGC(mglog.Error,
log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE decreased by " << fc.extra_size
<< " required for packet filter header");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
}
m_zOPT_ExpPayloadSize = *(int *)optval;
break;
case SRTO_TRANSTYPE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// XXX Note that here the configuration for SRTT_LIVE
// is the same as DEFAULT VALUES for these fields set
// in CUDT::CUDT.
switch (*(SRT_TRANSTYPE *)optval)
{
case SRTT_LIVE:
// Default live options:
// - tsbpd: on
// - latency: 120ms
// - linger: off
// - congctl: live
// - extraction method: message (reading call extracts one message)
m_bOPT_TsbPd = true;
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
m_iOPT_PeerTsbPdDelay = 0;
m_bOPT_TLPktDrop = true;
m_iOPT_SndDropDelay = 0;
m_bMessageAPI = true;
m_bRcvNakReport = true;
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
m_Linger.l_onoff = 0;
m_Linger.l_linger = 0;
m_CongCtl.select("live");
break;
case SRTT_FILE:
// File transfer mode:
// - tsbpd: off
// - latency: 0
// - linger: 2 minutes (180s)
// - congctl: file (original UDT congestion control)
// - extraction method: stream (reading call extracts as many bytes as available and fits in buffer)
m_bOPT_TsbPd = false;
m_iOPT_TsbPdDelay = 0;
m_iOPT_PeerTsbPdDelay = 0;
m_bOPT_TLPktDrop = false;
m_iOPT_SndDropDelay = -1;
m_bMessageAPI = false;
m_bRcvNakReport = false;
m_zOPT_ExpPayloadSize = 0; // use maximum
m_Linger.l_onoff = 1;
m_Linger.l_linger = 180; // 2 minutes
m_CongCtl.select("file");
break;
default:
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
break;
case SRTO_KMREFRESHRATE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// If you first change the KMREFRESHRATE, KMPREANNOUNCE
// will be set to the maximum allowed value
m_uKmRefreshRatePkt = *(int *)optval;
if (m_uKmPreAnnouncePkt == 0 || m_uKmPreAnnouncePkt > (m_uKmRefreshRatePkt - 1) / 2)
{
m_uKmPreAnnouncePkt = (m_uKmRefreshRatePkt - 1) / 2;
LOGC(mglog.Warn,
log << "SRTO_KMREFRESHRATE=0x" << hex << m_uKmRefreshRatePkt << ": setting SRTO_KMPREANNOUNCE=0x"
<< hex << m_uKmPreAnnouncePkt);
}
break;
case SRTO_KMPREANNOUNCE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
{
int val = *(int *)optval;
int kmref = m_uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_uKmRefreshRatePkt;
if (val > (kmref - 1) / 2)
{
LOGC(mglog.Error,
log << "SRTO_KMPREANNOUNCE=0x" << hex << val << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2)
<< " - OPTION REJECTED.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
m_uKmPreAnnouncePkt = val;
}
break;
case SRTO_ENFORCEDENCRYPTION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bOPT_StrictEncryption = bool_int_value(optval, optlen);
break;
case SRTO_PEERIDLETIMEO:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iOPT_PeerIdleTimeout = *(int *)optval;
break;
case SRTO_IPV6ONLY:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iIpV6Only = *(int *)optval;
break;
case SRTO_PACKETFILTER:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
{
string arg((char *)optval, optlen);
// Parse the configuration string prematurely
SrtFilterConfig fc;
if (!ParseFilterConfig(arg, fc))
{
LOGC(mglog.Error,
log << "SRTO_FILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. "
"FILTERTYPE ("
<< fc.type << ") must be installed (or builtin)");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size;
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
{
LOGC(mglog.Warn,
log << "Due to filter-required extra " << fc.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to "
<< efc_max_payload_size << " bytes");
m_zOPT_ExpPayloadSize = efc_max_payload_size;
}
m_OPT_PktFilterConfigString = arg;
}
break;
default:
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
}
void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen)
{
CGuard cg(m_ConnectionLock);
switch (optName)
{
case SRTO_MSS:
*(int *)optval = m_iMSS;
optlen = sizeof(int);
break;
case SRTO_SNDSYN:
*(bool *)optval = m_bSynSending;
optlen = sizeof(bool);
break;
case SRTO_RCVSYN:
*(bool *)optval = m_bSynRecving;
optlen = sizeof(bool);
break;
case SRTO_ISN:
*(int *)optval = m_iISN;
optlen = sizeof(int);
break;
case SRTO_FC:
*(int *)optval = m_iFlightFlagSize;
optlen = sizeof(int);
break;
case SRTO_SNDBUF:
*(int *)optval = m_iSndBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE);
optlen = sizeof(int);
break;
case SRTO_RCVBUF:
*(int *)optval = m_iRcvBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE);
optlen = sizeof(int);
break;
case SRTO_LINGER:
if (optlen < (int)(sizeof(linger)))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
*(linger *)optval = m_Linger;
optlen = sizeof(linger);
break;
case SRTO_UDP_SNDBUF:
*(int *)optval = m_iUDPSndBufSize;
optlen = sizeof(int);
break;
case SRTO_UDP_RCVBUF:
*(int *)optval = m_iUDPRcvBufSize;
optlen = sizeof(int);
break;
case SRTO_RENDEZVOUS:
*(bool *)optval = m_bRendezvous;
optlen = sizeof(bool);
break;
case SRTO_SNDTIMEO:
*(int *)optval = m_iSndTimeOut;
optlen = sizeof(int);
break;
case SRTO_RCVTIMEO:
*(int *)optval = m_iRcvTimeOut;
optlen = sizeof(int);
break;
case SRTO_REUSEADDR:
*(bool *)optval = m_bReuseAddr;
optlen = sizeof(bool);
break;
case SRTO_MAXBW:
*(int64_t *)optval = m_llMaxBW;
optlen = sizeof(int64_t);
break;
case SRTO_STATE:
*(int32_t *)optval = s_UDTUnited.getStatus(m_SocketID);
optlen = sizeof(int32_t);
break;
case SRTO_EVENT:
{
int32_t event = 0;
if (m_bBroken)
event |= UDT_EPOLL_ERR;
else
{
CGuard::enterCS(m_RecvLock);
if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady())
event |= UDT_EPOLL_IN;
CGuard::leaveCS(m_RecvLock);
if (m_pSndBuffer && (m_iSndBufSize > m_pSndBuffer->getCurrBufSize()))
event |= UDT_EPOLL_OUT;
}
*(int32_t *)optval = event;
optlen = sizeof(int32_t);
break;
}
case SRTO_SNDDATA:
if (m_pSndBuffer)
*(int32_t *)optval = m_pSndBuffer->getCurrBufSize();
else
*(int32_t *)optval = 0;
optlen = sizeof(int32_t);
break;
case SRTO_RCVDATA:
if (m_pRcvBuffer)
{
CGuard::enterCS(m_RecvLock);
*(int32_t *)optval = m_pRcvBuffer->getRcvDataSize();
CGuard::leaveCS(m_RecvLock);
}
else
*(int32_t *)optval = 0;
optlen = sizeof(int32_t);
break;
#ifdef SRT_ENABLE_IPOPTS
case SRTO_IPTTL:
if (m_bOpened)
*(int32_t *)optval = m_pSndQueue->getIpTTL();
else
*(int32_t *)optval = m_iIpTTL;
optlen = sizeof(int32_t);
break;
case SRTO_IPTOS:
if (m_bOpened)
*(int32_t *)optval = m_pSndQueue->getIpToS();
else
*(int32_t *)optval = m_iIpToS;
optlen = sizeof(int32_t);
break;
#endif
case SRTO_SENDER:
*(int32_t *)optval = m_bDataSender;
optlen = sizeof(int32_t);
break;
case SRTO_TSBPDMODE:
*(int32_t *)optval = m_bOPT_TsbPd;
optlen = sizeof(int32_t);
break;
case SRTO_LATENCY:
case SRTO_RCVLATENCY:
*(int32_t *)optval = m_iTsbPdDelay_ms;
optlen = sizeof(int32_t);
break;
case SRTO_PEERLATENCY:
*(int32_t *)optval = m_iPeerTsbPdDelay_ms;
optlen = sizeof(int32_t);
break;
case SRTO_TLPKTDROP:
*(int32_t *)optval = m_bTLPktDrop;
optlen = sizeof(int32_t);
break;
case SRTO_SNDDROPDELAY:
*(int32_t *)optval = m_iOPT_SndDropDelay;
optlen = sizeof(int32_t);
break;
case SRTO_PBKEYLEN:
if (m_pCryptoControl)
*(int32_t *)optval = m_pCryptoControl->KeyLen(); // Running Key length.
else
*(int32_t *)optval = m_iSndCryptoKeyLen; // May be 0.
optlen = sizeof(int32_t);
break;
case SRTO_KMSTATE:
if (!m_pCryptoControl)
*(int32_t *)optval = SRT_KM_S_UNSECURED;
else if (m_bDataSender)
*(int32_t *)optval = m_pCryptoControl->m_SndKmState;
else
*(int32_t *)optval = m_pCryptoControl->m_RcvKmState;
optlen = sizeof(int32_t);
break;
case SRTO_SNDKMSTATE: // State imposed by Agent depending on PW and KMX
if (m_pCryptoControl)
*(int32_t *)optval = m_pCryptoControl->m_SndKmState;
else
*(int32_t *)optval = SRT_KM_S_UNSECURED;
optlen = sizeof(int32_t);
break;
case SRTO_RCVKMSTATE: // State returned by Peer as informed during KMX
if (m_pCryptoControl)
*(int32_t *)optval = m_pCryptoControl->m_RcvKmState;
else
*(int32_t *)optval = SRT_KM_S_UNSECURED;
optlen = sizeof(int32_t);
break;
case SRTO_LOSSMAXTTL:
*(int32_t*)optval = m_iMaxReorderTolerance;
optlen = sizeof(int32_t);
break;
case SRTO_NAKREPORT:
*(bool *)optval = m_bRcvNakReport;
optlen = sizeof(bool);
break;
case SRTO_VERSION:
*(int32_t *)optval = m_lSrtVersion;
optlen = sizeof(int32_t);
break;
case SRTO_PEERVERSION:
*(int32_t *)optval = m_lPeerSrtVersion;
optlen = sizeof(int32_t);
break;
#ifdef SRT_ENABLE_CONNTIMEO
case SRTO_CONNTIMEO:
*(int *)optval = m_iConnTimeOut;
optlen = sizeof(int);
break;
#endif
case SRTO_MINVERSION:
*(uint32_t *)optval = m_lMinimumPeerSrtVersion;
optlen = sizeof(uint32_t);
break;
case SRTO_STREAMID:
if (size_t(optlen) < m_sStreamName.size() + 1)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
strcpy((char *)optval, m_sStreamName.c_str());
optlen = m_sStreamName.size();
break;
case SRTO_CONGESTION:
{
string tt = m_CongCtl.selected_name();
strcpy((char *)optval, tt.c_str());
optlen = tt.size();
}
break;
case SRTO_MESSAGEAPI:
optlen = sizeof(bool);
*(bool *)optval = m_bMessageAPI;
break;
case SRTO_PAYLOADSIZE:
optlen = sizeof(int);
*(int *)optval = m_zOPT_ExpPayloadSize;
break;
case SRTO_ENFORCEDENCRYPTION:
optlen = sizeof(int32_t); // also with TSBPDMODE and SENDER
*(int32_t *)optval = m_bOPT_StrictEncryption;
break;
case SRTO_IPV6ONLY:
optlen = sizeof(int);
*(int *)optval = m_iIpV6Only;
break;
case SRTO_PEERIDLETIMEO:
*(int *)optval = m_iOPT_PeerIdleTimeout;
optlen = sizeof(int);
break;
case SRTO_PACKETFILTER:
if (size_t(optlen) < m_OPT_PktFilterConfigString.size() + 1)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
strcpy((char *)optval, m_OPT_PktFilterConfigString.c_str());
optlen = m_OPT_PktFilterConfigString.size();
break;
default:
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
}
}
bool CUDT::setstreamid(SRTSOCKET u, const std::string &sid)
{
CUDT *that = getUDTHandle(u);
if (!that)
return false;
if (sid.size() > MAX_SID_LENGTH)
return false;
if (that->m_bConnected)
return false;
that->m_sStreamName = sid;
return true;
}
std::string CUDT::getstreamid(SRTSOCKET u)
{
CUDT *that = getUDTHandle(u);
if (!that)
return "";
return that->m_sStreamName;
}
// XXX REFACTOR: Make common code for CUDT constructor and clearData,
// possibly using CUDT::construct.
void CUDT::clearData()
{
// Initial sequence number, loss, acknowledgement, etc.
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
HLOGC(mglog.Debug, log << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
m_iEXPCount = 1;
m_iBandwidth = 1; // pkts/sec
// XXX use some constant for this 16
m_iDeliveryRate = 16;
m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize;
m_iAckSeqNo = 0;
m_ullLastAckTime_tk = 0;
// trace information
CGuard::enterCS(m_StatsLock);
m_stats.startTime = CTimer::getTime();
m_stats.sentTotal = m_stats.recvTotal = m_stats.sndLossTotal = m_stats.rcvLossTotal = m_stats.retransTotal =
m_stats.sentACKTotal = m_stats.recvACKTotal = m_stats.sentNAKTotal = m_stats.recvNAKTotal = 0;
m_stats.lastSampleTime = CTimer::getTime();
m_stats.traceSent = m_stats.traceRecv = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans =
m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0;
m_stats.traceRcvRetrans = 0;
m_stats.traceReorderDistance = 0;
m_stats.traceBelatedTime = 0.0;
m_stats.traceRcvBelated = 0;
m_stats.sndDropTotal = 0;
m_stats.traceSndDrop = 0;
m_stats.rcvDropTotal = 0;
m_stats.traceRcvDrop = 0;
m_stats.m_rcvUndecryptTotal = 0;
m_stats.traceRcvUndecrypt = 0;
m_stats.bytesSentTotal = 0;
m_stats.bytesRecvTotal = 0;
m_stats.bytesRetransTotal = 0;
m_stats.traceBytesSent = 0;
m_stats.traceBytesRecv = 0;
m_stats.sndFilterExtra = 0;
m_stats.rcvFilterExtra = 0;
m_stats.rcvFilterSupply = 0;
m_stats.rcvFilterLoss = 0;
m_stats.traceBytesRetrans = 0;
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
m_stats.traceRcvBytesLoss = 0;
#endif
m_stats.sndBytesDropTotal = 0;
m_stats.rcvBytesDropTotal = 0;
m_stats.traceSndBytesDrop = 0;
m_stats.traceRcvBytesDrop = 0;
m_stats.m_rcvBytesUndecryptTotal = 0;
m_stats.traceRcvBytesUndecrypt = 0;
m_stats.sndDuration = m_stats.m_sndDurationTotal = 0;
CGuard::leaveCS(m_StatsLock);
// Resetting these data because this happens when agent isn't connected.
m_bPeerTsbPd = false;
m_iPeerTsbPdDelay_ms = 0;
m_bTsbPd = m_bOPT_TsbPd; // Take the values from user-configurable options
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
m_bTLPktDrop = m_bOPT_TLPktDrop;
m_bPeerTLPktDrop = false;
m_bPeerNakReport = false;
m_bPeerRexmitFlag = false;
m_RdvState = CHandShake::RDV_INVALID;
m_ullRcvPeerStartTime = 0;
}
void CUDT::open()
{
CGuard cg(m_ConnectionLock);
clearData();
// structures for queue
if (m_pSNode == NULL)
m_pSNode = new CSNode;
m_pSNode->m_pUDT = this;
m_pSNode->m_llTimeStamp_tk = 1;
m_pSNode->m_iHeapLoc = -1;
if (m_pRNode == NULL)
m_pRNode = new CRNode;
m_pRNode->m_pUDT = this;
m_pRNode->m_llTimeStamp_tk = 1;
m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL;
m_pRNode->m_bOnList = false;
m_iRTT = 10 * COMM_SYN_INTERVAL_US;
m_iRTTVar = m_iRTT >> 1;
m_ullCPUFrequency = CTimer::getCPUFrequency();
// set minimum NAK and EXP timeout to 300ms
/*
XXX This code is blocked because the value of
m_ullMinNakInt_tk will be overwritten again in setupCC.
And in setupCC it will have an opportunity to make the
value overridden according to the statements in the SrtCongestion.
#ifdef SRT_ENABLE_NAKREPORT
if (m_bRcvNakReport)
m_ullMinNakInt_tk = m_iMinNakInterval_us * m_ullCPUFrequency;
else
#endif
*/
// Set up timers
m_ullMinNakInt_tk = 300000 * m_ullCPUFrequency;
m_ullMinExpInt_tk = 300000 * m_ullCPUFrequency;
m_ullACKInt_tk = COMM_SYN_INTERVAL_US * m_ullCPUFrequency;
m_ullNAKInt_tk = m_ullMinNakInt_tk;
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
m_ullLastRspTime_tk = currtime_tk;
m_ullNextACKTime_tk = currtime_tk + m_ullACKInt_tk;
m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk;
m_ullLastRspAckTime_tk = currtime_tk;
m_ullLastSndTime_tk = currtime_tk;
m_iReXmitCount = 1;
m_iPktCount = 0;
m_iLightACKCount = 1;
m_ullTargetTime_tk = 0;
m_ullTimeDiff_tk = 0;
// Now UDT is opened.
m_bOpened = true;
}
void CUDT::setListenState()
{
CGuard cg(m_ConnectionLock);
if (!m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
if (m_bConnecting || m_bConnected)
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;
}
size_t CUDT::fillSrtHandshake(uint32_t *srtdata, size_t srtlen, int msgtype, int hs_version)
{
if (srtlen < SRT_HS__SIZE)
{
LOGC(mglog.Fatal,
log << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS__SIZE << ")");
return 0;
}
srtlen = SRT_HS__SIZE; // We use only that much space.
memset(srtdata, 0, sizeof(uint32_t) * srtlen);
/* Current version (1.x.x) SRT handshake */
srtdata[SRT_HS_VERSION] = m_lSrtVersion; /* Required version */
srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities();
switch (msgtype)
{
case SRT_CMD_HSREQ:
return fillSrtHandshake_HSREQ(srtdata, srtlen, hs_version);
case SRT_CMD_HSRSP:
return fillSrtHandshake_HSRSP(srtdata, srtlen, hs_version);
default:
LOGC(mglog.Fatal, log << "IPE: createSrtHandshake/sendSrtMsg called with value " << msgtype);
return 0;
}
}
size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version)
{
// INITIATOR sends HSREQ.
// The TSBPD(SND|RCV) options are being set only if the TSBPD is set in the current agent.
// The agent has a decisive power only in the range of RECEIVING the data, however it can
// also influence the peer's latency. If agent doesn't set TSBPD mode, it doesn't send any
// latency flags, although the peer might still want to do Rx with TSBPD. When agent sets
// TsbPd mode, it defines latency values for Rx (itself) and Tx (peer's Rx). If peer does
// not set TsbPd mode, it will simply ignore the proposed latency (PeerTsbPdDelay), although
// if it has received the Rx latency as well, it must honor it and respond accordingly
// (the latter is only in case of HSv5 and bidirectional connection).
if (m_bOPT_TsbPd)
{
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay;
/*
* Sent data is real-time, use Time-based Packet Delivery,
* set option bit and configured delay
*/
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND;
if (hs_version < CUDT::HS_VERSION_SRT1)
{
// HSv4 - this uses only one value.
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay_ms);
}
else
{
// HSv5 - this will be understood only since this version when this exists.
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms);
m_bTsbPd = true;
// And in the reverse direction.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV;
srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms);
// This wasn't there for HSv4, this setting is only for the receiver.
// HSv5 is bidirectional, so every party is a receiver.
if (m_bTLPktDrop)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP;
}
}
if (m_bRcvNakReport)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT;
// I support SRT_OPT_REXMITFLG. Do you?
srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG;
// Declare the API used. The flag is set for "stream" API because
// the older versions will never set this flag, but all old SRT versions use message API.
if (!m_bMessageAPI)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM;
HLOGC(mglog.Debug,
log << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY])
<< " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS["
<< SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]");
return 3;
}
size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version)
{
// Setting m_ullRcvPeerStartTime is done in processSrtMsg_HSREQ(), so
// this condition will be skipped only if this function is called without
// getting first received HSREQ. Doesn't look possible in both HSv4 and HSv5.
if (m_ullRcvPeerStartTime != 0)
{
// If Agent doesn't set TSBPD, it will not set the TSBPD flag back to the Peer.
// The peer doesn't have be disturbed by it anyway.
if (m_bTsbPd)
{
/*
* We got and transposed peer start time (HandShake request timestamp),
* we can support Timestamp-based Packet Delivery
*/
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV;
if (hs_version < HS_VERSION_SRT1)
{
// HSv4 - this uses only one value
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay_ms);
}
else
{
// HSv5 - this puts "agent's" latency into RCV field and "peer's" -
// into SND field.
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms);
}
}
else
{
HLOGC(mglog.Debug, log << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag.");
}
// Hsv5, only when peer has declared TSBPD mode.
// The flag was already set, and the value already "maximized" in processSrtMsg_HSREQ().
if (m_bPeerTsbPd && hs_version >= HS_VERSION_SRT1)
{
// HSv5 is bidirectional - so send the TSBPDSND flag, and place also the
// peer's latency into SND field.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND;
srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms);
HLOGC(mglog.Debug,
log << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms);
}
else
{
HLOGC(mglog.Debug,
log << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5)
<< " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND");
}
if (m_bTLPktDrop)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP;
}
else
{
LOGC(mglog.Fatal, log << "IPE: fillSrtHandshake_HSRSP: m_ullRcvPeerStartTime NOT SET!");
return 0;
}
if (m_bRcvNakReport)
{
// HSv5: Note that this setting is independent on the value of
// m_bPeerNakReport, which represent this setting in the peer.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT;
/*
* NAK Report is so efficient at controlling bandwidth that sender TLPktDrop
* is not needed. SRT 1.0.5 to 1.0.7 sender TLPktDrop combined with SRT 1.0
* Timestamp-Based Packet Delivery was not well implemented and could drop
* big I-Frame tail before sending once on low latency setups.
* Disabling TLPktDrop in the receiver SRT Handshake Reply prevents the sender
* from enabling Too-Late Packet Drop.
*/
if (m_lPeerSrtVersion <= SrtVersion(1, 0, 7))
srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP;
}
if (m_lSrtVersion >= SrtVersion(1, 2, 0))
{
if (!m_bPeerRexmitFlag)
{
// Peer does not request to use rexmit flag, if so,
// we won't use as well.
HLOGC(mglog.Debug, log << "HSRSP/snd: AGENT understands REXMIT flag, but PEER DOES NOT. NOT setting.");
}
else
{
// Request that the rexmit bit be used as a part of msgno.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG;
HLOGF(mglog.Debug, "HSRSP/snd: AGENT UNDERSTANDS REXMIT flag and PEER reported that it does, too.");
}
}
else
{
// Since this is now in the code, it can occur only in case when you change the
// version specification in the build configuration.
HLOGF(mglog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag");
}
HLOGC(mglog.Debug,
log << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY])
<< " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS["
<< SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]");
return 3;
}
size_t CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size)
{
size_t srtlen = fillSrtHandshake(srtdata, size, cmd, handshakeVersion());
HLOGF(mglog.Debug,
"CMD:%s(%d) Len:%d Version: %s Flags: %08X (%s) sdelay:%d",
MessageTypeStr(UMSG_EXT, cmd).c_str(),
cmd,
(int)(srtlen * sizeof(int32_t)),
SrtVersionString(srtdata[SRT_HS_VERSION]).c_str(),
srtdata[SRT_HS_FLAGS],
SrtFlagString(srtdata[SRT_HS_FLAGS]).c_str(),
srtdata[SRT_HS_LATENCY]);
return srtlen;
}
void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in)
{
CPacket srtpkt;
int32_t srtcmd = (int32_t)cmd;
static const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ / sizeof(int32_t);
// This is in order to issue a compile error if the SRT_CMD_MAXSZ is
// too small to keep all the data. As this is "static const", declaring
// an array of such specified size in C++ isn't considered VLA.
static const int SRTDATA_SIZE = SRTDATA_MAXSIZE >= SRT_HS__SIZE ? SRTDATA_MAXSIZE : -1;
// This will be effectively larger than SRT_HS__SIZE, but it will be also used
// for incoming data. We have a guarantee that it won't be larger than SRTDATA_MAXSIZE.
uint32_t srtdata[SRTDATA_SIZE];
int srtlen = 0;
if (cmd == SRT_CMD_REJECT)
{
// This is a value returned by processSrtMsg underlying layer, potentially
// to be reported here. Should this happen, just send a rejection message.
cmd = SRT_CMD_HSRSP;
srtdata[SRT_HS_VERSION] = 0;
}
switch (cmd)
{
case SRT_CMD_HSREQ:
case SRT_CMD_HSRSP:
srtlen = prepareSrtHsMsg(cmd, srtdata, SRTDATA_SIZE);
break;
case SRT_CMD_KMREQ: // Sender
case SRT_CMD_KMRSP: // Receiver
srtlen = srtlen_in;
/* Msg already in network order
* But CChannel:sendto will swap again (assuming 32-bit fields)
* Pre-swap to cancel it.
*/
HtoNLA(srtdata, srtdata_in, srtlen);
m_pCryptoControl->updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT
break;
default:
LOGF(mglog.Error, "sndSrtMsg: cmd=%d unsupported", cmd);
break;
}
if (srtlen > 0)
{
/* srtpkt.pack will set message data in network order */
srtpkt.pack(UMSG_EXT, &srtcmd, srtdata, srtlen * sizeof(int32_t));
addressAndSend(srtpkt);
}
}
// PREREQUISITE:
// pkt must be set the buffer and configured for UMSG_HANDSHAKE.
// Note that this function replaces also serialization for the HSv4.
bool CUDT::createSrtHandshake(ref_t<CPacket> r_pkt,
ref_t<CHandShake> r_hs,
int srths_cmd,
int srtkm_cmd,
const uint32_t * kmdata,
size_t kmdata_wordsize /* IN WORDS, NOT BYTES!!! */)
{
CPacket & pkt = *r_pkt;
CHandShake &hs = *r_hs;
// This function might be called before the opposite version was recognized.
// Check if the version is exactly 4 because this means that the peer has already
// sent something - asynchronously, and usually in rendezvous - and we already know
// that the peer is version 4. In this case, agent must behave as HSv4, til the end.
if (m_ConnRes.m_iVersion == HS_VERSION_UDT4)
{
hs.m_iVersion = HS_VERSION_UDT4;
hs.m_iType = UDT_DGRAM;
if (hs.m_extension)
{
// Should be impossible
LOGC(mglog.Error, log << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing...");
hs.m_extension = false;
}
}
else
{
hs.m_iType = 0; // Prepare it for flags
}
HLOGC(mglog.Debug,
log << "createSrtHandshake: buf size=" << pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd)
<< " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize
<< " version=" << hs.m_iVersion);
// 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
// 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 (hs.m_iVersion > HS_VERSION_UDT4)
{
// Check if there was a failure to receie HSREQ before trying to craft HSRSP.
// If fillSrtHandshake_HSRSP catches the condition of m_ullRcvPeerStartTime == 0,
// it will return size 0, which will mess up with further extension procedures;
// PREVENT THIS HERE.
if (hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && m_ullRcvPeerStartTime == 0)
{
LOGC(mglog.Error,
log << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. "
"BLOCKING extensions.");
hs.m_extension = false;
}
// The situation when this function is called without requested extensions
// is URQ_CONCLUSION in rendezvous mode in some of the transitions.
// In this case for version 5 just clear the m_iType field, as it has
// different meaning in HSv5 and contains extension flags.
//
// Keep 0 in the SRT_HSTYPE_HSFLAGS field, but still advertise PBKEYLEN
// in the SRT_HSTYPE_ENCFLAGS field.
hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_iSndCryptoKeyLen);
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
HLOGC(mglog.Debug,
log << "createSrtHandshake: " << (whether ? "" : "NOT ")
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
// Note: This is required only when sending a HS message without SRT extensions.
// When this is to be sent with SRT extensions, then KMREQ will be attached here
// and the PBKEYLEN will be extracted from it. If this is going to attach KMRSP
// here, it's already too late (it should've been advertised before getting the first
// handshake message with KMREQ).
}
else
{
hs.m_iType = UDT_DGRAM;
}
// values > URQ_CONCLUSION include also error types
// if (hs.m_iVersion == HS_VERSION_UDT4 || hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and
// it's only valid for caller-listener mode
if (!hs.m_extension)
{
// Serialize only the basic handshake, if this is predicted for
// Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND.
size_t hs_size = pkt.getLength();
hs.store_to(pkt.m_pcData, Ref(hs_size));
pkt.setLength(hs_size);
HLOGC(mglog.Debug, log << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << hs.show());
return true;
}
// Sanity check, applies to HSv5 only cases.
if (srths_cmd == SRT_CMD_HSREQ && m_SrtHsSide == HSD_RESPONDER)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(mglog.Fatal, log << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!");
return false; // should cause rejection
}
string logext = "HSX";
bool have_kmreq = false;
bool have_sid = false;
bool have_congctl = false;
bool have_filter = false;
// Install the SRT extensions
hs.m_iType |= CHandShake::HS_EXT_HSREQ;
if (srths_cmd == SRT_CMD_HSREQ)
{
if (m_sStreamName != "")
{
have_sid = true;
hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext += ",SID";
}
}
// If this is a response, we have also information
// on the peer. If Peer is NOT filter capable, don't
// put filter config, even if agent is capable.
bool peer_filter_capable = true;
if (srths_cmd == SRT_CMD_HSRSP)
{
if (m_sPeerPktFilterConfigString != "")
{
peer_filter_capable = true;
}
else if (IsSet(m_lPeerSrtFlags, SRT_OPT_FILTERCAP))
{
peer_filter_capable = true;
}
else
{
peer_filter_capable = false;
}
}
// Now, if this is INITIATOR, then it has its
// filter config already set, if configured, otherwise
// it should not attach the filter config extension.
// If this is a RESPONDER, then it has already received
// the filter config string from the peer and therefore
// possibly confronted with the contents of m_OPT_FECConfigString,
// and if it decided to go with filter, it will be nonempty.
if (peer_filter_capable && m_OPT_PktFilterConfigString != "")
{
have_filter = true;
hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext += ",filter";
}
string sm = m_CongCtl.selected_name();
if (sm != "" && sm != "live")
{
have_congctl = true;
hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext += ",CONGCTL";
}
// Prevent adding KMRSP only in case when BOTH:
// - Agent has set no password
// - no KMREQ has arrived from Peer
// KMRSP must be always sent when:
// - Agent set a password, Peer did not send KMREQ: Agent sets snd=NOSECRET.
// - Agent set no password, but Peer sent KMREQ: Ageng sets rcv=NOSECRET.
if (m_CryptoSecret.len > 0 || kmdata_wordsize > 0)
{
have_kmreq = true;
hs.m_iType |= CHandShake::HS_EXT_KMREQ;
logext += ",KMX";
}
HLOGC(mglog.Debug, log << "createSrtHandshake: (ext: " << logext << ") data: " << hs.show());
// NOTE: The HSREQ is practically always required, although may happen
// in future that CONCLUSION can be sent multiple times for a separate
// stream encryption support, and this way it won't enclose HSREQ.
// Also, KMREQ may occur multiple times.
// So, initially store the UDT legacy handshake.
size_t hs_size = pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data
hs.store_to(pkt.m_pcData, Ref(hs_size)); // hs_size is updated
size_t ra_size = hs_size / sizeof(int32_t);
// Now attach the SRT handshake for HSREQ
size_t offset = ra_size;
uint32_t *p = reinterpret_cast<uint32_t *>(pkt.m_pcData);
// NOTE: since this point, ra_size has a size in int32_t elements, NOT BYTES.
// The first 4-byte item is the CMD/LENGTH spec.
uint32_t *pcmdspec = p + offset; // Remember the location to be filled later, when we know the length
++offset;
// 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.
// 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(ra_size);
HLOGC(mglog.Debug,
log << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size
<< " space left: " << (total_ra_size - offset));
if (have_sid)
{
// Use only in REQ phase and only if stream name is set
offset += ra_size;
pcmdspec = p + offset;
++offset;
// 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;
if (m_sStreamName.size() >= size_limit)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes");
return false;
}
size_t wordsize = (m_sStreamName.size() + 3) / 4;
size_t aligned_bytesize = wordsize * 4;
memset(p + offset, 0, aligned_bytesize);
memcpy(p + offset, m_sStreamName.data(), m_sStreamName.size());
// Preswap to little endian (in place due to possible padding zeros)
HtoILA((uint32_t *)(p + offset), (uint32_t *)(p + offset), wordsize);
ra_size = wordsize;
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_SID) | HS_CMDSPEC_SIZE::wrap(ra_size);
HLOGC(mglog.Debug,
log << "createSrtHandshake: after SID [" << m_sStreamName << "] length=" << m_sStreamName.size()
<< " alignedln=" << aligned_bytesize << ": offset=" << offset << " SID size=" << ra_size
<< " space left: " << (total_ra_size - offset));
}
if (have_congctl)
{
// Pass the congctl to the other side as informational.
// The other side should reject connection if it uses a different congctl.
// The other side should also respond with the congctl it uses, if its non-default (for backward compatibility).
// XXX Consider change the congctl settings in the listener socket to "adaptive"
// congctl and also "adaptive" value of CUDT::m_bMessageAPI so that the caller
// may ask for whatever kind of transmission it wants, or select transmission
// type differently for different connections, however with the same listener.
offset += ra_size;
pcmdspec = p + offset;
++offset;
size_t wordsize = (sm.size() + 3) / 4;
size_t aligned_bytesize = wordsize * 4;
memset(p + offset, 0, aligned_bytesize);
memcpy(p + offset, sm.data(), sm.size());
// Preswap to little endian (in place due to possible padding zeros)
HtoILA((uint32_t *)(p + offset), (uint32_t *)(p + offset), wordsize);
ra_size = wordsize;
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_CONGESTION) | HS_CMDSPEC_SIZE::wrap(ra_size);
HLOGC(mglog.Debug,
log << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size()
<< " alignedln=" << aligned_bytesize << ": offset=" << offset << " CONGCTL size=" << ra_size
<< " space left: " << (total_ra_size - offset));
}
if (have_filter)
{
offset += ra_size;
pcmdspec = p + offset;
++offset;
size_t wordsize = (m_OPT_PktFilterConfigString.size() + 3) / 4;
size_t aligned_bytesize = wordsize * 4;
memset(p + offset, 0, aligned_bytesize);
memcpy(p + offset, m_OPT_PktFilterConfigString.data(), m_OPT_PktFilterConfigString.size());
ra_size = wordsize;
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_FILTER) | HS_CMDSPEC_SIZE::wrap(ra_size);
HLOGC(mglog.Debug,
log << "createSrtHandshake: after filter [" << m_OPT_PktFilterConfigString << "] length="
<< m_OPT_PktFilterConfigString.size() << " alignedln=" << aligned_bytesize << ": offset=" << offset
<< " filter size=" << ra_size << " space left: " << (total_ra_size - offset));
}
// When encryption turned on
if (have_kmreq)
{
HLOGC(mglog.Debug,
log << "createSrtHandshake: "
<< (m_CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION"));
if (srtkm_cmd == SRT_CMD_KMREQ)
{
bool have_any_keys = false;
for (size_t ki = 0; ki < 2; ++ki)
{
// Skip those that have expired
if (!m_pCryptoControl->getKmMsg_needSend(ki, false))
continue;
m_pCryptoControl->getKmMsg_markSent(ki, false);
offset += ra_size;
size_t msglen = m_pCryptoControl->getKmMsg_size(ki);
// Make ra_size back in element unit
// Add one extra word if the size isn't aligned to 32-bit.
ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0);
// Store the CMD + SIZE in the next field
*(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size);
++offset;
// Copy the key - do the endian inversion because another endian inversion
// will be done for every control message before sending, and this KM message
// is ALREADY in network order.
const uint32_t *keydata = reinterpret_cast<const uint32_t *>(m_pCryptoControl->getKmMsg_data(ki));
HLOGC(mglog.Debug,
log << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size
<< " words (KmMsg_size=" << msglen << ")");
// XXX INSECURE ": [" << FormatBinaryString((uint8_t*)keydata, msglen) << "]";
// Yes, I know HtoNLA and NtoHLA do exactly the same operation, but I want
// to be clear about the true intention.
NtoHLA(p + offset, keydata, ra_size);
have_any_keys = true;
}
if (!have_any_keys)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(mglog.Error, log << "createSrtHandshake: IPE: all keys have expired, no KM to send.");
return false;
}
}
else if (srtkm_cmd == SRT_CMD_KMRSP)
{
uint32_t failure_kmrsp[] = {SRT_KM_S_UNSECURED};
const uint32_t *keydata = 0;
// Shift the starting point with the value of previously added block,
// to start with the new one.
offset += ra_size;
if (kmdata_wordsize == 0)
{
LOGC(mglog.Error,
log << "createSrtHandshake: Agent has PW, but Peer sent no KMREQ. Sending error KMRSP response");
ra_size = 1;
keydata = failure_kmrsp;
// Update the KM state as well
m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Agent has PW, but Peer won't decrypt
m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Peer won't encrypt as well.
}
else
{
if (!kmdata)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(mglog.Fatal, log << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!");
return false;
}
ra_size = kmdata_wordsize;
keydata = reinterpret_cast<const uint32_t *>(kmdata);
}
*(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size);
++offset; // Once cell, containting CMD spec and size
HLOGC(mglog.Debug,
log << "createSrtHandshake: KMRSP: applying returned key length="
<< ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata,
// kmdata_wordsize*sizeof(uint32_t)) << "]";
NtoHLA(p + offset, keydata, ra_size);
}
else
{
m_RejectReason = SRT_REJ_IPE;
LOGC(mglog.Fatal, log << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd);
return false;
}
}
// ra_size + offset has a value in element unit.
// Switch it again to byte unit.
pkt.setLength((ra_size + offset) * sizeof(int32_t));
HLOGC(mglog.Debug,
log << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(hs.m_iType)
<< " length: " << pkt.getLength() << " bytes");
return true;
}
static int
FindExtensionBlock(uint32_t *begin, size_t total_length, ref_t<size_t> r_out_len, ref_t<uint32_t *> r_next_block)
{
// Check if there's anything to process
if (total_length == 0)
{
*r_next_block = NULL;
*r_out_len = 0;
return SRT_CMD_NONE;
}
size_t & out_len = *r_out_len;
uint32_t *&next_block = *r_next_block;
// This function extracts the block command from the block and its length.
// The command value is returned as a function result.
// The size of that command block is stored into out_len.
// The beginning of the prospective next block is stored in next_block.
// The caller must be aware that:
// - exactly one element holds the block header (cmd+size), so the actual data are after this one.
// - the returned size is the number of uint32_t elements since that first data element
// - the remaining size should be manually calculated as total_length - 1 - out_len, or
// simply, as next_block - begin.
// Note that if the total_length is too short to extract the whole block, it will return
// SRT_CMD_NONE. Note that total_length includes this first CMDSPEC word.
//
// When SRT_CMD_NONE is returned, it means that nothing has been extracted and nothing else
// can be further extracted from this block.
int cmd = HS_CMDSPEC_CMD::unwrap(*begin);
size_t size = HS_CMDSPEC_SIZE::unwrap(*begin);
if (size + 1 > total_length)
return SRT_CMD_NONE;
out_len = size;
if (total_length == size + 1)
next_block = NULL;
else
next_block = begin + 1 + size;
return cmd;
}
static inline bool NextExtensionBlock(ref_t<uint32_t *> begin, uint32_t *next, ref_t<size_t> length)
{
if (!next)
return false;
*length = *length - (next - *begin);
*begin = next;
return true;
}
bool CUDT::processSrtMsg(const CPacket *ctrlpkt)
{
uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData;
size_t len = ctrlpkt->getLength();
int etype = ctrlpkt->getExtendedType();
uint32_t ts = ctrlpkt->m_iTimeStamp;
int res = SRT_CMD_NONE;
HLOGC(mglog.Debug, log << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t)));
switch (etype)
{
case SRT_CMD_HSREQ:
{
res = processSrtMsg_HSREQ(srtdata, len, ts, CUDT::HS_VERSION_UDT4);
break;
}
case SRT_CMD_HSRSP:
{
res = processSrtMsg_HSRSP(srtdata, len, ts, CUDT::HS_VERSION_UDT4);
break;
}
case SRT_CMD_KMREQ:
// Special case when the data need to be processed here
// and the appropriate message must be constructed for sending.
// No further processing required
{
uint32_t srtdata_out[SRTDATA_MAXSIZE];
size_t len_out = 0;
res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, srtdata_out, Ref(len_out), CUDT::HS_VERSION_UDT4);
if (res == SRT_CMD_KMRSP)
{
if (len_out == 1)
{
if (m_bOPT_StrictEncryption)
{
LOGC(mglog.Error,
log << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))
<< " - rejecting per strict encryption");
return false;
}
HLOGC(mglog.Debug,
log << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])));
}
else
{
HLOGC(mglog.Debug, log << "KMREQ -> requested to send KMRSP length=" << len_out);
}
sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out);
}
// XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now.
// Please review later.
else
{
LOGC(mglog.Error, log << "KMREQ failed to process the request - ignoring");
}
return true; // already done what's necessary
}
case SRT_CMD_KMRSP:
{
// KMRSP doesn't expect any following action
m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, CUDT::HS_VERSION_UDT4);
return true; // nothing to do
}
default:
return false;
}
if (res == SRT_CMD_NONE)
return true;
// Send the message that the message handler requested.
sendSrtMsg(res);
return true;
}
int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t len, uint32_t ts, int hsv)
{
// Set this start time in the beginning, regardless as to whether TSBPD is being
// used or not. This must be done in the Initiator as well as Responder.
/*
* Compute peer StartTime in our time reference
* This takes time zone, time drift into account.
* Also includes current packet transit time (rtt/2)
*/
#if 0 // Debug PeerStartTime if not 1st HS packet
{
uint64_t oldPeerStartTime = m_ullRcvPeerStartTime;
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
if (oldPeerStartTime) {
LOGC(mglog.Note, log << "rcvSrtMsg: 2nd PeerStartTime diff=" <<
(m_ullRcvPeerStartTime - oldPeerStartTime) << " usec");
}
}
#else
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
#endif
// Prepare the initial runtime values of latency basing on the option values.
// They are going to get the value fixed HERE.
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay;
if (len < SRT_CMD_HSREQ_MINSZ)
{
m_RejectReason = SRT_REJ_ROGUE;
/* Packet smaller than minimum compatible packet size */
LOGF(mglog.Error, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " invalid", SRT_CMD_HSREQ, len);
return SRT_CMD_NONE;
}
LOGF(mglog.Note,
"HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " vers=0x%x opts=0x%x delay=%d",
SRT_CMD_HSREQ,
len,
srtdata[SRT_HS_VERSION],
srtdata[SRT_HS_FLAGS],
SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]));
m_lPeerSrtVersion = srtdata[SRT_HS_VERSION];
m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS];
if (hsv == CUDT::HS_VERSION_UDT4)
{
if (m_lPeerSrtVersion >= SRT_VERSION_FEAT_HSv5)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5)
<< " is not acceptable.");
return SRT_CMD_REJECT;
}
}
else
{
if (m_lPeerSrtVersion < SRT_VERSION_FEAT_HSv5)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "HSREQ/rcv: With HSv5 version must be >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " .");
return SRT_CMD_REJECT;
}
}
// Check also if the version satisfies the minimum required version
if (m_lPeerSrtVersion < m_lMinimumPeerSrtVersion)
{
m_RejectReason = SRT_REJ_VERSION;
LOGC(mglog.Error,
log << "HSREQ/rcv: Peer version: " << SrtVersionString(m_lPeerSrtVersion)
<< " is too old for requested: " << SrtVersionString(m_lMinimumPeerSrtVersion) << " - REJECTING");
return SRT_CMD_REJECT;
}
HLOGC(mglog.Debug,
log << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_lPeerSrtVersion) << " Flags: " << m_lPeerSrtFlags
<< "(" << SrtFlagString(m_lPeerSrtFlags) << ")");
m_bPeerRexmitFlag = IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG);
HLOGF(mglog.Debug, "HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND");
// Check if both use the same API type. Reject if not.
bool peer_message_api = !IsSet(m_lPeerSrtFlags, SRT_OPT_STREAM);
if (peer_message_api != m_bMessageAPI)
{
m_RejectReason = SRT_REJ_MESSAGEAPI;
LOGC(mglog.Error,
log << "HSREQ/rcv: Agent uses " << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, but the Peer declares "
<< (peer_message_api ? "MESSAGE" : "STREAM") << " API. Not compatible transmission type, rejecting.");
return SRT_CMD_REJECT;
}
if (len < SRT_HS_LATENCY + 1)
{
// 3 is the size when containing VERSION, FLAGS and LATENCY. Less size
// makes it contain only the first two. Let's make it acceptable, as long
// as the latency flags aren't set.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting.");
return SRT_CMD_REJECT;
}
LOGC(mglog.Warn, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, not getting any TSBPD settings.");
// Don't process any further settings in this case. Turn off TSBPD, just for a case.
m_bTsbPd = false;
m_bPeerTsbPd = false;
return SRT_CMD_HSRSP;
}
uint32_t latencystr = srtdata[SRT_HS_LATENCY];
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND))
{
// TimeStamp-based Packet Delivery feature enabled
if (!m_bTsbPd)
{
LOGC(mglog.Warn, log << "HSREQ/rcv: Agent did not set rcv-TSBPD - ignoring proposed latency from peer");
// Note: also don't set the peer TSBPD flag HERE because
// - in HSv4 it will be a sender, so it doesn't matter anyway
// - in HSv5 if it's going to receive, the TSBPDRCV flag will define it.
}
else
{
int peer_decl_latency;
if (hsv < CUDT::HS_VERSION_SRT1)
{
// In HSv4 there is only one value and this is the latency
// that the sender peer proposes for the agent.
peer_decl_latency = SRT_HS_LATENCY_LEG::unwrap(latencystr);
}
else
{
// In HSv5 there are latency declared for sending and receiving separately.
// SRT_HS_LATENCY_SND is the value that the peer proposes to be the
// value used by agent when receiving data. We take this as a local latency value.
peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]);
}
// Use the maximum latency out of latency from our settings and the latency
// "proposed" by the peer.
int maxdelay = std::max(m_iTsbPdDelay_ms, peer_decl_latency);
HLOGC(mglog.Debug,
log << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency
<< " Selecting:" << maxdelay);
m_iTsbPdDelay_ms = maxdelay;
}
}
else
{
std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent";
HLOGC(mglog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for sending - " << how_about_agent);
}
// This happens when the HSv5 RESPONDER receives the HSREQ message; it declares
// that the peer INITIATOR will receive the data and informs about its predefined
// latency. We need to maximize this with our setting of the peer's latency and
// record as peer's latency, which will be then sent back with HSRSP.
if (hsv > CUDT::HS_VERSION_UDT4 && IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
// So, PEER uses TSBPD, set the flag.
// NOTE: it doesn't matter, if AGENT uses TSBPD.
m_bPeerTsbPd = true;
// SRT_HS_LATENCY_RCV is the value that the peer declares as to be
// used by it when receiving data. We take this as a peer's value,
// and select the maximum of this one and our proposed latency for the peer.
int peer_decl_latency = SRT_HS_LATENCY_RCV::unwrap(latencystr);
int maxdelay = std::max(m_iPeerTsbPdDelay_ms, peer_decl_latency);
HLOGC(mglog.Debug,
log << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay_ms << " Peer:" << peer_decl_latency
<< " Selecting:" << maxdelay);
m_iPeerTsbPdDelay_ms = maxdelay;
}
else
{
std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent";
HLOGC(mglog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for receiving - " << how_about_agent);
}
if (hsv > CUDT::HS_VERSION_UDT4)
{
// This is HSv5, do the same things as required for the sending party in HSv4,
// as in HSv5 this can also be a sender.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP))
{
// Too late packets dropping feature supported
m_bPeerTLPktDrop = true;
}
if (IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT))
{
// Peer will send Periodic NAK Reports
m_bPeerNakReport = true;
}
}
return SRT_CMD_HSRSP;
}
int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t len, uint32_t ts, int hsv)
{
// XXX Check for mis-version
// With HSv4 we accept only version less than 1.2.0
if (hsv == CUDT::HS_VERSION_UDT4 && srtdata[SRT_HS_VERSION] >= SRT_VERSION_FEAT_HSv5)
{
LOGC(mglog.Error, log << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable.");
return SRT_CMD_NONE;
}
if (len < SRT_CMD_HSRSP_MINSZ)
{
/* Packet smaller than minimum compatible packet size */
LOGF(mglog.Error, "HSRSP/rcv: cmd=%d(HSRSP) len=%" PRIzu " invalid", SRT_CMD_HSRSP, len);
return SRT_CMD_NONE;
}
// Set this start time in the beginning, regardless as to whether TSBPD is being
// used or not. This must be done in the Initiator as well as Responder. In case when
// agent is sender only (HSv4) this value simply won't be used.
/*
* Compute peer StartTime in our time reference
* This takes time zone, time drift into account.
* Also includes current packet transit time (rtt/2)
*/
#if 0 // Debug PeerStartTime if not 1st HS packet
{
uint64_t oldPeerStartTime = m_ullRcvPeerStartTime;
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
if (oldPeerStartTime) {
LOGC(mglog.Note, log << "rcvSrtMsg: 2nd PeerStartTime diff=" <<
(m_ullRcvPeerStartTime - oldPeerStartTime) << " usec");
}
}
#else
m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts);
#endif
m_lPeerSrtVersion = srtdata[SRT_HS_VERSION];
m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS];
HLOGF(mglog.Debug,
"HSRSP/rcv: Version: %s Flags: SND:%08X (%s)",
SrtVersionString(m_lPeerSrtVersion).c_str(),
m_lPeerSrtFlags,
SrtFlagString(m_lPeerSrtFlags).c_str());
if (hsv == CUDT::HS_VERSION_UDT4)
{
// The old HSv4 way: extract just one value and put it under peer.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
// TsbPd feature enabled
m_bPeerTsbPd = true;
m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]);
HLOGC(mglog.Debug,
log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms
<< " (Agent: declared:" << m_iTsbPdDelay_ms << " rcv:" << m_iTsbPdDelay_ms << ")");
}
// TSBPDSND isn't set in HSv4 by the RESPONDER, because HSv4 RESPONDER is always RECEIVER.
}
else
{
// HSv5 way: extract the receiver latency and sender latency, if used.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
// TsbPd feature enabled
m_bPeerTsbPd = true;
m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]);
HLOGC(mglog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms");
}
else
{
HLOGC(mglog.Debug, log << "HSRSP/rcv: Peer (responder) DOES NOT USE latency");
}
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND))
{
if (!m_bTsbPd)
{
LOGC(mglog.Warn,
log << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD.");
}
else
{
// Take this value as a good deal. In case when the Peer did not "correct" the latency
// because it has TSBPD turned off, just stay with the present value defined in options.
m_iTsbPdDelay_ms = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]);
HLOGC(mglog.Debug, log << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms");
}
}
}
if ((m_lSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP))
{
// Too late packets dropping feature supported
m_bPeerTLPktDrop = true;
}
if ((m_lSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT))
{
// Peer will send Periodic NAK Reports
m_bPeerNakReport = true;
}
if (m_lSrtVersion >= SrtVersion(1, 2, 0))
{
if (IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG))
{
// Peer will use REXMIT flag in packet retransmission.
m_bPeerRexmitFlag = true;
HLOGP(mglog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer.");
}
else
{
HLOGP(mglog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT");
}
}
else
{
HLOGF(mglog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag");
}
handshakeDone();
return SRT_CMD_NONE;
}
// This function is called only when the URQ_CONCLUSION handshake has been received from the peer.
bool CUDT::interpretSrtHandshake(const CHandShake &hs,
const CPacket & hspkt,
uint32_t *out_data SRT_ATR_UNUSED,
size_t * out_len)
{
// Initialize out_len to 0 to handle the unencrypted case
if (out_len)
*out_len = 0;
// The version=0 statement as rejection is used only since HSv5.
// The HSv4 sends the AGREEMENT handshake message with version=0, do not misinterpret it.
if (m_ConnRes.m_iVersion > HS_VERSION_UDT4 && hs.m_iVersion == 0)
{
m_RejectReason = SRT_REJ_PEER;
LOGC(mglog.Error, log << "HS VERSION = 0, meaning the handshake has been rejected.");
return false;
}
if (hs.m_iVersion < HS_VERSION_SRT1)
return true; // do nothing
// Anyway, check if the handshake contains any extra data.
if (hspkt.getLength() <= CHandShake::m_iContentSize)
{
m_RejectReason = SRT_REJ_ROGUE;
// This would mean that the handshake was at least HSv5, but somehow no extras were added.
// Dismiss it then, however this has to be logged.
LOGC(mglog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!");
return false;
}
// We still believe it should work, let's check the flags.
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType);
if (ext_flags == 0)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!");
return false;
}
HLOGC(mglog.Debug,
log << "HS VERSION=" << hs.m_iVersion << " EXTENSIONS: " << CHandShake::ExtensionFlagStr(ext_flags));
// Ok, now find the beginning of an int32_t array that follows the UDT handshake.
uint32_t *p = reinterpret_cast<uint32_t *>(hspkt.m_pcData + CHandShake::m_iContentSize);
size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0
if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ))
{
HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting HSREQ/RSP type extension");
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // this is ONE SHOT LOOP
{
int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next));
size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_HSREQ)
{
// Set is the size as it should, then give it for interpretation for
// the proper function.
if (blocklen < SRT_HS__SIZE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS__SIZE
<< ")");
return false; // don't interpret
}
int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1);
// Interpreted? Then it should be responded with SRT_CMD_HSRSP.
if (rescmd != SRT_CMD_HSRSP)
{
// m_RejectReason already set
LOGC(mglog.Error,
log << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd);
return false;
}
handshakeDone();
updateAfterSrtHandshake(SRT_CMD_HSREQ, HS_VERSION_SRT1);
}
else if (cmd == SRT_CMD_HSRSP)
{
// Set is the size as it should, then give it for interpretation for
// the proper function.
if (blocklen < SRT_HS__SIZE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS__SIZE
<< ")");
return false; // don't interpret
}
int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1);
// Interpreted? Then it should be responded with SRT_CMD_NONE.
// (nothing to be responded for HSRSP, unless there was some kinda problem)
if (rescmd != SRT_CMD_NONE)
{
// Just formally; the current code doesn't seem to return anything else.
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd);
return false;
}
handshakeDone();
updateAfterSrtHandshake(SRT_CMD_HSRSP, HS_VERSION_SRT1);
}
else if (cmd == SRT_CMD_NONE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error, log << "interpretSrtHandshake: 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.
return false;
}
else
{
// Any other kind of message extracted. Search on.
length -= (next - begin);
begin = next;
if (begin)
continue;
}
break;
}
}
HLOGC(mglog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ");
// Now check the encrypted
bool encrypted = false;
if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ))
{
HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting KMREQ/RSP type extension");
#ifdef SRT_ENABLE_ENCRYPTION
if (!m_pCryptoControl->hasPassphrase())
{
if (m_bOPT_StrictEncryption)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(
mglog.Error,
log << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per strict requirement");
return false;
}
LOGC(mglog.Error,
log << "HS KMREQ: Peer declares encryption, but agent does not - still allowing connection.");
// Still allow for connection, and allow Agent to send unencrypted stream to the peer.
// Also normally allow the key to be processed; worst case it will send the failure response.
}
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // This is one shot loop, unless REPEATED by 'continue'.
{
int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next));
HLOGC(mglog.Debug,
log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd));
size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_KMREQ)
{
if (!out_data || !out_len)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(mglog.Fatal, log << "IPE: HS/KMREQ extracted without passing target buffer!");
return false;
}
int res =
m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, out_data, Ref(*out_len), HS_VERSION_SRT1);
if (res != SRT_CMD_KMRSP)
{
m_RejectReason = SRT_REJ_IPE;
// Something went wrong.
HLOGC(mglog.Debug,
log << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res);
return false;
}
if (*out_len == 1)
{
// This means that there was an abnormal encryption situation occurred.
// This is inacceptable in case of strict encryption.
if (m_bOPT_StrictEncryption)
{
if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADSECRET)
{
m_RejectReason = SRT_REJ_BADSECRET;
}
else
{
m_RejectReason = SRT_REJ_UNSECURE;
}
LOGC(mglog.Error,
log << "interpretSrtHandshake: KMREQ result abnornal - rejecting per strict encryption");
return false;
}
}
encrypted = true;
}
else if (cmd == SRT_CMD_KMRSP)
{
int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, HS_VERSION_SRT1);
if (m_bOPT_StrictEncryption && res == -1)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(mglog.Error, log << "KMRSP failed - rejecting connection as per strict encryption.");
return false;
}
encrypted = true;
}
else if (cmd == SRT_CMD_NONE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error, log << "HS KMREQ expected - none found!");
return false;
}
else
{
HLOGC(mglog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
if (NextExtensionBlock(Ref(begin), next, Ref(length)))
continue;
}
break;
}
#else
// When encryption is not enabled at compile time, behave as if encryption wasn't set,
// so accordingly to StrictEncryption flag.
if (m_bOPT_StrictEncryption)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(mglog.Error,
log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting "
"per strict requirement");
return false;
}
LOGC(mglog.Error,
log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing "
"connection.");
encrypted = true;
#endif
}
bool have_congctl = false;
bool have_filter = false;
string agsm = m_CongCtl.selected_name();
if (agsm == "")
{
agsm = "live";
m_CongCtl.select("live");
}
if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG))
{
HLOGC(mglog.Debug, log << "interpretSrtHandshake: extracting various CONFIG extensions");
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // This is one shot loop, unless REPEATED by 'continue'.
{
int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next));
HLOGC(mglog.Debug,
log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd));
const size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_SID)
{
if (!bytelen || bytelen > MAX_SID_LENGTH)
{
LOGC(mglog.Error,
log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +MAX_SID_LENGTH
<< " - PROTOCOL ERROR, REJECTING");
return false;
}
// Copied through a cleared array. This is because the length is aligned to 4
// where the padding is filled by zero bytes. For the case when the string is
// exactly of a 4-divisible length, we make a big array with maximum allowed size
// filled with zeros. Copying to this array should then copy either only the valid
// characters of the string (if the lenght is divisible by 4), or the string with
// padding zeros. In all these cases in the resulting array we should have all
// subsequent characters of the string plus at least one '\0' at the end. This will
// make it a perfect NUL-terminated string, to be used to initialize a string.
char target[MAX_SID_LENGTH + 1];
memset(target, 0, MAX_SID_LENGTH + 1);
memcpy(target, begin + 1, bytelen);
// Un-swap on big endian machines
ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen);
m_sStreamName = target;
HLOGC(mglog.Debug,
log << "CONNECTOR'S REQUESTED SID [" << m_sStreamName << "] (bytelen=" << bytelen
<< " blocklen=" << blocklen << ")");
}
else if (cmd == SRT_CMD_CONGESTION)
{
if (have_congctl)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error, log << "CONGCTL BLOCK REPEATED!");
return false;
}
if (!bytelen || bytelen > MAX_SID_LENGTH)
{
LOGC(mglog.Error,
log << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > "
<< +MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING");
return false;
}
// Declare that congctl has been received
have_congctl = true;
char target[MAX_SID_LENGTH + 1];
memset(target, 0, MAX_SID_LENGTH + 1);
memcpy(target, begin + 1, bytelen);
// Un-swap on big endian machines
ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen);
string sm = target;
// As the congctl has been declared by the peer,
// check if your congctl is compatible.
// sm cannot be empty, but the agent's sm can be empty meaning live.
if (sm != agsm)
{
m_RejectReason = SRT_REJ_CONGESTION;
LOGC(mglog.Error,
log << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'");
return false;
}
HLOGC(mglog.Debug,
log << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen
<< ")");
}
else if (cmd == SRT_CMD_FILTER)
{
if (have_filter)
{
m_RejectReason = SRT_REJ_FILTER;
LOGC(mglog.Error, log << "FILTER BLOCK REPEATED!");
return false;
}
// Declare that filter has been received
have_filter = true;
// XXX This is the maximum string, but filter config
// shall be normally limited somehow, especially if used
// together with SID!
char target[MAX_SID_LENGTH + 1];
memset(target, 0, MAX_SID_LENGTH + 1);
memcpy(target, begin + 1, bytelen);
string fltcfg = target;
HLOGC(mglog.Debug,
log << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen
<< ")");
if (!checkApplyFilterConfig(fltcfg))
{
LOGC(mglog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected");
return false;
}
}
else if (cmd == SRT_CMD_NONE)
{
break;
}
else
{
// Found some block that is not interesting here. Skip this and get the next one.
HLOGC(mglog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
}
if (!NextExtensionBlock(Ref(begin), next, Ref(length)))
break;
}
}
// Post-checks
// Check if peer declared encryption
if (!encrypted && m_CryptoSecret.len > 0)
{
if (m_bOPT_StrictEncryption)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(mglog.Error,
log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per strict "
"requirement.");
return false;
}
LOGC(mglog.Error,
log << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets "
"from Peer).");
// This is required so that the sender is still allowed to send data, when encryption is required,
// just this will be for waste because the receiver won't decrypt them anyway.
m_pCryptoControl->createFakeSndContext();
m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Because Peer did not send KMX, though Agent has pw
m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Because Peer has no PW, as has sent no KMREQ.
return true;
}
// If agent has set some nondefault congctl, then congctl is expected from the peer.
if (agsm != "live" && !have_congctl)
{
m_RejectReason = SRT_REJ_CONGESTION;
LOGC(mglog.Error,
log << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live').");
return false;
}
// Ok, finished, for now.
return true;
}
bool CUDT::checkApplyFilterConfig(const std::string &confstr)
{
SrtFilterConfig cfg;
if (!ParseFilterConfig(confstr, cfg))
return false;
// Now extract the type, if present, and
// check if you have this type of corrector available.
if (!PacketFilter::correctConfig(cfg))
return false;
// Now parse your own string, if you have it.
if (m_OPT_PktFilterConfigString != "")
{
// - for rendezvous, both must be exactly the same, or only one side specified.
if (m_bRendezvous && m_OPT_PktFilterConfigString != confstr)
{
return false;
}
SrtFilterConfig mycfg;
if (!ParseFilterConfig(m_OPT_PktFilterConfigString, mycfg))
return false;
// Check only if both have set a filter of the same type.
if (mycfg.type != cfg.type)
return false;
// If so, then:
// - for caller-listener configuration, accept the listener version.
if (m_SrtHsSide == HSD_INITIATOR)
{
// This is a caller, this should apply all parameters received
// from the listener, forcefully.
for (map<string, string>::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x)
{
mycfg.parameters[x->first] = x->second;
}
}
else
{
// On a listener, only apply those that you haven't set
for (map<string, string>::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x)
{
if (!mycfg.parameters.count(x->first))
mycfg.parameters[x->first] = x->second;
}
}
HLOGC(mglog.Debug,
log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters)
<< " FORGN: " << Printable(cfg.parameters));
ostringstream myos;
myos << mycfg.type;
for (map<string, string>::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x)
{
myos << "," << x->first << ":" << x->second;
}
m_OPT_PktFilterConfigString = myos.str();
HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Effective config: " << m_OPT_PktFilterConfigString);
}
else
{
// Take the foreign configuration as a good deal.
HLOGC(mglog.Debug, log << "checkApplyFilterConfig: Good deal config: " << m_OPT_PktFilterConfigString);
m_OPT_PktFilterConfigString = confstr;
}
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size;
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
{
LOGC(mglog.Warn,
log << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to "
<< efc_max_payload_size << " bytes");
m_zOPT_ExpPayloadSize = efc_max_payload_size;
}
return true;
}
void CUDT::startConnect(const sockaddr *serv_addr, int32_t forced_isn)
{
CGuard cg(m_ConnectionLock);
HLOGC(mglog.Debug, log << "startConnect: -> " << SockaddrToString(serv_addr) << "...");
if (!m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
if (m_bListening)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// record peer/server address
delete m_pPeerAddr;
m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6;
memcpy(m_pPeerAddr, serv_addr, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
// register this socket in the rendezvous queue
// RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this
// function
#ifdef SRT_ENABLE_CONNTIMEO
uint64_t ttl = m_iConnTimeOut * uint64_t(1000);
#else
uint64_t ttl = 3000000;
#endif
// XXX DEBUG
// ttl = 0x1000000000000000;
// XXX
if (m_bRendezvous)
ttl *= 10;
ttl += CTimer::getTime();
m_pRcvQueue->registerConnector(m_SocketID, this, m_iIPversion, serv_addr, ttl);
// The m_iType is used in the INDUCTION for nothing. This value is only regarded
// in CONCLUSION handshake, however this must be created after the handshake version
// is already known. UDT_DGRAM is the value that was the only valid in the old SRT
// with HSv4 (it supported only live transmission), for HSv5 it will be changed to
// handle handshake extension flags.
m_ConnReq.m_iType = UDT_DGRAM;
// This is my current configuration
if (m_bRendezvous)
{
// For rendezvous, use version 5 in the waveahand and the cookie.
// In case when you get the version 4 waveahand, simply switch to
// the legacy HSv4 rendezvous and this time send version 4 CONCLUSION.
// The HSv4 client simply won't check the version nor the cookie and it
// will be sending its waveahands with version 4. Only when the party
// has sent version 5 waveahand should the agent continue with HSv5
// rendezvous.
m_ConnReq.m_iVersion = HS_VERSION_SRT1;
// m_ConnReq.m_iVersion = HS_VERSION_UDT4; // <--- Change in order to do regression test.
m_ConnReq.m_iReqType = URQ_WAVEAHAND;
m_ConnReq.m_iCookie = bake(serv_addr);
// This will be also passed to a HSv4 rendezvous, but fortunately the old
// SRT didn't read this field from URQ_WAVEAHAND message, only URQ_CONCLUSION.
m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_iSndCryptoKeyLen);
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
HLOGC(mglog.Debug,
log << "startConnect (rnd): " << (whether ? "" : "NOT ")
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
m_RdvState = CHandShake::RDV_WAVING;
m_SrtHsSide = HSD_DRAW; // initially not resolved.
}
else
{
// For caller-listener configuration, set the version 4 for INDUCTION
// due to a serious problem in UDT code being also in the older SRT versions:
// the listener peer simply sents the EXACT COPY of the caller's induction
// handshake, except the cookie, which means that when the caller sents version 5,
// the listener will respond with version 5, which is a false information. Therefore
// HSv5 clients MUST send HS_VERSION_UDT4 from the caller, regardless of currently
// supported handshake version.
//
// The HSv5 listener should only respond with INDUCTION with m_iVersion == HS_VERSION_SRT1.
m_ConnReq.m_iVersion = HS_VERSION_UDT4;
m_ConnReq.m_iReqType = URQ_INDUCTION;
m_ConnReq.m_iCookie = 0;
m_RdvState = CHandShake::RDV_INVALID;
}
m_ConnReq.m_iMSS = m_iMSS;
m_ConnReq.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
m_ConnReq.m_iID = m_SocketID;
CIPAddress::ntop(serv_addr, m_ConnReq.m_piPeerIP, m_iIPversion);
if (forced_isn == 0)
{
// Random Initial Sequence Number (normal mode)
srand((unsigned int)CTimer::getTime());
m_iISN = m_ConnReq.m_iISN = (int32_t)(CSeqNo::m_iMaxSeqNo * (double(rand()) / RAND_MAX));
}
else
{
// Predefined ISN (for debug purposes)
m_iISN = m_ConnReq.m_iISN = forced_isn;
}
m_iLastDecSeq = m_iISN - 1;
m_iSndLastAck = m_iISN;
m_iSndLastDataAck = m_iISN;
m_iSndLastFullAck = m_iISN;
m_iSndCurrSeqNo = m_iISN - 1;
m_iSndLastAck2 = m_iISN;
m_ullSndLastAck2Time = CTimer::getTime();
// Inform the server my configurations.
CPacket reqpkt;
reqpkt.setControl(UMSG_HANDSHAKE);
reqpkt.allocate(m_iMaxSRTPayloadSize);
// 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:
// - delete this memory
// - assign to m_pcData.
// If you use only manual assignment to m_pCData, this is then manual
// allocation and so it won't be deallocated in the destructor.
//
// (Desired would be to disallow modification of m_pcData outside the
// control of methods.)
// ID = 0, connection request
reqpkt.m_iID = 0;
size_t hs_size = m_iMaxSRTPayloadSize;
m_ConnReq.store_to(reqpkt.m_pcData, Ref(hs_size));
// Note that CPacket::allocate() sets also the size
// to the size of the allocated buffer, which not
// necessarily is to be the size of the data.
reqpkt.setLength(hs_size);
uint64_t now = CTimer::getTime();
reqpkt.m_iTimeStamp = int32_t(now - m_stats.startTime);
HLOGC(mglog.Debug,
log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (" << now << "). SENDING HS: " << m_ConnReq.show());
/*
* Race condition if non-block connect response thread scheduled before we set m_bConnecting to true?
* Connect response will be ignored and connecting will wait until timeout.
* Maybe m_ConnectionLock handling problem? Not used in CUDT::connect(const CPacket& response)
*/
m_llLastReqTime = now;
m_bConnecting = true;
m_pSndQueue->sendto(serv_addr, reqpkt);
//
///
//// ---> CONTINUE TO: <PEER>.CUDT::processConnectRequest()
/// (Take the part under condition: hs.m_iReqType == URQ_INDUCTION)
//// <--- RETURN WHEN: m_pSndQueue->sendto() is called.
//// .... SKIP UNTIL m_pRcvQueue->recvfrom() HERE....
//// (the first "sendto" will not be called due to being too early)
///
//
// asynchronous connect, return immediately
if (!m_bSynRecving)
{
HLOGC(mglog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker");
return;
}
// Wait for the negotiated configurations from the peer side.
// This packet only prepares the storage where we will read the
// next incoming packet.
CPacket response;
response.setControl(UMSG_HANDSHAKE);
response.allocate(m_iMaxSRTPayloadSize);
CUDTException e;
EConnectStatus cst = CONN_CONTINUE;
while (!m_bClosing)
{
int64_t tdiff = CTimer::getTime() - m_llLastReqTime;
// avoid sending too many requests, at most 1 request per 250ms
// SHORT VERSION:
// The immediate first run of this loop WILL SKIP THIS PART, so
// the processing really begins AFTER THIS CONDITION.
//
// Note that some procedures inside may set m_llLastReqTime to 0,
// which will result of this condition to trigger immediately in
// the next iteration.
if (tdiff > 250000)
{
HLOGC(mglog.Debug,
log << "startConnect: LOOP: time to send (" << tdiff << " > 250000). size=" << reqpkt.getLength());
if (m_bRendezvous)
reqpkt.m_iID = m_ConnRes.m_iID;
now = CTimer::getTime();
#if ENABLE_HEAVY_LOGGING
{
CHandShake debughs;
debughs.load_from(reqpkt.m_pcData, reqpkt.getLength());
HLOGC(mglog.Debug,
log << CONID() << "startConnect: REQ-TIME HIGH (" << now
<< "). cont/sending HS to peer: " << debughs.show());
}
#endif
m_llLastReqTime = now;
reqpkt.m_iTimeStamp = int32_t(now - m_stats.startTime);
m_pSndQueue->sendto(serv_addr, reqpkt);
}
else
{
HLOGC(mglog.Debug, log << "startConnect: LOOP: too early to send - " << tdiff << " < 250000");
}
cst = CONN_CONTINUE;
response.setLength(m_iMaxSRTPayloadSize);
if (m_pRcvQueue->recvfrom(m_SocketID, Ref(response)) > 0)
{
HLOGC(mglog.Debug, log << CONID() << "startConnect: got response for connect request");
cst = processConnectResponse(response, &e, true /*synchro*/);
HLOGC(mglog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst));
// Expected is that:
// - the peer responded with URQ_INDUCTION + cookie. This above function
// should check that and craft the URQ_CONCLUSION handshake, in which
// case this function returns CONN_CONTINUE. As an extra action taken
// for that case, we set the SECURING mode if encryption requested,
// and serialize again the handshake, possibly together with HS extension
// blocks, if HSv5 peer responded. The serialized handshake will be then
// sent again, as the loop is repeated.
// - the peer responded with URQ_CONCLUSION. This handshake was accepted
// as a connection, and for >= HSv5 the HS extension blocks have been
// also read and interpreted. In this case this function returns:
// - CONN_ACCEPT, if everything was correct - break this loop and return normally
// - CONN_REJECT in case of any problems with the delivered handshake
// (incorrect data or data conflict) - throw error exception
// - the peer responded with any of URQ_ERROR_*. - throw error exception
//
// The error exception should make the API connect() function fail, if blocking
// or mark the failure for that socket in epoll, if non-blocking.
if (cst == CONN_RENDEZVOUS)
{
// When this function returned CONN_RENDEZVOUS, this requires
// very special processing for the Rendezvous-v5 algorithm. This MAY
// involve also preparing a new handshake form, also interpreting the
// SRT handshake extension and crafting SRT handshake extension for the
// peer, which should be next sent. When this function returns CONN_CONTINUE,
// it means that it has done all that was required, however none of the below
// things has to be done (this function will do it by itself if needed).
// Otherwise the handshake rolling can be interrupted and considered complete.
cst = processRendezvous(Ref(reqpkt), response, serv_addr, true /*synchro*/, RST_OK);
if (cst == CONN_CONTINUE)
continue;
break;
}
if (cst == CONN_REJECT)
sendCtrl(UMSG_SHUTDOWN);
if (cst != CONN_CONTINUE && cst != CONN_CONFUSED)
break; // --> OUTSIDE-LOOP
// IMPORTANT
// [[using assert(m_pCryptoControl != nullptr)]];
// new request/response should be sent out immediately on receving a response
HLOGC(mglog.Debug,
log << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) << ", REQ-TIME: LOW.");
m_llLastReqTime = 0;
// 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
// 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);
HLOGC(mglog.Debug, log << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength());
// NOTE: BUGFIX: SERIALIZE AGAIN.
// The original UDT code didn't do it, so it was theoretically
// turned into conclusion, but was sending still the original
// induction handshake challenge message. It was working only
// thanks to that simultaneously there were being sent handshake
// messages from a separate thread (CSndQueue::worker) from
// RendezvousQueue, this time serialized properly, which caused
// that with blocking mode there was a kinda initial "drunk
// passenger with taxi driver talk" until the RendezvousQueue sends
// (when "the time comes") the right CONCLUSION handshake
// challenge message.
//
// Now that this is fixed, the handshake messages from RendezvousQueue
// are sent only when there is a rendezvous mode or non-blocking mode.
if (!createSrtHandshake(Ref(reqpkt), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0))
{
LOGC(mglog.Error, log << "createSrtHandshake failed - REJECTING.");
cst = CONN_REJECT;
break;
}
// These last 2 parameters designate the buffer, which is in use only for SRT_CMD_KMRSP.
// If m_ConnReq.m_iVersion == HS_VERSION_UDT4, this function will do nothing,
// except just serializing the UDT handshake.
// The trick is that the HS challenge is with version HS_VERSION_UDT4, but the
// listener should respond with HS_VERSION_SRT1, if it is HSv5 capable.
}
HLOGC(mglog.Debug,
log << "startConnect: timeout from Q:recvfrom, looping again; cst=" << ConnectStatusStr(cst));
#if ENABLE_HEAVY_LOGGING
// Non-fatal assertion
if (cst == CONN_REJECT) // Might be returned by processRendezvous
{
LOGC(mglog.Error,
log << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!");
break;
}
#endif
if (CTimer::getTime() > ttl)
{
// timeout
e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0);
break;
}
}
// <--- OUTSIDE-LOOP
// Here will fall the break when not CONN_CONTINUE.
// CONN_RENDEZVOUS is handled by processRendezvous.
// CONN_ACCEPT will skip this and pass on.
if (cst == CONN_REJECT)
{
e = CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
if (e.getErrorCode() == 0)
{
if (m_bClosing) // if the socket is closed before connection...
e = CUDTException(MJ_SETUP); // XXX NO MN ?
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_bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check
e = CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
if (e.getErrorCode() != 0)
{
m_bConnecting = false;
// The process is to be abnormally terminated, remove the connector
// now because most likely no other processing part has done anything with it.
m_pRcvQueue->removeConnector(m_SocketID);
throw e;
}
HLOGC(mglog.Debug, log << CONID() << "startConnect: handshake exchange succeeded");
// Parameters at the end.
HLOGC(mglog.Debug,
log << "startConnect: END. Parameters:"
" mss="
<< m_iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize()
<< " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iRTT << " bw=" << m_iBandwidth);
}
// Asynchronous connection
EConnectStatus CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT
{
EConnectStatus cst = CONN_CONTINUE;
CUDTException e;
CGuard cg(m_ConnectionLock); // FIX
HLOGC(mglog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing");
cst = processConnectResponse(pkt, &e, false);
HLOGC(mglog.Debug,
log << CONID() << "processAsyncConnectResponse: response processing result: " << ConnectStatusStr(cst)
<< "REQ-TIME LOW to enforce immediate response");
m_llLastReqTime = 0;
return cst;
}
bool CUDT::processAsyncConnectRequest(EReadStatus rst,
EConnectStatus cst,
const CPacket & response,
const sockaddr *serv_addr)
{
// IMPORTANT!
// This function is called, still asynchronously, but in the order
// of call just after the call to the above processAsyncConnectResponse.
// This should have got the original value returned from
// processConnectResponse through processAsyncConnectResponse.
CPacket request;
request.setControl(UMSG_HANDSHAKE);
request.allocate(m_iMaxSRTPayloadSize);
uint64_t now = CTimer::getTime();
request.m_iTimeStamp = int(now - m_stats.startTime);
HLOGC(mglog.Debug,
log << "processAsyncConnectRequest: REQ-TIME: HIGH (" << now << "). Should prevent too quick responses.");
m_llLastReqTime = now;
// ID = 0, connection request
request.m_iID = !m_bRendezvous ? 0 : m_ConnRes.m_iID;
bool status = true;
if (cst == CONN_RENDEZVOUS)
{
HLOGC(mglog.Debug, log << "processAsyncConnectRequest: passing to processRendezvous");
cst = processRendezvous(Ref(request), response, serv_addr, false /*asynchro*/, rst);
if (cst == CONN_ACCEPT)
{
HLOGC(mglog.Debug,
log << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. "
"Done.");
return true;
}
if (cst != CONN_CONTINUE)
{
// processRendezvous already set the reject reason
LOGC(mglog.Error,
log << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further.");
status = false;
}
}
else if (cst == CONN_REJECT)
{
// m_RejectReason already set at worker_ProcessAddressedPacket.
LOGC(mglog.Error,
log << "processAsyncConnectRequest: REJECT reported from HS processing, not processing further.");
return false;
}
else
{
// (this procedure will be also run for HSv4 rendezvous)
HLOGC(mglog.Debug, log << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength());
if (!createSrtHandshake(Ref(request), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0))
{
// All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired".
LOGC(mglog.Error, log << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing.");
status = false;
}
else
{
HLOGC(mglog.Debug,
log << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType)
<< " to socket " << request.m_iID << " size=" << request.getLength());
}
}
if (!status)
{
return false;
/* XXX Shouldn't it send a single response packet for the rejection?
// Set the version to 0 as "handshake rejection" status and serialize it
CHandShake zhs;
size_t size = request.getLength();
zhs.store_to(request.m_pcData, Ref(size));
request.setLength(size);
*/
}
HLOGC(mglog.Debug, log << "processAsyncConnectRequest: sending request packet, setting REQ-TIME HIGH.");
m_llLastReqTime = CTimer::getTime();
m_pSndQueue->sendto(serv_addr, request);
return status;
}
void CUDT::cookieContest()
{
if (m_SrtHsSide != HSD_DRAW)
return;
HLOGC(mglog.Debug, log << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie);
if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0)
{
// 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;
}
// 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.
int better_cookie = m_ConnReq.m_iCookie - m_ConnRes.m_iCookie;
if (better_cookie > 0)
{
m_SrtHsSide = HSD_INITIATOR;
return;
}
if (better_cookie < 0)
{
m_SrtHsSide = HSD_RESPONDER;
return;
}
// 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.
// 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.
m_SrtHsSide = HSD_DRAW;
}
EConnectStatus CUDT::processRendezvous(
ref_t<CPacket> reqpkt, const CPacket &response, const sockaddr *serv_addr, bool synchro, EReadStatus rst)
{
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
HLOGC(mglog.Debug, log << "processRendezvous: already in CONNECTED state.");
return CONN_ACCEPT;
}
uint32_t kmdata[SRTDATA_MAXSIZE];
size_t kmdatasize = SRTDATA_MAXSIZE;
CPacket &rpkt = *reqpkt;
cookieContest();
// We know that the other side was contacted and the other side has sent
// the handshake message - we know then both cookies. If it's a draw, it's
// a very rare case of creating identical cookies.
if (m_SrtHsSide == HSD_DRAW)
{
m_RejectReason = SRT_REJ_RDVCOOKIE;
LOGC(mglog.Error,
log << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute.");
return CONN_REJECT;
}
UDTRequestType rsp_type = URQ_FAILURE_TYPES; // just to track uninitialized errors
// We can assume that the Handshake packet received here as 'response'
// is already serialized in m_ConnRes. Check extra flags that are meaningful
// for further processing here.
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
bool needs_extension = ext_flags != 0; // Initial value: received HS has extensions.
bool needs_hsrsp;
rendezvousSwitchState(Ref(rsp_type), Ref(needs_extension), Ref(needs_hsrsp));
if (rsp_type > URQ_FAILURE_TYPES)
{
m_RejectReason = RejectReasonForURQ(rsp_type);
HLOGC(mglog.Debug,
log << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type));
return CONN_REJECT;
}
checkUpdateCryptoKeyLen("processRendezvous", m_ConnRes.m_iType);
// We have three possibilities here as it comes to HSREQ extensions:
// 1. The agent is loser in attention state, it sends EMPTY conclusion (without extensions)
// 2. The agent is loser in initiated state, it interprets incoming HSREQ and creates HSRSP
// 3. The agent is winner in attention or fine state, it sends HSREQ extension
m_ConnReq.m_iReqType = rsp_type;
m_ConnReq.m_extension = needs_extension;
// This must be done before prepareConnectionObjects().
applyResponseSettings();
// This must be done before interpreting and creating HSv5 extensions.
if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, 0))
{
// m_RejectReason already handled
HLOGC(mglog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects.");
return CONN_REJECT;
}
// Case 2.
if (needs_hsrsp)
{
// This means that we have received HSREQ extension with the handshake, so we need to interpret
// it and craft the response.
if (rst == RST_OK)
{
// We have JUST RECEIVED packet in this session (not that this is called as periodic update).
// Sanity check
m_llLastReqTime = 0;
if (response.getLength() == size_t(-1))
{
m_RejectReason = SRT_REJ_IPE;
LOGC(mglog.Fatal,
log << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)");
return CONN_REJECT;
}
if (!interpretSrtHandshake(m_ConnRes, response, kmdata, &kmdatasize))
{
HLOGC(mglog.Debug,
log << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW.");
return CONN_REJECT;
}
// Pass on, inform about the shortened response-waiting period.
HLOGC(mglog.Debug, log << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately.");
}
else
{
// If the last CONCLUSION message didn't contain the KMX extension, there's
// no key recorded yet, so it can't be extracted. Mark this kmdatasize empty though.
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ))
{
// This is a periodic handshake update, so you need to extract the KM data from the
// first message, provided that it is there.
size_t msgsize = m_pCryptoControl->getKmMsg_size(0);
if (msgsize == 0)
{
switch (m_pCryptoControl->m_RcvKmState)
{
// If the KMX process ended up with a failure, the KMX is not recorded.
// In this case as the KMRSP answer the "failure status" should be crafted.
case SRT_KM_S_NOSECRET:
case SRT_KM_S_BADSECRET:
{
HLOGC(mglog.Debug,
log << "processRendezvous: No KMX recorded, status = NOSECRET. Respond with NOSECRET.");
// Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case,
// that is, copy the NOSECRET code into KMX message.
memcpy(kmdata, &m_pCryptoControl->m_RcvKmState, sizeof(int32_t));
kmdatasize = 1;
}
break;
default:
// Remaining values:
// UNSECURED: should not fall here at alll
// SECURING: should not happen in HSv5
// SECURED: should have received the recorded KMX correctly (getKmMsg_size(0) > 0)
{
m_RejectReason = SRT_REJ_IPE;
// Remaining situations:
// - password only on this site: shouldn't be considered to be sent to a no-password site
LOGC(mglog.Error,
log << "processRendezvous: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV="
<< KmStateStr(m_pCryptoControl->m_RcvKmState)
<< " SND=" << KmStateStr(m_pCryptoControl->m_SndKmState));
return CONN_REJECT;
}
break;
}
}
else
{
kmdatasize = msgsize / 4;
if (msgsize > kmdatasize * 4)
{
// Sanity check
LOGC(mglog.Error, log << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize);
memset(kmdata + (kmdatasize * 4), 0, msgsize - (kmdatasize * 4));
++kmdatasize;
}
HLOGC(mglog.Debug,
log << "processRendezvous: getting KM DATA from the fore-recorded KMX from KMREQ, size="
<< kmdatasize);
memcpy(kmdata, m_pCryptoControl->getKmMsg_data(0), msgsize);
}
}
else
{
HLOGC(mglog.Debug, log << "processRendezvous: no KMX flag - not extracting KM data for KMRSP");
kmdatasize = 0;
}
}
// No matter the value of needs_extension, the extension is always needed
// when HSREQ was interpreted (to store HSRSP extension).
m_ConnReq.m_extension = true;
HLOGC(mglog.Debug,
log << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize);
rpkt.setLength(m_iMaxSRTPayloadSize);
if (!createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize))
{
HLOGC(mglog.Debug,
log << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW");
m_llLastReqTime = 0;
return CONN_REJECT;
}
// This means that it has received URQ_CONCLUSION with HSREQ, agent is then in RDV_FINE
// state, it sends here URQ_CONCLUSION with HSREQ/KMREQ extensions and it awaits URQ_AGREEMENT.
return CONN_CONTINUE;
}
// Special case: if URQ_AGREEMENT is to be sent, when this side is INITIATOR,
// then it must have received HSRSP, so it must interpret it. Otherwise it would
// end up with URQ_DONE, which means that it is the other side to interpret HSRSP.
if (m_SrtHsSide == HSD_INITIATOR && m_ConnReq.m_iReqType == URQ_AGREEMENT)
{
// The same is done in CUDT::postConnect(), however this section will
// not be done in case of rendezvous. The section in postConnect() is
// predicted to run only in regular CALLER handling.
if (rst != RST_OK || response.getLength() == size_t(-1))
{
// Actually the -1 length would be an IPE, but it's likely that this was reported already.
HLOGC(
mglog.Debug,
log << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)");
}
else
{
HLOGC(mglog.Debug,
log << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension");
if (!interpretSrtHandshake(m_ConnRes, response, 0, 0))
{
// m_RejectReason is already set, so set the reqtype accordingly
m_ConnReq.m_iReqType = URQFailure(m_RejectReason);
}
}
// This should be false, make a kinda assert here.
if (needs_extension)
{
LOGC(mglog.Fatal, log << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS");
m_ConnReq.m_extension = false;
}
}
HLOGC(mglog.Debug,
log << CONID() << "processRendezvous: COOKIES Agent/Peer: " << m_ConnReq.m_iCookie << "/"
<< m_ConnRes.m_iCookie << " HSD:" << (m_SrtHsSide == HSD_INITIATOR ? "initiator" : "responder")
<< " STATE:" << CHandShake::RdvStateStr(m_RdvState) << " ...");
if (rsp_type == URQ_DONE)
{
HLOGC(mglog.Debug, log << "... WON'T SEND any response, both sides considered connected");
}
else
{
HLOGC(mglog.Debug,
log << "... WILL SEND " << RequestTypeStr(rsp_type) << " " << (m_ConnReq.m_extension ? "with" : "without")
<< " SRT HS extensions");
}
// This marks the information for the serializer that
// the SRT handshake extension is required.
// Rest of the data will be filled together with
// serialization.
m_ConnReq.m_extension = needs_extension;
rpkt.setLength(m_iMaxSRTPayloadSize);
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
// When synchro=false, don't lock a mutex for rendezvous queue.
// This is required when this function is called in the
// receive queue worker thread - it would lock itself.
int cst = postConnect(response, true, 0, synchro);
if (cst == CONN_REJECT)
{
// m_RejectReason already set
HLOGC(mglog.Debug, log << "processRendezvous: rejecting due to problems in postConnect.");
return CONN_REJECT;
}
}
// URQ_DONE or URQ_AGREEMENT can be the result if the state is RDV_CONNECTED.
// If URQ_DONE, then there's nothing to be done, when URQ_AGREEMENT then return
// CONN_CONTINUE to make the caller send again the contents if the packet buffer,
// this time with URQ_AGREEMENT message, but still consider yourself connected.
if (rsp_type == URQ_DONE)
{
HLOGC(mglog.Debug, log << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)");
return CONN_ACCEPT;
}
// createSrtHandshake moved here because if the above conditions are satisfied,
// no response is going to be send, so nothing needs to be "created".
// needs_extension here distinguishes between cases 1 and 3.
// NOTE: in case when interpretSrtHandshake was run under the conditions above (to interpret HSRSP),
// then createSrtHandshake below will create only empty AGREEMENT message.
if (!createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0))
{
// m_RejectReason already set
LOGC(mglog.Error, log << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW");
m_llLastReqTime = 0;
return CONN_REJECT;
}
if (rsp_type == URQ_AGREEMENT && m_RdvState == CHandShake::RDV_CONNECTED)
{
// We are using our own serialization method (not the one called after
// processConnectResponse, this is skipped in case when this function
// is called), so we can also send this immediately. Agreement must be
// sent just once and the party must switch into CONNECTED state - in
// contrast to CONCLUSION messages, which should be sent in loop repeatedly.
//
// Even though in theory the AGREEMENT message sent just once may miss
// the target (as normal thing in UDP), this is little probable to happen,
// and this doesn't matter much because even if the other party doesn't
// get AGREEMENT, but will get payload or KEEPALIVE messages, it will
// turn into connected state as well. The AGREEMENT is rather kinda
// catalyzer here and may turn the entity on the right track faster. When
// AGREEMENT is missed, it may have kinda initial tearing.
const uint64_t now = CTimer::getTime();
m_llLastReqTime = now;
rpkt.m_iTimeStamp = int32_t(now - m_stats.startTime);
HLOGC(mglog.Debug,
log << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH ("
<< now << ").");
m_pSndQueue->sendto(serv_addr, rpkt);
return CONN_ACCEPT;
}
if (rst == RST_OK)
{
// the request time must be updated so that the next handshake can be sent out immediately
HLOGC(mglog.Debug,
log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType)
<< " REQ-TIME: LOW to send immediately, consider yourself conencted");
m_llLastReqTime = 0;
}
else
{
HLOGC(mglog.Debug, log << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected");
}
return CONN_CONTINUE;
}
EConnectStatus CUDT::processConnectResponse(const CPacket &response, CUDTException *eout, bool synchro) ATR_NOEXCEPT
{
// NOTE: ASSUMED LOCK ON: m_ConnectionLock.
// this is the 2nd half of a connection request. If the connection is setup successfully this returns 0.
// Returned values:
// - CONN_REJECT: there was some error when processing the response, connection should be rejected
// - CONN_ACCEPT: the handshake is done and finished correctly
// - CONN_CONTINUE: the induction handshake has been processed correctly, and expects CONCLUSION handshake
if (!m_bConnecting)
return CONN_REJECT;
// This is required in HSv5 rendezvous, in which it should send the URQ_AGREEMENT message to
// the peer, however switch to connected state.
HLOGC(mglog.Debug,
log << "processConnectResponse: TYPE:"
<< (response.isControl() ? MessageTypeStr(response.getType(), response.getExtendedType())
: string("DATA")));
// ConnectStatus res = CONN_REJECT; // used later for status - must be declared here due to goto POST_CONNECT.
// For HSv4, the data sender is INITIATOR, and the data receiver is RESPONDER,
// regardless of the connecting side affiliation. This will be changed for HSv5.
bool bidirectional = false;
HandshakeSide hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER;
// (defined here due to 'goto' below).
// SRT peer may send the SRT handshake private message (type 0x7fff) before a keep-alive.
// This condition is checked when the current agent is trying to do connect() in rendezvous mode,
// but the peer was faster to send a handshake packet earlier. This makes it continue with connecting
// process if the peer is already behaving as if the connection was already established.
// This value will check either the initial value, which is less than SRT1, or
// the value previously loaded to m_ConnReq during the previous handshake response.
// For the initial form this value should not be checked.
bool hsv5 = m_ConnRes.m_iVersion >= HS_VERSION_SRT1;
if (m_bRendezvous &&
(m_RdvState == CHandShake::RDV_CONNECTED // somehow Rendezvous-v5 switched it to CONNECTED.
|| !response.isControl() // WAS A PAYLOAD PACKET.
|| (response.getType() == UMSG_KEEPALIVE) // OR WAS A UMSG_KEEPALIVE message.
|| (response.getType() == UMSG_EXT) // OR WAS a CONTROL packet of some extended type (i.e. any SRT specific)
)
// This may happen if this is an initial state in which the socket type was not yet set.
// If this is a field that holds the response handshake record from the peer, this means that it wasn't received
// yet. HSv5: added version check because in HSv5 the m_iType field has different meaning and it may be 0 in
// case when the handshake does not carry SRT extensions.
&& (hsv5 || m_ConnRes.m_iType != UDT_UNDEFINED))
{
// a data packet or a keep-alive packet comes, which means the peer side is already connected
// in this situation, the previously recorded response will be used
// In HSv5 this situation is theoretically possible if this party has missed the URQ_AGREEMENT message.
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in");
if (hsv5)
{
m_RdvState = CHandShake::RDV_CONNECTED;
}
return postConnect(response, hsv5, eout, synchro);
}
if (!response.isControl(UMSG_HANDSHAKE))
{
m_RejectReason = SRT_REJ_ROGUE;
if (!response.isControl())
{
LOGC(mglog.Error, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected");
}
else
{
LOGC(mglog.Error,
log << CONID()
<< "processConnectResponse: CONFUSED: expected UMSG_HANDSHAKE as connection not yet established, "
"got: "
<< MessageTypeStr(response.getType(), response.getExtendedType()));
}
return CONN_CONFUSED;
}
if (m_ConnRes.load_from(response.m_pcData, response.getLength()) == -1)
{
m_RejectReason = SRT_REJ_ROGUE;
// Handshake data were too small to reach the Handshake structure. Reject.
LOGC(mglog.Error,
log << CONID()
<< "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting.");
return CONN_REJECT;
}
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show());
if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES)
{
m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType);
return CONN_REJECT;
}
if (size_t(m_ConnRes.m_iMSS) > CPacket::ETH_MAX_MTU_SIZE)
{
// Yes, we do abort to prevent buffer overrun. Set your MSS correctly
// and you'll avoid problems.
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Fatal, log << "MSS size " << m_iMSS << "exceeds MTU size!");
return CONN_REJECT;
}
// (see createCrypter() call below)
//
// The CCryptoControl attached object must be created early
// because it will be required to create a conclusion handshake in HSv5
//
if (m_bRendezvous)
{
// SANITY CHECK: A rendezvous socket should reject any caller requests (it's not a listener)
if (m_ConnRes.m_iReqType == URQ_INDUCTION)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error,
log << CONID()
<< "processConnectResponse: Rendezvous-point received INDUCTION handshake (expected WAVEAHAND). "
"Rejecting.");
return CONN_REJECT;
}
// The procedure for version 5 is completely different and changes the states
// differently, so the old code will still maintain HSv4 the old way.
if (m_ConnRes.m_iVersion > HS_VERSION_UDT4)
{
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED.");
return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous().
}
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendsezvous HSv4 DETECTED.");
// So, here it has either received URQ_WAVEAHAND handshake message (while it should be in URQ_WAVEAHAND itself)
// or it has received URQ_CONCLUSION/URQ_AGREEMENT message while this box has already sent URQ_WAVEAHAND to the
// peer, and DID NOT send the URQ_CONCLUSION yet.
if (m_ConnReq.m_iReqType == URQ_WAVEAHAND || m_ConnRes.m_iReqType == URQ_WAVEAHAND)
{
HLOGC(mglog.Debug,
log << CONID() << "processConnectResponse: REQ-TIME LOW. got HS RDV. Agent state:"
<< RequestTypeStr(m_ConnReq.m_iReqType) << " Peer HS:" << m_ConnRes.show());
// Here we could have received WAVEAHAND or CONCLUSION.
// For HSv4 simply switch to CONCLUSION for the sake of further handshake rolling.
// For HSv5, make the cookie contest and basing on this decide, which party
// should provide the HSREQ/KMREQ attachment.
if (!createCrypter(hsd, false /* unidirectional */))
{
m_RejectReason = SRT_REJ_RESOURCE;
m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE);
// the request time must be updated so that the next handshake can be sent out immediately.
m_llLastReqTime = 0;
return CONN_REJECT;
}
m_ConnReq.m_iReqType = URQ_CONCLUSION;
// the request time must be updated so that the next handshake can be sent out immediately.
m_llLastReqTime = 0;
return CONN_CONTINUE;
}
else
{
HLOGC(mglog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand");
}
}
else
{
// set cookie
if (m_ConnRes.m_iReqType == URQ_INDUCTION)
{
HLOGC(mglog.Debug,
log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" << hex
<< m_ConnRes.m_iCookie << " version:" << dec << m_ConnRes.m_iVersion
<< "), sending CONCLUSION HS with this cookie");
m_ConnReq.m_iCookie = m_ConnRes.m_iCookie;
m_ConnReq.m_iReqType = URQ_CONCLUSION;
// Here test if the LISTENER has responded with version HS_VERSION_SRT1,
// it means that it is HSv5 capable. It can still accept the HSv4 handshake.
if (m_ConnRes.m_iVersion > HS_VERSION_UDT4)
{
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
if (hs_flags != SrtHSRequest::SRT_MAGIC_CODE)
{
LOGC(mglog.Warn, log << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE");
}
checkUpdateCryptoKeyLen("processConnectResponse", m_ConnRes.m_iType);
// This will catch HS_VERSION_SRT1 and any newer.
// Set your highest version.
m_ConnReq.m_iVersion = HS_VERSION_SRT1;
// CONTROVERSIAL: use 0 as m_iType according to the meaning in HSv5.
// The HSv4 client might not understand it, which means that agent
// must switch itself to HSv4 rendezvous, and this time iType sould
// be set to UDT_DGRAM value.
m_ConnReq.m_iType = 0;
// This marks the information for the serializer that
// the SRT handshake extension is required.
// Rest of the data will be filled together with
// serialization.
m_ConnReq.m_extension = true;
// For HSv5, the caller is INITIATOR and the listener is RESPONDER.
// The m_bDataSender value should be completely ignored and the
// connection is always bidirectional.
bidirectional = true;
hsd = HSD_INITIATOR;
}
m_llLastReqTime = 0;
if (!createCrypter(hsd, bidirectional))
{
m_RejectReason = SRT_REJ_RESOURCE;
return CONN_REJECT;
}
// NOTE: This setup sets URQ_CONCLUSION and appropriate data in the handshake structure.
// The full handshake to be sent will be filled back in the caller function -- CUDT::startConnect().
return CONN_CONTINUE;
}
}
return postConnect(response, false, eout, synchro);
}
void CUDT::applyResponseSettings()
{
// Re-configure according to the negotiated values.
m_iMSS = m_ConnRes.m_iMSS;
m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize;
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
m_iPeerISN = m_ConnRes.m_iISN;
m_iRcvLastAck = m_ConnRes.m_iISN;
#ifdef ENABLE_LOGGING
m_iDebugPrevLastAck = m_iRcvLastAck;
#endif
m_iRcvLastSkipAck = m_iRcvLastAck;
m_iRcvLastAckAck = m_ConnRes.m_iISN;
m_iRcvCurrSeqNo = m_ConnRes.m_iISN - 1;
m_iRcvCurrPhySeqNo = m_ConnRes.m_iISN - 1;
m_PeerID = m_ConnRes.m_iID;
memcpy(m_piSelfIP, m_ConnRes.m_piPeerIP, 16);
HLOGC(mglog.Debug,
log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize
<< " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " isn=" << m_ConnRes.m_iISN
<< " peerID=" << m_ConnRes.m_iID);
}
EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTException *eout, bool synchro)
{
if (m_ConnRes.m_iVersion < HS_VERSION_SRT1)
m_ullRcvPeerStartTime = 0; // will be set correctly in SRT HS.
// This procedure isn't being executed in rendezvous because
// in rendezvous it's completed before calling this function.
if (!rendezvous)
{
// NOTE: THIS function must be called before calling prepareConnectionObjects.
// The reason why it's not part of prepareConnectionObjects is that the activities
// done there are done SIMILAR way in acceptAndRespond, which also calls this
// function. In fact, prepareConnectionObjects() represents the code that was
// done separately in processConnectResponse() and acceptAndRespond(), so this way
// this code is now common. Now acceptAndRespond() does "manually" something similar
// to applyResponseSettings(), just a little bit differently. This SHOULD be made
// common as a part of refactoring job, just needs a bit more time.
//
// Currently just this function must be called always BEFORE prepareConnectionObjects
// everywhere except acceptAndRespond().
applyResponseSettings();
// This will actually be done also in rendezvous HSv4,
// however in this case the HSREQ extension will not be attached,
// so it will simply go the "old way".
bool ok = prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout);
// May happen that 'response' contains a data packet that was sent in rendezvous mode.
// In this situation the interpretation of handshake was already done earlier.
if (ok && response.isControl())
{
ok = interpretSrtHandshake(m_ConnRes, response, 0, 0);
if (!ok && eout)
{
*eout = CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
}
if (!ok) // m_RejectReason already set
return CONN_REJECT;
}
CInfoBlock ib;
ib.m_iIPversion = m_iIPversion;
CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP);
if (m_pCache->lookup(&ib) >= 0)
{
m_iRTT = ib.m_iRTT;
m_iBandwidth = ib.m_iBandwidth;
}
SRT_REJECT_REASON rr = setupCC();
if (rr != SRT_REJ_UNKNOWN)
{
m_RejectReason = rr;
return CONN_REJECT;
}
// And, I am connected too.
m_bConnecting = false;
m_bConnected = true;
// register this socket for receiving data packets
m_pRNode->m_bOnList = true;
m_pRcvQueue->setNewEntry(this);
// XXX Problem around CONN_CONFUSED!
// If some too-eager packets were received from a listener
// that thinks it's connected, but his last handshake was missed,
// they are collected by CRcvQueue::storePkt. The removeConnector
// function will want to delete them all, so it would be nice
// if these packets can be re-delivered. Of course the listener
// should be prepared to resend them (as every packet can be lost
// on UDP), but it's kinda overkill when we have them already and
// can dispatch them.
// Remove from rendezvous queue (in this particular case it's
// actually removing the socket that undergoes asynchronous HS processing).
// Removing at THIS point because since when setNewEntry is called,
// the next iteration in the CRcvQueue::worker loop will be dispatching
// packets normally, as within-connection, so the "connector" won't
// play any role since this time.
// The connector, however, must stay alive until the setNewEntry is called
// because otherwise the packets that are coming for this socket before the
// connection process is complete will be rejected as "attack", instead of
// being enqueued for later pickup from the queue.
m_pRcvQueue->removeConnector(m_SocketID, synchro);
// acknowledge the management module.
CUDTSocket* s = s_UDTUnited.locate(m_SocketID);
if (!s)
{
if (eout)
{
*eout = CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0);
}
m_RejectReason = SRT_REJ_CLOSE;
return CONN_REJECT;
}
// copy address information of local node
// the local port must be correctly assigned BEFORE CUDT::startConnect(),
// otherwise if startConnect() fails, the multiplexer cannot be located
// by garbage collection and will cause leak
s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr);
CIPAddress::pton(s->m_pSelfAddr, s->m_pUDT->m_piSelfIP, s->m_iIPversion);
s->m_Status = SRTS_CONNECTED;
// acknowledde any waiting epolls to write
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
LOGC(mglog.Note, log << "Connection established to: " << SockaddrToString(m_pPeerAddr));
return CONN_ACCEPT;
}
void CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield)
{
int enc_flags = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(typefield);
// potentially 0-7 values are possible.
// When 0, don't change anything - it should rely on the value 0.
// When 1, 5, 6, 7, this is kinda internal error - ignore.
if (enc_flags >= 2 && enc_flags <= 4) // 2 = 128, 3 = 192, 4 = 256
{
int rcv_pbkeylen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(enc_flags);
if (m_iSndCryptoKeyLen == 0)
{
m_iSndCryptoKeyLen = rcv_pbkeylen;
HLOGC(mglog.Debug, log << loghdr << ": PBKEYLEN adopted from advertised value: " << m_iSndCryptoKeyLen);
}
else if (m_iSndCryptoKeyLen != rcv_pbkeylen)
{
// Conflict. Use SRTO_SENDER flag to check if this side should accept
// the enforcement, otherwise simply let it win.
if (!m_bDataSender)
{
LOGC(mglog.Warn,
log << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_iSndCryptoKeyLen << " by "
<< rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)");
m_iSndCryptoKeyLen = rcv_pbkeylen;
}
else
{
LOGC(mglog.Warn,
log << loghdr << ": PBKEYLEN conflict - keep " << m_iSndCryptoKeyLen
<< "; peer-advertised PBKEYLEN " << rcv_pbkeylen << " rejected because Agent is SRTO_SENDER");
}
}
}
else if (enc_flags != 0)
{
LOGC(mglog.Error, log << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags);
}
else
{
HLOGC(mglog.Debug, log << loghdr << ": No encryption flags found in type field: " << typefield);
}
}
// Rendezvous
void CUDT::rendezvousSwitchState(ref_t<UDTRequestType> rsptype, ref_t<bool> needs_extension, ref_t<bool> needs_hsrsp)
{
UDTRequestType req = m_ConnRes.m_iReqType;
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
bool has_extension = !!hs_flags; // it holds flags, if no flags, there are no extensions.
const HandshakeSide &hsd = m_SrtHsSide;
// Note important possibilities that are considered here:
// 1. The serial arrangement. This happens when one party has missed the
// URQ_WAVEAHAND message, it sent its own URQ_WAVEAHAND message, and then the
// firstmost message it received from the peer is URQ_CONCLUSION, as a response
// for agent's URQ_WAVEAHAND.
//
// In this case, Agent switches to RDV_FINE state and Peer switches to RDV_ATTENTION state.
//
// 2. The parallel arrangement. This happens when the URQ_WAVEAHAND message sent
// by both parties are almost in a perfect synch (a rare, but possible case). In this
// case, both parties receive one another's URQ_WAVEAHAND message and both switch to
// RDV_ATTENTION state.
//
// It's not possible to predict neither which arrangement will happen, or which
// party will be RDV_FINE in case when the serial arrangement has happened. What
// will actually happen will depend on random conditions.
//
// No matter this randomity, we have a limited number of possible conditions:
//
// Stating that "agent" is the party that has received the URQ_WAVEAHAND in whatever
// arrangement, we are certain, that "agent" switched to RDV_ATTENTION, and peer:
//
// - switched to RDV_ATTENTION state (so, both are in the same state independently)
// - switched to RDV_FINE state (so, the message interchange is actually more-less sequenced)
//
// In particular, there's no possibility of a situation that both are in RDV_FINE state
// because the agent can switch to RDV_FINE state only if it received URQ_CONCLUSION from
// the peer, while the peer could not send URQ_CONCLUSION without switching off RDV_WAVING
// (actually to RDV_ATTENTION). There's also no exit to RDV_FINE from RDV_ATTENTION.
// DEFAULT STATEMENT: don't attach extensions to URQ_CONCLUSION, neither HSREQ nor HSRSP.
*needs_extension = false;
*needs_hsrsp = false;
string reason;
#if ENABLE_HEAVY_LOGGING
HLOGC(mglog.Debug, log << "rendezvousSwitchState: HS: " << m_ConnRes.show());
struct LogAtTheEnd
{
CHandShake::RendezvousState ost;
UDTRequestType orq;
const CHandShake::RendezvousState &nst;
const UDTRequestType & nrq;
bool & needext;
bool & needrsp;
string & reason;
~LogAtTheEnd()
{
HLOGC(mglog.Debug,
log << "rendezvousSwitchState: STATE[" << CHandShake::RdvStateStr(ost) << "->"
<< CHandShake::RdvStateStr(nst) << "] REQTYPE[" << RequestTypeStr(orq) << "->"
<< RequestTypeStr(nrq) << "] "
<< "ext:" << (needext ? (needrsp ? "HSRSP" : "HSREQ") : "NONE")
<< (reason == "" ? string() : "reason:" + reason));
}
} l_logend = {m_RdvState, req, m_RdvState, *rsptype, *needs_extension, *needs_hsrsp, reason};
#endif
switch (m_RdvState)
{
case CHandShake::RDV_INVALID:
return;
case CHandShake::RDV_WAVING:
{
if (req == URQ_WAVEAHAND)
{
m_RdvState = CHandShake::RDV_ATTENTION;
// NOTE: if this->isWinner(), attach HSREQ
*rsptype = URQ_CONCLUSION;
if (hsd == HSD_INITIATOR)
*needs_extension = true;
return;
}
if (req == URQ_CONCLUSION)
{
m_RdvState = CHandShake::RDV_FINE;
*rsptype = URQ_CONCLUSION;
*needs_extension = true; // (see below - this needs to craft either HSREQ or HSRSP)
// if this->isWinner(), then craft HSREQ for that response.
// if this->isLoser(), then this packet should bring HSREQ, so craft HSRSP for the response.
if (hsd == HSD_RESPONDER)
*needs_hsrsp = true;
return;
}
}
reason = "WAVING -> WAVEAHAND or CONCLUSION";
break;
case CHandShake::RDV_ATTENTION:
{
if (req == URQ_WAVEAHAND)
{
// This is only possible if the URQ_CONCLUSION sent to the peer
// was lost on track. The peer is then simply unaware that the
// agent has switched to ATTENTION state and continues sending
// waveahands. In this case, just remain in ATTENTION state and
// retry with URQ_CONCLUSION, as normally.
*rsptype = URQ_CONCLUSION;
if (hsd == HSD_INITIATOR)
*needs_extension = true;
return;
}
if (req == URQ_CONCLUSION)
{
// We have two possibilities here:
//
// WINNER (HSD_INITIATOR): send URQ_AGREEMENT
if (hsd == HSD_INITIATOR)
{
// WINNER should get a response with HSRSP, otherwise this is kinda empty conclusion.
// If no HSRSP attached, stay in this state.
if (hs_flags == 0)
{
HLOGC(
mglog.Debug,
log << "rendezvousSwitchState: "
"{INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got CONCLUSION, remain in [ATTENTION]");
*rsptype = URQ_CONCLUSION;
*needs_extension = true; // If you expect to receive HSRSP, continue sending HSREQ
return;
}
m_RdvState = CHandShake::RDV_CONNECTED;
*rsptype = URQ_AGREEMENT;
return;
}
// LOSER (HSD_RESPONDER): send URQ_CONCLUSION and attach HSRSP extension, then expect URQ_AGREEMENT
if (hsd == HSD_RESPONDER)
{
// If no HSREQ attached, stay in this state.
// (Although this seems completely impossible).
if (hs_flags == 0)
{
LOGC(
mglog.Warn,
log << "rendezvousSwitchState: (IPE!)"
"{RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got CONCLUSION, remain in [ATTENTION]");
*rsptype = URQ_CONCLUSION;
*needs_extension = false; // If you received WITHOUT extensions, respond WITHOUT extensions (wait
// for the right message)
return;
}
m_RdvState = CHandShake::RDV_INITIATED;
*rsptype = URQ_CONCLUSION;
*needs_extension = true;
*needs_hsrsp = true;
return;
}
LOGC(mglog.Error, log << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state.");
// Fallback for cookie draw
m_RdvState = CHandShake::RDV_INVALID;
*rsptype = URQFailure(SRT_REJ_RDVCOOKIE);
return;
}
if (req == URQ_AGREEMENT)
{
// This means that the peer has received our URQ_CONCLUSION, but
// the agent missed the peer's URQ_CONCLUSION (received only initial
// URQ_WAVEAHAND).
if (hsd == HSD_INITIATOR)
{
// In this case the missed URQ_CONCLUSION was sent without extensions,
// whereas the peer received our URQ_CONCLUSION with HSREQ, and therefore
// it sent URQ_AGREEMENT already with HSRSP. This isn't a problem for
// us, we can go on with it, especially that the peer is already switched
// into CHandShake::RDV_CONNECTED state.
m_RdvState = CHandShake::RDV_CONNECTED;
// Both sides are connected, no need to send anything anymore.
*rsptype = URQ_DONE;
return;
}
if (hsd == HSD_RESPONDER)
{
// In this case the missed URQ_CONCLUSION was sent with extensions, so
// we have to request this once again. Send URQ_CONCLUSION in order to
// inform the other party that we need the conclusion message once again.
// The ATTENTION state should be maintained.
*rsptype = URQ_CONCLUSION;
*needs_extension = true;
*needs_hsrsp = true;
return;
}
}
}
reason = "ATTENTION -> WAVEAHAND(conclusion), CONCLUSION(agreement/conclusion), AGREEMENT (done/conclusion)";
break;
case CHandShake::RDV_FINE:
{
// In FINE state we can't receive URQ_WAVEAHAND because if the peer has already
// sent URQ_CONCLUSION, it's already in CHandShake::RDV_ATTENTION, and in this state it can
// only send URQ_CONCLUSION, whereas when it isn't in CHandShake::RDV_ATTENTION, it couldn't
// have sent URQ_CONCLUSION, and if it didn't, the agent wouldn't be in CHandShake::RDV_FINE state.
if (req == URQ_CONCLUSION)
{
// There's only one case when it should receive CONCLUSION in FINE state:
// When it's the winner. If so, it should then contain HSREQ extension.
// In case of loser, it shouldn't receive CONCLUSION at all - it should
// receive AGREEMENT.
// The winner case, received CONCLUSION + HSRSP - switch to CONNECTED and send AGREEMENT.
// So, check first if HAS EXTENSION
bool correct_switch = false;
if (hsd == HSD_INITIATOR && !has_extension)
{
// Received REPEATED empty conclusion that has initially switched it into FINE state.
// To exit FINE state we need the CONCLUSION message with HSRSP.
HLOGC(mglog.Debug,
log << "rendezvousSwitchState: {INITIATOR}[FINE] <CONCLUSION without HSRSP. Stay in [FINE], "
"await CONCLUSION+HSRSP");
}
else if (hsd == HSD_RESPONDER)
{
// In FINE state the RESPONDER expects only to be sent AGREEMENT.
// It has previously received CONCLUSION in WAVING state and this has switched
// it to FINE state. That CONCLUSION message should have contained extension,
// so if this is a repeated CONCLUSION+HSREQ, it should be responded with
// CONCLUSION+HSRSP.
HLOGC(mglog.Debug,
log << "rendezvousSwitchState: {RESPONDER}[FINE] <CONCLUSION. Stay in [FINE], await AGREEMENT");
}
else
{
correct_switch = true;
}
if (!correct_switch)
{
*rsptype = URQ_CONCLUSION;
// initiator should send HSREQ, responder HSRSP,
// in both cases extension is needed
*needs_extension = true;
*needs_hsrsp = hsd == HSD_RESPONDER;
return;
}
m_RdvState = CHandShake::RDV_CONNECTED;
*rsptype = URQ_AGREEMENT;
return;
}
if (req == URQ_AGREEMENT)
{
// The loser case, the agreement was sent in response to conclusion that
// already carried over the HSRSP extension.
// There's a theoretical case when URQ_AGREEMENT can be received in case of
// parallel arrangement, while the agent is already in CHandShake::RDV_CONNECTED state.
// This will be dispatched in the main loop and discarded.
m_RdvState = CHandShake::RDV_CONNECTED;
*rsptype = URQ_DONE;
return;
}
}
reason = "FINE -> CONCLUSION(agreement), AGREEMENT(done)";
break;
case CHandShake::RDV_INITIATED:
{
// In this state we just wait for URQ_AGREEMENT, which should cause it to
// switch to CONNECTED. No response required.
if (req == URQ_AGREEMENT)
{
// No matter in which state we'd be, just switch to connected.
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
HLOGC(mglog.Debug, log << "<-- AGREEMENT: already connected");
}
else
{
HLOGC(mglog.Debug, log << "<-- AGREEMENT: switched to connected");
}
m_RdvState = CHandShake::RDV_CONNECTED;
*rsptype = URQ_DONE;
return;
}
if (req == URQ_CONCLUSION)
{
// Receiving conclusion in this state means that the other party
// didn't get our conclusion, so send it again, the same as when
// exiting the ATTENTION state.
*rsptype = URQ_CONCLUSION;
if (hsd == HSD_RESPONDER)
{
HLOGC(mglog.Debug,
log << "rendezvousSwitchState: "
"{RESPONDER}[INITIATED] awaits AGREEMENT, "
"got CONCLUSION, sending CONCLUSION+HSRSP");
*needs_extension = true;
*needs_hsrsp = true;
return;
}
// Loser, initiated? This may only happen in parallel arrangement, where
// the agent exchanges empty conclusion messages with the peer, simultaneously
// exchanging HSREQ-HSRSP conclusion messages. Check if THIS message contained
// HSREQ, and set responding HSRSP in that case.
if (hs_flags == 0)
{
HLOGC(mglog.Debug,
log << "rendezvousSwitchState: "
"{INITIATOR}[INITIATED] awaits AGREEMENT, "
"got empty CONCLUSION, STILL RESPONDING CONCLUSION+HSRSP");
}
else
{
HLOGC(mglog.Debug,
log << "rendezvousSwitchState: "
"{INITIATOR}[INITIATED] awaits AGREEMENT, "
"got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP");
}
*needs_extension = true;
*needs_hsrsp = true;
return;
}
}
reason = "INITIATED -> AGREEMENT(done)";
break;
case CHandShake::RDV_CONNECTED:
// Do nothing. This theoretically should never happen.
*rsptype = URQ_DONE;
return;
}
HLOGC(mglog.Debug, log << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID");
// All others are treated as errors
m_RdvState = CHandShake::RDV_WAVING;
*rsptype = URQFailure(SRT_REJ_ROGUE);
}
/*
* Timestamp-based Packet Delivery (TsbPd) thread
* This thread runs only if TsbPd mode is enabled
* Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay.
*/
void *CUDT::tsbpd(void *param)
{
CUDT *self = (CUDT *)param;
THREAD_STATE_INIT("SRT:TsbPd");
CGuard::enterCS(self->m_RecvLock);
self->m_bTsbPdAckWakeup = true;
while (!self->m_bClosing)
{
int32_t current_pkt_seq = 0;
uint64_t tsbpdtime = 0;
bool rxready = false;
CGuard::enterCS(self->m_RcvBufferLock);
#ifdef SRT_ENABLE_RCVBUFSZ_MAVG
self->m_pRcvBuffer->updRcvAvgDataSize(CTimer::getTime());
#endif
if (self->m_bTLPktDrop)
{
int32_t skiptoseqno = -1;
bool passack = true; // Get next packet to wait for even if not acked
rxready = self->m_pRcvBuffer->getRcvFirstMsg(
Ref(tsbpdtime), Ref(passack), Ref(skiptoseqno), Ref(current_pkt_seq));
HLOGC(tslog.Debug,
log << boolalpha << "NEXT PKT CHECK: rdy=" << rxready << " passack=" << passack << " skipto=%"
<< skiptoseqno << " current=%" << current_pkt_seq << " buf-base=%" << self->m_iRcvLastSkipAck);
/*
* VALUES RETURNED:
*
* rxready: if true, packet at head of queue ready to play
* tsbpdtime: timestamp of packet at head of queue, ready or not. 0 if none.
* passack: if true, ready head of queue not yet acknowledged
* skiptoseqno: sequence number of packet at head of queue if ready to play but
* some preceeding packets are missing (need to be skipped). -1 if none.
*/
if (rxready)
{
/* Packet ready to play according to time stamp but... */
int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno);
if (skiptoseqno != -1 && seqlen > 0)
{
/*
* skiptoseqno != -1,
* packet ready to play but preceeded by missing packets (hole).
*/
/* Update drop/skip stats */
CGuard::enterCS(self->m_StatsLock);
self->m_stats.rcvDropTotal += seqlen;
self->m_stats.traceRcvDrop += seqlen;
/* Estimate dropped/skipped bytes from average payload */
int avgpayloadsz = self->m_pRcvBuffer->getRcvAvgPayloadSize();
self->m_stats.rcvBytesDropTotal += seqlen * avgpayloadsz;
self->m_stats.traceRcvBytesDrop += seqlen * avgpayloadsz;
CGuard::leaveCS(self->m_StatsLock);
self->dropFromLossLists(self->m_iRcvLastSkipAck,
CSeqNo::decseq(skiptoseqno)); // remove(from,to-inclusive)
self->m_pRcvBuffer->skipData(seqlen);
self->m_iRcvLastSkipAck = skiptoseqno;
#if ENABLE_LOGGING
int64_t timediff = 0;
if (tsbpdtime)
timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime());
#if ENABLE_HEAVY_LOGGING
HLOGC(tslog.Debug,
log << self->CONID() << "tsbpd: DROPSEQ: up to seq=" << CSeqNo::decseq(skiptoseqno) << " ("
<< seqlen << " packets) playable at " << FormatTime(tsbpdtime) << " delayed "
<< (timediff / 1000) << "." << (timediff % 1000) << " ms");
#endif
LOGC(dlog.Debug, log << "RCV-DROPPED packet delay=" << (timediff / 1000) << "ms");
#endif
tsbpdtime = 0; // Next sent ack will unblock
rxready = false;
}
else if (passack)
{
/* Packets ready to play but not yet acknowledged (should happen within 10ms) */
rxready = false;
tsbpdtime = 0; // Next sent ack will unblock
} /* else packet ready to play */
} /* else packets not ready to play */
}
else
{
rxready = self->m_pRcvBuffer->isRcvDataReady(Ref(tsbpdtime), Ref(current_pkt_seq));
}
CGuard::leaveCS(self->m_RcvBufferLock);
if (rxready)
{
HLOGC(tslog.Debug,
log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << current_pkt_seq << " (belated "
<< ((CTimer::getTime() - tsbpdtime) / 1000.0) << "ms)");
/*
* There are packets ready to be delivered
* signal a waiting "recv" call if there is any data available
*/
if (self->m_bSynRecving)
{
pthread_cond_signal(&self->m_RecvDataCond);
}
/*
* Set EPOLL_IN to wakeup any thread waiting on epoll
*/
self->s_UDTUnited.m_EPoll.update_events(self->m_SocketID, self->m_sPollID, UDT_EPOLL_IN, true);
CTimer::triggerEvent();
tsbpdtime = 0;
}
if (tsbpdtime != 0)
{
int64_t timediff = int64_t(tsbpdtime) - int64_t(CTimer::getTime());
/*
* Buffer at head of queue is not ready to play.
* Schedule wakeup when it will be.
*/
self->m_bTsbPdAckWakeup = false;
THREAD_PAUSED();
HLOGC(tslog.Debug,
log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << current_pkt_seq
<< " T=" << FormatTime(tsbpdtime) << " - waiting " << (timediff / 1000.0) << "ms");
CTimer::condTimedWaitUS(&self->m_RcvTsbPdCond, &self->m_RecvLock, timediff);
THREAD_RESUMED();
}
else
{
/*
* We have just signaled epoll; or
* receive queue is empty; or
* next buffer to deliver is not in receive queue (missing packet in sequence).
*
* Block until woken up by one of the following event:
* - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time
* if any)
* - New buffers ACKed
* - Closing the connection
*/
HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack");
self->m_bTsbPdAckWakeup = true;
THREAD_PAUSED();
pthread_cond_wait(&self->m_RcvTsbPdCond, &self->m_RecvLock);
THREAD_RESUMED();
}
}
CGuard::leaveCS(self->m_RecvLock);
THREAD_EXIT();
HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING");
return NULL;
}
bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout)
{
// This will be lazily created due to being the common
// code with HSv5 rendezvous, in which this will be run
// in a little bit "randomly selected" moment, but must
// be run once in the whole connection process.
if (m_pSndBuffer)
{
HLOGC(mglog.Debug, log << "prepareConnectionObjects: (lazy) already created.");
return true;
}
bool bidirectional = false;
if (hs.m_iVersion > HS_VERSION_UDT4)
{
bidirectional = true; // HSv5 is always bidirectional
}
// HSD_DRAW is received only if this side is listener.
// If this side is caller with HSv5, HSD_INITIATOR should be passed.
// If this is a rendezvous connection with HSv5, the handshake role
// is taken from m_SrtHsSide field.
if (hsd == HSD_DRAW)
{
if (bidirectional)
{
hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR.
}
else
{
hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER;
}
}
try
{
m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize);
m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_iRcvBufSize);
// after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space.
m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2);
m_pRcvLossList = new CRcvLossList(m_iFlightFlagSize);
}
catch (...)
{
// Simply reject.
if (eout)
{
*eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
}
m_RejectReason = SRT_REJ_RESOURCE;
return false;
}
if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy)
{
m_RejectReason = SRT_REJ_RESOURCE;
return false;
}
return true;
}
void CUDT::acceptAndRespond(const sockaddr *peer, CHandShake *hs, const CPacket &hspkt)
{
HLOGC(mglog.Debug, log << "acceptAndRespond: setting up data according to handshake");
CGuard cg(m_ConnectionLock);
m_ullRcvPeerStartTime = 0; // will be set correctly at SRT HS
// Uses the smaller MSS between the peers
if (hs->m_iMSS > m_iMSS)
hs->m_iMSS = m_iMSS;
else
m_iMSS = hs->m_iMSS;
// exchange info for maximum flow window size
m_iFlowWindowSize = hs->m_iFlightFlagSize;
hs->m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
m_iPeerISN = hs->m_iISN;
m_iRcvLastAck = hs->m_iISN;
#ifdef ENABLE_LOGGING
m_iDebugPrevLastAck = m_iRcvLastAck;
#endif
m_iRcvLastSkipAck = m_iRcvLastAck;
m_iRcvLastAckAck = hs->m_iISN;
m_iRcvCurrSeqNo = hs->m_iISN - 1;
m_iRcvCurrPhySeqNo = hs->m_iISN - 1;
m_PeerID = hs->m_iID;
hs->m_iID = m_SocketID;
// use peer's ISN and send it back for security check
m_iISN = hs->m_iISN;
m_iLastDecSeq = m_iISN - 1;
m_iSndLastAck = m_iISN;
m_iSndLastDataAck = m_iISN;
m_iSndLastFullAck = m_iISN;
m_iSndCurrSeqNo = m_iISN - 1;
m_iSndLastAck2 = m_iISN;
m_ullSndLastAck2Time = CTimer::getTime();
// this is a reponse handshake
hs->m_iReqType = URQ_CONCLUSION;
if (hs->m_iVersion > HS_VERSION_UDT4)
{
// The version is agreed; this code is executed only in case
// when AGENT is listener. In this case, conclusion response
// must always contain HSv5 handshake extensions.
hs->m_extension = true;
}
// get local IP address and send the peer its IP address (because UDP cannot get local IP address)
memcpy(m_piSelfIP, hs->m_piPeerIP, 16);
CIPAddress::ntop(peer, hs->m_piPeerIP, m_iIPversion);
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
HLOGC(mglog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
// Prepare all structures
if (!prepareConnectionObjects(*hs, HSD_DRAW, 0))
{
HLOGC(mglog.Debug, log << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT.");
// If the SRT Handshake extension was provided and wasn't interpreted
// correctly, the connection should be rejected.
//
// Respond with the rejection message and exit with exception
// so that the caller will know that this new socket should be deleted.
hs->m_iReqType = URQFailure(m_RejectReason);
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
// Since now you can use m_pCryptoControl
CInfoBlock ib;
ib.m_iIPversion = m_iIPversion;
CInfoBlock::convert(peer, m_iIPversion, ib.m_piIP);
if (m_pCache->lookup(&ib) >= 0)
{
m_iRTT = ib.m_iRTT;
m_iBandwidth = ib.m_iBandwidth;
}
// This should extract the HSREQ and KMREQ portion in the handshake packet.
// This could still be a HSv4 packet and contain no such parts, which will leave
// this entity as "non-SRT-handshaken", and await further HSREQ and KMREQ sent
// as UMSG_EXT.
uint32_t kmdata[SRTDATA_MAXSIZE];
size_t kmdatasize = SRTDATA_MAXSIZE;
if (!interpretSrtHandshake(*hs, hspkt, kmdata, &kmdatasize))
{
HLOGC(mglog.Debug, log << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT.");
// If the SRT Handshake extension was provided and wasn't interpreted
// correctly, the connection should be rejected.
//
// Respond with the rejection message and return false from
// this function so that the caller will know that this new
// socket should be deleted.
hs->m_iReqType = URQFailure(m_RejectReason);
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
SRT_REJECT_REASON rr = setupCC();
// UNKNOWN used as a "no error" value
if (rr != SRT_REJ_UNKNOWN)
{
hs->m_iReqType = URQFailure(rr);
m_RejectReason = rr;
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr *)new sockaddr_in : (sockaddr *)new sockaddr_in6;
memcpy(m_pPeerAddr, peer, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6));
// And of course, it is connected.
m_bConnected = true;
// register this socket for receiving data packets
m_pRNode->m_bOnList = true;
m_pRcvQueue->setNewEntry(this);
// send the response to the peer, see listen() for more discussions about this
// XXX 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;
// Allocate the maximum possible memory for an SRT payload.
// This is a maximum you can send once.
CPacket response;
response.setControl(UMSG_HANDSHAKE);
response.allocate(size);
// This will serialize the handshake according to its current form.
HLOGC(mglog.Debug,
log << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size);
if (!createSrtHandshake(Ref(response), Ref(*hs), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize))
{
LOGC(mglog.Error, log << "acceptAndRespond: error creating handshake response");
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
// Set target socket ID to the value from received handshake's source ID.
response.m_iID = m_PeerID;
#if ENABLE_HEAVY_LOGGING
{
// To make sure what REALLY is being sent, parse back the handshake
// data that have been just written into the buffer.
CHandShake debughs;
debughs.load_from(response.m_pcData, response.getLength());
HLOGC(mglog.Debug,
log << CONID() << "acceptAndRespond: sending HS to peer, reqtype=" << RequestTypeStr(debughs.m_iReqType)
<< " version=" << debughs.m_iVersion << " (connreq:" << RequestTypeStr(m_ConnReq.m_iReqType)
<< "), target_socket=" << response.m_iID << ", my_socket=" << debughs.m_iID);
}
#endif
// NOTE: BLOCK THIS instruction in order to cause the final
// handshake to be missed and cause the problem solved in PR #417.
// When missed this message, the caller should not accept packets
// coming as connected, but continue repeated handshake until finally
// received the listener's handshake.
m_pSndQueue->sendto(peer, response);
}
// This function is required to be called when a caller receives an INDUCTION
// response from the listener and would like to create a CONCLUSION that includes
// the SRT handshake extension. This extension requires that the crypter object
// be created, but it's still too early for it to be completely configured.
// This function then precreates the object so that the handshake extension can
// be created, as this happens before the completion of the connection (and
// therefore configuration of the crypter object), which can only take place upon
// reception of CONCLUSION response from the listener.
bool CUDT::createCrypter(HandshakeSide side, bool bidirectional)
{
// Lazy initialization
if (m_pCryptoControl)
return true;
// Write back this value, when it was just determined.
m_SrtHsSide = side;
m_pCryptoControl.reset(new CCryptoControl(this, m_SocketID));
// XXX These below are a little bit controversial.
// These data should probably be filled only upon
// reception of the conclusion handshake - otherwise
// they have outdated values.
m_pCryptoControl->setCryptoSecret(m_CryptoSecret);
if (bidirectional || m_bDataSender)
{
HLOGC(mglog.Debug, log << "createCrypter: setting RCV/SND KeyLen=" << m_iSndCryptoKeyLen);
m_pCryptoControl->setCryptoKeylen(m_iSndCryptoKeyLen);
}
return m_pCryptoControl->init(side, bidirectional);
}
SRT_REJECT_REASON CUDT::setupCC()
{
// Prepare configuration object,
// Create the CCC object and configure it.
// UDT also sets back the congestion window: ???
// m_dCongestionWindow = m_pCC->m_dCWndSize;
// 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.
// if (bidirectional || m_bDataSender || m_bTwoWayData)
// m_bPeerTsbPd = m_bOPT_TsbPd;
// SrtCongestion will retrieve whatever parameters it needs
// from *this.
if (!m_CongCtl.configure(this))
{
return SRT_REJ_CONGESTION;
}
// Configure filter module
if (m_OPT_PktFilterConfigString != "")
{
// This string, when nonempty, defines that the corrector shall be
// configured. Otherwise it's left uninitialized.
// At this point we state everything is checked and the appropriate
// corrector type is already selected, so now create it.
HLOGC(mglog.Debug, log << "filter: Configuring Corrector: " << m_OPT_PktFilterConfigString);
if (!m_PacketFilter.configure(this, m_pRcvBuffer->getUnitQueue(), m_OPT_PktFilterConfigString))
{
return SRT_REJ_FILTER;
}
m_PktFilterRexmitLevel = m_PacketFilter.arqLevel();
}
else
{
// When we have no filter, ARQ should work in ALWAYS mode.
m_PktFilterRexmitLevel = SRT_ARQ_ALWAYS;
}
// Override the value of minimum NAK interval, per SrtCongestion's wish.
// When default 0 value is returned, the current value set by CUDT
// is preserved.
uint64_t min_nak_tk = m_CongCtl->minNAKInterval();
if (min_nak_tk)
m_ullMinNakInt_tk = min_nak_tk;
// Update timers
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
m_ullLastRspTime_tk = currtime_tk;
m_ullNextACKTime_tk = currtime_tk + m_ullACKInt_tk;
m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk;
m_ullLastRspAckTime_tk = currtime_tk;
m_ullLastSndTime_tk = currtime_tk;
HLOGC(mglog.Debug,
log << "setupCC: setting parameters: mss=" << m_iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize
<< " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)"
<< " rtt=" << m_iRTT << " bw=" << m_iBandwidth);
updateCC(TEV_INIT, TEV_INIT_RESET);
return SRT_REJ_UNKNOWN;
}
void CUDT::considerLegacySrtHandshake(uint64_t timebase)
{
// Do a fast pre-check first - this simply declares that agent uses HSv5
// and the legacy SRT Handshake is not to be done. Second check is whether
// agent is sender (=initiator in HSv4).
if (!isTsbPd() || !m_bDataSender)
return;
if (m_iSndHsRetryCnt <= 0)
{
HLOGC(mglog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt);
return;
}
uint64_t now = CTimer::getTime();
if (timebase != 0)
{
// Then this should be done only if it's the right time,
// the TSBPD mode is on, and when the counter is "still rolling".
/*
* SRT Handshake with peer:
* If...
* - we want TsbPd mode; and
* - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and
* - and did not get answer back from peer
* - last sent handshake req should have been replied (RTT*1.5 elapsed); and
* then (re-)send handshake request.
*/
if (timebase > now) // too early
{
HLOGC(mglog.Debug, log << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times");
return;
}
}
// If 0 timebase, it means that this is the initial sending with the very first
// payload packet sent. Send only if this is still set to maximum+1 value.
else if (m_iSndHsRetryCnt < SRT_MAX_HSRETRY + 1)
{
HLOGC(mglog.Debug,
log << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt
<< " times");
return;
}
HLOGC(mglog.Debug, log << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response");
m_iSndHsRetryCnt--;
m_ullSndHsLastTime_us = now;
sendSrtMsg(SRT_CMD_HSREQ);
}
void CUDT::checkSndTimers(Whether2RegenKm regen)
{
if (m_SrtHsSide == HSD_INITIATOR)
{
HLOGC(mglog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase");
// Legacy method for HSREQ, only if initiator.
considerLegacySrtHandshake(m_ullSndHsLastTime_us + m_iRTT * 3 / 2);
}
else
{
HLOGC(mglog.Debug,
log << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)")
<< " - not considering legacy handshake");
}
// This must be done always on sender, regardless of HS side.
// When regen == DONT_REGEN_KM, it's a handshake call, so do
// it only for initiator.
if (regen || m_SrtHsSide == HSD_INITIATOR)
{
// Don't call this function in "non-regen mode" (sending only),
// if this side is RESPONDER. This shall be called only with
// regeneration request, which is required by the sender.
if (m_pCryptoControl)
m_pCryptoControl->sendKeysToPeer(regen);
}
}
void CUDT::addressAndSend(CPacket &pkt)
{
pkt.m_iID = m_PeerID;
pkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
m_pSndQueue->sendto(m_pPeerAddr, pkt);
}
bool CUDT::close()
{
// NOTE: this function is called from within the garbage collector thread.
if (!m_bOpened)
{
return false;
}
HLOGC(mglog.Debug, log << CONID() << " - closing socket:");
if (m_Linger.l_onoff != 0)
{
uint64_t entertime = CTimer::getTime();
HLOGC(mglog.Debug, log << CONID() << " ... (linger)");
while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) &&
(CTimer::getTime() - entertime < m_Linger.l_linger * uint64_t(1000000)))
{
// linger has been checked by previous close() call and has expired
if (m_ullLingerExpiration >= entertime)
break;
if (!m_bSynSending)
{
// if this socket enables asynchronous sending, return immediately and let GC to close it later
if (m_ullLingerExpiration == 0)
m_ullLingerExpiration = entertime + m_Linger.l_linger * uint64_t(1000000);
HLOGC(mglog.Debug,
log << "CUDT::close: linger-nonblocking, setting expire time T="
<< FormatTime(m_ullLingerExpiration));
return false;
}
#ifndef _WIN32
timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 1000000;
nanosleep(&ts, NULL);
#else
Sleep(1);
#endif
}
}
// remove this socket from the snd queue
if (m_bConnected)
m_pSndQueue->m_pSndUList->remove(this);
/*
* update_events below useless
* removing usock for EPolls right after (remove_usocks) clears it (in other HAI patch).
*
* What is in EPoll shall be the responsibility of the application, if it want local close event,
* it would remove the socket from the EPoll after close.
*/
// trigger any pending IO events.
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true);
// then remove itself from all epoll monitoring
try
{
for (set<int>::iterator i = m_sPollID.begin(); i != m_sPollID.end(); ++i)
s_UDTUnited.m_EPoll.remove_usock(*i, m_SocketID);
}
catch (...)
{
}
// XXX What's this, could any of the above actions make it !m_bOpened?
if (!m_bOpened)
{
return true;
}
// Inform the threads handler to stop.
m_bClosing = true;
HLOGC(mglog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock");
CGuard cg(m_ConnectionLock);
// Signal the sender and recver if they are waiting for data.
releaseSynch();
HLOGC(mglog.Debug, log << CONID() << "CLOSING, removing from listener/connector");
if (m_bListening)
{
m_bListening = false;
m_pRcvQueue->removeListener(this);
}
else if (m_bConnecting)
{
m_pRcvQueue->removeConnector(m_SocketID);
}
if (m_bConnected)
{
if (!m_bShutdown)
{
HLOGC(mglog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer");
sendCtrl(UMSG_SHUTDOWN);
}
m_pCryptoControl->close();
// Store current connection information.
CInfoBlock ib;
ib.m_iIPversion = m_iIPversion;
CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP);
ib.m_iRTT = m_iRTT;
ib.m_iBandwidth = m_iBandwidth;
m_pCache->update(&ib);
m_bConnected = false;
}
if (m_bTsbPd && !pthread_equal(m_RcvTsbPdThread, pthread_t()))
{
HLOGC(mglog.Debug, log << "CLOSING, joining TSBPD thread...");
void *retval;
int ret SRT_ATR_UNUSED = pthread_join(m_RcvTsbPdThread, &retval);
HLOGC(mglog.Debug, log << "... " << (ret == 0 ? "SUCCEEDED" : "FAILED"));
}
HLOGC(mglog.Debug, log << "CLOSING, joining send/receive threads");
// waiting all send and recv calls to stop
CGuard sendguard(m_SendLock);
CGuard recvguard(m_RecvLock);
// Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt(Ref(packet))
// from the processData(...) function while resetting Crypto Control.
CGuard::enterCS(m_RcvBufferLock);
m_pCryptoControl.reset();
CGuard::leaveCS(m_RcvBufferLock);
m_lSrtVersion = SRT_DEF_VERSION;
m_lPeerSrtVersion = SRT_VERSION_UNK;
m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1;
m_ullRcvPeerStartTime = 0;
m_bOpened = false;
return true;
}
/*
Old, mostly original UDT based version of CUDT::send.
Left for historical reasons.
int CUDT::send(const char* data, int len)
{
// throw an exception if not connected
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
return 0;
// Check if the current congctl accepts the call with given parameters.
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_SEND, data, len, -1, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
CGuard sendguard(m_SendLock);
if (m_pSndBuffer->getCurrBufSize() == 0)
{
// delay the EXP timer to avoid mis-fired timeout
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
// (fix keepalive) m_ullLastRspTime_tk = currtime_tk;
m_ullLastRspAckTime_tk = currtime_tk;
m_iReXmitCount = 1;
}
if (sndBuffersLeft() <= 0)
{
if (!m_bSynSending)
throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0);
else
{
{
// wait here during a blocking sending
CGuard sendblock_lock(m_SendBlockLock);
if (m_iSndTimeOut < 0)
{
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth)
pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock);
}
else
{
uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * uint64_t(1000);
timespec locktime;
locktime.tv_sec = exptime / 1000000;
locktime.tv_nsec = (exptime % 1000000) * 1000;
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth && (CTimer::getTime() < exptime))
pthread_cond_timedwait(&m_SendBlockCond, &m_SendBlockLock, &locktime);
}
}
// check the connection status
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if (!m_bPeerHealth)
{
m_bPeerHealth = true;
throw CUDTException(MJ_PEERERROR);
}
}
}
if (sndBuffersLeft() <= 0)
{
if (m_iSndTimeOut >= 0)
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
return 0;
}
int size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize);
// record total time used for sending
if (m_pSndBuffer->getCurrBufSize() == 0)
m_llSndDurationCounter = CTimer::getTime();
// insert the user buffer into the sending list
m_pSndBuffer->addBuffer(data, size); // inorder=false, ttl=-1
// insert this socket to snd list if it is not on the list yet
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
if (sndBuffersLeft() <= 0)
{
// write is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
}
return size;
}
*/
int CUDT::receiveBuffer(char *data, int len)
{
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, -1, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
CGuard recvguard(m_RecvLock);
if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
if (m_bShutdown)
{
// For stream API, return 0 as a sign of EOF for transmission.
// That's a bit controversial because theoretically the
// UMSG_SHUTDOWN message may be lost as every UDP packet, although
// another theory states that this will never happen because this
// packet has a total size of 42 bytes and such packets are
// declared as never dropped - but still, this is UDP so there's no
// guarantee.
// The most reliable way to inform the party that the transmission
// has ended would be to send a single empty packet (that is,
// a data packet that contains only an SRT header in the UDP
// payload), which is a normal data packet that can undergo
// normal sequence check and retransmission rules, so it's ensured
// that this packet will be received. Receiving such a packet should
// make this function return 0, potentially also without breaking
// the connection and potentially also with losing no ability to
// send some larger portion of data next time.
HLOGC(mglog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF");
return 0;
}
HLOGC(mglog.Debug,
log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no")
<< " SHUTDOWN. Reporting as BROKEN.");
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
if (!m_pRcvBuffer->isRcvDataReady())
{
if (!m_bSynRecving)
{
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
}
else
{
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_iRcvTimeOut < 0)
{
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
{
// Do not block forever, check connection status each 1 sec.
CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, 1000000);
}
}
else
{
uint64_t exptime = CTimer::getTime() + m_iRcvTimeOut * 1000;
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
{
CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, m_iRcvTimeOut * 1000);
if (CTimer::getTime() >= exptime)
break;
}
}
}
}
// throw an exception if not connected
if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
// See at the beginning
if (!m_bMessageAPI && m_bShutdown)
{
HLOGC(mglog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF");
return 0;
}
HLOGC(mglog.Debug,
log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no")
<< " SHUTDOWN. Reporting as BROKEN.");
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
const int res = m_pRcvBuffer->readBuffer(data, len);
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_bTsbPd)
{
HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup");
pthread_cond_signal(&m_RcvTsbPdCond);
}
if (!m_pRcvBuffer->isRcvDataReady())
{
// read is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
}
if ((res <= 0) && (m_iRcvTimeOut >= 0))
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
return res;
}
void CUDT::checkNeedDrop(ref_t<bool> bCongestion)
{
if (!m_bPeerTLPktDrop)
return;
if (!m_bMessageAPI)
{
LOGC(dlog.Error, log << "The SRTO_TLPKTDROP flag can only be used with message API.");
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
}
int bytes, timespan_ms;
// (returns buffer size in buffer units, ignored)
m_pSndBuffer->getCurrBufSize(Ref(bytes), Ref(timespan_ms));
// high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms)
// Minimum value must accomodate an I-Frame (~8 x average frame size)
// >>need picture rate or app to set min treshold
// >>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
int threshold_ms = 0;
if (m_iOPT_SndDropDelay >= 0)
{
threshold_ms = std::max(m_iPeerTsbPdDelay_ms + m_iOPT_SndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) +
(2 * COMM_SYN_INTERVAL_US / 1000);
}
if (threshold_ms && timespan_ms > threshold_ms)
{
// protect packet retransmission
CGuard::enterCS(m_RecvAckLock);
int dbytes;
int dpkts = m_pSndBuffer->dropLateData(dbytes, CTimer::getTime() - (threshold_ms * 1000));
if (dpkts > 0)
{
CGuard::enterCS(m_StatsLock);
m_stats.traceSndDrop += dpkts;
m_stats.sndDropTotal += dpkts;
m_stats.traceSndBytesDrop += dbytes;
m_stats.sndBytesDropTotal += dbytes;
CGuard::leaveCS(m_StatsLock);
#if ENABLE_HEAVY_LOGGING
int32_t realack = m_iSndLastDataAck;
#endif
int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts);
m_iSndLastAck = fakeack;
m_iSndLastDataAck = fakeack;
int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck);
m_pSndLossList->remove(minlastack);
/* If we dropped packets not yet sent, advance current position */
// THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1)
if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0)
{
m_iSndCurrSeqNo = minlastack;
}
LOGC(dlog.Error, log << "SND-DROPPED " << dpkts << " packets - lost delaying for " << timespan_ms << "ms");
HLOGC(dlog.Debug,
log << "drop,now " << CTimer::getTime() << "us," << realack << "-" << m_iSndCurrSeqNo << " seqs,"
<< dpkts << " pkts," << dbytes << " bytes," << timespan_ms << " ms");
}
*bCongestion = true;
CGuard::leaveCS(m_RecvAckLock);
}
else if (timespan_ms > (m_iPeerTsbPdDelay_ms / 2))
{
HLOGC(mglog.Debug,
log << "cong, NOW: " << CTimer::getTime() << "us, BYTES " << bytes << ", TMSPAN " << timespan_ms << "ms");
*bCongestion = true;
}
}
int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, uint64_t srctime)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
mctrl.msgttl = msttl;
mctrl.inorder = inorder;
mctrl.srctime = srctime;
return this->sendmsg2(data, len, Ref(mctrl));
}
int CUDT::sendmsg2(const char *data, int len, ref_t<SRT_MSGCTRL> r_mctrl)
{
SRT_MSGCTRL &mctrl = *r_mctrl;
bool bCongestion = false;
// throw an exception if not connected
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
{
LOGC(dlog.Error, log << "INVALID: Data size for sending declared with length: " << len);
return 0;
}
int msttl = mctrl.msgttl;
bool inorder = mctrl.inorder;
// Sendmsg isn't restricted to the congctl type, however the congctl
// may want to have something to say here.
// NOTE: SrtCongestion is also allowed to throw CUDTException() by itself!
{
SrtCongestion::TransAPI api = SrtCongestion::STA_MESSAGE;
CodeMinor mn = MN_INVALMSGAPI;
if (!m_bMessageAPI)
{
api = SrtCongestion::STA_BUFFER;
mn = MN_INVALBUFFERAPI;
}
if (!m_CongCtl->checkTransArgs(api, SrtCongestion::STAD_SEND, data, len, msttl, inorder))
throw CUDTException(MJ_NOTSUP, mn, 0);
}
// NOTE: the length restrictions differ in STREAM API and in MESSAGE API:
// - STREAM API:
// At least 1 byte free sending buffer space is needed
// (in practice, one unit buffer of 1456 bytes).
// This function will send as much as possible, and return
// how much was actually sent.
// - MESSAGE API:
// At least so many bytes free in the sending buffer is needed,
// as the length of the data, otherwise this function will block
// or return MJ_AGAIN until this condition is satisfied. The EXACTLY
// such number of data will be then written out, and this function
// will effectively return either -1 (error) or the value of 'len'.
// This call will be also rejected from upside when trying to send
// out a message of a length that exceeds the total size of the sending
// buffer (configurable by SRTO_SNDBUF).
if (m_bMessageAPI && len > int(m_iSndBufSize * m_iMaxSRTPayloadSize))
{
LOGC(dlog.Error,
log << "Message length (" << len << ") exceeds the size of sending buffer: "
<< (m_iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed.");
throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0);
}
/* XXX
This might be worth preserving for several occasions, but it
must be at least conditional because it breaks backward compat.
if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK())
{
LOGC(dlog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending
rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
*/
CGuard sendguard(m_SendLock);
if (m_pSndBuffer->getCurrBufSize() == 0)
{
// delay the EXP timer to avoid mis-fired timeout
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
CGuard ack_lock(m_RecvAckLock);
m_ullLastRspAckTime_tk = currtime_tk; // (fix keepalive)
m_iReXmitCount = 1; // can be modified in checkRexmitTimer and processCtrlAck (receiver's thread)
}
// checkNeedDrop(...) may lock m_RecvAckLock
// to modify m_pSndBuffer and m_pSndLossList
checkNeedDrop(Ref(bCongestion));
int minlen = 1; // Minimum sender buffer space required for STREAM API
if (m_bMessageAPI)
{
// For MESSAGE API the minimum outgoing buffer space required is
// the size that can carry over the whole message as passed here.
minlen = (len + m_iMaxSRTPayloadSize - 1) / m_iMaxSRTPayloadSize;
}
if (sndBuffersLeft() < minlen)
{
//>>We should not get here if SRT_ENABLE_TLPKTDROP
// XXX Check if this needs to be removed, or put to an 'else' condition for m_bTLPktDrop.
if (!m_bSynSending)
throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0);
{
// wait here during a blocking sending
CGuard sendblock_lock(m_SendBlockLock);
if (m_iSndTimeOut < 0)
{
while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth)
pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock);
}
else
{
const uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * uint64_t(1000);
while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth && exptime > CTimer::getTime())
CTimer::condTimedWaitUS(&m_SendBlockCond, &m_SendBlockLock, m_iSndTimeOut * uint64_t(1000));
}
}
// check the connection status
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if (!m_bPeerHealth)
{
m_bPeerHealth = true;
throw CUDTException(MJ_PEERERROR);
}
/*
* 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 availaible,
* we test twice if this code is outside the else section.
* This fix move it in the else (blocking-mode) section
*/
if (sndBuffersLeft() < minlen)
{
if (m_iSndTimeOut >= 0)
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
// XXX This looks very weird here, however most likely
// this will happen only in the following case, when
// the above loop has been interrupted, which happens when:
// 1. The buffers left gets enough for minlen - but this is excluded
// in the first condition here.
// 2. In the case of sending timeout, the above loop was interrupted
// due to reaching timeout, but this is excluded by the second
// condition here
// 3. The 'stillConnected()' or m_bPeerHealth condition is false, of which:
// - broken/closing status is checked and responded with CONNECTION/CONNLOST
// - not connected status is checked and responded with CONNECTION/NOCONN
// - m_bPeerHealth condition is checked and responded with PEERERROR
//
// ERGO: never happens?
LOGC(mglog.Fatal,
log << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. "
"Impossible.");
return 0;
}
}
// If the sender's buffer is empty,
// record total time used for sending
if (m_pSndBuffer->getCurrBufSize() == 0)
{
CGuard::enterCS(m_StatsLock);
m_stats.sndDurationCounter = CTimer::getTime();
CGuard::leaveCS(m_StatsLock);
}
int size = len;
if (!m_bMessageAPI)
{
// For STREAM API it's allowed to send less bytes than the given buffer.
// 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);
}
{
CGuard recvAckLock(m_RecvAckLock);
// insert the user buffer into the sending list
// This should be protected by a mutex. m_SendLock does this.
m_pSndBuffer->addBuffer(data, size, mctrl.msgttl, mctrl.inorder, mctrl.srctime, Ref(mctrl.msgno));
HLOGC(dlog.Debug, log << CONID() << "sock:SENDING srctime: " << mctrl.srctime << "us DATA SIZE: " << size);
if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried.
{
// write is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
}
}
// insert this socket to the snd list if it is not on the list yet
// m_pSndUList->pop may lock CSndUList::m_ListLock and then m_RecvAckLock
m_pSndQueue->m_pSndUList->update(this, CSndUList::rescheduleIf(bCongestion));
#ifdef SRT_ENABLE_ECN
if (bCongestion)
throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0);
#endif /* SRT_ENABLE_ECN */
return size;
}
int CUDT::recv(char *data, int len)
{
if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
{
LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recv.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (m_bMessageAPI)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
return receiveMessage(data, len, Ref(mctrl));
}
return receiveBuffer(data, len);
}
int CUDT::recvmsg(char *data, int len, uint64_t &srctime)
{
if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
{
LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (m_bMessageAPI)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
int ret = receiveMessage(data, len, Ref(mctrl));
srctime = mctrl.srctime;
return ret;
}
return receiveBuffer(data, len);
}
int CUDT::recvmsg2(char *data, int len, ref_t<SRT_MSGCTRL> mctrl)
{
if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
{
LOGC(dlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (m_bMessageAPI)
return receiveMessage(data, len, mctrl);
return receiveBuffer(data, len);
}
int CUDT::receiveMessage(char *data, int len, ref_t<SRT_MSGCTRL> r_mctrl)
{
SRT_MSGCTRL &mctrl = *r_mctrl;
// Recvmsg isn't restricted to the congctl type, it's the most
// basic method of passing the data. You can retrieve data as
// they come in, however you need to match the size of the buffer.
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, -1, false))
throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0);
CGuard recvguard(m_RecvLock);
/* XXX DEBUG STUFF - enable when required
char charbool[2] = {'0', '1'};
char ptrn [] = "RECVMSG/BEGIN BROKEN 1 CONN 1 CLOSING 1 SYNCR 1 NMSG ";
int pos [] = {21, 28, 38, 46, 53};
ptrn[pos[0]] = charbool[m_bBroken];
ptrn[pos[1]] = charbool[m_bConnected];
ptrn[pos[2]] = charbool[m_bClosing];
ptrn[pos[3]] = charbool[m_bSynRecving];
int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum());
strcpy(ptrn + pos[4] + wrtlen, "\n");
fputs(ptrn, stderr);
// */
if (m_bBroken || m_bClosing)
{
int res = m_pRcvBuffer->readMsg(data, len);
mctrl.srctime = 0;
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_bTsbPd)
pthread_cond_signal(&m_RcvTsbPdCond);
if (!m_pRcvBuffer->isRcvDataReady())
{
// read is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
}
if (res == 0)
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
else
return res;
}
if (!m_bSynRecving)
{
int res = m_pRcvBuffer->readMsg(data, len, r_mctrl);
if (res == 0)
{
// read is not available any more
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
pthread_cond_signal(&m_RcvTsbPdCond);
// Shut up EPoll if no more messages in non-blocking mode
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
}
else
{
if (!m_pRcvBuffer->isRcvDataReady())
{
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
pthread_cond_signal(&m_RcvTsbPdCond);
// Shut up EPoll if no more messages in non-blocking mode
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
// After signaling the tsbpd for ready data, report the bandwidth.
double bw SRT_ATR_UNUSED = Bps2Mbps(m_iBandwidth * m_iMaxSRTPayloadSize);
HLOGC(mglog.Debug,
log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth
<< " buffers per second)");
}
return res;
}
}
int res = 0;
bool timeout = false;
// Do not block forever, check connection status each 1 sec.
uint64_t recvtmo = m_iRcvTimeOut < 0 ? 1000 : m_iRcvTimeOut;
do
{
if (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady()))
{
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_bTsbPd)
{
HLOGP(tslog.Debug, "recvmsg: KICK tsbpd()");
pthread_cond_signal(&m_RcvTsbPdCond);
}
do
{
if (CTimer::condTimedWaitUS(&m_RecvDataCond, &m_RecvLock, recvtmo * 1000) == ETIMEDOUT)
{
if (!(m_iRcvTimeOut < 0))
timeout = true;
HLOGP(tslog.Debug, "recvmsg: DATA COND: EXPIRED -- trying to get data anyway");
}
else
{
HLOGP(tslog.Debug, "recvmsg: DATA COND: KICKED.");
}
} while (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady()));
}
/* XXX DEBUG STUFF - enable when required
LOGC(dlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected
<< " CLOSING " << m_bClosing << " TMOUT " << timeout
<< " NMSG " << m_pRcvBuffer->getRcvMsgNum());
*/
res = m_pRcvBuffer->readMsg(data, len, r_mctrl);
if (m_bBroken || m_bClosing)
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
else if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
} while ((res == 0) && !timeout);
if (!m_pRcvBuffer->isRcvDataReady())
{
// Falling here means usually that res == 0 && timeout == true.
// res == 0 would repeat the above loop, unless there was also a timeout.
// timeout has interrupted the above loop, but with res > 0 this condition
// wouldn't be satisfied.
// read is not available any more
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
{
HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)");
pthread_cond_signal(&m_RcvTsbPdCond);
}
// Shut up EPoll if no more messages in non-blocking mode
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
}
// Unblock when required
// LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT");
if ((res <= 0) && (m_iRcvTimeOut >= 0))
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
return res;
}
int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block)
{
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (size <= 0 && size != -1)
return 0;
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_SEND, 0, size, -1, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK())
{
LOGC(dlog.Error,
log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected.");
throw CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
CGuard sendguard(m_SendLock);
if (m_pSndBuffer->getCurrBufSize() == 0)
{
// delay the EXP timer to avoid mis-fired timeout
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
// (fix keepalive) m_ullLastRspTime_tk = currtime_tk;
m_ullLastRspAckTime_tk = currtime_tk;
m_iReXmitCount = 1;
}
// positioning...
try
{
if (size == -1)
{
ifs.seekg(0, std::ios::end);
size = ifs.tellg();
if (offset > size)
throw 0; // let it be caught below
}
// This will also set the position back to the beginning
// in case when it was moved to the end for measuring the size.
// This will also fail if the offset exceeds size, so measuring
// the size can be skipped if not needed.
ifs.seekg((streamoff)offset);
if (!ifs.good())
throw 0;
}
catch (...)
{
// XXX It would be nice to note that this is reported
// by exception only if explicitly requested by setting
// the exception flags in the stream. Here it's fixed so
// that when this isn't set, the exception is "thrown manually".
throw CUDTException(MJ_FILESYSTEM, MN_SEEKGFAIL);
}
int64_t tosend = size;
int unitsize;
// sending block by block
while (tosend > 0)
{
if (ifs.fail())
throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL);
if (ifs.eof())
break;
unitsize = int((tosend >= block) ? block : tosend);
{
CGuard lk(m_SendBlockLock);
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth)
pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock);
}
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if (!m_bPeerHealth)
{
// reset peer health status, once this error returns, the app should handle the situation at the peer side
m_bPeerHealth = true;
throw CUDTException(MJ_PEERERROR);
}
// record total time used for sending
if (m_pSndBuffer->getCurrBufSize() == 0)
{
CGuard::enterCS(m_StatsLock);
m_stats.sndDurationCounter = CTimer::getTime();
CGuard::leaveCS(m_StatsLock);
}
{
CGuard recvAckLock(m_RecvAckLock);
const int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize);
if (sentsize > 0)
{
tosend -= sentsize;
offset += sentsize;
}
if (sndBuffersLeft() <= 0)
{
// write is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false);
}
}
// insert this socket to snd list if it is not on the list yet
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
}
return size - tosend;
}
int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block)
{
if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
if (size <= 0)
return 0;
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_RECV, 0, size, -1, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
if (m_bTsbPd)
{
LOGC(dlog.Error, log << "Reading from file is incompatible with TSBPD mode and would cause a deadlock\n");
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
}
CGuard recvguard(m_RecvLock);
// Well, actually as this works over a FILE (fstream), not just a stream,
// the size can be measured anyway and predicted if setting the offset might
// have a chance to work or not.
// positioning...
try
{
if (offset > 0)
{
// Don't do anything around here if the offset == 0, as this
// is the default offset after opening. Whether this operation
// is performed correctly, it highly depends on how the file
// has been open. For example, if you want to overwrite parts
// of an existing file, the file must exist, and the ios::trunc
// flag must not be set. If the file is open for only ios::out,
// then the file will be truncated since the offset position on
// at the time when first written; if ios::in|ios::out, then
// it won't be truncated, just overwritten.
// What is required here is that if offset is 0, don't try to
// change the offset because this might be impossible with
// the current flag set anyway.
// Also check the status and CAUSE exception manually because
// you don't know, as well, whether the user has set exception
// flags.
ofs.seekp((streamoff)offset);
if (!ofs.good())
throw 0; // just to get caught :)
}
}
catch (...)
{
// XXX It would be nice to note that this is reported
// by exception only if explicitly requested by setting
// the exception flags in the stream. For a case, when it's not,
// an additional explicit throwing happens when failbit is set.
throw CUDTException(MJ_FILESYSTEM, MN_SEEKPFAIL);
}
int64_t torecv = size;
int unitsize = block;
int recvsize;
// receiving... "recvfile" is always blocking
while (torecv > 0)
{
if (ofs.fail())
{
// send the sender a signal so it will not be blocked forever
int32_t err_code = CUDTException::EFILE;
sendCtrl(UMSG_PEERERROR, &err_code);
throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL);
}
pthread_mutex_lock(&m_RecvDataLock);
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
pthread_cond_wait(&m_RecvDataCond, &m_RecvDataLock);
pthread_mutex_unlock(&m_RecvDataLock);
if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
unitsize = int((torecv == -1 || torecv >= block) ? block : torecv);
recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize);
if (recvsize > 0)
{
torecv -= recvsize;
offset += recvsize;
}
}
if (!m_pRcvBuffer->isRcvDataReady())
{
// read is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false);
}
return size - torecv;
}
void CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous)
{
if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
CGuard statsguard(m_StatsLock);
uint64_t currtime = CTimer::getTime();
perf->msTimeStamp = (currtime - m_stats.startTime) / 1000;
perf->pktSent = m_stats.traceSent;
perf->pktRecv = m_stats.traceRecv;
perf->pktSndLoss = m_stats.traceSndLoss;
perf->pktRcvLoss = m_stats.traceRcvLoss;
perf->pktRetrans = m_stats.traceRetrans;
perf->pktRcvRetrans = m_stats.traceRcvRetrans;
perf->pktSentACK = m_stats.sentACK;
perf->pktRecvACK = m_stats.recvACK;
perf->pktSentNAK = m_stats.sentNAK;
perf->pktRecvNAK = m_stats.recvNAK;
perf->usSndDuration = m_stats.sndDuration;
perf->pktReorderDistance = m_stats.traceReorderDistance;
perf->pktReorderTolerance = m_iReorderTolerance;
perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime;
perf->pktRcvBelated = m_stats.traceRcvBelated;
perf->pktSndFilterExtra = m_stats.sndFilterExtra;
perf->pktRcvFilterExtra = m_stats.rcvFilterExtra;
perf->pktRcvFilterSupply = m_stats.rcvFilterSupply;
perf->pktRcvFilterLoss = m_stats.rcvFilterLoss;
/* perf byte counters include all headers (SRT+UDP+IP) */
const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE;
perf->byteSent = m_stats.traceBytesSent + (m_stats.traceSent * pktHdrSize);
perf->byteRecv = m_stats.traceBytesRecv + (m_stats.traceRecv * pktHdrSize);
perf->byteRetrans = m_stats.traceBytesRetrans + (m_stats.traceRetrans * pktHdrSize);
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
perf->byteRcvLoss = m_stats.traceRcvBytesLoss + (m_stats.traceRcvLoss * pktHdrSize);
#endif
perf->pktSndDrop = m_stats.traceSndDrop;
perf->pktRcvDrop = m_stats.traceRcvDrop + m_stats.traceRcvUndecrypt;
perf->byteSndDrop = m_stats.traceSndBytesDrop + (m_stats.traceSndDrop * pktHdrSize);
perf->byteRcvDrop =
m_stats.traceRcvBytesDrop + (m_stats.traceRcvDrop * pktHdrSize) + m_stats.traceRcvBytesUndecrypt;
perf->pktRcvUndecrypt = m_stats.traceRcvUndecrypt;
perf->byteRcvUndecrypt = m_stats.traceRcvBytesUndecrypt;
perf->pktSentTotal = m_stats.sentTotal;
perf->pktRecvTotal = m_stats.recvTotal;
perf->pktSndLossTotal = m_stats.sndLossTotal;
perf->pktRcvLossTotal = m_stats.rcvLossTotal;
perf->pktRetransTotal = m_stats.retransTotal;
perf->pktSentACKTotal = m_stats.sentACKTotal;
perf->pktRecvACKTotal = m_stats.recvACKTotal;
perf->pktSentNAKTotal = m_stats.sentNAKTotal;
perf->pktRecvNAKTotal = m_stats.recvNAKTotal;
perf->usSndDurationTotal = m_stats.m_sndDurationTotal;
perf->byteSentTotal = m_stats.bytesSentTotal + (m_stats.sentTotal * pktHdrSize);
perf->byteRecvTotal = m_stats.bytesRecvTotal + (m_stats.recvTotal * pktHdrSize);
perf->byteRetransTotal = m_stats.bytesRetransTotal + (m_stats.retransTotal * pktHdrSize);
perf->pktSndFilterExtraTotal = m_stats.sndFilterExtraTotal;
perf->pktRcvFilterExtraTotal = m_stats.rcvFilterExtraTotal;
perf->pktRcvFilterSupplyTotal = m_stats.rcvFilterSupplyTotal;
perf->pktRcvFilterLossTotal = m_stats.rcvFilterLossTotal;
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
perf->byteRcvLossTotal = m_stats.rcvBytesLossTotal + (m_stats.rcvLossTotal * pktHdrSize);
#endif
perf->pktSndDropTotal = m_stats.sndDropTotal;
perf->pktRcvDropTotal = m_stats.rcvDropTotal + m_stats.m_rcvUndecryptTotal;
perf->byteSndDropTotal = m_stats.sndBytesDropTotal + (m_stats.sndDropTotal * pktHdrSize);
perf->byteRcvDropTotal =
m_stats.rcvBytesDropTotal + (m_stats.rcvDropTotal * pktHdrSize) + m_stats.m_rcvBytesUndecryptTotal;
perf->pktRcvUndecryptTotal = m_stats.m_rcvUndecryptTotal;
perf->byteRcvUndecryptTotal = m_stats.m_rcvBytesUndecryptTotal;
//<
double interval = double(currtime - m_stats.lastSampleTime);
//>mod
perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval;
perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval;
//<
perf->usPktSndPeriod = m_ullInterval_tk / double(m_ullCPUFrequency);
perf->pktFlowWindow = m_iFlowWindowSize;
perf->pktCongestionWindow = (int)m_dCongestionWindow;
perf->pktFlightSize = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) - 1;
perf->msRTT = (double)m_iRTT / 1000.0;
//>new
perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0;
perf->msRcvTsbPdDelay = m_bTsbPd ? m_iTsbPdDelay_ms : 0;
perf->byteMSS = m_iMSS;
perf->mbpsMaxBW = m_llMaxBW > 0 ? Bps2Mbps(m_llMaxBW) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0;
//<
uint32_t availbw = (uint64_t)(m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth);
perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize));
if (pthread_mutex_trylock(&m_ConnectionLock) == 0)
{
if (m_pSndBuffer)
{
#ifdef SRT_ENABLE_SNDBUFSZ_MAVG
if (instantaneous)
{
/* Get instant SndBuf instead of moving average for application-based Algorithm
(such as NAE) in need of fast reaction to network condition changes. */
perf->pktSndBuf = m_pSndBuffer->getCurrBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf));
}
else
{
perf->pktSndBuf = m_pSndBuffer->getAvgBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf));
}
#else
perf->pktSndBuf = m_pSndBuffer->getCurrBufSize(Ref(perf->byteSndBuf), Ref(perf->msSndBuf));
#endif
perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize);
//<
perf->byteAvailSndBuf = (m_iSndBufSize - perf->pktSndBuf) * m_iMSS;
}
else
{
perf->byteAvailSndBuf = 0;
// new>
perf->pktSndBuf = 0;
perf->byteSndBuf = 0;
perf->msSndBuf = 0;
//<
}
if (m_pRcvBuffer)
{
perf->byteAvailRcvBuf = m_pRcvBuffer->getAvailBufSize() * m_iMSS;
// new>
#ifdef SRT_ENABLE_RCVBUFSZ_MAVG
if (instantaneous) // no need for historical API for Rcv side
{
perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf);
}
else
{
perf->pktRcvBuf = m_pRcvBuffer->getRcvAvgDataSize(perf->byteRcvBuf, perf->msRcvBuf);
}
#else
perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf);
#endif
//<
}
else
{
perf->byteAvailRcvBuf = 0;
// new>
perf->pktRcvBuf = 0;
perf->byteRcvBuf = 0;
perf->msRcvBuf = 0;
//<
}
pthread_mutex_unlock(&m_ConnectionLock);
}
else
{
perf->byteAvailSndBuf = 0;
perf->byteAvailRcvBuf = 0;
// new>
perf->pktSndBuf = 0;
perf->byteSndBuf = 0;
perf->msSndBuf = 0;
perf->byteRcvBuf = 0;
perf->msRcvBuf = 0;
//<
}
if (clear)
{
m_stats.traceSndDrop = 0;
m_stats.traceRcvDrop = 0;
m_stats.traceSndBytesDrop = 0;
m_stats.traceRcvBytesDrop = 0;
m_stats.traceRcvUndecrypt = 0;
m_stats.traceRcvBytesUndecrypt = 0;
// new>
m_stats.traceBytesSent = m_stats.traceBytesRecv = m_stats.traceBytesRetrans = 0;
//<
m_stats.traceSent = m_stats.traceRecv = m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans =
m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0;
m_stats.sndDuration = 0;
m_stats.traceRcvRetrans = 0;
m_stats.traceRcvBelated = 0;
#ifdef SRT_ENABLE_LOSTBYTESCOUNT
m_stats.traceRcvBytesLoss = 0;
#endif
m_stats.sndFilterExtra = 0;
m_stats.rcvFilterExtra = 0;
m_stats.rcvFilterSupply = 0;
m_stats.rcvFilterLoss = 0;
m_stats.lastSampleTime = currtime;
}
}
void CUDT::updateCC(ETransmissionEvent evt, EventVariant arg)
{
// Special things that must be done HERE, not in SrtCongestion,
// because it involves the input buffer in CUDT. It would be
// slightly dangerous to give SrtCongestion access to it.
// According to the rules, the congctl should be ready at the same
// time when the sending buffer. For sanity check, check both first.
if (!m_CongCtl.ready() || !m_pSndBuffer)
{
LOGC(mglog.Error,
log << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY")
<< "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created"));
return;
}
HLOGC(mglog.Debug, log << "updateCC: EVENT:" << TransmissionEventStr(evt));
if (evt == TEV_INIT)
{
// only_input uses:
// 0: in the beginning and when SRTO_MAXBW was changed
// 1: SRTO_INPUTBW was changed
// 2: SRTO_OHEADBW was changed
EInitEvent only_input = arg.get<EventVariant::INIT>();
// false = TEV_INIT_RESET: in the beginning, or when MAXBW was changed.
if (only_input && m_llMaxBW)
{
HLOGC(mglog.Debug, log << "updateCC/TEV_INIT: non-RESET stage and m_llMaxBW already set to " << m_llMaxBW);
// Don't change
}
else // either m_llMaxBW == 0 or only_input == TEV_INIT_RESET
{
// 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
// Bytes/s
int bw = m_llMaxBW != 0 ? m_llMaxBW : // When used SRTO_MAXBW
m_llInputBW != 0 ? withOverhead(m_llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW
0; // When both MAXBW and INPUTBW are 0, request in-buffer sampling
// Note: setting bw == 0 uses BW_INFINITE value in LiveCC
m_CongCtl->updateBandwidth(m_llMaxBW, bw);
if (only_input == TEV_INIT_OHEADBW)
{
// On updated SRTO_OHEADBW don't change input rate.
// This only influences the call to withOverhead().
}
else
{
// No need to calculate input reate if the bandwidth is set
const bool disable_in_rate_calc = (bw != 0);
m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc);
}
HLOGC(mglog.Debug,
log << "updateCC/TEV_INIT: updating BW=" << m_llMaxBW
<< (only_input == TEV_INIT_RESET
? " (UNCHANGED)"
: only_input == TEV_INIT_OHEADBW ? " (only Overhead)" : " (updated sampling rate)"));
}
}
// This part is also required only by LiveCC, however not
// moved there due to that it needs access to CSndBuffer.
if (evt == TEV_ACK || evt == TEV_LOSSREPORT || evt == TEV_CHECKTIMER)
{
// Specific part done when MaxBW is set to 0 (auto) and InputBW is 0.
// This requests internal input rate sampling.
if (m_llMaxBW == 0 && m_llInputBW == 0)
{
// Get auto-calculated input rate, Bytes per second
const int64_t inputbw = m_pSndBuffer->getInputRate();
/*
* On blocked transmitter (tx full) and until connection closes,
* auto input rate falls to 0 but there may be still lot of packet to retransmit
* Calling updateBandwidth with 0 sets maxBW to default BW_INFINITE (1 Gbps)
* and sendrate skyrockets for retransmission.
* Keep previously set maximum in that case (inputbw == 0).
*/
if (inputbw != 0)
m_CongCtl->updateBandwidth(0, withOverhead(inputbw)); // Bytes/sec
}
}
HLOGC(mglog.Debug, log << "udpateCC: emitting signal for EVENT:" << TransmissionEventStr(evt));
// Now execute a congctl-defined action for that event.
EmitSignal(evt, arg);
// This should be done with every event except ACKACK and SEND/RECEIVE
// After any action was done by the congctl, update the congestion window and sending interval.
if (evt != TEV_ACKACK && evt != TEV_SEND && evt != TEV_RECEIVE)
{
// This part comes from original UDT.
// NOTE: THESE things come from CCC class:
// - m_dPktSndPeriod
// - m_dCWndSize
m_ullInterval_tk = (uint64_t)(m_CongCtl->pktSndPeriod_us() * m_ullCPUFrequency);
m_dCongestionWindow = m_CongCtl->cgWindowSize();
#if ENABLE_HEAVY_LOGGING
HLOGC(mglog.Debug,
log << "updateCC: updated values from congctl: interval=" << m_ullInterval_tk << "tk ("
<< m_CongCtl->pktSndPeriod_us() << "us) cgwindow=" << std::setprecision(3) << m_dCongestionWindow);
#endif
}
HLOGC(mglog.Debug, log << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt));
#if 0 // debug
static int callcnt = 0;
if (!(callcnt++ % 250)) cerr << "SndPeriod=" << (m_ullInterval_tk/m_ullCPUFrequency) << "\n");
#endif
}
void CUDT::initSynch()
{
pthread_mutex_init(&m_SendBlockLock, NULL);
pthread_cond_init(&m_SendBlockCond, NULL);
pthread_mutex_init(&m_RecvDataLock, NULL);
pthread_cond_init(&m_RecvDataCond, NULL);
pthread_mutex_init(&m_SendLock, NULL);
pthread_mutex_init(&m_RecvLock, NULL);
pthread_mutex_init(&m_RcvLossLock, NULL);
pthread_mutex_init(&m_RecvAckLock, NULL);
pthread_mutex_init(&m_RcvBufferLock, NULL);
pthread_mutex_init(&m_ConnectionLock, NULL);
pthread_mutex_init(&m_StatsLock, NULL);
memset(&m_RcvTsbPdThread, 0, sizeof m_RcvTsbPdThread);
pthread_cond_init(&m_RcvTsbPdCond, NULL);
}
void CUDT::destroySynch()
{
pthread_mutex_destroy(&m_SendBlockLock);
pthread_cond_destroy(&m_SendBlockCond);
pthread_mutex_destroy(&m_RecvDataLock);
pthread_cond_destroy(&m_RecvDataCond);
pthread_mutex_destroy(&m_SendLock);
pthread_mutex_destroy(&m_RecvLock);
pthread_mutex_destroy(&m_RcvLossLock);
pthread_mutex_destroy(&m_RecvAckLock);
pthread_mutex_destroy(&m_RcvBufferLock);
pthread_mutex_destroy(&m_ConnectionLock);
pthread_mutex_destroy(&m_StatsLock);
pthread_cond_destroy(&m_RcvTsbPdCond);
}
void CUDT::releaseSynch()
{
// wake up user calls
pthread_mutex_lock(&m_SendBlockLock);
pthread_cond_signal(&m_SendBlockCond);
pthread_mutex_unlock(&m_SendBlockLock);
pthread_mutex_lock(&m_SendLock);
pthread_mutex_unlock(&m_SendLock);
pthread_mutex_lock(&m_RecvDataLock);
pthread_cond_signal(&m_RecvDataCond);
pthread_mutex_unlock(&m_RecvDataLock);
pthread_mutex_lock(&m_RecvLock);
pthread_cond_signal(&m_RcvTsbPdCond);
pthread_mutex_unlock(&m_RecvLock);
pthread_mutex_lock(&m_RecvDataLock);
if (!pthread_equal(m_RcvTsbPdThread, pthread_t()))
{
pthread_join(m_RcvTsbPdThread, NULL);
m_RcvTsbPdThread = pthread_t();
}
pthread_mutex_unlock(&m_RecvDataLock);
pthread_mutex_lock(&m_RecvLock);
pthread_mutex_unlock(&m_RecvLock);
}
#if ENABLE_HEAVY_LOGGING
static void DebugAck(string hdr, int prev, int ack)
{
if (!prev)
{
HLOGC(mglog.Debug, log << hdr << "ACK " << ack);
return;
}
prev = CSeqNo::incseq(prev);
int diff = CSeqNo::seqoff(prev, ack);
if (diff < 0)
{
HLOGC(mglog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")");
return;
}
bool shorted = diff > 100; // sanity
if (shorted)
ack = CSeqNo::incseq(prev, 100);
ostringstream ackv;
for (; prev != ack; prev = CSeqNo::incseq(prev))
ackv << prev << " ";
if (shorted)
ackv << "...";
HLOGC(mglog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack);
}
#else
static inline void DebugAck(string, int, int) {}
#endif
void CUDT::sendCtrl(UDTMessageType pkttype, const void *lparam, void *rparam, int size)
{
CPacket ctrlpkt;
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
int nbsent = 0;
int local_prevack = 0;
#if ENABLE_HEAVY_LOGGING
struct SaveBack
{
int & target;
const int &source;
~SaveBack() { target = source; }
} l_saveback = {m_iDebugPrevLastAck, m_iRcvLastAck};
(void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable]
local_prevack = m_iDebugPrevLastAck;
#endif
switch (pkttype)
{
case UMSG_ACK: // 010 - Acknowledgement
{
int32_t ack;
// If there is no loss, the ACK is the current largest sequence number plus 1;
// Otherwise it is the smallest sequence number in the receiver loss list.
if (m_pRcvLossList->getLossLength() == 0)
ack = CSeqNo::incseq(m_iRcvCurrSeqNo);
else
ack = m_pRcvLossList->getFirstLostSeq();
if (m_iRcvLastAckAck == ack)
break;
// send out a lite ACK
// to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number
if (size == SEND_LITE_ACK)
{
ctrlpkt.pack(pkttype, NULL, &ack, size);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
DebugAck("sendCtrl(lite):" + CONID(), local_prevack, ack);
break;
}
// There are new received packets to acknowledge, update related information.
/* tsbpd thread may also call ackData when skipping packet so protect code */
CGuard::enterCS(m_RcvBufferLock);
// IF ack > m_iRcvLastAck
if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0)
{
int acksize = CSeqNo::seqoff(m_iRcvLastSkipAck, ack);
IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck);
m_iRcvLastAck = ack;
m_iRcvLastSkipAck = ack;
// XXX Unknown as to whether it matters.
// This if (acksize) causes that ackData() won't be called.
// With size == 0 it wouldn't do anything except calling CTimer::triggerEvent().
// This, again, signals the condition, CTimer::m_EventCond.
// This releases CTimer::waitForEvent() call used in CUDTUnited::selectEx().
// Preventing to call this on zero size makes sense, if it prevents false alerts.
if (acksize > 0)
m_pRcvBuffer->ackData(acksize);
CGuard::leaveCS(m_RcvBufferLock);
// If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond,
// signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which
// will signal m_RecvDataCond when there's time to play for particular
// data packet.
HLOGC(dlog.Debug,
log << "ACK: clip %" << oldack << "-%" << ack << ", REVOKED " << acksize << " from RCV buffer");
if (m_bTsbPd)
{
/* Newly acknowledged data, signal TsbPD thread */
pthread_mutex_lock(&m_RecvLock);
if (m_bTsbPdAckWakeup)
pthread_cond_signal(&m_RcvTsbPdCond);
pthread_mutex_unlock(&m_RecvLock);
}
else
{
if (m_bSynRecving)
{
// signal a waiting "recv" call if there is any data available
pthread_mutex_lock(&m_RecvDataLock);
pthread_cond_signal(&m_RecvDataCond);
pthread_mutex_unlock(&m_RecvDataLock);
}
// acknowledge any waiting epolls to read
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true);
CTimer::triggerEvent();
}
CGuard::enterCS(m_RcvBufferLock);
}
else if (ack == m_iRcvLastAck)
{
// If the ACK was just sent already AND elapsed time did not exceed RTT,
if ((currtime_tk - m_ullLastAckTime_tk) < ((m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency))
{
CGuard::leaveCS(m_RcvBufferLock);
break;
}
}
else
{
// Not possible (m_iRcvCurrSeqNo+1 < m_iRcvLastAck ?)
CGuard::leaveCS(m_RcvBufferLock);
break;
}
// [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]]
// Send out the ACK only if has not been received by the sender before
if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0)
{
// NOTE: The BSTATS feature turns on extra fields above size 6
// also known as ACKD_TOTAL_SIZE_VER100.
int32_t data[ACKD_TOTAL_SIZE];
// Case you care, CAckNo::incack does exactly the same thing as
// CSeqNo::incseq. Logically the ACK number is a different thing
// than sequence number (it's a "journal" for ACK request-response,
// and starts from 0, unlike sequence, which starts from a random
// number), but still the numbers are from exactly the same domain.
m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo);
data[ACKD_RCVLASTACK] = m_iRcvLastAck;
data[ACKD_RTT] = m_iRTT;
data[ACKD_RTTVAR] = m_iRTTVar;
data[ACKD_BUFFERLEFT] = m_pRcvBuffer->getAvailBufSize();
// a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock
if (data[ACKD_BUFFERLEFT] < 2)
data[ACKD_BUFFERLEFT] = 2;
// NOTE: m_CongCtl->ACKTimeout_us() should be taken into account.
if (currtime_tk - m_ullLastAckTime_tk > m_ullACKInt_tk)
{
int rcvRate;
int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size
data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed(Ref(rcvRate));
data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth();
//>>Patch while incompatible (1.0.2) receiver floating around
if (m_lPeerSrtVersion == SrtVersion(1, 0, 2))
{
data[ACKD_RCVRATE] = rcvRate; // bytes/sec
data[ACKD_XMRATE] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec
ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102;
}
else if (m_lPeerSrtVersion >= SrtVersion(1, 0, 3))
{
// Normal, currently expected version.
data[ACKD_RCVRATE] = rcvRate; // bytes/sec
ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101;
}
// ELSE: leave the buffer with ...UDTBASE size.
ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ctrlsz);
CTimer::rdtsc(m_ullLastAckTime_tk);
}
else
{
ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL);
}
ctrlpkt.m_iID = m_PeerID;
ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
DebugAck("sendCtrl: " + CONID(), local_prevack, ack);
m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck);
CGuard::enterCS(m_StatsLock);
++m_stats.sentACK;
++m_stats.sentACKTotal;
CGuard::leaveCS(m_StatsLock);
}
CGuard::leaveCS(m_RcvBufferLock);
break;
}
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
ctrlpkt.pack(pkttype, lparam);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
break;
case UMSG_LOSSREPORT: // 011 - Loss Report
{
// Explicitly defined lost sequences
if (rparam)
{
int32_t *lossdata = (int32_t *)rparam;
size_t bytes = sizeof(*lossdata) * size;
ctrlpkt.pack(pkttype, NULL, lossdata, bytes);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
CGuard::enterCS(m_StatsLock);
++m_stats.sentNAK;
++m_stats.sentNAKTotal;
CGuard::leaveCS(m_StatsLock);
}
// Call with no arguments - get loss list from internal data.
else if (m_pRcvLossList->getLossLength() > 0)
{
// 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)
{
ctrlpkt.pack(pkttype, NULL, data, losslen * 4);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
CGuard::enterCS(m_StatsLock);
++m_stats.sentNAK;
++m_stats.sentNAKTotal;
CGuard::leaveCS(m_StatsLock);
}
delete[] data;
}
// update next NAK time, which should wait enough time for the retansmission, but not too long
m_ullNAKInt_tk = (m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency;
// Fix the NAKreport period according to the congctl
m_ullNAKInt_tk = m_CongCtl->updateNAKInterval(
m_ullNAKInt_tk, m_RcvTimeWindow.getPktRcvSpeed(), m_pRcvLossList->getLossLength());
// This is necessary because a congctl need not wish to define
// its own minimum interval, in which case the default one is used.
if (m_ullNAKInt_tk < m_ullMinNakInt_tk)
m_ullNAKInt_tk = m_ullMinNakInt_tk;
break;
}
case UMSG_CGWARNING: // 100 - Congestion Warning
ctrlpkt.pack(pkttype);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
CTimer::rdtsc(m_ullLastWarningTime);
break;
case UMSG_KEEPALIVE: // 001 - Keep-alive
ctrlpkt.pack(pkttype);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
break;
case UMSG_HANDSHAKE: // 000 - Handshake
ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake));
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
break;
case UMSG_SHUTDOWN: // 101 - Shutdown
ctrlpkt.pack(pkttype);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
break;
case UMSG_DROPREQ: // 111 - Msg drop request
ctrlpkt.pack(pkttype, lparam, rparam, 8);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
break;
case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error
ctrlpkt.pack(pkttype, lparam);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt);
break;
case UMSG_EXT: // 0x7FFF - Resevered for future use
break;
default:
break;
}
// Fix keepalive
if (nbsent)
m_ullLastSndTime_tk = currtime_tk;
}
void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno)
{
// Update sender's loss list and acknowledge packets in the sender's buffer
{
// m_RecvAckLock protects sender's loss list and epoll
CGuard ack_lock(m_RecvAckLock);
const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno);
// IF distance between m_iSndLastDataAck and ack is nonempty...
if (offset <= 0)
return;
// update sending variables
m_iSndLastDataAck = ackdata_seqno;
// remove any loss that predates 'ack' (not to be considered loss anymore)
m_pSndLossList->remove(CSeqNo::decseq(m_iSndLastDataAck));
// acknowledge the sending buffer (remove data that predate 'ack')
m_pSndBuffer->ackData(offset);
// acknowledde any waiting epolls to write
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
}
// insert this socket to snd list if it is not on the list yet
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
if (m_bSynSending)
{
CGuard lk(m_SendBlockLock);
pthread_cond_signal(&m_SendBlockCond);
}
const int64_t currtime = CTimer::getTime();
// record total time used for sending
CGuard::enterCS(m_StatsLock);
m_stats.sndDuration += currtime - m_stats.sndDurationCounter;
m_stats.m_sndDurationTotal += currtime - m_stats.sndDurationCounter;
m_stats.sndDurationCounter = currtime;
CGuard::leaveCS(m_StatsLock);
}
void CUDT::processCtrlAck(const CPacket &ctrlpkt, const uint64_t currtime_tk)
{
const int32_t *ackdata = (const int32_t *)ctrlpkt.m_pcData;
const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK];
const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK;
HLOGC(mglog.Debug,
log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck
<< "]" << (isLiteAck ? "[LITE]" : "[FULL]"));
updateSndLossListOnACK(ackdata_seqno);
// Process a lite ACK
if (isLiteAck)
{
if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
{
CGuard ack_lock(m_RecvAckLock);
m_iFlowWindowSize -= CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno);
m_iSndLastAck = ackdata_seqno;
// TODO: m_ullLastRspAckTime_tk should be protected with m_RecvAckLock
// because the sendmsg2 may want to change it at the same time.
m_ullLastRspAckTime_tk = currtime_tk;
m_iReXmitCount = 1; // Reset re-transmit count since last ACK
}
return;
}
// Decide to send ACKACK or not
{
// Sequence number of the ACK packet
const int32_t ack_seqno = ctrlpkt.getAckSeqNo();
// Send ACK acknowledgement (UMSG_ACKACK).
// There can be less ACKACK packets in the stream, than the number of ACK packets.
// Only send ACKACK every syn interval or if ACK packet with the sequence number
// already acknowledged (with ACKACK) has come again, which probably means ACKACK was lost.
const uint64_t now = CTimer::getTime();
if ((now - m_ullSndLastAck2Time > (uint64_t)COMM_SYN_INTERVAL_US) || (ack_seqno == m_iSndLastAck2))
{
sendCtrl(UMSG_ACKACK, &ack_seqno);
m_iSndLastAck2 = ack_seqno;
m_ullSndLastAck2Time = now;
}
}
//
// Begin of the new code with TLPKTDROP.
//
// Protect packet retransmission
CGuard::enterCS(m_RecvAckLock);
// Check the validation of the ack
if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0)
{
CGuard::leaveCS(m_RecvAckLock);
// this should not happen: attack or bug
LOGC(glog.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)
{
// Update Flow Window Size, must update before and together with m_iSndLastAck
m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT];
m_iSndLastAck = ackdata_seqno;
m_ullLastRspAckTime_tk = currtime_tk; // Should be protected with m_RecvAckLock
m_iReXmitCount = 1; // Reset re-transmit count since last ACK
}
/*
* We must not ignore full ack received by peer
* if data has been artificially acked by late packet drop.
* 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,
* 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
CGuard::leaveCS(m_RecvAckLock);
return;
}
m_iSndLastFullAck = ackdata_seqno;
//
// END of the new code with TLPKTDROP
//
CGuard::leaveCS(m_RecvAckLock);
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(mglog.Error,
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(mglog.Error, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!");
// Ack is already interpreted, just skip further parts.
return;
}
// This check covers fields up to ACKD_BUFFERLEFT.
// Update RTT
// m_iRTT = ackdata[ACKD_RTT];
// m_iRTTVar = ackdata[ACKD_RTTVAR];
// XXX These ^^^ commented-out were blocked in UDT;
// the current RTT calculations are exactly the same as in UDT4.
const int rtt = ackdata[ACKD_RTT];
m_iRTTVar = avg_iir<4>(m_iRTTVar, abs(rtt - m_iRTT));
m_iRTT = avg_iir<8>(m_iRTT, rtt);
/* Version-dependent fields:
* Original UDT (total size: ACKD_TOTAL_SIZE_SMALL):
* ACKD_RCVLASTACK
* ACKD_RTT
* ACKD_RTTVAR
* ACKD_BUFFERLEFT
* Additional UDT fields, not always attached:
* ACKD_RCVSPEED
* ACKD_BANDWIDTH
* SRT extension version 1.0.2 (bstats):
* ACKD_RCVRATE
* SRT extension version 1.0.4:
* ACKD_XMRATE
*/
if (acksize > ACKD_TOTAL_SIZE_SMALL)
{
// This means that ACKD_RCVSPEED and ACKD_BANDWIDTH fields are available.
int pktps = ackdata[ACKD_RCVSPEED];
int bandwidth = ackdata[ACKD_BANDWIDTH];
int bytesps;
/* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE]) 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 */
if (acksize > ACKD_TOTAL_SIZE_UDTBASE)
bytesps = ackdata[ACKD_RCVRATE];
else
bytesps = pktps * m_iMaxSRTPayloadSize;
m_iBandwidth = avg_iir<8>(m_iBandwidth, bandwidth);
m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate, pktps);
m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate, bytesps);
// XXX not sure if ACKD_XMRATE is of any use. This is simply
// calculated as ACKD_BANDWIDTH * m_iMaxSRTPayloadSize.
// Update Estimated Bandwidth and packet delivery rate
// m_iRcvRate = m_iDeliveryRate;
// ^^ This has been removed because with the SrtCongestion class
// instead of reading the m_iRcvRate local field this will read
// cudt->deliveryRate() instead.
}
checkSndTimers(REGEN_KM);
updateCC(TEV_ACK, ackdata_seqno);
CGuard::enterCS(m_StatsLock);
++m_stats.recvACK;
++m_stats.recvACKTotal;
CGuard::leaveCS(m_StatsLock);
}
void CUDT::processCtrl(CPacket &ctrlpkt)
{
// Just heard from the peer, reset the expiration count.
m_iEXPCount = 1;
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
m_ullLastRspTime_tk = currtime_tk;
bool using_rexmit_flag = m_bPeerRexmitFlag;
HLOGC(mglog.Debug,
log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " ("
<< MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID);
switch (ctrlpkt.getType())
{
case UMSG_ACK: // 010 - Acknowledgement
processCtrlAck(ctrlpkt, currtime_tk);
break;
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
{
int32_t ack = 0;
int rtt = -1;
// update RTT
rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack);
if (rtt <= 0)
{
LOGC(mglog.Error,
log << "IPE: ACK node overwritten when acknowledging " << ctrlpkt.getAckSeqNo()
<< " (ack extracted: " << ack << ")");
break;
}
// if increasing delay detected...
// sendCtrl(UMSG_CGWARNING);
// RTT EWMA
m_iRTTVar = (m_iRTTVar * 3 + abs(rtt - m_iRTT)) >> 2;
m_iRTT = (m_iRTT * 7 + rtt) >> 3;
updateCC(TEV_ACKACK, ack);
// This function will put a lock on m_RecvLock by itself, as needed.
// It must be done inside because this function reads the current time
// and if waiting for the lock has caused a delay, the time will be
// inaccurate. Additionally it won't lock if TSBPD mode is off, and
// won't update anything. Note that if you set TSBPD mode and use
// srt_recvfile (which doesn't make any sense), you'll have a deadlock.
m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), m_RecvLock);
// update last ACK that has been received by the sender
if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0)
m_iRcvLastAckAck = ack;
break;
}
case UMSG_LOSSREPORT: // 011 - Loss Report
{
int32_t *losslist = (int32_t *)(ctrlpkt.m_pcData);
size_t losslist_len = ctrlpkt.getLength() / 4;
bool secure = true;
// protect packet retransmission
CGuard::enterCS(m_RecvAckLock);
// This variable is used in "normal" logs, so it may cause a warning
// when logging is forcefully off.
int32_t wrong_loss SRT_ATR_UNUSED = CSeqNo::m_iMaxSeqNo;
// decode loss list message and insert loss into the sender loss list
for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++i)
{
if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST))
{
// Then it's this is a <lo, hi> specification with HI in a consecutive cell.
int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]);
int32_t losslist_hi = losslist[i + 1];
// <lo, hi> specification means that the consecutive cell has been already interpreted.
++i;
HLOGF(mglog.Debug,
"received UMSG_LOSSREPORT: %d-%d (%d packets)...",
losslist_lo,
losslist_hi,
CSeqNo::seqoff(losslist_lo, losslist_hi) + 1);
if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) ||
(CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0))
{
// seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq
secure = false;
wrong_loss = losslist_hi;
// XXX leaveCS: really necessary? 'break' will break the 'for' loop, not the 'switch' statement.
// and the leaveCS is done again next to the 'for' loop end.
CGuard::leaveCS(m_RecvAckLock);
break;
}
int num = 0;
if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0)
num = m_pSndLossList->insert(losslist_lo, losslist_hi);
else if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0)
{
// This should be theoretically impossible because this would mean
// that the received packet loss report informs about the loss that predates
// the ACK sequence.
// However, this can happen if the packet reordering has caused the earlier sent
// LOSSREPORT will be delivered after later sent ACK. Whatever, ACK should be
// more important, so simply drop the part that predates ACK.
num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi);
}
CGuard::enterCS(m_StatsLock);
m_stats.traceSndLoss += num;
m_stats.sndLossTotal += num;
CGuard::leaveCS(m_StatsLock);
}
else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0)
{
HLOGF(mglog.Debug, "received UMSG_LOSSREPORT: %d (1 packet)...", losslist[i]);
if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0)
{
// seq_a must not be greater than the most recent sent seq
secure = false;
wrong_loss = losslist[i];
CGuard::leaveCS(m_RecvAckLock);
break;
}
int num = m_pSndLossList->insert(losslist[i], losslist[i]);
CGuard::enterCS(m_StatsLock);
m_stats.traceSndLoss += num;
m_stats.sndLossTotal += num;
CGuard::leaveCS(m_StatsLock);
}
}
CGuard::leaveCS(m_RecvAckLock);
updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len));
if (!secure)
{
LOGC(mglog.Warn,
log << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo
<< " vs loss %" << wrong_loss);
// this should not happen: attack or bug
m_bBroken = true;
m_iBrokenCounter = 0;
break;
}
// the lost packet (retransmission) should be sent out immediately
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
CGuard::enterCS(m_StatsLock);
++m_stats.recvNAK;
++m_stats.recvNAKTotal;
CGuard::leaveCS(m_StatsLock);
break;
}
case UMSG_CGWARNING: // 100 - Delay Warning
// One way packet delay is increasing, so decrease the sending rate
m_ullInterval_tk = (uint64_t)ceil(m_ullInterval_tk * 1.125);
m_iLastDecSeq = m_iSndCurrSeqNo;
// XXX Note as interesting fact: this is only prepared for handling,
// but nothing in the code is sending this message. Probably predicted
// for a custom congctl. There's a predicted place to call it under
// UMSG_ACKACK handling, but it's commented out.
break;
case UMSG_KEEPALIVE: // 001 - Keep-alive
// The only purpose of keep-alive packet is to tell that the peer is still alive
// nothing needs to be done.
break;
case UMSG_HANDSHAKE: // 000 - Handshake
{
CHandShake req;
req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength());
HLOGC(mglog.Debug, log << "processCtrl: got HS: " << req.show());
if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...???
|| (m_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
// resend the handshake packet
// This condition embraces cases when:
// - this is normal accept() and URQ_INDUCTION was received
// - this is rendezvous accept() and there's coming any kind of URQ except AGREEMENT (should be RENDEZVOUS
// or CONCLUSION)
// - this is any of URQ_ERROR_* - well...
CHandShake initdata;
initdata.m_iISN = m_iISN;
initdata.m_iMSS = m_iMSS;
initdata.m_iFlightFlagSize = m_iFlightFlagSize;
// For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT.
// For client-server we do URQ_INDUCTION --> URQ_CONCLUSION.
initdata.m_iReqType = (!m_bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT;
initdata.m_iID = m_SocketID;
uint32_t kmdata[SRTDATA_MAXSIZE];
size_t kmdatasize = SRTDATA_MAXSIZE;
bool have_hsreq = false;
if (req.m_iVersion > HS_VERSION_UDT4)
{
initdata.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener...
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
if (hs_flags != 0) // has SRT extensions
{
HLOGC(mglog.Debug,
log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)
<< " WITH SRT ext");
have_hsreq = interpretSrtHandshake(req, ctrlpkt, kmdata, &kmdatasize);
if (!have_hsreq)
{
initdata.m_iVersion = 0;
m_RejectReason = SRT_REJ_ROGUE;
initdata.m_iReqType = URQFailure(m_RejectReason);
}
else
{
// Extensions are added only in case of CONCLUSION (not AGREEMENT).
// Actually what is expected here is that this may either process the
// belated-repeated handshake from a caller (and then it's CONCLUSION,
// and should be added with HSRSP/KMRSP), or it's a belated handshake
// of Rendezvous when it has already considered itself connected.
// Sanity check - according to the rules, there should be no such situation
if (m_bRendezvous && m_SrtHsSide == HSD_RESPONDER)
{
LOGC(mglog.Error,
log << "processCtrl/HS: IPE???: RESPONDER should receive all its handshakes in "
"handshake phase.");
}
// The 'extension' flag will be set from this variable; set it to false
// in case when the AGREEMENT response is to be sent.
have_hsreq = initdata.m_iReqType == URQ_CONCLUSION;
HLOGC(mglog.Debug,
log << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType)
<< " kmdatasize=" << kmdatasize);
}
}
else
{
HLOGC(mglog.Debug, log << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType));
}
}
else
{
initdata.m_iVersion = HS_VERSION_UDT4;
}
initdata.m_extension = have_hsreq;
HLOGC(mglog.Debug,
log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType)
<< (have_hsreq ? " WITH SRT HS response extensions" : ""));
// XXX here interpret SRT handshake extension
CPacket response;
response.setControl(UMSG_HANDSHAKE);
response.allocate(m_iMaxSRTPayloadSize);
// If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE.
// There is also no possible IPE condition in case of HSv4 - for this version it will always return true.
if (createSrtHandshake(Ref(response), Ref(initdata), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize))
{
response.m_iID = m_PeerID;
response.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
int nbsent = m_pSndQueue->sendto(m_pPeerAddr, response);
if (nbsent)
{
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
m_ullLastSndTime_tk = currtime_tk;
}
}
}
else
{
HLOGC(mglog.Debug, log << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED.");
}
break;
}
case UMSG_SHUTDOWN: // 101 - Shutdown
m_bShutdown = true;
m_bClosing = true;
m_bBroken = true;
m_iBrokenCounter = 60;
// Signal the sender and recver if they are waiting for data.
releaseSynch();
// Unblock any call so they learn the connection_broken error
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true);
CTimer::triggerEvent();
break;
case UMSG_DROPREQ: // 111 - Msg drop request
CGuard::enterCS(m_RecvLock);
m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag);
CGuard::leaveCS(m_RecvLock);
dropFromLossLists(*(int32_t *)ctrlpkt.m_pcData, *(int32_t *)(ctrlpkt.m_pcData + 4));
// move forward with current recv seq no.
if ((CSeqNo::seqcmp(*(int32_t *)ctrlpkt.m_pcData, CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) &&
(CSeqNo::seqcmp(*(int32_t *)(ctrlpkt.m_pcData + 4), m_iRcvCurrSeqNo) > 0))
{
m_iRcvCurrSeqNo = *(int32_t *)(ctrlpkt.m_pcData + 4);
}
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
// giving the app a chance to fix the issue
m_bPeerHealth = false;
break;
case UMSG_EXT: // 0x7FFF - reserved and user defined messages
HLOGF(mglog.Debug, "CONTROL EXT MSG RECEIVED: %08X\n", ctrlpkt.getExtendedType());
{
// This has currently two roles in SRT:
// - HSv4 (legacy) handshake
// - refreshed KMX (initial KMX is done still in the HS process in HSv5)
bool understood = processSrtMsg(&ctrlpkt);
// CAREFUL HERE! This only means that this update comes from the UMSG_EXT
// message received, REGARDLESS OF WHAT IT IS. This version doesn't mean
// the handshake version, but the reason of calling this function.
//
// Fortunately, the only messages taken into account in this function
// are HSREQ and HSRSP, which should *never* be interchanged when both
// parties are HSv5.
if (understood)
{
updateAfterSrtHandshake(ctrlpkt.getExtendedType(), HS_VERSION_UDT4);
}
else
{
updateCC(TEV_CUSTOM, &ctrlpkt);
}
}
break;
default:
break;
}
}
void CUDT::updateSrtRcvSettings()
{
if (m_bTsbPd)
{
/* We are TsbPd receiver */
CGuard::enterCS(m_RecvLock);
m_pRcvBuffer->setRcvTsbPdMode(m_ullRcvPeerStartTime, m_iTsbPdDelay_ms * 1000);
CGuard::leaveCS(m_RecvLock);
HLOGF(mglog.Debug,
"AFTER HS: Set Rcv TsbPd mode: delay=%u.%03u secs",
m_iTsbPdDelay_ms / 1000,
m_iTsbPdDelay_ms % 1000);
}
else
{
HLOGC(mglog.Debug, log << "AFTER HS: Rcv TsbPd mode not set");
}
}
void CUDT::updateSrtSndSettings()
{
if (m_bPeerTsbPd)
{
/* We are TsbPd sender */
// XXX Check what happened here.
// m_iPeerTsbPdDelay_ms = m_CongCtl->getSndPeerTsbPdDelay();// + ((m_iRTT + (4 * m_iRTTVar)) / 1000);
/*
* For sender to apply Too-Late Packet Drop
* option (m_bTLPktDrop) must be enabled and receiving peer shall support it
*/
HLOGF(mglog.Debug,
"AFTER HS: Set Snd TsbPd mode %s: delay=%d.%03d secs",
m_bPeerTLPktDrop ? "with TLPktDrop" : "without TLPktDrop",
m_iPeerTsbPdDelay_ms / 1000,
m_iPeerTsbPdDelay_ms % 1000);
}
else
{
HLOGC(mglog.Debug, log << "AFTER HS: Snd TsbPd mode not set");
}
}
void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv)
{
switch (srt_cmd)
{
case SRT_CMD_HSREQ:
case SRT_CMD_HSRSP:
break;
default:
return;
}
// The only possibility here is one of these two:
// - Agent is RESPONDER and it receives HSREQ.
// - Agent is INITIATOR and it receives HSRSP.
//
// In HSv4, INITIATOR is sender and RESPONDER is receiver.
// In HSv5, both are sender AND receiver.
//
// This function will be called only ONCE in this
// instance, through either HSREQ or HSRSP.
if (hsv > HS_VERSION_UDT4)
{
updateSrtRcvSettings();
updateSrtSndSettings();
}
else if (srt_cmd == SRT_CMD_HSRSP)
{
// HSv4 INITIATOR is sender
updateSrtSndSettings();
}
else
{
// HSv4 RESPONDER is receiver
updateSrtRcvSettings();
}
}
int CUDT::packLostData(CPacket &packet, uint64_t &origintime)
{
// protect m_iSndLastDataAck from updating by ACK processing
CGuard ackguard(m_RecvAckLock);
while ((packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0)
{
const int offset = CSeqNo::seqoff(m_iSndLastDataAck, packet.m_iSeqNo);
if (offset < 0)
{
LOGC(dlog.Error,
log << "IPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo " << packet.m_iSeqNo
<< ", m_iSndLastDataAck " << m_iSndLastDataAck << ")=" << offset << ". Continue");
continue;
}
int msglen;
const int payload = m_pSndBuffer->readData(&(packet.m_pcData), offset, packet.m_iMsgNo, origintime, msglen);
SRT_ASSERT(payload != 0);
if (payload == -1)
{
int32_t seqpair[2];
seqpair[0] = packet.m_iSeqNo;
seqpair[1] = CSeqNo::incseq(seqpair[0], msglen);
sendCtrl(UMSG_DROPREQ, &packet.m_iMsgNo, seqpair, 8);
// only one msg drop request is necessary
m_pSndLossList->remove(seqpair[1]);
// skip all dropped packets
if (CSeqNo::seqcmp(m_iSndCurrSeqNo, CSeqNo::incseq(seqpair[1])) < 0)
m_iSndCurrSeqNo = CSeqNo::incseq(seqpair[1]);
continue;
}
// NOTE: This is just a sanity check. Returning 0 is impossible to happen
// in case of retransmission. If the offset was a positive value, then the
// block must exist in the old blocks because it wasn't yet cut off by ACK
// and has been already recorded as sent (otherwise the peer wouldn't send
// back the loss report). May something happen here in case when the send
// loss record has been updated by the FASTREXMIT.
else if (payload == 0)
continue;
// 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.forceUnlock();
CGuard::enterCS(m_StatsLock);
++m_stats.traceRetrans;
++m_stats.retransTotal;
m_stats.traceBytesRetrans += payload;
m_stats.bytesRetransTotal += payload;
CGuard::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)
{
packet.m_iMsgNo |= PACKET_SND_REXMIT;
}
return payload;
}
return 0;
}
int CUDT::packData(CPacket &packet, uint64_t &ts_tk)
{
int payload = 0;
bool probe = false;
uint64_t origintime = 0;
bool new_packet_packed = false;
bool filter_ctl_pkt = false;
int kflg = EK_NOENC;
uint64_t entertime_tk;
CTimer::rdtsc(entertime_tk);
#if 0 // debug: TimeDiff histogram
static int lldiffhisto[23] = {0};
static int llnodiff = 0;
if (m_ullTargetTime_tk != 0)
{
int ofs = 11 + ((entertime_tk - m_ullTargetTime_tk)/(int64_t)m_ullCPUFrequency)/1000;
if (ofs < 0) ofs = 0;
else if (ofs > 22) ofs = 22;
lldiffhisto[ofs]++;
}
else if(m_ullTargetTime_tk == 0)
{
llnodiff++;
}
static int callcnt = 0;
if (!(callcnt++ % 5000)) {
fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n",
lldiffhisto[0],lldiffhisto[1],lldiffhisto[2],lldiffhisto[3],lldiffhisto[4],lldiffhisto[5],
lldiffhisto[6],lldiffhisto[7],lldiffhisto[8],lldiffhisto[9],lldiffhisto[10],lldiffhisto[11]);
fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n",
lldiffhisto[12],lldiffhisto[13],lldiffhisto[14],lldiffhisto[15],lldiffhisto[16],lldiffhisto[17],
lldiffhisto[18],lldiffhisto[19],lldiffhisto[20],lldiffhisto[21],lldiffhisto[21],llnodiff);
}
#endif
if ((0 != m_ullTargetTime_tk) && (entertime_tk > m_ullTargetTime_tk))
m_ullTimeDiff_tk += entertime_tk - m_ullTargetTime_tk;
string reason;
payload = packLostData(packet, origintime);
if (payload > 0)
{
reason = "reXmit";
}
else if (m_PacketFilter &&
m_PacketFilter.packControlPacket(Ref(packet), m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags()))
{
HLOGC(mglog.Debug, log << "filter: filter/CTL packet ready - packing instead of data.");
payload = packet.getLength();
reason = "filter";
filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set
// Stats
{
CGuard lg(m_StatsLock);
++m_stats.sndFilterExtra;
++m_stats.sndFilterExtraTotal;
}
}
else
{
// If no loss, and no packetfilter control packet, pack a new packet.
// check congestion/flow window limit
int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow));
int seqdiff = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo));
if (cwnd >= seqdiff)
{
// XXX Here it's needed to set kflg to msgno_bitset in the block stored in the
// send buffer. This should be somehow avoided, the crypto flags should be set
// together with encrypting, and the packet should be sent as is, when rexmitting.
// It would be nice to research as to whether CSndBuffer::Block::m_iMsgNoBitset field
// isn't a useless redundant state copy. If it is, then taking the flags here can be removed.
kflg = m_pCryptoControl->getSndCryptoFlags();
payload = m_pSndBuffer->readData(&(packet.m_pcData), packet.m_iMsgNo, origintime, kflg);
if (payload)
{
m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo);
// m_pCryptoControl->m_iSndCurrSeqNo = m_iSndCurrSeqNo;
packet.m_iSeqNo = m_iSndCurrSeqNo;
// every 16 (0xF) packets, a packet pair is sent
if ((packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0)
probe = true;
new_packet_packed = true;
}
else
{
m_ullTargetTime_tk = 0;
m_ullTimeDiff_tk = 0;
ts_tk = 0;
return 0;
}
}
else
{
HLOGC(dlog.Debug,
log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow
<< ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << seqdiff);
m_ullTargetTime_tk = 0;
m_ullTimeDiff_tk = 0;
ts_tk = 0;
return 0;
}
reason = "normal";
}
// Normally packet.m_iTimeStamp field is set exactly here,
// usually as taken from m_StartTime and current time, unless live
// mode in which case it is based on 'origintime' as set during scheduling.
// In case when this is a filter control packet, the m_iTimeStamp field already
// contains the exactly needed value, and it's a timestamp clip, not a real
// timestamp.
if (!filter_ctl_pkt)
{
if (m_bPeerTsbPd)
{
/*
* When timestamp is carried over in this sending stream from a received stream,
* it may be older than the session start time causing a negative packet time
* that may block the receiver's Timestamp-based Packet Delivery.
* XXX Isn't it then better to not decrease it by m_StartTime? As long as it
* doesn't screw up the start time on the other side.
*/
if (origintime >= m_stats.startTime)
packet.m_iTimeStamp = int(origintime - m_stats.startTime);
else
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
}
else
{
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
}
}
packet.m_iID = m_PeerID;
packet.setLength(payload);
/* Encrypt if 1st time this packet is sent and crypto is enabled */
if (kflg)
{
// XXX Encryption flags are already set on the packet before calling this.
// See readData() above.
if (m_pCryptoControl->encrypt(Ref(packet)))
{
// Encryption failed
//>>Add stats for crypto failure
ts_tk = 0;
LOGC(dlog.Error, log << "ENCRYPT FAILED - packet won't be sent, size=" << payload);
return -1; // Encryption failed
}
payload = packet.getLength(); /* Cipher may change length */
reason += " (encrypted)";
}
if (new_packet_packed && m_PacketFilter)
{
HLOGC(mglog.Debug, log << "filter: Feeding packet for source clip");
m_PacketFilter.feedSource(Ref(packet));
}
#if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr()
HLOGC(mglog.Debug,
log << CONID() << "packData: " << reason << " packet seq=" << packet.m_iSeqNo << " (ACK=" << m_iSndLastAck
<< " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << packet.MessageFlagStr() << ")");
#endif
// Fix keepalive
m_ullLastSndTime_tk = entertime_tk;
considerLegacySrtHandshake(0);
// WARNING: TEV_SEND is the only event that is reported from
// the CSndQueue::worker thread. All others are reported from
// CRcvQueue::worker. If you connect to this signal, make sure
// that you are aware of prospective simultaneous access.
updateCC(TEV_SEND, &packet);
// XXX This was a blocked code also originally in UDT. Probably not required.
// Left untouched for historical reasons.
// Might be possible that it was because of that this is send from
// different thread than the rest of the signals.
// m_pSndTimeWindow->onPktSent(packet.m_iTimeStamp);
CGuard::enterCS(m_StatsLock);
m_stats.traceBytesSent += payload;
m_stats.bytesSentTotal += payload;
++m_stats.traceSent;
++m_stats.sentTotal;
CGuard::leaveCS(m_StatsLock);
if (probe)
{
// sends out probing packet pair
ts_tk = entertime_tk;
probe = false;
}
else
{
#if USE_BUSY_WAITING
ts_tk = entertime_tk + m_ullInterval_tk;
#else
if (m_ullTimeDiff_tk >= m_ullInterval_tk)
{
ts_tk = entertime_tk;
m_ullTimeDiff_tk -= m_ullInterval_tk;
}
else
{
ts_tk = entertime_tk + m_ullInterval_tk - m_ullTimeDiff_tk;
m_ullTimeDiff_tk = 0;
}
#endif
}
m_ullTargetTime_tk = ts_tk;
return payload;
}
// This is a close request, but called from the
void CUDT::processClose()
{
sendCtrl(UMSG_SHUTDOWN);
m_bShutdown = true;
m_bClosing = true;
m_bBroken = true;
m_iBrokenCounter = 60;
HLOGP(mglog.Debug, "processClose: sent message and set flags");
if (m_bTsbPd)
{
HLOGP(mglog.Debug, "processClose: lock-and-signal TSBPD");
CGuard rl(m_RecvLock);
pthread_cond_signal(&m_RcvTsbPdCond);
}
// Signal the sender and recver if they are waiting for data.
releaseSynch();
// Unblock any call so they learn the connection_broken error
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true);
HLOGP(mglog.Debug, "processClose: triggering timer event to spread the bad news");
CTimer::triggerEvent();
}
void CUDT::sendLossReport(const std::vector<std::pair<int32_t, int32_t> > &loss_seqs)
{
typedef vector<pair<int32_t, int32_t> > loss_seqs_t;
vector<int32_t> seqbuffer;
seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic
for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i)
{
if (i->first == i->second)
{
seqbuffer.push_back(i->first);
HLOGF(mglog.Debug, "lost packet %d: sending LOSSREPORT", i->first);
}
else
{
seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST);
seqbuffer.push_back(i->second);
HLOGF(mglog.Debug,
"lost packets %d-%d (%d packets): sending LOSSREPORT",
i->first,
i->second,
1 + CSeqNo::seqcmp(i->second, i->first));
}
}
if (!seqbuffer.empty())
{
sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], seqbuffer.size());
}
}
int CUDT::processData(CUnit *in_unit)
{
CPacket &packet = in_unit->m_Packet;
// XXX This should be called (exclusively) here:
// m_pRcvBuffer->addLocalTsbPdDriftSample(packet.getMsgTimeStamp());
// Just heard from the peer, reset the expiration count.
m_iEXPCount = 1;
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
m_ullLastRspTime_tk = currtime_tk;
// We are receiving data, start tsbpd thread if TsbPd is enabled
if (m_bTsbPd && pthread_equal(m_RcvTsbPdThread, pthread_t()))
{
HLOGP(mglog.Debug, "Spawning TSBPD thread");
int st = 0;
{
ThreadName tn("SRT:TsbPd");
st = pthread_create(&m_RcvTsbPdThread, NULL, CUDT::tsbpd, this);
}
if (st != 0)
return -1;
}
const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2;
#if ENABLE_HEAVY_LOGGING
static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"};
string rexmit_reason;
#endif
if (pktrexmitflag == 1)
{
// This packet was retransmitted
CGuard::enterCS(m_StatsLock);
m_stats.traceRcvRetrans++;
CGuard::leaveCS(m_StatsLock);
#if ENABLE_HEAVY_LOGGING
// Check if packet was retransmitted on request or on ack timeout
// Search the sequence in the loss record.
rexmit_reason = " by ";
if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo))
rexmit_reason += "REQUEST";
else
rexmit_reason += "ACK-TMOUT";
#endif
}
HLOGC(dlog.Debug,
log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo());
updateCC(TEV_RECEIVE, &packet);
++m_iPktCount;
const int pktsz = packet.getLength();
// Update time information
// XXX Note that this adds the byte size of a packet
// of which we don't yet know as to whether this has
// carried out some useful data or some excessive data
// that will be later discarded.
// FIXME: before adding this on the rcv time window,
// make sure that this packet isn't going to be
// effectively discarded, as repeated retransmission,
// for example, burdens the link, but doesn't better the speed.
m_RcvTimeWindow.onPktArrival(pktsz);
// Probe the packet pair if needed.
// Conditions and any extra data required for the packet
// this function will extract and test as needed.
const bool unordered = CSeqNo::seqcmp(packet.m_iSeqNo, m_iRcvCurrSeqNo) <= 0;
const bool retransmitted = m_bPeerRexmitFlag && packet.getRexmitFlag();
// Retransmitted and unordered packets do not provide expected measurement.
// We expect the 16th and 17th packet to be sent regularly,
// otherwise measurement must be rejected.
m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted);
CGuard::enterCS(m_StatsLock);
m_stats.traceBytesRecv += pktsz;
m_stats.bytesRecvTotal += pktsz;
++m_stats.traceRecv;
++m_stats.recvTotal;
CGuard::leaveCS(m_StatsLock);
typedef vector<pair<int32_t, int32_t> > loss_seqs_t;
loss_seqs_t filter_loss_seqs;
loss_seqs_t srt_loss_seqs;
vector<CUnit *> incoming;
bool was_sent_in_order = true;
bool reorder_prevent_lossreport = false;
// If the peer doesn't understand REXMIT flag, send rexmit request
// always immediately.
int initial_loss_ttl = 0;
if (m_bPeerRexmitFlag)
initial_loss_ttl = m_iReorderTolerance;
// After introduction of packet filtering, the "recordable loss detection"
// does not exactly match the true loss detection. When a FEC filter is
// working, for example, then getting one group filled with all packet but
// the last one and the FEC control packet, in this special case this packet
// won't be notified at all as lost because it will be recovered by the
// filter immediately before anyone notices what happened (and the loss
// detection for the further functionality is checked only afterwards,
// and in this case the immediate recovery makes the loss to not be noticed
// at all).
//
// Because of that the check for losses must happen BEFORE passing the packet
// to the filter and before the filter could recover the packet before anyone
// notices :)
if (packet.getMsgSeq() != 0) // disregard filter-control packets, their seq may mean nothing
{
int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo);
if (diff > 1)
{
CGuard lg(m_StatsLock);
int loss = diff - 1; // loss is all that is above diff == 1
m_stats.traceRcvLoss += loss;
m_stats.rcvLossTotal += loss;
uint64_t lossbytes = loss * m_pRcvBuffer->getRcvAvgPayloadSize();
m_stats.traceRcvBytesLoss += lossbytes;
m_stats.rcvBytesLossTotal += lossbytes;
HLOGC(mglog.Debug,
log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " "
<< CSeqNo::decseq(packet.m_iSeqNo) << "]");
}
if (diff > 0)
{
// Record if it was further than latest
m_iRcvCurrPhySeqNo = packet.m_iSeqNo;
}
}
{
// Start of offset protected section
// Prevent TsbPd thread from modifying Ack position while adding data
// offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData()
CGuard recvbuf_acklock(m_RcvBufferLock);
// vector<CUnit*> undec_units;
if (m_PacketFilter)
{
// Stuff this data into the filter
m_PacketFilter.receive(in_unit, Ref(incoming), Ref(filter_loss_seqs));
HLOGC(mglog.Debug,
log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs)
<< " loss to report, "
<< (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF"
: "REPORT ONLY THOSE"));
}
else
{
// Stuff in just one packet that has come in.
incoming.push_back(in_unit);
}
bool excessive = true; // stays true unless it was successfully added
// Needed for possibly check for needsQuickACK.
bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0);
// Loop over all incoming packets that were filtered out.
// In case when there is no filter, there's just one packet in 'incoming',
// the one that came in the input of this function.
for (vector<CUnit *>::iterator i = incoming.begin(); i != incoming.end(); ++i)
{
CUnit * u = *i;
CPacket &rpkt = u->m_Packet;
// m_iRcvLastSkipAck is the base sequence number for the receiver buffer.
// This is the offset in the buffer; if this is negative, it means that
// this sequence is already in the past and the buffer is not interested.
// Meaning, this packet will be rejected, even if it could potentially be
// one of missing packets in the transmission.
int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo);
IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED");
if (offset < 0)
{
IF_HEAVY_LOGGING(exc_type = "BELATED");
uint64_t tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp());
uint64_t bltime =
CountIIR(uint64_t(m_stats.traceBelatedTime) * 1000, CTimer::getTime() - tsbpdtime, 0.2);
CGuard::enterCS(m_StatsLock);
m_stats.traceBelatedTime = double(bltime) / 1000.0;
m_stats.traceRcvBelated++;
CGuard::leaveCS(m_StatsLock);
HLOGC(mglog.Debug,
log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/"
<< rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr());
continue;
}
const int avail_bufsize = m_pRcvBuffer->getAvailBufSize();
if (offset >= avail_bufsize)
{
// This is already a sequence discrepancy. Probably there could be found
// some way to make it continue reception by overriding the sequence and
// make a kinda TLKPTDROP, but there has been found no reliable way to do this.
if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty())
{
// Only in live mode. In File mode this shall not be possible
// because the sender should stop sending in this situation.
// In Live mode this means that there is a gap between the
// lowest sequence in the empty buffer and the incoming sequence
// that exceeds the buffer size. Receiving data in this situation
// is no longer possible and this is a point of no return.
LOGC(mglog.Error,
log << CONID() << "SEQUENCE DISCREPANCY. BREAKING CONNECTION. offset=" << offset
<< " avail=" << avail_bufsize << " ack.seq=" << m_iRcvLastSkipAck
<< " pkt.seq=" << rpkt.m_iSeqNo << " rcv-remain=" << m_pRcvBuffer->debugGetSize());
// This is a scoped lock with AckLock, but for the moment
// when processClose() is called this lock must be taken out,
// otherwise this will cause a deadlock. We don't need this
// lock anymore, and at 'return' it will be unlocked anyway.
recvbuf_acklock.forceUnlock();
processClose();
return -1;
}
else
{
LOGC(mglog.Error,
log << CONID() << "No room to store incoming packet: offset=" << offset
<< " avail=" << avail_bufsize << " ack.seq=" << m_iRcvLastSkipAck
<< " pkt.seq=" << rpkt.m_iSeqNo << " rcv-remain=" << m_pRcvBuffer->debugGetSize());
return -1;
}
}
bool adding_successful = true;
if (m_pRcvBuffer->addData(*i, offset) < 0)
{
// addData returns -1 if at the m_iLastAckPos+offset position there already is a packet.
// So this packet is "redundant".
IF_HEAVY_LOGGING(exc_type = "UNACKED");
adding_successful = false;
}
else
{
IF_HEAVY_LOGGING(exc_type = "ACCEPTED");
excessive = false;
if (u->m_Packet.getMsgCryptoFlags())
{
EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt(Ref(u->m_Packet)) : ENCS_NOTSUP;
if (rc != ENCS_CLEAR)
{
// Could not decrypt
// Keep packet in received buffer
// Crypto flags are still set
// It will be acknowledged
{
CGuard lg(m_StatsLock);
m_stats.traceRcvUndecrypt += 1;
m_stats.traceRcvBytesUndecrypt += pktsz;
m_stats.m_rcvUndecryptTotal += 1;
m_stats.m_rcvBytesUndecryptTotal += pktsz;
}
// Log message degraded to debug because it may happen very often
HLOGC(dlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data.");
adding_successful = false;
IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED");
}
}
}
HLOGC(mglog.Debug,
log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo << " offset=" << offset
<< " BUFr=" << avail_bufsize
<< " (" << exc_type << "/" << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: "
<< packet.MessageFlagStr());
// Decryption should have made the crypto flags EK_NOENC.
// Otherwise it's an error.
if (adding_successful)
{
HLOGC(dlog.Debug,
log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo));
if (CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection.
{
int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo);
int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo);
srt_loss_seqs.push_back(make_pair(seqlo, seqhi));
if (initial_loss_ttl)
{
// pack loss list for (possibly belated) NAK
// The LOSSREPORT will be sent in a while.
for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i)
{
m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl));
}
HLOGC(mglog.Debug,
log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs)
<< " tolerance: " << initial_loss_ttl);
reorder_prevent_lossreport = true;
}
}
}
// Update the current largest sequence number that has been received.
// Or it is a retransmitted packet, remove it from receiver loss list.
if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0)
{
m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received
}
else
{
unlose(rpkt); // was BELATED or RETRANSMITTED
was_sent_in_order &= 0 != pktrexmitflag;
}
}
// This is moved earlier after introducing filter because it shouldn't
// be executed in case when the packet was rejected by the receiver buffer.
// However now the 'excessive' condition may be true also in case when
// a truly non-excessive packet has been received, just it has been temporarily
// stored for better times by the filter module. This way 'excessive' is also true,
// although the old condition that a packet with a newer sequence number has arrived
// or arrived out of order may still be satisfied.
if (!incoming_belated && was_sent_in_order)
{
// Basing on some special case in the packet, it might be required
// to enforce sending ACK immediately (earlier than normally after
// a given period).
if (m_CongCtl->needsQuickACK(packet))
{
CTimer::rdtsc(m_ullNextACKTime_tk);
}
}
if (excessive)
{
return -1;
}
} // End of recvbuf_acklock
if (m_bClosing)
{
// RcvQueue worker thread can call processData while closing (or close while processData)
// This race condition exists in the UDT design but the protection against TsbPd thread
// (with AckLock) and decryption enlarged the probability window.
// Application can crash deep in decrypt stack since crypto context is deleted in close.
// RcvQueue worker thread will not necessarily be deleted with this connection as it can be
// used by others (socket multiplexer).
return -1;
}
if (incoming.empty())
{
// Treat as excessive. This is when a filter cumulates packets
// until the loss is rebuilt, or eats up a filter control packet
return -1;
}
if (!srt_loss_seqs.empty())
{
// A loss is detected
{
// TODO: Can unlock rcvloss after m_pRcvLossList->insert(...)?
// And probably protect m_FreshLoss as well.
HLOGC(mglog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING.");
// if record_loss == false, nothing will be contained here
// Insert lost sequence numbers to the receiver loss list
CGuard lg(m_RcvLossLock);
for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i)
{
// If loss found, insert them to the receiver loss list
m_pRcvLossList->insert(i->first, i->second);
}
}
const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS;
if (!reorder_prevent_lossreport && report_recorded_loss)
{
HLOGC(mglog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs));
sendLossReport(srt_loss_seqs);
}
if (m_bTsbPd)
{
pthread_mutex_lock(&m_RecvLock);
pthread_cond_signal(&m_RcvTsbPdCond);
pthread_mutex_unlock(&m_RecvLock);
}
}
// Separately report loss records of those reported by a filter.
// ALWAYS report whatever has been reported back by a filter. Note that
// the filter never reports anything when rexmit fallback level is ALWAYS or NEVER.
// With ALWAYS only those are reported that were recorded here by SRT.
// With NEVER, nothing is to be reported.
if (!filter_loss_seqs.empty())
{
HLOGC(mglog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs));
sendLossReport(filter_loss_seqs);
if (m_bTsbPd)
{
pthread_mutex_lock(&m_RecvLock);
pthread_cond_signal(&m_RcvTsbPdCond);
pthread_mutex_unlock(&m_RecvLock);
}
}
// Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it.
// PERFORMANCE CONSIDERATIONS:
// This list is quite inefficient as a data type and finding the candidate to send UMSG_LOSSREPORT
// is linear time. On the other hand, there are some special cases that are important for performance:
// - only the first (plus some following) could have had TTL drown to 0
// - the only (little likely) possibility that the next-to-first record has TTL=0 is when there was
// a loss range split (due to unlose() of one sequence)
// - first found record with TTL>0 means end of "ready to LOSSREPORT" records
// So:
// All you have to do is:
// - start with first element and continue with next elements, as long as they have TTL=0
// 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)
// can be quite well optimized.
vector<int32_t> lossdata;
{
CGuard lg(m_RcvLossLock);
// XXX There was a mysterious crash around m_FreshLoss. When the initial_loss_ttl is 0
// (that is, "belated loss report" feature is off), don't even touch m_FreshLoss.
if (initial_loss_ttl && !m_FreshLoss.empty())
{
deque<CRcvFreshLoss>::iterator i = m_FreshLoss.begin();
// 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 unlose) sequence that has split one detected loss
// into two records.
for (; i != m_FreshLoss.end() && i->ttl <= 0; ++i)
{
HLOGF(mglog.Debug,
"Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT",
i->seq[0],
i->seq[1],
CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1);
addLossRecord(lossdata, i->seq[0], i->seq[1]);
}
// Remove elements that have been processed and prepared for lossreport.
if (i != m_FreshLoss.begin())
{
m_FreshLoss.erase(m_FreshLoss.begin(), i);
i = m_FreshLoss.begin();
}
if (m_FreshLoss.empty())
{
HLOGP(mglog.Debug, "NO MORE FRESH LOSS RECORDS.");
}
else
{
HLOGF(mglog.Debug,
"STILL %" PRIzu " FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d",
m_FreshLoss.size(),
i->seq[0],
i->seq[1],
1 + CSeqNo::seqoff(i->seq[0], i->seq[1]),
i->ttl);
}
// 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], lossdata.size());
}
// was_sent_in_order means either of:
// - packet was sent in order (first if branch above)
// - packet was sent as old, but was a retransmitted packet
if (m_bPeerRexmitFlag && was_sent_in_order)
{
++m_iConsecOrderedDelivery;
if (m_iConsecOrderedDelivery >= 50)
{
m_iConsecOrderedDelivery = 0;
if (m_iReorderTolerance > 0)
{
m_iReorderTolerance--;
CGuard::enterCS(m_StatsLock);
m_stats.traceReorderDistance--;
CGuard::leaveCS(m_StatsLock);
HLOGF(mglog.Debug,
"ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d",
m_iReorderTolerance);
}
}
}
return 0;
}
/// This function is called when a packet has arrived, which was behind the current
/// received sequence - that is, belated or retransmitted. Try to remove the packet
/// from both loss records: the general loss record and the fresh loss record.
///
/// Additionally, check - if supported by the peer - whether the "latecoming" packet
/// has been sent due to retransmission or due to reordering, by checking the rexmit
/// support flag and rexmit flag itself. If this packet was surely ORIGINALLY SENT
/// it means that the current network connection suffers of packet reordering. This
/// way try to introduce a dynamic tolerance by calculating the difference between
/// the current packet reception sequence and this packet's sequence. This value
/// will be set to the tolerance value, which means that later packet retransmission
/// will not be required immediately, but only after receiving N next packets that
/// do not include the lacking packet.
/// The tolerance is not increased infinitely - it's bordered by m_iMaxReorderTolerance.
/// This value can be set in options - SRT_LOSSMAXTTL.
void CUDT::unlose(const CPacket &packet)
{
CGuard lg(m_RcvLossLock);
int32_t sequence = packet.m_iSeqNo;
m_pRcvLossList->remove(sequence);
// Rest of this code concerns only the "belated lossreport" feature.
bool has_increased_tolerance = false;
bool was_reordered = false;
if (m_bPeerRexmitFlag)
{
// If the peer understands the REXMIT flag, it means that the REXMIT flag is contained
// in the PH_MSGNO field.
// The packet is considered coming originally (just possibly out of order), if REXMIT
// flag is NOT set.
was_reordered = !packet.getRexmitFlag();
if (was_reordered)
{
HLOGF(mglog.Debug, "received out-of-band packet seq %d", sequence);
const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo));
CGuard::enterCS(m_StatsLock);
m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance);
CGuard::leaveCS(m_StatsLock);
if (seqdiff > m_iReorderTolerance)
{
const int new_tolerance = min(seqdiff, m_iMaxReorderTolerance);
HLOGF(mglog.Debug,
"Belated by %d seqs - Reorder tolerance %s %d",
seqdiff,
(new_tolerance == m_iReorderTolerance) ? "REMAINS with" : "increased to",
new_tolerance);
m_iReorderTolerance = new_tolerance;
has_increased_tolerance =
true; // Yes, even if reorder tolerance is already at maximum - this prevents decreasing tolerance.
}
}
else
{
HLOGC(mglog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence);
}
}
else
{
HLOGF(mglog.Debug, "received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence);
}
// Don't do anything if "belated loss report" feature is not used.
// In that case the FreshLoss list isn't being filled in at all, the
// loss report is sent directly.
// Note that this condition blocks two things being done in this function:
// - remove given sequence from the fresh loss record
// (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)
return;
size_t i = 0;
int had_ttl = 0;
for (i = 0; i < m_FreshLoss.size(); ++i)
{
had_ttl = m_FreshLoss[i].ttl;
switch (m_FreshLoss[i].revoke(sequence))
{
case CRcvFreshLoss::NONE:
continue; // Not found. Search again.
case CRcvFreshLoss::STRIPPED:
goto breakbreak; // Found and the modification is applied. We're done here.
case CRcvFreshLoss::DELETE:
// No more elements. Kill it.
m_FreshLoss.erase(m_FreshLoss.begin() + i);
// Every loss is unique. We're done here.
goto breakbreak;
case CRcvFreshLoss::SPLIT:
// Oh, this will be more complicated. This means that it was in between.
{
// So create a new element that will hold the upper part of the range,
// and this one modify to be the lower part of the range.
// Keep the current end-of-sequence value for the second element
int32_t next_end = m_FreshLoss[i].seq[1];
// seq-1 set to the end of this element
m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence);
// seq+1 set to the begin of the next element
int32_t next_begin = CSeqNo::incseq(sequence);
// Use position of the NEXT element because insertion happens BEFORE pointed element.
// Use the same TTL (will stay the same in the other one).
m_FreshLoss.insert(m_FreshLoss.begin() + i + 1,
CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl));
}
goto breakbreak;
}
}
// Could have made the "return" instruction instead of goto, but maybe there will be something
// to add in future, so keeping that.
breakbreak:;
if (i != m_FreshLoss.size())
{
HLOGF(mglog.Debug, "sequence %d removed from belated lossreport record", sequence);
}
if (was_reordered)
{
m_iConsecOrderedDelivery = 0;
if (has_increased_tolerance)
{
m_iConsecEarlyDelivery = 0; // reset counter
}
else if (had_ttl > 2)
{
++m_iConsecEarlyDelivery; // otherwise, and if it arrived quite earlier, increase counter
HLOGF(mglog.Debug, "... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery);
// After 10 consecutive
if (m_iConsecEarlyDelivery >= 10)
{
m_iConsecEarlyDelivery = 0;
if (m_iReorderTolerance > 0)
{
m_iReorderTolerance--;
CGuard::enterCS(m_StatsLock);
m_stats.traceReorderDistance--;
CGuard::leaveCS(m_StatsLock);
HLOGF(mglog.Debug,
"... reached %d times - decreasing tolerance to %d",
m_iConsecEarlyDelivery,
m_iReorderTolerance);
}
}
}
// If hasn't increased tolerance, but the packet appeared at TTL less than 2, do nothing.
}
}
void CUDT::dropFromLossLists(int32_t from, int32_t to)
{
CGuard lg(m_RcvLossLock);
m_pRcvLossList->remove(from, to);
HLOGF(mglog.Debug, "TLPKTDROP seq %d-%d (%d packets)", from, to, CSeqNo::seqoff(from, to));
if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0)
return;
// All code below concerns only "belated lossreport" feature.
// It's highly unlikely that this is waiting to send a belated UMSG_LOSSREPORT,
// so treat it rather as a sanity check.
// It's enough to check if the first element of the list starts with a sequence older than 'to'.
// If not, just do nothing.
size_t delete_index = 0;
for (size_t i = 0; i < m_FreshLoss.size(); ++i)
{
CRcvFreshLoss::Emod result = m_FreshLoss[i].revoke(from, to);
switch (result)
{
case CRcvFreshLoss::DELETE:
delete_index = i + 1; // PAST THE END
continue; // There may be further ranges that are included in this one, so check on.
case CRcvFreshLoss::NONE:
case CRcvFreshLoss::STRIPPED:
break; // THIS BREAKS ONLY 'switch', not 'for'!
case CRcvFreshLoss::SPLIT:; // This function never returns it. It's only a compiler shut-up.
}
break; // Now this breaks also FOR.
}
m_FreshLoss.erase(m_FreshLoss.begin(),
m_FreshLoss.begin() + delete_index); // with delete_index == 0 will do nothing
}
// This function, as the name states, should bake a new cookie.
int32_t CUDT::bake(const sockaddr *addr, int32_t current_cookie, int correction)
{
static unsigned int distractor = 0;
unsigned int rollover = distractor + 10;
for (;;)
{
// SYN cookie
char clienthost[NI_MAXHOST];
char clientport[NI_MAXSERV];
getnameinfo(addr,
(m_iIPversion == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6),
clienthost,
sizeof(clienthost),
clientport,
sizeof(clientport),
NI_NUMERICHOST | NI_NUMERICSERV);
int64_t timestamp = ((CTimer::getTime() - m_stats.startTime) / 60000000) + distractor -
correction; // secret changes every one minute
stringstream cookiestr;
cookiestr << clienthost << ":" << clientport << ":" << timestamp;
union {
unsigned char cookie[16];
int32_t cookie_val;
};
CMD5::compute(cookiestr.str().c_str(), cookie);
if (cookie_val != current_cookie)
return cookie_val;
++distractor;
// This is just to make the loop formally breakable,
// but this is virtually impossible to happen.
if (distractor == rollover)
return cookie_val;
}
}
// XXX This is quite a mystery, why this function has a return value
// and what the purpose for it was. There's just one call of this
// function in the whole code and in that call the return value is
// ignored. Actually this call happens in the CRcvQueue::worker thread,
// where it makes a response for incoming UDP packet that might be
// a connection request. Should any error occur in this process, there
// is no way to "report error" that happened here. Basing on that
// these values in original UDT code were quite like the values
// for m_iReqType, they have been changed to URQ_* symbols, which
// may mean that the intent for the return value was to send this
// value back as a control packet back to the connector.
//
// This function is run when the CRcvQueue object is reading packets
// from the multiplexer (@c CRcvQueue::worker_RetrieveUnit) and the
// target socket ID is 0.
//
// XXX Make this function return EConnectStatus enum type (extend if needed),
// and this will be directly passed to the caller.
SRT_REJECT_REASON CUDT::processConnectRequest(const sockaddr *addr, CPacket &packet)
{
// XXX ASSUMPTIONS:
// [[using assert(packet.m_iID == 0)]]
HLOGC(mglog.Debug, log << "processConnectRequest: received a connection request");
if (m_bClosing)
{
m_RejectReason = SRT_REJ_CLOSE;
HLOGC(mglog.Debug, log << "processConnectRequest: ... NOT. Rejecting because closing.");
return m_RejectReason;
}
/*
* Closing a listening socket only set bBroken
* If a connect packet is received while closing it gets through
* processing and crashes later.
*/
if (m_bBroken)
{
m_RejectReason = SRT_REJ_CLOSE;
HLOGC(mglog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken.");
return m_RejectReason;
}
size_t exp_len =
CHandShake::m_iContentSize; // When CHandShake::m_iContentSize is used in log, the file fails to link!
// NOTE!!! Old version of SRT code checks if the size of the HS packet
// is EQUAL to the above CHandShake::m_iContentSize.
// Changed to < exp_len because we actually need that the packet
// be at least of a size for handshake, although it may contain
// more data, depending on what's inside.
if (packet.getLength() < exp_len)
{
m_RejectReason = SRT_REJ_ROGUE;
HLOGC(mglog.Debug,
log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len
<< ")");
return m_RejectReason;
}
// Dunno 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))
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(mglog.Error, log << "processConnectRequest: the packet received as handshake is not a handshake message");
return m_RejectReason;
}
CHandShake hs;
hs.load_from(packet.m_pcData, packet.getLength());
// XXX MOST LIKELY this hs should be now copied into m_ConnRes field, which holds
// the handshake structure sent from the peer (no matter the role or mode).
// This should simplify the createSrtHandshake() function which can this time
// simply write the crafted handshake structure into m_ConnReq, which needs no
// participation of the local handshake and passing it as a parameter through
// newConnection() -> acceptAndRespond() -> createSrtHandshake(). This is also
// required as a source of the peer's information used in processing in other
// structures.
int32_t cookie_val = bake(addr);
HLOGC(mglog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val);
// REQUEST:INDUCTION.
// Set a cookie, a target ID, and send back the same as
// RESPONSE:INDUCTION.
if (hs.m_iReqType == URQ_INDUCTION)
{
HLOGC(mglog.Debug, log << "processConnectRequest: received type=induction, sending back with cookie+socket");
// XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp
// is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies
// only the first 4 bytes. Moreover, it's dangerous on some platforms because the char
// array need not be aligned to int32_t - changed to union in a hope that using int32_t
// inside a union will enforce whole union to be aligned to int32_t.
hs.m_iCookie = cookie_val;
packet.m_iID = hs.m_iID;
// Ok, now's the time. The listener sets here the version 5 handshake,
// even though the request was 4. This is because the old client would
// simply return THE SAME version, not even looking into it, giving the
// listener false impression as if it supported version 5.
//
// If the caller was really HSv4, it will simply ignore the version 5 in INDUCTION;
// it will respond with CONCLUSION, but with its own set version, which is version 4.
//
// If the caller was really HSv5, it will RECOGNIZE this version 5 in INDUCTION, so
// it will respond with version 5 when sending CONCLUSION.
hs.m_iVersion = HS_VERSION_SRT1;
// Additionally, set this field to a MAGIC value. This field isn't used during INDUCTION
// by HSv4 client, HSv5 client can use it to additionally verify that this is a HSv5 listener.
// In this field we also advertise the PBKEYLEN value. When 0, it's considered not advertised.
hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_iSndCryptoKeyLen);
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
HLOGC(mglog.Debug,
log << "processConnectRequest: " << (whether ? "" : "NOT ")
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
size_t size = packet.getLength();
hs.store_to(packet.m_pcData, Ref(size));
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
m_pSndQueue->sendto(addr, packet);
return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code.
}
// Otherwise this should be REQUEST:CONCLUSION.
// Should then come with the correct cookie that was
// set in the above INDUCTION, in the HS_VERSION_SRT1
// should also contain extra data.
HLOGC(mglog.Debug,
log << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie...");
if (hs.m_iCookie != cookie_val)
{
cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie
if (hs.m_iCookie != cookie_val)
{
m_RejectReason = SRT_REJ_RDVCOOKIE;
HLOGC(mglog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring.");
return m_RejectReason;
}
HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding.");
}
else
{
HLOGC(mglog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding.");
}
int32_t id = hs.m_iID;
// HANDSHAKE: The old client sees the version that does not match HS_VERSION_UDT4 (5).
// In this case it will respond with URQ_ERROR_REJECT. Rest of the data are the same
// as in the handshake request. When this message is received, the connector side should
// switch itself to the version number HS_VERSION_UDT4 and continue the old way (that is,
// continue sending URQ_INDUCTION, but this time with HS_VERSION_UDT4).
bool accepted_hs = true;
if (hs.m_iVersion == HS_VERSION_SRT1)
{
// No further check required.
// The m_iType contains handshake extension flags.
}
else if (hs.m_iVersion == HS_VERSION_UDT4)
{
// In UDT, and so in older SRT version, the hs.m_iType field should contain
// the socket type, although SRT only allowed this field to be UDT_DGRAM.
// Older SRT version contained that value in a field, but now that this can
// only contain UDT_DGRAM the field itself has been abandoned.
// For the sake of any old client that reports version 4 handshake, interpret
// this hs.m_iType field as a socket type and check if it's UDT_DGRAM.
// Note that in HSv5 hs.m_iType contains extension flags.
if (hs.m_iType != UDT_DGRAM)
{
m_RejectReason = SRT_REJ_ROGUE;
accepted_hs = false;
}
}
else
{
// Unsupported version
// (NOTE: This includes "version=0" which is a rejection flag).
m_RejectReason = SRT_REJ_VERSION;
accepted_hs = false;
}
if (!accepted_hs)
{
HLOGC(mglog.Debug,
log << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason
<< " MSG: " << srt_rejectreason_str(m_RejectReason));
// mismatch, reject the request
hs.m_iReqType = URQFailure(m_RejectReason);
size_t size = CHandShake::m_iContentSize;
hs.store_to(packet.m_pcData, Ref(size));
packet.m_iID = id;
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
m_pSndQueue->sendto(addr, packet);
}
else
{
SRT_REJECT_REASON error = SRT_REJ_UNKNOWN;
int result = s_UDTUnited.newConnection(m_SocketID, addr, &hs, packet, Ref(error));
// This is listener - m_RejectReason need not be set
// because listener has no functionality of giving the app
// insight into rejected callers.
// --->
// (global.) CUDTUnited::updateListenerMux
// (new Socket.) CUDT::acceptAndRespond
if (result == -1)
{
hs.m_iReqType = URQFailure(error);
LOGF(mglog.Error, "UU:newConnection: rsp(REJECT): %d - %s", hs.m_iReqType, srt_rejectreason_str(error));
}
// CONFUSION WARNING!
//
// The newConnection() will call acceptAndRespond() if the processing
// was successful - IN WHICH CASE THIS PROCEDURE SHOULD DO NOTHING.
// Ok, almost nothing - see update_events below.
//
// If newConnection() failed, acceptAndRespond() will not be called.
// Ok, more precisely, the thing that acceptAndRespond() is expected to do
// will not be done (this includes sending any response to the peer).
//
// Now read CAREFULLY. The newConnection() will return:
//
// - -1: The connection processing failed due to errors like:
// - memory alloation error
// - listen backlog exceeded
// - any error propagated from CUDT::open and CUDT::acceptAndRespond
// - 0: The connection already exists
// - 1: Connection accepted.
//
// So, update_events is called only if the connection is established.
// Both 0 (repeated) and -1 (error) require that a response be sent.
// The CPacket object that has arrived as a connection request is here
// reused for the connection rejection response (see URQ_ERROR_REJECT set
// as m_iReqType).
// send back a response if connection failed or connection already existed
// new connection response should be sent in acceptAndRespond()
if (result != 1)
{
HLOGC(mglog.Debug,
log << CONID() << "processConnectRequest: sending ABNORMAL handshake info req="
<< RequestTypeStr(hs.m_iReqType));
size_t size = CHandShake::m_iContentSize;
hs.store_to(packet.m_pcData, Ref(size));
packet.m_iID = id;
packet.m_iTimeStamp = int(CTimer::getTime() - m_stats.startTime);
m_pSndQueue->sendto(addr, packet);
}
else
{
// a new connection has been created, enable epoll for write
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
}
}
LOGC(mglog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType));
return RejectReasonForURQ(hs.m_iReqType);
}
void CUDT::addLossRecord(std::vector<int32_t> &lr, int32_t lo, int32_t hi)
{
if (lo == hi)
lr.push_back(lo);
else
{
lr.push_back(lo | LOSSDATA_SEQNO_RANGE_FIRST);
lr.push_back(hi);
}
}
void CUDT::checkACKTimer(uint64_t currtime_tk)
{
if (currtime_tk > m_ullNextACKTime_tk // ACK time has come
// OR the number of sent packets since last ACK has reached
// the congctl-defined value of ACK Interval
// (note that none of the builtin congctls defines ACK Interval)
|| (m_CongCtl->ACKMaxPackets() > 0 && m_iPktCount >= m_CongCtl->ACKMaxPackets()))
{
// ACK timer expired or ACK interval is reached
sendCtrl(UMSG_ACK);
CTimer::rdtsc(currtime_tk);
const int ack_interval_tk =
m_CongCtl->ACKTimeout_us() > 0 ? m_CongCtl->ACKTimeout_us() * m_ullCPUFrequency : m_ullACKInt_tk;
m_ullNextACKTime_tk = currtime_tk + ack_interval_tk;
m_iPktCount = 0;
m_iLightACKCount = 1;
}
// Or the transfer rate is so high that the number of packets
// have reached the value of SelfClockInterval * LightACKCount before
// the time has come according to m_ullNextACKTime_tk. In this case a "lite ACK"
// is sent, which doesn't contain statistical data and nothing more
// than just the ACK number. The "fat ACK" packets will be still sent
// normally according to the timely rules.
else if (m_iPktCount >= SELF_CLOCK_INTERVAL * m_iLightACKCount)
{
// send a "light" ACK
sendCtrl(UMSG_ACK, NULL, NULL, SEND_LITE_ACK);
++m_iLightACKCount;
}
}
void CUDT::checkNAKTimer(uint64_t currtime_tk)
{
// XXX The problem with working NAKREPORT with SRT_ARQ_ONREQ
// is not that it would be inappropriate, but because it's not
// implemented. The reason for it is that the structure of the
// loss list container (m_pRcvLossList) is such that it is expected
// that the loss records are ordered by sequence numbers (so
// that two ranges sticking together are merged in place).
// Unfortunately in case of SRT_ARQ_ONREQ losses must be recorded
// as before, but they should not be reported, until confirmed
// 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_bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS)
return;
/*
* m_bRcvNakReport enables NAK reports for SRT.
* Retransmission based on timeout is bandwidth consuming,
* not knowing what to retransmit when the only NAK sent by receiver is lost,
* all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT).
*/
const int loss_len = m_pRcvLossList->getLossLength();
SRT_ASSERT(loss_len >= 0);
if (loss_len > 0)
{
if (currtime_tk <= m_ullNextNAKTime_tk)
return; // wait for next NAK time
sendCtrl(UMSG_LOSSREPORT);
}
m_ullNextNAKTime_tk = currtime_tk + m_ullNAKInt_tk;
}
bool CUDT::checkExpTimer(uint64_t currtime_tk)
{
// In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class.
// There's nothing in the original code that alters these values.
uint64_t next_exp_time_tk;
if (m_CongCtl->RTO())
{
next_exp_time_tk = m_ullLastRspTime_tk + m_CongCtl->RTO() * m_ullCPUFrequency;
}
else
{
uint64_t exp_int_tk = (m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency;
if (exp_int_tk < m_iEXPCount * m_ullMinExpInt_tk)
exp_int_tk = m_iEXPCount * m_ullMinExpInt_tk;
next_exp_time_tk = m_ullLastRspTime_tk + exp_int_tk;
}
if (currtime_tk <= next_exp_time_tk)
return false;
// ms -> us
const int PEER_IDLE_TMO_US = m_iOPT_PeerIdleTimeout * 1000;
// Haven't received any information from the peer, is it dead?!
// timeout: at least 16 expirations and must be greater than 5 seconds
if ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) &&
(currtime_tk - m_ullLastRspTime_tk > PEER_IDLE_TMO_US * m_ullCPUFrequency))
{
//
// Connection is broken.
// UDT does not signal any information about this instead of to stop quietly.
// Application will detect this when it calls any UDT methods next time.
//
HLOGC(mglog.Debug,
log << "CONNECTION EXPIRED after " << ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "ms");
m_bClosing = true;
m_bBroken = true;
m_iBrokenCounter = 30;
// update snd U list to remove this socket
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
releaseSynch();
// app can call any UDT API to learn the connection_broken error
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR, true);
CTimer::triggerEvent();
return true;
}
HLOGC(mglog.Debug,
log << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed="
<< ((currtime_tk - m_ullLastRspTime_tk) / m_ullCPUFrequency) << "/" << (+PEER_IDLE_TMO_US) << "us");
++m_iEXPCount;
/*
* (keepalive fix)
* duB:
* It seems there is confusion of the direction of the Response here.
* LastRspTime is supposed to be when receiving (data/ctrl) from peer
* as shown in processCtrl and processData,
* Here we set because we sent something?
*
* Disabling this code that prevent quick reconnection when peer disappear
*/
// Reset last response time since we've just sent a heart-beat.
// (fixed) m_ullLastRspTime_tk = currtime_tk;
return false;
}
void CUDT::checkRexmitTimer(uint64_t currtime_tk)
{
/* There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT.
*
* LATEREXMIT is only used with FileCC.
* The mode is triggered when some time has passed since the last ACK from
* the receiver, while there is still some unacknowledged data in the sender's buffer,
* and the loss list is empty.
*
* FASTREXMIT is only used with LiveCC.
* The mode is triggered if the receiver does not send periodic NAK reports,
* when some time has passed since the last ACK from the receiver,
* while there is still some unacknowledged data in the sender's buffer.
*
* In case the above conditions are met, the unacknowledged packets
* in the sender's buffer will be added to loss list and retransmitted.
*/
const uint64_t rtt_syn = (m_iRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US);
const uint64_t exp_int = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency;
if (currtime_tk <= (m_ullLastRspAckTime_tk + exp_int))
return;
// If there is no unacknowledged data in the sending buffer,
// then there is nothing to retransmit.
if (m_pSndBuffer->getCurrBufSize() <= 0)
return;
const bool is_laterexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_LATEREXMIT;
const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT;
// If the receiver will send periodic NAK reports, then FASTREXMIT is inactive.
// MIND that probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off.
if (is_fastrexmit && m_bPeerNakReport)
return;
// We need to retransmit only when the data in the sender's buffer was already sent.
// Otherwise it might still be sent regulary.
bool retransmit = false;
// - the sender loss list is empty (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track)
if (is_laterexmit && (CSeqNo::incseq(m_iSndCurrSeqNo) != m_iSndLastAck) && m_pSndLossList->getLossLength() == 0)
retransmit = true;
if (is_fastrexmit && (CSeqNo::seqoff(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0))
retransmit = true;
if (retransmit)
{
// Sender: Insert all the packets sent after last received acknowledgement into the sender loss list.
CGuard acklock(m_RecvAckLock); // Protect packet retransmission
// Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list
const int32_t csn = m_iSndCurrSeqNo;
const int num = m_pSndLossList->insert(m_iSndLastAck, csn);
if (num > 0)
{
CGuard::enterCS(m_StatsLock);
m_stats.traceSndLoss += num;
m_stats.sndLossTotal += num;
CGuard::leaveCS(m_StatsLock);
HLOGC(mglog.Debug,
log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT")
<< " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " ("
<< CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)");
}
}
++m_iReXmitCount;
checkSndTimers(DONT_REGEN_KM);
const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT;
updateCC(TEV_CHECKTIMER, stage);
// immediately restart transmission
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
}
void CUDT::checkTimers()
{
// update CC parameters
updateCC(TEV_CHECKTIMER, TEV_CHT_INIT);
// uint64_t minint = (uint64_t)(m_ullCPUFrequency * m_pSndTimeWindow->getMinPktSndInt() * 0.9);
// if (m_ullInterval_tk < minint)
// m_ullInterval_tk = minint;
// NOTE: This commented-out ^^^ code was commented out in original UDT. Leaving for historical reasons
uint64_t currtime_tk;
CTimer::rdtsc(currtime_tk);
// This is a very heavy log, unblock only for temporary debugging!
#if 0
HLOGC(mglog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_ullNextACKTime_tk)
<< " AckInterval=" << m_iACKInterval
<< " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount);
#endif
// Check if it is time to send ACK
checkACKTimer(currtime_tk);
// Check if it is time to send a loss report
checkNAKTimer(currtime_tk);
// Check if the connection is expired
if (checkExpTimer(currtime_tk))
return;
// Check if FAST or LATE packet retransmission is required
checkRexmitTimer(currtime_tk);
// uint64_t exp_int = (m_iRTT + 4 * m_iRTTVar + COMM_SYN_INTERVAL_US) * m_ullCPUFrequency;
if (currtime_tk > m_ullLastSndTime_tk + (COMM_KEEPALIVE_PERIOD_US * m_ullCPUFrequency))
{
sendCtrl(UMSG_KEEPALIVE);
HLOGP(mglog.Debug, "KEEPALIVE");
}
}
void CUDT::addEPoll(const int eid)
{
CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
m_sPollID.insert(eid);
CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
if (!stillConnected())
return;
CGuard::enterCS(m_RecvLock);
if (m_pRcvBuffer->isRcvDataReady())
{
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true);
}
CGuard::leaveCS(m_RecvLock);
if (m_iSndBufSize > m_pSndBuffer->getCurrBufSize())
{
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true);
}
}
void CUDT::removeEPoll(const int eid)
{
// clear IO events notifications;
// since this happens after the epoll ID has been removed, they cannot be set again
set<int> remove;
remove.insert(eid);
s_UDTUnited.m_EPoll.update_events(m_SocketID, remove, UDT_EPOLL_IN | UDT_EPOLL_OUT, false);
CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
m_sPollID.erase(eid);
CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
}
void CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl)
{
if (evt >= TEV__SIZE)
return; // sanity check
m_Slots[evt].push_back(sl);
}
void CUDT::DisconnectSignal(ETransmissionEvent evt)
{
if (evt >= TEV__SIZE)
return; // sanity check
m_Slots[evt].clear();
}
void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var)
{
for (std::vector<EventSlot>::iterator i = m_Slots[tev].begin(); i != m_Slots[tev].end(); ++i)
{
i->emit(tev, var);
}
}
int CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes)
{
CUDTSocket *s = s_UDTUnited.locate(u);
if (!s || !s->m_pUDT)
return -1;
CSndBuffer *b = s->m_pUDT->m_pSndBuffer;
if (!b)
return -1;
int bytecount, timespan;
int count = b->getCurrBufSize(Ref(bytecount), Ref(timespan));
if (blocks)
*blocks = count;
if (bytes)
*bytes = bytecount;
return std::abs(timespan);
}
SRT_REJECT_REASON CUDT::rejectReason(SRTSOCKET u)
{
CUDTSocket *s = s_UDTUnited.locate(u);
if (!s || !s->m_pUDT)
return SRT_REJ_UNKNOWN;
return s->m_pUDT->m_RejectReason;
}
bool CUDT::runAcceptHook(CUDT *acore, const sockaddr *peer, const CHandShake *hs, const CPacket &hspkt)
{
// Prepare the information for the hook.
// We need streamid.
char target[MAX_SID_LENGTH + 1];
memset(target, 0, MAX_SID_LENGTH + 1);
// Just for a case, check the length.
// This wasn't done before, and we could risk memory crash.
// In case of error, this will remain unset and the empty
// string will be passed as streamid.
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs->m_iType);
// This tests if there are any extensions.
if (hspkt.getLength() > CHandShake::m_iContentSize + 4 && IsSet(ext_flags, CHandShake::HS_EXT_CONFIG))
{
uint32_t *begin = reinterpret_cast<uint32_t *>(hspkt.m_pcData + CHandShake::m_iContentSize);
size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // ONE SHOT, but continuable loop
{
int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next));
const size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_SID)
{
if (!bytelen || bytelen > MAX_SID_LENGTH)
{
LOGC(mglog.Error,
log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +MAX_SID_LENGTH
<< " - PROTOCOL ERROR, REJECTING");
return false;
}
// See comment at CUDT::interpretSrtHandshake().
memcpy(target, begin + 1, bytelen);
// Un-swap on big endian machines
ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen);
// Nothing more expected from connection block.
break;
}
else if (cmd == SRT_CMD_NONE)
{
// End of blocks
break;
}
else
{
// Any other kind of message extracted. Search on.
length -= (next - begin);
begin = next;
if (begin)
continue;
}
break;
}
}
try
{
int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs->m_iVersion, peer, target);
if (result == -1)
return false;
}
catch (...)
{
LOGP(mglog.Error, "runAcceptHook: hook interrupted by exception");
return false;
}
return true;
}