From c91d5f7e4139c5e8316a189446499bc3ee46915b Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Wed, 6 Sep 2017 17:51:58 +0200 Subject: [PATCH] The integrated handshake feature (aka HSv5) with some refactoring (#76) * Updated all changes for HSv5 handshake. * Added forgotten lacking files * Fixes reported from windows build. * Fixed problems reported by OS X/Clang * Fixed wrong fix on Mac/Windows. Added lacking stub function for logging in haicrypt * Another set of fixes for Windows * Changed logging API in siplex to C++03 version due to problems on Windows * Fixed nonportable alarm in siplex * Made all required fixes on Windows, also checked on Cygwin * Fixed include for back_inserter detected by Windows/msvs 2015 * A trick to solve a fictional github-reported conflict * Minor fixes --- CMakeLists.txt | 42 +- README.md | 1 + apps/siplex.cpp | 850 +++++ apps/stransmit.cpp | 1163 +------ apps/utility-test.cpp | 4 +- common/README.md | 29 + common/appcommon.hpp | 1 + common/logsupport.cpp | 131 + common/logsupport.hpp | 16 + {srtcore => common}/netinet_any.h | 4 +- {srtcore => common}/platform_sys.h | 0 common/socketoptions.hpp | 7 +- common/srt_compat.c | 2 +- common/threadname.h | 96 + common/transmitbase.hpp | 54 + common/transmitmedia.cpp | 1042 ++++++ common/transmitmedia.hpp | 125 + common/uriparser.cpp | 29 +- common/uriparser.hpp | 18 +- common/win/wintime.h | 5 + configure | 27 + configure-data.tcl | 2 +- haicrypt/filelist.maf | 6 +- haicrypt/haicrypt.h | 22 +- haicrypt/hcrypt.c | 100 +- haicrypt/hcrypt.h | 10 +- haicrypt/hcrypt_ctx_rx.c | 8 +- haicrypt/hcrypt_ctx_tx.c | 2 +- haicrypt/hcrypt_sa.c | 2 +- haicrypt/hcrypt_tx.c | 20 +- scripts/check-deps | 26 +- srtcore/{ => ATTIC}/ccc.cpp | 0 srtcore/{ => ATTIC}/ccc.h | 0 srtcore/Sources.txt | 36 - srtcore/api.cpp | 517 +-- srtcore/api.h | 11 +- srtcore/buffer.cpp | 165 +- srtcore/buffer.h | 56 - srtcore/channel.cpp | 62 +- srtcore/channel.h | 10 +- srtcore/common.cpp | 38 +- srtcore/common.h | 52 + srtcore/core.cpp | 4983 ++++++++++++++++++++-------- srtcore/core.h | 852 ++--- srtcore/crypto.cpp | 700 ++++ srtcore/crypto.h | 182 + srtcore/csrtcc.cpp | 935 ------ srtcore/csrtcc.h | 341 -- srtcore/epoll.cpp | 8 +- srtcore/epoll.h | 2 - srtcore/filelist.maf | 15 +- srtcore/handshake.cpp | 210 ++ srtcore/handshake.h | 278 ++ srtcore/logging.h | 3 - srtcore/packet.cpp | 142 +- srtcore/packet.h | 110 +- srtcore/queue.cpp | 332 +- srtcore/queue.h | 27 +- srtcore/srt.h | 52 +- srtcore/srt4udt.h | 36 +- srtcore/srt_c_api.cpp | 25 + srtcore/threadname.h | 2 + srtcore/udt.h | 29 +- srtcore/utilities.h | 240 +- srtcore/window.cpp | 42 +- srtcore/window.h | 17 +- 66 files changed, 9290 insertions(+), 5064 deletions(-) create mode 100644 apps/siplex.cpp create mode 100644 common/README.md create mode 100644 common/logsupport.cpp create mode 100644 common/logsupport.hpp rename {srtcore => common}/netinet_any.h (96%) rename {srtcore => common}/platform_sys.h (100%) create mode 100644 common/threadname.h create mode 100644 common/transmitbase.hpp create mode 100644 common/transmitmedia.cpp create mode 100644 common/transmitmedia.hpp rename srtcore/{ => ATTIC}/ccc.cpp (100%) rename srtcore/{ => ATTIC}/ccc.h (100%) delete mode 100644 srtcore/Sources.txt create mode 100644 srtcore/crypto.cpp create mode 100644 srtcore/crypto.h delete mode 100644 srtcore/csrtcc.cpp delete mode 100644 srtcore/csrtcc.h create mode 100644 srtcore/handshake.cpp create mode 100644 srtcore/handshake.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fd2f6ee40..a224147c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,14 +19,14 @@ cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) # XXX This can be potentially done in future, but there still exist # some dependent project using cmake 2.8 - this can't be done this way. #cmake_minimum_required (VERSION 3.0.2 FATAL_ERROR) -#project(SRT VERSION "1.2.1") +#project(SRT VERSION "1.3.0") project(SRT C CXX) set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") include(haiUtil) include(FindPkgConfig) -set (SRT_VERSION 1.2.0) +set (SRT_VERSION 1.3.0) set_version_variables(SRT_VERSION ${SRT_VERSION}) if (NOT DEFINED ENABLE_DEBUG) @@ -54,6 +54,8 @@ option(ENABLE_LOGGING "Should logging be enabled" ${ENABLE_LOGGING_DEFAULT}) option(ENABLE_SHARED "Should libsrt be built as a shared library" ON) option(ENABLE_SEPARATE_HAICRYPT "Should haicrypt be built as a separate library file" OFF) option(ENABLE_SUFLIP "Shuld suflip tool be built" OFF) +option(ENABLE_C_DEPS "Extra library dependencies in srt.pc for C language" OFF) +option(USE_STATIC_LIBSTDCXX "Should use static rather than shared libstdc++" OFF) # Always turn logging on if the build type is debug if (ENABLE_DEBUG) @@ -76,11 +78,11 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) if (DEFINED WITH_SRT_NAME) - set (TARGET_haisrt ${WITH_SRT_TARGET}) + set (TARGET_haisrt ${WITH_SRT_NAME}) endif() if (DEFINED WITH_HAICRYPT_NAME) - set (TARGET_haicrypt ${WITH_HAICRYPT_TARGET}) + set (TARGET_haicrypt ${WITH_HAICRYPT_NAME}) endif() set_if(DARWIN ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") @@ -158,6 +160,7 @@ if(WIN32) elseif(DARWIN) message(STATUS "DARWIN detected") add_definitions(-DOSX=1) + set(MACOSX_RPATH OFF) elseif(LINUX) add_definitions(-DLINUX=1) message(STATUS "LINUX detected" ) @@ -210,9 +213,9 @@ endif() # This is required in some projects that add some other sources # to the SRT library to be compiled together (aka "virtual library"). if (DEFINED SRT_EXTRA_LIB_INC) + message (STATUS "--- Extra injection: ${SRT_EXTRA_LIB_INC}") include(${SRT_EXTRA_LIB_INC}.cmake) # Expected to provide variables: - # - SOURCES_srt_extra # - EXTRA_stransmit endif() @@ -248,6 +251,9 @@ if (WIN32) adddirname(common "${HEADERS_common_indir}" HEADERS_srt_win32) endif() +# Add extra sources to haicrypt (actually all base dep handling) if defined +set (SOURCES_haicrypt ${SOURCES_haicrypt} ${SOURCES_haicrypt_dep}) + message(STATUS "SOURCES(haicrypt): ${SOURCES_haicrypt}") # NOTE: The "virtual library" is a library specification that cmake @@ -314,7 +320,7 @@ else() set (DEPENDS_srt ${TARGET_haicrypt}) endif() -add_library(${TARGET_srt} ${srt_libspec} ${SOURCES_srt} ${SOURCES_srt_extra}) +add_library(${TARGET_srt} ${srt_libspec} ${SOURCES_srt}) # --- # And back to target: haicrypt. Both targets must be defined @@ -427,10 +433,21 @@ endif() if ( ENABLE_CXX11 ) - add_executable(stransmit - ${CMAKE_SOURCE_DIR}/apps/stransmit.cpp + set (SOURCES_transmit ${CMAKE_SOURCE_DIR}/common/uriparser.cpp ${CMAKE_SOURCE_DIR}/common/socketoptions.cpp + ${CMAKE_SOURCE_DIR}/common/logsupport.cpp + ${CMAKE_SOURCE_DIR}/common/transmitmedia.cpp + ) + + add_executable(stransmit + ${CMAKE_SOURCE_DIR}/apps/stransmit.cpp + ${SOURCES_transmit} + ) + + add_executable(siplex + ${CMAKE_SOURCE_DIR}/apps/siplex.cpp + ${SOURCES_transmit} ) # Test programs @@ -439,13 +456,20 @@ if ( ENABLE_CXX11 ) # We state that Darwin always uses CLANG compiler, which honors this flag the same way. set_target_properties(stransmit PROPERTIES COMPILE_FLAGS "${CFLAGS_CXX_STANDARD} ${EXTRA_stransmit}") target_link_libraries(stransmit ${TARGET_srt} ${TARGET_haicrypt}) - target_link_libraries(utility-test ${TARGET_srt}) install(TARGETS stransmit RUNTIME DESTINATION bin) + + set_target_properties(siplex PROPERTIES COMPILE_FLAGS "${CFLAGS_CXX_STANDARD} ${EXTRA_stransmit}") + target_link_libraries(siplex ${TARGET_srt} ${TARGET_haicrypt}) + install(TARGETS siplex RUNTIME DESTINATION bin) + install(PROGRAMS scripts/sfplay DESTINATION bin) + target_link_libraries(utility-test ${TARGET_srt}) + endif() if (DEFINED SRT_EXTRA_APPS_INC) + message (STATUS "--- Extra injection: ${SRT_EXTRA_APPS_INC}") include(${SRT_EXTRA_APPS_INC}.cmake) # No extra variables expected. Just use the variables # already provided and define additional targets. diff --git a/README.md b/README.md index 29610c607..255dc458a 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ As audio/video packets are streamed from a source to a destination device, SRT d # Requirements * cmake (as build system) +* Tcl 8.5 (optional for user-friendly build system) * OpenSSL * Pthreads (for POSIX systems it's builtin, for Windows there's a library) diff --git a/apps/siplex.cpp b/apps/siplex.cpp new file mode 100644 index 000000000..ef0a2a7eb --- /dev/null +++ b/apps/siplex.cpp @@ -0,0 +1,850 @@ + +#include +#include +#include +#include +#include +#include +#include + +#define REQUIRE_CXX11 1 + +#include "../common/appcommon.hpp" // CreateAddrInet +#include "../common/uriparser.hpp" // UriParser +#include "../common/socketoptions.hpp" +#include "../common/logsupport.hpp" +#include "../common/transmitbase.hpp" +#include "../common/transmitmedia.hpp" +#include "../common/netinet_any.h" +#include "../common/threadname.h" + +#include +#include + +// Make the windows-nonexistent alarm an empty call +#ifdef WIN32 +#define alarm(argument) (void)0 +#define signal_alarm(fn) (void)0 +#else +#define signal_alarm(fn) signal(SIGALRM, fn) +#endif + + +using namespace std; + +// The length of the SRT payload used in srt_recvmsg call. +// So far, this function must be used and up to this length of payload. +const size_t DEFAULT_CHUNK = 1316; +const size_t DEF_MAX_STREAMS = 10; + +const logging::LogFA SRT_LOGFA_APP = 10; +logging::Logger applog(SRT_LOGFA_APP, &srt_logger_config, "siplex"); + +// Some utilities borrowed from tumux, as this is using options +// similar way. +template inline +std::string Printable(const Container& in, Value /*pseudoargument*/, Args&&... args) +{ + std::ostringstream os; + Print(os, args...); + os << "[ "; + for (auto i: in) + os << Value(i) << " "; + os << "]"; + return os.str(); +} + +template inline +std::string Printable(const Container& in) +{ + using Value = typename Container::value_type; + return Printable(in, Value()); +} + +template +inline void Split(const std::string & str, char delimiter, OutputIterator tokens) +{ + if ( str.empty() ) + return; // May cause crash and won't extract anything anyway + + std::size_t start; + std::size_t end = -1; + + do + { + start = end + 1; + end = str.find(delimiter, start); + *tokens = str.substr( + start, + (end == std::string::npos) ? std::string::npos : end - start); + ++tokens; + } while (end != std::string::npos); +} + +template +inline size_t safe_advance(It& it, size_t num, It end) +{ + while ( it != end && num ) + { + --num; + ++it; + } + + return num; // will be effectively 0, if reached the required point, or >0, if end was by that number earlier +} + +template +auto map_get(Map& m, const Key& key, typename Map::mapped_type def = typename Map::mapped_type()) -> typename Map::mapped_type +{ + auto it = m.find(key); + return it == m.end() ? def : it->second; +} + + + +volatile bool int_state = false; +void OnINT_SetIntState(int) +{ + cerr << "\n-------- REQUESTED INTERRUPT!\n"; + int_state = true; + if ( transmit_throw_on_interrupt ) + throw std::runtime_error("Requested exception interrupt"); +} + +volatile bool alarm_state = false; +void OnALRM_SetAlarmState(int) +{ + alarm_state = true; +} + +map defined_streams; +string file_pattern = "output%.dat"; + +struct MediumPair +{ + unique_ptr src; + unique_ptr tar; + thread runner; + size_t chunk = DEFAULT_CHUNK; + volatile bool interrupted = false; + volatile bool has_quit = false; + bytevector initial_portion; + string name; + + MediumPair(unique_ptr s, unique_ptr t): src(move(s)), tar(move(t)) {} + + void Stop() + { + interrupted = true; + runner.join(); + src.reset(); + tar.reset(); + } + + void TransmissionLoop() + { + struct MarkQuit + { + volatile bool& q; + + ~MarkQuit() + { + q = true; + applog.Note() << "MediumPair: Giving it 5 seconds delay before exiting"; + this_thread::sleep_for(chrono::seconds(5)); + } + } mq { has_quit }; + + applog.Note() << "STARTING TRANSMiSSION: " << name; + + if (!initial_portion.empty()) + { + tar->Write(initial_portion); + if ( tar->Broken() ) + { + applog.Note() << "OUTPUT BROKEN for loop: " << name; + return; + } + initial_portion.clear(); + } + + try + { + for (;;) + { + ostringstream sout; + alarm(1); + const bytevector& data = src->Read(chunk); + alarm(0); + if (alarm_state) + { + alarm_state = false; + // This means that it's just a checkpoint. + if ( interrupted ) + break; + continue; + } + sout << " << " << data.size() << " -> "; + if ( data.empty() && src->End() ) + { + sout << "EOS"; + applog.Note() << sout.str(); + break; + } + tar->Write(data); + if ( tar->Broken() ) + { + sout << " OUTPUT broken"; + applog.Note() << sout.str(); + break; + } + sout << " sent"; + if ( int_state ) + { + sout << " --- (interrupted on request)"; + applog.Note() << sout.str(); + break; + } + applog.Note() << sout.str(); + } + } + catch (Source::ReadEOF& x) + { + applog.Note() << "EOS - closing media for loop: " << name; + src->Close(); + tar->Close(); + applog.Note() << "CLOSED: " << name; + } + catch (std::runtime_error& x) + { + applog.Note() << "INTERRUPTED: " << x.what(); + src->Close(); + tar->Close(); + applog.Note() << "CLOSED: " << name; + } + catch (...) + { + applog.Note() << "UNEXPECTED EXCEPTION, rethrowing"; + throw; + } + } +}; + + +class MediaBase +{ +public: + list media; + + /// Take the Source and Target and bind them for a transmission. + /// This spawns a thread for transmission. + /// @param src source medium + /// @param tar target medium + /// @param initial_portion First portion of data read from @c src for any extra checks, which + /// are still meant to be delivered to @c tar + MediumPair& Link(std::unique_ptr src, std::unique_ptr tar, bytevector&& initial_portion, string name, string thread_name) + { + media.emplace_back(move(src), move(tar)); + MediumPair& med = media.back(); + med.initial_portion = move(initial_portion); + med.name = name; + + // Ok, got this, so we can start transmission. + ThreadName tn(thread_name.c_str()); + + med.runner = thread( [&med]() { med.TransmissionLoop(); }); + return med; + } + + void StopAll() + { + for (auto& x: media) + x.Stop(); + } + + ~MediaBase() + { + StopAll(); + } + +} g_media_base; + + +// This class is used when we don't know yet whether the given URI +// designates an effective listener or caller. So we create it, initialize, +// then we know what mode we'll be using. +// +// When caller, then we will do connect() using this object, then clone out +// a new object - of a direction specific class - which will steal the socket +// from this one and then roll the data. After this, this object is ready +// to connect again, and will create its own socket for that occasion, and +// the whole procedure repeats. +// +// When listener, then this object will be doing accept() and with every +// successful acceptation it will clone out a new object - of a direction +// specific class - which will steal just the connection socket from this +// object. This object will still live on and accept new connections and +// so on. +class SrtModel: public SrtCommon +{ +public: + bool is_caller = false; + string m_host; + int m_port = 0; + + + SrtModel(string host, int port, map par) + { + InitParameters(host, par); + if (m_mode == "caller") + is_caller = true; + else if (m_mode != "listener") + throw std::invalid_argument("Only caller and listener modes supported"); + + m_host = host; + m_port = port; + } + + bool Establish(ref_t name) + { + // This does connect or accept. + // When this returned true, the caller should create + // a new SrtSource or SrtTaget then call StealFrom(*this) on it. + + // If this is a connector and the peer doesn't have a corresponding + // medium, it should send back a single byte with value 0. This means + // that agent should stop connecting. + + if (is_caller) + { + // Establish a connection + + PrepareClient(); + + if (name.get() != "") + { + applog.Note() << "Connect with requesting stream [" << name.get() << "]"; + UDT::setstreamid(m_sock, name); + } + else + { + applog.Warn() << "NO STREAM ID for SRT connection"; + } + + if (m_outgoing_port) + { + applog.Note() << "Setting outgoing port: " << m_outgoing_port; + SetupAdapter("", m_outgoing_port); + } + + ConnectClient(m_host, m_port); + + if (m_outgoing_port == 0) + { + // Must rely on a randomly selected one. Extract the port + // so that it will be reused next time. + sockaddr_any s(AF_INET); + int namelen = s.size(); + if ( srt_getsockname(Socket(), &s, &namelen) == SRT_ERROR ) + { + Error(UDT::getlasterror(), "srt_getsockname"); + } + + m_outgoing_port = s.hport(); + applog.Note() << "Extracted outgoing port: " << m_outgoing_port << " - will reuse it for next connections"; + } + } + else + { + // Listener - get a socket by accepting. + // Check if the listener is already created first + if (Listener() == SRT_INVALID_SOCK) + { + applog.Note() << "Setting up listener: port=" << m_port << " backlog=5"; + PrepareListener(m_adapter, m_port, 5); + } + + applog.Note() << "Accepting a client..."; + AcceptNewClient(); + // This rewrites m_sock with a new SRT socket ("accepted" socket) + name = UDT::getstreamid(m_sock); + applog.Note() << "... GOT CLIENT for stream [" << name.get() << "]"; + } + + return true; + } + + void Stall() + { + // Call this function if everything is running in their own + // threads and there's nothing more to run. Check periodically + // if all threads are still alive, quit if all are dead. + + while (!int_state) + { + this_thread::sleep_for(chrono::seconds(1)); + + // Check all cars if any crashed + for (auto i = g_media_base.media.begin(), i_next = i; i != g_media_base.media.end(); i = i_next) + { + ++i_next; + if (i->has_quit) + { + applog.Note() << "Found QUIT mediumpair: " << i->name << " - removing from base"; + i->Stop(); + g_media_base.media.erase(i); + } + } + + if (g_media_base.media.empty()) + { + applog.Note() << "All media have quit. Marking exit."; + break; + } + } + } + + void Close() + { + if (m_sock != SRT_INVALID_SOCK) + { + srt_close(m_sock); + m_sock = SRT_INVALID_SOCK; + } + } +}; + +string ResolveFilePattern(int number) +{ + vector parts; + Split(::file_pattern, '%', back_inserter(parts)); + ostringstream os; + os << parts[0]; + for (auto i = parts.begin()+1; i < parts.end(); ++i) + os << number << *i; + + return os.str(); +} + +string SelectMedium(string id, bool mode_output) +{ + static int number = 0; + + // Empty ID is incorrect. + if ( id == "" ) + { + applog.Error() << "SelectMedium: empty id"; + return ""; + } + + string uri = map_get(defined_streams, id); + + // Test the URI if it is openable. + UriParser u(uri); + if ( u.scheme() == "file" && u.path() == "" ) + { + if (mode_output) + { + ++number; + string sol = ResolveFilePattern(number); + applog.Warn() << "SelectMedium: for [" << id << "] uri '" << uri << "' is file with no path - autogenerating filename: " << sol; + return sol; + } + applog.Error() << "SelectMedium: id not found: [" << id << "]"; + return ""; + } + + applog.Note() << "SelectMedium: for [" << id << "] found medium: " << uri; + return uri; +} + +bool PrepareStreamNames(const map>& params, bool mode_output) +{ + vector v; + string flag; + + if (mode_output) + { + // You have an incoming stream over SRT and you need to + // redirect it to the correct locally defined output stream. + + if (params.count("o") && !params.at("o").empty()) + { + // We have a defined list of parameters. + // Check if there's just one item and it's a file pattern + + // Each stream needs to be defined separately, at least to have IDs + // If this is a file without path, use the default file pattern. + + v = params.at("o"); + flag = "o"; + } + } + else + { + // You have some input media and you want to send them all + // over SRT medium. + if (params.count("i")) + { + v = params.at("i"); + flag = "i"; + } + } + + if ( v.empty() ) + return false; + + for (string& s: v) + { + UriParser u(s); + string id = u["id"]; + if ( id != "" ) + { + defined_streams[id] = s; + } + else + { + cerr << "Parameter at -" << flag << " without id: " << s << endl; + return false; + } + } + + return true; +} + +bool SelectAndLink(SrtModel& m, string id, bool mode_output) +{ + // So, we have made a connection that is now contained in m. + // For that connection we need to select appropriate stream + // to send. + // + // XXX + // Currently only one method implemented: select appropriate number from the list. + + // If SRT mode is caller, then SelectMedium will always return + // a nonempty string that is a key in defined_streams map. + // This is because in this case the id comes directly from + // that map's keys. + string medium = SelectMedium(id, mode_output); + if ( medium == "" ) + { + // No medium available for that stream, ignore it. + m.Close(); + return false; + } + + // Now create a medium and store. + unique_ptr source; + unique_ptr target; + string name; + ostringstream os; + SRTSOCKET sock = m.Socket(); + + string thread_name; + + if ( mode_output ) + { + // Create Source out of SrtModel and Target from the given medium + auto s = new SrtSource(); + s->StealFrom(m); + source.reset(s); + + target = Target::Create(medium); + + os << m.m_host << ":" << m.m_port << "[" << id << "]%" << sock << " -> " << medium; + thread_name = "TL>" + medium; + } + else + { + // Create Source of given medium and Target of SrtModel. + source = Source::Create(medium); + auto t = new SrtTarget(); + t->StealFrom(m); + target.reset(t); + + os << medium << " -> " << m.m_host << ":" << m.m_port << "[" << id << "]%" << sock; + thread_name = "TL<" + medium; + } + + bytevector dummy_initial_portion; + g_media_base.Link(move(source), move(target), move(dummy_initial_portion), os.str(), thread_name); + + return true; +} + +string Join(const vector& in, string sep) +{ + if ( in.empty() ) + return ""; + + ostringstream os; + + os << in[0]; + for (auto i = in.begin()+1; i != in.end(); ++i) + os << sep << *i; + return os.str(); +} + +typedef map> options_t; + +struct OutList +{ + typedef vector type; + static type process(const options_t::mapped_type& i) { return i; } +}; + +struct OutString +{ + typedef string type; + static type process(const options_t::mapped_type& i) { return Join(i, " "); } +}; + + +template +typename OutType::type Option(const options_t&, OutValue deflt=OutValue()) { return deflt; } + +template +typename OutType::type Option(const options_t& options, OutValue deflt, string key, Args... further_keys) +{ + auto i = options.find(key); + if ( i == options.end() ) + return Option(options, deflt, further_keys...); + return OutType::process(i->second); +} + +void Usage(string program) +{ + cerr << "Usage: " << program << " [-i INPUT...] [-o OUTPUT...]\n"; +} + +void Help(string program) +{ + Usage(program); + cerr << endl; + cerr << +"SIPLEX is a program that demonstrates two SRT features:\n" +" - using one UDP outgoing port for multiple connecting SRT sockets\n" +" - setting a resource ID on a socket visible on the listener side\n" +"\n" +"The will be input or output depending on the further -i/-o option.\n" +"The URIs specified as -i INPUT... will be used for input and therefore SRT for output,\n" +"and in the other way around if you use -o OUTPUT...\n" +"For every such URI you must specify additionally a parameter named 'id', which will be\n" +"interperted by the application and used to set resource id on an SRT socket when connecting\n" +"or to match with the id extracted from the accepted socket of incoming connection.\n" +"Example:\n" +"\tSender: siplex srt://remhost:2000 -i udp://:5000?id=low udp://:6000?id=high\n" +"\tReceiver: siplex srt://:2000 -o output-high.ts?id=high output-low.ts?id=low\n" +"\nHere you create a Sender which will connect to 'remhost' port 2000 using multiple SRT\n" +"sockets, all of which will be using the same outgoing port. Here the port is autoselected\n" +"by the first socket when connecting, every next one will reuse that port. Alternatively you\n" +"can enforce the outgoing port using 'port' parameter in the SRT URI.\n\n" +"Then for every input resource a separate connection is made and appropriate resource id\n" +"will be set to particular socket assigned to that resource according to the 'id' parameter.\n" +"When the listener side (here Receiver) gets the socket accepted, it will have the resource\n" +"id set just as the caller side did, in which case siplex will search for this id among the\n" +"registered resources and match the resource (output here) with this id. If the resource is\n" +"not found, the connection is closed immediately. This works the same way regardless of which\n" +"direction is used by caller or listener\n"; + +} + +int main( int argc, char** argv ) +{ + // This is mainly required on Windows to initialize the network system, + // for a case when the instance would use UDP. SRT does it on its own, independently. + if ( !SysInitializeNetwork() ) + throw std::runtime_error("Can't initialize network!"); + + // Initialize signals + + signal_alarm(OnALRM_SetAlarmState); + signal(SIGINT, OnINT_SetIntState); + signal(SIGTERM, OnINT_SetIntState); + + // Symmetrically, this does a cleanup; put into a local destructor to ensure that + // it's called regardless of how this function returns. + struct NetworkCleanup + { + ~NetworkCleanup() + { + SysCleanupNetwork(); + } + } cleanupobj; + + vector args; + copy(argv+1, argv+argc, back_inserter(args)); + + // Check options + map> params; + + string current_key = ""; + + for (string a: args) + { + if ( a[0] == '-' ) + { + current_key = a.substr(1); + params[current_key].clear(); + continue; + } + + params[current_key].push_back(a); + } + + // The call syntax is: + // + // siplex -o/-i ARGS... + // + // SRT URI should contain: + // srt://[host]:port?mode=MODE&adapter=ADAPTER&port=PORT&otherparameters... + // + // Extra parameters: + // + // mode: caller/listener/rendezvous. Default: if host empty, listener, otherwise caller. + // adapter: IP to select network device for listner or rendezvous. Default: for listener taken from host, otherwise 0.0.0.0 + // port: default=0. Used only for caller mode, sets the outgoing port number. If 0, system-selected (default behavior) + // + // Syntax cases for -i: + // + // Every item from ARGS... is an input URI. For every such case a new socket should be + // created and the data should be transmitted through that socket. + // + // Syntax cases for -o: + // + // EMPTY ARGS...: use 'output%.dat' file patter for every stream. + // PATTERN (one argument that contains % somewhere): define the output file pattern + // URI...: try to match the input stream to particular URI by 'name' parameter. If none matches, ignore. + + if ( params.count("-help") ) + { + Help(argv[0]); + return 1; + } + + if ( params[""].empty() ) + { + Usage(argv[0]); + return 1; + } + + if (params[""].size() > 1) + { + cerr << "Extra parameter after the first one: " << Printable(params[""]) << endl; + return 1; + } + + // Force exist + (void)params["o"]; + (void)params["i"]; + + if (!params["o"].empty() && !params["i"].empty()) + { + cerr << "Input-output mixed mode not supported. Specify either -i or -o.\n"; + return 1; + } + + bool mode_output = false; + + if (params["i"].empty()) + { + mode_output = true; + } + + if ( !PrepareStreamNames(params, mode_output)) + { + cerr << "Incorrect input/output specification\n"; + return 1; + } + + if ( defined_streams.empty() ) + { + cerr << "No streams defined\n"; + return 1; + } + + string loglevel = Option(params, "error", "ll", "loglevel"); + logging::LogLevel::type lev = SrtParseLogLevel(loglevel); + UDT::setloglevel(lev); + UDT::addlogfa(SRT_LOGFA_APP); + + string verbo = Option(params, "no", "v", "verbose"); + if ( verbo == "" || !false_names.count(verbo) ) + transmit_verbose = true; + + + string srt_uri = params[""][0]; + + UriParser up(srt_uri); + + if ( up.scheme() != "srt" ) + { + cerr << "First parameter must be a SRT-scheme URI\n"; + return 1; + } + + int iport = atoi(up.port().c_str()); + if ( iport <= 1024 ) + { + cerr << "Port value invalid: " << iport << " - must be >1024\n"; + return 1; + } + + SrtModel m(up.host(), iport, up.parameters()); + + ThreadName::set("main"); + + // Note: for input, there must be an exactly defined + // number of sources. The loop rolls up to all these sources. + // + // For output, if you use defined output URI, roll the loop until + // they are all managed. + // If you use file pattern, then: + // - if SRT is in listener mode, just listen infinitely + // - if SRT is in caller mode, the limit number of the streams must be used. Default is 10. + + set ids; + for (auto& mp: defined_streams) + ids.insert(mp.first); + + try + { + for(;;) + { + string id = *ids.begin(); + bool done = m.Establish(Ref(id)); + if ( !done ) + break; + + // The 'id' could have been altered. + // If Establish did connect(), then it gave this stream id, + // in which case it will return unchanged. If it did accept(), + // then it will be overwritten with the received stream id. + // Whatever the result was, we need to bind the transmitter with + // the local resource of this id, and if this failed, simply + // close the stream and ignore it. + + // Select medium from parameters. + if ( SelectAndLink(m, id, mode_output) ) + { + ids.erase(id); + if (ids.empty()) + break; + } + + ThreadName::set("main"); + } + + applog.Note() << "All local stream definitions covered. Waiting for interrupt/broken all connections."; + m.Stall(); + } + catch (std::exception& x) + { + cerr << "CATCH!\n" << x.what() << endl;; + } +} + + + + + diff --git a/apps/stransmit.cpp b/apps/stransmit.cpp index 3e6691daf..f0d637096 100644 --- a/apps/stransmit.cpp +++ b/apps/stransmit.cpp @@ -53,6 +53,8 @@ #define _CRT_SECURE_NO_WARNINGS 1 #endif +#define REQUIRE_CXX11 1 + #include #include #include @@ -71,6 +73,8 @@ #include "../common/appcommon.hpp" // CreateAddrInet #include "../common/uriparser.hpp" // UriParser #include "../common/socketoptions.hpp" +#include "../common/logsupport.hpp" +#include "../common/transmitbase.hpp" // NOTE: This is without "haisrt/" because it uses an internal path // to the library. Application using the "installed" library should @@ -78,196 +82,12 @@ #include #include -// FEATURES when undefined or == 2, sets developer mode. -// When FEATURES == 1, it enforces user mode. -// In user mode SRT output is disabled. -#if defined(FEATURES) && FEATURES != 0 - #if FEATURES == 2 - #define DEVELOPER_MODE 1 - #warning USING DEVELOPER MODE - #else - #define DEVELOPER_MODE 0 - #warning USING USER MODE - #endif -#else -#define DEVELOPER_MODE 1 -#endif - // The length of the SRT payload used in srt_recvmsg call. // So far, this function must be used and up to this length of payload. const size_t DEFAULT_CHUNK = 1316; using namespace std; -typedef std::vector bytevector; - -// This is based on codes taken from -// This is POSIX standard, so it's not going to change. -// Haivision standard only adds one more severity below -// DEBUG named DEBUG_TRACE to satisfy all possible needs. - -map srt_level_names -{ - { "alert", LOG_ALERT }, - { "crit", LOG_CRIT }, - { "debug", LOG_DEBUG }, - { "emerg", LOG_EMERG }, - { "err", LOG_ERR }, - { "error", LOG_ERR }, /* DEPRECATED */ - { "fatal", LOG_CRIT }, // XXX Added for SRT - { "info", LOG_INFO }, - // { "none", INTERNAL_NOPRI }, /* INTERNAL */ - { "notice", LOG_NOTICE }, - { "note", LOG_NOTICE }, // XXX Added for SRT - { "panic", LOG_EMERG }, /* DEPRECATED */ - { "warn", LOG_WARNING }, /* DEPRECATED */ - { "warning", LOG_WARNING }, - //{ "", -1 } -}; - - - -template -void PrintSrtStats(int sid, const PerfMonType& mon) -{ - cout << "======= SRT STATS: sid=" << sid << endl; - cout << "PACKETS SENT: " << mon.pktSent << " RECEIVED: " << mon.pktRecv << endl; - cout << "LOST PKT SENT: " << mon.pktSndLoss << " RECEIVED: " << mon.pktRcvLoss << endl; - cout << "REXMIT SENT: " << mon.pktRetrans << " RECEIVED: " << mon.pktRcvRetrans << endl; - cout << "RATE SENDING: " << mon.mbpsSendRate << " RECEIVING: " << mon.mbpsRecvRate << endl; - cout << "BELATED RECEIVED: " << mon.pktRcvBelated << " AVG TIME: " << mon.pktRcvAvgBelatedTime << endl; - cout << "REORDER DISTANCE: " << mon.pktReorderDistance << endl; - cout << "WINDOW: FLOW: " << mon.pktFlowWindow << " CONGESTION: " << mon.pktCongestionWindow << " FLIGHT: " << mon.pktFlightSize << endl; - cout << "RTT: " << mon.msRTT << "ms BANDWIDTH: " << mon.mbpsBandwidth << "Mb/s\n"; - cout << "BUFFERLEFT: SND: " << mon.byteAvailSndBuf << " RCV: " << mon.byteAvailRcvBuf << endl; -} - -logging::LogLevel::type ParseLogLevel(string level) -{ - using namespace logging; - - if ( level.empty() ) - return LogLevel::fatal; - - if ( isdigit(level[0]) ) - { - long lev = strtol(level.c_str(), 0, 10); - if ( lev >= SRT_LOG_LEVEL_MIN && lev <= SRT_LOG_LEVEL_MAX ) - return LogLevel::type(lev); - - cerr << "ERROR: Invalid loglevel number: " << level << " - fallback to FATAL\n"; - return LogLevel::fatal; - } - - int (*ToLower)(int) = &std::tolower; // manual overload resolution - transform(level.begin(), level.end(), level.begin(), ToLower); - - auto i = srt_level_names.find(level); - if ( i == srt_level_names.end() ) - { - cerr << "ERROR: Invalid loglevel spec: " << level << " - fallback to FATAL\n"; - return LogLevel::fatal; - } - - return LogLevel::type(i->second); -} - -set ParseLogFA(string fa) -{ - using namespace logging; - - set fas; - - // The split algo won't work on empty string. - if ( fa == "" ) - return fas; - - static string names [] = { "general", "bstats", "control", "data", "tsbpd", "rexmit" }; - size_t names_s = sizeof (names)/sizeof (names[0]); - - if ( fa == "all" ) - { - // Skip "general", it's always on - fas.insert(SRT_LOGFA_BSTATS); - fas.insert(SRT_LOGFA_CONTROL); - fas.insert(SRT_LOGFA_DATA); - fas.insert(SRT_LOGFA_TSBPD); - fas.insert(SRT_LOGFA_REXMIT); - return fas; - } - - int (*ToLower)(int) = &std::tolower; - transform(fa.begin(), fa.end(), fa.begin(), ToLower); - - vector xfas; - size_t pos = 0, ppos = 0; - for (;;) - { - if ( fa[pos] != ',' ) - { - ++pos; - if ( pos < fa.size() ) - continue; - } - size_t n = pos - ppos; - if ( n != 0 ) - xfas.push_back(fa.substr(ppos, n)); - ++pos; - if ( pos >= fa.size() ) - break; - ppos = pos; - } - - for (size_t i = 0; i < xfas.size(); ++i) - { - fa = xfas[i]; - string* names_p = find(names, names + names_s, fa); - if ( names_p == names + names_s ) - { - cerr << "ERROR: Invalid log functional area spec: '" << fa << "' - skipping\n"; - continue; - } - - size_t nfa = names_p - names; - - if ( nfa != 0 ) - fas.insert(nfa); - } - - return fas; -} - - -template -unique_ptr CreateMedium(const string& uri); - -class Source -{ -public: - virtual bytevector Read(size_t chunk) = 0; - virtual bool IsOpen() = 0; - virtual bool End() = 0; - static unique_ptr Create(const string& url) - { - return CreateMedium(url); - } - virtual ~Source() {} -}; - -class Target -{ -public: - virtual void Write(const bytevector& portion) = 0; - virtual bool IsOpen() = 0; - virtual bool Broken() = 0; - static unique_ptr Create(const string& url) - { - return CreateMedium(url); - } - virtual ~Target() {} -}; - - map g_options; @@ -282,19 +102,14 @@ string Option(string deflt, string key, Args... further_keys) return i->second; } -volatile bool int_state = false; -volatile bool throw_on_interrupt = false; -bool transmit_verbose = false; ostream* cverb = &cout; -bool bidirectional = false; -unsigned srt_maxlossttl = 0; -unsigned stats_report_freq = 0; +volatile bool int_state = false; void OnINT_SetIntState(int) { cerr << "\n-------- REQUESTED INTERRUPT!\n"; int_state = true; - if ( throw_on_interrupt ) + if ( transmit_throw_on_interrupt ) throw std::runtime_error("Requested exception interrupt"); } @@ -367,8 +182,6 @@ struct BandwidthGuard } }; -int bw_report = 0; - extern "C" void TestLogHandler(void* opaque, int level, const char* file, int line, const char* area, const char* message); int main( int argc, char** argv ) @@ -429,23 +242,21 @@ int main( int argc, char** argv ) if ( chunk == 0 ) chunk = DEFAULT_CHUNK; size_t bandwidth = stoul(Option("0", "b", "bandwidth", "bitrate"), 0, 0); - bw_report = stoul(Option("0", "r", "report", "bandwidth-report", "bitrate-report"), 0, 0); + transmit_bw_report = stoul(Option("0", "r", "report", "bandwidth-report", "bitrate-report"), 0, 0); transmit_verbose = Option("no", "v", "verbose") != "no"; bool crashonx = Option("no", "k", "crash") != "no"; - bidirectional = Option("no", "2", "rw", "bidirectional") != "no"; string loglevel = Option("error", "loglevel"); string logfa = Option("general", "logfa"); string logfile = Option("", "logfile"); - srt_maxlossttl = stoi(Option("0", "ttl", "max-loss-delay")); - stats_report_freq = stoi(Option("0", "s", "stats", "stats-report-frequency"), 0, 0); + transmit_stats_report = stoi(Option("0", "s", "stats", "stats-report-frequency"), 0, 0); bool internal_log = Option("no", "loginternal") != "no"; std::ofstream logfile_stream; // leave unused if not set - srt_setloglevel(ParseLogLevel(loglevel)); - set fas = ParseLogFA(logfa); + srt_setloglevel(SrtParseLogLevel(loglevel)); + set fas = SrtParseLogFA(logfa); for (set::iterator i = fas.begin(); i != fas.end(); ++i) srt_addlogfa(*i); @@ -529,965 +340,29 @@ int main( int argc, char** argv ) break; } - bw.Checkpoint(chunk, bw_report); + bw.Checkpoint(chunk, transmit_bw_report); } alarm(0); + } catch (std::exception& x) { + + cerr << "STD EXCEPTION: " << x.what() << endl; + cerr << "Waiting 5s for possible cleanup...\n"; + this_thread::sleep_for(chrono::seconds(5)); } catch (...) { + + cerr << "UNKNOWN type of EXCEPTION\n"; if ( crashonx ) throw; return 1; } + return 0; } // Class utilities -string udt_status_names [] = { -"INIT" , "OPENED", "LISTENING", "CONNECTING", "CONNECTED", "BROKEN", "CLOSING", "CLOSED", "NONEXIST" -}; - -// Medium concretizations - -class FileSource: public Source -{ - ifstream ifile; -public: - - FileSource(const string& path): ifile(path, ios::in | ios::binary) {} - - bytevector Read(size_t chunk) override - { - bytevector data(chunk); - ifile.read(data.data(), chunk); - size_t nread = size_t(ifile.gcount()); - if ( nread < data.size() ) - data.resize(nread); - return data; - } - - bool IsOpen() override { return bool(ifile); } - bool End() override { return ifile.eof(); } - //~FileSource() { ifile.close(); } -}; - -class FileTarget: public Target -{ - ofstream ofile; -public: - - FileTarget(const string& path): ofile(path, ios::out | ios::trunc | ios::binary) {} - - void Write(const bytevector& data) override - { - ofile.write(data.data(), data.size()); - } - - bool IsOpen() override { return !!ofile; } - bool Broken() override { return !ofile.good(); } - //~FileTarget() { ofile.close(); } -}; - -template struct File; -template <> struct File { typedef FileSource type; }; -template <> struct File { typedef FileTarget type; }; - -template -Iface* CreateFile(const string& name) { return new typename File::type (name); } - - - - -class SrtCommon -{ - int srt_conn_epoll = -1; -protected: - - bool m_output_direction = false; //< Defines which of SND or RCV option variant should be used, also to set SRT_SENDER for output - bool m_blocking_mode = true; //< enforces using SRTO_SNDSYN or SRTO_RCVSYN, depending on @a m_output_direction - int m_timeout = 0; //< enforces using SRTO_SNDTIMEO or SRTO_RCVTIMEO, depending on @a m_output_direction - bool m_tsbpdmode = true; - map m_options; // All other options, as provided in the URI - SRTSOCKET m_sock = SRT_INVALID_SOCK; - SRTSOCKET m_bindsock = SRT_INVALID_SOCK; - bool IsUsable() { SRT_SOCKSTATUS st = srt_getsockstate(m_sock); return st > SRTS_INIT && st < SRTS_BROKEN; } - bool IsBroken() { return srt_getsockstate(m_sock) > SRTS_CONNECTED; } - - void Init(string host, int port, map par, bool dir_output) - { - m_output_direction = dir_output; - - // Application-specific options: mode, blocking, timeout, adapter - if ( transmit_verbose ) - { - cout << "Parameters:\n"; - for (map::iterator i = par.begin(); i != par.end(); ++i) - { - cout << "\t" << i->first << " = '" << i->second << "'\n"; - } - } - - string mode = "default"; - if ( par.count("mode") ) - mode = par.at("mode"); - - if ( mode == "default" ) - { - // Use the following convention: - // 1. Server for source, Client for target - // 2. If host is empty, then always server. - if ( host == "" ) - mode = "server"; - //else if ( !dir_output ) - //mode = "server"; - else - mode = "client"; - } - par.erase("mode"); - - if ( par.count("blocking") ) - { - m_blocking_mode = !false_names.count(par.at("blocking")); - par.erase("blocking"); - } - - if ( par.count("timeout") ) - { - m_timeout = stoi(par.at("timeout"), 0, 0); - par.erase("timeout"); - } - - string adapter = ""; // needed for rendezvous only - if ( par.count("adapter") ) - { - adapter = par.at("adapter"); - par.erase("adapter"); - } - - if ( par.count("tsbpd") && false_names.count(par.at("tsbpd")) ) - { - m_tsbpdmode = false; - } - - // Assign the others here. - m_options = par; - - if ( transmit_verbose ) - cout << "Opening SRT " << (dir_output ? "target" : "source") << " " << mode - << "(" << (m_blocking_mode ? "" : "non-") << "blocking)" - << " on " << host << ":" << port << endl; - - if ( mode == "client" || mode == "caller" ) - OpenClient(host, port); - else if ( mode == "server" || mode == "listener" ) - OpenServer(host == "" ? adapter : host, port); - else if ( mode == "rendezvous" ) - OpenRendezvous(adapter, host, port); - else - { - throw std::invalid_argument("Invalid 'mode'. Use 'client' or 'server'"); - } - } - - int AddPoller(SRTSOCKET socket, int modes) - { - int pollid = srt_epoll_create(); - if ( pollid == -1 ) - throw std::runtime_error("Can't create epoll in nonblocking mode"); - srt_epoll_add_usock(pollid, socket, &modes); - return pollid; - } - - virtual int ConfigurePost(SRTSOCKET sock) - { - bool yes = m_blocking_mode; - int result = 0; - if ( m_output_direction ) - { - result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &yes, sizeof yes); - if ( result == -1 ) - return result; - - if ( m_timeout ) - return srt_setsockopt(sock, 0, SRTO_SNDTIMEO, &m_timeout, sizeof m_timeout); - } - else - { - result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &yes, sizeof yes); - if ( result == -1 ) - return result; - - if ( m_timeout ) - return srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &m_timeout, sizeof m_timeout); - } - - SrtConfigurePost(sock, m_options); - - for (auto o: srt_options) - { - if ( o.binding == SocketOption::POST && m_options.count(o.name) ) - { - string value = m_options.at(o.name); - bool ok = o.apply(sock, value); - if ( transmit_verbose ) - { - if ( !ok ) - cout << "WARNING: failed to set '" << o.name << "' (post, " << (m_output_direction? "target":"source") << ") to " << value << endl; - else - cout << "NOTE: SRT/post::" << o.name << "=" << value << endl; - } - } - } - - return 0; - } - - virtual int ConfigurePre(SRTSOCKET sock) - { - int result = 0; - - int no = 0; - if ( !m_tsbpdmode ) - { - result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); - if ( result == -1 ) - return result; - } - - if ( ::srt_maxlossttl != 0 ) - { - result = srt_setsockopt(sock, 0, SRTO_LOSSMAXTTL, &srt_maxlossttl, sizeof srt_maxlossttl); - if ( result == -1 ) - return result; - } - - // Let's pretend async mode is set this way. - // This is for asynchronous connect. - int maybe = m_blocking_mode; - result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &maybe, sizeof maybe); - if ( result == -1 ) - return result; - - //if ( m_timeout ) - // result = srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &m_timeout, sizeof m_timeout); - //if ( result == -1 ) - // return result; - - //if ( transmit_verbose ) - //{ - // cout << "PRE: blocking mode set: " << yes << " timeout " << m_timeout << endl; - //} - - // host is only checked for emptiness and depending on that the connection mode is selected. - // Here we are not exactly interested with that information. - vector failures; - SocketOption::Mode conmode = SrtConfigurePre(sock, "", m_options, &failures); - - if ( transmit_verbose && conmode == SocketOption::FAILURE ) - { - cout << "WARNING: failed to set options: "; - copy(failures.begin(), failures.end(), ostream_iterator(cout, ", ")); - cout << endl; - } - - return 0; - } - - void OpenClient(string host, int port) - { - m_sock = srt_socket(AF_INET, SOCK_DGRAM, 0); - if ( m_sock == SRT_ERROR ) - Error(UDT::getlasterror(), "srt_socket"); - - int stat = ConfigurePre(m_sock); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "ConfigurePre"); - - if ( !m_blocking_mode ) - { - srt_conn_epoll = AddPoller(m_sock, SRT_EPOLL_OUT); - } - - sockaddr_in sa = CreateAddrInet(host, port); - sockaddr* psa = (sockaddr*)&sa; - if ( transmit_verbose ) - { - cout << "Connecting to " << host << ":" << port << " ... "; - cout.flush(); - } - stat = srt_connect(m_sock, psa, sizeof sa); - if ( stat == SRT_ERROR ) - { - srt_close(m_sock); - Error(UDT::getlasterror(), "UDT::connect"); - } - - if ( !m_blocking_mode ) - { - if ( transmit_verbose ) - cout << "[ASYNC] " << flush; - - /* SPIN-WAITING version. Don't use it unless you know what you're doing. - - for (;;) - { - int state = UDT::getsockstate(m_sock); - if ( state < CONNECTED ) - { - if ( verbose ) - cout << state << flush; - usleep(250000); - continue; - } - else if ( state > CONNECTED ) - { - Error(UDT::getlasterror(), "UDT::connect status=" + udt_status_names[state]); - } - - stat = 0; // fake that connect() returned 0 - break; - } - */ - - // Socket readiness for connection is checked by polling on WRITE allowed sockets. - int len = 2; - SRTSOCKET ready[2]; - if ( srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) != -1 ) - { - if ( transmit_verbose ) - { - cout << "[EPOLL: " << len << " sockets] " << flush; - } - } - else - { - Error(UDT::getlasterror(), "srt_epoll_wait"); - } - } - - if ( transmit_verbose ) - cout << " connected.\n"; - stat = ConfigurePost(m_sock); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "ConfigurePost"); - } - - void Error(UDT::ERRORINFO& udtError, string src) - { - int udtResult = udtError.getErrorCode(); - if ( transmit_verbose ) - cout << "FAILURE\n" << src << ": [" << udtResult << "] " << udtError.getErrorMessage() << endl; - udtError.clear(); - throw std::invalid_argument("error in " + src); - } - - void OpenServer(string host, int port) - { - m_bindsock = srt_socket(AF_INET, SOCK_DGRAM, 0); - if ( m_bindsock == SRT_ERROR ) - Error(UDT::getlasterror(), "srt_socket"); - - int stat = ConfigurePre(m_bindsock); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "ConfigurePre"); - - if ( !m_blocking_mode ) - { - srt_conn_epoll = AddPoller(m_bindsock, SRT_EPOLL_OUT); - } - - sockaddr_in sa = CreateAddrInet(host, port); - sockaddr* psa = (sockaddr*)&sa; - if ( transmit_verbose ) - { - cout << "Binding a server on " << host << ":" << port << " ..."; - cout.flush(); - } - stat = srt_bind(m_bindsock, psa, sizeof sa); - if ( stat == SRT_ERROR ) - { - srt_close(m_bindsock); - Error(UDT::getlasterror(), "srt_bind"); - } - - if ( transmit_verbose ) - { - cout << " listen... "; - cout.flush(); - } - stat = srt_listen(m_bindsock, 1); - if ( stat == SRT_ERROR ) - { - srt_close(m_bindsock); - Error(UDT::getlasterror(), "srt_listen"); - } - - sockaddr_in scl; - int sclen = sizeof scl; - if ( transmit_verbose ) - { - cout << " accept... "; - cout.flush(); - } - ::throw_on_interrupt = true; - - if ( !m_blocking_mode ) - { - if ( transmit_verbose ) - cout << "[ASYNC] " << flush; - - int len = 2; - SRTSOCKET ready[2]; - if ( srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) == -1 ) - Error(UDT::getlasterror(), "srt_epoll_wait"); - - if ( transmit_verbose ) - { - cout << "[EPOLL: " << len << " sockets] " << flush; - } - } - - m_sock = srt_accept(m_bindsock, (sockaddr*)&scl, &sclen); - if ( m_sock == SRT_INVALID_SOCK ) - { - srt_close(m_bindsock); - Error(UDT::getlasterror(), "srt_accept"); - } - - if ( transmit_verbose ) - cout << " connected.\n"; - ::throw_on_interrupt = false; - - // ConfigurePre is done on bindsock, so any possible Pre flags - // are DERIVED by sock. ConfigurePost is done exclusively on sock. - stat = ConfigurePost(m_sock); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "ConfigurePost"); - } - - void OpenRendezvous(string adapter, string host, int port) - { - m_sock = srt_socket(AF_INET, SOCK_DGRAM, 0); - if ( m_sock == SRT_ERROR ) - Error(UDT::getlasterror(), "srt_socket"); - - bool yes = true; - srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); - - int stat = ConfigurePre(m_sock); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "ConfigurePre"); - - if ( !m_blocking_mode ) - { - srt_conn_epoll = AddPoller(m_bindsock, SRT_EPOLL_OUT); - } - - sockaddr_in localsa = CreateAddrInet(adapter, port); - sockaddr* plsa = (sockaddr*)&localsa; - if ( transmit_verbose ) - { - cout << "Binding a server on " << adapter << ":" << port << " ..."; - cout.flush(); - } - stat = srt_bind(m_sock, plsa, sizeof localsa); - if ( stat == SRT_ERROR ) - { - srt_close(m_sock); - Error(UDT::getlasterror(), "srt_bind"); - } - - sockaddr_in sa = CreateAddrInet(host, port); - sockaddr* psa = (sockaddr*)&sa; - if ( transmit_verbose ) - { - cout << "Connecting to " << host << ":" << port << " ... "; - cout.flush(); - } - stat = srt_connect(m_sock, psa, sizeof sa); - if ( stat == SRT_ERROR ) - { - srt_close(m_sock); - Error(UDT::getlasterror(), "srt_connect"); - } - - if ( transmit_verbose ) - cout << " connected.\n"; - - stat = ConfigurePost(m_sock); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "ConfigurePost"); - } - - ~SrtCommon() - { - if ( transmit_verbose ) - cout << "SrtCommon: DESTROYING CONNECTION, closing sockets\n"; - if ( m_sock != UDT::INVALID_SOCK ) - srt_close(m_sock); - - if ( m_bindsock != UDT::INVALID_SOCK ) - srt_close(m_bindsock); - } -}; - -class SrtSource: public Source, public SrtCommon -{ - int srt_epoll = -1; -public: - - SrtSource(string host, int port, const map& par) - { - Init(host, port, par, false); - - if ( !m_blocking_mode ) - { - srt_epoll = AddPoller(m_sock, SRT_EPOLL_IN); - } - } - - bytevector Read(size_t chunk) override - { - static size_t counter = 1; - - bytevector data(chunk); - bool ready = true; - int stat; - do - { - ::throw_on_interrupt = true; - stat = srt_recvmsg(m_sock, data.data(), chunk); - ::throw_on_interrupt = false; - if ( stat == SRT_ERROR ) - { - if ( !m_blocking_mode ) - { - // EAGAIN for SRT READING - if ( srt_getlasterror(NULL) == SRT_EASYNCRCV ) - { - if ( transmit_verbose ) - { - cout << "AGAIN: - waiting for data by epoll...\n"; - } - // Poll on this descriptor until reading is available, indefinitely. - int len = 2; - SRTSOCKET ready[2]; - if ( srt_epoll_wait(srt_epoll, ready, &len, 0, 0, -1, 0, 0, 0, 0) != -1 ) - { - if ( transmit_verbose ) - { - cout << "... epoll reported ready " << len << " sockets\n"; - } - continue; - } - // If was -1, then passthru. - } - } - Error(UDT::getlasterror(), "recvmsg"); - return bytevector(); - } - - if ( stat == 0 ) - { - // Not necessarily eof. Closed connection is reported as error. - this_thread::sleep_for(chrono::milliseconds(10)); - ready = false; - } - } - while (!ready); - - chunk = size_t(stat); - if ( chunk < data.size() ) - data.resize(chunk); - - CBytePerfMon perf; - srt_bstats(m_sock, &perf, false); - if ( bw_report && int(counter % bw_report) == bw_report - 1 ) - { - cout << "+++/+++SRT BANDWIDTH: " << perf.mbpsBandwidth << endl; - } - - if ( stats_report_freq && counter % stats_report_freq == stats_report_freq - 1) - { - //CPerfMon pmon; - //memset(&pmon, 0, sizeof pmon); - //UDT::perfmon(m_sock, &pmon, false); - //PrintSrtStats(m_sock, pmon); - PrintSrtStats(m_sock, perf); - } - - ++counter; - - return data; - } - - virtual int ConfigurePre(UDTSOCKET sock) override - { - int result = SrtCommon::ConfigurePre(sock); - if ( result == -1 ) - return result; - // For sending party, the SRT_SENDER flag must be set, otherwise - // the connection will be pure UDT. - int yes = 1; - - if ( ::bidirectional ) - { - result = srt_setsockopt(sock, 0, SRTO_TWOWAYDATA, &yes, sizeof yes); - if ( result == -1 ) - return result; - } - - return 0; - } - - bool IsOpen() override { return IsUsable(); } - bool End() override { return IsBroken(); } -}; - -class SrtTarget: public Target, public SrtCommon -{ - int srt_epoll = -1; -public: - - SrtTarget(string host, int port, const map& par) - { - Init(host, port, par, true); - } - - virtual int ConfigurePre(SRTSOCKET sock) override - { - int result = SrtCommon::ConfigurePre(sock); - if ( result == -1 ) - return result; - // For sending party, the SRT_SENDER flag must be set, otherwise - // the connection will be pure UDT. - int yes = 1; - - if ( ::bidirectional ) - { - result = srt_setsockopt(sock, 0, SRTO_TWOWAYDATA, &yes, sizeof yes); - if ( result == -1 ) - return result; - } - else - { - result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); - if ( result == -1 ) - return result; - } - - return 0; - } - - void Write(const bytevector& data) override - { - ::throw_on_interrupt = true; - - // Check first if it's ready to write. - // If not, wait indefinitely. - if ( !m_blocking_mode ) - { - int ready[2]; - int len = 2; - if ( srt_epoll_wait(srt_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) == SRT_ERROR ) - Error(UDT::getlasterror(), "srt_epoll_wait"); - } - - int stat = srt_sendmsg2(m_sock, data.data(), data.size(), nullptr); - if ( stat == SRT_ERROR ) - Error(UDT::getlasterror(), "srt_sendmsg"); - ::throw_on_interrupt = false; - } - - bool IsOpen() override { return IsUsable(); } - bool Broken() override { return IsBroken(); } - -}; - -template struct Srt; -template <> struct Srt { typedef SrtSource type; }; -template <> struct Srt { typedef SrtTarget type; }; - -template -Iface* CreateSrt(const string& host, int port, const map& par) { return new typename Srt::type (host, port, par); } - -class ConsoleSource: public Source -{ -public: - - ConsoleSource() - { - } - - bytevector Read(size_t chunk) override - { - bytevector data(chunk); - bool st = cin.read(data.data(), chunk).good(); - chunk = size_t(cin.gcount()); - if ( chunk == 0 && !st ) - return bytevector(); - - if ( chunk < data.size() ) - data.resize(chunk); - - return data; - } - - bool IsOpen() override { return cin.good(); } - bool End() override { return cin.eof(); } -}; - -class ConsoleTarget: public Target -{ -public: - - ConsoleTarget() - { - } - - void Write(const bytevector& data) override - { - cout.write(data.data(), data.size()); - } - - bool IsOpen() override { return cout.good(); } - bool Broken() override { return cout.eof(); } -}; - -template struct Console; -template <> struct Console { typedef ConsoleSource type; }; -template <> struct Console { typedef ConsoleTarget type; }; - -template -Iface* CreateConsole() { return new typename Console::type (); } - - -// More options can be added in future. -SocketOption udp_options [] { - { "ipttl", IPPROTO_IP, IP_TTL, SocketOption::INT, SocketOption::PRE }, - { "iptos", IPPROTO_IP, IP_TOS, SocketOption::INT, SocketOption::PRE }, -}; - -class UdpCommon -{ -protected: - int m_sock = -1; - sockaddr_in sadr; - string adapter; - map m_options; - - void Setup(string host, int port, map attr) - { - m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (m_sock == -1) - { - perror("UdpCommon:socket"); - throw std::runtime_error("UdpCommon: failed to create a socket"); - } - sadr = CreateAddrInet(host, port); - - if ( attr.count("multicast") ) - { - adapter = attr.count("adapter") ? attr.at("adapter") : string(); - sockaddr_in maddr; - if ( adapter == "" ) - { - maddr.sin_addr.s_addr = htonl(INADDR_ANY); - } - else - { - maddr = CreateAddrInet(adapter, port); - } - - ip_mreq mreq; - mreq.imr_multiaddr.s_addr = sadr.sin_addr.s_addr; - mreq.imr_interface.s_addr = maddr.sin_addr.s_addr; -#ifdef WIN32 - const char* mreq_arg = (const char*)&mreq; - const auto status_error = SOCKET_ERROR; -#else - const void* mreq_arg = &mreq; - const auto status_error = -1; -#endif - int res = setsockopt(m_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq_arg, sizeof(mreq)); - - if ( res == status_error ) - { - throw runtime_error("adding to multicast membership failed"); - } - attr.erase("multicast"); - attr.erase("adapter"); - } - - m_options = attr; - - for (auto o: udp_options) - { - // Ignore "binding" - for UDP there are no post options. - if ( m_options.count(o.name) ) - { - string value = m_options.at(o.name); - bool ok = o.apply(m_sock, value); - if ( transmit_verbose && !ok ) - cout << "WARNING: failed to set '" << o.name << "' to " << value << endl; - } - } - } - - ~UdpCommon() - { -#ifdef WIN32 - if (m_sock != -1) - { - shutdown(m_sock, SD_BOTH); - closesocket(m_sock); - m_sock = -1; - } -#else - close(m_sock); -#endif - } -}; - - -class UdpSource: public Source, public UdpCommon -{ - bool eof = true; -public: - - UdpSource(string host, int port, const map& attr) - { - Setup(host, port, attr); - int stat = ::bind(m_sock, (sockaddr*)&sadr, sizeof sadr); - if ( stat == -1 ) - { - perror("bind"); - throw runtime_error("bind failed, UDP cannot read"); - } - eof = false; - } - - bytevector Read(size_t chunk) override - { - bytevector data(chunk); - sockaddr_in sa; - socklen_t si = sizeof(sockaddr_in); - int stat = recvfrom(m_sock, data.data(), chunk, 0, (sockaddr*)&sa, &si); - if ( stat == -1 || stat == 0 ) - { - eof = true; - return bytevector(); - } - - chunk = size_t(stat); - if ( chunk < data.size() ) - data.resize(chunk); - - return data; - } - - bool IsOpen() override { return m_sock != -1; } - bool End() override { return eof; } -}; - -class UdpTarget: public Target, public UdpCommon -{ -public: - UdpTarget(string host, int port, const map& attr ) - { - Setup(host, port, attr); - } - - void Write(const bytevector& data) override - { - int stat = sendto(m_sock, data.data(), data.size(), 0, (sockaddr*)&sadr, sizeof sadr); - if ( stat == -1 ) - { - perror("UdpTarget: write"); - throw runtime_error("Error during write"); - } - } - - bool IsOpen() override { return m_sock != -1; } - bool Broken() override { return false; } -}; - -template struct Udp; -template <> struct Udp { typedef UdpSource type; }; -template <> struct Udp { typedef UdpTarget type; }; - -template -Iface* CreateUdp(const string& host, int port, const map& par) { return new typename Udp::type (host, port, par); } - -template -inline bool IsOutput() { return false; } - -template<> -inline bool IsOutput() { return true; } - -template -unique_ptr CreateMedium(const string& uri) -{ - unique_ptr ptr; - - UriParser u(uri); - - int iport = 0; - switch ( u.type() ) - { - default: ; // do nothing, return nullptr - case UriParser::FILE: - if ( u.host() == "con" || u.host() == "console" ) - { - if ( IsOutput() && ( - (transmit_verbose && cverb == &cout) - || bw_report) ) - { - cerr << "ERROR: file://con with -v or -r would result in mixing the data and text info.\n"; - cerr << "ERROR: HINT: you can stream through a FIFO (named pipe)\n"; - throw invalid_argument("incorrect parameter combination"); - } - ptr.reset( CreateConsole() ); - } - else - ptr.reset( CreateFile(u.path())); - break; - - - case UriParser::SRT: -#if !DEVELOPER_MODE - if ( IsOutput() ) - { - cerr << "SRT output not supported\n"; - throw invalid_argument("incorrect output"); - } -#endif - iport = atoi(u.port().c_str()); - if ( iport <= 1024 ) - { - cerr << "Port value invalid: " << iport << " - must be >1024\n"; - throw invalid_argument("Invalid port number"); - } - ptr.reset( CreateSrt(u.host(), iport, u.parameters()) ); - break; - - - case UriParser::UDP: - iport = atoi(u.port().c_str()); - if ( iport <= 1024 ) - { - cerr << "Port value invalid: " << iport << " - must be >1024\n"; - throw invalid_argument("Invalid port number"); - } - ptr.reset( CreateUdp(u.host(), iport, u.parameters()) ); - break; - - } - - return ptr; -} - void TestLogHandler(void* opaque, int level, const char* file, int line, const char* area, const char* message) { diff --git a/apps/utility-test.cpp b/apps/utility-test.cpp index 413deee37..21637a7ac 100644 --- a/apps/utility-test.cpp +++ b/apps/utility-test.cpp @@ -23,14 +23,14 @@ #include #include #include -#include +#include int main() { using namespace std; cout << "PacketBoundary: " << hex << MSGNO_PACKET_BOUNDARY::mask << endl; - + cout << "PB_FIRST: " << hex << PacketBoundaryBits(PB_FIRST) << endl; cout << "PB_LAST: " << hex << PacketBoundaryBits(PB_LAST) << endl; cout << "PB_SOLO: " << hex << PacketBoundaryBits(PB_SOLO) << endl; diff --git a/common/README.md b/common/README.md new file mode 100644 index 000000000..146057f18 --- /dev/null +++ b/common/README.md @@ -0,0 +1,29 @@ +Common files +============ + +This directory is meant to hold source files that may be reusable components. + +Initially they were thought of as common for multiple applications. + +The UriParser class is universal enough to parse and interpret URI. +It's important for SRT to specify not only the medium address, but +also some extra parameters. + +The SocketOption class is originally part of stransmit, but it was extracted +so that you can do the same in your application. It's useful for having +a text-specified option with value that you'd set to a socket, which is +the form that has been extracted from the URI by UriParser. It's also +responsible for a standard way of extracting the caller/listener/rendezvous +mode. + +These things are not meant for the public API yet, but they are extracted +the way that may allow you to use it in your application. + + +Compat +====== + +This part contains some portability problem resolutions, including: + - `clock_gettime`, a function that is unavailable on Mac + - `strerror` in a version that is both portable and thread safe + diff --git a/common/appcommon.hpp b/common/appcommon.hpp index 122d207dd..a33b4eb96 100644 --- a/common/appcommon.hpp +++ b/common/appcommon.hpp @@ -64,6 +64,7 @@ inline void SysCleanupNetwork() {} #endif #include +#include // NOTE: MINGW currently does not include support for inet_pton(). See // http://mingw.5.n7.nabble.com/Win32API-request-for-new-functions-td22029.html diff --git a/common/logsupport.cpp b/common/logsupport.cpp new file mode 100644 index 000000000..a3f4be691 --- /dev/null +++ b/common/logsupport.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include "logsupport.hpp" +#include "../srtcore/srt.h" + +using namespace std; + +// This is based on codes taken from +// This is POSIX standard, so it's not going to change. +// Haivision standard only adds one more severity below +// DEBUG named DEBUG_TRACE to satisfy all possible needs. + +map srt_level_names +{ + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "debug", LOG_DEBUG }, + { "emerg", LOG_EMERG }, + { "err", LOG_ERR }, + { "error", LOG_ERR }, /* DEPRECATED */ + { "fatal", LOG_CRIT }, // XXX Added for SRT + { "info", LOG_INFO }, + // WTF? Undefined symbol? { "none", INTERNAL_NOPRI }, /* INTERNAL */ + { "notice", LOG_NOTICE }, + { "note", LOG_NOTICE }, // XXX Added for SRT + { "panic", LOG_EMERG }, /* DEPRECATED */ + { "warn", LOG_WARNING }, /* DEPRECATED */ + { "warning", LOG_WARNING }, + //{ "", -1 } +}; + + + +logging::LogLevel::type SrtParseLogLevel(string level) +{ + using namespace logging; + + if ( level.empty() ) + return LogLevel::fatal; + + if ( isdigit(level[0]) ) + { + long lev = strtol(level.c_str(), 0, 10); + if ( lev >= SRT_LOG_LEVEL_MIN && lev <= SRT_LOG_LEVEL_MAX ) + return LogLevel::type(lev); + + cerr << "ERROR: Invalid loglevel number: " << level << " - fallback to FATAL\n"; + return LogLevel::fatal; + } + + int (*ToLower)(int) = &std::tolower; // manual overload resolution + transform(level.begin(), level.end(), level.begin(), ToLower); + + auto i = srt_level_names.find(level); + if ( i == srt_level_names.end() ) + { + cerr << "ERROR: Invalid loglevel spec: " << level << " - fallback to FATAL\n"; + return LogLevel::fatal; + } + + return LogLevel::type(i->second); +} + +set SrtParseLogFA(string fa) +{ + using namespace logging; + + set fas; + + // The split algo won't work on empty string. + if ( fa == "" ) + return fas; + + static string names [] = { "general", "bstats", "control", "data", "tsbpd", "rexmit" }; + size_t names_s = sizeof (names)/sizeof (names[0]); + + if ( fa == "all" ) + { + // Skip "general", it's always on + fas.insert(SRT_LOGFA_BSTATS); + fas.insert(SRT_LOGFA_CONTROL); + fas.insert(SRT_LOGFA_DATA); + fas.insert(SRT_LOGFA_TSBPD); + fas.insert(SRT_LOGFA_REXMIT); + return fas; + } + + int (*ToLower)(int) = &std::tolower; + transform(fa.begin(), fa.end(), fa.begin(), ToLower); + + vector xfas; + size_t pos = 0, ppos = 0; + for (;;) + { + if ( fa[pos] != ',' ) + { + ++pos; + if ( pos < fa.size() ) + continue; + } + size_t n = pos - ppos; + if ( n != 0 ) + xfas.push_back(fa.substr(ppos, n)); + ++pos; + if ( pos >= fa.size() ) + break; + ppos = pos; + } + + for (size_t i = 0; i < xfas.size(); ++i) + { + fa = xfas[i]; + string* names_p = find(names, names + names_s, fa); + if ( names_p == names + names_s ) + { + cerr << "ERROR: Invalid log functional area spec: '" << fa << "' - skipping\n"; + continue; + } + + size_t nfa = names_p - names; + + if ( nfa != 0 ) + fas.insert(nfa); + } + + return fas; +} + + diff --git a/common/logsupport.hpp b/common/logsupport.hpp new file mode 100644 index 000000000..d695338b8 --- /dev/null +++ b/common/logsupport.hpp @@ -0,0 +1,16 @@ +#ifndef INC__LOGSUPPORT_HPP +#define INC__LOGSUPPORT_HPP + +#include "../srtcore/udt.h" +#include "../srtcore/logging_api.h" + +logging::LogLevel::type SrtParseLogLevel(std::string level); +std::set SrtParseLogFA(std::string fa); + +UDT_API extern std::map srt_level_names; + +namespace logging { class LogConfig; } +UDT_API extern logging::LogConfig srt_logger_config; + + +#endif diff --git a/srtcore/netinet_any.h b/common/netinet_any.h similarity index 96% rename from srtcore/netinet_any.h rename to common/netinet_any.h index b0fda9b3c..e553a029f 100644 --- a/srtcore/netinet_any.h +++ b/common/netinet_any.h @@ -52,8 +52,8 @@ struct sockaddr_any { switch (sa.sa_family) { - case AF_INET: return static_cast(sizeof sin); - case AF_INET6: return static_cast(sizeof sin6); + case AF_INET: return socklen_t(sizeof sin); + case AF_INET6: return socklen_t(sizeof sin6); default: return 0; // fallback, impossible } diff --git a/srtcore/platform_sys.h b/common/platform_sys.h similarity index 100% rename from srtcore/platform_sys.h rename to common/platform_sys.h diff --git a/common/socketoptions.hpp b/common/socketoptions.hpp index e3ca2ec42..c0025ab57 100644 --- a/common/socketoptions.hpp +++ b/common/socketoptions.hpp @@ -166,7 +166,12 @@ SocketOption srt_options [] { { "tsbpddelay", 0, SRTO_TSBPDDELAY, SocketOption::INT, SocketOption::PRE }, { "tlpktdrop", 0, SRTO_TLPKTDROP, SocketOption::BOOL, SocketOption::PRE }, { "nakreport", 0, SRTO_NAKREPORT, SocketOption::BOOL, SocketOption::PRE }, - { "conntimeo", 0, SRTO_CONNTIMEO, SocketOption::INT, SocketOption::PRE } + { "conntimeo", 0, SRTO_CONNTIMEO, SocketOption::INT, SocketOption::PRE }, + { "lossmaxttl", 0, SRTO_LOSSMAXTTL, SocketOption::INT, SocketOption::PRE }, + { "rcvlatency", 0, SRTO_RCVLATENCY, SocketOption::INT, SocketOption::PRE }, + { "peerlatency", 0, SRTO_PEERLATENCY, SocketOption::INT, SocketOption::PRE }, + { "minversion", 0, SRTO_MINVERSION, SocketOption::INT, SocketOption::PRE }, + { "streamid", 0, SRTO_STREAMID, SocketOption::STRING, SocketOption::PRE } }; } diff --git a/common/srt_compat.c b/common/srt_compat.c index b03083c6b..1bb730a03 100644 --- a/common/srt_compat.c +++ b/common/srt_compat.c @@ -151,7 +151,7 @@ extern const char * SysStrError(int errnum, char * buf, size_t buflen) buf[0] = '\0'; #if defined(_WIN32) || defined(WIN32) - const char* lpMsgBuf; + char* lpMsgBuf; // Note: Intentionally the "fixed char size" types are used despite using // character size dependent FormatMessage (instead of FormatMessageA) so that diff --git a/common/threadname.h b/common/threadname.h new file mode 100644 index 000000000..6c78e222a --- /dev/null +++ b/common/threadname.h @@ -0,0 +1,96 @@ +/***************************************************************************** + * SRT - Secure, Reliable, Transport + * Copyright (c) 2017 Haivision Systems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * + *****************************************************************************/ + +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +#ifndef INC__THREADNAME_H +#define INC__THREADNAME_H + +#ifdef __linux__ + +#include + +class ThreadName +{ + char old_name[128]; + char new_name[128]; + bool good; + +public: + static const size_t BUFSIZE = 128; + + static bool get(char* namebuf) + { + return prctl(PR_GET_NAME, (unsigned long)namebuf, 0, 0) != -1; + } + + static bool set(const char* name) + { + return prctl(PR_SET_NAME, (unsigned long)name, 0, 0) != -1; + } + + + ThreadName(const char* name) + { + if ( get(old_name) ) + { + snprintf(new_name, 127, "%s", name); + new_name[127] = 0; + prctl(PR_SET_NAME, (unsigned long)new_name, 0, 0); + } + } + + ~ThreadName() + { + if ( good ) + prctl(PR_SET_NAME, (unsigned long)old_name, 0, 0); + } +}; + +#else + +// Fake class, which does nothing. You can also take a look how +// this works in other systems that are not supported here and add +// the support. This is a fallback for systems that do not support +// thread names. + +class ThreadName +{ +public: + + static bool get(char*) { return false; } + static bool set(const char*) { return false; } + + ThreadName(const char*) + { + } + + ~ThreadName() // just to make it "non-trivially-destructible" for compatibility with normal version + { + } + +}; + + + +#endif +#endif diff --git a/common/transmitbase.hpp b/common/transmitbase.hpp new file mode 100644 index 000000000..183a5df6d --- /dev/null +++ b/common/transmitbase.hpp @@ -0,0 +1,54 @@ +#ifndef INC__COMMON_TRANMITBASE_HPP +#define INC__COMMON_TRANMITBASE_HPP + +#include +#include +#include + +typedef std::vector bytevector; +extern bool transmit_verbose; +extern volatile bool transmit_throw_on_interrupt; +extern int transmit_bw_report; +extern unsigned transmit_stats_report; +extern std::ostream* transmit_cverb; + +class Location +{ +public: + UriParser uri; + Location() {} +}; + +class Source: public Location +{ +public: + virtual bytevector Read(size_t chunk) = 0; + virtual bool IsOpen() = 0; + virtual bool End() = 0; + static std::unique_ptr Create(const std::string& url); + virtual void Close() {} + virtual ~Source() {} + + class ReadEOF: public std::runtime_error + { + public: + ReadEOF(const std::string& fn): std::runtime_error( "EOF while reading file: " + fn ) + { + } + }; +}; + +class Target: public Location +{ +public: + virtual void Write(const bytevector& portion) = 0; + virtual bool IsOpen() = 0; + virtual bool Broken() = 0; + virtual void Close() {} + static std::unique_ptr Create(const std::string& url); + virtual ~Target() {} +}; + + + +#endif diff --git a/common/transmitmedia.cpp b/common/transmitmedia.cpp new file mode 100644 index 000000000..ba81bf4d7 --- /dev/null +++ b/common/transmitmedia.cpp @@ -0,0 +1,1042 @@ +// Medium concretizations + +// Just for formality. This file should be used +#include +#include +#include +#include +#include +#include +#include +#include + +#include "appcommon.hpp" +#include "socketoptions.hpp" +#include "uriparser.hpp" +#include "transmitmedia.hpp" + +using namespace std; + +bool transmit_verbose = false; +std::ostream* transmit_cverb = nullptr; +volatile bool transmit_throw_on_interrupt = false; +int transmit_bw_report = 0; +unsigned transmit_stats_report = 0; + +class FileSource: public Source +{ + ifstream ifile; + string filename_copy; +public: + + FileSource(const string& path): ifile(path, ios::in | ios::binary), filename_copy(path) + { + if ( !ifile ) + throw std::runtime_error(path + ": Can't open file for reading"); + } + + bytevector Read(size_t chunk) override + { + bytevector data(chunk); + ifile.read(data.data(), chunk); + size_t nread = ifile.gcount(); + if ( nread < data.size() ) + data.resize(nread); + + if ( data.empty() ) + throw ReadEOF(filename_copy); + return data; + } + + bool IsOpen() override { return bool(ifile); } + bool End() override { return ifile.eof(); } + //~FileSource() { ifile.close(); } +}; + +class FileTarget: public Target +{ + ofstream ofile; +public: + + FileTarget(const string& path): ofile(path, ios::out | ios::trunc | ios::binary) {} + + void Write(const bytevector& data) override + { + ofile.write(data.data(), data.size()); + } + + bool IsOpen() override { return !!ofile; } + bool Broken() override { return !ofile.good(); } + //~FileTarget() { ofile.close(); } + void Close() override { ofile.close(); } +}; + +template struct File; +template <> struct File { typedef FileSource type; }; +template <> struct File { typedef FileTarget type; }; + +template +Iface* CreateFile(const string& name) { return new typename File::type (name); } + + +template +void PrintSrtStats(int sid, const PerfMonType& mon) +{ + cout << "======= SRT STATS: sid=" << sid << endl; + cout << "PACKETS SENT: " << mon.pktSent << " RECEIVED: " << mon.pktRecv << endl; + cout << "LOST PKT SENT: " << mon.pktSndLoss << " RECEIVED: " << mon.pktRcvLoss << endl; + cout << "REXMIT SENT: " << mon.pktRetrans << " RECEIVED: " << mon.pktRcvRetrans << endl; + cout << "RATE SENDING: " << mon.mbpsSendRate << " RECEIVING: " << mon.mbpsRecvRate << endl; + cout << "BELATED RECEIVED: " << mon.pktRcvBelated << " AVG TIME: " << mon.pktRcvAvgBelatedTime << endl; + cout << "REORDER DISTANCE: " << mon.pktReorderDistance << endl; + cout << "WINDOW: FLOW: " << mon.pktFlowWindow << " CONGESTION: " << mon.pktCongestionWindow << " FLIGHT: " << mon.pktFlightSize << endl; + cout << "RTT: " << mon.msRTT << "ms BANDWIDTH: " << mon.mbpsBandwidth << "Mb/s\n"; + cout << "BUFFERLEFT: SND: " << mon.byteAvailSndBuf << " RCV: " << mon.byteAvailRcvBuf << endl; +} + + +void SrtCommon::InitParameters(string host, map par) +{ + // Application-specific options: mode, blocking, timeout, adapter + if ( transmit_verbose ) + { + cout << "Parameters:\n"; + for (map::iterator i = par.begin(); i != par.end(); ++i) + { + cout << "\t" << i->first << " = '" << i->second << "'\n"; + } + } + + m_mode = "default"; + if ( par.count("mode") ) + m_mode = par.at("mode"); + + if ( m_mode == "default" ) + { + // Use the following convention: + // 1. Server for source, Client for target + // 2. If host is empty, then always server. + if ( host == "" ) + m_mode = "listener"; + //else if ( !dir_output ) + //m_mode = "server"; + else + m_mode = "caller"; + } + + if ( m_mode == "client" ) + m_mode = "caller"; + else if ( m_mode == "server" ) + m_mode = "listener"; + + par.erase("mode"); + + if ( par.count("blocking") ) + { + m_blocking_mode = !false_names.count(par.at("blocking")); + par.erase("blocking"); + } + + if ( par.count("timeout") ) + { + m_timeout = stoi(par.at("timeout"), 0, 0); + par.erase("timeout"); + } + + if ( par.count("adapter") ) + { + m_adapter = par.at("adapter"); + par.erase("adapter"); + } + else if (m_mode == "listener") + { + // For listener mode, adapter is taken from host, + // if 'adapter' parameter is not given + m_adapter = host; + } + + if ( par.count("tsbpd") && false_names.count(par.at("tsbpd")) ) + { + m_tsbpdmode = false; + } + + if (par.count("port")) + { + m_outgoing_port = stoi(par.at("port"), 0, 0); + par.erase("port"); + } + + // Assign the others here. + m_options = par; + +} + +void SrtCommon::PrepareListener(string host, int port, int backlog) +{ + m_bindsock = srt_socket(AF_INET, SOCK_DGRAM, 0); + if ( m_bindsock == SRT_ERROR ) + Error(UDT::getlasterror(), "srt_socket"); + + int stat = ConfigurePre(m_bindsock); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "ConfigurePre"); + + if ( !m_blocking_mode ) + { + srt_conn_epoll = AddPoller(m_bindsock, SRT_EPOLL_OUT); + } + + sockaddr_in sa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&sa; + if ( transmit_verbose ) + { + cout << "Binding a server on " << host << ":" << port << " ..."; + cout.flush(); + } + stat = srt_bind(m_bindsock, psa, sizeof sa); + if ( stat == SRT_ERROR ) + { + srt_close(m_bindsock); + Error(UDT::getlasterror(), "srt_bind"); + } + + if ( transmit_verbose ) + { + cout << " listen... "; + cout.flush(); + } + stat = srt_listen(m_bindsock, backlog); + if ( stat == SRT_ERROR ) + { + srt_close(m_bindsock); + Error(UDT::getlasterror(), "srt_listen"); + } + + if ( transmit_verbose ) + { + cout << " accept... "; + cout.flush(); + } + ::transmit_throw_on_interrupt = true; + + if ( !m_blocking_mode ) + { + if ( transmit_verbose ) + cout << "[ASYNC] " << flush; + + int len = 2; + SRTSOCKET ready[2]; + if ( srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) == -1 ) + Error(UDT::getlasterror(), "srt_epoll_wait"); + + if ( transmit_verbose ) + { + cout << "[EPOLL: " << len << " sockets] " << flush; + } + } +} + +void SrtCommon::StealFrom(SrtCommon& src) +{ + // This is used when SrtCommon class designates a listener + // object that is doing Accept in appropriate direction class. + // The new object should get the accepted socket. + m_output_direction = src.m_output_direction; + m_blocking_mode = src.m_blocking_mode; + m_timeout = src.m_timeout; + m_tsbpdmode = src.m_tsbpdmode; + m_options = src.m_options; + m_bindsock = SRT_INVALID_SOCK; // no listener + m_sock = src.m_sock; + src.m_sock = SRT_INVALID_SOCK; // STEALING +} + +void SrtCommon::AcceptNewClient() +{ + sockaddr_in scl; + int sclen = sizeof scl; + m_sock = srt_accept(m_bindsock, (sockaddr*)&scl, &sclen); + if ( m_sock == SRT_INVALID_SOCK ) + { + srt_close(m_bindsock); + Error(UDT::getlasterror(), "srt_accept"); + } + + if ( transmit_verbose ) + cout << " connected.\n"; + ::transmit_throw_on_interrupt = false; + + // ConfigurePre is done on bindsock, so any possible Pre flags + // are DERIVED by sock. ConfigurePost is done exclusively on sock. + int stat = ConfigurePost(m_sock); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "ConfigurePost"); +} + +void SrtCommon::Init(string host, int port, map par, bool dir_output) +{ + m_output_direction = dir_output; + InitParameters(host, par); + + if ( transmit_verbose ) + cout << "Opening SRT " << (dir_output ? "target" : "source") << " " << m_mode + << "(" << (m_blocking_mode ? "" : "non-") << "blocking)" + << " on " << host << ":" << port << endl; + + if ( m_mode == "caller" ) + OpenClient(host, port); + else if ( m_mode == "listener" ) + OpenServer(m_adapter, port); + else if ( m_mode == "rendezvous" ) + OpenRendezvous(m_adapter, host, port); + else + { + throw std::invalid_argument("Invalid 'mode'. Use 'client' or 'server'"); + } +} + +int SrtCommon::AddPoller(SRTSOCKET socket, int modes) +{ + int pollid = srt_epoll_create(); + if ( pollid == -1 ) + throw std::runtime_error("Can't create epoll in nonblocking mode"); + srt_epoll_add_usock(pollid, socket, &modes); + return pollid; +} + +int SrtCommon::ConfigurePost(SRTSOCKET sock) +{ + bool yes = m_blocking_mode; + int result = 0; + if ( m_output_direction ) + { + result = srt_setsockopt(sock, 0, SRTO_SNDSYN, &yes, sizeof yes); + if ( result == -1 ) + return result; + + if ( m_timeout ) + return srt_setsockopt(sock, 0, SRTO_SNDTIMEO, &m_timeout, sizeof m_timeout); + } + else + { + result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &yes, sizeof yes); + if ( result == -1 ) + return result; + + if ( m_timeout ) + return srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &m_timeout, sizeof m_timeout); + } + + SrtConfigurePost(sock, m_options); + + for (auto o: srt_options) + { + if ( o.binding == SocketOption::POST && m_options.count(o.name) ) + { + string value = m_options.at(o.name); + bool ok = o.apply(sock, value); + if ( transmit_verbose ) + { + if ( !ok ) + cout << "WARNING: failed to set '" << o.name << "' (post, " << (m_output_direction? "target":"source") << ") to " << value << endl; + else + cout << "NOTE: SRT/post::" << o.name << "=" << value << endl; + } + } + } + + return 0; +} + +int SrtCommon::ConfigurePre(SRTSOCKET sock) +{ + int result = 0; + + int no = 0; + if ( !m_tsbpdmode ) + { + result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &no, sizeof no); + if ( result == -1 ) + return result; + } + + // Let's pretend async mode is set this way. + // This is for asynchronous connect. + int maybe = m_blocking_mode; + result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &maybe, sizeof maybe); + if ( result == -1 ) + return result; + + //if ( m_timeout ) + // result = srt_setsockopt(sock, 0, SRTO_RCVTIMEO, &m_timeout, sizeof m_timeout); + //if ( result == -1 ) + // return result; + + //if ( transmit_verbose ) + //{ + // cout << "PRE: blocking mode set: " << yes << " timeout " << m_timeout << endl; + //} + + // host is only checked for emptiness and depending on that the connection mode is selected. + // Here we are not exactly interested with that information. + vector failures; + SocketOption::Mode conmode = SrtConfigurePre(sock, "", m_options, &failures); + + if ( transmit_verbose && conmode == SocketOption::FAILURE ) + { + cout << "WARNING: failed to set options: "; + copy(failures.begin(), failures.end(), ostream_iterator(cout, ", ")); + cout << endl; + } + + return 0; +} + +void SrtCommon::SetupAdapter(const string& host, int port) +{ + sockaddr_in localsa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&localsa; + int stat = srt_bind(m_sock, psa, sizeof localsa); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "srt_bind"); +} + +void SrtCommon::OpenClient(string host, int port) +{ + PrepareClient(); + + if ( m_outgoing_port ) + { + SetupAdapter("", m_outgoing_port); + } + + ConnectClient(host, port); +} + +void SrtCommon::PrepareClient() +{ + m_sock = srt_socket(AF_INET, SOCK_DGRAM, 0); + if ( m_sock == SRT_ERROR ) + Error(UDT::getlasterror(), "srt_socket"); + + int stat = ConfigurePre(m_sock); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "ConfigurePre"); + + if ( !m_blocking_mode ) + { + srt_conn_epoll = AddPoller(m_sock, SRT_EPOLL_OUT); + } + +} + +/* + This may be used sometimes for testing, but it's nonportable. +void SrtCommon::SpinWaitAsync() +{ + static string udt_status_names [] = { + "INIT" , "OPENED", "LISTENING", "CONNECTING", "CONNECTED", "BROKEN", "CLOSING", "CLOSED", "NONEXIST" + }; + + for (;;) + { + SRT_SOCKSTATUS state = srt_getsockstate(m_sock); + if ( int(state) < SRTS_CONNECTED ) + { + if ( transmit_verbose ) + cout << state << flush; + usleep(250000); + continue; + } + else if ( int(state) > SRTS_CONNECTED ) + { + Error(UDT::getlasterror(), "UDT::connect status=" + udt_status_names[state]); + } + + return; + } +} +*/ + +void SrtCommon::ConnectClient(string host, int port) +{ + + sockaddr_in sa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&sa; + if ( transmit_verbose ) + { + cout << "Connecting to " << host << ":" << port << " ... "; + cout.flush(); + } + int stat = srt_connect(m_sock, psa, sizeof sa); + if ( stat == SRT_ERROR ) + { + srt_close(m_sock); + Error(UDT::getlasterror(), "UDT::connect"); + } + + // Wait for REAL connected state if nonblocking mode + if ( !m_blocking_mode ) + { + if ( transmit_verbose ) + cout << "[ASYNC] " << flush; + + // SPIN-WAITING version. Don't use it unless you know what you're doing. + // SpinWaitAsync(); + + // Socket readiness for connection is checked by polling on WRITE allowed sockets. + int len = 2; + SRTSOCKET ready[2]; + if ( srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) != -1 ) + { + if ( transmit_verbose ) + { + cout << "[EPOLL: " << len << " sockets] " << flush; + } + } + else + { + Error(UDT::getlasterror(), "srt_epoll_wait"); + } + } + + if ( transmit_verbose ) + cout << " connected.\n"; + stat = ConfigurePost(m_sock); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "ConfigurePost"); +} + +void SrtCommon::Error(UDT::ERRORINFO& udtError, string src) +{ + int udtResult = udtError.getErrorCode(); + string message = udtError.getErrorMessage(); + if ( transmit_verbose ) + cout << "FAILURE\n" << src << ": [" << udtResult << "] " << message << endl; + else + cerr << "\nERROR #" << udtResult << ": " << message << endl; + + udtError.clear(); + throw std::runtime_error("error in " + src + ": " + message); +} + +void SrtCommon::OpenRendezvous(string adapter, string host, int port) +{ + m_sock = srt_socket(AF_INET, SOCK_DGRAM, 0); + if ( m_sock == SRT_ERROR ) + Error(UDT::getlasterror(), "srt_socket"); + + bool yes = true; + srt_setsockopt(m_sock, 0, SRTO_RENDEZVOUS, &yes, sizeof yes); + + int stat = ConfigurePre(m_sock); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "ConfigurePre"); + + if ( !m_blocking_mode ) + { + srt_conn_epoll = AddPoller(m_sock, SRT_EPOLL_OUT); + } + + sockaddr_in localsa = CreateAddrInet(adapter, port); + sockaddr* plsa = (sockaddr*)&localsa; + if ( transmit_verbose ) + { + cout << "Binding a server on " << adapter << ":" << port << " ..."; + cout.flush(); + } + stat = srt_bind(m_sock, plsa, sizeof localsa); + if ( stat == SRT_ERROR ) + { + srt_close(m_sock); + Error(UDT::getlasterror(), "srt_bind"); + } + + sockaddr_in sa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&sa; + if ( transmit_verbose ) + { + cout << "Connecting to " << host << ":" << port << " ... "; + cout.flush(); + } + stat = srt_connect(m_sock, psa, sizeof sa); + if ( stat == SRT_ERROR ) + { + srt_close(m_sock); + Error(UDT::getlasterror(), "srt_connect"); + } + + // Wait for REAL connected state if nonblocking mode + if ( !m_blocking_mode ) + { + if ( transmit_verbose ) + cout << "[ASYNC] " << flush; + + // SPIN-WAITING version. Don't use it unless you know what you're doing. + // SpinWaitAsync(); + + // Socket readiness for connection is checked by polling on WRITE allowed sockets. + int len = 2; + SRTSOCKET ready[2]; + if ( srt_epoll_wait(srt_conn_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) != -1 ) + { + if ( transmit_verbose ) + { + cout << "[EPOLL: " << len << " sockets] " << flush; + } + } + else + { + Error(UDT::getlasterror(), "srt_epoll_wait"); + } + } + + if ( transmit_verbose ) + cout << " connected.\n"; + + stat = ConfigurePost(m_sock); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "ConfigurePost"); +} + +void SrtCommon::Close() +{ + if ( transmit_verbose ) + cout << "SrtCommon: DESTROYING CONNECTION, closing sockets (rt%" << m_sock << " ls%" << m_bindsock << ")...\n"; + + bool yes = true; + if ( m_sock != UDT::INVALID_SOCK ) + { + srt_setsockflag(m_sock, SRTO_SNDSYN, &yes, sizeof yes); + srt_close(m_sock); + } + + if ( m_bindsock != UDT::INVALID_SOCK ) + { + // Set sndsynchro to the socket to synch-close it. + srt_setsockflag(m_bindsock, SRTO_SNDSYN, &yes, sizeof yes); + srt_close(m_bindsock); + } + if ( transmit_verbose ) + cout << "SrtCommon: ... done.\n"; +} + +SrtCommon::~SrtCommon() +{ + Close(); +} + +SrtSource::SrtSource(string host, int port, const map& par) +{ + Init(host, port, par, false); + + if ( !m_blocking_mode ) + { + srt_epoll = AddPoller(m_sock, SRT_EPOLL_IN); + } + + ostringstream os; + os << host << ":" << port; + hostport_copy = os.str(); +} + +bytevector SrtSource::Read(size_t chunk) +{ + static size_t counter = 1; + + bytevector data(chunk); + bool ready = true; + int stat; + do + { + ::transmit_throw_on_interrupt = true; + stat = srt_recvmsg(m_sock, data.data(), chunk); + ::transmit_throw_on_interrupt = false; + if ( stat == SRT_ERROR ) + { + if ( !m_blocking_mode ) + { + // EAGAIN for SRT READING + if ( srt_getlasterror(NULL) == SRT_EASYNCRCV ) + { + if ( transmit_verbose ) + { + cout << "AGAIN: - waiting for data by epoll...\n"; + } + // Poll on this descriptor until reading is available, indefinitely. + int len = 2; + SRTSOCKET ready[2]; + if ( srt_epoll_wait(srt_epoll, ready, &len, 0, 0, -1, 0, 0, 0, 0) != -1 ) + { + if ( transmit_verbose ) + { + cout << "... epoll reported ready " << len << " sockets\n"; + } + continue; + } + // If was -1, then passthru. + } + } + Error(UDT::getlasterror(), "recvmsg"); + } + + if ( stat == 0 ) + { + throw ReadEOF(hostport_copy); + } + } + while (!ready); + + chunk = size_t(stat); + if ( chunk < data.size() ) + data.resize(chunk); + + CBytePerfMon perf; + srt_bstats(m_sock, &perf, true); + if ( transmit_bw_report && int(counter % transmit_bw_report) == transmit_bw_report - 1 ) + { + cout << "+++/+++SRT BANDWIDTH: " << perf.mbpsBandwidth << endl; + } + + if ( transmit_stats_report && counter % transmit_stats_report == transmit_stats_report - 1) + { + //CPerfMon pmon; + //memset(&pmon, 0, sizeof pmon); + //UDT::perfmon(m_sock, &pmon, false); + //PrintSrtStats(m_sock, pmon); + PrintSrtStats(m_sock, perf); + } + + ++counter; + + return data; +} + +int SrtTarget::ConfigurePre(SRTSOCKET sock) +{ + int result = SrtCommon::ConfigurePre(sock); + if ( result == -1 ) + return result; + + int yes = 1; + // This is for the HSv4 compatibility, if both parties are HSv5 + // (min. version 1.2.1), then this setting simply does nothing. + // In HSv4 this setting is obligatory otherwise the SRT handshake + // extension will not be done at all. + result = srt_setsockopt(sock, 0, SRTO_SENDER, &yes, sizeof yes); + if ( result == -1 ) + return result; + + return 0; +} + +void SrtTarget::Write(const bytevector& data) +{ + ::transmit_throw_on_interrupt = true; + + // Check first if it's ready to write. + // If not, wait indefinitely. + if ( !m_blocking_mode ) + { + int ready[2]; + int len = 2; + if ( srt_epoll_wait(srt_epoll, 0, 0, ready, &len, -1, 0, 0, 0, 0) == SRT_ERROR ) + Error(UDT::getlasterror(), "srt_epoll_wait"); + } + + int stat = srt_sendmsg2(m_sock, data.data(), data.size(), nullptr); + if ( stat == SRT_ERROR ) + Error(UDT::getlasterror(), "srt_sendmsg"); + ::transmit_throw_on_interrupt = false; +} + + +template struct Srt; +template <> struct Srt { typedef SrtSource type; }; +template <> struct Srt { typedef SrtTarget type; }; + +template +Iface* CreateSrt(const string& host, int port, const map& par) { return new typename Srt::type (host, port, par); } + +class ConsoleSource: public Source +{ +public: + + ConsoleSource() + { + } + + bytevector Read(size_t chunk) override + { + bytevector data(chunk); + bool st = cin.read(data.data(), chunk).good(); + chunk = cin.gcount(); + if ( chunk == 0 && !st ) + return bytevector(); + + if ( chunk < data.size() ) + data.resize(chunk); + if ( data.empty() ) + throw ReadEOF("CONSOLE device"); + + return data; + } + + bool IsOpen() override { return cin.good(); } + bool End() override { return cin.eof(); } +}; + +class ConsoleTarget: public Target +{ +public: + + ConsoleTarget() + { + } + + void Write(const bytevector& data) override + { + cout.write(data.data(), data.size()); + } + + bool IsOpen() override { return cout.good(); } + bool Broken() override { return cout.eof(); } +}; + +template struct Console; +template <> struct Console { typedef ConsoleSource type; }; +template <> struct Console { typedef ConsoleTarget type; }; + +template +Iface* CreateConsole() { return new typename Console::type (); } + + +// More options can be added in future. +SocketOption udp_options [] { + { "ipttl", IPPROTO_IP, IP_TTL, SocketOption::INT, SocketOption::PRE }, + { "iptos", IPPROTO_IP, IP_TOS, SocketOption::INT, SocketOption::PRE }, +}; + +class UdpCommon +{ +protected: + int m_sock = -1; + sockaddr_in sadr; + string adapter; + map m_options; + + void Setup(string host, int port, map attr) + { + m_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (m_sock == -1) + { + perror("UdpCommon:socket"); + throw std::runtime_error("UdpCommon: failed to create a socket"); + } + sadr = CreateAddrInet(host, port); + + if ( attr.count("multicast") ) + { + adapter = attr.count("adapter") ? attr.at("adapter") : string(); + sockaddr_in maddr; + if ( adapter == "" ) + { + maddr.sin_addr.s_addr = htonl(INADDR_ANY); + } + else + { + maddr = CreateAddrInet(adapter, port); + } + + ip_mreq mreq; + mreq.imr_multiaddr.s_addr = sadr.sin_addr.s_addr; + mreq.imr_interface.s_addr = maddr.sin_addr.s_addr; +#ifdef WIN32 + const char* mreq_arg = (const char*)&mreq; + const auto status_error = SOCKET_ERROR; +#else + const void* mreq_arg = &mreq; + const auto status_error = -1; +#endif + int res = setsockopt(m_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq_arg, sizeof(mreq)); + + if ( res == status_error ) + { + throw runtime_error("adding to multicast membership failed"); + } + attr.erase("multicast"); + attr.erase("adapter"); + } + + m_options = attr; + + for (auto o: udp_options) + { + // Ignore "binding" - for UDP there are no post options. + if ( m_options.count(o.name) ) + { + string value = m_options.at(o.name); + bool ok = o.apply(m_sock, value); + if ( transmit_verbose && !ok ) + cout << "WARNING: failed to set '" << o.name << "' to " << value << endl; + } + } + } + + ~UdpCommon() + { +#ifdef WIN32 + if (m_sock != -1) + { + shutdown(m_sock, SD_BOTH); + closesocket(m_sock); + m_sock = -1; + } +#else + close(m_sock); +#endif + } +}; + + +class UdpSource: public Source, public UdpCommon +{ + bool eof = true; +public: + + UdpSource(string host, int port, const map& attr) + { + Setup(host, port, attr); + int stat = ::bind(m_sock, (sockaddr*)&sadr, sizeof sadr); + if ( stat == -1 ) + { + perror("bind"); + throw runtime_error("bind failed, UDP cannot read"); + } + eof = false; + } + + bytevector Read(size_t chunk) override + { + bytevector data(chunk); + sockaddr_in sa; + socklen_t si = sizeof(sockaddr_in); + int stat = recvfrom(m_sock, data.data(), chunk, 0, (sockaddr*)&sa, &si); + if ( stat == -1 || stat == 0 ) + { + eof = true; + return bytevector(); + } + + chunk = size_t(stat); + if ( chunk < data.size() ) + data.resize(chunk); + + return data; + } + + bool IsOpen() override { return m_sock != -1; } + bool End() override { return eof; } +}; + +class UdpTarget: public Target, public UdpCommon +{ +public: + UdpTarget(string host, int port, const map& attr ) + { + Setup(host, port, attr); + } + + void Write(const bytevector& data) override + { + int stat = sendto(m_sock, data.data(), data.size(), 0, (sockaddr*)&sadr, sizeof sadr); + if ( stat == -1 ) + { + perror("UdpTarget: write"); + throw runtime_error("Error during write"); + } + } + + bool IsOpen() override { return m_sock != -1; } + bool Broken() override { return false; } +}; + +template struct Udp; +template <> struct Udp { typedef UdpSource type; }; +template <> struct Udp { typedef UdpTarget type; }; + +template +Iface* CreateUdp(const string& host, int port, const map& par) { return new typename Udp::type (host, port, par); } + +template +inline bool IsOutput() { return false; } + +template<> +inline bool IsOutput() { return true; } + +template +extern unique_ptr CreateMedium(const string& uri) +{ + unique_ptr ptr; + + UriParser u(uri); + + int iport = 0; + switch ( u.type() ) + { + default: ; // do nothing, return nullptr + case UriParser::FILE: + if ( u.host() == "con" || u.host() == "console" ) + { + if ( IsOutput() && ( + (transmit_verbose && transmit_cverb == &cout) + || transmit_bw_report) ) + { + cerr << "ERROR: file://con with -v or -r would result in mixing the data and text info.\n"; + cerr << "ERROR: HINT: you can stream through a FIFO (named pipe)\n"; + throw invalid_argument("incorrect parameter combination"); + } + ptr.reset( CreateConsole() ); + } + else + ptr.reset( CreateFile(u.path())); + break; + + + case UriParser::SRT: + iport = atoi(u.port().c_str()); + if ( iport <= 1024 ) + { + cerr << "Port value invalid: " << iport << " - must be >1024\n"; + throw invalid_argument("Invalid port number"); + } + ptr.reset( CreateSrt(u.host(), iport, u.parameters()) ); + break; + + + case UriParser::UDP: + iport = atoi(u.port().c_str()); + if ( iport <= 1024 ) + { + cerr << "Port value invalid: " << iport << " - must be >1024\n"; + throw invalid_argument("Invalid port number"); + } + ptr.reset( CreateUdp(u.host(), iport, u.parameters()) ); + break; + + } + + ptr->uri = move(u); + return ptr; +} + + +std::unique_ptr Source::Create(const std::string& url) +{ + return CreateMedium(url); +} + +std::unique_ptr Target::Create(const std::string& url) +{ + return CreateMedium(url); +} diff --git a/common/transmitmedia.hpp b/common/transmitmedia.hpp new file mode 100644 index 000000000..5ec27fd1d --- /dev/null +++ b/common/transmitmedia.hpp @@ -0,0 +1,125 @@ +#ifndef INC__COMMON_TRANSMITMEDIA_HPP +#define INC__COMMON_TRANSMITMEDIA_HPP + +#include +#include + +#include "transmitbase.hpp" + +using namespace std; + +class SrtCommon +{ + int srt_conn_epoll = -1; + + void SpinWaitAsync(); + +protected: + + bool m_output_direction = false; //< Defines which of SND or RCV option variant should be used, also to set SRT_SENDER for output + bool m_blocking_mode = true; //< enforces using SRTO_SNDSYN or SRTO_RCVSYN, depending on @a m_output_direction + int m_timeout = 0; //< enforces using SRTO_SNDTIMEO or SRTO_RCVTIMEO, depending on @a m_output_direction + bool m_tsbpdmode = true; + int m_outgoing_port = 0; + string m_mode; + string m_adapter; + map m_options; // All other options, as provided in the URI + SRTSOCKET m_sock = SRT_INVALID_SOCK; + SRTSOCKET m_bindsock = SRT_INVALID_SOCK; + bool IsUsable() { SRT_SOCKSTATUS st = srt_getsockstate(m_sock); return st > SRTS_INIT && st < SRTS_BROKEN; } + bool IsBroken() { return srt_getsockstate(m_sock) > SRTS_CONNECTED; } + +public: + void InitParameters(string host, map par); + void PrepareListener(string host, int port, int backlog); + void StealFrom(SrtCommon& src); + void AcceptNewClient(); + + SRTSOCKET Socket() { return m_sock; } + SRTSOCKET Listener() { return m_bindsock; } + + virtual void Close(); + +protected: + + void Error(UDT::ERRORINFO& udtError, string src); + void Init(string host, int port, map par, bool dir_output); + int AddPoller(SRTSOCKET socket, int modes); + virtual int ConfigurePost(SRTSOCKET sock); + virtual int ConfigurePre(SRTSOCKET sock); + + void OpenClient(string host, int port); + void PrepareClient(); + void SetupAdapter(const std::string& host, int port); + void ConnectClient(string host, int port); + + void OpenServer(string host, int port) + { + PrepareListener(host, port, 1); + AcceptNewClient(); + } + + void OpenRendezvous(string adapter, string host, int port); + + virtual ~SrtCommon(); +}; + + +class SrtSource: public Source, public SrtCommon +{ + int srt_epoll = -1; + std::string hostport_copy; +public: + + SrtSource(std::string host, int port, const std::map& par); + SrtSource() + { + // Do nothing - create just to prepare for use + } + + bytevector Read(size_t chunk) override; + + /* + In this form this isn't needed. + Unblock if any extra settings have to be made. + virtual int ConfigurePre(UDTSOCKET sock) override + { + int result = SrtCommon::ConfigurePre(sock); + if ( result == -1 ) + return result; + return 0; + } + */ + + bool IsOpen() override { return IsUsable(); } + bool End() override { return IsBroken(); } + void Close() override { return SrtCommon::Close(); } +}; + +class SrtTarget: public Target, public SrtCommon +{ + int srt_epoll = -1; +public: + + SrtTarget(std::string host, int port, const std::map& par) + { + Init(host, port, par, true); + + if ( !m_blocking_mode ) + { + srt_epoll = AddPoller(m_sock, SRT_EPOLL_OUT); + } + + } + + SrtTarget() {} + + int ConfigurePre(SRTSOCKET sock) override; + void Write(const bytevector& data) override; + bool IsOpen() override { return IsUsable(); } + bool Broken() override { return IsBroken(); } + void Close() override { return SrtCommon::Close(); } + +}; + +#endif diff --git a/common/uriparser.cpp b/common/uriparser.cpp index 22aeaa86e..52ea18882 100644 --- a/common/uriparser.cpp +++ b/common/uriparser.cpp @@ -49,34 +49,9 @@ struct UriParserInit } g_uriparser_init; -//++ -// UriParser -//-- -#ifdef TEST1 - -static void pf(string name, string value) -{ - if ( name.substr(0,2) == "m_" ) - name = name.substr(2); - cerr << name << ": " << value << endl; -} - -#define PF(field) pf(#field, field) -#endif - UriParser::UriParser(const string& strUrl, DefaultExpect exp) { Parse(strUrl, exp); -#ifdef TEST1 - - cerr << "PARSED URI: " << m_origUri << endl; - PF(m_proto); - PF(m_host); - PF(m_port); - PF(m_path); - - cerr << "SCHEME INDEX: " << int(m_uriType) << endl; -#endif } UriParser::~UriParser(void) @@ -255,14 +230,16 @@ void UriParser::Parse(const string& strUrl, DefaultExpect exp) #ifdef TEST + using namespace std; int main( int argc, char** argv ) { UriParser parser (argv[1]); + (void)argc; cout << "PARSING URL: " << argv[1] << endl; - + cerr << "SCHEME INDEX: " << int(parser.type()) << endl; cout << "PROTOCOL: " << parser.proto() << endl; cout << "HOST: " << parser.host() << endl; cout << "PORT: " << parser.portno() << endl; diff --git a/common/uriparser.hpp b/common/uriparser.hpp index 80206bd3b..88d5ea004 100644 --- a/common/uriparser.hpp +++ b/common/uriparser.hpp @@ -34,28 +34,30 @@ class UriParser public: enum DefaultExpect { EXPECT_FILE, EXPECT_HOST }; + enum Type + { + UNKNOWN, FILE, UDP, TCP, SRT, RTMP, HTTP + }; UriParser(const std::string& strUrl, DefaultExpect exp = EXPECT_FILE); + UriParser(): m_uriType(UNKNOWN) {} virtual ~UriParser(void); // Some predefined types - enum Type - { - UNKNOWN, FILE, UDP, TCP, SRT, RTMP, HTTP - }; Type type(); // Operations public: std::string uri() { return m_origUri; } - std::string proto(void); + std::string proto(); std::string scheme() { return proto(); } - std::string host(void); - std::string port(void); + std::string host(); + std::string port(); unsigned short int portno(); std::string hostport() { return host() + ":" + port(); } - std::string path(void); + std::string path(); std::string queryValue(const std::string& strKey); + std::string operator[](const std::string& key) { return queryValue(key); } const std::map& parameters() { return m_mapQuery; } private: diff --git a/common/win/wintime.h b/common/win/wintime.h index af810b6c0..f2d756532 100644 --- a/common/win/wintime.h +++ b/common/win/wintime.h @@ -4,6 +4,11 @@ #include #include #include + +// XXX Remove haicrypt dependency - this include file +// and HAICRYPT_API modifier below. The gettimeofday function +// should not be exposed as public and compiling srtcore +// and haicrypt into one library file should suffice. #include "haicrypt.h" #ifdef __cplusplus diff --git a/configure b/configure index fe1c2f5b2..9ede91729 100755 --- a/configure +++ b/configure @@ -59,6 +59,31 @@ proc resolve opt { return [list --$opt $var $type $mark] } +# Check if a --disable option has its --enable counterpart. If so, +# then just invert the option. +proc resolve_disablers {} { + set enablers "" + set optkeys_len [llength $::optkeys] + for {set pos 0} {$pos < $optkeys_len} {incr pos} { + set opt [lindex $::optkeys $pos] + if { [string match --disable-* $opt] } { + set inverted enable-[string range $opt 10 end] + if { $inverted in [dict keys $::options] } { + lset ::optkeys $pos --$inverted + set val $::optval($opt) + unset ::optval($opt) + if { $val == "" || ![string is boolean $val] } { + set ::optval(--$inverted) 0 + } else { + set ::optval(--$inverted) [expr {!$val}] + } + + puts "NOTE: $opt changed into --$inverted=$::optval(--$inverted)" + } + } + } +} + foreach {o desc} $options { lassign [resolve $o] optname optvar opttype optmark set opt($optname) [list $optvar $opttype $optmark] @@ -156,6 +181,8 @@ if { $saveopt != "" } { set cmakeopt "" +resolve_disablers + if { [info proc preprocess] != "" } { preprocess } diff --git a/configure-data.tcl b/configure-data.tcl index 89b26ab35..a0148341c 100644 --- a/configure-data.tcl +++ b/configure-data.tcl @@ -34,7 +34,7 @@ set options { - enable-dynamic "compile SRT parts as shared objects (dynamic libraries)" + enable-shared "compile SRT parts as shared objects (dynamic libraries)" disable-c++11 "turn off parts that require C++11 support" enable-debug "turn on debug+nonoptimized build mode" enable-profile "turn on profile instrumentation" diff --git a/haicrypt/filelist.maf b/haicrypt/filelist.maf index 831abe2e4..99f3628dc 100644 --- a/haicrypt/filelist.maf +++ b/haicrypt/filelist.maf @@ -1,7 +1,5 @@ -# This file is currently reserved for future refactoring, when all headers -# are going to be moved here. This is the list of headers considered to be -# attached to the installation package. Once possible, please move the below -# header files from ../include back to this directory. +# HaiCrypt library contents + PUBLIC HEADERS haicrypt.h hcrypt_ctx.h diff --git a/haicrypt/haicrypt.h b/haicrypt/haicrypt.h index cba408d99..30b2954a1 100644 --- a/haicrypt/haicrypt.h +++ b/haicrypt/haicrypt.h @@ -53,11 +53,13 @@ extern "C" { /* * Define (in Makefile) the HaiCrypt ciphers compiled in */ +//#define HAICRYPT_USE_DAVINCI_DSP 1 /* Preferred for makito classic stream encryption */ //#define HAICRYPT_USE_OPENSSL_EVP 1 /* Preferred for most cases */ //#define HAICRYPT_USE_OPENSSL_AES 1 /* Mandatory for key wrapping and prng */ typedef void *HaiCrypt_Cipher; +HAICRYPT_API HaiCrypt_Cipher HaiCryptCipher_Davinci_DSP(void); /* Makito DSP AES */ HAICRYPT_API HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP(void); /* OpenSSL EVP interface (default to EVP_CTR) */ HAICRYPT_API HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CBC(void); /* OpenSSL EVP interface for AES-CBC */ HAICRYPT_API HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CTR(void); /* OpenSSL EVP interface for AES-CTR */ @@ -115,11 +117,20 @@ typedef struct { unsigned int km_pre_announce_pkt; /* Keying Material Pre/Post Announce (pkts) */ }HaiCrypt_Cfg; -typedef void *HaiCrypt_Handle; +typedef enum HaiCrypt_CryptoDir { HAICRYPT_CRYPTO_DIR_RX, HAICRYPT_CRYPTO_DIR_TX } HaiCrypt_CryptoDir; + +//typedef void *HaiCrypt_Handle; +// internally it will be correctly interpreted, +// for the outsider it's just some kinda incomplete type +// but still if you use any kinda pointer instead, you'll get complaints +typedef struct hcrypt_Session* HaiCrypt_Handle; + + HAICRYPT_API int HaiCrypt_SetLogLevel(int level, int logfa); -HAICRYPT_API int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); +HAICRYPT_API int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); +HAICRYPT_API int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handle *phhc); HAICRYPT_API int HaiCrypt_Close(HaiCrypt_Handle hhc); HAICRYPT_API int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_p); HAICRYPT_API int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, @@ -132,6 +143,13 @@ HAICRYPT_API int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], siz HAICRYPT_API int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); HAICRYPT_API int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); +/* Status values */ + +#define HAICRYPT_ERROR -1 +#define HAICRYPT_ERROR_WRONG_SECRET -2 +#define HAICRYPT_OK 0 + + #ifdef __cplusplus } #endif diff --git a/haicrypt/hcrypt.c b/haicrypt/hcrypt.c index c4e3929b2..ca2862890 100644 --- a/haicrypt/hcrypt.c +++ b/haicrypt/hcrypt.c @@ -39,7 +39,16 @@ written by #include "hcrypt.h" -int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) +int HaiCrypt_SetLogLevel(int level, int logfa) +{ + // Oh well. Implement some day. + (void)logfa; + (void)level; + return 0; +} + + +int HaiCrypt_Create(const HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) { hcrypt_Session *crypto; hcrypt_Cipher *cipher; @@ -51,9 +60,9 @@ int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) ASSERT(NULL != cfg); - HCRYPT_LOG_INIT(); - //Test log - HCRYPT_LOG(LOG_INFO, "creating crypto context(flags=0x%x)\n", cfg->flags); + HCRYPT_LOG_INIT(); + //Test log + HCRYPT_LOG(LOG_INFO, "creating crypto context(flags=0x%x)\n", cfg->flags); if (!(HAICRYPT_CFG_F_CRYPTO & cfg->flags)) { HCRYPT_LOG(LOG_INFO, "no supported flags set (0x%x)\n", cfg->flags); @@ -124,6 +133,7 @@ int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) } crypto->cipher = cfg->cipher; + crypto->cfg.data_max_len = cfg->data_max_len; /* Setup transport packet info */ switch (cfg->xport) { @@ -188,6 +198,88 @@ int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) return(0); } +int HaiCrypt_Clone(HaiCrypt_Handle hhcSrc, HaiCrypt_CryptoDir tx, HaiCrypt_Handle *phhc) +{ + hcrypt_Session *cryptoSrc = (hcrypt_Session *)hhcSrc; + hcrypt_Session *cryptoClone; + unsigned char *mem_buf; + size_t mem_siz, inbuf_siz; + + *phhc = NULL; + + ASSERT(NULL != hhcSrc); + + /* + * If cipher has no special input buffer alignment requirement, + * handle it in the crypto session. + */ + inbuf_siz = cryptoSrc->inbuf_siz ; + + /* Allocate crypto session control struct */ + mem_siz = sizeof(hcrypt_Session) // structure + + inbuf_siz; + + cryptoClone = malloc(mem_siz); + if (NULL == cryptoClone){ + HCRYPT_LOG(LOG_ERR, "%s\n", "malloc failed"); + return(-1); + } + mem_buf = (unsigned char *)cryptoClone; + mem_buf += sizeof(*cryptoClone); + memset(cryptoClone, 0, sizeof(*cryptoClone)); + memcpy(cryptoClone, cryptoSrc, sizeof(*cryptoClone)); + + if (inbuf_siz) { + cryptoClone->inbuf = mem_buf; + mem_buf += inbuf_siz; + } + timerclear(&cryptoClone->km.tx_last); + + /* Adjust pointers pointing into cryproSrc after copy + msg_info adn ciphers are extern statics so this is ok*/ + cryptoClone->ctx_pair[0].alt = &cryptoClone->ctx_pair[1]; + cryptoClone->ctx_pair[1].alt = &cryptoClone->ctx_pair[0]; + + /* create a new cipher (OpenSSL) context */ + cryptoClone->cipher_data = cryptoClone->cipher->open(cryptoClone->cfg.data_max_len); + if (NULL == cryptoClone->cipher_data) { + //shred + free(cryptoClone); + return(-1); + } + if (tx) { /* Sender */ + hcrypt_Ctx *ctx = cryptoClone->ctx = &cryptoClone->ctx_pair[0]; + + cryptoClone->ctx_pair[0].flags |= HCRYPT_CTX_F_ENCRYPT; + cryptoClone->ctx_pair[1].flags |= HCRYPT_CTX_F_ENCRYPT; + + /* Set SEK in cipher */ + if (cryptoClone->cipher->setkey(cryptoClone->cipher_data, ctx, ctx->sek, ctx->sek_len)) { + HCRYPT_LOG(LOG_ERR, "cipher setkey(sek[%zd]) failed\n", ctx->sek_len); + return(-1); + } + ctx->status = HCRYPT_CTX_S_ACTIVE; + } else { /* Receiver */ + + /* Configure contexts */ + if (hcryptCtx_Rx_Init(cryptoClone, &cryptoClone->ctx_pair[0], NULL) + || hcryptCtx_Rx_Init(cryptoClone, &cryptoClone->ctx_pair[1], NULL)) { + free(cryptoClone); + return(-1); + } + + /* Clear salt to force later regeneration of KEK as AES decrypting key, + copyed one is encrypting key */ + cryptoClone->ctx_pair[0].flags &= ~HCRYPT_CTX_F_ENCRYPT; + cryptoClone->ctx_pair[1].flags &= ~HCRYPT_CTX_F_ENCRYPT; + memset(cryptoClone->ctx_pair[0].salt, 0, sizeof(cryptoClone->ctx_pair[0].salt)); + cryptoClone->ctx_pair[0].salt_len = 0; + } + + *phhc = (void *)cryptoClone; + return(0); +} + int HaiCrypt_Close(HaiCrypt_Handle hhc) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; diff --git a/haicrypt/hcrypt.h b/haicrypt/hcrypt.h index 28dfbe0cd..093aebe31 100644 --- a/haicrypt/hcrypt.h +++ b/haicrypt/hcrypt.h @@ -84,6 +84,10 @@ typedef struct { int se; /* Stream Encapsulation (HCRYPT_SE_xxx) */ hcrypt_MsgInfo * msg_info; + struct { + size_t data_max_len; + }cfg; + struct { struct timeval tx_period; /* Keying Material tx period (milliseconds) */ struct timeval tx_last; /* Keying Material last tx time */ @@ -201,10 +205,10 @@ int AES_unwrap_key(AES_KEY *key, const unsigned char *iv, unsigned char *out } while(0) -int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Secret *secret); +int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Secret *secret); int hcryptCtx_GenSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx); -int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Cfg *cfg); +int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg); int hcryptCtx_Tx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx); int hcryptCtx_Tx_Refresh(hcrypt_Session *crypto); int hcryptCtx_Tx_PreSwitch(hcrypt_Session *crypto); @@ -214,7 +218,7 @@ int hcryptCtx_Tx_AsmKM(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *a int hcryptCtx_Tx_ManageKM(hcrypt_Session *crypto); int hcryptCtx_Tx_InjectKM(hcrypt_Session *crypto, void *out_p[], size_t out_len_p[], int maxout); -int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Cfg *cfg); +int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg); int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *msg, size_t msg_len); #endif /* HCRYPT_H */ diff --git a/haicrypt/hcrypt_ctx_rx.c b/haicrypt/hcrypt_ctx_rx.c index 8546700a7..29a647082 100644 --- a/haicrypt/hcrypt_ctx_rx.c +++ b/haicrypt/hcrypt_ctx_rx.c @@ -30,16 +30,17 @@ written by #include /* memcpy */ #include "hcrypt.h" -int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Cfg *cfg) +int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg) { ctx->mode = HCRYPT_CTX_MODE_AESCTR; ctx->status = HCRYPT_CTX_S_INIT; ctx->msg_info = crypto->msg_info; - if (hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { + if (cfg && hcryptCtx_SetSecret(crypto, ctx, &cfg->secret)) { return(-1); } + ctx->status = HCRYPT_CTX_S_SARDY; return(0); } @@ -69,7 +70,7 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m int do_pbkdf = 0; if (NULL == crypto) { - HCRYPT_LOG(LOG_INFO, "%s", "Invalid params\n"); + HCRYPT_LOG(LOG_ERR, "Rx_ParseKM: invalid params: crypto=%p\n", crypto); return(-1); } @@ -170,7 +171,6 @@ int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t m HCRYPT_LOG(LOG_WARNING, "%s", "unwrap key failed\n"); return(-2); //Report unmatched shared secret } - /* * First SEK in KMmsg is eSEK if both SEK present */ diff --git a/haicrypt/hcrypt_ctx_tx.c b/haicrypt/hcrypt_ctx_tx.c index 879ab3295..d91d20ac8 100644 --- a/haicrypt/hcrypt_ctx_tx.c +++ b/haicrypt/hcrypt_ctx_tx.c @@ -37,7 +37,7 @@ written by #endif #include "hcrypt.h" -int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Cfg *cfg) +int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Cfg *cfg) { ctx->cfg.key_len = cfg->key_len; diff --git a/haicrypt/hcrypt_sa.c b/haicrypt/hcrypt_sa.c index cac58dceb..4ac07dd68 100644 --- a/haicrypt/hcrypt_sa.c +++ b/haicrypt/hcrypt_sa.c @@ -34,7 +34,7 @@ written by #include /* memcpy */ #include "hcrypt.h" -int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Secret *secret) +int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, const HaiCrypt_Secret *secret) { int iret; (void)crypto; diff --git a/haicrypt/hcrypt_tx.c b/haicrypt/hcrypt_tx.c index d42d4cac3..f68f53719 100644 --- a/haicrypt/hcrypt_tx.c +++ b/haicrypt/hcrypt_tx.c @@ -71,14 +71,14 @@ int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_ int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx; + hcrypt_Ctx *ctx = NULL; int nbout = 0; if ((NULL == crypto) - || (NULL == crypto->ctx) + || (NULL == (ctx = crypto->ctx)) || (NULL == out_p) || (NULL == out_len_p)) { - HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + HCRYPT_LOG(LOG_ERR, "ManageKeys: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } @@ -98,11 +98,11 @@ int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[ int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx; + hcrypt_Ctx *ctx = NULL; if ((NULL == crypto) || (NULL == (ctx = crypto->ctx))){ - HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + HCRYPT_LOG(LOG_ERR, "GetKeyFlags: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } return(hcryptCtx_GetKeyFlags(ctx)); @@ -112,12 +112,12 @@ int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *in_pfx, unsigned char *in_data, size_t in_len) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx; + hcrypt_Ctx *ctx = NULL; int nbout = 0; if ((NULL == crypto) || (NULL == (ctx = crypto->ctx))){ - HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + HCRYPT_LOG(LOG_ERR, "Tx_Data: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } /* Get/Set packet index */ @@ -145,14 +145,14 @@ int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout) { hcrypt_Session *crypto = (hcrypt_Session *)hhc; - hcrypt_Ctx *ctx; + hcrypt_Ctx *ctx = NULL; int nb, nbout = 0; if ((NULL == crypto) - || (NULL == crypto->ctx) + || (NULL == (ctx = crypto->ctx)) || (NULL == out_p) || (NULL == out_len_p)) { - HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + HCRYPT_LOG(LOG_ERR, "Tx_Process: invalid params: crypto=%p crypto->ctx=%p\n", crypto, ctx); return(-1); } diff --git a/scripts/check-deps b/scripts/check-deps index f1e6fea9f..d8e3dd940 100755 --- a/scripts/check-deps +++ b/scripts/check-deps @@ -20,31 +20,37 @@ exec tclsh "$0" "$@" || echo "Please install 'tcl' package first - it's required # License along with this library; If not, see # +if { [catch {package require Tcl 8.5}] } { + puts stderr "Tcl version at least 8.5 required, please upgrade" + exit 1 +} + set ok 1 +if { [catch {exec pkg-config --exists openssl}] } { + set ok 0 + puts "Openssl: NOT INSTALLED, please install (libssl-dev\[el\], openssl-dev\[el\] etc.)" +} else { + puts "Openssl: found version [exec pkg-config --modversion openssl] -- ok" +} + + set nothave [catch {set cmake [exec cmake --version]}] if { $nothave } { - puts "CMake version >= 2.6 required - please install cmake" + puts "CMake version >= 2.8 required - please install cmake" set ok 0 } else { set cmakel1 [lindex [split $cmake \n] 0] set cv [lindex $cmakel1 end] - if { [package vcompare $cv 2.6] == -1 } { - puts "CMake version >= 2.6 required - please upgrade cmake" + if { [package vcompare $cv 2.8] == -1 } { + puts "CMake version >= 2.8 required - please upgrade cmake" set ok 0 } else { puts "Cmake version $cv -- ok." } } -set nothave [catch {exec pkg-config --exists libcrypto}] - -if { $nothave } { - puts "Required libcrypto to compile SRT (usually libopenssl-devel package)" - set ok 0 -} - # May others also apply diff --git a/srtcore/ccc.cpp b/srtcore/ATTIC/ccc.cpp similarity index 100% rename from srtcore/ccc.cpp rename to srtcore/ATTIC/ccc.cpp diff --git a/srtcore/ccc.h b/srtcore/ATTIC/ccc.h similarity index 100% rename from srtcore/ccc.h rename to srtcore/ATTIC/ccc.h diff --git a/srtcore/Sources.txt b/srtcore/Sources.txt deleted file mode 100644 index 9aa2cd354..000000000 --- a/srtcore/Sources.txt +++ /dev/null @@ -1,36 +0,0 @@ -api.cpp -buffer.cpp -cache.cpp -ccc.cpp -channel.cpp -common.cpp -core.cpp -csrtcc.cpp -epoll.cpp -list.cpp -md5.cpp -packet.cpp -queue.cpp -srt_c_api.cpp -window.cpp -../common/srt_compat.c -api.h -buffer.h -cache.h -ccc.h -channel.h -common.h -core.h -csrtcc.h -epoll.h -list.h -logging.h -md5.h -netinet_any.h -packet.h -platform_sys.h -queue.h -srt4udt.h -threadname.h -utilities.h -window.h \ No newline at end of file diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 6d0130f1e..e31db26f8 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -74,7 +74,7 @@ modified by using namespace std; -extern logging::LogConfig logger_config; +extern logging::LogConfig srt_logger_config; extern logging::Logger mglog; @@ -160,6 +160,14 @@ m_ClosedSockets() CUDTUnited::~CUDTUnited() { + // Call it if it wasn't called already. + // This will happen at the end of main() of the application, + // when the user didn't call srt_cleanup(). + if (m_bGCStatus) + { + cleanup(); + } + pthread_mutex_destroy(&m_ControlLock); pthread_mutex_destroy(&m_IDLock); pthread_mutex_destroy(&m_InitLock); @@ -205,8 +213,10 @@ int CUDTUnited::startup() pthread_mutex_init(&m_GCStopLock, NULL); pthread_cond_init(&m_GCStopCond, NULL); - ThreadName tn("SRT:GC"); - pthread_create(&m_GCThread, NULL, garbageCollect, this); + { + ThreadName tn("SRT:GC"); + pthread_create(&m_GCThread, NULL, garbageCollect, this); + } m_bGCStatus = true; @@ -305,8 +315,7 @@ UDTSOCKET CUDTUnited::newSocket(int af, int type) return ns->m_SocketID; } -int CUDTUnited::newConnection( - const UDTSOCKET listen, const sockaddr* peer, CHandShake* hs) +int CUDTUnited::newConnection(const UDTSOCKET listen, const sockaddr* peer, CHandShake* hs, const CPacket& hspkt) { CUDTSocket* ns = NULL; CUDTSocket* ls = locate(listen); @@ -376,8 +385,7 @@ int CUDTUnited::newConnection( CGuard::enterCS(m_IDLock); ns->m_SocketID = -- m_SocketIDGenerator; - LOGC(mglog.Debug).form( - "newConnection: generated socket id %d\n", ns->m_SocketID); + LOGC(mglog.Debug).form("newConnection: generated socket id %d\n", ns->m_SocketID); CGuard::leaveCS(m_IDLock); ns->m_ListenSocket = listen; @@ -395,37 +403,38 @@ int CUDTUnited::newConnection( // memory, it will continue to work, but fail to accept connection). try { - // This assignment must happen b4 the call to CUDT::connect() because - // this call causes sending the SRT Handshake through this socket. - // Without this mapping the socket cannot be found and therefore - // the SRT Handshake message would fail. - LOGC(mglog.Debug).form( - "newConnection: incoming %s, mapping socket %d\n", - SockaddrToString(peer).c_str(), ns->m_SocketID); - { - CGuard cg(m_ControlLock); - m_Sockets[ns->m_SocketID] = ns; - } - - // bind to the same addr of listening socket - ns->m_pUDT->open(); - updateMux(ns, ls); - ns->m_pUDT->acceptAndRespond(peer, hs); + // This assignment must happen b4 the call to CUDT::connect() because + // this call causes sending the SRT Handshake through this socket. + // Without this mapping the socket cannot be found and therefore + // the SRT Handshake message would fail. + LOGC(mglog.Debug).form( + "newConnection: incoming %s, mapping socket %d\n", + SockaddrToString(peer).c_str(), ns->m_SocketID); + { + CGuard cg(m_ControlLock); + m_Sockets[ns->m_SocketID] = ns; + } + + // bind to the same addr of listening socket + ns->m_pUDT->open(); + updateListenerMux(ns, ls); + ns->m_pUDT->acceptAndRespond(peer, hs, hspkt); } catch (...) { - // The mapped socket should be now unmapped to preserve the situation that - // was in the original UDT code. - // Note that it's for 99.99% unlikely that this code will be ever executed - // (i.e. the lacking memory caused exception and anything still works) - { - CGuard cg(m_ControlLock); - m_Sockets.erase(ns->m_SocketID); - } - error = 1; - LOGC(mglog.Debug).form( - "newConnection: error while accepting, connection rejected"); - goto ERR_ROLLBACK; + // The mapped socket should be now unmapped to preserve the situation that + // was in the original UDT code. + // In SRT additionally the acceptAndRespond() function (it was called probably + // connect() in UDT code) may fail, in which case this socket should not be + // further processed and should be removed. + { + CGuard cg(m_ControlLock); + m_Sockets.erase(ns->m_SocketID); + } + error = 1; + LOGC(mglog.Debug).form( + "newConnection: error while accepting, connection rejected"); + goto ERR_ROLLBACK; } ns->m_Status = UDT_CONNECTED; @@ -438,10 +447,10 @@ int CUDTUnited::newConnection( CGuard::enterCS(m_ControlLock); try { - LOGC(mglog.Debug).form( - "newConnection: mapping peer %d to that socket (%d)\n", - ns->m_PeerID, ns->m_SocketID); - m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID); + LOGC(mglog.Debug).form( + "newConnection: mapping peer %d to that socket (%d)\n", + ns->m_PeerID, ns->m_SocketID); + m_PeerRec[ns->getPeerSpec()].insert(ns->m_SocketID); } catch (...) { @@ -660,8 +669,7 @@ int CUDTUnited::listen(const UDTSOCKET u, int backlog) return 0; } -UDTSOCKET CUDTUnited::accept( - const UDTSOCKET listen, sockaddr* addr, int* addrlen) +UDTSOCKET CUDTUnited::accept(const UDTSOCKET listen, sockaddr* addr, int* addrlen) { if ((addr) && (!addrlen)) throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); @@ -685,48 +693,46 @@ UDTSOCKET CUDTUnited::accept( // !!only one conection can be set up each time!! while (!accepted) { - CGuard cg(ls->m_AcceptLock); - - if ((UDT_LISTENING != ls->m_Status) || ls->m_pUDT->m_bBroken) - { - // This socket has been closed. - accepted = true; - } - else if (ls->m_pQueuedSockets->size() > 0) - { - // XXX Actually this should at best be something like that: - // set::iterator b = ls->m_pQueuedSockets->begin(); - // u = *b; - // ls->m_pQueuedSockets->erase(b); - // ls->m_pAcceptSockets.insert(u); - // It is also questionable why m_pQueuedSockets should be oftype 'set'. - // There's no quick-searching capabilities of that container used - // anywhere except checkBrokenSockets and garbageCollect, which aren't - // performance-critical, - // whereas it's mainly used for getting the first element and iterating - // over elements, which is slow in case of std::set. It's also doubtful - // as to whether the sorting capability of std::set is properly used; - // the first is taken here, which is actually the socket with lowest - // possible descriptor value (as default operator< and ascending - // sorting used for std::set where UDTSOCKET=int). - - u = *(ls->m_pQueuedSockets->begin()); - // why suggest the position - it is std::set! - ls->m_pAcceptSockets->insert(ls->m_pAcceptSockets->end(), u); - ls->m_pQueuedSockets->erase(ls->m_pQueuedSockets->begin()); - accepted = true; - } - else if (!ls->m_pUDT->m_bSynRecving) - { - accepted = true; - } - - if (!accepted && (UDT_LISTENING == ls->m_Status)) - pthread_cond_wait(&(ls->m_AcceptCond), &(ls->m_AcceptLock)); - - if (ls->m_pQueuedSockets->empty()) - m_EPoll.update_events( - listen, ls->m_pUDT->m_sPollID, UDT_EPOLL_IN, false); + CGuard cg(ls->m_AcceptLock); + + if ((UDT_LISTENING != ls->m_Status) || ls->m_pUDT->m_bBroken) + { + // This socket has been closed. + accepted = true; + } + else if (ls->m_pQueuedSockets->size() > 0) + { + // XXX Actually this should at best be something like that: + // set::iterator b = ls->m_pQueuedSockets->begin(); + // u = *b; + // ls->m_pQueuedSockets->erase(b); + // ls->m_pAcceptSockets.insert(u); + // It is also questionable why m_pQueuedSockets should be of type 'set'. + // There's no quick-searching capabilities of that container used anywhere except + // checkBrokenSockets and garbageCollect, which aren't performance-critical, + // whereas it's mainly used for getting the first element and iterating + // over elements, which is slow in case of std::set. It's also doubtful + // as to whether the sorting capability of std::set is properly used; + // the first is taken here, which is actually the socket with lowest + // possible descriptor value (as default operator< and ascending sorting + // used for std::set where UDTSOCKET=int). + + u = *(ls->m_pQueuedSockets->begin()); + // why suggest the position - it is std::set! + ls->m_pAcceptSockets->insert(ls->m_pAcceptSockets->end(), u); + ls->m_pQueuedSockets->erase(ls->m_pQueuedSockets->begin()); + accepted = true; + } + else if (!ls->m_pUDT->m_bSynRecving) + { + accepted = true; + } + + if (!accepted && (UDT_LISTENING == ls->m_Status)) + pthread_cond_wait(&(ls->m_AcceptCond), &(ls->m_AcceptLock)); + + if (ls->m_pQueuedSockets->empty()) + m_EPoll.update_events(listen, ls->m_pUDT->m_sPollID, UDT_EPOLL_IN, false); } if (u == CUDT::INVALID_SOCK) @@ -759,8 +765,7 @@ UDTSOCKET CUDTUnited::accept( return u; } -int CUDTUnited::connect( - const UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +int CUDTUnited::connect(const UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) { CUDTSocket* s = locate(u); if (!s) @@ -768,12 +773,8 @@ int CUDTUnited::connect( CGuard cg(s->m_ControlLock); - // check the size of SOCKADDR structure - // XXX Smart boy. Check the parameter... then ignore it completely. - // Seriously: why does this function receive parameters by name/namelen, - // when the sockaddr::sa_family value expected for that thing is already - // fixed (that is, it's implicitly expected that - // s->m_iIPversion == name->sa_family)? + // XXX Consider translating this to using sockaddr_any, + // this should take out all the "IP version check" things. if (AF_INET == s->m_iIPversion) { if (namelen != sizeof(sockaddr_in)) @@ -791,7 +792,8 @@ int CUDTUnited::connect( if (!s->m_pUDT->m_bRendezvous) { s->m_pUDT->open(); - updateMux(s); // <<---- updateMux -> C(Snd|Rcv)Queue::init + updateMux(s); // <<---- updateMux + // -> C(Snd|Rcv)Queue::init // -> pthread_create(...C(Snd|Rcv)Queue::worker...) s->m_Status = UDT_OPENED; } @@ -812,27 +814,15 @@ int CUDTUnited::connect( * rendez-vous mode. Holding the s->m_ControlLock prevent close * from cancelling the connect */ - // The same thing is done USING InvertedLock! - //// if (s->m_pUDT->m_bSynRecving) - //// CGuard::leaveCS(s->m_ControlLock); - try { - // These above unlock-lock have been commented out; the below - // InvertedGuard should do this job. It unlock in the constructor, - // then locks in the destructor, no matter if an exception has fired. - InvertedGuard l_unlocker( - s->m_pUDT->m_bSynRecving ? &s->m_ControlLock : 0); - s->m_pUDT->connect(name, forced_isn); + // InvertedGuard unlocks in the constructor, then locks in the + // destructor, no matter if an exception has fired. + InvertedGuard l_unlocker( s->m_pUDT->m_bSynRecving ? &s->m_ControlLock : 0 ); + s->m_pUDT->startConnect(name, forced_isn); } - catch (CUDTException& e) + catch (CUDTException& e) // Interceptor, just to change the state. { - // Fixes ORT-119. - // The same thing is done USING InvertedLock! - //// if (s->m_pUDT->m_bSynRecving) - //// { - //// CGuard::enterCS(s->m_ControlLock); - //// } s->m_Status = UDT_OPENED; throw e; } @@ -867,8 +857,8 @@ void CUDTUnited::connect_complete(const UDTSOCKET u) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); // copy address information of local node - // the local port must be correctly assigned BEFORE CUDT::connect(), - // otherwise if connect() fails, the multiplexer cannot be located + // 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); @@ -882,8 +872,15 @@ int CUDTUnited::close(const UDTSOCKET u) if (!s) throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + LOGC(mglog.Debug) << s->m_pUDT->CONID() << " CLOSE. Acquiring control lock"; + CGuard socket_cg(s->m_ControlLock); + LOGC(mglog.Debug) << s->m_pUDT->CONID() << " CLOSING (removing from listening, closing CUDT)"; + + bool synch_close = s->m_pUDT->m_bSynSending; + int id = s->m_SocketID; + if (s->m_Status == UDT_LISTENING) { if (s->m_pUDT->m_bBroken) @@ -901,6 +898,7 @@ int CUDTUnited::close(const UDTSOCKET u) // But there's no reason to destroy the world by occupying the // listener slot in the RcvQueue. + LOGC(mglog.Debug) << s->m_pUDT->CONID() << " CLOSING (removing listener immediately)"; { CGuard cg(s->m_pUDT->m_ConnectionLock); s->m_pUDT->m_bListening = false; @@ -912,33 +910,86 @@ int CUDTUnited::close(const UDTSOCKET u) pthread_cond_broadcast(&(s->m_AcceptCond)); pthread_mutex_unlock(&(s->m_AcceptLock)); - return 0; } - - s->m_pUDT->close(); - - // synchronize with garbage collection. - CGuard manager_cg(m_ControlLock); - - // since "s" is located before m_ControlLock, locate it again in case - // it became invalid - map::iterator i = m_Sockets.find(u); - if ((i == m_Sockets.end()) || (i->second->m_Status == UDT_CLOSED)) - return 0; - s = i->second; - - s->m_Status = UDT_CLOSED; - - // a socket will not be immediated removed when it is closed - // in order to prevent other methods from accessing invalid address - // a timer is started and the socket will be removed after approximately - // 1 second - s->m_TimeStamp = CTimer::getTime(); - - m_Sockets.erase(s->m_SocketID); - m_ClosedSockets.insert(pair(s->m_SocketID, s)); - - CTimer::triggerEvent(); + else + { + s->m_pUDT->close(); + + // synchronize with garbage collection. + LOGC(mglog.Debug) << "%" << id << " CUDT::close done. GLOBAL CLOSE: " << s->m_pUDT->CONID() << ". Acquiring GLOBAL control lock"; + CGuard manager_cg(m_ControlLock); + + // since "s" is located before m_ControlLock, locate it again in case + // it became invalid + map::iterator i = m_Sockets.find(u); + if ((i == m_Sockets.end()) || (i->second->m_Status == UDT_CLOSED)) + return 0; + s = i->second; + + s->m_Status = UDT_CLOSED; + + // a socket will not be immediated removed when it is closed + // in order to prevent other methods from accessing invalid address + // a timer is started and the socket will be removed after approximately + // 1 second + s->m_TimeStamp = CTimer::getTime(); + + m_Sockets.erase(s->m_SocketID); + m_ClosedSockets.insert(pair(s->m_SocketID, s)); + + CTimer::triggerEvent(); + } + + LOGC(mglog.Debug) << "%" << id << ": GLOBAL: CLOSING DONE"; + + // Check if the ID is still in closed sockets before you access it + // (the last triggerEvent could have deleted it). + if ( synch_close ) + { +#if SRT_ENABLE_CLOSE_SYNCH + + // Ok, now you are keeping GC thread hands off the internal data. + // You can check then if it has already deleted the socket or not. + // The socket is either in m_ClosedSockets or is already gone. + LOGC(mglog.Debug) << "%" << id << " GLOBAL CLOSING: sync-waiting for releasing socket resources..."; + for (;;) + { + // Done the other way, but still done. You can stop waiting. + if ( m_ClosedSockets.count(id) == 0 || !s->m_pUDT->m_bOpened ) + { + LOGC(mglog.Debug) << "%" << id << " GLOBAL CLOSING: ... gone in the meantime, whatever. Exiting close()."; + break; + } + + // If so, we can then take out the control lock and let GC do its job. + // It means that the thing to be done by CUDT::close() is not yet complete, + // so we have to wait. + + // This below will unlock the control lock and wait for the signal. + + // Do a 0.20s rolling, just for safety, when we missed the signal. + timeval now; + timespec timeout; + gettimeofday(&now, 0); + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = (now.tv_usec + 200000) * 1000; + + int st = pthread_cond_timedwait(&s->m_pUDT->m_CloseSynchCond, &m_ControlLock, &timeout); + + // In case of whatever error, jump back to checking the condition. + // One of the errors that MIGHT happen is that the condition object + // has been deleted while waiting. This will cause this function to return + // error code. But if this happened, then surely it was deleted together with + // the whole object and so the socket has been removed from m_ClosedSockets. + // This way, the entry condition in this loop will be false and the loop breaks. + if ( st == 0 ) + { + LOGC(mglog.Debug) << "GLOBAL CLOSING: ... synch-closed. Exiting close()."; + break; + } + } +#endif + } return 0; } @@ -1061,12 +1112,6 @@ int CUDTUnited::select( if ((s->m_pUDT->m_bConnected && s->m_pUDT->m_pRcvBuffer->isRcvDataReady() -// This is unnecessary for TSBPD because isRcvDataReady() and getRcvMsgNum() > 0 -// do exactly the same thing under the hood. -#if !defined(SRT_ENABLE_TSBPD) - && ((s->m_pUDT->m_iSockType == UDT_STREAM) - || (s->m_pUDT->m_pRcvBuffer->getRcvMsgNum() > 0)) -#endif ) || (!s->m_pUDT->m_bListening && (s->m_pUDT->m_bBroken || !s->m_pUDT->m_bConnected)) @@ -1164,12 +1209,6 @@ int CUDTUnited::selectEx( { if ((s->m_pUDT->m_bConnected && s->m_pUDT->m_pRcvBuffer->isRcvDataReady() -// This is unnecessary for TSBPD because isRcvDataReady() and getRcvMsgNum() > 0 -// do exactly the same thing under the hood. -#if !defined(SRT_ENABLE_TSBPD) - && ((s->m_pUDT->m_iSockType == UDT_STREAM) - || (s->m_pUDT->m_pRcvBuffer->getRcvMsgNum() > 0)) -#endif ) || (s->m_pUDT->m_bListening && (s->m_pQueuedSockets->size() > 0))) @@ -1489,7 +1528,9 @@ void CUDTUnited::removeSocket(const UDTSOCKET u) UDT_EPOLL_IN|UDT_EPOLL_OUT|UDT_EPOLL_ERR, false); // delete this one + LOGC(mglog.Debug) << "GC/removeSocket: closing associated UDT %" << u; i->second->m_pUDT->close(); + LOGC(mglog.Debug) << "GC/removeSocket: DELETING SOCKET %" << u; delete i->second; m_ClosedSockets.erase(i); @@ -1497,7 +1538,7 @@ void CUDTUnited::removeSocket(const UDTSOCKET u) m = m_mMultiplexer.find(mid); if (m == m_mMultiplexer.end()) { - //something is wrong!!! + LOGC(mglog.Fatal) << "IPE: For socket %" << u << " MUXER id=" << mid << " NOT FOUND!"; return; } @@ -1506,6 +1547,9 @@ void CUDTUnited::removeSocket(const UDTSOCKET u) // u, m->second.m_iRefCount); if (0 == m->second.m_iRefCount) { + LOGC(mglog.Debug) << "MUXER id=" << mid << " lost last socket %" + << u << " - deleting muxer bound to port " + << m->second.m_pChannel->bindAddressAny().hport(); m->second.m_pChannel->close(); delete m->second.m_pSndQueue; delete m->second.m_pRcvQueue; @@ -1590,7 +1634,7 @@ void CUDTUnited::updateMux( try { if (udpsock) - m.m_pChannel->open(*udpsock); + m.m_pChannel->attach(*udpsock); else m.m_pChannel->open(addr); } @@ -1601,6 +1645,7 @@ void CUDTUnited::updateMux( throw e; } + // XXX Looks stupid. Simplify. Use sockaddr_any. sockaddr* sa = (AF_INET == s->m_pUDT->m_iIPversion) ? (sockaddr*) new sockaddr_in : (sockaddr*) new sockaddr_in6; @@ -1630,10 +1675,50 @@ void CUDTUnited::updateMux( s->m_iMuxID = m.m_iID; LOGC(mglog.Debug).form( - "creating new multiplexer for port %hu\n", m.m_iPort); -} - -void CUDTUnited::updateMux(CUDTSocket* s, const CUDTSocket* ls) + "creating new multiplexer for port %i\n", m.m_iPort); +} + +// XXX This is actually something completely stupid. +// This function is going to find a multiplexer for the port contained +// in the 'ls' listening socket, by searching through the multiplexer +// container. +// +// Somehow, however, it's not even predicted a situation that the multiplexer +// for that port doesn't exist - that is, this function WILL find the +// multiplexer. How can it be so certain? It's because the listener has +// already created the multiplexer during the call to bind(), so if it +// didn't, this function wouldn't even have a chance to be called. +// +// Why can't then the multiplexer be recorded in the 'ls' listening socket data +// to be accessed immediately, especially when one listener can't bind to more than +// one multiplexer at a time (well, even if it could, there's still no reason why +// this should be extracted by "querying")? +// +// Maybe because the multiplexer container is a map, not a list. +// Why is this then a map? Because it's addressed by MuxID. Why do we need +// mux id? Because we don't have a list... ? +// +// But what's the multiplexer ID? It's a socket ID for which it was originally created. +// +// Is this then shared? Yes, only between the listener socket and the accepted sockets, +// or in case of "bound" connecting sockets (by binding you can enforce the port number, +// which can be the same for multiple SRT sockets). +// Not shared in case of unbound connecting socket or rendezvous socket. +// +// Ok, in which situation do we need dispatching by mux id? Only when the socket is being +// deleted. How does the deleting procedure know the muxer id? Because it is recorded here +// at the time when it's found, as... the socket ID of the actual listener socket being +// actually the first socket to create the multiplexer, so the multiplexer gets its id. +// +// Still, no reasons found why the socket can't contain a list iterator to a multiplexer +// INSTEAD of m_iMuxID. There's no danger in this solutio because the multiplexer is never +// deleted until there's at least one socket using it. +// +// The multiplexer may even physically be contained in the CUDTUnited object, just track +// the multiple users of it (the listener and the accepted sockets). When deleting, you +// simply "unsubscribe" yourself from the multiplexer, which will unref it and remove the +// list element by the iterator kept by the socket. +void CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) { CGuard cg(m_ControlLock); @@ -1648,7 +1733,7 @@ void CUDTUnited::updateMux(CUDTSocket* s, const CUDTSocket* ls) if (i->second.m_iPort == port) { LOGC(mglog.Debug).form( - "updateMux: reusing multiplexer for port %hd\n", port); + "updateMux: reusing multiplexer for port %i\n", port); // reuse the existing multiplexer ++ i->second.m_iRefCount; s->m_pUDT->m_pSndQueue = i->second.m_pSndQueue; @@ -1669,21 +1754,21 @@ void* CUDTUnited::garbageCollect(void* p) while (!self->m_bClosing) { - INCREMENT_THREAD_ITERATIONS(); - self->checkBrokenSockets(); + INCREMENT_THREAD_ITERATIONS(); + self->checkBrokenSockets(); -//#ifdef WIN32 -// self->checkTLSValue(); -//#endif + //#ifdef WIN32 + // self->checkTLSValue(); + //#endif - timeval now; - timespec timeout; - gettimeofday(&now, 0); - timeout.tv_sec = now.tv_sec + 1; - timeout.tv_nsec = now.tv_usec * 1000; + timeval now; + timespec timeout; + gettimeofday(&now, 0); + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = now.tv_usec * 1000; - pthread_cond_timedwait( - &self->m_GCStopCond, &self->m_GCStopLock, &timeout); + pthread_cond_timedwait( + &self->m_GCStopCond, &self->m_GCStopLock, &timeout); } // remove all sockets and multiplexers @@ -2504,7 +2589,6 @@ int CUDT::perfmon(UDTSOCKET u, CPerfMon* perf, bool clear) } } -#ifdef SRT_ENABLE_BSTATS int CUDT::bstats(UDTSOCKET u, CBytePerfMon* perf, bool clear) { try @@ -2527,7 +2611,6 @@ int CUDT::bstats(UDTSOCKET u, CBytePerfMon* perf, bool clear) return ERROR; } } -#endif CUDT* CUDT::getUDTHandle(UDTSOCKET u) { @@ -2856,8 +2939,7 @@ int epoll_wait( return CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); } - -#ifdef HAI_PATCH +/* #define SET_RESULT(val, num, fds, it) \ if (val != NULL) \ @@ -2879,36 +2961,21 @@ int epoll_wait( } \ } \ } -#else //>>empty set below do not update num -#define SET_RESULT(val, num, fds, it) \ - if ((val != NULL) && !val->empty()) \ - { \ - if (*num > static_cast(val->size())) \ - *num = val->size(); \ - int count = 0; \ - for (it = val->begin(); it != val->end(); ++ it) \ - { \ - if (count >= *num) \ - break; \ - fds[count ++] = *it; \ - } \ - } -#endif -// Trial version, not yet used :) -static inline void set_result( - set* val, - int* num, - UDTSOCKET* fds, - set::const_iterator it) +*/ + +template +inline void set_result(set* val, int* num, SOCKTYPE* fds) { - if ( !val ) + if ( !val || !num || !fds ) return; if (*num > int(val->size())) - *num = val->size(); + *num = int(val->size()); // will get 0 if val->empty() int count = 0; - for (it = val->begin(); it != val->end(); ++ it) + + // This loop will run 0 times if val->empty() + for (typename set::const_iterator it = val->begin(); it != val->end(); ++ it) { if (count >= *num) break; @@ -2952,12 +3019,17 @@ int epoll_wait2( int ret = CUDT::epoll_wait(eid, rval, wval, msTimeOut, lrval, lwval); if (ret > 0) { - set::const_iterator i; - SET_RESULT(rval, rnum, readfds, i); - SET_RESULT(wval, wnum, writefds, i); - set::const_iterator j; - SET_RESULT(lrval, lrnum, lrfds, j); - SET_RESULT(lwval, lwnum, lwfds, j); + //set::const_iterator i; + //SET_RESULT(rval, rnum, readfds, i); + set_result(rval, rnum, readfds); + //SET_RESULT(wval, wnum, writefds, i); + set_result(wval, wnum, writefds); + + //set::const_iterator j; + //SET_RESULT(lrval, lrnum, lrfds, j); + set_result(lrval, lrnum, lrfds); + //SET_RESULT(lwval, lwnum, lwfds, j); + set_result(lwval, lwnum, lwfds); } return ret; } @@ -3002,12 +3074,10 @@ int perfmon(UDTSOCKET u, TRACEINFO* perf, bool clear) return CUDT::perfmon(u, perf, clear); } -#ifdef SRT_ENABLE_BSTATS int bstats(UDTSOCKET u, TRACEBSTATS* perf, bool clear) { return CUDT::bstats(u, perf, clear); } -#endif UDTSTATUS getsockstate(UDTSOCKET u) { @@ -3016,47 +3086,56 @@ UDTSTATUS getsockstate(UDTSOCKET u) void setloglevel(logging::LogLevel::type ll) { - CGuard gg(logger_config.mutex); - logger_config.max_level = ll; + CGuard gg(srt_logger_config.mutex); + srt_logger_config.max_level = ll; } void addlogfa(logging::LogFA fa) { - CGuard gg(logger_config.mutex); - logger_config.enabled_fa.insert(fa); + CGuard gg(srt_logger_config.mutex); + srt_logger_config.enabled_fa.insert(fa); } void dellogfa(logging::LogFA fa) { - CGuard gg(logger_config.mutex); - logger_config.enabled_fa.erase(fa); + CGuard gg(srt_logger_config.mutex); + srt_logger_config.enabled_fa.erase(fa); } void resetlogfa(set fas) { - CGuard gg(logger_config.mutex); + CGuard gg(srt_logger_config.mutex); set enfas; copy(fas.begin(), fas.end(), std::inserter(enfas, enfas.begin())); - logger_config.enabled_fa = enfas; + srt_logger_config.enabled_fa = enfas; } void setlogstream(std::ostream& stream) { - CGuard gg(logger_config.mutex); - logger_config.log_stream = &stream; + CGuard gg(srt_logger_config.mutex); + srt_logger_config.log_stream = &stream; } void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) { - CGuard gg(logger_config.mutex); - logger_config.loghandler_opaque = opaque; - logger_config.loghandler_fn = handler; + CGuard gg(srt_logger_config.mutex); + srt_logger_config.loghandler_opaque = opaque; + srt_logger_config.loghandler_fn = handler; } void setlogflags(int flags) { - CGuard gg(logger_config.mutex); - logger_config.flags = flags; + CGuard gg(srt_logger_config.mutex); + srt_logger_config.flags = flags; +} + +UDT_API bool setstreamid(UDTSOCKET u, const std::string& sid) +{ + return CUDT::setstreamid(u, sid); +} +UDT_API std::string getstreamid(UDTSOCKET u) +{ + return CUDT::getstreamid(u); } } // namespace UDT diff --git a/srtcore/api.h b/srtcore/api.h index cd6253113..4e09b0ef6 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -73,6 +73,7 @@ modified by #include "queue.h" #include "cache.h" #include "epoll.h" +#include "handshake.h" class CUDT; @@ -162,7 +163,7 @@ friend class CRendezvousQueue; /// @param [in,out] hs handshake information from peer side (in), negotiated value (out); /// @return If the new connection is successfully created: 1 success, 0 already exist, -1 error. - int newConnection(const UDTSOCKET listen, const sockaddr* peer, CHandShake* hs); + int newConnection(const UDTSOCKET listen, const sockaddr* peer, CHandShake* hs, const CPacket& hspkt); /// look up the UDT entity according to its ID. /// @param [in] u the UDT socket ID. @@ -193,10 +194,8 @@ friend class CRendezvousQueue; int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); int epoll_remove_usock(const int eid, const UDTSOCKET u); int epoll_remove_ssock(const int eid, const SYSSOCKET s); -#ifdef HAI_PATCH int epoll_update_usock(const int eid, const UDTSOCKET u, const int* events = NULL); int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); -#endif /* HAI_PATCH */ int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* lwfds = NULL); int epoll_release(const int eid); @@ -232,7 +231,7 @@ friend class CRendezvousQueue; CUDTSocket* locate(const UDTSOCKET u); CUDTSocket* locate(const sockaddr* peer, const UDTSOCKET id, int32_t isn); void updateMux(CUDTSocket* s, const sockaddr* addr = NULL, const UDPSOCKET* = NULL); - void updateMux(CUDTSocket* s, const CUDTSocket* ls); + void updateListenerMux(CUDTSocket* s, const CUDTSocket* ls); private: std::map m_mMultiplexer; // UDP multiplexer @@ -277,8 +276,8 @@ inline std::string SockaddrToString(const sockaddr* sadr) : 0; // (cast to (void*) is required because otherwise the 2-3 arguments // of ?: operator would have different types, which isn't allowed in C++. - if ( !addr ) - return "unknown:0"; + if ( !addr ) + return "unknown:0"; std::ostringstream output; char hostbuf[1024]; diff --git a/srtcore/buffer.cpp b/srtcore/buffer.cpp index 856eee15a..13d1d4b0a 100644 --- a/srtcore/buffer.cpp +++ b/srtcore/buffer.cpp @@ -82,7 +82,6 @@ m_iNextMsgNo(1), m_iSize(size), m_iMSS(mss), m_iCount(0) -#ifdef SRT_ENABLE_BSTATS ,m_iBytesCount(0) ,m_LastOriginTime(0) #ifdef SRT_ENABLE_SNDBUFSZ_MAVG @@ -91,15 +90,12 @@ m_iCount(0) ,m_iBytesCountMAvg(0) ,m_TimespanMAvg(0) #endif -#endif /* SRT_ENABLE_BSTATS */ -#ifdef SRT_ENABLE_INPUTRATE ,m_iInRatePktsCount(0) ,m_iInRateBytesCount(0) ,m_InRateStartTime(0) ,m_InRatePeriod(500000) // 0.5 sec (fast start) ,m_iInRateBps(10000000/8) // 10 Mbps (1.25 MBps) ,m_iAvgPayloadSz(7*188) -#endif /* SRT_ENABLE_INPUTRATE */ { // initial physical buffer of "size" m_pBuffer = new Buffer; @@ -210,18 +206,14 @@ void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order) CGuard::enterCS(m_BufLock); m_iCount += size; -#ifdef SRT_ENABLE_BSTATS m_iBytesCount += len; #ifdef SRT_ENABLE_CBRTIMESTAMP m_LastOriginTime = srctime; #else m_LastOriginTime = time; #endif /* SRT_ENABLE_CBRTIMESTAMP */ -#endif /* SRT_ENABLE_BSTATS */ -#ifdef SRT_ENABLE_INPUTRATE updInputRate(time, size, len); -#endif /* SRT_ENABLE_INRATE */ #ifdef SRT_ENABLE_SNDBUFSZ_MAVG updAvgBufSize(time); @@ -240,7 +232,6 @@ void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order) m_iNextMsgNo = 1; } -#ifdef SRT_ENABLE_INPUTRATE void CSndBuffer::setInputRateSmpPeriod(int period) { m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation @@ -260,7 +251,7 @@ void CSndBuffer::updInputRate(uint64_t time, int pkts, int bytes) //Payload average size m_iAvgPayloadSz = m_iInRateBytesCount / m_iInRatePktsCount; //Required Byte/sec rate (payload + headers) - m_iInRateBytesCount += (m_iInRatePktsCount * SRT_DATA_PKTHDR_SIZE); + m_iInRateBytesCount += (m_iInRatePktsCount * CPacket::SRT_DATA_HDR_SIZE); m_iInRateBps = (int)(((int64_t)m_iInRateBytesCount * 1000000) / (time - m_InRateStartTime)); LOGC(dlog.Debug).form("updInputRate: pkts:%d bytes:%d avg=%d rate=%d kbps interval=%llu\n", @@ -300,7 +291,6 @@ int CSndBuffer::getInputRate(int& payloadsz, int& period) period = (int)m_InRatePeriod; return(m_iInRateBps); } -#endif /* SRT_ENABLE_INPUTRATE */ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) { @@ -346,9 +336,7 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) CGuard::enterCS(m_BufLock); m_iCount += size; -#ifdef SRT_ENABLE_BSTATS m_iBytesCount += total; -#endif CGuard::leaveCS(m_BufLock); @@ -359,29 +347,47 @@ int CSndBuffer::addBufferFromFile(fstream& ifs, int len) return total; } -#if defined(SRT_ENABLE_TSBPD) int CSndBuffer::readData(char** data, int32_t& msgno_bitset, uint64_t& srctime, unsigned kflgs) -#else -int CSndBuffer::readData(char** data, int32_t& msgno_bitset, unsigned kflgs) -#endif { // No data to read if (m_pCurrBlock == m_pLastBlock) return 0; + // Make the packet REFLECT the data stored in the buffer. *data = m_pCurrBlock->m_pcData; int readlen = m_pCurrBlock->m_iLength; + // XXX This is probably done because the encryption should happen + // just once, and so this sets the encryption flags to both msgno bitset + // IN THE PACKET and IN THE BLOCK. This is probably to make the encryption + // happen at the time when scheduling a new packet to send, but the packet + // must remain in the send buffer until it's ACKed. For the case of rexmit + // the packet will be taken "as is" (that is, already encrypted). + // + // The problem is in the order of things: + // 0. When the application stores the data, some of the flags for PH_MSGNO are set. + // 1. The readData() is called to get the original data sent by the application. + // 2. The data are original and must be encrypted. They WILL BE encrypted, later. + // 3. So far we are in readData() so the encryption flags must be updated NOW because + // later we won't have access to the block's data. + // 4. After exiting from readData(), the packet is being encrypted. It's immediately + // sent, however the data must remain in the sending buffer until they are ACKed. + // 5. In case when rexmission is needed, the second overloaded version of readData + // is being called, and the buffer + PH_MSGNO value is extracted. All interesting + // flags must be present and correct at that time. + // + // The only sensible way to fix this problem is to encrypt the packet not after + // extracting from here, but when the packet is stored into CSndBuffer. The appropriate + // flags for PH_MSGNO will be applied directly there. Then here the value for setting + // PH_MSGNO will be set as is. m_pCurrBlock->m_iMsgNoBitset |= MSGNO_ENCKEYSPEC::wrap(kflgs); - msgno_bitset = m_pCurrBlock->m_iMsgNoBitset; -#ifdef SRT_ENABLE_TSBPD + srctime = #ifdef SRT_ENABLE_SRCTIMESTAMP m_pCurrBlock->m_SourceTime ? m_pCurrBlock->m_SourceTime : #endif /* SRT_ENABLE_SRCTIMESTAMP */ m_pCurrBlock->m_OriginTime; -#endif /* SRT_ENABLE_TSBPD */ m_pCurrBlock = m_pCurrBlock->m_pNext; @@ -390,16 +396,14 @@ int CSndBuffer::readData(char** data, int32_t& msgno_bitset, unsigned kflgs) return readlen; } -#ifdef SRT_ENABLE_TSBPD int CSndBuffer::readData(char** data, const int offset, int32_t& msgno_bitset, uint64_t& srctime, int& msglen) -#else /* SRT_ENABLE_TSBPD */ -int CSndBuffer::readData(char** data, const int offset, int32_t& msgno_bitset, int& msglen) -#endif /* SRT_ENABLE_TSBPD */ { CGuard bufferguard(m_BufLock); Block* p = m_pFirstBlock; + // XXX Suboptimal procedure to keep the blocks identifiable + // by sequence number. Consider using some circular buffer. for (int i = 0; i < offset; ++ i) p = p->m_pNext; @@ -440,15 +444,20 @@ int CSndBuffer::readData(char** data, const int offset, int32_t& msgno_bitset, i *data = p->m_pcData; int readlen = p->m_iLength; + + // XXX Here the value predicted to be applied to PH_MSGNO field is extracted. + // As this function is predicted to extract the data to send as a rexmited packet, + // the packet must be in the form ready to send - so, in case of encryption, + // encrypted, and with all ENC flags already set. So, the first call to send + // the packet originally (the other overload of this function) must set these + // flags. msgno_bitset = p->m_iMsgNoBitset; -#ifdef SRT_ENABLE_TSBPD srctime = #ifdef SRT_ENABLE_SRCTIMESTAMP p->m_SourceTime ? p->m_SourceTime : #endif /* SRT_ENABLE_SRCTIMESTAMP */ p->m_OriginTime; -#endif /* SRT_ENABLE_TSBPD */ return readlen; } @@ -457,18 +466,16 @@ void CSndBuffer::ackData(int offset) { CGuard bufferguard(m_BufLock); -#ifdef SRT_ENABLE_BSTATS bool move = false; - for (int i = 0; i < offset; ++ i) { + for (int i = 0; i < offset; ++ i) + { m_iBytesCount -= m_pFirstBlock->m_iLength; - if (m_pFirstBlock == m_pCurrBlock) move = true; + if (m_pFirstBlock == m_pCurrBlock) + move = true; m_pFirstBlock = m_pFirstBlock->m_pNext; } - if (move) m_pCurrBlock = m_pFirstBlock; -#else - for (int i = 0; i < offset; ++ i) - m_pFirstBlock = m_pFirstBlock->m_pNext; -#endif + if (move) + m_pCurrBlock = m_pFirstBlock; m_iCount -= offset; @@ -484,7 +491,6 @@ int CSndBuffer::getCurrBufSize() const return m_iCount; } -#ifdef SRT_ENABLE_BSTATS #ifdef SRT_ENABLE_SNDBUFSZ_MAVG int CSndBuffer::getAvgBufSize(int &bytes, int ×pan) @@ -583,7 +589,6 @@ int CSndBuffer::dropLateData(int &bytes, uint64_t latetime) return(dpkts); } #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_BSTATS */ void CSndBuffer::increase() { @@ -670,14 +675,12 @@ void CSndBuffer::increase() // XXX Init values moved to in-class. -#ifdef SRT_ENABLE_TSBPD //const uint32_t CRcvBuffer::TSBPD_WRAP_PERIOD = (30*1000000); //30 seconds (in usec) //const int CRcvBuffer::TSBPD_DRIFT_MAX_VALUE = 5000; // usec //const int CRcvBuffer::TSBPD_DRIFT_MAX_SAMPLES = 1000; // ACK-ACK packets #ifdef SRT_DEBUG_TSBPD_DRIFT //const int CRcvBuffer::TSBPD_DRIFT_PRT_SAMPLES = 200; // ACK-ACK packets #endif -#endif CRcvBuffer::CRcvBuffer(CUnitQueue* queue, int bufsize): m_pUnit(NULL), @@ -687,14 +690,11 @@ m_iStartPos(0), m_iLastAckPos(0), m_iMaxPos(0), m_iNotch(0) -#ifdef SRT_ENABLE_BSTATS ,m_BytesCountLock() ,m_iBytesCount(0) ,m_iAckedPktsCount(0) ,m_iAckedBytesCount(0) ,m_iAvgPayloadSz(7*188) -#endif -#ifdef SRT_ENABLE_TSBPD ,m_bTsbPdMode(false) ,m_uTsbPdDelay(0) ,m_ullTsbPdTimeBase(0) @@ -708,7 +708,6 @@ m_iNotch(0) ,m_iCountMAvg(0) ,m_iBytesCountMAvg(0) #endif -#endif { m_pUnit = new CUnit* [m_iSize]; for (int i = 0; i < m_iSize; ++ i) @@ -719,9 +718,7 @@ m_iNotch(0) memset(m_TsbPdDriftHisto1ms, 0, sizeof(m_TsbPdDriftHisto1ms)); #endif -#ifdef SRT_ENABLE_BSTATS pthread_mutex_init(&m_BytesCountLock, NULL); -#endif /* SRT_ENABLE_BSTATS */ } CRcvBuffer::~CRcvBuffer() @@ -737,12 +734,9 @@ CRcvBuffer::~CRcvBuffer() delete [] m_pUnit; -#ifdef SRT_ENABLE_BSTATS pthread_mutex_destroy(&m_BytesCountLock); -#endif /* SRT_ENABLE_BSTATS */ } -#ifdef SRT_ENABLE_BSTATS void CRcvBuffer::countBytes(int pkts, int bytes, bool acked) { /* @@ -770,26 +764,18 @@ void CRcvBuffer::countBytes(int pkts, int bytes, bool acked) if (bytes < 0) m_iBytesCount += bytes; /* removed bytes from rcv buffer */ } } -#endif /* SRT_ENABLE_BSTATS */ int CRcvBuffer::addData(CUnit* unit, int offset) { int pos = (m_iLastAckPos + offset) % m_iSize; -#ifdef HAI_PATCH if (offset >= m_iMaxPos) m_iMaxPos = offset + 1; -#else - if (offset > m_iMaxPos) - m_iMaxPos = offset; -#endif if (m_pUnit[pos] != NULL) { return -1; } m_pUnit[pos] = unit; -#ifdef SRT_ENABLE_BSTATS countBytes(1, unit->m_Packet.getLength()); -#endif /* SRT_ENABLE_BSTATS */ unit->m_iFlag = CUnit::GOOD; ++ m_pUnitQueue->m_iCount; @@ -803,18 +789,14 @@ int CRcvBuffer::readBuffer(char* data, int len) int lastack = m_iLastAckPos; int rs = len; -#ifdef SRT_ENABLE_TSBPD uint64_t now = (m_bTsbPdMode ? CTimer::getTime() : 0LL); -#endif /* SRT_ENABLE_TSBPD */ LOGC(mglog.Debug) << CONID() << "readBuffer: start=" << p << " lastack=" << lastack; while ((p != lastack) && (rs > 0)) { -#ifdef SRT_ENABLE_TSBPD LOGC(mglog.Debug) << CONID() << "readBuffer: chk if time2play: NOW=" << now << " PKT TS=" << getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()); if (m_bTsbPdMode && (getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()) > now)) break; /* too early for this unit, return whatever was copied */ -#endif /* SRT_ENABLE_TSBPD */ int unitsize = m_pUnit[p]->m_Packet.getLength() - m_iNotch; if (unitsize > rs) @@ -823,7 +805,7 @@ int CRcvBuffer::readBuffer(char* data, int len) memcpy(data, m_pUnit[p]->m_Packet.m_pcData + m_iNotch, unitsize); data += unitsize; - if ((rs > unitsize) || (rs == m_pUnit[p]->m_Packet.getLength() - m_iNotch)) + if ((rs > unitsize) || (rs == int(m_pUnit[p]->m_Packet.getLength()) - m_iNotch)) { CUnit* tmp = m_pUnit[p]; m_pUnit[p] = NULL; @@ -841,10 +823,8 @@ int CRcvBuffer::readBuffer(char* data, int len) rs -= unitsize; } -#ifdef SRT_ENABLE_BSTATS /* we removed acked bytes form receive buffer */ countBytes(-1, -(len - rs), true); -#endif /* SRT_ENABLE_BSTATS */ m_iStartPos = p; return len - rs; @@ -866,7 +846,7 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) if (ofs.fail()) break; - if ((rs > unitsize) || (rs == m_pUnit[p]->m_Packet.getLength() - m_iNotch)) + if ((rs > unitsize) || (rs == int(m_pUnit[p]->m_Packet.getLength()) - m_iNotch)) { CUnit* tmp = m_pUnit[p]; m_pUnit[p] = NULL; @@ -884,10 +864,8 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) rs -= unitsize; } -#ifdef SRT_ENABLE_BSTATS /* we removed acked bytes form receive buffer */ countBytes(-1, -(len - rs), true); -#endif /* SRT_ENABLE_BSTATS */ m_iStartPos = p; return len - rs; @@ -895,7 +873,6 @@ int CRcvBuffer::readBufferToFile(fstream& ofs, int len) void CRcvBuffer::ackData(int len) { -#ifdef SRT_ENABLE_BSTATS { int pkts = 0; int bytes = 0; @@ -909,7 +886,6 @@ void CRcvBuffer::ackData(int len) } if (pkts > 0) countBytes(pkts, bytes, true); } -#endif m_iLastAckPos = (m_iLastAckPos + len) % m_iSize; m_iMaxPos -= len; if (m_iMaxPos < 0) @@ -918,7 +894,6 @@ void CRcvBuffer::ackData(int len) CTimer::triggerEvent(); } -#ifdef SRT_ENABLE_TSBPD #ifdef SRT_ENABLE_TLPKTDROP void CRcvBuffer::skipData(int len) @@ -995,10 +970,8 @@ bool CRcvBuffer::getRcvFirstMsg(uint64_t& tsbpdtime, bool& passack, int32_t& ski bool CRcvBuffer::getRcvReadyMsg(uint64_t& tsbpdtime, CPacket** pppkt) { tsbpdtime = 0; -#ifdef SRT_ENABLE_BSTATS int rmpkts = 0; int rmbytes = 0; -#endif /* SRT_ENABLE_BSTATS */ for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) { bool freeunit = false; @@ -1024,7 +997,7 @@ bool CRcvBuffer::getRcvReadyMsg(uint64_t& tsbpdtime, CPacket** pppkt) if (tsbpdtime > CTimer::getTime()) return false; - if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != 0) + if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != EK_NOENC) freeunit = true; /* packet not decrypted */ else return true; @@ -1034,10 +1007,8 @@ bool CRcvBuffer::getRcvReadyMsg(uint64_t& tsbpdtime, CPacket** pppkt) { CUnit* tmp = m_pUnit[i]; m_pUnit[i] = NULL; -#ifdef SRT_ENABLE_BSTATS rmpkts++; rmbytes += tmp->m_Packet.getLength(); -#endif /* SRT_ENABLE_BSTATS */ tmp->m_iFlag = CUnit::FREE; --m_pUnitQueue->m_iCount; @@ -1045,10 +1016,8 @@ bool CRcvBuffer::getRcvReadyMsg(uint64_t& tsbpdtime, CPacket** pppkt) m_iStartPos = 0; } } -#ifdef SRT_ENABLE_BSTATS - /* removed skipped, dropped, undecryptable bytes from rcv buffer */ - countBytes(-rmpkts, -rmbytes, true); -#endif + /* removed skipped, dropped, undecryptable bytes from rcv buffer */ + countBytes(-rmpkts, -rmbytes, true); return false; } @@ -1112,20 +1081,6 @@ bool CRcvBuffer::isRcvDataReady() return isRcvDataReady(tsbpdtime); } -#else -bool CRcvBuffer::isRcvDataReady() const -{ - return(getRcvDataSize() > 0); - /* - int p, q; - bool passack; - - return scanMsg(p, q, passack) ? 1 : 0; - */ -} - -#endif /* SRT_ENABLE_TSBPD */ - int CRcvBuffer::getAvailBufSize() const { // One slot must be empty in order to tell the difference between "empty buffer" and "full buffer" @@ -1141,9 +1096,6 @@ int CRcvBuffer::getRcvDataSize() const } -#ifdef SRT_ENABLE_BSTATS -#ifdef SRT_ENABLE_TSBPD - #ifdef SRT_ENABLE_RCVBUFSZ_MAVG /* Return moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ int CRcvBuffer::getRcvAvgDataSize(int &bytes, int ×pan) @@ -1268,15 +1220,11 @@ int CRcvBuffer::getRcvDataSize(int &bytes, int ×pan) return m_iAckedPktsCount; } -#endif /* SRT_ENABLE_TSBPD */ - int CRcvBuffer::getRcvAvgPayloadSize() const { return m_iAvgPayloadSz; } -#endif /* SRT_ENABLE_BSTATS */ - void CRcvBuffer::dropMsg(int32_t msgno, bool using_rexmit_flag) { for (int i = m_iStartPos, n = (m_iLastAckPos + m_iMaxPos) % m_iSize; i != n; i = (i + 1) % m_iSize) @@ -1285,7 +1233,6 @@ void CRcvBuffer::dropMsg(int32_t msgno, bool using_rexmit_flag) m_pUnit[i]->m_iFlag = CUnit::DROPPED; } -#ifdef SRT_ENABLE_TSBPD uint64_t CRcvBuffer::getTsbPdTimeBase(uint32_t timestamp) { /* @@ -1474,19 +1421,13 @@ int CRcvBuffer::readMsg(char* data, int len) return readMsg(data, len, tsbpdtime); } -#endif -#ifdef SRT_ENABLE_TSBPD int CRcvBuffer::readMsg(char* data, int len, uint64_t& tsbpdtime) -#else /* SRT_ENABLE_TSBPD */ -int CRcvBuffer::readMsg(char* data, int len) -#endif { int p, q; bool passack; bool empty = true; -#ifdef SRT_ENABLE_TSBPD if (m_bTsbPdMode) { passack = false; @@ -1510,7 +1451,6 @@ int CRcvBuffer::readMsg(char* data, int len) } } else -#endif { tsbpdtime = 0; if (scanMsg(p, q, passack)) @@ -1533,10 +1473,8 @@ int CRcvBuffer::readMsg(char* data, int len) memcpy(data, m_pUnit[p]->m_Packet.m_pcData, unitsize); data += unitsize; rs -= unitsize; -#ifdef SRT_ENABLE_BSTATS /* we removed bytes form receive buffer */ countBytes(-1, -unitsize, true); -#endif /* SRT_ENABLE_BSTATS */ #if ENABLE_LOGGING @@ -1590,10 +1528,8 @@ bool CRcvBuffer::scanMsg(int& p, int& q, bool& passack) if ((m_iStartPos == m_iLastAckPos) && (m_iMaxPos <= 0)) return false; -#ifdef SRT_ENABLE_BSTATS int rmpkts = 0; int rmbytes = 0; -#endif /* SRT_ENABLE_BSTATS */ //skip all bad msgs at the beginning while (m_iStartPos != m_iLastAckPos) { @@ -1634,20 +1570,16 @@ bool CRcvBuffer::scanMsg(int& p, int& q, bool& passack) CUnit* tmp = m_pUnit[m_iStartPos]; m_pUnit[m_iStartPos] = NULL; -#ifdef SRT_ENABLE_BSTATS rmpkts++; rmbytes += tmp->m_Packet.getLength(); -#endif /* SRT_ENABLE_BSTATS */ tmp->m_iFlag = CUnit::FREE; -- m_pUnitQueue->m_iCount; if (++ m_iStartPos == m_iSize) m_iStartPos = 0; } -#ifdef SRT_ENABLE_BSTATS /* we removed bytes form receive buffer */ countBytes(-rmpkts, -rmbytes, true); -#endif /* SRT_ENABLE_BSTATS */ p = -1; // message head q = m_iStartPos; // message tail @@ -1655,11 +1587,8 @@ bool CRcvBuffer::scanMsg(int& p, int& q, bool& passack) bool found = false; // looking for the first message -#if defined(HAI_PATCH) //>>m_pUnit[size + m_iMaxPos] is not valid + //>>m_pUnit[size + m_iMaxPos] is not valid for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i < n; ++ i) -#else - for (int i = 0, n = m_iMaxPos + getRcvDataSize(); i <= n; ++ i) -#endif { if ((NULL != m_pUnit[q]) && (CUnit::GOOD == m_pUnit[q]->m_iFlag)) { diff --git a/srtcore/buffer.h b/srtcore/buffer.h index 0a0be274d..8fa060871 100644 --- a/srtcore/buffer.h +++ b/srtcore/buffer.h @@ -109,14 +109,9 @@ class CSndBuffer /// @param [in] kflags Odd|Even crypto key flag /// @return Actual length of data read. - #if defined(SRT_ENABLE_TSBPD) int readData(char** data, int32_t& msgno, uint64_t& origintime, unsigned kflgs); - #else //defined(SRT_ENABLE_TSBPD) - int readData(char** data, int32_t& msgno, unsigned kflgs); - #endif -#if defined(SRT_ENABLE_TSBPD) /// Find data position to pack a DATA packet for a retransmission. /// @param [out] data the pointer to the data position. /// @param [in] offset offset from the last ACK point. @@ -127,24 +122,6 @@ class CSndBuffer int readData(char** data, const int offset, int32_t& msgno, uint64_t& origintime, int& msglen); -#else /* SRT_ENABLE_TSBPD */ - /// Find data position to pack a DATA packet from the furthest reading point. - /// @param [out] data the pointer to the data position. - /// @param [out] msgno message number of the packet. - /// @return Actual length of data read. - - int readData(char** data, int32_t& msgno); - - /// Find data position to pack a DATA packet for a retransmission. - /// @param [out] data the pointer to the data position. - /// @param [in] offset offset from the last ACK point. - /// @param [out] msgno message number of the packet. - /// @param [out] msglen length of the message - /// @return Actual length of data read. - - int readData(char** data, const int offset, int32_t& msgno, int& msglen); - -#endif /* SRT_ENABLE_TSBPD */ /// Update the ACK point and may release/unmap/return the user data according to the flag. /// @param [in] offset number of packets acknowledged. @@ -159,19 +136,15 @@ class CSndBuffer int dropLateData(int &bytes, uint64_t latetime); #endif -#ifdef SRT_ENABLE_BSTATS #ifdef SRT_ENABLE_SNDBUFSZ_MAVG void updAvgBufSize(uint64_t time); int getAvgBufSize(int &bytes, int ×pan); #endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ int getCurrBufSize(int &bytes, int ×pan); -#endif /* SRT_ENABLE_BSTATS */ -#ifdef SRT_ENABLE_INPUTRATE int getInputRate(int& payloadtsz, int& period); void updInputRate(uint64_t time, int pkts, int bytes); void setInputRateSmpPeriod(int period); -#endif /* SRT_ENABLE_INPUTRATE */ private: void increase(); @@ -223,7 +196,6 @@ class CSndBuffer int m_iCount; // number of used blocks -#ifdef SRT_ENABLE_BSTATS int m_iBytesCount; // number of payload bytes in queue uint64_t m_LastOriginTime; @@ -233,16 +205,13 @@ class CSndBuffer int m_iBytesCountMAvg; int m_TimespanMAvg; #endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ -#endif /* SRT_ENABLE_BSTATS */ -#ifdef SRT_ENABLE_INPUTRATE int m_iInRatePktsCount; // number of payload bytes added since InRateStartTime int m_iInRateBytesCount; // number of payload bytes added since InRateStartTime uint64_t m_InRateStartTime; uint64_t m_InRatePeriod; // usec int m_iInRateBps; // Input Rate in Bytes/sec int m_iAvgPayloadSz; // Average packet payload size -#endif /* SRT_ENABLE_INPUTRATE */ private: CSndBuffer(const CSndBuffer&); @@ -303,7 +272,6 @@ class CRcvBuffer int getRcvDataSize() const; -#ifdef SRT_ENABLE_BSTATS /// Query how many data was received and acknowledged. /// @param bytes [out] bytes /// @param spantime [out] spantime @@ -330,7 +298,6 @@ class CRcvBuffer /// @return size (bytes) of payload size int getRcvAvgPayloadSize() const; -#endif /* SRT_ENABLE_BSTATS */ /// Mark the message to be dropped from the message list. @@ -346,7 +313,6 @@ class CRcvBuffer int readMsg(char* data, int len); -#ifdef SRT_ENABLE_TSBPD /// read a message. /// @param [out] data buffer to write the message into. /// @param [in] len size of the buffer. @@ -450,29 +416,11 @@ class CRcvBuffer uint64_t getPktTsbPdTime(uint32_t timestamp); private: -#else /* SRT_ENABLE_TSBPD */ - - /// Query how many messages are available now. - /// @return number of messages available for recvmsg. - - int getRcvMsgNum() - { - int p, q; - bool passack; - - return scanMsg(p, q, passack) ? 1 : 0; - } - - bool isRcvDataReady() const; -#endif /* SRT_ENABLE_TSBPD */ - -#ifdef SRT_ENABLE_BSTATS /// thread safe bytes counter /// @param bytes [in] number of bytes added/delete (if negative) to/from rcv buffer. // XXX Please document. void countBytes(int pkts, int bytes, bool acked = false); -#endif /* SRT_ENABLE_BSTATS */ private: bool scanMsg(int& start, int& end, bool& passack); @@ -489,15 +437,12 @@ class CRcvBuffer int m_iNotch; // the starting read point of the first unit -#ifdef SRT_ENABLE_BSTATS pthread_mutex_t m_BytesCountLock; // used to protect counters operations int m_iBytesCount; // Number of payload bytes in the buffer int m_iAckedPktsCount; // Number of acknowledged pkts in the buffer int m_iAckedBytesCount; // Number of acknowledged payload bytes in the buffer int m_iAvgPayloadSz; // Average payload size for dropped bytes estimation -#endif /* SRT_ENABLE_BSTATS */ -#ifdef SRT_ENABLE_TSBPD bool m_bTsbPdMode; // true: apply TimeStamp-Based Rx Mode uint32_t m_uTsbPdDelay; // aggreed delay uint64_t m_ullTsbPdTimeBase; // localtime base for TsbPd mode @@ -538,7 +483,6 @@ class CRcvBuffer #ifdef SRT_DEBUG_TSBPD_OUTJITTER unsigned long m_ulPdHisto[4][10]; #endif /* SRT_DEBUG_TSBPD_OUTJITTER */ -#endif /* SRT_ENABLE_TSBPD */ private: CRcvBuffer(); diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index a15179bc7..1e9235154 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -80,9 +80,11 @@ modified by #include #include // Logging #include +#include #include "channel.h" #include "packet.h" +#include "api.h" // SockaddrToString - possibly move it to somewhere else #include "logging.h" #ifdef WIN32 @@ -121,7 +123,8 @@ m_iIpTTL(-1), m_iIpToS(-1), #endif m_iSndBufSize(65536), -m_iRcvBufSize(65536) +m_iRcvBufSize(65536), +m_BindAddr(version) { m_iSockAddrSize = (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); } @@ -148,6 +151,8 @@ void CChannel::open(const sockaddr* addr) if (0 != ::bind(m_iSocket, addr, namelen)) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + memcpy(&m_BindAddr, addr, namelen); + m_BindAddr.len = namelen; } else { @@ -166,14 +171,18 @@ void CChannel::open(const sockaddr* addr) if (0 != ::bind(m_iSocket, res->ai_addr, res->ai_addrlen)) throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + memcpy(&m_BindAddr, res->ai_addr, res->ai_addrlen); + m_BindAddr.len = res->ai_addrlen; ::freeaddrinfo(res); } + LOGC(mglog.Debug) << "CHANNEL: Bound to local address: " << SockaddrToString(&m_BindAddr); + setUDPSockOpt(); } -void CChannel::open(UDPSOCKET udpsock) +void CChannel::attach(UDPSOCKET udpsock) { m_iSocket = udpsock; setUDPSockOpt(); @@ -307,7 +316,9 @@ void CChannel::getPeerAddr(sockaddr* addr) const int CChannel::sendto(const sockaddr* addr, CPacket& packet) const { + LOGC(mglog.Debug) << "CChannel::sendto: SENDING NOW DST=" << SockaddrToString(addr) << "/ID=" << packet.m_iID; // convert control information into network order + // XXX USE HtoNLA! if (packet.isControl()) for (int i = 0, n = packet.getLength() / 4; i < n; ++ i) *((uint32_t *)packet.m_pcData + i) = htonl(*((uint32_t *)packet.m_pcData + i)); @@ -359,8 +370,10 @@ int CChannel::sendto(const sockaddr* addr, CPacket& packet) const return res; } -int CChannel::recvfrom(sockaddr* addr, CPacket& packet) const +EReadStatus CChannel::recvfrom(sockaddr* addr, CPacket& packet) const { + EReadStatus status = RST_OK; + #ifndef WIN32 msghdr mh; mh.msg_name = addr; @@ -407,24 +420,47 @@ int CChannel::recvfrom(sockaddr* addr, CPacket& packet) const int msg_flags = 0; #endif - // These logs are theoretically errors, but this isn't anything problematic - // for the application, and in certain conditions they can be spit out very - // often and therefore influence the processing time. + // Note that there are exactly four groups of possible errors + // reported by recvmsg(): + + // 1. Temporary error, can't get the data, but you can try again. + // Codes: EAGAIN/EWOULDBLOCK, EINTR + // Return: RST_AGAIN. + // + // 2. Problems that should never happen due to unused configurations. + // Codes: ECONNREFUSED, ENOTCONN + // Return: RST_ERROR, just formally treat this as IPE. + // + // 3. Unexpected runtime errors: + // Codes: EINVAL, EFAULT, ENOMEM, ENOTSOCK + // Return: RST_ERROR. Except ENOMEM, this can only be an IPE. ENOMEM + // should make the program stop as lacking memory will kill the program anyway soon. + // + // 4. Expected socket closed in the meantime by another thread. + // Codes: EBADF + // Return: RST_ERROR. This will simply make the worker thread exit, which is + // expected to happen after CChannel::close() is called by another thread. + if ( res == -1 ) { -#if ENABLE_LOGGING int err = NET_ERROR; - if ( err != EAGAIN ) // For EAGAIN, this isn't an error, just a useless call. + if ( err == EAGAIN || err == EINTR ) // For EAGAIN, this isn't an error, just a useless call. + { + status = RST_AGAIN; + } + else { LOGC(mglog.Debug) << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]"; + status = RST_ERROR; } -#endif + goto Return_error; } // Sanity check for a case when it didn't fill in even the header if ( size_t(res) < CPacket::HDR_SIZE ) { + status = RST_AGAIN; LOGC(mglog.Debug) << CONID() << "POSSIBLE ATTACK: received too short packet with " << res << " bytes"; goto Return_error; } @@ -438,7 +474,7 @@ int CChannel::recvfrom(sockaddr* addr, CPacket& packet) const // In normal conditions, no flags should be set. This shouldn't use any // other flags, but OTOH this situation also theoretically shouldn't happen // and it does. As a safe precaution, simply treat any flag set on the - // messate as "some problem". + // message as "some problem". // // As a response for this situation, fake that you received no package. This will be // then a "fake drop", which will result in reXmission. This isn't even much of a fake @@ -448,12 +484,14 @@ int CChannel::recvfrom(sockaddr* addr, CPacket& packet) const { LOGC(mglog.Debug) << CONID() << "NET ERROR: packet size=" << res << " msg_flags=0x" << hex << msg_flags << ", possibly MSG_TRUNC (0x" << hex << int(MSG_TRUNC) << ")"; + status = RST_AGAIN; goto Return_error; } packet.setLength(res - CPacket::HDR_SIZE); // convert back into local host order + // XXX use NtoHLA(). //for (int i = 0; i < 4; ++ i) // packet.m_nHeader[i] = ntohl(packet.m_nHeader[i]); { @@ -471,9 +509,9 @@ int CChannel::recvfrom(sockaddr* addr, CPacket& packet) const *((uint32_t *)packet.m_pcData + j) = ntohl(*((uint32_t *)packet.m_pcData + j)); } - return packet.getLength(); + return RST_OK; Return_error: packet.setLength(-1); - return -1; + return status; } diff --git a/srtcore/channel.h b/srtcore/channel.h index d0ece37a0..be44421f9 100644 --- a/srtcore/channel.h +++ b/srtcore/channel.h @@ -66,7 +66,7 @@ modified by #include "udt.h" #include "packet.h" - +#include "netinet_any.h" class CChannel { @@ -90,7 +90,7 @@ class CChannel /// Open a UDP channel based on an existing UDP socket. /// @param udpsock [in] UDP socket descriptor. - void open(UDPSOCKET udpsock); + void attach(UDPSOCKET udpsock); /// Disconnect and close the UDP entity. @@ -138,7 +138,7 @@ class CChannel /// @param packet [in] reference to a CPacket entity. /// @return Actual size of data received. - int recvfrom(sockaddr* addr, CPacket& packet) const; + EReadStatus recvfrom(sockaddr* addr, CPacket& packet) const; #ifdef SRT_ENABLE_IPOPTS /// Set the IP TTL. @@ -164,6 +164,9 @@ class CChannel int getIpToS() const; #endif + const sockaddr* bindAddress() { return &m_BindAddr; } + const sockaddr_any& bindAddressAny() { return m_BindAddr; } + private: void setUDPSockOpt(); @@ -178,6 +181,7 @@ class CChannel #endif int m_iSndBufSize; // UDP sending buffer size int m_iRcvBufSize; // UDP receiving buffer size + sockaddr_any m_BindAddr; }; diff --git a/srtcore/common.cpp b/srtcore/common.cpp index f7d4779b7..54d0bbce3 100644 --- a/srtcore/common.cpp +++ b/srtcore/common.cpp @@ -298,17 +298,18 @@ void CTimer::sleep() // // Automatically lock in constructor -CGuard::CGuard(pthread_mutex_t& lock): -m_Mutex(lock), -m_iLocked() +CGuard::CGuard(pthread_mutex_t& lock, bool shouldwork): + m_Mutex(lock), + m_iLocked(-1) { - m_iLocked = pthread_mutex_lock(&m_Mutex); + if (shouldwork) + m_iLocked = pthread_mutex_lock(&m_Mutex); } // Automatically unlock in destructor CGuard::~CGuard() { - if (0 == m_iLocked) + if (m_iLocked == 0) pthread_mutex_unlock(&m_Mutex); } @@ -347,7 +348,7 @@ CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): m_iMajor(major), m_iMinor(minor) { - if (-1 == err) + if (err == -1) #ifndef WIN32 m_iErrno = errno; #else @@ -393,11 +394,7 @@ const char* CUDTException::getErrorMessage() break; case MN_NORES: -#ifdef HAI_PATCH m_strMsg += ": unable to create/configure SRT socket"; -#else /* HAI_PATCH */ - m_strMsg += ": unable to create/configure UDP socket"; -#endif /* HAI_PATCH */ break; case MN_SECURITY: @@ -523,11 +520,7 @@ const char* CUDTException::getErrorMessage() break; case MN_XSIZE: -#ifdef HAI_PATCH m_strMsg += ": Message is too large to send (it must be less than the SRT send buffer size)"; -#else /* HAI_PATCH */ - m_strMsg += ": Message is too large to send (it must be less than the UDT send buffer size)"; -#endif /* HAI_PATCH */ break; case MN_EIDINVAL: @@ -803,11 +796,12 @@ std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) }; static string srt_types [] = { - "", + "none", "hsreq", "hsrsp", "kmreq", "kmrsp", + "sid" }; #define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) @@ -829,3 +823,17 @@ std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) return udt_types[mt]; } + +std::string ConnectStatusStr(EConnectStatus cst) +{ + return (cst == CONN_CONTINUE + ? "INDUCED/CONCLUDING" + : cst == CONN_ACCEPT + ? "ACCEPTED" + : cst == CONN_RENDEZVOUS + ? "RENDEZVOUS (HSv5)" + : cst == CONN_AGAIN + ? "AGAIN" + : "REJECTED"); +} + diff --git a/srtcore/common.h b/srtcore/common.h index 9b8eda8ec..ea0437b5c 100644 --- a/srtcore/common.h +++ b/srtcore/common.h @@ -102,10 +102,62 @@ enum UDTMessageType UMSG_EXT = 0x7FFF //< For the use of user-defined control packets. }; +// This side's role is: INITIATOR prepares the environment first, and sends +// appropriate information to the peer. The peer must be RESPONDER and be ready +// to receive it. It's important for the encryption: the INITIATOR side generates +// the KM, and sends it to RESPONDER. RESPONDER awaits KM received from the +// INITIATOR. Note that in bidirectional mode - that is always with HSv5 - the +// INITIATOR creates both sending and receiving contexts, then sends the key to +// RESPONDER, which creates both sending and receiving contexts, using the same +// key received from INITIATOR. +// +// The method of selection: +// +// In HSv4, it's always data sender INITIATOR, and receiver - RESPONDER. The HSREQ +// and KMREQ are done AFTER the UDT connection is done using UMSG_EXT extension +// messages. As this is unidirectional, the INITIATOR prepares the sending context +// only, the RESPONDER - receiving context only. +// +// In HSv5, for caller-listener configuration, it's simple: caller is INITIATOR, +// listener is RESPONDER. In case of rendezvous the parties are equivalent, +// so the role is resolved by "cookie contest". Rendezvous sockets both know +// each other's cookie generated during the URQ_WAVEAHAND handshake phase. +// The cookies are simply compared as integer numbers; the party that has baked +// a bigger cookie wins, and becomes a INITIATOR. The other loses and becomes an +// RESPONDER. +// +// The case of a draw - that both occasionally have baked identical cookies - +// is treated as an extremely rare and virtually impossible case, so this +// results in connection rejected. +enum HandshakeSide +{ + HSD_DRAW, + HSD_INITIATOR, //< Side that initiates HSREQ/KMREQ. HSv4: data sender, HSv5: connecting socket or winner rendezvous socket + HSD_RESPONDER //< Side that expects HSREQ/KMREQ from the peer. HSv4: data receiver, HSv5: accepted socket or loser rendezvous socket +}; + // For debug std::string MessageTypeStr(UDTMessageType mt, uint32_t extt = 0); //////////////////////////////////////////////////////////////////////////////// +// Commonly used by various reading facilities +enum EReadStatus +{ + RST_OK = 0, // A new portion of data has been received + RST_AGAIN, // Nothing has been received, try again + RST_ERROR = -1 // Irrecoverable error, please close descriptor and stop reading. +}; + +enum EConnectStatus +{ + CONN_ACCEPT = 0, // Received final handshake that confirms connection established + CONN_REJECT = -1, // Error during processing handshake. + CONN_CONTINUE = 1, // induction->conclusion phase + CONN_RENDEZVOUS = 2, // pass to a separate rendezvous processing (HSv5 only) + CONN_AGAIN = -2 // No data was read, don't change any state. +}; + +std::string ConnectStatusStr(EConnectStatus est); #endif diff --git a/srtcore/core.cpp b/srtcore/core.cpp index 37799a14d..548b6d0a1 100644 --- a/srtcore/core.cpp +++ b/srtcore/core.cpp @@ -73,9 +73,12 @@ modified by #endif #include #include +#include "srt.h" #include "queue.h" #include "core.h" #include "logging.h" +#include "crypto.h" +#include "../common/logsupport.hpp" // Required due to containing extern srt_logger_config // Again, just in case when some "smart guy" provided such a global macro #ifdef min @@ -85,11 +88,6 @@ modified by #undef max #endif -#ifdef SRT_ENABLE_SRTCC_EMB -#include "csrtcc.h" -//#define CSRTCC CSRTCC -#endif /* SRT_ENABLE_SRTCC_EMB */ - using namespace std; struct AllFaOn @@ -106,14 +104,14 @@ struct AllFaOn } } logger_fa_all; -logging::LogConfig logger_config (logger_fa_all.allfa); +logging::LogConfig srt_logger_config (logger_fa_all.allfa); -logging::Logger glog(SRT_LOGFA_GENERAL, &logger_config, "SRT.g"); -logging::Logger blog(SRT_LOGFA_BSTATS, &logger_config, "SRT.b"); -logging::Logger mglog(SRT_LOGFA_CONTROL, &logger_config, "SRT.c"); -logging::Logger dlog(SRT_LOGFA_DATA, &logger_config, "SRT.d"); -logging::Logger tslog(SRT_LOGFA_TSBPD, &logger_config, "SRT.t"); -logging::Logger rxlog(SRT_LOGFA_REXMIT, &logger_config, "SRT.r"); +logging::Logger glog(SRT_LOGFA_GENERAL, &srt_logger_config, "SRT.g"); +logging::Logger blog(SRT_LOGFA_BSTATS, &srt_logger_config, "SRT.b"); +logging::Logger mglog(SRT_LOGFA_CONTROL, &srt_logger_config, "SRT.c"); +logging::Logger dlog(SRT_LOGFA_DATA, &srt_logger_config, "SRT.d"); +logging::Logger tslog(SRT_LOGFA_TSBPD, &srt_logger_config, "SRT.t"); +logging::Logger rxlog(SRT_LOGFA_REXMIT, &srt_logger_config, "SRT.r"); CUDTUnited CUDT::s_UDTUnited; @@ -123,47 +121,84 @@ const int CUDT::ERROR = -1; const UDTSOCKET UDT::INVALID_SOCK = CUDT::INVALID_SOCK; const int UDT::ERROR = CUDT::ERROR; -const int32_t CSeqNo::m_iSeqNoTH = 0x3FFFFFFF; -const int32_t CSeqNo::m_iMaxSeqNo = 0x7FFFFFFF; -const int32_t CAckNo::m_iMaxAckSeqNo = 0x7FFFFFFF; +// SRT Version constants +#define SRT_VERSION_UNK 0 +#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */ -//const int32_t CMsgNo::m_iMsgNoTH = 0x03FFFFFF; -//const int32_t CMsgNo::m_iMaxMsgNo = 0x07FFFFFF; -// XXX This is moved to packet.h in-class definition -#ifdef SRT_ENABLE_TSBPD -#ifdef SRT_DEBUG_TSBPD_WRAP //Receiver -//const uint32_t CPacket::MAX_TIMESTAMP = 0x07FFFFFF; //27 bit fast wraparound for tests (~2m15s) -#else -//const uint32_t CPacket::MAX_TIMESTAMP = 0xFFFFFFFF; //Full 32 bit (01h11m35s) +#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: HAISRT_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 -#endif /* SRT_ENABLE_TSBPD */ +/* 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 +*/ -const int CUDT::m_iVersion = 4; -const int CUDT::m_iSYNInterval = 10000; -const int CUDT::m_iSelfClockInterval = 64; void CUDT::construct() { - m_pSndBuffer = NULL; - m_pRcvBuffer = NULL; - m_pSndLossList = NULL; - m_pRcvLossList = NULL; + m_pSndBuffer = NULL; + m_pRcvBuffer = NULL; + m_pSndLossList = NULL; + m_pRcvLossList = NULL; #if SRT_BELATED_LOSSREPORT - 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_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; #endif - m_pSndQueue = NULL; - m_pRcvQueue = NULL; - m_pPeerAddr = NULL; - m_pSNode = NULL; - m_pRNode = NULL; - - // Initilize mutex and condition variables - initSynch(); + m_pSndQueue = NULL; + m_pRcvQueue = NULL; + m_pPeerAddr = NULL; + m_pSNode = NULL; + m_pRNode = NULL; + + // Congestion Control fields + m_dCWndSize = 1000; + m_dMaxCWndSize = 0; + m_iRcvRate = 0; + m_iACKPeriod = 0; + m_iACKInterval = 0; + m_bUserDefinedRTO = false; + m_iRTO = -1; + m_llSndMaxBW = 30000000/8; // 30Mbps in Bytes/sec + m_iSndAvgPayloadSize = 7*188; // = 1316 -- shouldn't be configurable? + + m_SndHsLastTime = 0; + m_SndHsRetryCnt = SRT_MAX_HSRETRY+1; // Will be reset to 0 for HSv5, this value is important for HSv4 + + updatePktSndPeriod(); + + // Initilize mutex and condition variables + initSynch(); } CUDT::CUDT() @@ -202,39 +237,31 @@ CUDT::CUDT() //Cfg m_bDataSender = false; //Sender only if true: does not recv data m_bTwoWayData = false; -#ifdef SRT_ENABLE_TSBPD - m_bTsbPdMode = true; //Enable TsbPd on sender - m_iTsbPdDelay = 120; //Receiver TsbPd delay (mSec) + m_bOPT_TsbPd = true; //Enable TsbPd on sender + m_iOPT_TsbPdDelay = 120; //Receiver TsbPd delay (mSec) + m_iOPT_PeerTsbPdDelay = 0; //Peer's TsbPd delay as receiver (here is its minimum value, if used) #ifdef SRT_ENABLE_TLPKTDROP + m_bOPT_TLPktDrop = true; m_bTLPktDrop = true; //Too-late Packet Drop #endif /* SRT_ENABLE_TLPKTDROP */ //Runtime - m_bTsbPdSnd = false; - m_SndTsbPdDelay = 0; - m_bTsbPdRcv = false; - m_RcvTsbPdDelay = 0; + m_bPeerTsbPd = false; + m_iPeerTsbPdDelay = 0; + m_bTsbPd = false; + m_iTsbPdDelay = 0; + m_iPeerTsbPdDelay = 0; #ifdef SRT_ENABLE_TLPKTDROP - m_bTLPktDropSnd = false; + m_bPeerTLPktDrop = false; #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ #ifdef SRT_ENABLE_NAKREPORT m_bRcvNakReport = true; //Receiver's Periodic NAK Reports m_iMinNakInterval = 20000; //Minimum NAK Report Period (usec) m_iNakReportAccel = 2; //Default NAK Report Period (RTT) accelerator #endif /* SRT_ENABLE_NAKREPORT */ -#ifdef SRT_ENABLE_INPUTRATE 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) -#endif m_bTwoWayData = false; -#ifdef SRT_ENABLE_SRTCC_EMB - m_pCCFactory = new CCCFactory; -#else /* SRT_ENABLE_SRTCC_EMB */ - m_pCCFactory = new CCCFactory; -#endif /* SRT_ENABLE_SRTCC_EMB */ - m_pCC = NULL; - m_pSRTCC = NULL; m_pCache = NULL; // Initial status @@ -247,6 +274,10 @@ CUDT::CUDT() m_bBroken = false; m_bPeerHealth = true; m_ullLingerExpiration = 0; + + m_lSrtVersion = SRT_DEF_VERSION; + m_lPeerSrtVersion = 0; // not defined until connected. + m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1; } CUDT::CUDT(const CUDT& ancestor) @@ -277,27 +308,26 @@ CUDT::CUDT(const CUDT& ancestor) m_iIpTTL = ancestor.m_iIpTTL; m_iIpToS = ancestor.m_iIpToS; #endif -#ifdef SRT_ENABLE_INPUTRATE m_llInputBW = ancestor.m_llInputBW; m_iOverheadBW = ancestor.m_iOverheadBW; -#endif m_bDataSender = ancestor.m_bDataSender; m_bTwoWayData = ancestor.m_bTwoWayData; -#ifdef SRT_ENABLE_TSBPD - m_bTsbPdMode = ancestor.m_bTsbPdMode; - m_iTsbPdDelay = ancestor.m_iTsbPdDelay; + m_bOPT_TsbPd = ancestor.m_bOPT_TsbPd; + m_iOPT_TsbPdDelay = ancestor.m_iOPT_TsbPdDelay; + m_iOPT_PeerTsbPdDelay = ancestor.m_iOPT_PeerTsbPdDelay; + m_iTsbPdDelay = 0; + m_iPeerTsbPdDelay = 0; #ifdef SRT_ENABLE_TLPKTDROP + m_bOPT_TLPktDrop = ancestor.m_bOPT_TLPktDrop; m_bTLPktDrop = ancestor.m_bTLPktDrop; #endif /* SRT_ENABLE_TLPKTDROP */ //Runtime - m_bTsbPdSnd = false; - m_SndTsbPdDelay = 0; - m_bTsbPdRcv = false; - m_RcvTsbPdDelay = 0; + m_bPeerTsbPd = false; + m_iPeerTsbPdDelay = 0; + m_bTsbPd = false; #ifdef SRT_ENABLE_TLPKTDROP - m_bTLPktDropSnd = false; + m_bPeerTLPktDrop = false; #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ #ifdef SRT_ENABLE_NAKREPORT m_bRcvNakReport = ancestor.m_bRcvNakReport; m_iMinNakInterval = ancestor.m_iMinNakInterval; @@ -307,8 +337,6 @@ CUDT::CUDT(const CUDT& ancestor) m_CryptoSecret = ancestor.m_CryptoSecret; m_iSndCryptoKeyLen = ancestor.m_iSndCryptoKeyLen; - m_pCCFactory = ancestor.m_pCCFactory->clone(); - m_pCC = NULL; m_pCache = ancestor.m_pCache; // Initial status @@ -321,6 +349,10 @@ CUDT::CUDT(const CUDT& ancestor) m_bBroken = false; m_bPeerHealth = true; m_ullLingerExpiration = 0; + + m_lSrtVersion = SRT_DEF_VERSION; + m_lPeerSrtVersion = 0; // not defined until connected. + m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1; } CUDT::~CUDT() @@ -336,8 +368,6 @@ CUDT::~CUDT() delete m_pRcvBuffer; delete m_pSndLossList; delete m_pRcvLossList; - delete m_pCCFactory; - delete m_pCC; delete m_pPeerAddr; delete m_pSNode; delete m_pRNode; @@ -363,325 +393,342 @@ static bool bool_int_value(const void* optval, int optlen) void CUDT::setOpt(UDT_SOCKOPT optName, const void* optval, int optlen) { - if (m_bBroken || m_bClosing) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + 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 UDT_MSS: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + CGuard cg(m_ConnectionLock); + CGuard sendguard(m_SendLock); + CGuard recvguard(m_RecvLock); - 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 UDT_SNDSYN: - m_bSynSending = bool_int_value(optval, optlen); - break; + switch (optName) + { + case UDT_MSS: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - case UDT_RCVSYN: - m_bSynRecving = bool_int_value(optval, optlen); - break; + if (*(int*)optval < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - case UDT_CC: - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - if (m_pCCFactory != NULL) - delete m_pCCFactory; - m_pCCFactory = ((CCCVirtualFactory *)optval)->clone(); + m_iMSS = *(int*)optval; - break; + // 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; - case UDT_FC: - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + break; - if (*(int*)optval < 1) - throw CUDTException(MJ_NOTSUP, MN_INVAL); + case UDT_SNDSYN: + m_bSynSending = bool_int_value(optval, optlen); + break; - // Mimimum recv flight flag size is 32 packets - if (*(int*)optval > 32) - m_iFlightFlagSize = *(int*)optval; - else - m_iFlightFlagSize = 32; + case UDT_RCVSYN: + m_bSynRecving = bool_int_value(optval, optlen); + break; - break; + case UDT_FC: + if (m_bConnecting || m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - case UDT_SNDBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + if (*(int*)optval < 1) + throw CUDTException(MJ_NOTSUP, MN_INVAL); - if (*(int*)optval <= 0) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + // Mimimum recv flight flag size is 32 packets + if (*(int*)optval > 32) + m_iFlightFlagSize = *(int*)optval; + else + m_iFlightFlagSize = 32; - m_iSndBufSize = *(int*)optval / (m_iMSS - CPacket::UDP_HDR_SIZE); + break; - break; + case UDT_SNDBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - case UDT_RCVBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + if (*(int*)optval <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - if (*(int*)optval <= 0) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + m_iSndBufSize = *(int*)optval / (m_iMSS - CPacket::UDP_HDR_SIZE); - { - // 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); + break; - // Mimimum recv buffer size is 32 packets - size_t mssin_size = m_iMSS - CPacket::UDP_HDR_SIZE; + case UDT_RCVBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - // XXX This magic 32 deserves some constant - if (val > mssin_size * 32) - m_iRcvBufSize = val / mssin_size; - else - m_iRcvBufSize = 32; + if (*(int*)optval <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - // recv buffer MUST not be greater than FC size - if (m_iRcvBufSize > m_iFlightFlagSize) - m_iRcvBufSize = m_iFlightFlagSize; - } + { + // 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; + break; - case UDT_LINGER: - m_Linger = *(linger*)optval; - break; + case UDT_LINGER: + m_Linger = *(linger*)optval; + break; - case UDP_SNDBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + case UDP_SNDBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_iUDPSndBufSize = *(int*)optval; + m_iUDPSndBufSize = *(int*)optval; - if (m_iUDPSndBufSize < m_iMSS) - m_iUDPSndBufSize = m_iMSS; + if (m_iUDPSndBufSize < m_iMSS) + m_iUDPSndBufSize = m_iMSS; - break; + break; - case UDP_RCVBUF: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + case UDP_RCVBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_iUDPRcvBufSize = *(int*)optval; + m_iUDPRcvBufSize = *(int*)optval; - if (m_iUDPRcvBufSize < m_iMSS) - m_iUDPRcvBufSize = m_iMSS; + if (m_iUDPRcvBufSize < m_iMSS) + m_iUDPRcvBufSize = m_iMSS; - break; + break; - case UDT_RENDEZVOUS: - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_bRendezvous = bool_int_value(optval, optlen); - break; + case UDT_RENDEZVOUS: + if (m_bConnecting || m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + m_bRendezvous = bool_int_value(optval, optlen); + break; - case UDT_SNDTIMEO: - m_iSndTimeOut = *(int*)optval; - break; + case UDT_SNDTIMEO: + m_iSndTimeOut = *(int*)optval; + break; - case UDT_RCVTIMEO: - m_iRcvTimeOut = *(int*)optval; - break; + case UDT_RCVTIMEO: + m_iRcvTimeOut = *(int*)optval; + break; - case UDT_REUSEADDR: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_bReuseAddr = bool_int_value(optval, optlen); - break; + case UDT_REUSEADDR: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + m_bReuseAddr = bool_int_value(optval, optlen); + break; - case UDT_MAXBW: - m_llMaxBW = *(int64_t*)optval; -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_llMaxBW != 0) - { //Absolute MaxBW setting - if (m_pSRTCC != NULL) m_pSRTCC->setMaxBW(m_llMaxBW); //Bytes/sec -#ifdef SRT_ENABLE_INPUTRATE - if (m_pSndBuffer != NULL) m_pSndBuffer->setInputRateSmpPeriod(0); - } - else if (m_llInputBW != 0) - { //Application provided input rate - if (m_pSRTCC) - m_pSRTCC->setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec - if (m_pSndBuffer != NULL) - m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling - } - else - { //Internal input rate sampling - if (m_pSndBuffer != NULL) m_pSndBuffer->setInputRateSmpPeriod(500000); -#endif /* SRT_ENABLE_INPUTRATE */ - } -#endif /* SRT_ENABLE_SRTCC_EMB */ - break; + case UDT_MAXBW: + m_llMaxBW = *(int64_t*)optval; + // XXX + // Note that this below code is effective only + // when done on an already connected socket. Otherwise + // the given attached objects don't exist. + if (m_llMaxBW != 0) + { //Absolute MaxBW setting + setMaxBW(m_llMaxBW); //Bytes/sec + if (m_pSndBuffer) + { + m_pSndBuffer->setInputRateSmpPeriod(0); + } + } + else if (m_llInputBW != 0) + { //Application provided input rate + setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec + if (m_pSndBuffer) + { + m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling + } + } + else + { //Internal input rate sampling + if (m_pSndBuffer) + m_pSndBuffer->setInputRateSmpPeriod(500000); + } + break; #ifdef SRT_ENABLE_IPOPTS - case SRT_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 SRT_IPTOS: - if (m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); - m_iIpToS = *(int*)optval; - break; + case SRT_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 SRT_IPTOS: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + m_iIpToS = *(int*)optval; + break; #endif -#ifdef SRT_ENABLE_INPUTRATE - case SRT_INPUTBW: - m_llInputBW = *(int64_t*)optval; -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_llMaxBW != 0) - { //Keep MaxBW setting - ; - } - else if (m_llInputBW != 0) - { //Application provided input rate - if (m_pSRTCC) - m_pSRTCC->setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec - if (m_pSndBuffer != NULL) - m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling - } - else - { //Internal input rate sampling - if (m_pSndBuffer != NULL) - m_pSndBuffer->setInputRateSmpPeriod(500000); //Enable input rate sampling - } -#endif /* SRT_ENABLE_SRTCC_EMB */ - break; - - case SRT_OHEADBW: - if ((*(int*)optval < 5) - || (*(int*)optval > 100)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - m_iOverheadBW = *(int*)optval; -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_llMaxBW != 0) - { //Keep MaxBW setting - ; - } - else if (m_llInputBW != 0) - { //Adjust MaxBW for new overhead - if (m_pSRTCC) - m_pSRTCC->setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec - } - //else - // Keep input rate sampling setting, next CCupdate will adjust MaxBW -#endif /* SRT_ENABLE_SRTCC_EMB */ - break; -#endif /* SRT_ENABLE_INPUTRATE */ - - case SRT_SENDER: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bDataSender = bool_int_value(optval, optlen); - break; - - case SRT_TWOWAYDATA: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bTwoWayData = bool_int_value(optval, optlen); - break; - -#ifdef SRT_ENABLE_TSBPD - case SRT_TSBPDMODE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bTsbPdMode = bool_int_value(optval, optlen); - break; - - case SRT_TSBPDDELAY: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_iTsbPdDelay = *(int*)optval; - break; + case SRT_INPUTBW: + m_llInputBW = *(int64_t*)optval; + if (m_llMaxBW != 0) + { //Keep MaxBW setting + ; + } + else if (m_llInputBW != 0) + { //Application provided input rate + setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec + if (m_pSndBuffer) + { + m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling + } + } + else + { //Internal input rate sampling + if (m_pSndBuffer) + m_pSndBuffer->setInputRateSmpPeriod(500000); //Enable input rate sampling + } + break; + + case SRT_OHEADBW: + if ((*(int*)optval < 5) + || (*(int*)optval > 100)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + m_iOverheadBW = *(int*)optval; + if (m_llMaxBW != 0) + { //Keep MaxBW setting + ; + } + else if (m_llInputBW != 0) + { //Adjust MaxBW for new overhead + setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec + } + //else + // Keep input rate sampling setting, next CCupdate will adjust MaxBW + break; + + case SRT_SENDER: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_bDataSender = bool_int_value(optval, optlen); + break; + + case SRT_TSBPDMODE: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_bOPT_TsbPd = bool_int_value(optval, optlen); + break; + + case SRT_TSBPDDELAY: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_iOPT_TsbPdDelay = *(int*)optval; + m_iOPT_PeerTsbPdDelay = *(int*)optval; + break; + + case SRT_RCVLATENCY: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_iOPT_TsbPdDelay = *(int*)optval; + break; + + case SRT_PEERLATENCY: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_iOPT_PeerTsbPdDelay = *(int*)optval; + break; #ifdef SRT_ENABLE_TLPKTDROP - case SRT_TSBPDMAXLAG: - //Obsolete - break; - - case SRT_TLPKTDROP: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bTLPktDrop = bool_int_value(optval, optlen); - break; + case SRT_TSBPDMAXLAG: + //Obsolete + break; + + case SRT_TLPKTDROP: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_bOPT_TLPktDrop = bool_int_value(optval, optlen); + break; #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ - case SRT_PASSPHRASE: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + case SRT_PASSPHRASE: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - if ((optlen != 0) - && (10 > optlen) - && (HAICRYPT_SECRET_MAX_SZ < optlen)) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + // 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); - break; + 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); + break; - case SRT_PBKEYLEN: - case SRT_SNDPBKEYLEN: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + case SRT_PBKEYLEN: + case SRT_SNDPBKEYLEN: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - if ((*(int*)optval != 0) //Encoder: No encryption, Decoder: get key from Keyint Material - && (*(int*)optval != 16) - && (*(int*)optval != 24) - && (*(int*)optval != 32)) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + if ((*(int*)optval != 0) //Encoder: No encryption, Decoder: get key from Keyint Material + && (*(int*)optval != 16) + && (*(int*)optval != 24) + && (*(int*)optval != 32)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - m_iSndCryptoKeyLen = *(int*)optval; - break; + m_iSndCryptoKeyLen = *(int*)optval; + break; #ifdef SRT_ENABLE_NAKREPORT - case SRT_RCVNAKREPORT: - if (m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); - m_bRcvNakReport = bool_int_value(optval, optlen); - break; + case SRT_RCVNAKREPORT: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_bRcvNakReport = bool_int_value(optval, optlen); + break; #endif /* SRT_ENABLE_NAKREPORT */ #ifdef SRT_ENABLE_CONNTIMEO - case SRT_CONNTIMEO: - m_iConnTimeOut = *(int*)optval; - break; + case SRT_CONNTIMEO: + m_iConnTimeOut = *(int*)optval; + break; #endif #if SRT_BELATED_LOSSREPORT - case SRT_LOSSMAXTTL: - m_iMaxReorderTolerance = *(int*)optval; - break; + case SRT_LOSSMAXTTL: + m_iMaxReorderTolerance = *(int*)optval; + break; #endif - default: - throw CUDTException(MJ_NOTSUP, MN_NONE, 0); - } + case SRT_AGENTVERSION: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_lSrtVersion = *(uint32_t*)optval; + break; + + case SRT_MINVERSION: + if (m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + m_lMinimumPeerSrtVersion = *(uint32_t*)optval; + break; + + case SRT_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; + + default: + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } } void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) @@ -705,14 +752,6 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) optlen = sizeof(bool); break; - case UDT_CC: - if (!m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); - *(CCC**)optval = m_pCC; - optlen = sizeof(CCC*); - - break; - case UDT_FC: *(int*)optval = m_iFlightFlagSize; optlen = sizeof(int); @@ -783,15 +822,10 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) event |= UDT_EPOLL_ERR; else { -#ifdef SRT_ENABLE_TSBPD CGuard::enterCS(m_RecvLock); if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady()) event |= UDT_EPOLL_IN; CGuard::leaveCS(m_RecvLock); -#else /* SRT_ENABLE_TSBPD */ - if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady()) - event |= UDT_EPOLL_IN; -#endif /* SRT_ENABLE_TSBPD */ if (m_pSndBuffer && (m_iSndBufSize > m_pSndBuffer->getCurrBufSize())) event |= UDT_EPOLL_OUT; } @@ -809,17 +843,12 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) break; case UDT_RCVDATA: -#ifdef SRT_ENABLE_TSBPD if (m_pRcvBuffer) { CGuard::enterCS(m_RecvLock); *(int32_t*)optval = m_pRcvBuffer->getRcvDataSize(); CGuard::leaveCS(m_RecvLock); } -#else /* SRT_ENABLE_TSBPD */ - if (m_pRcvBuffer) - *(int32_t*)optval = m_pRcvBuffer->getRcvDataSize(); -#endif/* SRT_ENABLE_TSBPD */ else *(int32_t*)optval = 0; optlen = sizeof(int32_t); @@ -846,22 +875,23 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) optlen = sizeof(int32_t); break; - case SRT_TWOWAYDATA: - *(int32_t*)optval = m_bTwoWayData; - optlen = sizeof(int32_t); - break; -#ifdef SRT_ENABLE_TSBPD case SRT_TSBPDMODE: - *(int32_t*)optval = m_bTsbPdMode; + *(int32_t*)optval = m_bOPT_TsbPd; optlen = sizeof(int32_t); break; case SRT_TSBPDDELAY: + case SRT_RCVLATENCY: *(int32_t*)optval = m_iTsbPdDelay; optlen = sizeof(int32_t); break; + case SRT_PEERLATENCY: + *(int32_t*)optval = m_iPeerTsbPdDelay; + optlen = sizeof(int32_t); + break; + #ifdef SRT_ENABLE_TLPKTDROP case SRT_TSBPDMAXLAG: //Obsolete: preserve binary compatibility. @@ -874,41 +904,39 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) optlen = sizeof(int32_t); break; #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ case SRT_PBKEYLEN: - /* - * Before TWOWAY support this was returning the sender's keylen from both side of the connection when connected. - * Maintain binary compatibility for sender-only and receiver-only peers. - */ -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_pSRTCC) - *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_pSRTCC->m_iSndKmKeyLen : m_pSRTCC->m_iRcvKmKeyLen; + if (m_pCryptoControl) + *(int32_t*)optval = m_pCryptoControl->KeyLen(); // Running Key length. else -#endif - *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_iSndCryptoKeyLen : 0; + *(int32_t*)optval = m_iSndCryptoKeyLen; // May be 0. optlen = sizeof(int32_t); break; + /* + XXX This was an experimental bidirectional implementation using HSv4, + which was using two separate KMX processes per direction. HSv4 bidirectional + implementation has been completely abandoned for the sake of HSv5, in + which there's only one KMX process performed as a part of handshake this + time and therefore there's still one key, one key length and the encryption + uses the same SEK for both directions (in result, the same password). + case SRT_SNDPBKEYLEN: -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_pSRTCC) - *(int32_t*)optval = m_pSRTCC->m_iSndKmKeyLen; + if (m_pCryptoControl) + *(int32_t*)optval = m_pCryptoControl->m_iSndKmKeyLen; else -#endif *(int32_t*)optval = m_iSndCryptoKeyLen; optlen = sizeof(int32_t); break; case SRT_RCVPBKEYLEN: -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_pSRTCC) - *(int32_t*)optval = m_pSRTCC->m_iRcvKmKeyLen; + if (m_pCryptoControl) + *(int32_t*)optval = m_pCryptoControl->m_iRcvKmKeyLen; else -#endif *(int32_t*)optval = 0; //Defined on sender's side only optlen = sizeof(int32_t); break; + */ case SRT_SNDPEERKMSTATE: /* Sender's peer decryption state */ /* @@ -916,21 +944,17 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) * where sender reports peer (receiver) state and the receiver reports local state when connected. * Maintain binary compatibility and return what SRT_RCVKMSTATE returns for receive-only connected peer. */ -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_pSRTCC) - *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_pSRTCC->m_iSndPeerKmState : m_pSRTCC->m_iRcvKmState; + if (m_pCryptoControl) + *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_pCryptoControl->m_iSndPeerKmState : m_pCryptoControl->m_iRcvKmState; else -#endif *(int32_t*)optval = SRT_KM_S_UNSECURED; optlen = sizeof(int32_t); break; case SRT_RCVKMSTATE: /* Receiver decryption state */ -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_pSRTCC) - *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_pSRTCC->m_iSndPeerKmState : m_pSRTCC->m_iRcvKmState; + if (m_pCryptoControl) + *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_pCryptoControl->m_iSndPeerKmState : m_pCryptoControl->m_iRcvKmState; else -#endif *(int32_t*)optval = SRT_KM_S_UNSECURED; optlen = sizeof(int32_t); break; @@ -942,23 +966,15 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) break; #endif /* SRT_ENABLE_NAKREPORT */ -#ifdef SRT_ENABLE_SRTCC_EMB case SRT_AGENTVERSION: - if (m_pSRTCC) - *(int32_t*)optval = m_pSRTCC->m_SrtVersion; - else - *(int32_t*)optval = 0; + *(int32_t*)optval = m_lSrtVersion; optlen = sizeof(int32_t); break; case SRT_PEERVERSION: - if (m_pSRTCC) - *(int32_t*)optval = m_pSRTCC->getPeerSrtVersion(); - else - *(int32_t*)optval = 0; + *(int32_t*)optval = m_lPeerSrtVersion; optlen = sizeof(int32_t); break; -#endif #ifdef SRT_ENABLE_CONNTIMEO case SRT_CONNTIMEO: @@ -967,25 +983,61 @@ void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) break; #endif + case SRT_MINVERSION: + *(uint32_t*)optval = m_lMinimumPeerSrtVersion; + optlen = sizeof(uint32_t); + break; + + case SRT_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; + default: throw CUDTException(MJ_NOTSUP, MN_NONE, 0); } } +bool CUDT::setstreamid(UDTSOCKET 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(UDTSOCKET u) +{ + CUDT* that = getUDTHandle(u); + if (!that) + return ""; + + return that->m_sStreamName; +} + void CUDT::clearData() { // Initial sequence number, loss, acknowledgement, etc. m_iPktSize = m_iMSS - CPacket::UDP_HDR_SIZE; m_iPayloadSize = m_iPktSize - CPacket::HDR_SIZE; + LOGC(mglog.Debug) << "clearData: PAYLOAD SIZE: " << m_iPayloadSize; + m_iEXPCount = 1; m_iBandwidth = 1; //pkts/sec -#ifdef SRT_ENABLE_BSTATS // XXX use some constant for this 16 m_iDeliveryRate = 16 * m_iPayloadSize; -#else - m_iDeliveryRate = 16; -#endif m_iAckSeqNo = 0; m_ullLastAckTime = 0; @@ -1008,7 +1060,6 @@ void CUDT::clearData() m_iRcvUndecryptTotal = 0; m_iTraceRcvUndecrypt = 0; -#ifdef SRT_ENABLE_BSTATS m_ullBytesSentTotal = 0; m_ullBytesRecvTotal = 0; m_ullBytesRetransTotal = 0; @@ -1023,26 +1074,28 @@ void CUDT::clearData() #endif /* SRT_ENABLE_TLPKTDROP */ m_ullRcvBytesUndecryptTotal = 0; m_ullTraceRcvBytesUndecrypt = 0; -#endif /* SRT_ENABLE_BSTATS */ -#ifdef SRT_ENABLE_TSBPD - m_bTsbPdSnd = false; - m_SndTsbPdDelay = 0; - m_bTsbPdRcv = false; - m_RcvTsbPdDelay = 0; + // Resetting these data because this happens when agent isn't connected. + m_bPeerTsbPd = false; + m_iPeerTsbPdDelay = 0; + + m_bTsbPd = m_bOPT_TsbPd; // Take the values from user-configurable options + m_iTsbPdDelay = m_iOPT_TsbPdDelay; + m_bTLPktDrop = m_bOPT_TLPktDrop; #ifdef SRT_ENABLE_TLPKTDROP - m_bTLPktDropSnd = false; + m_bPeerTLPktDrop = false; #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ #ifdef SRT_ENABLE_NAKREPORT - m_bSndPeerNakReport = false; + m_bPeerNakReport = false; #endif /* SRT_ENABLE_NAKREPORT */ m_bPeerRexmitFlag = false; m_llSndDuration = m_llSndDurationTotal = 0; + m_RdvState = CHandShake::RDV_INVALID; + m_ullRcvPeerStartTime = 0; } void CUDT::open() @@ -1065,12 +1118,12 @@ void CUDT::open() m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL; m_pRNode->m_bOnList = false; - m_iRTT = 10 * m_iSYNInterval; + m_iRTT = 10 * CPacket::SYN_INTERVAL; m_iRTTVar = m_iRTT >> 1; m_ullCPUFrequency = CTimer::getCPUFrequency(); // set up the timers - m_ullSYNInt = m_iSYNInterval * m_ullCPUFrequency; + m_ullSYNInt = CPacket::SYN_INTERVAL * m_ullCPUFrequency; // set minimum NAK and EXP timeout to 300ms #ifdef SRT_ENABLE_NAKREPORT @@ -1131,306 +1184,2384 @@ void CUDT::setListenState() m_bListening = true; } -void CUDT::connect(const sockaddr* serv_addr, int32_t forced_isn) +size_t CUDT::fillSrtHandshake(uint32_t* srtdata, size_t srtlen, int msgtype, int hs_version) { - CGuard cg(m_ConnectionLock); + if ( srtlen < SRT_HS__SIZE ) + { + LOGC(mglog.Fatal) << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS__SIZE << ")"; + return 0; + } - if (!m_bOpened) - throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + srtlen = SRT_HS__SIZE; // We use only that much space. - if (m_bListening) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + 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] |= SRT_OPT_HAICRYPT; - if (m_bConnecting || m_bConnected) - throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + 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) << "IPE: createSrtHandshake/sendSrtMsg called with value " << msgtype; return 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)); +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 = m_iOPT_TsbPdDelay; + m_iPeerTsbPdDelay = 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; - // 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 * 1000ULL; -#else - uint64_t ttl = 3000000; -#endif - if (m_bRendezvous) - ttl *= 10; - ttl += CTimer::getTime(); - m_pRcvQueue->registerConnector(m_SocketID, this, m_iIPversion, serv_addr, ttl); - - // This is my current configurations - m_ConnReq.m_iVersion = m_iVersion; - m_ConnReq.m_iType = m_iSockType; - m_ConnReq.m_iMSS = m_iMSS; - m_ConnReq.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize)? m_iRcvBufSize : m_iFlightFlagSize; - m_ConnReq.m_iReqType = (!m_bRendezvous) ? URQ_INDUCTION : URQ_RENDEZVOUS; - m_ConnReq.m_iID = m_SocketID; - CIPAddress::ntop(serv_addr, m_ConnReq.m_piPeerIP, m_iIPversion); - - if ( forced_isn == 0 ) - { - // Random Initial Sequence Number - srand((unsigned int)CTimer::getTime()); - m_iISN = m_ConnReq.m_iISN = (int32_t)(CSeqNo::m_iMaxSeqNo * (double(rand()) / RAND_MAX)); - } - else - { - m_iISN = m_ConnReq.m_iISN = forced_isn; - } + if ( hs_version < CUDT::HS_VERSION_SRT1 ) + { + // HSv4 - this uses only one value. + srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay); + } + else + { + // HSv5 - this will be understood only since this version when this exists. + srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay); - m_iLastDecSeq = m_iISN - 1; - m_iSndLastAck = m_iISN; - m_iSndLastDataAck = m_iISN; -#ifdef SRT_ENABLE_TLPKTDROP - m_iSndLastFullAck = m_iISN; -#endif /* SRT_ENABLE_TLPKTDROP */ - m_iSndCurrSeqNo = m_iISN - 1; - m_iSndLastAck2 = m_iISN; - m_ullSndLastAck2Time = CTimer::getTime(); + 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); - // Inform the server my configurations. - CPacket request; - char* reqdata = new char [m_iPayloadSize]; - request.pack(UMSG_HANDSHAKE, NULL, reqdata, m_iPayloadSize); - // ID = 0, connection request - request.m_iID = 0; - - int hs_size = m_iPayloadSize; - m_ConnReq.serialize(reqdata, hs_size); - request.setLength(hs_size); - -#ifdef SRT_ENABLE_CTRLTSTAMP - uint64_t now = CTimer::getTime(); - request.m_iTimeStamp = int(now - m_StartTime); -#elif defined(HAI_PATCH) - uint64_t now = CTimer::getTime(); + // This wasn't there for HSv4, this setting is only for the receiver. + // HSv5 is bidirectional, so every party is a receiver. + +#ifdef SRT_ENABLE_TLPKTDROP + if (m_bTLPktDrop) + srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; #endif + } + } - LOGC(mglog.Debug) << CONID() << "CUDT::connect: sending UDT handshake for socket=" << m_ConnReq.m_iID; + // I support SRT_OPT_REXMITFLG. Do you? + srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; -#ifdef HAI_PATCH - /* - * 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, request); -#else /* HAI_PATCH */ - m_pSndQueue->sendto(serv_addr, request); - m_llLastReqTime = CTimer::getTime(); + LOGC(mglog.Debug) << "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]) << "]"; - m_bConnecting = true; -#endif /* HAI_PATCH */ + return 3; +} - // asynchronous connect, return immediately - if (!m_bSynRecving) - { - delete [] reqdata; - return; - } +size_t CUDT::fillSrtHandshake_HSRSP(uint32_t* srtdata, size_t /* srtlen - unused */, int hs_version) +{ + // Setting m_ullRcvPeerStartTime is done ine 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; - // Wait for the negotiated configurations from the peer side. - CPacket response; - char* resdata = new char [m_iPayloadSize]; - response.pack(UMSG_HANDSHAKE, NULL, resdata, m_iPayloadSize); + if ( hs_version < HS_VERSION_SRT1 ) + { + // HSv4 - this uses only one value + srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay); + } + 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); + } + } + else + { + LOGC(mglog.Debug) << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag."; + } - CUDTException e; + // 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); - while (!m_bClosing) - { - // avoid sending too many requests, at most 1 request per 250ms - if (CTimer::getTime() - m_llLastReqTime > 250000) - { - m_ConnReq.serialize(reqdata, hs_size); - request.setLength(hs_size); - if (m_bRendezvous) - request.m_iID = m_ConnRes.m_iID; -#ifdef SRT_ENABLE_CTRLTSTAMP - now = CTimer::getTime(); - m_llLastReqTime = now; - request.m_iTimeStamp = int(now - m_StartTime); - m_pSndQueue->sendto(serv_addr, request); -#else /* SRT_ENABLE_CTRLTSTAMP */ - m_pSndQueue->sendto(serv_addr, request); - m_llLastReqTime = CTimer::getTime(); -#endif /* SRT_ENABLE_CTRLTSTAMP */ - } + LOGC(mglog.Debug) << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay; + } + else + { + LOGC(mglog.Debug) << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5) + << " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND"; + } + +#ifdef SRT_ENABLE_TLPKTDROP + if (m_bTLPktDrop) + srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; +#endif + } + else + { + LOGC(mglog.Fatal) << "IPE: fillSrtHandshake_HSRSP: m_ullRcvPeerStartTime NOT SET!"; + return 0; + } + + +#ifdef SRT_ENABLE_NAKREPORT + 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; + } +#endif + + 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. + LOGC(mglog.Debug) << "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; + LOGC(mglog.Debug).form("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. + LOGC(mglog.Debug).form("HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag" ); + } + + LOGC(mglog.Debug) << "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()); + LOGC(mglog.Debug).form("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: + LOGC(mglog.Error).form( "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 r_pkt, ref_t 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; + + LOGC(mglog.Debug) << "createSrtHandshake: have buffer size=" << pkt.getLength() << " kmdata_wordsize=" << kmdata_wordsize; + + // 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. + if (hs.m_iVersion > HS_VERSION_UDT4) + { + // 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. + hs.m_iType = 0; + } + + size_t hs_size = pkt.getLength(); + hs.store_to(pkt.m_pcData, Ref(hs_size)); + pkt.setLength(hs_size); + LOGC(mglog.Debug) << "createSrtHandshake: (no HSREQ/KMREQ ext) data: " << hs.show(); + return true; + } + + string logext = "HSREQ"; + + bool have_kmreq = false; + bool have_sid = false; + + // Install the SRT extensions + hs.m_iType = CHandShake::HS_EXT_HSREQ; + + if ( srths_cmd == SRT_CMD_HSREQ && m_sStreamName != "" ) + { + have_sid = true; + hs.m_iType |= CHandShake::HS_EXT_SID; + logext += ",SID"; + } + + if (m_iSndCryptoKeyLen > 0) + { + have_kmreq = true; + hs.m_iType |= CHandShake::HS_EXT_KMREQ; + logext += ",KMREQ"; + } + + LOGC(mglog.Debug) << "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(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_iPayloadSize 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); + + LOGC(mglog.Debug) << "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_iPayloadSize/2; + + if ( m_sStreamName.size() >= size_limit ) + { + LOGC(mglog.Error) << "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()); + + ra_size = wordsize; + *pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_SID) | HS_CMDSPEC_SIZE::wrap(ra_size); + + LOGC(mglog.Debug) << "createSrtHandshake: after SID [" << m_sStreamName << "] length=" << m_sStreamName.size() << " alignedln=" << aligned_bytesize + << ": offset=" << offset << " SID size=" << ra_size << " space left: " << (total_ra_size - offset); + } + + // When encryption turned on + if (have_kmreq) + { + LOGC(mglog.Debug) << "createSrtHandshake: Agent uses 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) ) + continue; + + m_pCryptoControl->getKmMsg_markSent(ki); + + 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(m_pCryptoControl->getKmMsg_data(ki)); + + LOGC(mglog.Debug) << "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 ) + { + LOGC(mglog.Error) << "createSrtHandshake: IPE: all keys have expired, no KM to send."; + return false; + } + } + else if ( srtkm_cmd == SRT_CMD_KMRSP ) + { + if ( !kmdata || kmdata_wordsize == 0 ) + { + LOGC(mglog.Fatal) << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!"; + return false; + } + + // Shift the starting point with the value of previously added block, + // to start with the new one. + offset += ra_size; + + ra_size = kmdata_wordsize; + *(p + offset) = HS_CMDSPEC_CMD::wrap(srtkm_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size); + ++offset; + LOGC(mglog.Debug) << "createSrtHandshake: KMRSP: applying returned key length=" + << ra_size; // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata, kmdata_wordsize*sizeof(uint32_t)) << "]"; + + const uint32_t* keydata = reinterpret_cast(kmdata); + NtoHLA(p + offset, keydata, ra_size); + } + else + { + LOGC(mglog.Fatal) << "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)); + + LOGC(mglog.Debug) << "createSrtHandshake: filled HSv5 handshake flags: " + << hs.m_iType << " length: " << pkt.getLength() << " bytes"; + + return true; +} + +static int FindExtensionBlock(uint32_t* begin, size_t total_length, + ref_t r_out_len, ref_t r_next_block) +{ + 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; +} + +void 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; + + LOGC(mglog.Debug) << "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 ) + { + LOGC(mglog.Debug) << "KMREQ -> requested to send KMRSP length=" << len_out; + sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out); + } + else + { + LOGC(mglog.Error) << "KMREQ failed to process the request - ignoring"; + } + + return; // 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; // nothing to do + } + + default: + LOGC(mglog.Error).form( "rcvSrtMsg: cmd=%d len=%zu unsupported message", etype, len); + break; + } + + if ( res == SRT_CMD_NONE ) + return; + + // Send the message that the message handler requested. + sendSrtMsg(res); +} + +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).form( "rcvSrtMsg: 2nd PeerStartTime diff=%lld usec", + (long long)(m_ullRcvPeerStartTime - oldPeerStartTime)); + } + } +#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 = m_iOPT_TsbPdDelay; + m_iPeerTsbPdDelay = m_iOPT_PeerTsbPdDelay; + + if (len < SRT_CMD_HSREQ_MINSZ) + { + /* Packet smaller than minimum compatible packet size */ + LOGC(mglog.Error).form( "HSREQ/rcv: cmd=%d(HSREQ) len=%zu invalid", SRT_CMD_HSREQ, len); + return SRT_CMD_NONE; + } + + LOGC(mglog.Note).form( "HSREQ/rcv: cmd=%d(HSREQ) len=%zu 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]; + uint32_t peer_srt_options = srtdata[SRT_HS_FLAGS]; + + if ( hsv == CUDT::HS_VERSION_UDT4 ) + { + if ( m_lPeerSrtVersion >= SRT_VERSION_FEAT_HSv5 ) + { + LOGC(mglog.Error) << "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 ) + { + LOGC(mglog.Error) << "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 ) + { + LOGC(mglog.Error) << "HSREQ/rcv: Peer version: " << SrtVersionString(m_lPeerSrtVersion) + << " is too old for requested: " << SrtVersionString(m_lMinimumPeerSrtVersion) << " - REJECTING"; + return SRT_CMD_REJECT; + } + + LOGC(mglog.Debug) << "HSREQ/rcv: PEER Version: " + << SrtVersionString(m_lPeerSrtVersion) + << " Flags: " << peer_srt_options + << "(" << SrtFlagString(peer_srt_options) << ")"; + + m_bPeerRexmitFlag = IsSet(peer_srt_options, SRT_OPT_REXMITFLG); + LOGC(mglog.Debug).form("HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND" ); + + 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(peer_srt_options, SRT_OPT_TSBPDSND) || IsSet(peer_srt_options, SRT_OPT_TSBPDRCV) ) + { + LOGC(mglog.Error) << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting."; + return SRT_CMD_REJECT; + } + + LOGC(mglog.Warn) << "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(peer_srt_options, SRT_OPT_TSBPDSND) ) + { + //TimeStamp-based Packet Delivery feature enabled + if ( !m_bTsbPd ) + { + LOGC(mglog.Warn) << "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, peer_decl_latency); + LOGC(mglog.Debug) << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay + << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay; + m_iTsbPdDelay = maxdelay; + } + } + else + { + std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent"; + LOGC(mglog.Debug) << "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(peer_srt_options, 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, peer_decl_latency); + LOGC(mglog.Debug) << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay + << " Peer:" << peer_decl_latency << " Selecting:" << maxdelay; + m_iPeerTsbPdDelay = maxdelay; + } + else + { + std::string how_about_agent = m_bTsbPd ? "BUT AGENT DOES" : "and nor does Agent"; + LOGC(mglog.Debug) << "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. +#ifdef SRT_ENABLE_TLPKTDROP + if (IsSet(peer_srt_options, SRT_OPT_TLPKTDROP)) + { + //Too late packets dropping feature supported + m_bPeerTLPktDrop = true; + } +#endif /* SRT_ENABLE_TLPKTDROP */ +#ifdef SRT_ENABLE_NAKREPORT + if (IsSet(peer_srt_options, SRT_OPT_NAKREPORT)) + { + //Peer will send Periodic NAK Reports + m_bPeerNakReport = true; + } +#endif /* SRT_ENABLE_NAKREPORT */ + } + + + 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) << "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 */ + LOGC(mglog.Error).form( "HSRSP/rcv: cmd=%d(HSRSP) len=%zu 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).form( "rcvSrtMsg: 2nd PeerStartTime diff=%lld usec", + (long long)(m_ullRcvPeerStartTime - oldPeerStartTime)); + } + } +#else + m_ullRcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ts); +#endif + + m_lPeerSrtVersion = srtdata[SRT_HS_VERSION]; + uint32_t peer_srt_options = srtdata[SRT_HS_FLAGS]; + + LOGC(mglog.Debug).form("HSRSP/rcv: Version: %s Flags: SND:%08X (%s)", + SrtVersionString(m_lPeerSrtVersion).c_str(), + peer_srt_options, + SrtFlagString(peer_srt_options).c_str()); + + + if ( hsv == CUDT::HS_VERSION_UDT4 ) + { + // The old HSv4 way: extract just one value and put it under peer. + if (IsSet(peer_srt_options, SRT_OPT_TSBPDRCV)) + { + //TsbPd feature enabled + m_bPeerTsbPd = true; + m_iPeerTsbPdDelay = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]); + LOGC(mglog.Debug) << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay + << " (Agent: declared:" << m_iTsbPdDelay << " rcv:" << m_iTsbPdDelay << ")"; + } + // 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(peer_srt_options, SRT_OPT_TSBPDRCV)) + { + //TsbPd feature enabled + m_bPeerTsbPd = true; + m_iPeerTsbPdDelay = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]); + LOGC(mglog.Debug) << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay; + } + else + { + LOGC(mglog.Debug) << "HSRSP/rcv: Peer (responder) DOES NOT USE latency"; + } + + if (IsSet(peer_srt_options, SRT_OPT_TSBPDSND)) + { + if (!m_bTsbPd) + { + LOGC(mglog.Warn) << "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 = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]); + LOGC(mglog.Debug) << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay; + } + } + } + +#ifdef SRT_ENABLE_TLPKTDROP + if ((m_lSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(peer_srt_options, SRT_OPT_TLPKTDROP)) + { + //Too late packets dropping feature supported + m_bPeerTLPktDrop = true; + } +#endif /* SRT_ENABLE_TLPKTDROP */ +#ifdef SRT_ENABLE_NAKREPORT + if ((m_lSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(peer_srt_options, SRT_OPT_NAKREPORT)) + { + //Peer will send Periodic NAK Reports + m_bPeerNakReport = true; + } +#endif /* SRT_ENABLE_NAKREPORT */ + + if ( m_lSrtVersion >= SrtVersion(1, 2, 0) ) + { + if ( IsSet(peer_srt_options, SRT_OPT_REXMITFLG) ) + { + //Peer will use REXMIT flag in packet retransmission. + m_bPeerRexmitFlag = true; + LOGP(mglog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer."); + } + else + { + LOGP(mglog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT"); + } + } + else + { + LOGC(mglog.Debug).form("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, 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 ) + { + LOGC(mglog.Error) << "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 ) + { + // 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) << "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 = hs.m_iType; + + if ( ext_flags == 0 ) + { + LOGC(mglog.Error) << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!"; + return false; + } + + LOGC(mglog.Debug) << "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(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) ) + { + LOGC(mglog.Debug) << "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 ) + { + LOGC(mglog.Error) << "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 ) + { + LOGC(mglog.Error) << "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 ) + { + LOGC(mglog.Error) << "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 ) + { + LOGC(mglog.Error) << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd; + return false; + } + handshakeDone(); + updateAfterSrtHandshake(SRT_CMD_HSRSP, HS_VERSION_SRT1); + } + else if ( cmd == SRT_CMD_NONE ) + { + LOGC(mglog.Error) << "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; + } + } + + LOGC(mglog.Debug) << "interpretSrtHandshake: HSREQ done, checking KMREQ"; + + // Now check the encrypted + + bool encrypted = false; + + if ( IsSet(ext_flags, CHandShake::HS_EXT_KMREQ) ) + { + LOGC(mglog.Debug) << "interpretSrtHandshake: extracting KMREQ/RSP type extension"; + + if (m_iSndCryptoKeyLen <= 0) + { + LOGC(mglog.Error) << "HS KMREQ: Peer declares encryption, but agent does not."; + return false; + } + + 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)); + + LOGC(mglog.Debug) << "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 ) + { + LOGC(mglog.Fatal) << "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 ) + { + // Something went wrong. + LOGC(mglog.Debug) << "interpretSrtHandshake: KMREQ processing failed - returned " << res; + return false; + } + encrypted = true; + } + else if ( cmd == SRT_CMD_KMRSP ) + { + m_pCryptoControl->processSrtMsg_KMRSP(begin+1, bytelen, HS_VERSION_SRT1); + // XXX Possible to check status? + encrypted = true; + } + else if ( cmd == SRT_CMD_NONE ) + { + LOGC(mglog.Error) << "HS KMREQ expected - none found!"; + return false; + } + else + { + LOGC(mglog.Debug) << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd); + length -= (next - begin); + begin = next; + if (begin) + continue; + } + + break; + } + } + + if ( IsSet(ext_flags, CHandShake::HS_EXT_SID) ) + { + LOGC(mglog.Debug) << "interpretSrtHandshake: extracting SID 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, unless REPEATED by 'continue'. + { + int cmd = FindExtensionBlock(begin, length, Ref(blocklen), Ref(next)); + + LOGC(mglog.Debug) << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd); + + size_t bytelen = blocklen*sizeof(uint32_t); + if ( cmd == SRT_CMD_SID ) + { + // 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); + m_sStreamName = target; + LOGC(mglog.Debug) << "CONNECTOR'S REQUESTED SID [" << m_sStreamName << "] (bytelen=" << bytelen << " blocklen=" << blocklen; + } + else if ( cmd == SRT_CMD_NONE ) + { + LOGC(mglog.Error) << "HS SID expected - none found!"; + return false; + } + else + { + LOGC(mglog.Debug) << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd); + length -= (next - begin); + begin = next; + if (begin) + continue; + } + + break; + } + } + + if ( !encrypted && m_iSndCryptoKeyLen > 0 ) + { + LOGC(mglog.Error) << "HS EXT: Agent declares encryption, but peer does not."; + return false; + } + + // Ok, finished, for now. + return true; +} + +void CUDT::startConnect(const sockaddr* serv_addr, int32_t forced_isn) +{ + CGuard cg(m_ConnectionLock); + + LOGC(mglog.Debug) << "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 * 1000ULL; +#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); + + // 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); + 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_iType = m_iSockType; + 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; +#ifdef SRT_ENABLE_TLPKTDROP + m_iSndLastFullAck = m_iISN; +#endif /* SRT_ENABLE_TLPKTDROP */ + 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_iPayloadSize); + // 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_iPayloadSize; + 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_StartTime); + + LOGC(mglog.Debug) << CONID() << "CUDT::startConnect: REQ-TIME HIGH. 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: .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) + { + 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_iPayloadSize); + + CUDTException e; + + 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) + { + LOGC(mglog.Debug) << "startConnect: LOOP: time to send (" << tdiff << " > 250000). size=" << reqpkt.getLength(); + + if (m_bRendezvous) + reqpkt.m_iID = m_ConnRes.m_iID; + +#if ENABLE_LOGGING + { + CHandShake debughs; + debughs.load_from(reqpkt.m_pcData, reqpkt.getLength()); + LOGC(mglog.Debug) << CONID() << "startConnect: REQ-TIME HIGH. cont/sending HS to peer: " << debughs.show(); + } +#endif + + now = CTimer::getTime(); + m_llLastReqTime = now; + reqpkt.m_iTimeStamp = int32_t(now - m_StartTime); + m_pSndQueue->sendto(serv_addr, reqpkt); + } + else + { + LOGC(mglog.Debug) << "startConnect: LOOP: too early to send - " << tdiff << " < 250000"; + } + + EConnectStatus cst = CONN_CONTINUE; + response.setLength(m_iPayloadSize); + if (m_pRcvQueue->recvfrom(m_SocketID, Ref(response)) > 0) + { + LOGC(mglog.Debug) << CONID() << "startConnect: got response for connect request"; + cst = processConnectResponse(response, &e, true /*synchro*/); + + LOGC(mglog.Debug) << CONID() << "startConnect: response processing result: " + << (cst == CONN_CONTINUE + ? "INDUCED/CONCLUDING" + : cst == CONN_ACCEPT + ? "ACCEPTED" + : cst == CONN_RENDEZVOUS + ? "RENDEZVOUS (HSv5)" + : "REJECTED"); + + // 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*/); + if (cst == CONN_CONTINUE) + continue; + break; + } + + if ( cst != CONN_CONTINUE ) + break; + + // IMPORTANT + // [[using assert(m_pCryptoControl != nullptr)]]; + + // new request/response should be sent out immediately on receving a response + LOGC(mglog.Debug) << "startConnect: REQ-TIME: LOW, should resend request quickly."; + m_llLastReqTime = 0; + + // (if security needed, set the SECURING state) + if (m_iSndCryptoKeyLen > 0) + { + m_pCryptoControl->m_iSndKmState = SRT_KM_S_SECURING; + m_pCryptoControl->m_iSndPeerKmState = SRT_KM_S_SECURING; + m_pCryptoControl->m_iRcvKmState = SRT_KM_S_SECURING; + m_pCryptoControl->m_iRcvPeerKmState = SRT_KM_S_SECURING; + } + + // 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_iPayloadSize 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_iPayloadSize); + + // 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. + + LOGC(mglog.Debug) << "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 - weird + // as it's used in this mode - 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. + // + // Now that this is fixed, the handshake messages from RendezvousQueue + // are sent only when there is a rendezvous mode or non-blocking mode. + createSrtHandshake(Ref(reqpkt), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0); + } + + if ( cst == CONN_REJECT ) + { + e = CUDTException(MJ_SETUP, MN_REJECTED, 0); + break; + } + + if (CTimer::getTime() > ttl) + { + // timeout + e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0); + break; + } + } + + 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_ERROR_REJECT) // connection request rejected + 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) + throw e; + + LOGC(mglog.Debug) << CONID() << "startConnect: handshake exchange succeeded"; + + // Parameters at the end. + LOGC(mglog.Debug) << "startConnect: END. Parameters:" + " mss=" << m_iMSS << + " max-cwnd-size=" << m_dMaxCWndSize << + " cwnd-size=" << m_dCWndSize << + " rcv-rate=" << m_iRcvRate << + " 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 + LOGC(mglog.Debug) << CONID() << "processAsyncConnectResponse: got response for connect request, processing"; + cst = processConnectResponse(pkt, &e, false); + + LOGC(mglog.Debug) << CONID() << "processAsyncConnectResponse: response processing result: " + << ConnectStatusStr(cst); + + return cst; +} + +bool CUDT::processAsyncConnectRequest(EConnectStatus cst, const CPacket& response, const sockaddr* serv_addr) +{ + // Ok, LISTEN UP! + // + // 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_iPayloadSize); + uint64_t now = CTimer::getTime(); + request.m_iTimeStamp = int(now - this->m_StartTime); + + LOGC(mglog.Debug) << "startConnect: REQ-TIME: HIGH. Should prevent too quick responses."; + m_llLastReqTime = now; + // ID = 0, connection request + request.m_iID = !m_bRendezvous ? 0 : m_ConnRes.m_iID; + + if ( cst == CONN_RENDEZVOUS ) + { + LOGC(mglog.Debug) << "processAsyncConnectRequest: passing to processRendezvous"; + cst = processRendezvous(Ref(request), response, serv_addr, false /*asynchro*/); + if (cst == CONN_ACCEPT) + { + LOGC(mglog.Debug) << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. Done."; + return true; + } + + if (cst != CONN_CONTINUE) + { + LOGC(mglog.Error) << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further."; + return false; + } + } + else + { + // (this procedure will be also run for HSv4 rendezvous) + size_t hs_size = m_iPayloadSize; + LOGC(mglog.Debug) << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength(); + if (!createSrtHandshake(Ref(request), Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0)) + { + LOGC(mglog.Error) << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing."; + return false; + } + hs_size = request.getLength(); - response.setLength(m_iPayloadSize); - if (m_pRcvQueue->recvfrom(m_SocketID, response) > 0) - { - if (processConnectResponse(response) <= 0) - break; + LOGC(mglog.Debug) << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType) + << " to socket " << request.m_iID << " size=" << hs_size; + } - // new request/response should be sent out immediately on receving a response - m_llLastReqTime = 0; - } + m_pSndQueue->sendto(serv_addr, request); - if (CTimer::getTime() > ttl) - { - // timeout - e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0); - break; - } - } + return true; // CORRECTLY HANDLED, REMOVE CONNECTOR. +} - delete [] reqdata; - delete [] resdata; +void CUDT::cookieContest() +{ + if (m_SrtHsSide != HSD_DRAW) + return; - 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_ERROR_REJECT) // connection request rejected - 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 ( m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0 ) + { + // 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 reqpkt, const CPacket& response, const sockaddr* serv_addr, bool synchro) +{ + if ( m_RdvState == CHandShake::RDV_CONNECTED ) + { + LOGC(mglog.Debug) << "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 ) + return CONN_REJECT; + + UDTRequestType rsp_type; + bool needs_extension = m_ConnRes.m_iType != 0; // Initial value: received HS has extensions. + bool needs_hsrsp = rendezvousSwitchState(Ref(rsp_type), Ref(needs_extension)); + + // 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; + + if (rsp_type > URQ_FAILURE_TYPES) + { + LOGC(mglog.Debug) << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type); + return CONN_REJECT; + } + + // This must be done before prepareConnectionObjects(). + applyResponseSettings(); + + // This must be done before interpreting and creating HSv5 extensions. + if ( !prepareConnectionObjects(m_ConnRes, m_SrtHsSide, 0)) + { + LOGC(mglog.Debug) << "processRendezvous: rejecting due to problems in prepareConnectionObjects."; + return CONN_REJECT; + } + + // (if security needed, set the SECURING state) + // (don't change it if already in FINE or INITIATED state). + if ((m_RdvState == CHandShake::RDV_WAVING || m_RdvState == CHandShake::RDV_ATTENTION) && m_iSndCryptoKeyLen > 0) + { + m_pCryptoControl->m_iSndKmState = SRT_KM_S_SECURING; + m_pCryptoControl->m_iSndPeerKmState = SRT_KM_S_SECURING; + m_pCryptoControl->m_iRcvKmState = SRT_KM_S_SECURING; + m_pCryptoControl->m_iRcvPeerKmState = SRT_KM_S_SECURING; + } + + // Case 2. + if ( needs_hsrsp ) + { + LOGC(mglog.Debug) << "startConnect: REQ-TIME: LOW. Respond immediately."; + m_llLastReqTime = 0; + // This means that we have received HSREQ extension with the handshake, so we need to interpret + // it and craft the response. + if ( !interpretSrtHandshake(m_ConnRes, response, kmdata, &kmdatasize) ) + { + LOGC(mglog.Debug) << "processRendezvous: rejecting due to problems in interpretSrtHandshake."; + return CONN_REJECT; + } + + // 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; + + LOGC(mglog.Debug) << "processConnectResponse: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize; + + rpkt.setLength(m_iPayloadSize); + if (!createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize)) + { + LOGC(mglog.Debug) << "processRendezvous: rejecting due to problems in createSrtHandshake."; + 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. + LOGC(mglog.Debug) << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension"; + if ( !interpretSrtHandshake(m_ConnRes, response, 0, 0) ) + { + m_ConnReq.m_iReqType = URQ_ERROR_REJECT; + } + // This should be false, make a kinda assert here. + if ( needs_extension ) + { + LOGC(mglog.Fatal) << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS"; + m_ConnReq.m_extension = false; + } + } + + LOGC(mglog.Debug) << 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 ) + LOGC(mglog.Debug) << "... WON'T SEND any response, both sides considered connected"; + else + LOGC(mglog.Debug) << "... 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_iPayloadSize); + // 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. + createSrtHandshake(reqpkt, Ref(m_ConnReq), SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0); + + 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 ) + { + LOGC(mglog.Debug) << "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 ) + { + LOGC(mglog.Debug) << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)"; + return CONN_ACCEPT; + } - if (e.getErrorCode() != 0) - throw e; + 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. + + LOGC(mglog.Debug) << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH."; + uint64_t now = CTimer::getTime(); + m_llLastReqTime = now; + rpkt.m_iTimeStamp = int32_t(now - m_StartTime); + m_pSndQueue->sendto(serv_addr, rpkt); + + return CONN_ACCEPT; + } - LOGC(mglog.Debug) << CONID() << "CUDT::connect: handshake exchange succeeded"; + // the request time must be updated so that the next handshake can be sent out immediately. + LOGC(mglog.Debug) << "startConnect: REQ-TIME: LOW. Respond immediately."; + m_llLastReqTime = 0; + LOGC(mglog.Debug) << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType) << " SENDING response, but consider yourself conencted"; + return CONN_CONTINUE; } -int CUDT::processConnectResponse(const CPacket& response) ATR_NOEXCEPT +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. - // returning -1 means there is an error. - // returning 1 or 2 means the connection is in process and needs more handshake + // 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, it's expected CONCLUSION handshake if (!m_bConnecting) - return -1; + 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. + LOGC(mglog.Debug) << "processConnectResponse: TYPE:" << MessageTypeStr(response.getType(), response.getExtendedType()); + //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. - /* 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 && ( - !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. - && (m_ConnRes.m_iType != UDT_UNDEFINED)) + 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 - goto POST_CONNECT; + //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. + LOGC(mglog.Debug) << CONID() << "processConnectResponse: already connected - pinning in"; + if (hsv5) + { + m_RdvState = CHandShake::RDV_CONNECTED; + } + + return postConnect(response, hsv5, eout, synchro); } if ( !response.isControl(UMSG_HANDSHAKE) ) - return -1; + { + LOGC(mglog.Error) << CONID() << "processConnectResponse: received non-addresed packet not UMSG_HANDSHAKE: " + << MessageTypeStr(response.getType(), response.getExtendedType()); + return CONN_REJECT; + } + + if ( m_ConnRes.load_from(response.m_pcData, response.getLength()) == -1 ) + { + // Handshake data were too small to reach the Handshake structure. Reject. + LOGC(mglog.Error) << CONID() << "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting."; + return CONN_REJECT; + } + + LOGC(mglog.Debug) << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show(); + if ( m_ConnRes.m_iReqType > URQ_FAILURE_TYPES ) + { + return CONN_REJECT; + } - m_ConnRes.deserialize(response.m_pcData, response.getLength()); + 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. + LOGC(mglog.Fatal) << "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) { - // regular connect should NOT communicate with rendezvous connect - // rendezvous connect require 3-way handshake - if (m_ConnRes.m_iReqType == URQ_INDUCTION) - return -1; + // SANITY CHECK: A rendezvous socket should reject any caller requests (it's not a listener) + if (m_ConnRes.m_iReqType == URQ_INDUCTION) + { + LOGC(mglog.Error) << CONID() << "processConnectResponse: Rendezvous-point received INDUCTION handshake (expected WAVEAHAND). Rejecting."; + return CONN_REJECT; + } - if ( m_ConnReq.m_iReqType == URQ_RENDEZVOUS - || m_ConnRes.m_iReqType == URQ_RENDEZVOUS ) - { - 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 1; - } + // 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 ) + { + LOGC(mglog.Debug) << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED."; + return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous(). + } + + LOGC(mglog.Debug) << 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 ) + { + LOGC(mglog.Debug) << 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. + + createCrypter(hsd, false /* unidirectional */); + + 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 + { + LOGC(mglog.Debug) << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand"; + } } else { // set cookie if (m_ConnRes.m_iReqType == URQ_INDUCTION) { - m_ConnReq.m_iReqType = URQ_CONCLUSION; + LOGC(mglog.Debug) << 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 ) + { + // 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; - return 1; + createCrypter(hsd, bidirectional); + + // 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; } } -POST_CONNECT: - // Remove from rendezvous queue - m_pRcvQueue->removeConnector(m_SocketID); + return postConnect(response, false, eout, synchro); +} - // Re-configure according to the negotiated values. - m_iMSS = m_ConnRes.m_iMSS; - m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; - m_iPktSize = m_iMSS - CPacket::UDP_HDR_SIZE; - m_iPayloadSize = m_iPktSize - CPacket::HDR_SIZE; - m_iPeerISN = m_ConnRes.m_iISN; - m_iRcvLastAck = m_ConnRes.m_iISN; +void CUDT::applyResponseSettings() +{ + // Re-configure according to the negotiated values. + m_iMSS = m_ConnRes.m_iMSS; + m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize; + m_iPktSize = m_iMSS - CPacket::UDP_HDR_SIZE; + m_iPayloadSize = m_iPktSize - CPacket::HDR_SIZE; + m_iPeerISN = m_ConnRes.m_iISN; + m_iRcvLastAck = m_ConnRes.m_iISN; #ifdef ENABLE_LOGGING - m_iDebugPrevLastAck = m_iRcvLastAck; + m_iDebugPrevLastAck = m_iRcvLastAck; #endif #ifdef SRT_ENABLE_TLPKTDROP - m_iRcvLastSkipAck = m_iRcvLastAck; + m_iRcvLastSkipAck = m_iRcvLastAck; #endif /* SRT_ENABLE_TLPKTDROP */ - m_iRcvLastAckAck = m_ConnRes.m_iISN; - m_iRcvCurrSeqNo = m_ConnRes.m_iISN - 1; - m_PeerID = m_ConnRes.m_iID; - memcpy(m_piSelfIP, m_ConnRes.m_piPeerIP, 16); + m_iRcvLastAckAck = m_ConnRes.m_iISN; + m_iRcvCurrSeqNo = m_ConnRes.m_iISN - 1; + m_PeerID = m_ConnRes.m_iID; + memcpy(m_piSelfIP, m_ConnRes.m_piPeerIP, 16); + + LOGC(mglog.Debug) << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iPayloadSize + << " mss=" << m_ConnRes.m_iMSS + << " flw=" << m_ConnRes.m_iFlightFlagSize + << " isn=" << m_ConnRes.m_iISN + << " peerID=" << m_ConnRes.m_iID; +} - // Prepare all data structures - try - { - m_pSndBuffer = new CSndBuffer(32, m_iPayloadSize); - 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 (...) - { - // XXX Will cause error in C++11; the NOEXCEPT declaration - // is false in this case. This is probably wrong - the function - // should return -1 and set appropriate "errno". - // The original UDT code contained throw() declaration; this would - // make this instruction resolved to std::unexpected(). - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } +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. - 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; - } + // Remove from rendezvous queue (in this particular case it's + // actually removing the socket that undergoes asynchronous HS processing). + m_pRcvQueue->removeConnector(m_SocketID, synchro); - setupCC(); + // 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); + if ( ok ) + { + ok = interpretSrtHandshake(m_ConnRes, response, 0, 0); + if (!ok && eout) + { + *eout = CUDTException(MJ_SETUP, MN_REJECTED, 0); + } + } + if ( !ok ) + return CONN_REJECT; + } - // And, I am connected too. - m_bConnecting = false; - m_bConnected = true; + // XXX Probably redundant - processSrtMsg_HSRSP should do it in both + // HSv4 and HSv5 modes. + handshakeDone(); - // register this socket for receiving data packets - m_pRNode->m_bOnList = true; - m_pRcvQueue->setNewEntry(this); + 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; + } - // acknowledge the management module. - s_UDTUnited.connect_complete(m_SocketID); + // And, I am connected too. + m_bConnecting = false; + m_bConnected = true; - // acknowledde any waiting epolls to write - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + // register this socket for receiving data packets + m_pRNode->m_bOnList = true; + m_pRcvQueue->setNewEntry(this); - return 0; + // acknowledge the management module. + s_UDTUnited.connect_complete(m_SocketID); + + // acknowledde any waiting epolls to write + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + + return CONN_ACCEPT; +} + +// Rendezvous +bool CUDT::rendezvousSwitchState(ref_t rsptype, ref_t needs_extension) +{ + UDTRequestType req = m_ConnRes.m_iReqType; + bool has_extension = !!m_ConnRes.m_iType; // 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. + + needs_extension = false; + + string reason; + +#if ENABLE_LOGGING + + LOGC(mglog.Debug) << "rendezvousSwitchState: HS: " << m_ConnRes.show(); + + struct LogAtTheEnd + { + CHandShake::RendezvousState ost; + UDTRequestType orq; + const CHandShake::RendezvousState& nst; + const UDTRequestType& nrq; + bool& needext; + string& reason; + LogAtTheEnd(CHandShake::RendezvousState st, UDTRequestType rq, + const CHandShake::RendezvousState& rst, const UDTRequestType& rrq, bool& needx, string& rsn): + ost(st), orq(rq), nst(rst), nrq(rrq), needext(needx), reason(rsn) {} + ~LogAtTheEnd() + { + LOGC(mglog.Debug) << "rendezvousSwitchState: STATE[" + << CHandShake::RdvStateStr(ost) << "->" << CHandShake::RdvStateStr(nst) << "] REQTYPE[" + << RequestTypeStr(orq) << "->" << RequestTypeStr(nrq) << "] " + << (needext ? "HSREQ-ext" : "") << (reason == "" ? string() : "reason:" + reason); + } + } l_logend(m_RdvState, req, m_RdvState, rsptype, needs_extension, reason); + +#endif + + switch (m_RdvState) + { + case CHandShake::RDV_INVALID: return false; + + 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 false; + } + + 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 ) + return true; + return false; + } + + } + 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 false; + } + + 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 (m_ConnRes.m_iType == 0) + { + LOGC(mglog.Debug) << "rendezvousSwitchState: {INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got CONCLUSION, remain in [ATTENTION]"; + rsptype = URQ_CONCLUSION; + return false; + } + m_RdvState = CHandShake::RDV_CONNECTED; + rsptype = URQ_AGREEMENT; + return false; + } + + // LOSER (HSD_RESPONDER): send URQ_CONCLUSION and attach HSRSP extension, then expect URQ_AGREEMENT + if ( hsd == HSD_RESPONDER ) + { + m_RdvState = CHandShake::RDV_INITIATED; + rsptype = URQ_CONCLUSION; + return true; + } + } + + 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 false; + } + + 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; + // This is a conclusion message to call for getting HSREQ, so no extensions. + return false; + } + } + + } + 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 party 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. + LOGC(mglog.Debug) << "rendezvousSwitchState: {INITIATOR}[FINE] CONID() << "TSBPD:DROPSEQ: up to seq=" << CSeqNo::decseq(skiptoseqno) + LOGC(tslog.Note) << self->CONID() << "tsbpd: DROPSEQ: up to seq=" << CSeqNo::decseq(skiptoseqno) << " (" << seqlen << " packets) playable at " << logging::FormatTime(tsbpdtime) << " delayed " << (timediff/1000) << "." << (timediff%1000) << " ms"; @@ -1527,7 +3658,7 @@ void* CUDT::tsbpd(void* param) int seq=0; if ( rdpkt ) seq = rdpkt->getSeqNo(); - LOGC(tslog.Debug) << self->CONID() << "PLAYING PACKET seq=" << seq << " (belated " << ((CTimer::getTime() - tsbpdtime)/1000.0) << "ms)"; + LOGC(tslog.Debug) << self->CONID() << "tsbpd: PLAYING PACKET seq=" << 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 @@ -1558,7 +3689,7 @@ void* CUDT::tsbpd(void* param) if ( rdpkt ) seq = rdpkt->getSeqNo(); uint64_t now = CTimer::getTime(); - LOGC(tslog.Debug) << self->CONID() << "FUTURE PACKET seq=" << seq << " T=" << logging::FormatTime(tsbpdtime) << " - waiting " << ((tsbpdtime - now)/1000.0) << "ms"; + LOGC(tslog.Debug) << self->CONID() << "tsbpd: FUTURE PACKET seq=" << seq << " T=" << logging::FormatTime(tsbpdtime) << " - waiting " << ((tsbpdtime - now)/1000.0) << "ms"; pthread_cond_timedwait(&self->m_RcvTsbPdCond, &self->m_RecvLock, &locktime); THREAD_RESUMED(); } @@ -1574,6 +3705,7 @@ void* CUDT::tsbpd(void* param) * - New buffers ACKed * - Closing the connection */ + LOGC(tslog.Debug) << self->CONID() << "tsbpd: no data, scheduling wakeup at ack"; self->m_bTsbPdAckWakeup = true; THREAD_PAUSED(); pthread_cond_wait(&self->m_RcvTsbPdCond, &self->m_RecvLock); @@ -1582,14 +3714,76 @@ void* CUDT::tsbpd(void* param) } CGuard::leaveCS(self->m_RecvLock); THREAD_EXIT(); + LOGC(tslog.Debug) << self->CONID() << "tsbpd: EXITING"; return NULL; } -#endif /* SRT_ENABLE_TSBPD */ -void CUDT::acceptAndRespond(const sockaddr* peer, CHandShake* hs) +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) + { + LOGC(mglog.Debug) << "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 acceptor and caller always donor. + } + else + { + hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER; + } + } + + try + { + m_pSndBuffer = new CSndBuffer(32, m_iPayloadSize); + 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); + } + return false; + } + + if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy) + return false; + + return setupCC(); +} + +void CUDT::acceptAndRespond(const sockaddr* peer, CHandShake* hs, const CPacket& hspkt) { + LOGC(mglog.Debug) << "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; @@ -1631,25 +3825,25 @@ void CUDT::acceptAndRespond(const sockaddr* peer, CHandShake* hs) // 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); m_iPktSize = m_iMSS - CPacket::UDP_HDR_SIZE; m_iPayloadSize = m_iPktSize - CPacket::HDR_SIZE; + LOGC(mglog.Debug) << "acceptAndRespond: PAYLOAD SIZE: " << m_iPayloadSize; // Prepare all structures - try - { - m_pSndBuffer = new CSndBuffer(32, m_iPayloadSize); - m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_iRcvBufSize); - m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2); - m_pRcvLossList = new CRcvLossList(m_iFlightFlagSize); - } - catch (...) - { - throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); - } + prepareConnectionObjects(*hs, HSD_DRAW, 0); + // Since now you can use m_pCryptoControl CInfoBlock ib; ib.m_iIPversion = m_iIPversion; @@ -1660,7 +3854,24 @@ void CUDT::acceptAndRespond(const sockaddr* peer, CHandShake* hs) m_iBandwidth = ib.m_iBandwidth; } - setupCC(); + // 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) ) + { + LOGC(mglog.Debug) << "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 = URQ_ERROR_REJECT; + 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)); @@ -1673,94 +3884,223 @@ void CUDT::acceptAndRespond(const sockaddr* peer, CHandShake* hs) 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_iPayloadSize; + // Allocate the maximum possible memory for an SRT payload. + // This is a maximum you can send once. CPacket response; - int size = CHandShake::m_iContentSize; - char* buffer = new char[size]; - hs->serialize(buffer, size); - response.pack(UMSG_HANDSHAKE, NULL, buffer, size); - response.m_iID = m_PeerID; -#ifdef SRT_ENABLE_CTRLTSTAMP - response.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); -#endif - m_pSndQueue->sendto(peer, response); - - delete [] buffer; -} + response.setControl(UMSG_HANDSHAKE); + response.allocate(size); -void CUDT::setupCC(void) -{ - m_pCC = m_pCCFactory->create(); - m_pSRTCC = dynamic_cast(m_pCC); // will become NULL if CCC class is not CSRTCC. - if ( !m_pSRTCC ) - throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); - m_pCC->m_UDT = m_SocketID; - m_pCC->setMSS(m_iMSS); - m_pCC->setMaxCWndSize(m_iFlowWindowSize); - m_pCC->setSndCurrSeqNo(m_iSndCurrSeqNo); - m_pCC->setRcvRate(m_iDeliveryRate); - m_pCC->setRTT(m_iRTT); - m_pCC->setBandwidth(m_iBandwidth); -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_llMaxBW != 0) + // This will serialize the handshake according to its current form. + LOGC(mglog.Debug) << "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)) { - m_pSRTCC->setMaxBW(m_llMaxBW); //Bytes/sec -#ifdef SRT_ENABLE_INPUTRATE - m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling + throw CUDTException(MJ_SETUP, MN_REJECTED, 0); } - else if (m_llInputBW != 0) - { - m_pSRTCC->setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec - m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling - } - else + +#if ENABLE_LOGGING { - m_pSndBuffer->setInputRateSmpPeriod(500000); //Enable input rate sampling (fast start) -#endif /* SRT_ENABLE_INPUTRATE */ + // 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()); + LOGC(mglog.Debug) << 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 + m_pSndQueue->sendto(peer, response); +} - m_pSRTCC->setCryptoSecret(&m_CryptoSecret); - if (m_bDataSender || m_bTwoWayData) - m_pSRTCC->setSndCryptoKeylen(m_iSndCryptoKeyLen); -#ifdef SRT_ENABLE_TSBPD - if (m_bDataSender || m_bTwoWayData) - m_pSRTCC->setSndTsbPdMode(m_bTsbPdMode); - m_pSRTCC->setTsbPdDelay(m_iTsbPdDelay); -#ifdef SRT_ENABLE_TLPKTDROP - /* - * Set SRT handshake receiver packet drop flag - */ -// if (!m_bDataSender) - m_pSRTCC->setRcvTLPktDrop(m_bTLPktDrop); -#endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ +// 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; + + 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. + if ( bidirectional ) + m_bTwoWayData = true; + + m_pCryptoControl->setCryptoSecret(m_CryptoSecret); + + if ( bidirectional || m_bDataSender ) + { + m_pCryptoControl->setCryptoKeylen(m_iSndCryptoKeyLen); + } + + return m_pCryptoControl->init(side, bidirectional); +} + +// This function was earlier part of the congestion control class +// intitialization as long as it existed. This functionality is +// now in CUDT class, and this is the initialization part for all +// of the congestion control runtime states. +bool CUDT::setupCC() +{ + // XXX Not sure about that. May happen that AGENT wants + // tsbpd mode, but PEER doesn't, even in bidirectional mode. + // This way, the reception side should get precedense. + //if (bidirectional || m_bDataSender || m_bTwoWayData) + // m_bPeerTsbPd = m_bOPT_TsbPd; + #ifdef SRT_ENABLE_NAKREPORT - /* - * Enable receiver's Periodic NAK Reports - */ - m_pSRTCC->setRcvNakReport(m_bRcvNakReport); - m_ullMinNakInt = m_iMinNakInterval * m_ullCPUFrequency; + /* + * Enable receiver's Periodic NAK Reports + */ + m_ullMinNakInt = m_iMinNakInterval * m_ullCPUFrequency; #endif /* SRT_ENABLE_NAKREPORT */ -#endif /* SRT_ENABLE_SRTCC_EMB */ - m_pCC->init(); + //m_pCryptoControl->m_iMSS = m_iMSS; + m_dMaxCWndSize = m_iFlowWindowSize; + //m_pCryptoControl->m_iSndCurrSeqNo = m_iSndCurrSeqNo; + m_iRcvRate = m_iDeliveryRate; + //m_pCryptoControl->m_iRTT = m_iRTT; + //m_pCryptoControl->m_iBandwidth = m_iBandwidth; + + + LOGC(mglog.Debug) << "setupCC: setting parameters: mss=" << m_iMSS + << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize + << " rcvrate=" << m_iDeliveryRate + << " rtt=" << m_iRTT + << " bw=" << m_iBandwidth; + + if (m_llMaxBW != 0) + { + setMaxBW(m_llMaxBW); //Bytes/sec + m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling + } + else if (m_llInputBW != 0) + { + setMaxBW((m_llInputBW * (100 + m_iOverheadBW))/100); //Bytes/sec + m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling + } + else + { + m_pSndBuffer->setInputRateSmpPeriod(500000); //Enable input rate sampling (fast start) + } + + m_ullInterval = (uint64_t)(m_dPktSndPeriod * m_ullCPUFrequency); + m_dCongestionWindow = m_dCWndSize; + + return true; +} + +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 ( m_SndHsRetryCnt == 0 || !m_bDataSender ) + 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 ( !isTsbPd() // tsbpd off = no HSREQ required + || m_SndHsRetryCnt <= 0 // expired (actually sanity check, theoretically impossible to be <0) + || timebase > now ) // too early + 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_SndHsRetryCnt < SRT_MAX_HSRETRY+1) + { + return; + } + + m_SndHsRetryCnt--; + m_SndHsLastTime = now; + sendSrtMsg(SRT_CMD_HSREQ); +} + +void CUDT::checkSndTimers(Whether2RegenKm regen) +{ + if (!m_bDataSender) + return; + + considerLegacySrtHandshake(m_SndHsLastTime + m_iRTT*3/2); + m_pCryptoControl->sendKeysToPeer(regen); + + /* + * Readjust the max SndPeriod onACK (and onTimeout) + */ + updatePktSndPeriod(); +} + +void CUDT::setMaxBW(int64_t maxbw) +{ + m_llSndMaxBW = maxbw > 0 ? maxbw : BW_INFINITE; + updatePktSndPeriod(); + +#ifdef SRT_ENABLE_NOCWND + /* + * UDT default flow control should not trigger under normal SRT operation + * UDT stops sending if the number of packets in transit (not acknowledged) + * is larger than the congestion window. + * Up to SRT 1.0.6, this value was set at 1000 pkts, which may be insufficient + * for satellite links with ~1000 msec RTT and high bit rate. + */ + // XXX Consider making this a socket option. + m_dCWndSize = m_dMaxCWndSize; +#else + m_dCWndSize = 1000; +#endif +} - m_ullInterval = (uint64_t)(m_pCC->m_dPktSndPeriod * m_ullCPUFrequency); - m_dCongestionWindow = m_pCC->m_dCWndSize; - return; +void CUDT::addressAndSend(CPacket& pkt) +{ + pkt.m_iID = m_PeerID; + pkt.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); + m_pSndQueue->sendto(m_pPeerAddr, pkt); } + void CUDT::close() { + // NOTE: this function is called from within the garbage collector thread. + if (!m_bOpened) { return; } + LOGC(mglog.Debug) << CONID() << " - closing socket:"; + if (m_Linger.l_onoff != 0) { uint64_t entertime = CTimer::getTime(); + LOGC(mglog.Debug) << CONID() << " ... (linger)"; while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) && (CTimer::getTime() - entertime < m_Linger.l_linger * 1000000ULL)) { // linger has been checked by previous close() call and has expired @@ -1791,7 +4131,6 @@ void CUDT::close() if (m_bConnected) m_pSndQueue->m_pSndUList->remove(this); -#ifdef HAI_PATCH /* * update_events below useless * removing usock for EPolls right after (remove_usocks) clears it (in other HAI patch). @@ -1799,7 +4138,6 @@ void CUDT::close() * 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. */ -#endif /* HAI_PATCH */ // 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 @@ -1812,6 +4150,7 @@ void CUDT::close() { } + // XXX What's this, could any of the above actions make it !m_bOpened? if (!m_bOpened) { return; @@ -1820,11 +4159,15 @@ void CUDT::close() // Inform the threads handler to stop. m_bClosing = true; + LOGC(mglog.Debug) << CONID() << "CLOSING STATE. Acquiring connection lock"; + CGuard cg(m_ConnectionLock); // Signal the sender and recver if they are waiting for data. releaseSynch(); + LOGC(mglog.Debug) << CONID() << "CLOSING, removing from listener/connector"; + if (m_bListening) { m_bListening = false; @@ -1832,15 +4175,18 @@ void CUDT::close() } else if (m_bConnecting) { - m_pRcvQueue->removeConnector(m_SocketID); + m_pRcvQueue->removeConnector(m_SocketID); } if (m_bConnected) { if (!m_bShutdown) - sendCtrl(UMSG_SHUTDOWN); + { + LOGC(mglog.Debug) << CONID() << "CLOSING - sending SHUTDOWN to the peer"; + sendCtrl(UMSG_SHUTDOWN); + } - m_pCC->close(); + m_pCryptoControl->close(); // Store current connection information. CInfoBlock ib; @@ -1853,18 +4199,34 @@ void CUDT::close() m_bConnected = false; } + if ( m_bTsbPd && !pthread_equal(m_RcvTsbPdThread, pthread_t())) + { + LOGC(mglog.Debug) << "CLOSING, joining TSBPD thread..."; + void* retval; + int ret = pthread_join(m_RcvTsbPdThread, &retval); + LOGC(mglog.Debug) << "... " << (ret == 0 ? "SUCCEEDED" : "FAILED"); + } + + LOGC(mglog.Debug) << "CLOSING, joining send/receive threads"; + // waiting all send and recv calls to stop CGuard sendguard(m_SendLock); CGuard recvguard(m_RecvLock); -#ifdef SRT_ENABLE_SRTCC_EMB CGuard::enterCS(m_AckLock); - /* Release crypto context under AckLock in cast decrypt is in progress */ - if (m_pSRTCC) - m_pSRTCC->freeCryptoCtx(); + /* Release CCryptoControl internals (crypto context) under AckLock in case decrypt is in progress */ + m_pCryptoControl.reset(); CGuard::leaveCS(m_AckLock); -#endif /* SRT_ENABLE_SRTCC_EMB */ + m_lSrtVersion = SRT_DEF_VERSION; + m_lPeerSrtVersion = SRT_VERSION_UNK; + m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1; + m_ullRcvPeerStartTime = 0; + + LOGC(mglog.Debug) << "CLOSING %" << m_SocketID << " - sync signal"; + //pthread_mutex_lock(&m_CloseSynchLock); + pthread_cond_broadcast(&m_CloseSynchCond); + //pthread_mutex_unlock(&m_CloseSynchLock); // CLOSED. m_bOpened = false; } @@ -1984,7 +4346,6 @@ int CUDT::recv(char* data, int len) CGuard recvguard(m_RecvLock); -#ifdef SRT_ENABLE_TSBPD if (!m_pRcvBuffer->isRcvDataReady()) { if (!m_bSynRecving) @@ -2024,42 +4385,6 @@ int CUDT::recv(char* data, int len) } } -#else /* SRT_ENABLE_TSBPD */ - if (!m_pRcvBuffer->isRcvDataReady()) - { - if (!m_bSynRecving) - { - throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); - } - else - { - pthread_mutex_lock(&m_RecvDataLock); - if (m_iRcvTimeOut < 0) - { - while (!m_bBroken && m_bConnected && !m_bClosing && !m_pRcvBuffer->isRcvDataReady()) - { - pthread_cond_wait(&m_RecvDataCond, &m_RecvDataLock); - } - } - else - { - uint64_t exptime = CTimer::getTime() + m_iRcvTimeOut * 1000; - timespec locktime; - locktime.tv_sec = exptime / 1000000; - locktime.tv_nsec = (exptime % 1000000) * 1000; - - while (!m_bBroken && m_bConnected && !m_bClosing && !m_pRcvBuffer->isRcvDataReady()) - { - pthread_cond_timedwait(&m_RecvDataCond, &m_RecvDataLock, &locktime); - if (CTimer::getTime() >= exptime) - break; - } - } - pthread_mutex_unlock(&m_RecvDataLock); - } - } -#endif /* SRT_ENABLE_TSBPD */ - // throw an exception if not connected if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); @@ -2068,14 +4393,12 @@ int CUDT::recv(char* data, int len) int res = m_pRcvBuffer->readBuffer(data, len); -#ifdef SRT_ENABLE_TSBPD /* Kick TsbPd thread to schedule next wakeup (if running) */ - if (m_bTsbPdRcv) + if (m_bTsbPd) { LOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); pthread_cond_signal(&m_RcvTsbPdCond); } -#endif /* SRT_ENABLE_TSBPD */ if (!m_pRcvBuffer->isRcvDataReady()) @@ -2132,7 +4455,7 @@ int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) } #if defined(SRT_ENABLE_TLPKTDROP) || defined(SRT_ENABLE_ECN) - if (m_bTLPktDropSnd) + if (m_bPeerTLPktDrop) { int bytes, timespan; m_pSndBuffer->getCurrBufSize(bytes, timespan); @@ -2145,9 +4468,9 @@ int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) // picture rate would be useful in auto SRT setting for min latency #define SRT_TLPKTDROP_MINTHRESHOLD 1000 // (msec) // XXX static const uint32_t SRT_TLPKTDROP_MINTHRESHOLD = 1000; // (msec) - // XXX int msecThreshold = std::max(m_SndTsbPdDelay, SRT_TLPKTDROP_MINTHRESHOLD) + (2*m_iSYNInterval/1000); - int msecThreshold = (m_SndTsbPdDelay > SRT_TLPKTDROP_MINTHRESHOLD ? m_SndTsbPdDelay : SRT_TLPKTDROP_MINTHRESHOLD) - + (2 * m_iSYNInterval / 1000); + // XXX int msecThreshold = std::max(m_iPeerTsbPdDelay, SRT_TLPKTDROP_MINTHRESHOLD) + (2*CPacket::SYN_INTERVAL/1000); + int msecThreshold = (m_iPeerTsbPdDelay > SRT_TLPKTDROP_MINTHRESHOLD ? m_iPeerTsbPdDelay : SRT_TLPKTDROP_MINTHRESHOLD) + + (2 * CPacket::SYN_INTERVAL / 1000); if (timespan > msecThreshold) { // protect packet retransmission @@ -2181,7 +4504,7 @@ int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) CGuard::leaveCS(m_AckLock); } else #endif /* SRT_ENABLE_TLPKTDROP */ - if ((uint32_t)timespan > (m_SndTsbPdDelay/2)) + if (timespan > (m_iPeerTsbPdDelay/2)) { LOGC(mglog.Debug).form("cong, NOW: %llu, BYTES %d, TMSPAN %d", (unsigned long long)CTimer::getTime(), bytes, timespan); bCongestion = true; @@ -2223,7 +4546,6 @@ int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) else if (!m_bConnected) throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); } -#ifdef HAI_PATCH /* * 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, @@ -2235,21 +4557,11 @@ int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) if (m_iSndTimeOut >= 0) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + // XXX Not sure if this was intended: + // The 'len' exceeds the bytes left in the send buffer... + // ... so we do nothing and return success??? return 0; } -#else - } - - if ((m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iPayloadSize < len) - { - if (m_iSndTimeOut >= 0) - throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); - - // XXX Not sure if this was intended: - // The 'len' exceeds the bytes left in the send buffer... - // ... so we do nothing and return success??? - return 0; -#endif } // record total time used for sending @@ -2296,8 +4608,6 @@ int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) return len; } -#ifdef SRT_ENABLE_TSBPD - int CUDT::recvmsg(char* data, int len) { #ifdef SRT_ENABLE_SRCTIMESTAMP @@ -2338,7 +4648,7 @@ int CUDT::recvmsg(char* data, int len, uint64_t& srctime) int res = m_pRcvBuffer->readMsg(data, len); /* Kick TsbPd thread to schedule next wakeup (if running) */ - if (m_bTsbPdRcv) + if (m_bTsbPd) pthread_cond_signal(&m_RcvTsbPdCond); if (!m_pRcvBuffer->isRcvDataReady()) @@ -2366,7 +4676,7 @@ int CUDT::recvmsg(char* data, int len, uint64_t& srctime) // read is not available any more // Kick TsbPd thread to schedule next wakeup (if running) - if (m_bTsbPdRcv) + if (m_bTsbPd) pthread_cond_signal(&m_RcvTsbPdCond); // Shut up EPoll if no more messages in non-blocking mode @@ -2378,7 +4688,7 @@ int CUDT::recvmsg(char* data, int len, uint64_t& srctime) if (!m_pRcvBuffer->isRcvDataReady()) { // Kick TsbPd thread to schedule next wakeup (if running) - if (m_bTsbPdRcv) + if (m_bTsbPd) pthread_cond_signal(&m_RcvTsbPdCond); // Shut up EPoll if no more messages in non-blocking mode @@ -2402,7 +4712,7 @@ int CUDT::recvmsg(char* data, int len, uint64_t& srctime) if (!m_bBroken && m_bConnected && !m_bClosing && !timeout && (!m_pRcvBuffer->isRcvDataReady())) { /* Kick TsbPd thread to schedule next wakeup (if running) */ - if (m_bTsbPdRcv) + if (m_bTsbPd) { LOGP(tslog.Debug, "recvmsg: KICK tsbpd()"); pthread_cond_signal(&m_RcvTsbPdCond); @@ -2419,7 +4729,7 @@ int CUDT::recvmsg(char* data, int len, uint64_t& srctime) { if (!(m_iRcvTimeOut < 0)) timeout = true; - LOGP(tslog.Debug, "recvmsg: DATA COND: expired -- trying to get data anyway"); + LOGP(tslog.Debug, "recvmsg: DATA COND: EXPIRED -- trying to get data anyway"); } else { @@ -2455,138 +4765,40 @@ int CUDT::recvmsg(char* data, int len, uint64_t& srctime) if (!m_pRcvBuffer->isRcvDataReady()) { - // read is not available any more - - // Kick TsbPd thread to schedule next wakeup (if running) - if (m_bTsbPdRcv) - { - LOGP(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); - } - - /* XXX DEBUG STUFF - enable when required - { - char ptrn [] = "RECVMSG/EXIT RES ? RCVTIMEOUT "; - char chartribool [3] = { '-', '0', '+' }; - int pos [] = { 17, 29 }; - ptrn[pos[0]] = chartribool[int(res >= 0) + int(res > 0)]; - sprintf(ptrn + pos[1], "%d\n", m_iRcvTimeOut); - fputs(ptrn, stderr); - } - // */ - - if ((res <= 0) && (m_iRcvTimeOut >= 0)) - throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); - - return res; -} - -#else /* SRT_ENABLE_TSBPD */ - -int CUDT::recvmsg(char* data, int len) -{ - if (m_iSockType == UDT_STREAM) - throw CUDTException(MJ_NOTSUP, MN_ISSTREAM, 0); - - // throw an exception if not connected - if (!m_bConnected) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - - if (len <= 0) - return 0; - - CGuard recvguard(m_RecvLock); - - if (m_bBroken || m_bClosing) - { - int res = m_pRcvBuffer->readMsg(data, len); - - if (m_pRcvBuffer->getRcvMsgNum() <= 0) - { - // read is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); - } - - if (res == 0) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - else - return res; - } - - if (!m_bSynRecving) - { - int res = m_pRcvBuffer->readMsg(data, len); -#ifdef HAI_PATCH // Shut up EPoll if no more messages in non-blocking mode - if (res == 0) - { - // read is not available any more - 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->getRcvMsgNum() <= 0) - { - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); - } - return res; - } -#else /* HAI_PATCH */ - if (res == 0) - throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); - else - return res; -#endif /* HAI_PATCH */ - } - - int res = 0; - bool timeout = false; - - do - { - pthread_mutex_lock(&m_RecvDataLock); - - if (m_iRcvTimeOut < 0) - { - while (!m_bBroken && m_bConnected && !m_bClosing && ((res = m_pRcvBuffer->readMsg(data, len == 0)))) - pthread_cond_wait(&m_RecvDataCond, &m_RecvDataLock); - } - else - { - uint64_t exptime = CTimer::getTime() + m_iRcvTimeOut * 1000ULL; - timespec locktime; - - locktime.tv_sec = exptime / 1000000; - locktime.tv_nsec = (exptime % 1000000) * 1000; + // 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. - if (pthread_cond_timedwait(&m_RecvDataCond, &m_RecvDataLock, &locktime) == ETIMEDOUT) - timeout = true; - res = m_pRcvBuffer->readMsg(data, len); - } - pthread_mutex_unlock(&m_RecvDataLock); + // read is not available any more - if (m_bBroken || m_bClosing) - throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); - else if (!m_bConnected) - throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); - } while ((res == 0) && !timeout); + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPd) + { + LOGP(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); + } - if (m_pRcvBuffer->getRcvMsgNum() <= 0) + /* XXX DEBUG STUFF - enable when required { - // read is not available any more - s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + char ptrn [] = "RECVMSG/EXIT RES ? RCVTIMEOUT "; + char chartribool [3] = { '-', '0', '+' }; + int pos [] = { 17, 29 }; + ptrn[pos[0]] = chartribool[int(res >= 0) + int(res > 0)]; + sprintf(ptrn + pos[1], "%d\n", m_iRcvTimeOut); + fputs(ptrn, stderr); } + // */ if ((res <= 0) && (m_iRcvTimeOut >= 0)) throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); return res; } -#endif /* SRT_ENABLE_TSBPD */ int64_t CUDT::sendfile(fstream& ifs, int64_t& offset, int64_t size, int block) { @@ -2830,7 +5042,6 @@ void CUDT::sample(CPerfMon* perf, bool clear) } } -#ifdef SRT_ENABLE_BSTATS void CUDT::bstats(CBytePerfMon* perf, bool clear) { if (!m_bConnected) @@ -2932,17 +5143,13 @@ void CUDT::bstats(CBytePerfMon* perf, bool clear) perf->pktFlightSize = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) - 1; perf->msRTT = (double)m_iRTT/1000.0; //>new -#ifdef SRT_ENABLE_TSBPD - perf->msSndTsbPdDelay = m_bTsbPdSnd ? m_SndTsbPdDelay : 0; - perf->msRcvTsbPdDelay = m_bTsbPdRcv ? m_RcvTsbPdDelay : 0; -#endif + perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay : 0; + perf->msRcvTsbPdDelay = m_bTsbPd ? m_iTsbPdDelay : 0; perf->byteMSS = m_iMSS; perf->mbpsMaxBW = (double)m_llMaxBW * 8.0/1000000.0; -#ifdef SRT_ENABLE_SRTCC_EMB /* Maintained by CC if auto maxBW (0) */ if (m_llMaxBW == 0) - perf->mbpsMaxBW = (double)m_pSRTCC->m_llSndMaxBW * 8.0/1000000.0; -#endif + perf->mbpsMaxBW = (double)m_llSndMaxBW * 8.0/1000000.0; //< uint32_t availbw = (uint64_t)(m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth); @@ -2950,7 +5157,8 @@ void CUDT::bstats(CBytePerfMon* perf, bool clear) if (pthread_mutex_trylock(&m_ConnectionLock) == 0) { - if (m_pSndBuffer != NULL) { + if (m_pSndBuffer) + { //new> #ifdef SRT_ENABLE_SNDBUFSZ_MAVG perf->pktSndBuf = m_pSndBuffer->getAvgBufSize(perf->byteSndBuf, perf->msSndBuf); @@ -2969,16 +5177,15 @@ void CUDT::bstats(CBytePerfMon* perf, bool clear) //< } - if (m_pRcvBuffer != NULL) { + if (m_pRcvBuffer) + { perf->byteAvailRcvBuf = m_pRcvBuffer->getAvailBufSize() * m_iMSS; //new> - #ifdef SRT_ENABLE_TSBPD #ifdef SRT_ENABLE_RCVBUFSZ_MAVG perf->pktRcvBuf = m_pRcvBuffer->getRcvAvgDataSize(perf->byteRcvBuf, perf->msRcvBuf); #else perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf); #endif - #endif //< } else { perf->byteAvailRcvBuf = 0; @@ -3023,50 +5230,36 @@ void CUDT::bstats(CBytePerfMon* perf, bool clear) m_LastSampleTime = currtime; } } -#endif /* SRT_ENABLE_BSTATS */ void CUDT::CCUpdate() { -#if defined(SRT_ENABLE_SRTCC_EMB) && defined(SRT_ENABLE_INPUTRATE) - if ((m_llMaxBW == 0) //Auto MaxBW - && (m_llInputBW == 0) //No application provided input rate - && (m_pSndBuffer != NULL)) //Internal input rate sampling - { - int period; - int payloadsz; //CC will use its own average payload size - int64_t maxbw = m_pSndBuffer->getInputRate(payloadsz, period); //Auto input rate - - /* - * 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 pCC->setMaxBW with 0 will set maxBW to default (30Mbps) - * and sendrate skyrockets for retransmission. - * Keep previously set maximum in that case (maxbw == 0). - */ - if (maxbw != 0) - m_pSRTCC->setMaxBW((maxbw * (100 + m_iOverheadBW))/100); //Bytes/sec - - if ((m_llSentTotal > 2000) && (period < 5000000)) - m_pSndBuffer->setInputRateSmpPeriod(5000000); //5 sec period after fast start - } - m_ullInterval = (uint64_t)(m_pCC->m_dPktSndPeriod * m_ullCPUFrequency); - m_dCongestionWindow = m_pCC->m_dCWndSize; - -#else /* SRT_ENABLE_SRTCC_EMB && SRT_ENABLE_INPUTRATE */ - m_ullInterval = (uint64_t)(m_pCC->m_dPktSndPeriod * m_ullCPUFrequency); - m_dCongestionWindow = m_pCC->m_dCWndSize; - - if (m_llMaxBW <= 0) - return; - const double minSP = 1000000.0 / (double(m_llMaxBW) / m_iMSS) * m_ullCPUFrequency; + if ((m_llMaxBW == 0) //Auto MaxBW + && (m_llInputBW == 0) //No application provided input rate + && m_pSndBuffer) //Internal input rate sampling + { + int period; + int payloadsz; //CC will use its own average payload size + int64_t maxbw = m_pSndBuffer->getInputRate(payloadsz, period); //Auto input rate + + /* + * 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 setMaxBW with 0 will set maxBW to default (30Mbps) + * and sendrate skyrockets for retransmission. + * Keep previously set maximum in that case (maxbw == 0). + */ + if (maxbw != 0) + setMaxBW((maxbw * (100 + m_iOverheadBW))/100); //Bytes/sec - if (m_ullInterval < (uint64_t)minSP) - m_ullInterval = (uint64_t)minSP; -#endif /* SRT_ENABLE_SRTCC_EMB && SRT_ENABLE_INPUTRATE */ + if ((m_llSentTotal > 2000) && (period < 5000000)) + m_pSndBuffer->setInputRateSmpPeriod(5000000); //5 sec period after fast start + } + m_ullInterval = (uint64_t)(m_dPktSndPeriod * m_ullCPUFrequency); + m_dCongestionWindow = m_dCWndSize; #if 0//debug - static int callcnt = 0; - if (!(callcnt++ % 250)) fprintf(stderr, "SndPeriod=%llu\n", (unsigned long long)m_ullInterval/m_ullCPUFrequency); + static int callcnt = 0; + if (!(callcnt++ % 250)) fprintf(stderr, "SndPeriod=%llu\n", (unsigned long long)m_ullInterval/m_ullCPUFrequency); #endif } @@ -3081,10 +5274,11 @@ void CUDT::initSynch() pthread_mutex_init(&m_RcvLossLock, NULL); pthread_mutex_init(&m_AckLock, NULL); pthread_mutex_init(&m_ConnectionLock, NULL); -#ifdef SRT_ENABLE_TSBPD memset(&m_RcvTsbPdThread, 0, sizeof m_RcvTsbPdThread); pthread_cond_init(&m_RcvTsbPdCond, NULL); -#endif /* SRT_ENABLE_TSBPD */ + + pthread_mutex_init(&m_CloseSynchLock, NULL); + pthread_cond_init(&m_CloseSynchCond, NULL); } void CUDT::destroySynch() @@ -3098,9 +5292,10 @@ void CUDT::destroySynch() pthread_mutex_destroy(&m_RcvLossLock); pthread_mutex_destroy(&m_AckLock); pthread_mutex_destroy(&m_ConnectionLock); -#ifdef SRT_ENABLE_TSBPD pthread_cond_destroy(&m_RcvTsbPdCond); -#endif /* SRT_ENABLE_TSBPD */ + + pthread_mutex_destroy(&m_CloseSynchLock); + pthread_cond_destroy(&m_CloseSynchCond); } void CUDT::releaseSynch() @@ -3117,7 +5312,6 @@ void CUDT::releaseSynch() pthread_cond_signal(&m_RecvDataCond); pthread_mutex_unlock(&m_RecvDataLock); -#ifdef SRT_ENABLE_TSBPD pthread_mutex_lock(&m_RecvLock); pthread_cond_signal(&m_RcvTsbPdCond); pthread_mutex_unlock(&m_RecvLock); @@ -3126,7 +5320,6 @@ void CUDT::releaseSynch() pthread_join(m_RcvTsbPdThread, NULL); m_RcvTsbPdThread = pthread_t(); } -#endif pthread_mutex_lock(&m_RecvLock); pthread_mutex_unlock(&m_RecvLock); } @@ -3169,9 +5362,7 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size uint64_t currtime; CTimer::rdtsc(currtime); -#ifdef SRT_ENABLE_CTRLTSTAMP ctrlpkt.m_iTimeStamp = int(currtime/m_ullCPUFrequency - m_StartTime); -#endif int nbsent = 0; int local_prevack = 0; @@ -3255,8 +5446,7 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size // will signal m_RecvDataCond when there's time to play particular // data packet. -#ifdef SRT_ENABLE_TSBPD - if (m_bTsbPdRcv) + if (m_bTsbPd) { /* Newly acknowledged data, signal TsbPD thread */ pthread_mutex_lock(&m_RecvLock); @@ -3265,7 +5455,6 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size pthread_mutex_unlock(&m_RecvLock); } else -#endif /* SRT_ENABLE_TSBPD */ { if (m_bSynRecving) { @@ -3306,7 +5495,7 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size // Send out the ACK only if has not been received by the sender before if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0) { - // NOTE: SRT_ENABLE_BSTATS turns on extra fields above size 6 + // NOTE: The BSTATS feature turns on extra fields above size 6 // also known as ACKD_TOTAL_SIZE_VER100. int32_t data[ACKD_TOTAL_SIZE]; @@ -3323,18 +5512,14 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size if (currtime - m_ullLastAckTime > m_ullSYNInt) { -#ifdef SRT_ENABLE_BSTATS int rcvRate; - int version = 0; + uint32_t version = 0; int ctrlsz = ACKD_TOTAL_SIZE_VER100 * ACKD_FIELD_SIZE; // Minimum required size data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed(rcvRate); data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); -#ifdef SRT_ENABLE_SRTCC_EMB - if (m_pSRTCC) - version = m_pSRTCC->getPeerSrtVersion(); -#endif /* SRT_ENABLE_SRTCC_EMB */ + version = m_lPeerSrtVersion; //>>Patch while incompatible (1.0.2) receiver floating around if ( version == SrtVersion(1, 0, 2) ) { @@ -3349,11 +5534,6 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size } ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ctrlsz); -#else /* SRT_ENABLE_BSTATS */ - data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed(); - data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth(); - ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE); // total size is bandwidth + 1 if !SRT_ENABLE_BSTATS -#endif /* SRT_ENABLE_BSTATS */ CTimer::rdtsc(m_ullLastAckTime); } else @@ -3362,9 +5542,7 @@ void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size } ctrlpkt.m_iID = m_PeerID; -#ifdef SRT_ENABLE_CTRLTSTAMP ctrlpkt.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); -#endif /* SRT_ENABLE_CTRLTSTAMP */ nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); DebugAck(local_prevack, ack, CONID()); @@ -3519,7 +5697,7 @@ void CUDT::processCtrl(CPacket& ctrlpkt) bool using_rexmit_flag = m_bPeerRexmitFlag; LOGC(mglog.Debug) << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" - << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") SID=" << ctrlpkt.m_iID; + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID; switch (ctrlpkt.getType()) { @@ -3553,7 +5731,7 @@ void CUDT::processCtrl(CPacket& ctrlpkt) // send ACK acknowledgement // number of ACK2 can be much less than number of ACK uint64_t now = CTimer::getTime(); - if ((now - m_ullSndLastAck2Time > (uint64_t)m_iSYNInterval) || (ack == m_iSndLastAck2)) + if ((now - m_ullSndLastAck2Time > (uint64_t)CPacket::SYN_INTERVAL) || (ack == m_iSndLastAck2)) { sendCtrl(UMSG_ACKACK, &ack); m_iSndLastAck2 = ack; @@ -3715,7 +5893,6 @@ void CUDT::processCtrl(CPacket& ctrlpkt) m_iRTTVar = (m_iRTTVar * 3 + abs(rtt - m_iRTT)) >> 2; m_iRTT = (m_iRTT * 7 + rtt) >> 3; - m_pCC->setRTT(m_iRTT); /* Version-dependent fields: * Original UDT (total size: ACKD_TOTAL_SIZE_UDTBASE): @@ -3732,7 +5909,6 @@ void CUDT::processCtrl(CPacket& ctrlpkt) * ACKD_XMRATE */ -#ifdef SRT_ENABLE_BSTATS if (acksize >= ACKD_TOTAL_SIZE_VER101) //was 32 in SRT v1.0.2 { /* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE]) and delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */ @@ -3746,8 +5922,7 @@ void CUDT::processCtrl(CPacket& ctrlpkt) m_iBandwidth = (m_iBandwidth * 7 + ackdata[ACKD_BANDWIDTH]) >> 3; // Update Estimated Bandwidth and packet delivery rate - m_pCC->setRcvRate(m_iDeliveryRate); - m_pCC->setBandwidth(m_iBandwidth); + m_iRcvRate = m_iDeliveryRate; } else if (acksize > ACKD_TOTAL_SIZE_UDTBASE) // This embraces range (...UDTBASE - ...VER100) { @@ -3761,25 +5936,10 @@ void CUDT::processCtrl(CPacket& ctrlpkt) m_iBandwidth = (m_iBandwidth * 7 + ackdata[ACKD_BANDWIDTH]) >> 3; // Update Estimated Bandwidth and packet delivery rate - m_pCC->setRcvRate(m_iDeliveryRate); - m_pCC->setBandwidth(m_iBandwidth); - } -#else /* SRT_ENABLE_BSTATS */ - if (ctrlpkt.getLength() > 16) - { - // Update Estimated Bandwidth and packet delivery rate - if (ackdata[ACKD_RCVSPEED] > 0) - m_iDeliveryRate = (m_iDeliveryRate * 7 + ackdata[ACKD_RCVSPEED]) >> 3; - - if (ackdata[ACKD_BANDWIDTH] > 0) - m_iBandwidth = (m_iBandwidth * 7 + ackdata[ACKD_BANDWIDTH]) >> 3; - - m_pCC->setRcvRate(m_iDeliveryRate); - m_pCC->setBandwidth(m_iBandwidth); + m_iRcvRate = m_iDeliveryRate; // XXX DUPLICATED FIELD? } -#endif /* SRT_ENABLE_BSTATS */ - m_pCC->onACK(ack); + checkSndTimers(REGEN_KM); CCUpdate(); ++ m_iRecvACK; @@ -3805,13 +5965,9 @@ void CUDT::processCtrl(CPacket& ctrlpkt) m_iRTTVar = (m_iRTTVar * 3 + abs(rtt - m_iRTT)) >> 2; m_iRTT = (m_iRTT * 7 + rtt) >> 3; - m_pCC->setRTT(m_iRTT); - -#ifdef SRT_ENABLE_TSBPD CGuard::enterCS(m_RecvLock); m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp()); CGuard::leaveCS(m_RecvLock); -#endif /* SRT_ENABLE_TSBPD */ // update last ACK that has been received by the sender if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0) @@ -3824,7 +5980,6 @@ void CUDT::processCtrl(CPacket& ctrlpkt) { int32_t* losslist = (int32_t *)(ctrlpkt.m_pcData); - m_pCC->onLoss(losslist, ctrlpkt.getLength() / 4); CCUpdate(); bool secure = true; @@ -3936,25 +6091,90 @@ void CUDT::processCtrl(CPacket& ctrlpkt) case UMSG_HANDSHAKE: //000 - Handshake { CHandShake req; - req.deserialize(ctrlpkt.m_pcData, ctrlpkt.getLength()); + req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength()); + + LOGC(mglog.Debug) << "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; - char* hs = new char [m_iPayloadSize]; - int hs_size = m_iPayloadSize; - initdata.serialize(hs, hs_size); - sendCtrl(UMSG_HANDSHAKE, NULL, hs, hs_size); - delete [] hs; + 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... + if ( req.m_iType != 0 ) // has SRT extensions + { + LOGC(mglog.Debug) << "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; + initdata.m_iReqType = URQ_ERROR_INVALID; + } + else + { + initdata.m_extension = true; + } + } + else + { + LOGC(mglog.Debug) << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType); + } + } + else + { + initdata.m_iVersion = HS_VERSION_UDT4; + } + + initdata.m_extension = have_hsreq; + + LOGC(mglog.Debug) << 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_iPayloadSize); + + // 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; + uint64_t currtime; + CTimer::rdtsc(currtime); + response.m_iTimeStamp = int(currtime/m_ullCPUFrequency - m_StartTime); + int nbsent = m_pSndQueue->sendto(m_pPeerAddr, response); + if (nbsent) + { + uint64_t currtime; + CTimer::rdtsc(currtime); + m_ullLastSndTime = currtime; + } + } + + } + else + { + LOGC(mglog.Debug) << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED."; } break; @@ -3976,13 +6196,9 @@ void CUDT::processCtrl(CPacket& ctrlpkt) break; case UMSG_DROPREQ: //111 - Msg drop request -#ifdef SRT_ENABLE_TSBPD CGuard::enterCS(m_RecvLock); m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag); CGuard::leaveCS(m_RecvLock); -#else /* SRT_ENABLE_TSBPD */ - m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag); -#endif /* SRT_ENABLE_TSBPD */ unlose(*(int32_t*)ctrlpkt.m_pcData, *(int32_t*)(ctrlpkt.m_pcData + 4)); @@ -4023,85 +6239,118 @@ void CUDT::processCtrl(CPacket& ctrlpkt) return; } #endif /* SRT_ENABLE_SND2WAYPROTECT */ - m_pCC->processCustomMsg(&ctrlpkt); // --> m_pSRTCC->processSrtMessage(&ctrlpkt) - CCUpdate(); -#if defined(SRT_ENABLE_TSBPD) && defined(SRT_ENABLE_SRTCC_EMB) - switch(ctrlpkt.getExtendedType()) - { - case SRT_CMD_HSREQ: - { - m_bTsbPdRcv = m_pSRTCC->getRcvTsbPdInfo(); + 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. + // XXX Would be nice to make some assertion for that - you never know + // what message you receive. + updateAfterSrtHandshake(ctrlpkt.getExtendedType(), HS_VERSION_UDT4); + break; - if (m_bTsbPdRcv) - { - /* We are TsbPd receiver */ - // m_bTsbPdRcv = true; // XXX redundant? - m_RcvTsbPdDelay = m_pSRTCC->getRcvTsbPdDelay(); - CGuard::enterCS(m_RecvLock); - m_pRcvBuffer->setRcvTsbPdMode(m_pSRTCC->getRcvPeerStartTime(), m_RcvTsbPdDelay * 1000); - CGuard::leaveCS(m_RecvLock); - - LOGC(mglog.Debug).form( "Set Rcv TsbPd mode: delay=%u.%03u secs", - m_RcvTsbPdDelay/1000, - m_RcvTsbPdDelay%1000); - } - // FIX: The agent that is being handshaken by the peer - // only now knows the flags that have been updated through - // the call of processCustomMsg(). - m_bSndPeerNakReport = m_pSRTCC->getSndPeerNakReport(); - m_bPeerRexmitFlag = m_pSRTCC->getPeerRexmitFlag(); - LOGC(mglog.Debug).form("REXMIT FLAG IS: %d", m_bPeerRexmitFlag); - } - break; - case SRT_CMD_HSRSP: - { - m_bTsbPdSnd = m_pSRTCC->getSndTsbPdInfo(); + default: + break; + } +} - if (m_bTsbPdSnd) - { - /* We are TsbPd sender */ - m_SndTsbPdDelay = m_pSRTCC->getSndPeerTsbPdDelay();// + ((m_iRTT + (4 * m_iRTTVar)) / 1000); +void CUDT::updateSrtRcvSettings() +{ + if (m_bTsbPd) + { + /* We are TsbPd receiver */ + CGuard::enterCS(m_RecvLock); + m_pRcvBuffer->setRcvTsbPdMode(m_ullRcvPeerStartTime, m_iTsbPdDelay * 1000); + CGuard::leaveCS(m_RecvLock); + + LOGC(mglog.Debug).form( "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03u secs", + m_iTsbPdDelay/1000, + m_iTsbPdDelay%1000); + } + else + { + LOGC(mglog.Debug) << "AFTER HS: Rcv TsbPd mode not set"; + } +} + +void CUDT::updateSrtSndSettings() +{ + if (m_bPeerTsbPd) + { + /* We are TsbPd sender */ + //m_iPeerTsbPdDelay = m_pCryptoControl->getSndPeerTsbPdDelay();// + ((m_iRTT + (4 * m_iRTTVar)) / 1000); #if defined(SRT_ENABLE_TLPKTDROP) - /* - * For sender to apply Too-Late Packet Drop - * option (m_bTLPktDrop) must be enabled and receiving peer shall support it - */ - m_bTLPktDropSnd = m_bTLPktDrop && m_pSRTCC->getSndPeerTLPktDrop(); - LOGC(mglog.Debug).form( "Set Snd TsbPd mode %s: delay=%d.%03d secs", - m_bTLPktDropSnd ? "with TLPktDrop" : "without TLPktDrop", - m_SndTsbPdDelay/1000, m_SndTsbPdDelay%1000); + /* + * For sender to apply Too-Late Packet Drop + * option (m_bTLPktDrop) must be enabled and receiving peer shall support it + */ + // XXX HSv5: this is already set from the SRT HS management function. + // m_bPeerTLPktDrop = m_bTLPktDrop && m_pCryptoControl->getSndPeerTLPktDrop(); + LOGC(mglog.Debug).form( "AFTER HS: Set Snd TsbPd mode %s: delay=%d.%03d secs", + m_bPeerTLPktDrop ? "with TLPktDrop" : "without TLPktDrop", + m_iPeerTsbPdDelay/1000, m_iPeerTsbPdDelay%1000); #else /* SRT_ENABLE_TLPKTDROP */ - LOGC(mglog.Debug).form( "Set Snd TsbPd mode %s: delay=%d.%03d secs", - "without TLPktDrop", - m_SndTsbPdDelay/1000, m_SndTsbPdDelay%1000); + LOGC(mglog.Debug).form( "AFTER HS: Set Snd TsbPd mode %s: delay=%d.%03d secs", + "without TLPktDrop", + m_iPeerTsbPdDelay/1000, m_iPeerTsbPdDelay%1000); #endif /* SRT_ENABLE_TLPKTDROP */ - } - m_bSndPeerNakReport = m_pSRTCC->getSndPeerNakReport(); - m_bPeerRexmitFlag = m_pSRTCC->getPeerRexmitFlag(); - LOGC(mglog.Debug).form("REXMIT FLAG IS: %d", m_bPeerRexmitFlag); - } - break; - default: - break; - } -#endif /* SRT_ENABLE_TSBPD */ + } + else + { + LOGC(mglog.Debug) << "AFTER HS: Snd TsbPd mode not set"; + } +} - break; +void CUDT::updateAfterSrtHandshake(int srt_cmd, int hsv) +{ + CCUpdate(); - default: - break; - } + 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::packData(CPacket& packet, uint64_t& ts) { int payload = 0; bool probe = false; -#ifdef SRT_ENABLE_TSBPD uint64_t origintime = 0; -#endif /* SRT_ENABLE_TSBPD */ - int kflg = 0; + int kflg = EK_NOENC; uint64_t entertime; CTimer::rdtsc(entertime); @@ -4148,11 +6397,7 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) int msglen; -#ifdef SRT_ENABLE_TSBPD payload = m_pSndBuffer->readData(&(packet.m_pcData), offset, packet.m_iMsgNo, origintime, msglen); -#else /* SRT_ENABLE_TSBPD */ - payload = m_pSndBuffer->readData(&(packet.m_pcData), offset, packet.m_iMsgNo, msglen); -#endif /* SRT_ENABLE_TSBPD */ if (-1 == payload) { @@ -4182,10 +6427,8 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) ++ m_iTraceRetrans; ++ m_iRetransTotal; -#ifdef SRT_ENABLE_BSTATS m_ullTraceBytesRetrans += payload; m_ullBytesRetransTotal += payload; -#endif //* @@ -4206,17 +6449,19 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) // check congestion/flow window limit int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow)); - if (cwnd >= CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo))) + int seqdiff = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)); + if (cwnd >= seqdiff) { - kflg = m_pSRTCC->getSndCryptoFlags(); -#ifdef SRT_ENABLE_TSBPD + // 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(); if (0 != (payload = m_pSndBuffer->readData(&(packet.m_pcData), packet.m_iMsgNo, origintime, kflg))) -#else - if (0 != (payload = m_pSndBuffer->readData(&(packet.m_pcData), packet.m_iMsgNo, kflg))) -#endif { m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo); - m_pCC->setSndCurrSeqNo(m_iSndCurrSeqNo); + //m_pCryptoControl->m_iSndCurrSeqNo = m_iSndCurrSeqNo; packet.m_iSeqNo = m_iSndCurrSeqNo; @@ -4234,8 +6479,8 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) } else { - LOGC(dlog.Debug).form( "congested maxbw=%lld cwnd=%d seqlen=%d\n", - (unsigned long long)m_pSRTCC->m_llSndMaxBW, cwnd, CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo))); + LOGC(dlog.Debug) << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow + << ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << seqdiff; m_ullTargetTime = 0; m_ullTimeDiff = 0; ts = 0; @@ -4245,8 +6490,7 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) reason = "normal"; } -#ifdef SRT_ENABLE_TSBPD - if (m_bTsbPdSnd) + if (m_bPeerTsbPd) { /* * When timestamp is carried over in this sending stream from a received stream, @@ -4261,17 +6505,20 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); } else -#endif /* SRT_ENABLE_TSBPD */ + { + packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); + } - packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); packet.m_iID = m_PeerID; packet.setLength(payload); -#ifdef SRT_ENABLE_SRTCC_EMB /* Encrypt if 1st time this packet is sent and crypto is enabled */ if (kflg) { - if (packet.encrypt(m_pSRTCC->getSndCryptoCtx())) + // XXX Encryption flags are already set on the packet before calling this. + // See readData() above. + if (m_pCryptoControl->encrypt(Ref(packet))) + //if (packet.encrypt(m_pCryptoControl->getSndCryptoCtx())) { /* Encryption failed */ //>>Add stats for crypto failure @@ -4288,19 +6535,19 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) << " MSG/FLAGS: " << packet.MessageFlagStr() << ")"; #endif -#endif #ifdef SRT_FIX_KEEPALIVE m_ullLastSndTime = entertime; #endif /* SRT_FIX_KEEPALIVE */ - m_pCC->onPktSent(&packet); + //onPktSent(&packet); + // (expanding the call) + considerLegacySrtHandshake(0); + m_iSndAvgPayloadSize = ((m_iSndAvgPayloadSize * 127) + packet.getLength()) / 128; //m_pSndTimeWindow->onPktSent(packet.m_iTimeStamp); -#ifdef SRT_ENABLE_BSTATS m_ullTraceBytesSent += payload; m_ullBytesSentTotal += payload; -#endif ++ m_llTraceSent; ++ m_llSentTotal; @@ -4308,6 +6555,7 @@ int CUDT::packData(CPacket& packet, uint64_t& ts) { // sends out probing packet pair ts = entertime; + probe = false; } else { @@ -4337,29 +6585,15 @@ int CUDT::processData(CUnit* unit) CPacket& packet = unit->m_Packet; // XXX This should be called (exclusively) here: - //m_pRcvBuffer->addRcvTsbPdDriftSample(packet.getMsgTimeStamp()); -#if SRT_ENABLE_SND2WAYPROTECT - if (m_bDataSender) - { - /* - * SRT 1.1.2 and earlier sender can assert if accepting data that will not be read. - * Ignoring received data. - */ - LOGP(mglog.Error, "Error: receiving data in SRT sender-only side: breaking connection."); - m_bBroken = true; - m_iBrokenCounter = 0; - return -1; - } -#endif /* SRT_ENABLE_SND2WAYPROTECT */ + //m_pRcvBuffer->addLocalTsbPdDriftSample(packet.getMsgTimeStamp()); // Just heard from the peer, reset the expiration count. m_iEXPCount = 1; uint64_t currtime; CTimer::rdtsc(currtime); m_ullLastRspTime = currtime; -#ifdef SRT_ENABLE_TSBPD /* We are receiver, start tsbpd thread if TsbPd is enabled */ - if (m_bTsbPdRcv && pthread_equal(m_RcvTsbPdThread, pthread_t())) + if (m_bTsbPd && pthread_equal(m_RcvTsbPdThread, pthread_t())) { LOGP(mglog.Debug, "Spawning TSBPD thread"); int st = 0; @@ -4370,7 +6604,6 @@ int CUDT::processData(CUnit* unit) if ( st != 0 ) return -1; } -#endif /* SRT_ENABLE_TSBPD */ int pktrexmitflag = m_bPeerRexmitFlag ? (int)packet.getRexmitFlag() : 2; static const string rexmitstat [] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; @@ -4411,11 +6644,11 @@ int CUDT::processData(CUnit* unit) LOGC(dlog.Debug) << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo(); // << "(" << rexmitstat[pktrexmitflag] << rexmit_reason << ")"; - m_pCC->onPktReceived(&packet); + // XXX CCC hook was here! + // onPktReceived(&packet); ++ m_iPktCount; int pktsz = packet.getLength(); -#ifdef SRT_ENABLE_BSTATS // update time information m_RcvTimeWindow.onPktArrival(pktsz); @@ -4427,19 +6660,9 @@ int CUDT::processData(CUnit* unit) m_ullTraceBytesRecv += pktsz; m_ullBytesRecvTotal += pktsz; -#else - m_RcvTimeWindow.onPktArrival(); - - // check if it is probing packet pair - if ((packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) - m_RcvTimeWindow.probe1Arrival(); - else if ((packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 1) - m_RcvTimeWindow.probe2Arrival(); -#endif ++ m_llTraceRecv; ++ m_llRecvTotal; -#ifdef SRT_ENABLE_TSBPD { /* * Start of offset protected section @@ -4447,7 +6670,6 @@ int CUDT::processData(CUnit* unit) * offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData() */ CGuard offsetcg(m_AckLock); -#endif /* SRT_ENABLE_TSBPD */ #ifdef SRT_ENABLE_TLPKTDROP int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, packet.m_iSeqNo); @@ -4489,7 +6711,8 @@ int CUDT::processData(CUnit* unit) LOGC(mglog.Debug) << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << (excessive ? " EXCESSIVE" : " ACCEPTED") - << " (" << exc_type << "/" << rexmitstat[pktrexmitflag] << rexmit_reason << ")"; + << " (" << exc_type << "/" << rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " + << packet.MessageFlagStr(); if ( excessive ) { @@ -4498,8 +6721,9 @@ int CUDT::processData(CUnit* unit) if (packet.getMsgCryptoFlags()) { -#ifdef SRT_ENABLE_SRTCC_EMB - EncryptionStatus rc = m_pSRTCC ? packet.decrypt(m_pSRTCC->getRcvCryptoCtx()) : ENCS_NOTSUP; + // Crypto should be already created during connection process, + // this is rather a kinda sanity check. + EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt(Ref(packet)) : ENCS_NOTSUP; if ( rc != ENCS_CLEAR ) { /* @@ -4513,10 +6737,8 @@ int CUDT::processData(CUnit* unit) m_iRcvUndecryptTotal += 1; m_ullRcvBytesUndecryptTotal += pktsz; } -#endif /* SRT_ENABLE_SRTCC_EMB */ } -#ifdef SRT_ENABLE_TSBPD } /* End of offsetcg */ if (m_bClosing) { @@ -4530,7 +6752,6 @@ int CUDT::processData(CUnit* unit) */ return(-1); } -#endif /* SRT_ENABLE_TSBPD */ #if SRT_BELATED_LOSSREPORT // If the peer doesn't understand REXMIT flag, send rexmit request @@ -4553,55 +6774,51 @@ int CUDT::processData(CUnit* unit) // Loss detection. if (CSeqNo::seqcmp(packet.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) { - { - CGuard lg(m_RcvLossLock); - int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); - int32_t seqhi = CSeqNo::decseq(packet.m_iSeqNo); - // If loss found, insert them to the receiver loss list - m_pRcvLossList->insert(seqlo, seqhi); + { + CGuard lg(m_RcvLossLock); + int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo); + int32_t seqhi = CSeqNo::decseq(packet.m_iSeqNo); + // If loss found, insert them to the receiver loss list + m_pRcvLossList->insert(seqlo, seqhi); #if SRT_BELATED_LOSSREPORT - if ( initial_loss_ttl ) - { - // pack loss list for (possibly belated) NAK - // The LOSSREPORT will be sent in a while. - m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl)); - LOGC(mglog.Debug).form("added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi, 1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl); - } - else + if ( initial_loss_ttl ) + { + // pack loss list for (possibly belated) NAK + // The LOSSREPORT will be sent in a while. + m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl)); + LOGC(mglog.Debug).form("added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi, 1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl); + } + else #endif - { - // old code; run immediately when tolerance = 0 - // or this feature isn't used because of the peer - int32_t seq[2] = { seqlo, seqhi }; - if ( seqlo == seqhi ) - sendCtrl(UMSG_LOSSREPORT, NULL, &seq[1], 1); - else - { - seq[0] |= LOSSDATA_SEQNO_RANGE_FIRST; - sendCtrl(UMSG_LOSSREPORT, NULL, seq, 2); - } - LOGC(mglog.Debug).form("lost packets %d-%d (%d packets): sending LOSSREPORT", seqlo, seqhi, 1+CSeqNo::seqcmp(seqhi, seqlo)); - } + { + // old code; run immediately when tolerance = 0 + // or this feature isn't used because of the peer + int32_t seq[2] = { seqlo, seqhi }; + if ( seqlo == seqhi ) + sendCtrl(UMSG_LOSSREPORT, NULL, &seq[1], 1); + else + { + seq[0] |= LOSSDATA_SEQNO_RANGE_FIRST; + sendCtrl(UMSG_LOSSREPORT, NULL, seq, 2); + } + LOGC(mglog.Debug).form("lost packets %d-%d (%d packets): sending LOSSREPORT", seqlo, seqhi, 1+CSeqNo::seqcmp(seqhi, seqlo)); + } - int loss = CSeqNo::seqlen(m_iRcvCurrSeqNo, packet.m_iSeqNo) - 2; - m_iTraceRcvLoss += loss; - m_iRcvLossTotal += loss; -#ifdef SRT_ENABLE_BSTATS - uint64_t lossbytes = loss * m_pRcvBuffer->getRcvAvgPayloadSize(); - m_ullTraceRcvBytesLoss += lossbytes; - m_ullRcvBytesLossTotal += lossbytes; -#endif /* SRT_ENABLE_BSTATS */ - } + int loss = CSeqNo::seqlen(m_iRcvCurrSeqNo, packet.m_iSeqNo) - 2; + m_iTraceRcvLoss += loss; + m_iRcvLossTotal += loss; + uint64_t lossbytes = loss * m_pRcvBuffer->getRcvAvgPayloadSize(); + m_ullTraceRcvBytesLoss += lossbytes; + m_ullRcvBytesLossTotal += lossbytes; + } -#ifdef SRT_ENABLE_TSBPD - if (m_bTsbPdRcv) - { - pthread_mutex_lock(&m_RecvLock); - pthread_cond_signal(&m_RcvTsbPdCond); - pthread_mutex_unlock(&m_RecvLock); - } -#endif /* SRT_ENABLE_TSBPD */ + if (m_bTsbPd) + { + pthread_mutex_lock(&m_RecvLock); + pthread_cond_signal(&m_RcvTsbPdCond); + pthread_mutex_unlock(&m_RecvLock); + } } #ifdef SRT_BELATED_LOSSREPORT @@ -4921,6 +7138,43 @@ void CUDT::unlose(int32_t from, int32_t to) #endif } +// 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_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 @@ -4932,49 +7186,73 @@ void CUDT::unlose(int32_t from, int32_t to) // 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. int CUDT::processConnectRequest(const sockaddr* addr, CPacket& packet) { - LOGC(mglog.Note).form("listen"); - if (m_bClosing){ - LOGC(mglog.Error).form("listen reject: closing"); + // XXX ASSUMPTIONS: + // [[using assert(packet.m_iID == 0)]] + + LOGC(mglog.Debug) << "processConnectRequest: received a connection request"; + + if (m_bClosing) + { + LOGC(mglog.Debug) << "processConnectRequest: ... NOT. Rejecting because closing."; return int(URQ_ERROR_REJECT); } + /* * 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){ - LOGC(mglog.Error).form("listen reject: broken"); + if (m_bBroken) + { + LOGC(mglog.Debug) << "processConnectRequest: ... NOT. Rejecting because broken."; return int(URQ_ERROR_REJECT); } + size_t exp_len = CHandShake::m_iContentSize; // When CHandShake::m_iContentSize is used in log, the file fails to link! - if (packet.getLength() != CHandShake::m_iContentSize){ - LOGC(mglog.Error).form("listen invalid: invalif lengh %d!= %d",packet.getLength(),CHandShake::m_iContentSize); + // 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) + { + LOGC(mglog.Debug) << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len << ")"; return int(URQ_ERROR_INVALID); } - CHandShake hs; - hs.deserialize(packet.m_pcData, packet.getLength()); - - // 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_StartTime) / 60000000; // secret changes every one minute - stringstream cookiestr; - cookiestr << clienthost << ":" << clientport << ":" << timestamp; - union + + // 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) ) { - unsigned char cookie[16]; - int32_t cookie_val; - }; - CMD5::compute(cookiestr.str().c_str(), cookie); + LOGC(mglog.Error) << "processConnectRequest: the packet received as handshake is not a handshake message"; + return int(URQ_ERROR_INVALID); + } + + CHandShake hs; + hs.load_from(packet.m_pcData, packet.getLength()); + + int32_t cookie_val = bake(addr); + LOGC(mglog.Debug) << "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) { + LOGC(mglog.Debug) << "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 @@ -4982,101 +7260,151 @@ int CUDT::processConnectRequest(const sockaddr* addr, CPacket& packet) // inside a union will enforce whole union to be aligned to int32_t. hs.m_iCookie = cookie_val; packet.m_iID = hs.m_iID; - int size = packet.getLength(); - hs.serialize(packet.m_pcData, size); -#ifdef SRT_ENABLE_CTRLTSTAMP + + // 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. + + hs.m_iType = SrtHSRequest::SRT_MAGIC_CODE; + + size_t size = packet.getLength(); + hs.store_to(packet.m_pcData, Ref(size)); packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); -#endif m_pSndQueue->sendto(addr, packet); - return 0; // XXX URQ_RENDEZVOUS, oh really? + return URQ_INDUCTION; } - else + + // 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. + + LOGC(mglog.Debug) << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie..."; + if (hs.m_iCookie != cookie_val) { - if (hs.m_iCookie != cookie_val) - { - timestamp --; - cookiestr << clienthost << ":" << clientport << ":" << timestamp; - CMD5::compute(cookiestr.str().c_str(), cookie); + cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie - if (hs.m_iCookie != cookie_val) - { - LOGC(mglog.Note).form("listen rsp: %d", URQ_CONCLUSION); - return int(URQ_CONCLUSION); // Don't look at me, I just change integers to symbols! - } - } + if (hs.m_iCookie != cookie_val) + { + LOGC(mglog.Debug) << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring."; + return int(URQ_CONCLUSION); // Don't look at me, I just change integers to symbols! + } + + LOGC(mglog.Debug) << "processConnectRequest: ... correct (FIXED) cookie. Proceeding."; + } + else + { + LOGC(mglog.Debug) << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding."; } int32_t id = hs.m_iID; - // When a peer side connects in... - if ( packet.isControl(UMSG_HANDSHAKE) ) + // 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) { - if ((hs.m_iVersion != m_iVersion) || (hs.m_iType != m_iSockType)) - { - // mismatch, reject the request - hs.m_iReqType = URQ_ERROR_REJECT; - int size = CHandShake::m_iContentSize; - hs.serialize(packet.m_pcData, size); - packet.m_iID = id; -#ifdef SRT_ENABLE_CTRLTSTAMP - packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); -#endif - m_pSndQueue->sendto(addr, packet); - } - else - { - int result = s_UDTUnited.newConnection(m_SocketID, addr, &hs); - if (result == -1) - { - hs.m_iReqType = URQ_ERROR_REJECT; - LOGC(mglog.Error).form("listen rsp(REJECT): %d", URQ_ERROR_REJECT); - } + // No further check required. + // The m_iType contains flags informing about attached extensions. + } + else if (hs.m_iVersion == HS_VERSION_UDT4) + { + // Check additionally if the socktype also matches (OLD UDT compatibility). + // WARNING. The hs.m_iType has a different meaning in HS_VERSION_SRT1. In SRT version + // the socket may only be SOCK_DGRAM. + if (hs.m_iType != m_iSockType) + accepted_hs = false; + } + else + { + // Unsupported version + // (NOTE: This includes "version=0" which is a rejection flag). + accepted_hs = false; + } - // XXX developer disorder 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. - // - // 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_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) - { - int size = CHandShake::m_iContentSize; - hs.serialize(packet.m_pcData, size); - packet.m_iID = id; -#ifdef SRT_ENABLE_CTRLTSTAMP - packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); -#endif - 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); - } - } + if (!accepted_hs) + { + LOGC(mglog.Debug) << "processConnectRequest: version/type mismatch. Sending URQ_ERROR_REJECT."; + // mismatch, reject the request + hs.m_iReqType = URQ_ERROR_REJECT; + 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_StartTime); + m_pSndQueue->sendto(addr, packet); + } + else + { + int result = s_UDTUnited.newConnection(m_SocketID, addr, &hs, packet); + // ---> + // (global.) CUDTUnited::updateListenerMux + // (new Socket.) CUDT::acceptAndRespond + if (result == -1) + { + hs.m_iReqType = URQ_ERROR_REJECT; + LOGC(mglog.Error).form("UU:newConnection: rsp(REJECT): %d", URQ_ERROR_REJECT); + } + + // XXX developer disorder 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) + { + LOGC(mglog.Debug) << 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_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).form("listen ret: %d", hs.m_iReqType); + LOGC(mglog.Note) << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType); return hs.m_iReqType; } @@ -5106,18 +7434,18 @@ void CUDT::checkTimers() // This is a very heavy log, unblock only for temporary debugging! #if 0 LOGC(mglog.Debug) << CONID() << "checkTimers: nextacktime=" << logging::FormatTime(m_ullNextACKTime) - << " AckInterval=" << m_pCC->m_iACKInterval + << " AckInterval=" << m_iACKInterval << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount; #endif - if ((currtime > m_ullNextACKTime) || ((m_pCC->m_iACKInterval > 0) && (m_pCC->m_iACKInterval <= m_iPktCount))) + if ((currtime > m_ullNextACKTime) || ((m_iACKInterval > 0) && (m_iACKInterval <= m_iPktCount))) { // ACK timer expired or ACK interval is reached sendCtrl(UMSG_ACK); CTimer::rdtsc(currtime); - if (m_pCC->m_iACKPeriod > 0) - m_ullNextACKTime = currtime + m_pCC->m_iACKPeriod * m_ullCPUFrequency; + if (m_iACKPeriod > 0) + m_ullNextACKTime = currtime + m_iACKPeriod * m_ullCPUFrequency; else m_ullNextACKTime = currtime + m_ullACKInt; @@ -5159,11 +7487,11 @@ void CUDT::checkTimers() #endif uint64_t next_exp_time; - if (m_pCC->m_bUserDefinedRTO) - next_exp_time = m_ullLastRspTime + m_pCC->m_iRTO * m_ullCPUFrequency; + if (m_bUserDefinedRTO) + next_exp_time = m_ullLastRspTime + m_iRTO * m_ullCPUFrequency; else { - uint64_t exp_int = (m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + m_iSYNInterval) * m_ullCPUFrequency; + uint64_t exp_int = (m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + CPacket::SYN_INTERVAL) * m_ullCPUFrequency; if (exp_int < m_iEXPCount * m_ullMinExpInt) exp_int = m_iEXPCount * m_ullMinExpInt; next_exp_time = m_ullLastRspTime + exp_int; @@ -5172,11 +7500,8 @@ void CUDT::checkTimers() if (currtime > next_exp_time) { // Haven't receive any information from the peer, is it dead?! -#ifdef HAI_PATCH //Comment says 10 second, code says 5 // timeout: at least 16 expirations and must be greater than 5 seconds -#else - // timeout: at least 16 expirations and must be greater than 10 seconds -#endif + // XXX USE Constants for these 16 exp and 5 seconds if ((m_iEXPCount > 16) && (currtime - m_ullLastRspTime > 5000000 * m_ullCPUFrequency)) { // @@ -5243,7 +7568,7 @@ void CUDT::checkTimers() CGuard::leaveCS(m_AckLock); #endif /* SRT_ENABLE_TLPKTDROP */ - m_pCC->onTimeout(); + checkSndTimers(DONT_REGEN_KM); CCUpdate(); // immediately restart transmission @@ -5278,11 +7603,11 @@ void CUDT::checkTimers() // Not required if peer send Periodic NAK Reports. if ((1) #ifdef SRT_ENABLE_NAKREPORT - && !m_bSndPeerNakReport + && !m_bPeerNakReport #endif && m_pSndBuffer->getCurrBufSize() > 0) { - uint64_t exp_int = (m_iReXmitCount * (m_iRTT + 4 * m_iRTTVar + 2 * m_iSYNInterval) + m_iSYNInterval) * m_ullCPUFrequency; + uint64_t exp_int = (m_iReXmitCount * (m_iRTT + 4 * m_iRTTVar + 2 * CPacket::SYN_INTERVAL) + CPacket::SYN_INTERVAL) * m_ullCPUFrequency; if (currtime > (m_ullLastRspAckTime + exp_int)) { @@ -5316,7 +7641,7 @@ void CUDT::checkTimers() ++m_iReXmitCount; - m_pCC->onTimeout(); + checkSndTimers(DONT_REGEN_KM); CCUpdate(); // immediately restart transmission @@ -5326,7 +7651,7 @@ void CUDT::checkTimers() #endif /* SRT_ENABLE_FASTREXMIT */ #ifdef SRT_FIX_KEEPALIVE -// uint64_t exp_int = (m_iRTT + 4 * m_iRTTVar + m_iSYNInterval) * m_ullCPUFrequency; +// uint64_t exp_int = (m_iRTT + 4 * m_iRTTVar + CPacket::SYN_INTERVAL) * m_ullCPUFrequency; if (currtime > m_ullLastSndTime + (1000000 * m_ullCPUFrequency)) { sendCtrl(UMSG_KEEPALIVE); @@ -5344,20 +7669,20 @@ void CUDT::addEPoll(const int eid) if (!m_bConnected || m_bBroken || m_bClosing) return; -#ifdef SRT_ENABLE_TSBPD +/* new code */ 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); -#else /* SRT_ENABLE_TSBPD */ +/* (OLD CODE) if (((m_iSockType == UDT_DGRAM) && (m_pRcvBuffer->getRcvMsgNum() > 0)) || ((m_iSockType == UDT_STREAM) && m_pRcvBuffer->isRcvDataReady())) { s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true); } -#endif /* SRT_ENABLE_TSBPD */ +*/ if (m_iSndBufSize > m_pSndBuffer->getCurrBufSize()) { s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); diff --git a/srtcore/core.h b/srtcore/core.h index aed6f7251..1e68cc15a 100644 --- a/srtcore/core.h +++ b/srtcore/core.h @@ -77,9 +77,9 @@ modified by #include "packet.h" #include "channel.h" #include "api.h" -#include "ccc.h" #include "cache.h" #include "queue.h" +#include "handshake.h" #include "utilities.h" #include @@ -93,10 +93,7 @@ extern logging::Logger rxlog; - - - - +// XXX Utility function - to be moved to utilities.h? template inline T CountIIR(T base, T newval, double factor) { @@ -120,7 +117,6 @@ enum AckDataItem // Extra stats for SRT ACKD_RCVSPEED = 4, // length would be 16 ACKD_BANDWIDTH = 5, -#ifdef SRT_ENABLE_BSTATS ACKD_TOTAL_SIZE_VER100 = 6, // length = 24 ACKD_RCVRATE = 6, ACKD_TOTAL_SIZE_VER101 = 7, // length = 28 @@ -130,534 +126,626 @@ enum AckDataItem ACKD_TOTAL_SIZE_VER102 = 8, // 32 // FEATURE BLOCKED. Probably not to be restored. // ACKD_ACKBITMAP = 8, -#endif ACKD_TOTAL_SIZE = ACKD_TOTAL_SIZE_VER102 // length = 32 (or more) }; const size_t ACKD_FIELD_SIZE = sizeof(int32_t); +// For HSv4 legacy handshake +#define SRT_MAX_HSRETRY 10 /* Maximum SRT handshake retry */ + enum SeqPairItems { SEQ_BEGIN = 0, SEQ_END = 1, SEQ_SIZE = 2 }; -// XXX enum UDTSockType { UDT_STREAM = 1, UDT_DGRAM }; -- moved to common.h - // Extended SRT Congestion control class - only an incomplete definition required -class CSRTCC; +class CCryptoControl; class CUDT { -friend class CUDTSocket; -friend class CUDTUnited; -friend class CCC; -friend struct CUDTComp; -friend class CCache; -friend class CRendezvousQueue; -friend class CSndQueue; -friend class CRcvQueue; -friend class CSndUList; -friend class CRcvUList; + friend class CUDTSocket; + friend class CUDTUnited; + friend class CCC; + friend struct CUDTComp; + friend class CCache; + friend class CRendezvousQueue; + friend class CSndQueue; + friend class CRcvQueue; + friend class CSndUList; + friend class CRcvUList; private: // constructor and desctructor - void construct(); - void clearData(); - CUDT(); - CUDT(const CUDT& ancestor); - const CUDT& operator=(const CUDT&) {return *this;} - ~CUDT(); + void construct(); + void clearData(); + CUDT(); + CUDT(const CUDT& ancestor); + const CUDT& operator=(const CUDT&) {return *this;} + ~CUDT(); public: //API - static int startup(); - static int cleanup(); - static UDTSOCKET socket(int af, int type = SOCK_STREAM, int protocol = 0); - static int bind(UDTSOCKET u, const sockaddr* name, int namelen); - static int bind(UDTSOCKET u, UDPSOCKET udpsock); - static int listen(UDTSOCKET u, int backlog); - static UDTSOCKET accept(UDTSOCKET u, sockaddr* addr, int* addrlen); - static int connect(UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); - static int close(UDTSOCKET u); - static int getpeername(UDTSOCKET u, sockaddr* name, int* namelen); - static int getsockname(UDTSOCKET u, sockaddr* name, int* namelen); - static int getsockopt(UDTSOCKET u, int level, UDT_SOCKOPT optname, void* optval, int* optlen); - static int setsockopt(UDTSOCKET u, int level, UDT_SOCKOPT optname, const void* optval, int optlen); - static int send(UDTSOCKET u, const char* buf, int len, int flags); - static int recv(UDTSOCKET u, char* buf, int len, int flags); + static int startup(); + static int cleanup(); + static UDTSOCKET socket(int af, int type = SOCK_STREAM, int protocol = 0); + static int bind(UDTSOCKET u, const sockaddr* name, int namelen); + static int bind(UDTSOCKET u, UDPSOCKET udpsock); + static int listen(UDTSOCKET u, int backlog); + static UDTSOCKET accept(UDTSOCKET u, sockaddr* addr, int* addrlen); + static int connect(UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + static int close(UDTSOCKET u); + static int getpeername(UDTSOCKET u, sockaddr* name, int* namelen); + static int getsockname(UDTSOCKET u, sockaddr* name, int* namelen); + static int getsockopt(UDTSOCKET u, int level, UDT_SOCKOPT optname, void* optval, int* optlen); + static int setsockopt(UDTSOCKET u, int level, UDT_SOCKOPT optname, const void* optval, int optlen); + static int send(UDTSOCKET u, const char* buf, int len, int flags); + static int recv(UDTSOCKET u, char* buf, int len, int flags); #ifdef SRT_ENABLE_SRCTIMESTAMP - static int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, uint64_t srctime = 0LL); - static int recvmsg(UDTSOCKET u, char* buf, int len, uint64_t& srctime); + static int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, uint64_t srctime = 0LL); + static int recvmsg(UDTSOCKET u, char* buf, int len, uint64_t& srctime); #else - static int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false); -#endif - static int recvmsg(UDTSOCKET u, char* buf, int len); - static int64_t sendfile(UDTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = 364000); - static int64_t recvfile(UDTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = 7280000); - static int select(int nfds, ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout); - static int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); - static int epoll_create(); - static int epoll_add_usock(const int eid, const UDTSOCKET u, const int* events = NULL); - static int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); - static int epoll_remove_usock(const int eid, const UDTSOCKET u); - static int epoll_remove_ssock(const int eid, const SYSSOCKET s); -#ifdef HAI_PATCH - static int epoll_update_usock(const int eid, const UDTSOCKET u, const int* events = NULL); - static int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); -#endif /* HAI_PATCH */ - static int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); - static int epoll_release(const int eid); - static CUDTException& getlasterror(); - static int perfmon(UDTSOCKET u, CPerfMon* perf, bool clear = true); -#ifdef SRT_ENABLE_BSTATS - static int bstats(UDTSOCKET u, CBytePerfMon* perf, bool clear = true); + static int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false); #endif - static UDTSTATUS getsockstate(UDTSOCKET u); + static int recvmsg(UDTSOCKET u, char* buf, int len); + static int64_t sendfile(UDTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = 364000); + static int64_t recvfile(UDTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = 7280000); + static int select(int nfds, ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout); + static int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); + static int epoll_create(); + static int epoll_add_usock(const int eid, const UDTSOCKET u, const int* events = NULL); + static int epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + static int epoll_remove_usock(const int eid, const UDTSOCKET u); + static int epoll_remove_ssock(const int eid, const SYSSOCKET s); + static int epoll_update_usock(const int eid, const UDTSOCKET u, const int* events = NULL); + static int epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events = NULL); + static int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); + static int epoll_release(const int eid); + static CUDTException& getlasterror(); + static int perfmon(UDTSOCKET u, CPerfMon* perf, bool clear = true); + static int bstats(UDTSOCKET u, CBytePerfMon* perf, bool clear = true); + static UDTSTATUS getsockstate(UDTSOCKET u); + static bool setstreamid(UDTSOCKET u, const std::string& sid); + static std::string getstreamid(UDTSOCKET u); public: // internal API - static CUDT* getUDTHandle(UDTSOCKET u); - static std::vector existingSockets(); + static CUDT* getUDTHandle(UDTSOCKET u); + static std::vector existingSockets(); + + void addressAndSend(CPacket& pkt); + void sendSrtMsg(int cmd, uint32_t *srtdata_in = NULL, int srtlen_in = 0); + + bool isTsbPd() { return m_bOPT_TsbPd; } + int RTT() { return m_iRTT; } private: - /// initialize a UDT entity and bind to a local address. + /// initialize a UDT entity and bind to a local address. + + void open(); + + /// Start listening to any connection request. + + void setListenState(); + + /// Connect to a UDT entity listening at address "peer". + /// @param peer [in] The address of the listening UDT entity. + + void startConnect(const sockaddr* peer, int32_t forced_isn); - void open(); + /// Process the response handshake packet. Failure reasons can be: + /// * Socket is not in connecting state + /// * Response @a pkt is not a handshake control message + /// * Rendezvous socket has once processed a regular handshake + /// @param pkt [in] handshake packet. + /// @retval 0 Connection successful + /// @retval 1 Connection in progress (m_ConnReq turned into RESPONSE) + /// @retval -1 Connection failed - /// Start listening to any connection request. + EConnectStatus processConnectResponse(const CPacket& pkt, CUDTException* eout, bool synchro) ATR_NOEXCEPT; - void setListenState(); - /// Connect to a UDT entity listening at address "peer". - /// @param peer [in] The address of the listening UDT entity. + // This function works in case of HSv5 rendezvous. It changes the state + // according to the present state and received message type, as well as the + // INITIATOR/RESPONDER side resolved through cookieContest(). + // The resulting data are: + // - rsptype: handshake message type that should be sent back to the peer (nothing if URQ_DONE) + // - needs_extension: the HSREQ/KMREQ or HSRSP/KMRSP extensions should be attached to the handshake message. + // - RETURNED VALUE: if true, it means a URQ_CONCLUSION message was received with HSRSP/KMRSP extensions and needs HSRSP/KMRSP. + bool rendezvousSwitchState(ref_t rsptype, ref_t needs_extension); + void cookieContest(); + EConnectStatus processRendezvous(ref_t reqpkt, const CPacket &response, const sockaddr* serv_addr, bool synchro); + bool prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout); + EConnectStatus postConnect(const CPacket& response, bool rendezvous, CUDTException* eout, bool synchro); + void applyResponseSettings(); + EConnectStatus processAsyncConnectResponse(const CPacket& pkt) ATR_NOEXCEPT; + bool processAsyncConnectRequest(EConnectStatus cst, const CPacket& response, const sockaddr* serv_addr); - void connect(const sockaddr* peer, int32_t forced_isn); - /// Process the response handshake packet. Failure reasons can be: - /// * Socket is not in connecting state - /// * Response @a pkt is not a handshake control message - /// * Rendezvous socket has once processed a regular handshake - /// @param pkt [in] handshake packet. - /// @retval 0 Connection successful - /// @retval 1 Connection in progress (m_ConnReq turned into RESPONSE) - /// @retval -1 Connection failed + size_t fillSrtHandshake_HSREQ(uint32_t* srtdata, size_t srtlen, int hs_version); + size_t fillSrtHandshake_HSRSP(uint32_t* srtdata, size_t srtlen, int hs_version); + size_t fillSrtHandshake(uint32_t* srtdata, size_t srtlen, int msgtype, int hs_version); - int processConnectResponse(const CPacket& pkt) ATR_NOEXCEPT; + bool createSrtHandshake(ref_t reqpkt, ref_t hs, + int srths_cmd, int srtkm_cmd, const uint32_t* data, size_t datalen); - void processRendezvous(const CPacket& pkt) ATR_NOEXCEPT - { - CGuard cg(m_ConnectionLock); // FIX - processConnectResponse(pkt); - } + size_t prepareSrtHsMsg(int cmd, uint32_t* srtdata, size_t size); - /// Connect to a UDT entity listening at address "peer", which has sent "hs" request. - /// @param peer [in] The address of the listening UDT entity. - /// @param hs [in/out] The handshake information sent by the peer side (in), negotiated value (out). + void processSrtMsg(const CPacket *ctrlpkt); + int processSrtMsg_HSREQ(const uint32_t* srtdata, size_t len, uint32_t ts, int hsv); + int processSrtMsg_HSRSP(const uint32_t* srtdata, size_t len, uint32_t ts, int hsv); + bool interpretSrtHandshake(const CHandShake& hs, const CPacket& hspkt, uint32_t* out_data, size_t* out_len); - void acceptAndRespond(const sockaddr* peer, CHandShake* hs); + void updateAfterSrtHandshake(int srt_cmd, int hsv); - /// Close the opened UDT entity. + void updateSrtRcvSettings(); + void updateSrtSndSettings(); - void close(); + /// Connect to a UDT entity listening at address "peer", which has sent "hs" request. + /// @param peer [in] The address of the listening UDT entity. + /// @param hs [in/out] The handshake information sent by the peer side (in), negotiated value (out). - /// Request UDT to send out a data block "data" with size of "len". - /// @param data [in] The address of the application data to be sent. - /// @param len [in] The size of the data block. - /// @return Actual size of data sent. + void acceptAndRespond(const sockaddr* peer, CHandShake* hs, const CPacket& hspkt); - int send(const char* data, int len); + /// Close the opened UDT entity. - /// Request UDT to receive data to a memory block "data" with size of "len". - /// @param data [out] data received. - /// @param len [in] The desired size of data to be received. - /// @return Actual size of data received. + void close(); - int recv(char* data, int len); + /// Request UDT to send out a data block "data" with size of "len". + /// @param data [in] The address of the application data to be sent. + /// @param len [in] The size of the data block. + /// @return Actual size of data sent. - /// send a message of a memory block "data" with size of "len". - /// @param data [out] data received. - /// @param len [in] The desired size of data to be received. - /// @param ttl [in] the time-to-live of the message. - /// @param inorder [in] if the message should be delivered in order. - /// @param srctime [in] Time when the data were ready to send. - /// @return Actual size of data sent. + int send(const char* data, int len); + + /// Request UDT to receive data to a memory block "data" with size of "len". + /// @param data [out] data received. + /// @param len [in] The desired size of data to be received. + /// @return Actual size of data received. + + int recv(char* data, int len); + + /// send a message of a memory block "data" with size of "len". + /// @param data [out] data received. + /// @param len [in] The desired size of data to be received. + /// @param ttl [in] the time-to-live of the message. + /// @param inorder [in] if the message should be delivered in order. + /// @param srctime [in] Time when the data were ready to send. + /// @return Actual size of data sent. #ifdef SRT_ENABLE_SRCTIMESTAMP - int sendmsg(const char* data, int len, int ttl, bool inorder, uint64_t srctime); + int sendmsg(const char* data, int len, int ttl, bool inorder, uint64_t srctime); #else - int sendmsg(const char* data, int len, int ttl, bool inorder); + int sendmsg(const char* data, int len, int ttl, bool inorder); #endif - /// Receive a message to buffer "data". - /// @param data [out] data received. - /// @param len [in] size of the buffer. - /// @return Actual size of data received. + /// Receive a message to buffer "data". + /// @param data [out] data received. + /// @param len [in] size of the buffer. + /// @return Actual size of data received. #ifdef SRT_ENABLE_SRCTIMESTAMP - int recvmsg(char* data, int len, uint64_t& srctime); + int recvmsg(char* data, int len, uint64_t& srctime); #endif - int recvmsg(char* data, int len); + int recvmsg(char* data, int len); - /// Request UDT to send out a file described as "fd", starting from "offset", with size of "size". - /// @param ifs [in] The input file stream. - /// @param offset [in, out] From where to read and send data; output is the new offset when the call returns. - /// @param size [in] How many data to be sent. - /// @param block [in] size of block per read from disk - /// @return Actual size of data sent. + /// Request UDT to send out a file described as "fd", starting from "offset", with size of "size". + /// @param ifs [in] The input file stream. + /// @param offset [in, out] From where to read and send data; output is the new offset when the call returns. + /// @param size [in] How many data to be sent. + /// @param block [in] size of block per read from disk + /// @return Actual size of data sent. - int64_t sendfile(std::fstream& ifs, int64_t& offset, int64_t size, int block = 366000); + int64_t sendfile(std::fstream& ifs, int64_t& offset, int64_t size, int block = 366000); - /// Request UDT to receive data into a file described as "fd", starting from "offset", with expected size of "size". - /// @param ofs [out] The output file stream. - /// @param offset [in, out] From where to write data; output is the new offset when the call returns. - /// @param size [in] How many data to be received. - /// @param block [in] size of block per write to disk - /// @return Actual size of data received. + /// Request UDT to receive data into a file described as "fd", starting from "offset", with expected size of "size". + /// @param ofs [out] The output file stream. + /// @param offset [in, out] From where to write data; output is the new offset when the call returns. + /// @param size [in] How many data to be received. + /// @param block [in] size of block per write to disk + /// @return Actual size of data received. - int64_t recvfile(std::fstream& ofs, int64_t& offset, int64_t size, int block = 7320000); + int64_t recvfile(std::fstream& ofs, int64_t& offset, int64_t size, int block = 7320000); - /// Configure UDT options. - /// @param optName [in] The enum name of a UDT option. - /// @param optval [in] The value to be set. - /// @param optlen [in] size of "optval". + /// Configure UDT options. + /// @param optName [in] The enum name of a UDT option. + /// @param optval [in] The value to be set. + /// @param optlen [in] size of "optval". - void setOpt(UDT_SOCKOPT optName, const void* optval, int optlen); + void setOpt(UDT_SOCKOPT optName, const void* optval, int optlen); - /// Read UDT options. - /// @param optName [in] The enum name of a UDT option. - /// @param optval [in] The value to be returned. - /// @param optlen [out] size of "optval". + /// Read UDT options. + /// @param optName [in] The enum name of a UDT option. + /// @param optval [in] The value to be returned. + /// @param optlen [out] size of "optval". - void getOpt(UDT_SOCKOPT optName, void* optval, int& optlen); + void getOpt(UDT_SOCKOPT optName, void* optval, int& optlen); - /// read the performance data since last sample() call. - /// @param perf [in, out] pointer to a CPerfMon structure to record the performance data. - /// @param clear [in] flag to decide if the local performance trace should be cleared. + /// read the performance data since last sample() call. + /// @param perf [in, out] pointer to a CPerfMon structure to record the performance data. + /// @param clear [in] flag to decide if the local performance trace should be cleared. - void sample(CPerfMon* perf, bool clear = true); -#ifdef SRT_ENABLE_BSTATS - void bstats(CBytePerfMon* perf, bool clear = true); -#endif + void sample(CPerfMon* perf, bool clear = true); - /// Mark sequence contained in the given packet as not lost. This - /// removes the loss record from both current receiver loss list and - /// the receiver fresh loss list. - void unlose(const CPacket& oldpacket); - void unlose(int32_t from, int32_t to); + // XXX please document + void bstats(CBytePerfMon* perf, bool clear = true); + + /// Mark sequence contained in the given packet as not lost. This + /// removes the loss record from both current receiver loss list and + /// the receiver fresh loss list. + void unlose(const CPacket& oldpacket); + void unlose(int32_t from, int32_t to); private: - static CUDTUnited s_UDTUnited; // UDT global management base + static CUDTUnited s_UDTUnited; // UDT global management base public: - static const UDTSOCKET INVALID_SOCK; // invalid socket descriptor - static const int ERROR; // socket api error returned value + static const UDTSOCKET INVALID_SOCK; // invalid socket descriptor + static const int ERROR; // socket api error returned value + + int handshakeVersion() + { + return m_ConnRes.m_iVersion; + } - std::string CONID() const - { + std::string CONID() const + { #if ENABLE_LOGGING - std::ostringstream os; - os << "%" << m_SocketID << ":"; - return os.str(); + std::ostringstream os; + os << "%" << m_SocketID << ":"; + return os.str(); #else - return ""; + return ""; #endif - } + } + + UDTSOCKET socketID() { return m_SocketID; } private: // Identification - UDTSOCKET m_SocketID; // UDT socket number - UDTSockType m_iSockType; // Type of the UDT connection (SOCK_STREAM or SOCK_DGRAM) - UDTSOCKET m_PeerID; // peer id, for multiplexer - static const int m_iVersion; // UDT version, for compatibility use + UDTSOCKET m_SocketID; // UDT socket number + UDTSockType m_iSockType; // Type of the UDT connection (SOCK_STREAM or SOCK_DGRAM) + UDTSOCKET m_PeerID; // peer id, for multiplexer +public: + static const int HS_VERSION_UDT4 = 4; + static const int HS_VERSION_SRT1 = 5; private: // Packet sizes - int m_iPktSize; // Maximum/regular packet size, in bytes - int m_iPayloadSize; // Maximum/regular payload size, in bytes + int m_iPktSize; // Maximum/regular packet size, in bytes + int m_iPayloadSize; // Maximum/regular payload size, in bytes private: // Options - int m_iMSS; // Maximum Segment Size, in bytes - bool m_bSynSending; // Sending syncronization mode - bool m_bSynRecving; // Receiving syncronization mode - int m_iFlightFlagSize; // Maximum number of packets in flight from the peer side - int m_iSndBufSize; // Maximum UDT sender buffer size - int m_iRcvBufSize; // Maximum UDT receiver buffer size - linger m_Linger; // Linger information on close - int m_iUDPSndBufSize; // UDP sending buffer size - int m_iUDPRcvBufSize; // UDP receiving buffer size - int m_iIPversion; // IP version - bool m_bRendezvous; // Rendezvous connection mode + int m_iMSS; // Maximum Segment Size, in bytes + bool m_bSynSending; // Sending syncronization mode + bool m_bSynRecving; // Receiving syncronization mode + int m_iFlightFlagSize; // Maximum number of packets in flight from the peer side + int m_iSndBufSize; // Maximum UDT sender buffer size + int m_iRcvBufSize; // Maximum UDT receiver buffer size + linger m_Linger; // Linger information on close + int m_iUDPSndBufSize; // UDP sending buffer size + int m_iUDPRcvBufSize; // UDP receiving buffer size + int m_iIPversion; // IP version + bool m_bRendezvous; // Rendezvous connection mode #ifdef SRT_ENABLE_CONNTIMEO - int m_iConnTimeOut; // connect timeout in milliseconds + int m_iConnTimeOut; // connect timeout in milliseconds #endif - int m_iSndTimeOut; // sending timeout in milliseconds - int m_iRcvTimeOut; // receiving timeout in milliseconds - bool m_bReuseAddr; // reuse an exiting port or not, for UDP multiplexer - int64_t m_llMaxBW; // maximum data transfer rate (threshold) + int m_iSndTimeOut; // sending timeout in milliseconds + int m_iRcvTimeOut; // receiving timeout in milliseconds + bool m_bReuseAddr; // reuse an exiting port or not, for UDP multiplexer + int64_t m_llMaxBW; // maximum data transfer rate (threshold) #ifdef SRT_ENABLE_IPOPTS - int m_iIpTTL; - int m_iIpToS; + int m_iIpTTL; + int m_iIpToS; #endif - HaiCrypt_Secret m_CryptoSecret; - int m_iSndCryptoKeyLen; - bool m_bDataSender; - bool m_bTwoWayData; -#ifdef SRT_ENABLE_TSBPD - bool m_bTsbPdMode; // TimeStamp-Based Rx - int m_iTsbPdDelay; // Rx delay to absorb burst in milliseconds + // These fields keep the options for encryption + // (SRTO_PASSPHRASE, SRTO_PBKEYLEN). Crypto object is + // created later and takes values from these. + HaiCrypt_Secret m_CryptoSecret; + int m_iSndCryptoKeyLen; + + bool m_bDataSender; + bool m_bTwoWayData; + + uint64_t m_SndHsLastTime; //Last SRT handshake request time + int m_SndHsRetryCnt; //SRT handshake retries left + + // MOVED FROM CSRTCC + double m_dPktSndPeriod; // Packet sending period, in microseconds + double m_dCWndSize; // Congestion window size, in packets + double m_dMaxCWndSize; // maximum cwnd size, in packets + int m_iRcvRate; // packet arrive rate at receiver side, packets per second + int m_iACKPeriod; // Periodical timer to send an ACK, in milliseconds + int m_iACKInterval; // How many packets to send one ACK, in packets + bool m_bUserDefinedRTO; // if the RTO value is defined by users + int m_iRTO; // RTO value, microseconds + + int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec) + int m_iSndAvgPayloadSize; //Average Payload Size of packets to xmit + + void updatePktSndPeriod() + { + double pktsize = m_iSndAvgPayloadSize + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + m_dPktSndPeriod = 1000000.0 * (pktsize/m_llSndMaxBW); + } + + void considerLegacySrtHandshake(uint64_t timebase); + + void setMaxBW(int64_t maxbw); + void checkSndTimers(Whether2RegenKm regen = DONT_REGEN_KM); + + void handshakeDone() + { + m_SndHsRetryCnt = 0; + } + + + bool m_bOPT_TsbPd; // Whether AGENT will do TSBPD Rx (whether peer does, is not agent's problem) + int m_iOPT_TsbPdDelay; // Agent's Rx latency + int m_iOPT_PeerTsbPdDelay; // Peer's Rx latency for the traffic made by Agent's Tx. + bool m_bOPT_TLPktDrop; // Whether Agent WILL DO TLPKTDROP on Rx. + std::string m_sStreamName; + + int m_iTsbPdDelay; // Rx delay to absorb burst in milliseconds + int m_iPeerTsbPdDelay; // Tx delay that the peer uses to absorb burst in milliseconds #ifdef SRT_ENABLE_TLPKTDROP - bool m_bTLPktDrop; // Enable Too-late Packet Drop + bool m_bTLPktDrop; // Enable Too-late Packet Drop #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ -#ifdef SRT_ENABLE_INPUTRATE - int64_t m_llInputBW; // Input stream rate (bytes/sec) - int m_iOverheadBW; // Percent above input stream rate (applies if m_llMaxBW == 0) -#endif + int64_t m_llInputBW; // Input stream rate (bytes/sec) + int m_iOverheadBW; // Percent above input stream rate (applies if m_llMaxBW == 0) #ifdef SRT_ENABLE_NAKREPORT - bool m_bRcvNakReport; // Enable Receiver Periodic NAK Reports + bool m_bRcvNakReport; // Enable Receiver Periodic NAK Reports #endif private: // congestion control - CCCVirtualFactory* m_pCCFactory; // Factory class to create a specific CC instance - CCC* m_pCC; // congestion control class - CSRTCC* m_pSRTCC; // congestion control SRT class (needed for extended API) - CCache* m_pCache; // network information cache + UniquePtr m_pCryptoControl; // congestion control SRT class (small data extension) + CCache* m_pCache; // network information cache private: // Status - volatile bool m_bListening; // If the UDT entit is listening to connection - volatile bool m_bConnecting; // The short phase when connect() is called but not yet completed - volatile bool m_bConnected; // Whether the connection is on or off - volatile bool m_bClosing; // If the UDT entity is closing - volatile bool m_bShutdown; // If the peer side has shutdown the connection - volatile bool m_bBroken; // If the connection has been broken - volatile bool m_bPeerHealth; // If the peer status is normal - bool m_bOpened; // If the UDT entity has been opened - int m_iBrokenCounter; // a counter (number of GC checks) to let the GC tag this socket as disconnected - - int m_iEXPCount; // Expiration counter - int m_iBandwidth; // Estimated bandwidth, number of packets per second - int m_iRTT; // RTT, in microseconds - int m_iRTTVar; // RTT variance - int m_iDeliveryRate; // Packet arrival rate at the receiver side - - uint64_t m_ullLingerExpiration; // Linger expiration time (for GC to close a socket with data in sending buffer) - - CHandShake m_ConnReq; // connection request - CHandShake m_ConnRes; // connection response - int64_t m_llLastReqTime; // last time when a connection request is sent + volatile bool m_bListening; // If the UDT entit is listening to connection + volatile bool m_bConnecting; // The short phase when connect() is called but not yet completed + volatile bool m_bConnected; // Whether the connection is on or off + volatile bool m_bClosing; // If the UDT entity is closing + volatile bool m_bShutdown; // If the peer side has shutdown the connection + volatile bool m_bBroken; // If the connection has been broken + volatile bool m_bPeerHealth; // If the peer status is normal + bool m_bOpened; // If the UDT entity has been opened + int m_iBrokenCounter; // a counter (number of GC checks) to let the GC tag this socket as disconnected + + int m_iEXPCount; // Expiration counter + int m_iBandwidth; // Estimated bandwidth, number of packets per second + int m_iRTT; // RTT, in microseconds + int m_iRTTVar; // RTT variance + int m_iDeliveryRate; // Packet arrival rate at the receiver side + + uint64_t m_ullLingerExpiration; // Linger expiration time (for GC to close a socket with data in sending buffer) + + CHandShake m_ConnReq; // connection request + CHandShake m_ConnRes; // connection response + CHandShake::RendezvousState m_RdvState; // HSv5 rendezvous state + HandshakeSide m_SrtHsSide; // HSv5 rendezvous handshake side resolved from cookie contest (DRAW if not yet resolved) + int64_t m_llLastReqTime; // last time when a connection request is sent private: // Sending related data - CSndBuffer* m_pSndBuffer; // Sender buffer - CSndLossList* m_pSndLossList; // Sender loss list - CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window + CSndBuffer* m_pSndBuffer; // Sender buffer + CSndLossList* m_pSndLossList; // Sender loss list + CPktTimeWindow<16, 16> m_SndTimeWindow; // Packet sending time window - volatile uint64_t m_ullInterval; // Inter-packet time, in CPU clock cycles - uint64_t m_ullTimeDiff; // aggregate difference in inter-packet time + volatile uint64_t m_ullInterval; // Inter-packet time, in CPU clock cycles + uint64_t m_ullTimeDiff; // aggregate difference in inter-packet time - volatile int m_iFlowWindowSize; // Flow control window size - volatile double m_dCongestionWindow; // congestion window size + volatile int m_iFlowWindowSize; // Flow control window size + volatile double m_dCongestionWindow; // congestion window size #ifdef SRT_ENABLE_TLPKTDROP - volatile int32_t m_iSndLastFullAck; // Last full ACK received + volatile int32_t m_iSndLastFullAck; // Last full ACK received #endif /* SRT_ENABLE_TLPKTDROP */ - volatile int32_t m_iSndLastAck; // Last ACK received - volatile int32_t m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list - volatile int32_t m_iSndCurrSeqNo; // The largest sequence number that has been sent - int32_t m_iLastDecSeq; // Sequence number sent last decrease occurs - int32_t m_iSndLastAck2; // Last ACK2 sent back - uint64_t m_ullSndLastAck2Time; // The time when last ACK2 was sent back + volatile int32_t m_iSndLastAck; // Last ACK received + volatile int32_t m_iSndLastDataAck; // The real last ACK that updates the sender buffer and loss list + volatile int32_t m_iSndCurrSeqNo; // The largest sequence number that has been sent + int32_t m_iLastDecSeq; // Sequence number sent last decrease occurs + int32_t m_iSndLastAck2; // Last ACK2 sent back + uint64_t m_ullSndLastAck2Time; // The time when last ACK2 was sent back #ifdef SRT_ENABLE_CBRTIMESTAMP - uint64_t m_ullSndLastCbrTime; // Last timestamp set in a data packet to send (usec) + uint64_t m_ullSndLastCbrTime; // Last timestamp set in a data packet to send (usec) #endif - int32_t m_iISN; // Initial Sequence Number -#ifdef SRT_ENABLE_TSBPD - bool m_bTsbPdSnd; // Peer accept TimeStamp-Based Rx mode - uint32_t m_SndTsbPdDelay; + int32_t m_iISN; // Initial Sequence Number + bool m_bPeerTsbPd; // Peer accept TimeStamp-Based Rx mode #ifdef SRT_ENABLE_TLPKTDROP - bool m_bTLPktDropSnd; // Enable sender late packet dropping + bool m_bPeerTLPktDrop; // Enable sender late packet dropping #endif /* SRT_ENABLE_TLPKTDROP */ -#endif /* SRT_ENABLE_TSBPD */ #ifdef SRT_ENABLE_NAKREPORT - int m_iMinNakInterval; // Minimum NAK Report Period (usec) - int m_iNakReportAccel; // NAK Report Period (RTT) accelerator - bool m_bSndPeerNakReport; // Sender's peer (receiver) issues Periodic NAK Reports - bool m_bPeerRexmitFlag; // Receiver supports rexmit flag in payload packets + int m_iMinNakInterval; // Minimum NAK Report Period (usec) + int m_iNakReportAccel; // NAK Report Period (RTT) accelerator + bool m_bPeerNakReport; // Sender's peer (receiver) issues Periodic NAK Reports + bool m_bPeerRexmitFlag; // Receiver supports rexmit flag in payload packets #endif /* SRT_ENABLE_NAKREPORT */ #ifdef SRT_ENABLE_FASTREXMIT - int32_t m_iReXmitCount; // Re-Transmit Count since last ACK + int32_t m_iReXmitCount; // Re-Transmit Count since last ACK #endif /* SRT_ENABLE_FASTREXMIT */ - void CCUpdate(); + void CCUpdate(); private: // Receiving related data - CRcvBuffer* m_pRcvBuffer; //< Receiver buffer - CRcvLossList* m_pRcvLossList; //< Receiver loss list + CRcvBuffer* m_pRcvBuffer; //< Receiver buffer + CRcvLossList* m_pRcvLossList; //< Receiver loss list #if SRT_BELATED_LOSSREPORT - std::deque m_FreshLoss; //< Lost sequence already added to m_pRcvLossList, but not yet sent UMSG_LOSSREPORT for. - int m_iReorderTolerance; //< Current value of dynamic reorder tolerance - int m_iMaxReorderTolerance; //< Maximum allowed value for dynamic reorder tolerance - int m_iConsecEarlyDelivery; //< Increases with every OOO packet that came m_FreshLoss; //< Lost sequence already added to m_pRcvLossList, but not yet sent UMSG_LOSSREPORT for. + int m_iReorderTolerance; //< Current value of dynamic reorder tolerance + int m_iMaxReorderTolerance; //< Maximum allowed value for dynamic reorder tolerance + int m_iConsecEarlyDelivery; //< Increases with every OOO packet that came m_ACKWindow; //< ACK history window - CPktTimeWindow<16, 64> m_RcvTimeWindow; //< Packet arrival time window + CACKWindow<1024> m_ACKWindow; //< ACK history window + CPktTimeWindow<16, 64> m_RcvTimeWindow; //< Packet arrival time window - int32_t m_iRcvLastAck; //< Last sent ACK + int32_t m_iRcvLastAck; //< Last sent ACK #ifdef ENABLE_LOGGING - int32_t m_iDebugPrevLastAck; + int32_t m_iDebugPrevLastAck; #endif #ifdef SRT_ENABLE_TLPKTDROP - int32_t m_iRcvLastSkipAck; // Last dropped sequence ACK + int32_t m_iRcvLastSkipAck; // Last dropped sequence ACK #endif /* SRT_ENABLE_TLPKTDROP */ - uint64_t m_ullLastAckTime; // Timestamp of last ACK - int32_t m_iRcvLastAckAck; // Last sent ACK that has been acknowledged - int32_t m_iAckSeqNo; // Last ACK sequence number - int32_t m_iRcvCurrSeqNo; // Largest received sequence number + uint64_t m_ullLastAckTime; // Timestamp of last ACK + int32_t m_iRcvLastAckAck; // Last sent ACK that has been acknowledged + int32_t m_iAckSeqNo; // Last ACK sequence number + int32_t m_iRcvCurrSeqNo; // Largest received sequence number + + uint64_t m_ullLastWarningTime; // Last time that a warning message is sent - uint64_t m_ullLastWarningTime; // Last time that a warning message is sent + int32_t m_iPeerISN; // Initial Sequence Number of the peer side + uint64_t m_ullRcvPeerStartTime; - int32_t m_iPeerISN; // Initial Sequence Number of the peer side + uint32_t m_lSrtVersion; + uint32_t m_lMinimumPeerSrtVersion; + uint32_t m_lPeerSrtVersion; -#ifdef SRT_ENABLE_TSBPD - bool m_bTsbPdRcv; // Peer sends TimeStamp-Based Packet Delivery Packets - uint32_t m_RcvTsbPdDelay; // Aggreed TsbPD added latency - pthread_t m_RcvTsbPdThread; // Rcv TsbPD Thread handle - pthread_cond_t m_RcvTsbPdCond; - bool m_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent - static void* tsbpd(void* param); -#endif /* SRT_ENABLE_TSBPD */ + bool m_bTsbPd; // Peer sends TimeStamp-Based Packet Delivery Packets + pthread_t m_RcvTsbPdThread; // Rcv TsbPD Thread handle + pthread_cond_t m_RcvTsbPdCond; + bool m_bTsbPdAckWakeup; // Signal TsbPd thread on Ack sent + static void* tsbpd(void* param); private: // synchronization: mutexes and conditions - pthread_mutex_t m_ConnectionLock; // used to synchronize connection operation + pthread_mutex_t m_ConnectionLock; // used to synchronize connection operation - pthread_cond_t m_SendBlockCond; // used to block "send" call - pthread_mutex_t m_SendBlockLock; // lock associated to m_SendBlockCond + pthread_cond_t m_SendBlockCond; // used to block "send" call + pthread_mutex_t m_SendBlockLock; // lock associated to m_SendBlockCond - pthread_mutex_t m_AckLock; // used to protected sender's loss list when processing ACK + pthread_mutex_t m_AckLock; // used to protected sender's loss list when processing ACK - pthread_cond_t m_RecvDataCond; // used to block "recv" when there is no data - pthread_mutex_t m_RecvDataLock; // lock associated to m_RecvDataCond + pthread_cond_t m_RecvDataCond; // used to block "recv" when there is no data + pthread_mutex_t m_RecvDataLock; // lock associated to m_RecvDataCond - pthread_mutex_t m_SendLock; // used to synchronize "send" call - pthread_mutex_t m_RecvLock; // used to synchronize "recv" call + pthread_mutex_t m_SendLock; // used to synchronize "send" call + pthread_mutex_t m_RecvLock; // used to synchronize "recv" call - pthread_mutex_t m_RcvLossLock; // Protects the receiver loss list (access: CRcvQueue::worker, CUDT::tsbpd) + pthread_mutex_t m_RcvLossLock; // Protects the receiver loss list (access: CRcvQueue::worker, CUDT::tsbpd) - void initSynch(); - void destroySynch(); - void releaseSynch(); + // This is required to synchronize the background part of the closing socket process + // with the call of srt_close(). The condition is broadcast at the end regardless of + // the settings. The srt_close() function is blocked from exiting until this signal + // is received when the socket is set SRTO_SNDSYN. + pthread_mutex_t m_CloseSynchLock; + pthread_cond_t m_CloseSynchCond; + + void initSynch(); + void destroySynch(); + void releaseSynch(); private: // Common connection Congestion Control setup - void setupCC(void); + bool setupCC(); + bool createCrypter(HandshakeSide side, bool bidi); private: // Generation and processing of packets - void sendCtrl(UDTMessageType pkttype, void* lparam = NULL, void* rparam = NULL, int size = 0); - void processCtrl(CPacket& ctrlpkt); - int packData(CPacket& packet, uint64_t& ts); - int processData(CUnit* unit); - int processConnectRequest(const sockaddr* addr, CPacket& packet); - static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi); + void sendCtrl(UDTMessageType pkttype, void* lparam = NULL, void* rparam = NULL, int size = 0); + void processCtrl(CPacket& ctrlpkt); + int packData(CPacket& packet, uint64_t& ts); + int processData(CUnit* unit); + int processConnectRequest(const sockaddr* addr, CPacket& packet); + static void addLossRecord(std::vector& lossrecord, int32_t lo, int32_t hi); + int32_t bake(const sockaddr* addr, int32_t previous_cookie = 0, int correction = 0); private: // Trace - uint64_t m_StartTime; // timestamp when the UDT entity is started - int64_t m_llSentTotal; // total number of sent data packets, including retransmissions - int64_t m_llRecvTotal; // total number of received packets - int m_iSndLossTotal; // total number of lost packets (sender side) - int m_iRcvLossTotal; // total number of lost packets (receiver side) - int m_iRetransTotal; // total number of retransmitted packets - int m_iSentACKTotal; // total number of sent ACK packets - int m_iRecvACKTotal; // total number of received ACK packets - int m_iSentNAKTotal; // total number of sent NAK packets - int m_iRecvNAKTotal; // total number of received NAK packets + uint64_t m_StartTime; // timestamp when the UDT entity is started + int64_t m_llSentTotal; // total number of sent data packets, including retransmissions + int64_t m_llRecvTotal; // total number of received packets + int m_iSndLossTotal; // total number of lost packets (sender side) + int m_iRcvLossTotal; // total number of lost packets (receiver side) + int m_iRetransTotal; // total number of retransmitted packets + int m_iSentACKTotal; // total number of sent ACK packets + int m_iRecvACKTotal; // total number of received ACK packets + int m_iSentNAKTotal; // total number of sent NAK packets + int m_iRecvNAKTotal; // total number of received NAK packets #ifdef SRT_ENABLE_TLPKTDROP - int m_iSndDropTotal; - int m_iRcvDropTotal; + int m_iSndDropTotal; + int m_iRcvDropTotal; #endif -#ifdef SRT_ENABLE_BSTATS - uint64_t m_ullBytesSentTotal; // total number of bytes sent, including retransmissions - uint64_t m_ullBytesRecvTotal; // total number of received bytes - uint64_t m_ullRcvBytesLossTotal; // total number of loss bytes (estimate) - uint64_t m_ullBytesRetransTotal; // total number of retransmitted bytes + uint64_t m_ullBytesSentTotal; // total number of bytes sent, including retransmissions + uint64_t m_ullBytesRecvTotal; // total number of received bytes + uint64_t m_ullRcvBytesLossTotal; // total number of loss bytes (estimate) + uint64_t m_ullBytesRetransTotal; // total number of retransmitted bytes #ifdef SRT_ENABLE_TLPKTDROP - uint64_t m_ullSndBytesDropTotal; - uint64_t m_ullRcvBytesDropTotal; + uint64_t m_ullSndBytesDropTotal; + uint64_t m_ullRcvBytesDropTotal; #endif /* SRT_ENABLE_TLPKTDROP */ - int m_iRcvUndecryptTotal; - uint64_t m_ullRcvBytesUndecryptTotal; -#endif /* SRT_ENABLE_BSTATS */ - int64_t m_llSndDurationTotal; // total real time for sending - - uint64_t m_LastSampleTime; // last performance sample time - int64_t m_llTraceSent; // number of packets sent in the last trace interval - int64_t m_llTraceRecv; // number of packets received in the last trace interval - int m_iTraceSndLoss; // number of lost packets in the last trace interval (sender side) - int m_iTraceRcvLoss; // number of lost packets in the last trace interval (receiver side) - int m_iTraceRetrans; // number of retransmitted packets in the last trace interval - int m_iSentACK; // number of ACKs sent in the last trace interval - int m_iRecvACK; // number of ACKs received in the last trace interval - int m_iSentNAK; // number of NAKs sent in the last trace interval - int m_iRecvNAK; // number of NAKs received in the last trace interval + int m_iRcvUndecryptTotal; + uint64_t m_ullRcvBytesUndecryptTotal; + int64_t m_llSndDurationTotal; // total real time for sending + + uint64_t m_LastSampleTime; // last performance sample time + int64_t m_llTraceSent; // number of packets sent in the last trace interval + int64_t m_llTraceRecv; // number of packets received in the last trace interval + int m_iTraceSndLoss; // number of lost packets in the last trace interval (sender side) + int m_iTraceRcvLoss; // number of lost packets in the last trace interval (receiver side) + int m_iTraceRetrans; // number of retransmitted packets in the last trace interval + int m_iSentACK; // number of ACKs sent in the last trace interval + int m_iRecvACK; // number of ACKs received in the last trace interval + int m_iSentNAK; // number of NAKs sent in the last trace interval + int m_iRecvNAK; // number of NAKs received in the last trace interval #ifdef SRT_ENABLE_TLPKTDROP - int m_iTraceSndDrop; - int m_iTraceRcvDrop; + int m_iTraceSndDrop; + int m_iTraceRcvDrop; #endif /* SRT_ENABLE_TLPKTDROP */ - int m_iTraceRcvRetrans; - int m_iTraceReorderDistance; - double m_fTraceBelatedTime; - int64_t m_iTraceRcvBelated; -#ifdef SRT_ENABLE_BSTATS - uint64_t m_ullTraceBytesSent; // number of bytes sent in the last trace interval - uint64_t m_ullTraceBytesRecv; // number of bytes sent in the last trace interval - uint64_t m_ullTraceRcvBytesLoss; // number of bytes bytes lost in the last trace interval (estimate) - uint64_t m_ullTraceBytesRetrans; // number of bytes retransmitted in the last trace interval + int m_iTraceRcvRetrans; + int m_iTraceReorderDistance; + double m_fTraceBelatedTime; + int64_t m_iTraceRcvBelated; + uint64_t m_ullTraceBytesSent; // number of bytes sent in the last trace interval + uint64_t m_ullTraceBytesRecv; // number of bytes sent in the last trace interval + uint64_t m_ullTraceRcvBytesLoss; // number of bytes bytes lost in the last trace interval (estimate) + uint64_t m_ullTraceBytesRetrans; // number of bytes retransmitted in the last trace interval #ifdef SRT_ENABLE_TLPKTDROP - uint64_t m_ullTraceSndBytesDrop; - uint64_t m_ullTraceRcvBytesDrop; + uint64_t m_ullTraceSndBytesDrop; + uint64_t m_ullTraceRcvBytesDrop; #endif /* SRT_ENABLE_TLPKTDROP */ - int m_iTraceRcvUndecrypt; - uint64_t m_ullTraceRcvBytesUndecrypt; -#endif /* SRT_ENABLE_BSTATS */ - int64_t m_llSndDuration; // real time for sending - int64_t m_llSndDurationCounter; // timers to record the sending duration + int m_iTraceRcvUndecrypt; + uint64_t m_ullTraceRcvBytesUndecrypt; + int64_t m_llSndDuration; // real time for sending + int64_t m_llSndDurationCounter; // timers to record the sending duration -private: // Timers - uint64_t m_ullCPUFrequency; // CPU clock frequency, used for Timer, ticks per microsecond +public: + + static const int m_iSelfClockInterval = 64; // ACK interval for self-clocking + static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq + static const int PACKETPAIR_MASK = 0xF; - static const int m_iSYNInterval; // Periodical Rate Control Interval, 10000 microsecond - static const int m_iSelfClockInterval; // ACK interval for self-clocking - static const int SEND_LITE_ACK = sizeof(int32_t); // special size for ack containing only ack seq - static const int PACKETPAIR_MASK = 0xF; + static const int64_t BW_INFINITE = 30000000/8; //Infinite=> 30Mbps - uint64_t m_ullNextACKTime; // Next ACK time, in CPU clock cycles, same below - uint64_t m_ullNextNAKTime; // Next NAK time + static const size_t MAX_SID_LENGTH = 512; - volatile uint64_t m_ullSYNInt; // SYN interval - volatile uint64_t m_ullACKInt; // ACK interval - volatile uint64_t m_ullNAKInt; // NAK interval - volatile uint64_t m_ullLastRspTime; // time stamp of last response from the peer +private: // Timers + uint64_t m_ullCPUFrequency; // CPU clock frequency, used for Timer, ticks per microsecond + uint64_t m_ullNextACKTime; // Next ACK time, in CPU clock cycles, same below + uint64_t m_ullNextNAKTime; // Next NAK time + + volatile uint64_t m_ullSYNInt; // SYN interval + volatile uint64_t m_ullACKInt; // ACK interval + volatile uint64_t m_ullNAKInt; // NAK interval + volatile uint64_t m_ullLastRspTime; // time stamp of last response from the peer #ifdef SRT_ENABLE_FASTREXMIT - volatile uint64_t m_ullLastRspAckTime; // time stamp of last ACK from the peer + volatile uint64_t m_ullLastRspAckTime; // time stamp of last ACK from the peer #endif /* SRT_ENABLE_FASTREXMIT */ #ifdef SRT_FIX_KEEPALIVE - volatile uint64_t m_ullLastSndTime; // time stamp of last data/ctrl sent + volatile uint64_t m_ullLastSndTime; // time stamp of last data/ctrl sent #endif /* SRT_FIX_KEEPALIVE */ - uint64_t m_ullMinNakInt; // NAK timeout lower bound; too small value can cause unnecessary retransmission - uint64_t m_ullMinExpInt; // timeout lower bound threshold: too small timeout can cause problem + uint64_t m_ullMinNakInt; // NAK timeout lower bound; too small value can cause unnecessary retransmission + uint64_t m_ullMinExpInt; // timeout lower bound threshold: too small timeout can cause problem - int m_iPktCount; // packet counter for ACK - int m_iLightACKCount; // light ACK counter + int m_iPktCount; // packet counter for ACK + int m_iLightACKCount; // light ACK counter - uint64_t m_ullTargetTime; // scheduled time of next packet sending + uint64_t m_ullTargetTime; // scheduled time of next packet sending - void checkTimers(); + void checkTimers(); private: // for UDP multiplexer - CSndQueue* m_pSndQueue; // packet sending queue - CRcvQueue* m_pRcvQueue; // packet receiving queue - sockaddr* m_pPeerAddr; // peer address - uint32_t m_piSelfIP[4]; // local UDP IP address - CSNode* m_pSNode; // node information for UDT list used in snd queue - CRNode* m_pRNode; // node information for UDT list used in rcv queue + CSndQueue* m_pSndQueue; // packet sending queue + CRcvQueue* m_pRcvQueue; // packet receiving queue + sockaddr* m_pPeerAddr; // peer address + uint32_t m_piSelfIP[4]; // local UDP IP address + CSNode* m_pSNode; // node information for UDT list used in snd queue + CRNode* m_pRNode; // node information for UDT list used in rcv queue private: // for epoll - std::set m_sPollID; // set of epoll ID to trigger - void addEPoll(const int eid); - void removeEPoll(const int eid); + std::set m_sPollID; // set of epoll ID to trigger + void addEPoll(const int eid); + void removeEPoll(const int eid); }; diff --git a/srtcore/crypto.cpp b/srtcore/crypto.cpp new file mode 100644 index 000000000..3b94c5144 --- /dev/null +++ b/srtcore/crypto.cpp @@ -0,0 +1,700 @@ +/***************************************************************************** + * SRT - Secure, Reliable, Transport + * Copyright (c) 2017 Haivision Systems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * + *****************************************************************************/ + +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +#include +#include +#include +#include + +#include "udt.h" +#include "utilities.h" +#include +#include "crypto.h" +#include "logging.h" +#include "core.h" + +extern logging::Logger mglog, dlog; + +#define SRT_MAX_KMRETRY 10 + +//#define SRT_CMD_KMREQ 3 /* HaiCryptTP SRT Keying Material */ +//#define SRT_CMD_KMRSP 4 /* HaiCryptTP SRT Keying Material ACK */ +#define SRT_CMD_KMREQ_SZ HCRYPT_MSG_KM_MAX_SZ /* */ +#if SRT_CMD_KMREQ_SZ > SRT_CMD_MAXSZ +#error SRT_CMD_MAXSZ too small +#endif +/* Key Material Request (Network Order) + See HaiCryptTP SRT (hcrypt_xpt_srt.c) +*/ + +// 10* HAICRYPT_DEF_KM_PRE_ANNOUNCE +const int SRT_CRYPT_KM_PRE_ANNOUNCE = 0x10000; + +static std::string KmStateStr(SRT_KM_STATE state) +{ + switch (state) + { +#define TAKE(val) case SRT_KM_S_##val : return #val + TAKE(UNSECURED); + TAKE(SECURED); + TAKE(SECURING); + TAKE(NOSECRET); + TAKE(BADSECRET); +#undef TAKE + default: return "???"; + } +} + +static std::string FormatKmMessage(std::string hdr, bool isrcv, int cmd, size_t srtlen, SRT_KM_STATE agt_state, SRT_KM_STATE peer_state) +{ + std::ostringstream os; + os << hdr << ": cmd=" << cmd << "(" << (cmd == SRT_CMD_KMREQ ? "KMREQ":"KMRSP") <<") len=" + << size_t(srtlen*sizeof(int32_t)) << " "; + if ( !isrcv ) + { + os << "Snd/PeerKmState=" << KmStateStr(agt_state) << "/" << KmStateStr(peer_state); + } + else + { + os << "Peer/RcvKmState=" << KmStateStr(peer_state) << "/" << KmStateStr(agt_state); + } + + return os.str(); +} + +void CCryptoControl::updateKmState(int cmd, size_t srtlen) +{ + if (cmd == SRT_CMD_KMREQ) + { + if ( SRT_KM_S_UNSECURED == m_iSndKmState) + { + m_iSndKmState = SRT_KM_S_SECURING; + m_iSndPeerKmState = SRT_KM_S_SECURING; + } + LOGP(mglog.Note, FormatKmMessage("sndSrtMsg", false, cmd, srtlen, m_iSndKmState, m_iSndPeerKmState)); + } + else + { + LOGP(mglog.Note, FormatKmMessage("sndSrtMsg", true, cmd, srtlen, m_iRcvKmState, m_iRcvPeerKmState)); + } +} + + +int CCryptoControl::processSrtMsg_KMREQ(const uint32_t* srtdata, size_t bytelen, uint32_t* srtdata_out, ref_t r_srtlen, int hsv) +{ + size_t& srtlen = r_srtlen; + //Receiver + /* All 32-bit msg fields swapped on reception + * But HaiCrypt expect network order message + * Re-swap to cancel it. + */ + srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]); + HtoNLA(srtdata_out, srtdata, srtlen); + unsigned char* kmdata = reinterpret_cast(srtdata_out); + + std::vector kmcopy(kmdata, kmdata + bytelen); + + // The side that has received KMREQ is always an HSD_RESPONDER, regardless of + // what has called this function. The HSv5 handshake only enforces bidirectional + // connection. + + bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; + + // Local macro to return rejection appropriately. + // For HSv5 this function is part of the general handshake, so this should + // reject the connection. + // For HSv4 this is received by a custom message after the connection is + // established, so in this case the rejection response must be sent as KMRSP. +#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { srtlen = 1; goto HSv4_ErrorReport; } + + int rc = HAICRYPT_OK; // needed before 'goto' run from KMREQ_RESULT_REJECTION macro + size_t sek_len = 0; + + // What we have to do: + // If encryption is on (we know that by having m_KmSecret nonempty), create + // the crypto context (if bidirectional, create for both sending and receiving). + // Both crypto contexts should be set with the same length of the key. + // The problem with interpretinting this should be reported as SRT_CMD_NONE, + // should be appropriately handled by the caller, as it expects that this + // function normally return SRT_CMD_KMRSP. + if ( bytelen <= HCRYPT_MSG_KM_OFS_SALT ) //Sanity on message + { + LOGC(mglog.Error) << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT; + KMREQ_RESULT_REJECTION(); + } + + // This below probably shouldn't happen because KMREQ isn't expected to be + // received when Agent does not declare encryption. + if (m_KmSecret.len == 0) //We have a shared secret <==> encryption is on + { + LOGC(mglog.Error) << "processSrtMsg_KMREQ: Agent does not declare encryption - REJECTING!"; + KMREQ_RESULT_REJECTION(); + } + + LOGC(mglog.Debug) << "KMREQ: getting SEK and creating receiver crypto"; + sek_len = hcryptMsg_KM_GetSekLen(kmdata); + if ( sek_len == 0 ) + { + LOGC(mglog.Error) << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!"; + KMREQ_RESULT_REJECTION(); + } + + m_iRcvKmKeyLen = sek_len; + if (!createCryptoCtx(Ref(m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX)) + { + LOGC(mglog.Error) << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject..."; + KMREQ_RESULT_REJECTION(); + } + + if (bidirectional) + { + m_iSndKmKeyLen = m_iRcvKmKeyLen; + if (!createCryptoCtx(Ref(m_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX)) + { + LOGC(mglog.Error) << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - must reject..."; + KMREQ_RESULT_REJECTION(); + } + } + + if (m_iRcvPeerKmState == SRT_KM_S_UNSECURED) + { + m_iRcvPeerKmState = SRT_KM_S_SECURING; + if (0 == m_KmSecret.len) + m_iRcvKmState = SRT_KM_S_NOSECRET; + else + m_iRcvKmState = SRT_KM_S_SECURING; + LOGC(mglog.Debug) << "processSrtMsg_KMREQ: RCV unsecured - changing state to " + << (m_iRcvKmState == SRT_KM_S_SECURING ? "SECURING" : "NOSECRET"); + } + + rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0); + switch(rc >= 0 ? HAICRYPT_OK : rc) + { + case HAICRYPT_OK: + m_iRcvPeerKmState = SRT_KM_S_SECURED; + m_iRcvKmState = SRT_KM_S_SECURED; + LOGC(mglog.Debug) << "KMREQ/rcv: (snd) Rx process successful - SECURED"; + //Send back the whole message to confirm + break; + case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key + m_iRcvKmState = SRT_KM_S_BADSECRET; + //Send status KMRSP message to tel error + srtlen = 1; + LOGC(mglog.Error) << "KMREQ/rcv: (snd) Rx process failure - BADSECRET"; + break; + case HAICRYPT_ERROR: //Other errors + default: + m_iRcvKmState = SRT_KM_S_SECURING; + //Send status KMRSP message to tel error + srtlen = 1; + LOGC(mglog.Error) << "KMREQ/rcv: (snd) Rx process failure - SECURING"; + break; + } + + LOGP(mglog.Note, FormatKmMessage("processSrtMsg_KMREQ", true, SRT_CMD_KMREQ, bytelen, m_iRcvKmState, m_iRcvPeerKmState)); + + if (m_iRcvKmState == SRT_KM_S_SECURED && bidirectional ) + { + // For HSv5, the above error indication should turn into rejection reaction. + if ( srtlen == 1 ) + return SRT_CMD_NONE; + + m_iSndKmKeyLen = m_iRcvKmKeyLen; + if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto)) + { + LOGC(mglog.Error) << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - must reject..."; + KMREQ_RESULT_REJECTION(); + } + if (m_iSndPeerKmState == SRT_KM_S_UNSECURED) + { + m_iSndPeerKmState = SRT_KM_S_SECURING; + if (0 == m_KmSecret.len) + m_iSndKmState = SRT_KM_S_NOSECRET; + else + m_iSndKmState = SRT_KM_S_SECURING; + LOGC(mglog.Debug) << "processSrtMsg_KMREQ: SND unsecured - changing state to " + << (m_iSndKmState == SRT_KM_S_SECURING ? "SECURING" : "NOSECRET"); + } + + LOGP(mglog.Note, FormatKmMessage("processSrtMsg_KMREQ", false, SRT_CMD_KMREQ, bytelen, m_iSndKmState, m_iSndPeerKmState)); + + if ( srtlen == 1 ) + return SRT_CMD_NONE; + } + + return SRT_CMD_KMRSP; + +HSv4_ErrorReport: + srtdata_out[SRT_KMR_KMSTATE] = m_iRcvKmState; + return SRT_CMD_KMRSP; +#undef KMREQ_RESULT_REJECTION +} + +int CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int hsv) +{ + /* All 32-bit msg fields (if present) swapped on reception + * But HaiCrypt expect network order message + * Re-swap to cancel it. + */ + uint32_t srtd[SRTDATA_MAXSIZE]; + size_t srtlen = len/sizeof(uint32_t); + HtoNLA(srtd, srtdata, srtlen); + + bool bidirectional = hsv > CUDT::HS_VERSION_UDT4; + + if (srtlen == 1) // Error report. Set accordingly. + { + m_iSndPeerKmState = SRT_KM_STATE(srtd[SRT_KMR_KMSTATE]); /* Bad or no passphrase */ + m_SndKmMsg[0].iPeerRetry = 0; + m_SndKmMsg[1].iPeerRetry = 0; + LOGC(mglog.Error) << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_iSndPeerKmState); + } + else + { + LOGC(mglog.Debug) << "processSrtMsg_KMRSP: received key response len=" << len; + // XXX INSECURE << ": [" << FormatBinaryString((uint8_t*)srtd, len) << "]"; + bool key1 = getKmMsg_acceptResponse(0, srtd, len); + bool key2 = true; + if ( !key1 ) + key2 = getKmMsg_acceptResponse(1, srtd, len); // <--- NOTE SEQUENCING! + + if (key1 || key2) + { + m_iSndKmState = SRT_KM_S_SECURED; + m_iSndPeerKmState = SRT_KM_S_SECURED; + LOGC(mglog.Debug) << "processSrtMsg_KMRSP: KM response matches key " << (key1 ? 1 : 2); + } + else + { + LOGC(mglog.Error) << "processSrtMsg_KMRSP: KM response key matches no key"; + /* XXX INSECURE + LOGC(mglog.Error) << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len) + << "] matches no key 0=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[0].Msg, m_SndKmMsg[0].MsgLen) + << "] 1=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[1].Msg, m_SndKmMsg[1].MsgLen) << "]"; + */ + } + } + + if ( bidirectional ) + { + m_iRcvKmState = m_iSndKmState; + m_iRcvPeerKmState = m_iSndPeerKmState; + } + + LOGP(mglog.Note, FormatKmMessage("processSrtMsg_KMRSP", false, SRT_CMD_KMRSP, len, m_iSndKmState, m_iSndPeerKmState)); + + return SRT_CMD_NONE; +} + +void CCryptoControl::sendKeysToPeer(Whether2RegenKm regen) +{ + // XXX This must be done somehow differently for bidi + if ( !m_hSndCrypto ) + return; + uint64_t now; + /* + * Crypto Key Distribution to peer: + * If... + * - we want encryption; and + * - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and + * - and did not get answer back from peer; and + * - last sent Keying Material req should have been replied (RTT*1.5 elapsed); + * then (re-)send handshake request. + */ + if ( ((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0)) + && ((m_SndKmLastTime + ((m_parent->RTT() * 3)/2)) <= (now = CTimer::getTime()))) + { + for (int ki = 0; ki < 2; ki++) + { + if (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0) + { + m_SndKmMsg[ki].iPeerRetry--; + m_SndKmLastTime = now; + m_parent->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen/sizeof(uint32_t)); + } + } + } + + if (regen) + regenCryptoKm(true, m_parent->handshakeVersion() > CUDT::HS_VERSION_UDT4); // regenerate and send +} + +void CCryptoControl::regenCryptoKm(bool sendit, bool bidirectional) +{ + if (!m_hSndCrypto) + return; + + void *out_p[2]; + size_t out_len_p[2]; + int nbo = HaiCrypt_Tx_ManageKeys(m_hSndCrypto, out_p, out_len_p, 2); + int sent = 0; + + LOGC(mglog.Debug) << "regenCryptoKm: regenerating crypto keys nbo=" << nbo; + + for (int i = 0; i < nbo && i < 2; i++) + { + /* + * New connection keying material + * or regenerated after crypto_cfg.km_refresh_rate_pkt packets . + * Send to peer + */ + // XXX Need to make it clearer and less hardcoded values + int ki = hcryptMsg_KM_GetKeyIndex((unsigned char *)(out_p[i])) & 0x1; + if ((out_len_p[i] != m_SndKmMsg[ki].MsgLen) + || (0 != memcmp(out_p[i], m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen))) + { + + uint8_t* oldkey = m_SndKmMsg[ki].Msg; + LOGC(mglog.Debug).form("new key[%d] len=%zd,%zd msg=%0x,%0x\n", + ki, out_len_p[i], m_SndKmMsg[ki].MsgLen, + *(int32_t *)out_p[i], + *(int32_t *)oldkey); + /* New Keying material, send to peer */ + memcpy(m_SndKmMsg[ki].Msg, out_p[i], out_len_p[i]); + m_SndKmMsg[ki].MsgLen = out_len_p[i]; + m_SndKmMsg[ki].iPeerRetry = SRT_MAX_KMRETRY; + + if (bidirectional) + { + // "Send" this key also to myself, just to be applied to the receiver crypto, + // exactly the same way how this key is interpreted on the peer side into its receiver crypto + int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen, NULL, NULL, 0); + if ( rc < 0 ) + { + LOGC(mglog.Fatal) << "regenCryptoKm: IPE: applying key generated in snd crypto into rcv crypto: failed code=" << rc; + // The party won't be able to decrypt incoming data! + // Not sure if anything has to be reported. + } + } + + if (sendit) + { + m_parent->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen/sizeof(uint32_t)); + sent++; + } + } + } + if (sent) + m_SndKmLastTime = CTimer::getTime(); +} + +CCryptoControl::CCryptoControl(CUDT* parent, UDTSOCKET id): +m_parent(parent), // should be initialized in createCC() +m_SocketID(id), +m_iSndKmKeyLen(0), +m_iRcvKmKeyLen(0), +m_iSndKmState(SRT_KM_S_UNSECURED), +m_iSndPeerKmState(SRT_KM_S_UNSECURED), +m_iRcvKmState(SRT_KM_S_UNSECURED), +m_iRcvPeerKmState(SRT_KM_S_UNSECURED), +m_bDataSender(false) +{ + + m_KmSecret.len = 0; + //send + m_SndKmLastTime = 0; + m_SndKmMsg[0].MsgLen = 0; + m_SndKmMsg[0].iPeerRetry = 0; + m_SndKmMsg[1].MsgLen = 0; + m_SndKmMsg[1].iPeerRetry = 0; + m_hSndCrypto = NULL; + //recv + m_hRcvCrypto = NULL; +} + +bool CCryptoControl::init(HandshakeSide side, bool bidirectional) +{ + // NOTE: initiator creates m_hSndCrypto. When bidirectional, + // it creates also m_hRcvCrypto with the same key length. + // Acceptor creates nothing - it will create appropriate + // contexts when receiving KMREQ from the initiator. + + LOGC(mglog.Debug) << "CCryptoControl::init: HS SIDE:" + << (side == HSD_INITIATOR ? "INITIATOR" : "RESPONDER") + << " DIRECTION:" << (bidirectional ? "BOTH" : (side == HSD_INITIATOR) ? "SENDER" : "RECEIVER"); + + if (bidirectional) + m_bDataSender = true; // both directions on, so you are always a sender + + if ( side == HSD_INITIATOR ) + { + if (m_iSndKmKeyLen > 0) + { + bool ok = createCryptoCtx(Ref(m_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX); + LOGC(mglog.Debug) << "CCryptoControl::init: creating SND crypto context: " << ok; + + if (ok && bidirectional) + { + m_iRcvKmKeyLen = m_iSndKmKeyLen; + int st = HaiCrypt_Clone(m_hSndCrypto, HAICRYPT_CRYPTO_DIR_RX, &m_hRcvCrypto); + LOGC(mglog.Debug) << "CCryptoControl::init: creating CLONED RCV crypto context: status=" << st; + ok = st == 0; + } + + if (!ok) + return false; + + regenCryptoKm(false, bidirectional); // regen, but don't send. + } + else + { + LOGC(mglog.Debug) << "CCryptoControl::init: CAN'T CREATE crypto: key length for SND = " << m_iSndKmKeyLen; + } + } + else + { + LOGC(mglog.Debug) << "CCryptoControl::init: NOT creating crypto contexts - will be created upon reception of KMREQ"; + } + + return true; +} + +void CCryptoControl::close() +{ + /* Wipeout secrets */ + memset(&m_KmSecret, 0, sizeof(m_KmSecret)); +} + +std::string CCryptoControl::CONID() const +{ + if ( m_SocketID == 0 ) + return ""; + + std::ostringstream os; + os << "%" << m_SocketID << ":"; + + return os.str(); +} + +static std::string CryptoFlags(int flg) +{ + using namespace std; + + vector f; + if (flg & HAICRYPT_CFG_F_CRYPTO) + f.push_back("crypto"); + if (flg & HAICRYPT_CFG_F_TX) + f.push_back("TX"); + if (flg & HAICRYPT_CFG_F_FEC) + f.push_back("fec"); + + ostringstream os; + copy(f.begin(), f.end(), ostream_iterator(os, "|")); + return os.str(); +} + +bool CCryptoControl::createCryptoCtx(ref_t hCrypto, size_t keylen, HaiCrypt_CryptoDir cdir) +{ + //HaiCrypt_Handle& hCrypto (rh); + + if (hCrypto) + { + // XXX You can check here if the existing handle represents + // a correctly defined crypto. But this doesn't seem to be + // necessary - the whole CCryptoControl facility seems to be valid only + // within the frames of one connection. + return true; + } + + if ((m_KmSecret.len <= 0) || (keylen <= 0)) + { + LOGC(mglog.Error) << CONID() << "cryptoCtx: missing secret (" << m_KmSecret.len << ") or key length (" << keylen << ")"; + return false; + } + + HaiCrypt_Cfg crypto_cfg; + memset(&crypto_cfg, 0, sizeof(crypto_cfg)); + + crypto_cfg.flags = HAICRYPT_CFG_F_CRYPTO | (cdir == HAICRYPT_CRYPTO_DIR_TX ? HAICRYPT_CFG_F_TX : 0); + crypto_cfg.xport = HAICRYPT_XPT_SRT; + crypto_cfg.cipher = HaiCryptCipher_OpenSSL_EVP(); + crypto_cfg.key_len = (size_t)keylen; + crypto_cfg.data_max_len = HAICRYPT_DEF_DATA_MAX_LENGTH; //MTU + crypto_cfg.km_tx_period_ms = 0;//No HaiCrypt KM inject period, handled in SRT; + crypto_cfg.km_refresh_rate_pkt = HAICRYPT_DEF_KM_REFRESH_RATE; + crypto_cfg.km_pre_announce_pkt = SRT_CRYPT_KM_PRE_ANNOUNCE; + crypto_cfg.secret = m_KmSecret; + //memcpy(&crypto_cfg.secret, &m_KmSecret, sizeof(crypto_cfg.secret)); + + LOGC(mglog.Debug) << "CRYPTO CFG: flags=" << CryptoFlags(crypto_cfg.flags) << " xport=" << crypto_cfg.xport << " cipher=" << crypto_cfg.cipher + << " keylen=" << crypto_cfg.key_len << " passphrase_length=" << crypto_cfg.secret.len; + + if (HaiCrypt_Create(&crypto_cfg, &hCrypto.get()) != HAICRYPT_OK) + { + LOGC(mglog.Error) << CONID() << "cryptoCtx: could not create " << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " crypto ctx"; + return false; + } + + LOGC(mglog.Debug) << CONID() << "cryptoCtx: CREATED crypto for dir=" << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " keylen=" << keylen; + + return true; +} + + +HaiCrypt_Handle CCryptoControl::getRcvCryptoCtx() +{ + /* + * We are receiver and + * have detected that incoming packets are encrypted + */ + if (SRT_KM_S_SECURED == m_iRcvKmState) + { + return(m_hRcvCrypto); //Return working crypto only + } + if (SRT_KM_S_UNSECURED == m_iRcvPeerKmState) + { + m_iRcvPeerKmState = SRT_KM_S_SECURING; + if (0 != m_KmSecret.len) + { // We have a passphrase, wait for keying material + m_iRcvKmState = SRT_KM_S_SECURING; + } + else + { // We don't have a passphrase, will never decrypt + m_iRcvKmState = SRT_KM_S_NOSECRET; + } + } + + LOGC(mglog.Warn) << "getRcvCryptoCtx: NOT RCV SECURE. States agent=" << KmStateStr(m_iRcvKmState) << " peer=" << KmStateStr(m_iRcvPeerKmState); + return(NULL); +} + +EncryptionStatus CCryptoControl::encrypt(ref_t r_packet) +{ + // Encryption not enabled - do nothing. + if ( getSndCryptoFlags() == EK_NOENC ) + return ENCS_CLEAR; + + CPacket& packet = r_packet; + int rc = HaiCrypt_Tx_Data(m_hSndCrypto, (uint8_t*)packet.getHeader(), (uint8_t*)packet.m_pcData, packet.getLength()); + if (rc < 0) + { + return ENCS_FAILED; + } + else if ( rc > 0 ) + { + // XXX what happens if the encryption is said to be "succeeded", + // but the length is 0? Shouldn't this be treated as unwanted? + packet.setLength(rc); + } + + return ENCS_CLEAR; +} + +EncryptionStatus CCryptoControl::decrypt(ref_t r_packet) +{ + CPacket& packet = r_packet; + + if (packet.getMsgCryptoFlags() == EK_NOENC) + { + LOGC(mglog.Debug) << "CPacket::decrypt: packet not encrypted"; + return ENCS_CLEAR; // not encrypted, no need do decrypt, no flags to be modified + } + + // If not secured, preted for securing, but make + // decryption failed. + if (m_iRcvKmState != SRT_KM_S_SECURED) + { + if (m_iRcvPeerKmState == SRT_KM_S_UNSECURED) + { + m_iRcvPeerKmState = SRT_KM_S_SECURING; + if (m_KmSecret.len != 0) + { // We have a passphrase, wait for keying material + m_iRcvKmState = SRT_KM_S_SECURING; + } + else + { // We don't have a passphrase, will never decrypt + m_iRcvKmState = SRT_KM_S_NOSECRET; + } + + LOGC(mglog.Error) << "DECRYPTION FAILED: KM not configured or not yet received"; + return ENCS_FAILED; + } + } + + int rc = HaiCrypt_Rx_Data(m_hRcvCrypto, (uint8_t *)packet.getHeader(), (uint8_t *)packet.m_pcData, packet.getLength()); + if ( rc <= 0 ) + { + LOGC(mglog.Debug) << "decrypt ERROR: HaiCrypt_Rx_Data failure=" << rc << " - returning failed decryption"; + // -1: decryption failure + // 0: key not received yet + return ENCS_FAILED; + } + // Otherwise: rc == decrypted text length. + packet.setLength(rc); /* In case clr txt size is different from cipher txt */ + + // Decryption succeeded. Update flags. + packet.setMsgCryptoFlags(EK_NOENC); + + LOGC(mglog.Debug) << "decrypt: successfully decrypted, resulting length=" << rc; + return ENCS_CLEAR; +} + + +CCryptoControl::~CCryptoControl() +{ + if (m_hSndCrypto) + { + HaiCrypt_Close(m_hSndCrypto); + } + + if (m_hRcvCrypto) + { + HaiCrypt_Close(m_hRcvCrypto); + } +} + + +std::string SrtFlagString(int32_t flags) +{ +#define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) + + std::string output; + static std::string namera[] = { "TSBPD-snd", "TSBPD-rcv", "haicrypt", "TLPktDrop", "NAKReport", "ReXmitFlag" }; + + size_t i = 0; + for ( ; i < LEN(namera); ++i ) + { + if ( (flags & 1) == 1 ) + { + output += "+" + namera[i] + " "; + } + else + { + output += "-" + namera[i] + " "; + } + + flags >>= 1; + //if ( flags == 0 ) + // break; + } + +#undef LEN + + if ( flags != 0 ) + { + output += "+unknown"; + } + + return output; +} diff --git a/srtcore/crypto.h b/srtcore/crypto.h new file mode 100644 index 000000000..36c501286 --- /dev/null +++ b/srtcore/crypto.h @@ -0,0 +1,182 @@ +/***************************************************************************** + * SRT - Secure, Reliable, Transport + * Copyright (c) 2017 Haivision Systems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * + *****************************************************************************/ + +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +#ifndef INC__CRYPTO_H +#define INC__CRYPTO_H + +#include +#include + +// UDT +#include "udt.h" +#include "packet.h" +#include "utilities.h" + +#include +#include + + +// For KMREQ/KMRSP. Only one field is used. +const size_t SRT_KMR_KMSTATE = 0; + +#define SRT_CMD_MAXSZ HCRYPT_MSG_KM_MAX_SZ /* Maximum SRT custom messages payload size (bytes) */ +const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ/sizeof(int32_t); + +enum Whether2RegenKm {DONT_REGEN_KM = 0, REGEN_KM = 1}; + +class CCryptoControl +{ +//public: + class CUDT* m_parent; + UDTSOCKET m_SocketID; + + size_t m_iSndKmKeyLen; //Key length + size_t m_iRcvKmKeyLen; //Key length from rx KM + + // Temporarily allow these to be accessed. +public: + SRT_KM_STATE m_iSndKmState; //Sender Km State + SRT_KM_STATE m_iSndPeerKmState; //Sender's peer (receiver) Km State + SRT_KM_STATE m_iRcvKmState; //Receiver Km State + SRT_KM_STATE m_iRcvPeerKmState; //Receiver's peer (sender) Km State + +private: + bool m_bDataSender; //Sender side (for crypto, TsbPD handshake) + + HaiCrypt_Secret m_KmSecret; //Key material shared secret + // Sender + uint64_t m_SndKmLastTime; + struct { + unsigned char Msg[HCRYPT_MSG_KM_MAX_SZ]; + size_t MsgLen; + int iPeerRetry; + } m_SndKmMsg[2]; + HaiCrypt_Handle m_hSndCrypto; + // Receiver + HaiCrypt_Handle m_hRcvCrypto; + +public: + +private: + + void regenCryptoKm(bool sendit, bool bidirectional); + +public: + + size_t KeyLen() { return m_iSndKmKeyLen; } + + // Needed for CUDT + void updateKmState(int cmd, size_t srtlen); + + // Detailed processing + int processSrtMsg_KMREQ(const uint32_t* srtdata, size_t len, uint32_t* srtdata_out, ref_t r_srtlen, int hsv); + int processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int hsv); + + const unsigned char* getKmMsg_data(size_t ki) const { return m_SndKmMsg[ki].Msg; } + size_t getKmMsg_size(size_t ki) const { return m_SndKmMsg[ki].MsgLen; } + bool getKmMsg_needSend(size_t ki) const + { + return (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0); + } + + void getKmMsg_markSent(size_t ki) + { + m_SndKmMsg[ki].iPeerRetry--; + m_SndKmLastTime = CTimer::getTime(); + } + + bool getKmMsg_acceptResponse(size_t ki, const uint32_t* srtmsg, size_t bytesize) + { + if ( m_SndKmMsg[ki].MsgLen == bytesize + && 0 == memcmp(m_SndKmMsg[ki].Msg, srtmsg, m_SndKmMsg[ki].MsgLen)) + { + m_SndKmMsg[ki].iPeerRetry = 0; + return true; + } + return false; + } + + CCryptoControl(CUDT* parent, UDTSOCKET id); + + std::string CONID() const; + + bool init(HandshakeSide, bool); + void close(); + + // This function is used in: + // - HSv4 (initial key material exchange - in HSv5 it's attached to handshake) + // - case of key regeneration, which should be then exchanged again + void sendKeysToPeer(Whether2RegenKm regen); + + + void setCryptoSecret(const HaiCrypt_Secret& secret) + { + m_KmSecret = secret; + //memcpy(&m_KmSecret, &secret, sizeof(m_KmSecret)); + } + + void setSndCryptoKeylen(size_t keylen) + { + m_iSndKmKeyLen = keylen; + m_bDataSender = true; + } + + void setCryptoKeylen(size_t keylen) + { + m_iSndKmKeyLen = keylen; + m_iRcvKmKeyLen = keylen; + } + + bool createCryptoCtx(ref_t rh, size_t keylen, HaiCrypt_CryptoDir tx); + + HaiCrypt_Handle getSndCryptoCtx() const + { + return(m_hSndCrypto); + } + + HaiCrypt_Handle getRcvCryptoCtx(); + + int getSndCryptoFlags() const + { + return(m_hSndCrypto ? HaiCrypt_Tx_GetKeyFlags(m_hSndCrypto) : 0); + } + + /// Encrypts the packet. If encryption is not turned on, it + /// does nothing. If the encryption is not correctly configured, + /// the encryption will fail. + /// XXX Encryption flags in the PH_MSGNO + /// field in the header must be correctly set before calling. + EncryptionStatus encrypt(ref_t r_packet); + + /// Decrypts the packet. If the packet has ENCKEYSPEC part + /// in PH_MSGNO set to EK_NOENC, it does nothing. It decrypts + /// only if the encryption correctly configured, otherwise it + /// fails. After successful decryption, the ENCKEYSPEC part + // in PH_MSGNO is set to EK_NOENC. + EncryptionStatus decrypt(ref_t r_packet); + + ~CCryptoControl(); +}; + +#endif // SRT_CONGESTION_CONTROL_H diff --git a/srtcore/csrtcc.cpp b/srtcore/csrtcc.cpp deleted file mode 100644 index 1dcc6ea54..000000000 --- a/srtcore/csrtcc.cpp +++ /dev/null @@ -1,935 +0,0 @@ -/***************************************************************************** - * SRT - Secure, Reliable, Transport - * Copyright (c) 2017 Haivision Systems Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; If not, see - * - *****************************************************************************/ - -/***************************************************************************** -written by - Haivision Systems Inc. - *****************************************************************************/ - -#include -#include - -#include -#include "csrtcc.h" -#include "logging.h" - -extern logging::Logger mglog, dlog; - -#define SRT_CMD_MAXSZ HCRYPT_MSG_KM_MAX_SZ /* Maximum SRT custom messages payload size (bytes) */ -#define SRT_MAX_HSRETRY 10 /* Maximum SRT handshake retry */ - -//#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 -*/ - -#define SRT_MAX_KMRETRY 10 - -//#define SRT_CMD_KMREQ 3 /* HaiCryptTP SRT Keying Material */ -//#define SRT_CMD_KMRSP 4 /* HaiCryptTP SRT Keying Material ACK */ -#define SRT_CMD_KMREQ_SZ HCRYPT_MSG_KM_MAX_SZ /* */ -#if SRT_CMD_KMREQ_SZ > SRT_CMD_MAXSZ -#error SRT_CMD_MAXSZ too small -#endif -/* Key Material Request (Network Order) - See HaiCryptTP SRT (hcrypt_xpt_srt.c) -*/ - - - -void CSRTCC::sendSrtMsg(int cmd, int32_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. - int32_t srtdata[SRTDATA_SIZE]; - - int srtlen = 0; - - switch(cmd){ - case SRT_CMD_HSREQ: - memset(srtdata, 0, sizeof(srtdata)); - -#ifdef SRT_VERSION_MAJ2 - if (SRT_VERSION_MAJ(m_PeerSrtVersion) == SRT_VERSION_MAJ1) - { - //>>duB: fix that to not set m_SrtVersion under normal operations (version downgrade) - //>>only set to test version handshake - //m_SrtVersion = SRT_VERSION_1XX; //highest compatible version 1 - // move 1.x.x handshake code here when default becomes 2.x.x - //break; //>>fall through until 2.x.x implemented - } - else -#endif - /* - XXX Do some version renegotiation if needed; - The "unknown version" may be used for something else, - currently not predicted to happen. - if (SRT_VERSION_MAJ(m_PeerSrtVersion) == SRT_VERSION_UNK) - { - // Some fallback... - srtdata[SRT_HS_VERSION] = 0; // Rejection - } - else - */ - { - /* Current version (1.x.x) SRT handshake */ - srtdata[SRT_HS_VERSION] = m_SrtVersion; /* Required version */ - if (m_bSndTsbPdMode) - { - /* - * Sent data is real-time, use Time-based Packet Delivery, - * set option bit and configured delay - */ - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND; - srtdata[SRT_HS_EXTRAS] = SRT_HS_EXTRAS_LO::wrap(m_TsbPdDelay); - } - - srtdata[SRT_HS_FLAGS] |= SRT_OPT_HAICRYPT; - srtlen = SRT_HS__SIZE; - - // I support SRT_OPT_REXMITFLG. Do you? - srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; - - LOGC(mglog.Note).form( "sndSrtMsg: cmd=%d(HSREQ) len=%d vers=0x%x opts=0x%x delay=%d\n", - cmd, (int)(srtlen * sizeof(int32_t)), - srtdata[SRT_HS_VERSION], - srtdata[SRT_HS_FLAGS], - SRT_HS_EXTRAS_LO::unwrap(srtdata[SRT_HS_EXTRAS])); - } - break; - - case SRT_CMD_HSRSP: - memset(srtdata, 0, sizeof(srtdata)); - - -#ifdef SRT_VERSION_MAJ2 - if (SRT_VERSION_MAJ(m_PeerSrtVersion) == SRT_VERSION_MAJ1) - { - //>>duB: fix that to not set m_SrtVersion under normal operations (version downgrade) - //>>only set to test version handshake - //m_SrtVersion = SRT_VERSION_1XX; //highest compatible version 1 - // move 1.x.x handshake code here when default becomes 2.x.x - //break; //>>fall through until 2.x.x implemented - } - else -#endif - /* - if (SRT_VERSION_MAJ(m_PeerSrtVersion) == SRT_VERSION_UNK) - { - // Some fallback... - srtdata[SRT_HS_VERSION] = 0; // Rejection - } - else - */ - { - /* Current version (1.x.x) SRT handshake */ - srtdata[SRT_HS_VERSION] = m_SrtVersion; /* Required version */ - if (0 != m_RcvPeerStartTime) - { - /* - * We got and transposed peer start time (HandShake request timestamp), - * we can support Timestamp-based Packet Delivery - */ - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV; -#ifdef SRT_ENABLE_TLPKTDROP - if ((m_SrtVersion >= SrtVersion(1, 0, 5)) && m_bRcvTLPktDrop) - srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP; -#endif - srtdata[SRT_HS_EXTRAS] = SRT_HS_EXTRAS_LO::wrap(m_RcvTsbPdDelay); - } - - srtdata[SRT_HS_FLAGS] |= SRT_OPT_HAICRYPT; - -#ifdef SRT_ENABLE_NAKREPORT - if ((m_SrtVersion >= SrtVersion(1, 1, 0)) && m_bRcvNakReport) - { - 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_PeerSrtVersion <= SrtVersion(1, 0, 7)) - srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP; - } -#endif - - if ( m_SrtVersion >= SrtVersion(1, 2, 0) ) - { - // Request that the rexmit bit be used as a part of msgno. - srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG; - LOGC(mglog.Debug).form("HS RP1: I UNDERSTAND REXMIT flag" ); - } - else - { - // Since this is now in the code, it can occur only in case when you change the - // version specification in the build configuration. - LOGC(mglog.Debug).form("HS RP1: I DO NOT UNDERSTAND REXMIT flag" ); - } - srtlen = SRT_HS__SIZE; - - LOGC(mglog.Note).form( "sndSrtMsg: cmd=%d(HSRSP) len=%d vers=0x%x opts=0x%x delay=%d\n", - cmd, (int)(srtlen * sizeof(int32_t)), srtdata[SRT_HS_VERSION], srtdata[SRT_HS_FLAGS], srtdata[SRT_HS_EXTRAS]); - } - break; - -#ifdef SRT_ENABLE_HAICRYPT - case SRT_CMD_KMREQ: //Sender - srtlen = srtlen_in; - /* Msg already in network order - * But CChannel:sendto will swap again (assuming 32-bit fields) - * Pre-swap to cancel it. - */ - for (int i = 0; i < srtlen; ++ i) srtdata[i] = htonl(srtdata_in[i]); - - if (SRT_KM_S_UNSECURED == m_iSndKmState) - { - m_iSndKmState = SRT_KM_S_SECURING; - m_iSndPeerKmState = SRT_KM_S_SECURING; - } - LOGC(mglog.Note).form( "sndSrtMsg: cmd=%d(KMREQ) len=%d Snd/PeerKmState=%s/%s\n", - cmd, (int)(srtlen * sizeof(int32_t)), - SRT_KM_S_SECURED == m_iSndKmState ? "secured" - : SRT_KM_S_SECURING == m_iSndKmState ? "securing" : "unsecured", - SRT_KM_S_SECURED == m_iSndPeerKmState ? "secured" - : SRT_KM_S_NOSECRET == m_iSndPeerKmState ? "no-secret" - : SRT_KM_S_BADSECRET == m_iSndPeerKmState ? "bad-secret" - : SRT_KM_S_SECURING == m_iSndPeerKmState ? "securing" : "unsecured"); - break; - - 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. - */ - for (int i = 0; i < srtlen; ++ i) srtdata[i] = htonl(srtdata_in[i]); - - LOGC(mglog.Note).form( "sndSrtMsg: cmd=%d(KMRSP) len=%d Peer/RcvKmState=%s/%s\n", - cmd, (int)(srtlen * sizeof(int32_t)), - SRT_KM_S_SECURED == m_iRcvPeerKmState ? "secured" - : SRT_KM_S_SECURING == m_iRcvPeerKmState ? "securing" : "unsecured", - SRT_KM_S_SECURED == m_iRcvKmState ? "secured" - : SRT_KM_S_NOSECRET == m_iRcvKmState ? "no-secret" - : SRT_KM_S_BADSECRET == m_iRcvKmState ? "bad-secret" - : SRT_KM_S_SECURING == m_iRcvKmState ? "securing" : "unsecured"); - break; -#endif /* SRT_ENABLE_HAICRYPT */ - - default: - LOGC(mglog.Error).form( "sndSrtMsg: cmd=%d unsupported\n", cmd); - break; - } - if (srtlen > 0) - { - LOGC(mglog.Debug).form("CMD:%s Version: %s Flags: %08X (%s)\n", - MessageTypeStr(UMSG_EXT, srtcmd).c_str(), - SrtVersionString(srtdata[SRT_HS_VERSION]).c_str(), - srtdata[SRT_HS_FLAGS], - SrtFlagString(srtdata[SRT_HS_FLAGS]).c_str()); - /* srtpkt.pack will set message data in network order */ - srtpkt.pack(UMSG_EXT, &srtcmd, srtdata, srtlen * sizeof(int32_t)); - sendCustomMsg(srtpkt); - } -} - - -void CSRTCC::processSrtMsg(const CPacket *ctrlpkt) -{ - int32_t *srtdata = (int32_t *)ctrlpkt->m_pcData; - - switch(ctrlpkt->getExtendedType()) - { - case SRT_CMD_HSREQ: - if (ctrlpkt->getLength() < SRT_CMD_HSREQ_MINSZ) - { - /* Packet smaller than minimum compatible packet size */ - LOGC(mglog.Error).form( "rcvSrtMsg: cmd=%d(HSREQ) len=%d invalid\n", ctrlpkt->getExtendedType(), ctrlpkt->getLength()); - } - else switch(SRT_VERSION_MAJ(srtdata[SRT_HS_VERSION])) - { -#ifdef SRT_VERSION_2XX - case SRT_VERSION_MAJ2: /* Peer SRT version == 2.x.x */ - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(HSREQ) len=%d vers=0x%x\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), srtdata[SRT_HS_VERSION]); - - m_PeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_RcvPeerSrtOptions = srtdata[SRT_HS_FLAGS]; - sendSrtMsg(SRT_CMD_HSRSP); - break; -#endif - case SRT_VERSION_MAJ1: /* Peer SRT version == 1.x.x */ - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(HSREQ) len=%d vers=0x%x opts=0x%x delay=%d\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), srtdata[SRT_HS_VERSION], srtdata[SRT_HS_FLAGS], srtdata[SRT_HS_EXTRAS]); - - m_PeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_RcvPeerSrtOptions = srtdata[SRT_HS_FLAGS]; - - LOGC(mglog.Debug).form("HS RQ: Version: %s Flags: %08X (%s)\n", - SrtVersionString(m_PeerSrtVersion).c_str(), - m_RcvPeerSrtOptions, - SrtFlagString(m_RcvPeerSrtOptions).c_str()); - - if ( IsSet(m_RcvPeerSrtOptions, SRT_OPT_TSBPDSND) ) - { - //TimeStamp-based Packet Delivery feature enabled - m_bRcvTsbPdMode = true; //Sender use TsbPd, enable TsbPd rx. - - /* - * Take max of sender/receiver TsbPdDelay - */ - m_RcvTsbPdDelay = SRT_HS_EXTRAS_LO::unwrap(srtdata[SRT_HS_EXTRAS]); - if (m_TsbPdDelay > m_RcvTsbPdDelay) - { - m_RcvTsbPdDelay = m_TsbPdDelay; - } - - /* - * 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_RcvPeerStartTime; - m_RcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ctrlpkt->m_iTimeStamp); - if (oldPeerStartTime) { - LOGC(mglog.Note).form( "rcvSrtMsg: 2nd PeerStartTime diff=%lld usec\n", - (long long)(m_RcvPeerStartTime - oldPeerStartTime)); - } - } -#else - m_RcvPeerStartTime = CTimer::getTime() - (uint64_t)((uint32_t)ctrlpkt->m_iTimeStamp); -#endif - } - - m_bPeerRexmitFlag = IsSet(m_RcvPeerSrtOptions, SRT_OPT_REXMITFLG); - LOGC(mglog.Debug).form("HS RQ: peer %s REXMIT flag\n", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND" ); - sendSrtMsg(SRT_CMD_HSRSP); - break; - - default: - /* Peer tries SRT version handshake we don't support */ - - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(HSREQ) vers=0x%x unsupported: try downgrade\n", - ctrlpkt->getExtendedType(), srtdata[SRT_HS_VERSION]); - - /* Respond with our max supported version, peer may still support it */ - m_RcvPeerSrtOptions = SRT_VERSION_UNK; - sendSrtMsg(SRT_CMD_HSRSP); - break; - } - break; - - case SRT_CMD_HSRSP: - if (ctrlpkt->getLength() < SRT_CMD_HSRSP_MINSZ) - { - /* Packet smaller than minimum compatible packet size */ - LOGC(mglog.Error).form( "rcvSrtMsg: cmd=%d(HSRSP) len=%d invalid\n", ctrlpkt->getExtendedType(), ctrlpkt->getLength()); - } - else switch(SRT_VERSION_MAJ(srtdata[SRT_HS_VERSION])) - { -#ifdef SRT_VERSION_2XX - case SRT_VERSION_MAJ2: /* Peer SRT version == 2.x.x */ - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(HSRSP) len=%d vers=0x%x\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), srtdata[SRT_HS_VERSION]); - - m_PeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_SndPeerSrtOptions = srtdata[SRT_HS_FLAGS]; - // add 2.x.x handshake code here - m_SndHsRetryCnt = 0; /* Handshake done */ - break; - - case SRT_VERSION_MAJ1: /* Peer SRT version == 1.x.x */ - if (m_PeerSrtVersion == 0) - { - /* - * Peer does not support our current version, - * restart handshake using 1.x.x method - */ - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(HSRSP) len=%d vers=0x%x downgrading handshake\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), srtdata[SRT_HS_VERSION]); - - m_PeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_SndPeerSrtOptions = srtdata[SRT_HS_FLAGS]; - m_SndHsRetryCnt = SRT_MAX_HSRETRY; /* Reset handshake retry counter */ - m_SndHsLastTime = CTimer::getTime(); - sendSrtMsg(SRT_CMD_HSREQ); - } - else -#else - case SRT_VERSION_MAJ1: /* Peer SRT version == 1.x.x */ -#endif - { - /* Response from peer to SRT 1.x.x handshake request */ - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(HSRSP) len=%d vers=0x%x opts=0x%x delay=%d\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), srtdata[SRT_HS_VERSION], srtdata[SRT_HS_FLAGS], srtdata[SRT_HS_EXTRAS]); - - m_PeerSrtVersion = srtdata[SRT_HS_VERSION]; - m_SndPeerSrtOptions = srtdata[SRT_HS_FLAGS]; - - LOGC(mglog.Debug).form("HS RP: Version: %s Flags: SND:%08X (%s) RCV:%08X (%s)\n", - SrtVersionString(m_PeerSrtVersion).c_str(), - m_SndPeerSrtOptions, - SrtFlagString(m_SndPeerSrtOptions).c_str(), - m_RcvPeerSrtOptions, - SrtFlagString(m_RcvPeerSrtOptions).c_str()); - - if (IsSet(m_SndPeerSrtOptions, SRT_OPT_TSBPDRCV)) - { - //TsbPd feature enabled - m_SndPeerTsbPdDelay = SRT_HS_EXTRAS_LO::unwrap(srtdata[SRT_HS_EXTRAS]); - } -#ifdef SRT_ENABLE_TLPKTDROP - if ((m_SrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_SndPeerSrtOptions, SRT_OPT_TLPKTDROP)) - { - //Too late packets dropping feature supported - m_bSndPeerTLPktDrop = true; - } -#endif /* SRT_ENABLE_TLPKTDROP */ -#ifdef SRT_ENABLE_NAKREPORT - if ((m_SrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_SndPeerSrtOptions, SRT_OPT_NAKREPORT)) - { - //Peer will send Periodic NAK Reports - m_bSndPeerNakReport = true; - } -#endif /* SRT_ENABLE_NAKREPORT */ - - if ( m_SrtVersion >= SrtVersion(1, 2, 0) ) - { - if ( IsSet(m_SndPeerSrtOptions, SRT_OPT_REXMITFLG) ) - { - //Peer will use REXMIT flag in packet retransmission. - m_bPeerRexmitFlag = true; - LOGC(mglog.Debug).form("HS RP2: I UNDERSTAND REXMIT flag and SO DOES PEER\n"); - } - else - { - LOGC(mglog.Debug).form("HS RP: I UNDERSTAND REXMIT flag, but PEER DOES NOT\n"); - } - } - else - { - LOGC(mglog.Debug).form("HS RP: I DO NOT UNDERSTAND REXMIT flag\n" ); - } - - m_SndHsRetryCnt = 0; /* Handshake done */ - } - break; - - default: - /* Peer responded with obsolete unsupported version */ - LOGC(mglog.Error).form( "rcvSrtMsg: cmd=%d(HSRSP) vers=0x%x unsuppported version\n", - ctrlpkt->getExtendedType(), srtdata[SRT_HS_VERSION]); - m_SndHsRetryCnt = 0; /* Handshake failed, stop trying */ - break; - } - break; - - case SRT_CMD_KMREQ: //Receiver - /* All 32-bit msg fields swapped on reception - * But HaiCrypt expect network order message - * Re-swap to cancel it. - */ - { - int srtlen = ctrlpkt->getLength()/sizeof(srtdata[SRT_KMR_KMSTATE]); - for (int i = 0; i < srtlen; i++) - srtdata[i] = htonl(srtdata[i]); - - if ((NULL == m_hRcvCrypto) //No crypto context (we are receiver) - && (0 < m_KmSecret.len) //We have a shared secret - && ((srtlen * sizeof(srtdata[SRT_KMR_KMSTATE])) > HCRYPT_MSG_KM_OFS_SALT)) //Sanity on message - { - m_iRcvKmKeyLen = (int)hcryptMsg_KM_GetSekLen((unsigned char *)srtdata); - if (0 < m_iRcvKmKeyLen) m_hRcvCrypto = createCryptoCtx(m_iRcvKmKeyLen, 0); - } - - if (SRT_KM_S_UNSECURED == m_iRcvPeerKmState) - { - m_iRcvPeerKmState = SRT_KM_S_SECURING; - if (0 == m_KmSecret.len) - m_iRcvKmState = SRT_KM_S_NOSECRET; - else - m_iRcvKmState = SRT_KM_S_SECURING; - } - - /* Maybe we have it now */ - if (NULL != m_hRcvCrypto) - { - int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, (unsigned char *)srtdata, ctrlpkt->getLength(), NULL, NULL, 0); - switch(rc >= 0 ? 0 : rc) - { - case 0: //Success - m_iRcvPeerKmState = SRT_KM_S_SECURED; - m_iRcvKmState = SRT_KM_S_SECURED; - //Send back the whole message to confirm - break; - case -2: //Unmatched shared secret to decrypt wrapped key - m_iRcvKmState = SRT_KM_S_BADSECRET; - //Send status KMRSP message to tel error - srtlen = 1; - break; - case -1: //Other errors - default: - m_iRcvKmState = SRT_KM_S_SECURING; - //Send status KMRSP message to tel error - srtlen = 1; - break; - } - } - else - { - //Send status KMRSP message to tel error - srtlen = 1; - } - - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(KMREQ) len=%d Peer/RcvKmState=%s/%s\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), - SRT_KM_S_SECURED == m_iRcvPeerKmState ? "secured" - : SRT_KM_S_SECURING == m_iRcvPeerKmState ? "securing" : "unsecured", - SRT_KM_S_SECURED == m_iRcvKmState ? "secured" - : SRT_KM_S_NOSECRET == m_iRcvKmState ? "no-secret" - : SRT_KM_S_BADSECRET == m_iRcvKmState ? "bad-secret" - : SRT_KM_S_SECURING == m_iRcvKmState ? "securing" : "unsecured"); - - if (srtlen == 1) - srtdata[SRT_KMR_KMSTATE] = m_iRcvKmState; - sendSrtMsg(SRT_CMD_KMRSP, srtdata, srtlen); - } - break; - - case SRT_CMD_KMRSP: - { - /* All 32-bit msg fields (if present) swapped on reception - * But HaiCrypt expect network order message - * Re-swap to cancel it. - */ - int srtlen = ctrlpkt->getLength()/sizeof(int32_t); - for (int i = 0; i < srtlen; ++ i) - srtdata[i] = htonl(srtdata[i]); - - if (srtlen == 1) - { - m_iSndPeerKmState = srtdata[SRT_KMR_KMSTATE]; /* Bad or no passphrase */ - m_SndKmMsg[0].iPeerRetry = 0; - m_SndKmMsg[1].iPeerRetry = 0; - } - else if ((m_SndKmMsg[0].MsgLen == (srtlen * sizeof(int32_t))) - && (0 == memcmp(m_SndKmMsg[0].Msg, srtdata, m_SndKmMsg[0].MsgLen))) - { - m_SndKmMsg[0].iPeerRetry = 0; /* Handshake ctx 0 done */ - m_iSndKmState = SRT_KM_S_SECURED; - m_iSndPeerKmState = SRT_KM_S_SECURED; - - } - else if ((m_SndKmMsg[1].MsgLen == (srtlen * sizeof(int32_t))) - && (0 == memcmp(m_SndKmMsg[1].Msg, srtdata, m_SndKmMsg[1].MsgLen))) - { - m_SndKmMsg[1].iPeerRetry = 0; /* Handshake ctx 1 done */ - m_iSndKmState = SRT_KM_S_SECURED; - m_iSndPeerKmState = SRT_KM_S_SECURED; - } - LOGC(mglog.Note).form( "rcvSrtMsg: cmd=%d(KMRSP) len=%d Snd/PeerKmState=%s/%s\n", - ctrlpkt->getExtendedType(), ctrlpkt->getLength(), - SRT_KM_S_SECURED == m_iSndKmState ? "secured" - : SRT_KM_S_SECURING == m_iSndKmState ? "securing" : "unsecured", - SRT_KM_S_SECURED == m_iSndPeerKmState ? "secured" - : SRT_KM_S_NOSECRET == m_iSndPeerKmState ? "no-secret" - : SRT_KM_S_BADSECRET == m_iSndPeerKmState ? "bad-secret" - : SRT_KM_S_SECURING == m_iSndPeerKmState ? "securing" : "unsecured"); - } - break; - - default: - LOGC(mglog.Error).form( "rcvSrtMsg: cmd=%d len=%d unsupported message\n", ctrlpkt->getExtendedType(), ctrlpkt->getLength()); - break; - } -} - - -void CSRTCC::checkSndTimers() -{ - uint64_t now; - - if (!m_bDataSender) return; - - /* - * 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 ((m_bSndTsbPdMode) - && (m_SndHsRetryCnt > 0) - && ((m_SndHsLastTime + ((m_iRTT * 3)/2)) <= (now = CTimer::getTime()))) { - m_SndHsRetryCnt--; - m_SndHsLastTime = now; - sendSrtMsg(SRT_CMD_HSREQ); - } - - /* - * Crypto Key Distribution to peer: - * If... - * - we want encryption; and - * - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and - * - and did not get answer back from peer; and - * - last sent Keying Material req should have been replied (RTT*1.5 elapsed); - * then (re-)send handshake request. - */ - if ((m_hSndCrypto) - && ((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0)) - && ((m_SndKmLastTime + ((m_iRTT * 3)/2)) <= (now = CTimer::getTime()))) - { - for (int ki = 0; ki < 2; ki++) - { - if (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0) - { - m_SndKmMsg[ki].iPeerRetry--; - m_SndKmLastTime = now; - sendSrtMsg(SRT_CMD_KMREQ, (int32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen/sizeof(int32_t)); - } - } - } - /* - * Readjust the max SndPeriod onACK (and onTimeout) - */ - m_dPktSndPeriod = 1000000.0 / (double(m_llSndMaxBW) / (m_iSndAvgPayloadSize + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE)); -#if 0//debug - static int callcnt = 0; - if (!(callcnt++ % 100)) fprintf(stderr, "onAck: SndPeriod=%f AvgPkt=%d\n", m_dPktSndPeriod, m_iSndAvgPayloadSize); -#endif -} - -void CSRTCC::regenCryptoKm(bool sendit) -{ - if (m_hSndCrypto == NULL) return; - - void *out_p[2]; - size_t out_len_p[2]; - int nbo = HaiCrypt_Tx_ManageKeys(m_hSndCrypto, out_p, out_len_p, 2); - int sent = 0; - - for (int i = 0; i < nbo && i < 2; i++) - { - /* - * New connection keying material - * or regenerated after crypto_cfg.km_refresh_rate_pkt packets . - * Send to peer - */ - int ki = hcryptMsg_KM_GetKeyIndex((unsigned char *)(out_p[i])) & 0x1; - if ((out_len_p[i] != m_SndKmMsg[ki].MsgLen) - || (0 != memcmp(out_p[i], m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen))) - { -#ifdef DEBUG_KM - fprintf(stderr, "new key[%d] len=%zd,%zd msg=%0x,%0x\n", - ki, out_len_p[i], m_SndKmMsg[ki].MsgLen, - *(int32_t *)out_p[i], *(int32_t *)(&m_SndKmMsg[ki].Msg[0])); -#endif - /* New Keying material, send to peer */ - memcpy(m_SndKmMsg[ki].Msg, out_p[i], out_len_p[i]); - m_SndKmMsg[ki].MsgLen = out_len_p[i]; - m_SndKmMsg[ki].iPeerRetry = SRT_MAX_KMRETRY; - - if (sendit) - { - sendSrtMsg(SRT_CMD_KMREQ, (int32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen/sizeof(int32_t)); - sent++; - } - } - } - if (sent) - m_SndKmLastTime = CTimer::getTime(); -} - -CSRTCC::CSRTCC() -{ - //Settings - m_SrtVersion = SRT_DEF_VERSION; - m_bDataSender = false; - m_bSndTsbPdMode = false; - m_TsbPdDelay = 120; //msec - - m_dCWndSize = 1000; - - //Data - m_bRcvTsbPdMode = true; -#ifdef SRT_ENABLE_TLPKTDROP - m_bRcvTLPktDrop = false; //Settings - m_bSndPeerTLPktDrop = false; //Data -#endif -#ifdef SRT_ENABLE_NAKREPORT - m_bRcvNakReport = false; //Settings - m_bSndPeerNakReport = false; //Data -#endif - m_bPeerRexmitFlag = false; - - m_PeerSrtVersion = SRT_VERSION_UNK; - m_SndPeerSrtOptions = 0; - m_RcvPeerSrtOptions = 0; - m_RcvPeerStartTime = 0; - - m_SndHsLastTime = 0; - m_SndHsRetryCnt = SRT_MAX_HSRETRY; - - m_iSndAvgPayloadSize = (7*188); - m_llSndMaxBW = 30000000/8; // 30Mbps in Bytes/sec - m_dPktSndPeriod = 1000000.0 / (double(m_llSndMaxBW) / (m_iSndAvgPayloadSize + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE)); - - m_KmSecret.len = 0; - //send - m_iSndKmKeyLen = 0; - m_iSndKmState = SRT_KM_S_UNSECURED; - m_iSndPeerKmState = SRT_KM_S_UNSECURED; - m_SndKmLastTime = 0; - m_SndKmMsg[0].MsgLen = 0; - m_SndKmMsg[0].iPeerRetry = 0; - m_SndKmMsg[1].MsgLen = 0; - m_SndKmMsg[1].iPeerRetry = 0; - m_hSndCrypto = NULL; - //recv - m_iRcvKmKeyLen = 0; - m_iRcvKmState = SRT_KM_S_UNSECURED; - m_iRcvPeerKmState = SRT_KM_S_UNSECURED; - m_hRcvCrypto = NULL; - - m_sock = 0; // as uninitialized -} - -void CSRTCC::init() -{ - if (m_bDataSender) - { - m_SndHsRetryCnt = SRT_MAX_HSRETRY+1; - //sendSrtMsg(SRT_CMD_HSREQ); - //m_SndHsLastTime = CTimer::getTime(); - if ((m_iSndKmKeyLen > 0) && (m_hSndCrypto == NULL)) - m_hSndCrypto = createCryptoCtx(m_iSndKmKeyLen, true); - if (m_hSndCrypto) - regenCryptoKm(false); - } -} - -void CSRTCC::close() -{ - m_sock = 0; - - /* Wipeout secrets */ - memset(&m_KmSecret, 0, sizeof(m_KmSecret)); - m_SrtVersion = SRT_DEF_VERSION; - m_bDataSender = false; - m_bSndTsbPdMode = false; - m_bSndTsbPdMode = false; -#ifdef SRT_ENABLE_TLPKTDROP - m_bSndPeerTLPktDrop = false; -#endif -#ifdef SRT_ENABLE_NAKREPORT - m_bSndPeerNakReport = false; -#endif - m_PeerSrtVersion = SRT_VERSION_UNK; - m_RcvPeerStartTime = 0; - - m_SndHsLastTime = 0; - m_SndHsRetryCnt = SRT_MAX_HSRETRY; -} - - -void CSRTCC::onACK(int32_t ackno) -{ - (void)ackno; //unused - - /* - * We are receiving an ACK so we are sender. - * SRT handshake with peer (receiver) initiated on sender connection (init()) - * Initial Crypto Keying Material too. - */ - checkSndTimers(); - if (m_hSndCrypto) - regenCryptoKm(); -} - -void CSRTCC::onPktSent(const CPacket *pkt) -{ - if ((m_SndHsRetryCnt == SRT_MAX_HSRETRY+1) && m_bDataSender) - { - m_SndHsRetryCnt--; - m_SndHsLastTime = CTimer::getTime(); - sendSrtMsg(SRT_CMD_HSREQ); - } - m_iSndAvgPayloadSize = ((m_iSndAvgPayloadSize * 127) + pkt->getLength()) / 128; - m_sock = pkt->m_iID; -} - -std::string CSRTCC::CONID() const -{ - if ( m_sock == 0 ) - return ""; - - std::ostringstream os; - os << "%" << m_sock << ":"; - - return os.str(); -} - -HaiCrypt_Handle CSRTCC::createCryptoCtx(int keylen, int tx) -{ - HaiCrypt_Handle hCrypto = NULL; - - if ((m_KmSecret.len > 0) && (keylen > 0)) - { - HaiCrypt_Cfg crypto_cfg; - memset(&crypto_cfg, 0, sizeof(crypto_cfg)); - - crypto_cfg.flags = HAICRYPT_CFG_F_CRYPTO | (tx ? HAICRYPT_CFG_F_TX : 0); - crypto_cfg.xport = HAICRYPT_XPT_SRT; - crypto_cfg.cipher = HaiCryptCipher_OpenSSL_EVP(); - crypto_cfg.key_len = (size_t)keylen; - crypto_cfg.data_max_len = HAICRYPT_DEF_DATA_MAX_LENGTH; //MTU - crypto_cfg.km_tx_period_ms = 0;//No HaiCrypt KM inject period, handled in SRT; - crypto_cfg.km_refresh_rate_pkt = HAICRYPT_DEF_KM_REFRESH_RATE; - crypto_cfg.km_pre_announce_pkt = 0x10000; //HAICRYPT_DEF_KM_PRE_ANNOUNCE; - - memcpy(&crypto_cfg.secret, &m_KmSecret, sizeof(crypto_cfg.secret)); - - if (HaiCrypt_Create(&crypto_cfg, &hCrypto)) - { - LOGC(dlog.Error) << CONID() << "cryptoCtx: could not create " << (tx ? "tx" : "rx") << " crypto ctx"; - hCrypto = NULL; - } - } - else - { - LOGC(dlog.Error) << CONID() << "cryptoCtx: missing secret (" << m_KmSecret.len << ") or key length (" << keylen << ")"; - } - return(hCrypto); -} - - -HaiCrypt_Handle CSRTCC::getRcvCryptoCtx() -{ - /* - * We are receiver and - * have detected that incoming packets are encrypted - */ - if (SRT_KM_S_SECURED == m_iRcvKmState) - { - return(m_hRcvCrypto); //Return working crypto only - } - if (SRT_KM_S_UNSECURED == m_iRcvPeerKmState) - { - m_iRcvPeerKmState = SRT_KM_S_SECURING; - if (0 != m_KmSecret.len) - { // We have a passphrase, wait for keying material - m_iRcvKmState = SRT_KM_S_SECURING; - } - else - { // We don't have a passphrase, will never decrypt - m_iRcvKmState = SRT_KM_S_NOSECRET; - } - } - return(NULL); -} - - -void CSRTCC::freeCryptoCtx() -{ - if (NULL != m_hSndCrypto) - { - HaiCrypt_Close(m_hSndCrypto); - m_hSndCrypto = NULL; - } - if (NULL != m_hRcvCrypto) - { - HaiCrypt_Close(m_hRcvCrypto); - m_hRcvCrypto = NULL; - } -} - - - -std::string SrtFlagString(int32_t flags) -{ -#define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) - - std::string output; - static std::string namera[] = { "TSBPD-snd", "TSBPD-rcv", "haicrypt", "TLPktDrop", "NAKReport", "ReXmitFlag" }; - - size_t i = 0; - for ( ; i < LEN(namera); ++i ) - { - if ( (flags & 1) == 1 ) - { - output += "+" + namera[i] + " "; - } - else - { - output += "-" + namera[i] + " "; - } - - flags >>= 1; - //if ( flags == 0 ) - // break; - } - -#undef LEN - - if ( flags != 0 ) - { - output += "+unknown"; - } - - return output; -} diff --git a/srtcore/csrtcc.h b/srtcore/csrtcc.h deleted file mode 100644 index 71c73de44..000000000 --- a/srtcore/csrtcc.h +++ /dev/null @@ -1,341 +0,0 @@ -/***************************************************************************** - * SRT - Secure, Reliable, Transport - * Copyright (c) 2017 Haivision Systems Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; If not, see - * - *****************************************************************************/ - -/***************************************************************************** -written by - Haivision Systems Inc. - *****************************************************************************/ - -#ifndef CSRTCC_H -#define CSRTCC_H - -#define _CRT_SECURE_NO_WARNINGS -#include -#include -#include - -// UDT -#include "udt.h" -#include "core.h" -#include "ccc.h" - -#include - - -#define SRT_VERSION_UNK 0 -#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */ -#define SRT_VERSION_1XX 0x010200 /* Version 1 highest supported: 1.2.0 */ - -#if 0//Test version upgrade -#define SRT_VERSION_MAJ2 0x020000 /* Version 2 major */ -#define SRT_VERSION_2XX 0x020000 /* Version 2 highest supported: 2.0.0 */ -#define SRT_DEF_VERSION SRT_VERSION_2XX /* Current version */ -#else -//#define SRT_DEF_VERSION SRT_VERSION_1XX /* Current version */ -#endif - - -#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)) - -inline int SrtVersion(int major, int minor, int patch) -{ - return patch + minor*0x100 + major*0x10000; -} - -inline int32_t SrtParseVersion(const char* v) -{ - int major, minor, patch; - // On Windows/MSC, ignore C4996 warning here. - // This warning states that sscanf is unsafe because the %s and %c conversions - // don't specify the size of the buffer. Just because of this fact, sscanf is - // considered unsafe - even though I don't use %s or %c here at all. - int result = sscanf(v, "%d.%d.%d", &major, &minor, &patch); - - if ( result != 3 ) - { - fprintf(stderr, "Invalid version format for SRT_VERSION: %s - use m.n.p\n", v); - throw v; // Throwing exception, as this function will be run before main() - } - - return major*0x10000 + minor*0x100 + patch; -} - -const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION); - -inline std::string SrtVersionString(int version) -{ - int patch = version % 0x100; - int minor = (version/0x100)%0x100; - int major = version/0x10000; - std::ostringstream buf; - buf << major << "." << minor << "." << patch; - return buf.str(); -} - -enum SrtOptions -{ - SRT_OPT_TSBPDSND = 0x00000001, /* Timestamp-based Packet delivery real-time data sender */ - SRT_OPT_TSBPDRCV = 0x00000002, /* Timestamp-based Packet delivery real-time data receiver */ - SRT_OPT_HAICRYPT = 0x00000004, /* HaiCrypt AES-128/192/256-CTR */ - SRT_OPT_TLPKTDROP = 0x00000008, /* Drop real-time data packets too late to be processed in time */ - SRT_OPT_NAKREPORT = 0x00000010, /* Periodic NAK report */ - SRT_OPT_REXMITFLG = 0x00000020, // One bit in payload packet msgno is "retransmitted" flag -}; - -std::string SrtFlagString(int32_t flags); - -const int SRT_CMD_HSREQ = 1, - SRT_CMD_HSRSP = 2, - SRT_CMD_KMREQ = 3, - SRT_CMD_KMRSP = 4; - -enum SrtDataStruct -{ - SRT_HS_VERSION = 0, - SRT_HS_FLAGS, - SRT_HS_EXTRAS, - - // Keep it always last - SRT_HS__SIZE -}; - -typedef Bits<31, 16> SRT_HS_EXTRAS_HI; -typedef Bits<15, 0> SRT_HS_EXTRAS_LO; - -// For KMREQ/KMRSP. Only one field is used. -const size_t SRT_KMR_KMSTATE = 0; - - -class CSRTCC : public CCC -{ -public: - int m_SrtVersion; //Local SRT Version (test program can simulate older versions) - int64_t m_llSndMaxBW; //Max bandwidth (bytes/sec) - int m_iSndAvgPayloadSize; //Average Payload Size of packets to xmit - - int m_iSndKmKeyLen; //Key length - int m_iRcvKmKeyLen; //Key length from rx KM - - /* -#define SRT_KM_S_UNSECURED 0 //No encryption -#define SRT_KM_S_SECURING 1 //Stream encrypted, exchanging Keying Material -#define SRT_KM_S_SECURED 2 //Stream encrypted, keying Material exchanged, decrypting ok. -#define SRT_KM_S_NOSECRET 3 //Stream encrypted and no secret to decrypt Keying Material -#define SRT_KM_S_BADSECRET 4 //Stream encrypted and wrong secret, cannot decrypt Keying Material -*/ - int m_iSndKmState; //Sender Km State - int m_iSndPeerKmState; //Sender's peer (receiver) Km State - int m_iRcvKmState; //Receiver Km State - int m_iRcvPeerKmState; //Receiver's peer (sender) Km State - - -protected: - bool m_bDataSender; //Sender side (for crypto, TsbPD handshake) - unsigned m_TsbPdDelay; //Set TsbPD delay (mSec) - -#ifdef SRT_ENABLE_TLPKTDROP - bool m_bRcvTLPktDrop; //Receiver Enabled Too-Late Packet Drop - bool m_bSndPeerTLPktDrop; //Sender's Peer supports Too Late Packets drop -#endif -#ifdef SRT_ENABLE_NAKREPORT - bool m_bRcvNakReport; //Enable Receiver Periodic NAK Reports - bool m_bSndPeerNakReport; //Sender's peer sends Periodic NAK Reports -#endif - bool m_bPeerRexmitFlag; //Peer will receive MSGNO with 26 bits only (bit 26 is used as a rexmit flag) - int m_PeerSrtVersion; //Peer Version received in handshake message - - int m_SndPeerSrtOptions; //Sender's Peer Options received in SRT Handshake Response message - bool m_bSndTsbPdMode; //Sender is TsbPD - unsigned m_SndPeerTsbPdDelay; //Sender's Peer Exchanged (largest) TsbPD delay (mSec) - - int m_RcvPeerSrtOptions; //Receiver's Peer Options received in SRT Handshake Request message - bool m_bRcvTsbPdMode; //Receiver is TsbPD - unsigned m_RcvTsbPdDelay; //Receiver Exchanged (largest) TsbPD delay (mSec) - uint64_t m_RcvPeerStartTime; //Receiver's Peer StartTime (base of pkt timestamp) in local time reference - - uint64_t m_SndHsLastTime; //Last SRT handshake request time - int m_SndHsRetryCnt; //SRT handshake retries left - - HaiCrypt_Secret m_KmSecret; //Key material shared secret - // Sender - uint64_t m_SndKmLastTime; - struct { - char Msg[HCRYPT_MSG_KM_MAX_SZ]; - size_t MsgLen; - int iPeerRetry; - } m_SndKmMsg[2]; - HaiCrypt_Handle m_hSndCrypto; - // Receiver - HaiCrypt_Handle m_hRcvCrypto; - - UDTSOCKET m_sock; // for logging - -private: - void sendSrtMsg(int cmd, int32_t *srtdata_in = NULL, int srtlen_in = 0); - void processSrtMsg(const CPacket *ctrlpkt); - void checkSndTimers(); - void regenCryptoKm(bool sendit = true); - -public: - CSRTCC(); - - std::string CONID() const; - -protected: - virtual void init(); - virtual void close(); - virtual void onACK(int32_t ackno); - virtual void onPktSent(const CPacket *pkt); - virtual void onTimeout() { checkSndTimers(); } - - virtual void processCustomMsg(const CPacket *ctrlpkt) - { - processSrtMsg(ctrlpkt); - } - -public: - - void setSndTsbPdMode(bool tsbpd) - { - m_bDataSender = true; - m_bSndTsbPdMode = tsbpd; - } - - void setTsbPdDelay(int delay) - { - m_TsbPdDelay = (unsigned)delay; - } - - int getPeerSrtVersion() - { - return(m_PeerSrtVersion); - } - - unsigned getRcvTsbPdDelay() - { - return(m_RcvTsbPdDelay); - } - - unsigned getSndPeerTsbPdDelay() - { - return(m_SndPeerTsbPdDelay); - } - - bool getSndTsbPdInfo() - { - return(m_bSndTsbPdMode); - } - - bool getRcvTsbPdInfo(uint64_t *starttime) - { - if (NULL != starttime) - *starttime = m_RcvPeerStartTime; - return(m_bRcvTsbPdMode); - } - - bool getRcvTsbPdInfo() - { - return m_bRcvTsbPdMode; - } - - uint64_t getRcvPeerStartTime() - { - return m_RcvPeerStartTime; - } - -#ifdef SRT_ENABLE_TLPKTDROP - void setRcvTLPktDrop(bool pktdrop) - { - m_bRcvTLPktDrop = pktdrop; - } - - unsigned getSndPeerTLPktDrop() - { - return(m_bSndPeerTLPktDrop); - } -#endif - - void setMaxBW(int64_t maxbw) - { - m_llSndMaxBW = maxbw > 0 ? maxbw : 30000000/8; //Infinite=> 30Mbps - m_dPktSndPeriod = ((double)(m_iSndAvgPayloadSize + CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE) / m_llSndMaxBW) * 1000000.0; - -#ifdef SRT_ENABLE_NOCWND - /* - * UDT default flow control should not trigger under normal SRT operation - * UDT stops sending if the number of packets in transit (not acknowledged) - * is larger than the congestion window. - * Up to SRT 1.0.6, this value was set at 1000 pkts, which may be insufficient - * for satellite links with ~1000 msec RTT and high bit rate. - */ - m_dCWndSize = m_dMaxCWndSize; -#else - m_dCWndSize = 1000; -#endif - } - - void setCryptoSecret(HaiCrypt_Secret *secret) - { - memcpy(&m_KmSecret, secret, sizeof(m_KmSecret)); - } - - void setSndCryptoKeylen(int keylen) - { - m_iSndKmKeyLen = keylen; - m_bDataSender = true; - } - - HaiCrypt_Handle createCryptoCtx(int keylen, int tx = 0); - - HaiCrypt_Handle getSndCryptoCtx() const - { - return(m_hSndCrypto); - } - - HaiCrypt_Handle getRcvCryptoCtx(); - - int getSndCryptoFlags() const - { - return(m_hSndCrypto ? HaiCrypt_Tx_GetKeyFlags(m_hSndCrypto) : 0); - } - - void freeCryptoCtx(); - -#ifdef SRT_ENABLE_NAKREPORT - void setRcvNakReport(bool nakreport) - { - m_bRcvNakReport = nakreport; - } - - bool getSndPeerNakReport() - { - return(m_bSndPeerNakReport); - } -#endif - - bool getPeerRexmitFlag() - { - return m_bPeerRexmitFlag; - } - -}; - -#endif // SRT_CONGESTION_CONTROL_H diff --git a/srtcore/epoll.cpp b/srtcore/epoll.cpp index 68912db2a..0c3761c53 100644 --- a/srtcore/epoll.cpp +++ b/srtcore/epoll.cpp @@ -231,7 +231,7 @@ int CEPoll::remove_usock(const int eid, const UDTSOCKET& u) p->second.m_sUDTSocksIn.erase(u); p->second.m_sUDTSocksOut.erase(u); p->second.m_sUDTSocksEx.erase(u); -#ifdef HAI_PATCH + /* * We are no longer interested in signals from this socket * If some are up, they will unblock EPoll forever. @@ -240,7 +240,6 @@ int CEPoll::remove_usock(const int eid, const UDTSOCKET& u) p->second.m_sUDTReads.erase(u); p->second.m_sUDTWrites.erase(u); p->second.m_sUDTExcepts.erase(u); -#endif /* HAI_PATCH */ return 0; } @@ -274,8 +273,8 @@ int CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) return 0; } -#ifdef HAI_PATCH // Need this to atomically modify polled events (ex: remove write/keep read) +// Need this to atomically modify polled events (ex: remove write/keep read) int CEPoll::update_usock(const int eid, const UDTSOCKET& u, const int* events) { CGuard pg(m_EPollLock); @@ -379,7 +378,6 @@ int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) return 0; } -#endif /* HAI_PATCH */ int CEPoll::wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) { @@ -483,7 +481,7 @@ int CEPoll::wait(const int eid, set* readfds, set* writefd //faster approaches can be applied for specific systems in the future. //"select" has a limitation on the number of sockets - SYSSOCKET max_fd = 0; + int max_fd = 0; fd_set readfds; fd_set writefds; diff --git a/srtcore/epoll.h b/srtcore/epoll.h index c33960c2e..f97a841e5 100644 --- a/srtcore/epoll.h +++ b/srtcore/epoll.h @@ -129,7 +129,6 @@ friend class CRendezvousQueue; /// @return 0 if success, otherwise an error number. int remove_ssock(const int eid, const SYSSOCKET& s); -#ifdef HAI_PATCH /// update a UDT socket events from an EPoll. /// @param [in] eid EPoll ID. /// @param [in] u UDT socket ID. @@ -145,7 +144,6 @@ friend class CRendezvousQueue; /// @return 0 if success, otherwise an error number. int update_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); -#endif /* HAI_PATCH */ /// wait for EPoll events or timeout. /// @param [in] eid EPoll ID. diff --git a/srtcore/filelist.maf b/srtcore/filelist.maf index f476bd3fd..a940e8bed 100644 --- a/srtcore/filelist.maf +++ b/srtcore/filelist.maf @@ -4,12 +4,12 @@ SOURCES api.cpp buffer.cpp cache.cpp -ccc.cpp channel.cpp common.cpp core.cpp -csrtcc.cpp +crypto.cpp epoll.cpp +handshake.cpp list.cpp md5.cpp packet.cpp @@ -23,7 +23,7 @@ srt.h logging_api.h PROTECTED HEADERS -platform_sys.h +../common/platform_sys.h udt.h srt4udt.h @@ -31,21 +31,20 @@ PRIVATE HEADERS api.h buffer.h cache.h -ccc.h channel.h common.h core.h -csrtcc.h +crypto.h epoll.h +handshake.h list.h logging.h md5.h -netinet_any.h +../common/netinet_any.h packet.h -platform_sys.h queue.h srt4udt.h -threadname.h +../common/threadname.h utilities.h window.h diff --git a/srtcore/handshake.cpp b/srtcore/handshake.cpp new file mode 100644 index 000000000..2ef832f7a --- /dev/null +++ b/srtcore/handshake.cpp @@ -0,0 +1,210 @@ +/***************************************************************************** +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. +*****************************************************************************/ + + +#include +#include +#include +#include +#include + +#include "udt.h" +#include "handshake.h" +#include "utilities.h" + +using namespace std; + + +CHandShake::CHandShake(): +m_iVersion(AF_UNSPEC), +m_iType(UDT_UNDEFINED), +m_iISN(0), +m_iMSS(0), +m_iFlightFlagSize(0), +m_iReqType(URQ_WAVEAHAND), +m_iID(0), +m_iCookie(0), +m_extension(false) +{ + for (int i = 0; i < 4; ++ i) + m_piPeerIP[i] = 0; +} + +int CHandShake::store_to(char* buf, ref_t r_size) +{ + size_t& size = r_size; + if (size < m_iContentSize) + return -1; + + int32_t* p = reinterpret_cast(buf); + *p++ = m_iVersion; + *p++ = m_iType; + *p++ = m_iISN; + *p++ = m_iMSS; + *p++ = m_iFlightFlagSize; + *p++ = int32_t(m_iReqType); + *p++ = m_iID; + *p++ = m_iCookie; + for (int i = 0; i < 4; ++ i) + *p++ = m_piPeerIP[i]; + + size = m_iContentSize; + + return 0; +} + +int CHandShake::load_from(const char* buf, size_t size) +{ + if (size < m_iContentSize) + return -1; + + const int32_t* p = reinterpret_cast(buf); + + m_iVersion = *p++; + m_iType = *p++; + m_iISN = *p++; + m_iMSS = *p++; + m_iFlightFlagSize = *p++; + m_iReqType = UDTRequestType(*p++); + m_iID = *p++; + m_iCookie = *p++; + for (int i = 0; i < 4; ++ i) + m_piPeerIP[i] = *p++; + + return 0; +} + +#ifdef ENABLE_LOGGING +std::string RequestTypeStr(UDTRequestType rq) +{ + switch ( rq ) + { + case URQ_INDUCTION: return "induction"; + case URQ_WAVEAHAND: return "waveahand"; + case URQ_CONCLUSION: return "conclusion"; + case URQ_AGREEMENT: return "agreement"; + case URQ_ERROR_INVALID: return "ERROR:invalid"; + case URQ_ERROR_REJECT: return "ERROR:reject"; + case URQ_DONE: return "done(HSv5RDV)"; + + default: return "INVALID"; + } +} + +string CHandShake::RdvStateStr(CHandShake::RendezvousState s) +{ + switch (s) + { + case RDV_WAVING: return "waving"; + case RDV_ATTENTION: return "attention"; + case RDV_FINE: return "fine"; + case RDV_INITIATED: return "initiated"; + case RDV_CONNECTED: return "connected"; + default: ; + } + + return "invalid"; +} +#endif + +string CHandShake::show() +{ + ostringstream so; + + so << "version=" << m_iVersion << " type=" << hex << m_iType << dec + << " ISN=" << m_iISN << " MSS=" << m_iMSS << " FLW=" << m_iFlightFlagSize + << " reqtype=" << RequestTypeStr(m_iReqType) << " srcID=" << m_iID + << " cookie=" << hex << m_iCookie << dec + << " srcIP="; + + const unsigned char* p = (const unsigned char*)m_piPeerIP, * pe = p + 4*(sizeof (uint32_t)); + + copy(p, pe, ostream_iterator(so, ".")); + + // XXX HS version symbols should be probably declared inside + // CHandShake, not CUDT. + if ( m_iVersion > 4 ) + { + string ext; + + if ( m_iType & HS_EXT_HSREQ ) + ext += "HSREQ "; + + if ( m_iType & HS_EXT_KMREQ ) + ext += "KMREQ "; + + if ( ext == "" ) + ext = "(no extensions)"; + + so << ext; + } + + return so.str(); +} + + +// XXX This code isn't currently used. Left here because it can +// be used in future, should any refactoring for the "manual word placement" +// code be done. +bool SrtHSRequest::serialize(char* buf, size_t size) const +{ + if (size < SRT_HS_SIZE) + return false; + + int32_t* p = reinterpret_cast(buf); + + *p++ = m_iSrtVersion; + *p++ = m_iSrtFlags; + *p++ = m_iSrtTsbpd; + *p++ = 0; // SURPRISE! Seriously, use (something) if this "reserved" is going to be used for something. + return true; +} + + +bool SrtHSRequest::deserialize(const char* buf, size_t size) +{ + m_iSrtVersion = 0; // just to let users recognize if it succeeded or not. + + if (size < SRT_HS_SIZE) + return false; + + const int32_t* p = reinterpret_cast(buf); + + m_iSrtVersion = (*p++); + m_iSrtFlags = (*p++); + m_iSrtTsbpd = (*p++); + m_iSrtReserved = (*p++); + return true; +} diff --git a/srtcore/handshake.h b/srtcore/handshake.h new file mode 100644 index 000000000..14c7ba8ae --- /dev/null +++ b/srtcore/handshake.h @@ -0,0 +1,278 @@ +/***************************************************************************** +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. +*****************************************************************************/ + +#ifndef INC__HANDSHAKE_H +#define INC__HANDSHAKE_H + +#include "crypto.h" +#include "utilities.h" + +typedef Bits<31, 16> HS_CMDSPEC_CMD; +typedef Bits<15, 0> HS_CMDSPEC_SIZE; + +enum SrtOptions +{ + SRT_OPT_TSBPDSND = 0x00000001, /* Timestamp-based Packet delivery real-time data sender */ + SRT_OPT_TSBPDRCV = 0x00000002, /* Timestamp-based Packet delivery real-time data receiver */ + SRT_OPT_HAICRYPT = 0x00000004, /* HaiCrypt AES-128/192/256-CTR */ + SRT_OPT_TLPKTDROP = 0x00000008, /* Drop real-time data packets too late to be processed in time */ + SRT_OPT_NAKREPORT = 0x00000010, /* Periodic NAK report */ + SRT_OPT_REXMITFLG = 0x00000020, // One bit in payload packet msgno is "retransmitted" flag +}; + + +std::string SrtFlagString(int32_t flags); + +const int SRT_CMD_REJECT = 0, // REJECT is only a symbol for return type + SRT_CMD_HSREQ = 1, + SRT_CMD_HSRSP = 2, + SRT_CMD_KMREQ = 3, + SRT_CMD_KMRSP = 4, + SRT_CMD_SID = 5, + SRT_CMD_NONE = -1; // for cases when no pong for ping is required + +enum SrtDataStruct +{ + SRT_HS_VERSION = 0, + SRT_HS_FLAGS, + SRT_HS_LATENCY, + + // Keep it always last + SRT_HS__SIZE +}; + +// For HSv5 the lo and hi part is used for particular side's latency +typedef Bits<31, 16> SRT_HS_LATENCY_RCV; +typedef Bits<15, 0> SRT_HS_LATENCY_SND; +// For HSv4 only the lower part is used. +typedef Bits<15, 0> SRT_HS_LATENCY_LEG; + + +// XXX These structures are currently unused. The code can be changed +// so that these are used instead of manual tailoring of the messages. +struct SrtHandshakeExtension +{ +protected: + + uint32_t m_SrtCommand; // Used only in extension + +public: + SrtHandshakeExtension(int cmd) + { + m_SrtCommand = cmd; + } + + void setCommand(int cmd) + { + m_SrtCommand = cmd; + } + +}; + +struct SrtHSRequest: public SrtHandshakeExtension +{ + static const int32_t SRT_MAGIC_CODE = 0x4A171510; +private: + friend class CHandShake; + + static const size_t SRT_HS_SIZE = 4*sizeof(uint32_t); // 4 existing fields + static const size_t SRT_EXT_HS_SIZE = 2*sizeof(uint32_t) + SRT_HS_SIZE; // SRT magic and SRT HS type, used only in UDT HS ext + + typedef Bits<15, 0> SRT_TSBPD_DELAY; + + uint32_t m_iSrtVersion; + uint32_t m_iSrtFlags; + uint32_t m_iSrtTsbpd; + uint32_t m_iSrtReserved; + +public: + + SrtHSRequest(): SrtHandshakeExtension(SRT_CMD_HSREQ), m_iSrtVersion(), m_iSrtFlags(), m_iSrtTsbpd(), m_iSrtReserved() {} + + void setVersion(uint32_t v) { m_iSrtVersion = v; } + uint32_t version() const { return m_iSrtVersion; } + + void setFlag(SrtOptions opt) { m_iSrtFlags |= uint32_t(opt); } + void clearFlag(SrtOptions opt) { m_iSrtFlags &= ~opt; } + uint32_t flags() const { return m_iSrtFlags; } + + void setTsbPdDelay(uint16_t delay) { m_iSrtTsbpd |= SRT_TSBPD_DELAY::wrap(delay); } + // Unknown what the 1-16 bits have to be used for. + uint16_t tsbPdDelay() const + { + return SRT_TSBPD_DELAY::unwrap(m_iSrtTsbpd); + } + + size_t size() const { return SRT_EXT_HS_SIZE; } + + bool serialize(char* p, size_t size) const; + bool deserialize(const char* mem, size_t size); +}; + +struct SrtKMRequest: public SrtHandshakeExtension +{ + uint32_t m_iKmState; + char m_aKey[1]; // dynamic size +}; + + +//////////////////////////////////////////////////////////////////////////////// + +enum UDTRequestType +{ + URQ_INDUCTION_TYPES = 0, // XXX used to check in one place. Consdr rm. + + URQ_INDUCTION = 1, // First part for client-server connection + URQ_WAVEAHAND = 0, // First part for rendezvous connection + + URQ_CONCLUSION = -1, // Second part of handshake negotiation + URQ_AGREEMENT = -2, // Extra (last) step for rendezvous only + URQ_DONE = -3, // Special value used only in state-switching, to state that nothing should be sent in response + + // Note: the client-server connection uses: + // --> INDUCTION (empty) + // <-- INDUCTION (cookie) + // --> CONCLUSION (cookie) + // <-- CONCLUSION (ok) + + // The rendezvous HSv4 (legacy): + // --> WAVEAHAND (effective only if peer is also connecting) + // <-- CONCLUSION (empty) (consider yourself connected upon reception) + // --> AGREEMENT (sent as a response for conclusion, requires no response) + + // The rendezvous HSv5 (using SRT extensions): + // --> WAVEAHAND (with cookie) + // --- (selecting INITIATOR/RESPONDER by cookie contest - comparing one another's cookie) + // <-- CONCLUSION (without extensions, if RESPONDER, with extensions, if INITIATOR) + // --> CONCLUSION (with response extensions, if RESPONDER) + // <-- AGREEMENT (sent exclusively by INITIATOR upon reception of CONCLUSIOn with response extensions) + + // Errors reported by the peer, also used as useless error codes + // in handshake processing functions. + URQ_FAILURE_TYPES = 1000, + URQ_ERROR_REJECT = 1002, + URQ_ERROR_INVALID = 1004 +}; + + + +// XXX Change all uses of that field to UDTRequestType when possible +#if ENABLE_LOGGING +std::string RequestTypeStr(UDTRequestType); +#else +inline std::string RequestTypeStr(UDTRequestType) { return ""; } +#endif + + +class CHandShake +{ +public: + CHandShake(); + + int store_to(char* buf, ref_t size); + int load_from(const char* buf, size_t size); + +public: + // This is the size of SERIALIZED handshake. + // Might be defined as simply sizeof(CHandShake), but the + // enum values would have to be forced as int32_t, which is only + // available in C++11. Theoretically they are all 32-bit, but + // such a statement is not reliable and not portable. + static const size_t m_iContentSize = 48; // Size of hand shake data + + // Extension flags + + static const int32_t HS_EXT_HSREQ = BIT(0); + static const int32_t HS_EXT_KMREQ = BIT(1); + static const int32_t HS_EXT_SID = BIT(2); + + static std::string ExtensionFlagStr(int32_t fl) + { + std::string output = ""; + if ( fl & HS_EXT_HSREQ ) + output += " hsreq"; + if ( fl & HS_EXT_KMREQ ) + output += " kmreq"; + if ( fl & HS_EXT_SID ) + output += " streamid"; + return output; + } + + // Applicable only when m_iVersion == HS_VERSION_SRT1 + int32_t flags() { return m_iType; } + +public: + int32_t m_iVersion; // UDT version (HS_VERSION_* symbols) + int32_t m_iType; // UDT4: socket type (UDT_STREAM or UDT_DGRAM); SRT1: extension flags + int32_t m_iISN; // random initial sequence number + int32_t m_iMSS; // maximum segment size + int32_t m_iFlightFlagSize; // flow control window size + UDTRequestType m_iReqType; // connection request type: 1: regular connection request, 0: rendezvous connection request, -1/-2: response + int32_t m_iID; // socket ID + int32_t m_iCookie; // cookie + uint32_t m_piPeerIP[4]; // The IP address that the peer's UDP port is bound to + + bool m_extension; + + std::string show(); + +// The rendezvous state machine used in HSv5 only (in HSv4 everything is happening the old way). +// +// The WAVING state is the very initial state of the rendezvous connection and restored after the +// connection is closed. +// The ATTENTION and FINE are two alternative states that are transited to from WAVING. The possible +// situations are that: +// - (most likely) one party transits to ATTENTION and the other party transits to FINE +// - (rare case) both parties transit to ATTENTION +enum RendezvousState +{ + RDV_INVALID, //< This socket wasn't prepared for rendezvous process. Reject any events. + RDV_WAVING, //< Initial state for rendezvous. No contact seen from the peer. + RDV_ATTENTION, //< When received URQ_WAVEAHAND. [WAVING]:URQ_WAVEAHAND --> [ATTENTION]. + RDV_FINE, //< When received URQ_CONCLUSION. [WAVING]:URQ_CONCLUSION --> [FINE]. + RDV_INITIATED, //< When received URQ_CONCLUSION+HSREQ extension in ATTENTION state. + RDV_CONNECTED //< Final connected state. [ATTENTION]:URQ_CONCLUSION --> [CONNECTED] <-- [FINE]:URQ_AGREEMENT. +}; + +#if ENABLE_LOGGING +static std::string RdvStateStr(RendezvousState s); +#else +static std::string RdvStateStr(RendezvousState) { return ""; } +#endif + +}; + + +#endif diff --git a/srtcore/logging.h b/srtcore/logging.h index f351e1d34..80ab6213e 100644 --- a/srtcore/logging.h +++ b/srtcore/logging.h @@ -220,9 +220,6 @@ struct LogDispatcher::Proxy { LogDispatcher& that; - // XXX this is C++03 solution only. Use unique_ptr in C++11. - // It must be done with dynamic ostringstream because ostringstream - // is not copyable. std::ostringstream os; // Cache the 'enabled' state in the beginning. If the logging diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp index fe1eee3f8..ca5b04359 100644 --- a/srtcore/packet.cpp +++ b/srtcore/packet.cpp @@ -172,52 +172,72 @@ modified by #include #include "packet.h" +#include "logging.h" - -const int CHandShake::m_iContentSize = 48; - +extern logging::Logger mglog; // Set up the aliases in the constructure CPacket::CPacket(): __pad(), +m_data_owned(false), m_iSeqNo((int32_t&)(m_nHeader[PH_SEQNO])), m_iMsgNo((int32_t&)(m_nHeader[PH_MSGNO])), m_iTimeStamp((int32_t&)(m_nHeader[PH_TIMESTAMP])), m_iID((int32_t&)(m_nHeader[PH_ID])), m_pcData((char*&)(m_PacketVector[PV_DATA].iov_base)) { - m_nHeader.clear(); - - // The part at PV_HEADER will be always set to a builtin buffer - // containing SRT header. - m_PacketVector[PV_HEADER].iov_base = m_nHeader.raw(); - m_PacketVector[PV_HEADER].iov_len = HDR_SIZE; - - // The part at PV_DATA is zero-initialized. It should be - // set (through m_pcData and setLength()) to some externally - // provided buffer before calling CChannel::sendto(). - m_PacketVector[PV_DATA].iov_base = NULL; - m_PacketVector[PV_DATA].iov_len = 0; + m_nHeader.clear(); + + // The part at PV_HEADER will be always set to a builtin buffer + // containing SRT header. + m_PacketVector[PV_HEADER].iov_base = m_nHeader.raw(); + m_PacketVector[PV_HEADER].iov_len = HDR_SIZE; + + // The part at PV_DATA is zero-initialized. It should be + // set (through m_pcData and setLength()) to some externally + // provided buffer before calling CChannel::sendto(). + m_PacketVector[PV_DATA].iov_base = NULL; + m_PacketVector[PV_DATA].iov_len = 0; +} + +void CPacket::allocate(size_t alloc_buffer_size) +{ + m_PacketVector[PV_DATA].iov_base = new char[alloc_buffer_size]; + m_PacketVector[PV_DATA].iov_len = alloc_buffer_size; + m_data_owned = true; +} + +void CPacket::deallocate() +{ + if (m_data_owned) + delete [] (char*)m_PacketVector[PV_DATA].iov_base; + m_PacketVector[PV_DATA].iov_base = 0; + m_PacketVector[PV_DATA].iov_len = 0; } CPacket::~CPacket() { + // PV_HEADER is always owned, PV_DATA may use a "borrowed" buffer. + // Delete the internal buffer only if it was declared as owned. + if (m_data_owned) + delete [] (char*)m_PacketVector[PV_DATA].iov_base; } -int CPacket::getLength() const + +size_t CPacket::getLength() const { return m_PacketVector[PV_DATA].iov_len; } -void CPacket::setLength(int len) +void CPacket::setLength(size_t len) { m_PacketVector[PV_DATA].iov_len = len; } void CPacket::pack(UDTMessageType pkttype, void* lparam, void* rparam, int size) { - // Set (bit-0 = 1) and (bit-1~15 = type) - m_nHeader[PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(pkttype); + // Set (bit-0 = 1) and (bit-1~15 = type) + setControl(pkttype); // Set additional information and control information field switch (pkttype) @@ -394,10 +414,24 @@ EncryptionKeySpec CPacket::getMsgCryptoFlags() const return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(m_nHeader[PH_MSGNO])); } +// This is required as the encryption/decryption happens in place. +// This is required to clear off the flags after decryption or set +// crypto flags after encrypting a packet. +void CPacket::setMsgCryptoFlags(EncryptionKeySpec spec) +{ + int32_t clr_msgno = m_nHeader[PH_MSGNO] & ~MSGNO_ENCKEYSPEC::mask; + m_nHeader[PH_MSGNO] = clr_msgno | EncryptionKeyBits(spec); +} + +/* + Leaving old code for historical reasons. This is moved to CSRTCC. EncryptionStatus CPacket::encrypt(HaiCrypt_Handle hcrypto) { if ( !hcrypto ) + { + LOGC(mglog.Error) << "IPE: NULL crypto passed to CPacket::encrypt!"; return ENCS_FAILED; + } int rc = HaiCrypt_Tx_Data(hcrypto, (uint8_t *)m_nHeader.raw(), (uint8_t *)m_pcData, m_PacketVector[PV_DATA].iov_len); if ( rc < 0 ) @@ -414,10 +448,16 @@ EncryptionStatus CPacket::encrypt(HaiCrypt_Handle hcrypto) EncryptionStatus CPacket::decrypt(HaiCrypt_Handle hcrypto) { if (getMsgCryptoFlags() == EK_NOENC) + { + //LOGC(mglog.Debug) << "CPacket::decrypt: packet not encrypted"; return ENCS_CLEAR; // not encrypted, no need do decrypt, no flags to be modified + } if (!hcrypto) - return ENCS_FAILED; // "invalid argument" (leave encryption flags untouched) + { + LOGC(mglog.Error) << "IPE: NULL crypto passed to CPacket::decrypt!"; + return ENCS_FAILED; // "invalid argument" (leave encryption flags untouched) + } int rc = HaiCrypt_Rx_Data(hcrypto, (uint8_t *)m_nHeader.raw(), (uint8_t *)m_pcData, m_PacketVector[PV_DATA].iov_len); if ( rc <= 0 ) @@ -427,7 +467,7 @@ EncryptionStatus CPacket::decrypt(HaiCrypt_Handle hcrypto) return ENCS_FAILED; } // Otherwise: rc == decrypted text length. - m_PacketVector[PV_DATA].iov_len = rc; /* In case clr txt size is different from cipher txt */ + m_PacketVector[PV_DATA].iov_len = rc; // In case clr txt size is different from cipher txt // Decryption succeeded. Update flags. m_nHeader[PH_MSGNO] &= ~MSGNO_ENCKEYSPEC::mask; // sets EK_NOENC to ENCKEYSPEC bits. @@ -435,13 +475,13 @@ EncryptionStatus CPacket::decrypt(HaiCrypt_Handle hcrypto) return ENCS_CLEAR; } -#ifdef SRT_ENABLE_TSBPD +*/ + uint32_t CPacket::getMsgTimeStamp() const { // SRT_DEBUG_TSBPD_WRAP may enable smaller timestamp for faster wraparoud handling tests return (uint32_t)m_nHeader[PH_TIMESTAMP] & TIMESTAMP_MASK; } -#endif /* SRT_ENABLE_TSBPD */ CPacket* CPacket::clone() const { @@ -474,59 +514,3 @@ std::string CPacket::MessageFlagStr() return out.str(); } #endif - -CHandShake::CHandShake(): -m_iVersion(AF_UNSPEC), -m_iType(UDT_UNDEFINED), -m_iISN(0), -m_iMSS(0), -m_iFlightFlagSize(0), -m_iReqType(0), -m_iID(0), -m_iCookie(0) -{ - for (int i = 0; i < 4; ++ i) - m_piPeerIP[i] = 0; -} - -int CHandShake::serialize(char* buf, int& size) -{ - if (size < m_iContentSize) - return -1; - - int32_t* p = (int32_t*)buf; - *p++ = m_iVersion; - *p++ = m_iType; - *p++ = m_iISN; - *p++ = m_iMSS; - *p++ = m_iFlightFlagSize; - *p++ = m_iReqType; - *p++ = m_iID; - *p++ = m_iCookie; - for (int i = 0; i < 4; ++ i) - *p++ = m_piPeerIP[i]; - - size = m_iContentSize; - - return 0; -} - -int CHandShake::deserialize(const char* buf, int size) -{ - if (size < m_iContentSize) - return -1; - - int32_t* p = (int32_t*)buf; - m_iVersion = *p++; - m_iType = *p++; - m_iISN = *p++; - m_iMSS = *p++; - m_iFlightFlagSize = *p++; - m_iReqType = *p++; - m_iID = *p++; - m_iCookie = *p++; - for (int i = 0; i < 4; ++ i) - m_piPeerIP[i] = *p++; - - return 0; -} diff --git a/srtcore/packet.h b/srtcore/packet.h index db38a5f4f..d2bcecf36 100644 --- a/srtcore/packet.h +++ b/srtcore/packet.h @@ -68,9 +68,13 @@ modified by #include "common.h" #include "utilities.h" -#include - #ifdef WIN32 + +// XXX This is stupid. Instead of providing this, the +// appropriate equivalent system type on Windows should be used. +// Then, instead of `iovec`, some portable definition should be used. +// See the definition of WSARecvMsg for details. + struct iovec { int iov_len; @@ -117,7 +121,7 @@ inline int32_t CreateControlExtSeqNo(int exttype) return SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(size_t(UMSG_EXT)) | SEQNO_EXTTYPE::wrap(exttype); } -// MSGNO breakdown: B B|O|K K|M M M M M M M M M M M...M +// MSGNO breakdown: B B|O|K K|R|M M M M M M M M M M...M typedef Bits<31, 30> MSGNO_PACKET_BOUNDARY; typedef Bits<29> MSGNO_PACKET_INORDER; typedef Bits<28, 27> MSGNO_ENCKEYSPEC; @@ -181,15 +185,18 @@ friend class CRcvQueue; CPacket(); ~CPacket(); + void allocate(size_t size); + void deallocate(); + /// Get the payload or the control information field length. /// @return the payload or the control information field length. - int getLength() const; + size_t getLength() const; /// Set the payload or the control information field length. /// @param len [in] the payload or the control information field length. - void setLength(int len); + void setLength(size_t len); /// Pack a Control packet. /// @param pkttype [in] packet type filed. @@ -229,11 +236,14 @@ friend class CRcvQueue; bool isControl() const { // read bit 0 - // This "0!=" is a "special Microsoft aware conversion to bool" - // which gets rid of a warning. return 0!= SEQNO_CONTROL::unwrap(m_nHeader[PH_SEQNO]); } + void setControl(UDTMessageType type) + { + m_nHeader[PH_SEQNO] = SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(type); + } + /// Read the extended packet type. /// @return extended packet type filed (0x000 ~ 0xFFF). @@ -277,7 +287,9 @@ friend class CRcvQueue; /// @return packet header field [1] (bit 3~4). EncryptionKeySpec getMsgCryptoFlags() const; + void setMsgCryptoFlags(EncryptionKeySpec spec); + /* /// Encrypt packet if crypto context present /// @param hcrypto HaiCrypt handle. /// @retval -1 encryption enabled and failed. @@ -295,8 +307,8 @@ friend class CRcvQueue; EncryptionStatus decrypt(HaiCrypt_Handle hcrypto); + */ -#ifdef SRT_ENABLE_TSBPD /// Read the message time stamp. /// @return packet header field [2] (bit 0~31, bit 0-26 if SRT_DEBUG_TSBPD_WRAP). @@ -308,12 +320,12 @@ friend class CRcvQueue; static const uint32_t MAX_TIMESTAMP = 0xFFFFFFFF; //Full 32 bit (01h11m35s) #endif + static const int32_t SYN_INTERVAL = 10000; // Periodical Rate Control Interval, 10000 microsecond + protected: static const uint32_t TIMESTAMP_MASK = MAX_TIMESTAMP; // this value to be also used as a mask public: -#endif /* SRT_ENABLE_TSBPD */ - /// Clone this packet. /// @return Pointer to the new packet. @@ -348,10 +360,16 @@ friend class CRcvQueue; typedef DynamicStruct HEADER_TYPE; HEADER_TYPE m_nHeader; //< The 128-bit header field - //uint32_t m_nHeader[PH_SIZE]; //< The 128-bit header field + // XXX NOTE: iovec here is not portable. On Windows there's a different + // (although similar) structure defined, which means that this way the + // Windows function that is an equivalent of `recvmsg` cannot be used. + // For example, something like that: + // class IoVector: public iovec { public: size_t size() { return iov_len; } char* data() { return iov_base; } }; + // class IoVector: public WSAMSG { public: size_t size() { return len; } char* data() { return buf; } }; iovec m_PacketVector[PV_SIZE]; //< The 2-demension vector of UDT packet [header, data] int32_t __pad; + bool m_data_owned; protected: CPacket& operator=(const CPacket&); @@ -368,69 +386,23 @@ friend class CRcvQueue; static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size // Used in many computations + // Actually this can be also calculated as: sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct udphdr). static const size_t UDP_HDR_SIZE = 28; // 20 bytes IPv4 + 8 bytes of UDP { u16 sport, dport, len, csum }. -#if ENABLE_LOGGING - std::string MessageFlagStr(); -#endif -}; - -//////////////////////////////////////////////////////////////////////////////// - -enum UDTRequestType -{ - URQ_INDUCTION_TYPES = 0, // used to check in one place. Consdr rm. - - URQ_INDUCTION = 1, // First part for client-server connection - URQ_RENDEZVOUS = 0, // First part for rendezvous connection - - URQ_CONCLUSION = -1, // Second part of handshake negotiation - URQ_AGREEMENT = -2, // Extra (last) step for rendezvous only - - // Note: the client-server connection uses: - // --> INDUCTION (empty) - // <-- INDUCTION (cookie) - // --> CONCLUSION (cookie) - // <-- CONCLUSION (ok) - - // The rendezvous connection uses: - // --> RENDEZVOUS (effective only if peer is also connecting) - // <-- CONCLUSION - // --> AGREEMENT - - // Errors reported by the peer, also used as useless error codes - // in handshake processing functions. - URQ_FAILURE_TYPES = 1000, - URQ_ERROR_REJECT = 1002, - URQ_ERROR_INVALID = 1004 -}; - -class CHandShake -{ -public: - CHandShake(); + static const size_t SRT_DATA_HDR_SIZE = UDP_HDR_SIZE + HDR_SIZE; - int serialize(char* buf, int& size); - int deserialize(const char* buf, int size); + // Some well known data + static const size_t ETH_MAX_MTU_SIZE = 1500; -public: - // This is the size of SERIALIZED handshake. - // Might be defined as simply sizeof(CHandShake), but the - // enum values would have to be forced as int32_t, which is only - // available in C++11. Theoretically they are all 32-bit, but - // such a statement is not reliable and not portable. - static const int m_iContentSize; // Size of hand shake data + // And derived + static const size_t SRT_MAX_PAYLOAD_SIZE = ETH_MAX_MTU_SIZE - SRT_DATA_HDR_SIZE; -public: - int32_t m_iVersion; // UDT version - int32_t m_iType; // UDT socket type - int32_t m_iISN; // random initial sequence number - int32_t m_iMSS; // maximum segment size - int32_t m_iFlightFlagSize; // flow control window size - int32_t m_iReqType; // connection request type: 1: regular connection request, 0: rendezvous connection request, -1/-2: response - int32_t m_iID; // socket ID - int32_t m_iCookie; // cookie - uint32_t m_piPeerIP[4]; // The IP address that the peer's UDP port is bound to + std::string MessageFlagStr() +#if ENABLE_LOGGING + ; +#else + { return ""; } +#endif }; diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp index 70c4acf69..5a214fafc 100644 --- a/srtcore/queue.cpp +++ b/srtcore/queue.cpp @@ -838,9 +838,9 @@ void CRendezvousQueue::insert(const UDTSOCKET& id, CUDT* u, int ipv, const socka m_lRendezvousID.push_back(r); } -void CRendezvousQueue::remove(const UDTSOCKET& id) +void CRendezvousQueue::remove(const UDTSOCKET& id, bool should_lock) { - CGuard vg(m_RIDVectorLock); + CGuard vg(m_RIDVectorLock, should_lock); for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++ i) { @@ -858,14 +858,15 @@ void CRendezvousQueue::remove(const UDTSOCKET& id) } } -CUDT* CRendezvousQueue::retrieve(const sockaddr* addr, UDTSOCKET& id) +CUDT* CRendezvousQueue::retrieve(const sockaddr* addr, ref_t r_id) { CGuard vg(m_RIDVectorLock); + UDTSOCKET& id = r_id; // TODO: optimize search for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++ i) { - if (CIPAddress::ipcmp(addr, i->m_pPeerAddr, i->m_iIPversion) && ((0 == id) || (id == i->m_iID))) + if (CIPAddress::ipcmp(addr, i->m_pPeerAddr, i->m_iIPversion) && ((id == 0) || (id == i->m_iID))) { id = i->m_iID; return i->m_pUDT; @@ -875,28 +876,50 @@ CUDT* CRendezvousQueue::retrieve(const sockaddr* addr, UDTSOCKET& id) return NULL; } -void CRendezvousQueue::updateConnStatus() +void CRendezvousQueue::updateConnStatus(EConnectStatus cst, const CPacket& response) { if (m_lRendezvousID.empty()) return; CGuard vg(m_RIDVectorLock); -#ifdef HAI_PATCH // Remove iterator increment (to remove elements while looping) - for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end();) -#else /* HAI_PATCH */ - for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++ i) -#endif /* HAI_PATCH */ + int debug_nupd = 0; + int debug_nrun = 0; + + LOGC(mglog.Debug) << "updateConnStatus: updating after getting pkt id=" << response.m_iID << " status: " << ConnectStatusStr(cst); + + list::iterator i = m_lRendezvousID.begin(); + while (i != m_lRendezvousID.end()) { + // NOTE: This is a SAFE LOOP. + // Incrementation will be done at the end, after the processing did not + // REMOVE the currently processed element. When the element was removed, + // the iterator value for the next iteration will be taken from erase()'s result. + + ++debug_nrun; // avoid sending too many requests, at most 1 request per 250ms - if (CTimer::getTime() - i->m_pUDT->m_llLastReqTime > 250000) + uint64_t now = CTimer::getTime(); + uint64_t then = i->m_pUDT->m_llLastReqTime; + bool nowstime = (now - then) > 250000; + + LOGC(mglog.Debug) << "RID:%" << i->m_iID << " then=" << then << " now=" << now << " passed=" << (now-then) + << " -- now's " << (nowstime ? "" : "NOT ") << "the time"; + + if (nowstime) { + // XXX This guy... has created a loop that rolls in infinity without any + // sleeps inside and makes it once per about 50 calls send a hs conclusion + // for a randomly sampled rendezvous ID of a socket out of the list. + // Ok, probably the rendezvous ID should be just one so not much to + // sample from, but if so, why the container? + // + // This must be somehow fixed! if (CTimer::getTime() >= i->m_ullTTL) { + LOGC(mglog.Debug) << "RendezvousQueue: EXPIRED. removing from queue"; // connection timer expired, acknowledge app via epoll i->m_pUDT->m_bConnecting = false; CUDT::s_UDTUnited.m_EPoll.update_events(i->m_iID, i->m_pUDT->m_sPollID, UDT_EPOLL_ERR, true); -#ifdef HAI_PATCH // BugBug /* * Setting m_bConnecting to false but keeping socket in rendezvous queue is not a good idea. * Next CUDT::close will not remove it from rendezvous queue (because !m_bConnecting) @@ -908,33 +931,40 @@ void CRendezvousQueue::updateConnStatus() delete (sockaddr_in6*)i->m_pPeerAddr; i = m_lRendezvousID.erase(i); -#endif /* HAI_PATCH */ continue; } - CPacket request; - char* reqdata = new char [i->m_pUDT->m_iPayloadSize]; - request.pack(UMSG_HANDSHAKE, NULL, reqdata, i->m_pUDT->m_iPayloadSize); - // ID = 0, connection request - request.m_iID = !i->m_pUDT->m_bRendezvous ? 0 : i->m_pUDT->m_ConnRes.m_iID; - int hs_size = i->m_pUDT->m_iPayloadSize; - i->m_pUDT->m_ConnReq.serialize(reqdata, hs_size); - request.setLength(hs_size); -#ifdef SRT_ENABLE_CTRLTSTAMP - uint64_t now = CTimer::getTime(); - request.m_iTimeStamp = int(now - i->m_pUDT->m_StartTime); - i->m_pUDT->m_llLastReqTime = now; - i->m_pUDT->m_pSndQueue->sendto(i->m_pPeerAddr, request); -#else - i->m_pUDT->m_pSndQueue->sendto(i->m_pPeerAddr, request); - i->m_pUDT->m_llLastReqTime = CTimer::getTime(); -#endif - delete [] reqdata; + // This queue is used only in case of Rendezvous or Async mode caller-listener. + // Synchronous connection requests are handled in startConnect() completely. + // XXX Most likely this is just an ASYNC ONLY procedure, not even needed for + // rendezvous (blocking rendezvous is also managed in startConnect). + // Don't run this when CONN_AGAIN because this means that no unit has been read + // from the peer ever. + if (cst != CONN_AGAIN && (i->m_pUDT->m_bRendezvous || !i->m_pUDT->m_bSynRecving)) + { + ++debug_nupd; + list::iterator i_next = i; + ++i_next; + if (i->m_pUDT->processAsyncConnectRequest(cst, response, i->m_pPeerAddr)) + { + // This has been processed and most likely removed. + i = i_next; + continue; + } + } + /* + This debug log is blocked because it is sent multiple times in a millisecond without a good reason. + else + { + LOGC(mglog.Debug) << "updateConnStatus: RendezvousQueue not to send CONCLUSION for the non-rdv sokket."; + } + // */ } -#ifdef HAI_PATCH + i++; -#endif /* HAI_PATCH */ } + + LOGC(mglog.Debug) << "updateConnStatus: total of " << debug_nupd << " sockets updated, the loop ran " << (debug_nrun-debug_nupd) << " uselessly."; } // @@ -1017,15 +1047,17 @@ void* CRcvQueue::worker(void* param) { CRcvQueue* self = (CRcvQueue*)param; sockaddr_any sa ( self->m_UnitQueue.m_iIPversion ); - //sockaddr* addr = (AF_INET == self->m_UnitQueue.m_iIPversion) ? (sockaddr*) new sockaddr_in : (sockaddr*) new sockaddr_in6; int32_t id; THREAD_STATE_INIT("SRT Rx Queue"); CUnit* unit = 0; + EConnectStatus cst = CONN_AGAIN; while (!self->m_bClosing) { - if ( self->worker_RetrieveUnit(id, unit, &sa) ) + bool have_received = false; + EReadStatus rst = self->worker_RetrieveUnit(id, unit, &sa); + if (rst == RST_OK) { if ( id < 0 ) { @@ -1035,26 +1067,50 @@ void* CRcvQueue::worker(void* param) continue; } + // NOTE: cst state is being changed here. + // This state should be maintained through any next failed calls to worker_RetrieveUnit. + // Any error switches this to rejection, just for a case. + // Note to rendezvous connection. This can accept: // - ID == 0 - take the first waiting rendezvous socket // - ID > 0 - find the rendezvous socket that has this ID. if (id == 0) { // ID 0 is for connection request, which should be passed to the listening socket or rendezvous sockets - self->worker_ProcessConnectionRequest(unit, &sa); + cst = self->worker_ProcessConnectionRequest(unit, &sa); } else { // Otherwise ID is expected to be associated with: // - an enqueued rendezvous socket // - a socket connected to a peer - self->worker_ProcessAddressedPacket(id, unit, &sa); + cst = self->worker_ProcessAddressedPacket(id, unit, &sa); } + LOGC(mglog.Debug) << self->CONID() << "worker: result for the unit: " << ConnectStatusStr(cst); + have_received = true; } + else if (rst == RST_ERROR) + { + // According to the description by CChannel::recvfrom, this can be either of: + // - IPE: all errors except EBADF + // - socket was closed in the meantime by another thread: EBADF + // If EBADF, then it's expected that the "closing" state is also set. + // Check that just to report possible errors, but interrupt the loop anyway. + if (self->m_bClosing) + { + LOGC(mglog.Debug) << self->CONID() << "CChannel reported error, but Queue is closing - INTERRUPTING worker."; + } + else + { + LOGC(mglog.Fatal) << self->CONID() << "CChannel reported ERROR DURING TRANSMISSION - IPE. INTERRUPTING worker anyway."; + } + cst = CONN_REJECT; + break; + } + // OTHERWISE: this is an "AGAIN" situation. No data was read, but the process should continue. -//TIMER_CHECK: - // take care of the timing event for all UDT sockets + // take care of the timing event for all UDT sockets uint64_t currtime; CTimer::rdtsc(currtime); @@ -1080,22 +1136,44 @@ void* CRcvQueue::worker(void* param) ul = self->m_pRcvUList->m_pUList; } + if ( have_received ) + LOGC(mglog.Debug) << "worker: updateConnStatus (after received packet from the party)"; + // Check connection requests status for all sockets in the RendezvousQueue. - self->m_pRendezvousQueue->updateConnStatus(); + // Pass the connection status from the last call of: + // worker_ProcessAddressedPacket ---> + // worker_TryAsyncRend_OrStore ---> + // CUDT::processAsyncConnectResponse ---> + // CUDT::processConnectResponse + self->m_pRendezvousQueue->updateConnStatus(cst, unit->m_Packet); } - /* - if (AF_INET == self->m_UnitQueue.m_iIPversion) - delete (sockaddr_in*)addr; - else - delete (sockaddr_in6*)addr; - */ - THREAD_EXIT(); return NULL; } -bool CRcvQueue::worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr* addr) +static string PacketInfo(const CPacket& pkt) +{ + ostringstream os; + os << "TARGET=" << pkt.m_iID << " "; + + if (pkt.isControl()) + { + os << "CONTROL: " << MessageTypeStr(pkt.getType(), pkt.getExtendedType()) << " size=" << pkt.getLength(); + } + else + { + // It's hard to extract the information about peer's supported rexmit flag. + // This is only a log, nothing crucial, so we can risk displaying incorrect message number. + // Declaring that the peer supports rexmit flag cuts off the highest bit from + // the displayed number. + os << "DATA: msg=" << pkt.getMsgSeq(true) << " seq=" << pkt.getSeqNo() << " size=" << pkt.getLength(); + } + + return os.str(); +} + +EReadStatus CRcvQueue::worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr* addr) { #ifdef NO_BUSY_WAITING m_pTimer->tick(); @@ -1120,57 +1198,48 @@ bool CRcvQueue::worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr* addr) temp.m_pcData = new char[m_iPayloadSize]; temp.setLength(m_iPayloadSize); THREAD_PAUSED(); - m_pChannel->recvfrom(addr, temp); + EReadStatus rst = m_pChannel->recvfrom(addr, temp); THREAD_RESUMED(); #if ENABLE_LOGGING - string packetinfo; - if (temp.isControl()) - { - packetinfo = "CONTROL: " + MessageTypeStr(temp.getType(), temp.getExtendedType()); - } - else - { - ostringstream os; - // It's hard to extract the information about peer's supported rexmit flag. - // This is only a log, nothing crucial, so we can risk displaying incorrect message number. - // Declaring that the peer supports rexmit flag cuts off the highest bit from - // the displayed number. - os << "DATA: msg=" << temp.getMsgSeq(true) << " seq=" << temp.getSeqNo(); - packetinfo = os.str(); - } - LOGC(mglog.Error) << CONID() << "LOCAL STORAGE DEPLETED. Dropping 1 packet: " << packetinfo; - + LOGC(mglog.Error) << CONID() << "LOCAL STORAGE DEPLETED. Dropping 1 packet: " << PacketInfo(temp); #endif delete [] temp.m_pcData; - return false; + + // Be transparent for RST_ERROR, but ignore the correct + // data read and fake that the packet was dropped. + return rst == RST_ERROR ? RST_ERROR : RST_AGAIN; } unit->m_Packet.setLength(m_iPayloadSize); // reading next incoming packet, recvfrom returns -1 is nothing has been received THREAD_PAUSED(); - if (m_pChannel->recvfrom(addr, unit->m_Packet) < 0) + EReadStatus rst = m_pChannel->recvfrom(addr, unit->m_Packet); + THREAD_RESUMED(); + + if (rst == RST_OK) { - THREAD_RESUMED(); - return false; + id = unit->m_Packet.m_iID; + LOGC(mglog.Debug) << "INCOMING PACKET: BOUND=" << SockaddrToString(m_pChannel->bindAddress()) << " " << PacketInfo(unit->m_Packet); } - THREAD_RESUMED(); - id = unit->m_Packet.m_iID; - return true; + return rst; } -void CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* addr) +EConnectStatus CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* addr) { + LOGC(mglog.Debug) << "Got sockID=0 from " << SockaddrToString(addr) << " - trying to resolve it as a connection request..."; // Introduced protection because it may potentially happen // that another thread could have closed the socket at // the same time and inject a bug between checking the // pointer for NULL and using it. + int listener_ret = 0; bool have_listener = false; { CGuard cg(m_LSLock); if (m_pListener) { - m_pListener->processConnectRequest(addr, unit->m_Packet); + LOGC(mglog.Note) << "... PASSING request from: " << SockaddrToString(addr) << " to agent:" << m_pListener->socketID(); + listener_ret = m_pListener->processConnectRequest(addr, unit->m_Packet); // XXX This returns some very significant return value, which // is completely ignored here. // Actually this is the only place in the code where this @@ -1198,33 +1267,41 @@ void CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* add } } - if ( !have_listener ) - worker_TryConnectRendezvous(0, unit, addr); // 0 id because this is connection request - else + // NOTE: Rendezvous sockets do bind(), but not listen(). It means that the socket is + // ready to accept connection requests, but they are not being redirected to the listener + // socket, as this is not a listener socket at all. This goes then HERE. + + if ( have_listener ) // That is, the above block with m_pListener->processConnectRequest was executed { - LOGC(mglog.Note) << CONID() << "listener received connection request from: " << SockaddrToString(addr); + LOGC(mglog.Note) << CONID() << "listener managed the connection request from: " << SockaddrToString(addr) + << " result:" << RequestTypeStr(UDTRequestType(listener_ret)); + return (listener_ret >= URQ_FAILURE_TYPES ? CONN_REJECT : CONN_CONTINUE); } + + // If there's no listener waiting for the packet, just store it into the queue. + return worker_TryAsyncRend_OrStore(0, unit, addr); // 0 id because the packet came in with that very ID. } -void CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr* addr) +EConnectStatus CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr* addr) { CUDT* u = m_pHash->lookup(id); if ( !u ) { - // Fallback to rendezvous connection request. - // If this still didn't make it, ignore. - return worker_TryConnectRendezvous(id, unit, addr); + // Pass this to either async rendezvous connection, + // or store the packet in the queue. + LOGC(mglog.Debug) << "worker_ProcessAddressedPacket: resending to target socket %" << id; + return worker_TryAsyncRend_OrStore(id, unit, addr); } // Found associated CUDT - process this as control or data packet // addressed to an associated socket. if (!CIPAddress::ipcmp(addr, u->m_pPeerAddr, u->m_iIPversion)) { - LOGC(mglog.Debug) << CONID() << "Packet for SID=" << id << " asoc with " << CIPAddress::show(u->m_pPeerAddr) - << " received from " << CIPAddress::show(addr) << " (CONSIDERED ATTACK ATTEMPT)"; + LOGC(mglog.Debug) << CONID() << "Packet for SID=" << id << " asoc with " << SockaddrToString(u->m_pPeerAddr) + << " received from " << SockaddrToString(addr) << " (CONSIDERED ATTACK ATTEMPT)"; // This came not from the address that is the peer associated // with the socket. Reject. - return; + return CONN_REJECT; } if (!u->m_bConnected || u->m_bBroken || u->m_bClosing) @@ -1233,7 +1310,7 @@ void CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const soc // or destroyed. Ignore. // XXX send UMSG_SHUTDOWN in this case? // XXX May it require mutex protection? - return; + return CONN_REJECT; } if (unit->m_Packet.isControl()) @@ -1243,38 +1320,62 @@ void CRcvQueue::worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const soc u->checkTimers(); m_pRcvUList->update(u); + + return CONN_CONTINUE; } -void CRcvQueue::worker_TryConnectRendezvous(int32_t id, CUnit* unit, const sockaddr* addr) +// This function responds to the fact that a packet has come +// for a socket that does not expect to receive a normal connection +// request. This can be then: +// - a normal packet of whatever kind, just to be processed by the message loop +// - a rendezvous connection +// This function then tries to manage the packet as a rendezvous connection +// request in ASYNC mode; when this is not applicable, it stores the packet +// in the "receiving queue" so that it will be picked up in the "main" thread. +EConnectStatus CRcvQueue::worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr* addr) { - CUDT* u = m_pRendezvousQueue->retrieve(addr, id); + // This 'retrieve' requires that 'id' be either one of those + // stored in the rendezvous queue (see CRcvQueue::registerConnector) + // or simply 0, but then at least the address must match one of these. + // If the id was 0, it will be set to the actual socket ID of the returned CUDT. + CUDT* u = m_pRendezvousQueue->retrieve(addr, Ref(id)); if ( !u ) { // XXX this socket is then completely unknown to the system. // May be nice to send some rejection info to the peer. if ( id == 0 ) - LOGC(mglog.Debug) << CONID() << "Rendezvous: no sockets expect connection from " << CIPAddress::show(addr) << " - POSSIBLE ATTACK"; + LOGC(mglog.Debug) << CONID() << "AsyncOrRND: no sockets expect connection from " + << SockaddrToString(addr) << " - POSSIBLE ATTACK"; else - LOGC(mglog.Debug) << CONID() << "Rendezvous: no sockets expect socket " << id << " from " << CIPAddress::show(addr) << " - POSSIBLE ATTACK"; - return; + LOGC(mglog.Debug) << CONID() << "AsyncOrRND: no sockets expect socket " << id << " from " + << SockaddrToString(addr) << " - POSSIBLE ATTACK"; + return CONN_REJECT; } // asynchronous connect: call connect here // otherwise wait for the UDT socket to retrieve this packet if (!u->m_bSynRecving) { - u->processRendezvous(unit->m_Packet); - } - else - { - storePkt(id, unit->m_Packet.clone()); + LOGC(mglog.Debug) << "AsyncOrRND: packet RESOLVED TO ID=" << id << " -- continuing as ASYNC CONNECT"; + // This is practically same as processConnectResponse, just this applies + // appropriate mutex lock - which can't be done here because it's intentionally private. + // OTOH it can't be applied to processConnectResponse because the synchronous + // call to this method applies the lock by itself, and same-thread-double-locking is nonportable (crashable). + return u->processAsyncConnectResponse(unit->m_Packet); } + LOGC(mglog.Debug) << "AsyncOrRND: packet RESOLVED TO ID=" << id << " -- continuing through CENTRAL PACKET QUEUE"; + // This is where also the packets for rendezvous connection will be landing, + // in case of a synchronous connection. + storePkt(id, unit->m_Packet.clone()); + + return CONN_CONTINUE; } -int CRcvQueue::recvfrom(int32_t id, CPacket& packet) +int CRcvQueue::recvfrom(int32_t id, ref_t r_packet) { CGuard bufferlock(m_PassLock); + CPacket& packet = r_packet; map >::iterator i = m_mBuffer.find(id); @@ -1308,6 +1409,11 @@ int CRcvQueue::recvfrom(int32_t id, CPacket& packet) // copy packet content // XXX Check if this wouldn't be better done by providing // copy constructor for DynamicStruct. + // XXX Another thing: this looks wasteful. This expects an already + // allocated memory on the packet, this thing gets the packet, + // copies it into the passed packet and then the source packet + // gets deleted. Why not simply return the originally stored packet, + // without copying, allocation and deallocation? memcpy(packet.m_nHeader, newpkt->m_nHeader, CPacket::HDR_SIZE); memcpy(packet.m_pcData, newpkt->m_pcData, newpkt->getLength()); packet.setLength(newpkt->getLength()); @@ -1348,23 +1454,25 @@ void CRcvQueue::registerConnector(const UDTSOCKET& id, CUDT* u, int ipv, const s m_pRendezvousQueue->insert(id, u, ipv, addr, ttl); } -void CRcvQueue::removeConnector(const UDTSOCKET& id) +void CRcvQueue::removeConnector(const UDTSOCKET& id, bool should_lock) { - m_pRendezvousQueue->remove(id); + LOGC(mglog.Debug) << "removeConnector: removing %" << id; + m_pRendezvousQueue->remove(id, should_lock); - CGuard bufferlock(m_PassLock); + CGuard bufferlock(m_PassLock); - map >::iterator i = m_mBuffer.find(id); - if (i != m_mBuffer.end()) - { - while (!i->second.empty()) - { - delete [] i->second.front()->m_pcData; - delete i->second.front(); - i->second.pop(); - } - m_mBuffer.erase(i); - } + map >::iterator i = m_mBuffer.find(id); + if (i != m_mBuffer.end()) + { + LOGC(mglog.Debug) << "removeConnector: ... and its packet queue with " << i->second.size() << " packets collected"; + while (!i->second.empty()) + { + delete [] i->second.front()->m_pcData; + delete i->second.front(); + i->second.pop(); + } + m_mBuffer.erase(i); + } } void CRcvQueue::setNewEntry(CUDT* u) diff --git a/srtcore/queue.h b/srtcore/queue.h index f0bc84a8a..b9f0b6125 100644 --- a/srtcore/queue.h +++ b/srtcore/queue.h @@ -67,6 +67,8 @@ modified by #include "channel.h" #include "common.h" #include "packet.h" +#include "netinet_any.h" +#include "utilities.h" #include #include #include @@ -308,10 +310,15 @@ class CRendezvousQueue public: void insert(const UDTSOCKET& id, CUDT* u, int ipv, const sockaddr* addr, uint64_t ttl); - void remove(const UDTSOCKET& id); - CUDT* retrieve(const sockaddr* addr, UDTSOCKET& id); - void updateConnStatus(); + // The should_lock parameter is given here to state as to whether + // the lock should be applied here. If called from some internals + // and the lock IS ALREADY APPLIED, use false here to prevent + // double locking and deadlock in result. + void remove(const UDTSOCKET& id, bool should_lock); + CUDT* retrieve(const sockaddr* addr, ref_t id); + + void updateConnStatus(EConnectStatus, const CPacket& response); private: struct CRL @@ -416,7 +423,7 @@ friend class CUDTUnited; public: // XXX There's currently no way to access the socket ID set for - // whatever the queue is currently working for. Required to find + // whatever the queue is currently working. Required to find // some way to do this, possibly by having a "reverse pointer". // Currently just "unimplemented". std::string CONID() const { return ""; } @@ -436,16 +443,16 @@ friend class CUDTUnited; /// @param [out] packet received packet /// @return Data size of the packet - int recvfrom(int32_t id, CPacket& packet); + int recvfrom(int32_t id, ref_t packet); private: static void* worker(void* param); pthread_t m_WorkerThread; // Subroutines of worker - bool worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr* sa); - void worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* sa); - void worker_TryConnectRendezvous(int32_t id, CUnit* unit, const sockaddr* sa); - void worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr* sa); + EReadStatus worker_RetrieveUnit(int32_t& id, CUnit*& unit, sockaddr* sa); + EConnectStatus worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* sa); + EConnectStatus worker_TryAsyncRend_OrStore(int32_t id, CUnit* unit, const sockaddr* sa); + EConnectStatus worker_ProcessAddressedPacket(int32_t id, CUnit* unit, const sockaddr* sa); private: CUnitQueue m_UnitQueue; // The received packet queue @@ -465,7 +472,7 @@ friend class CUDTUnited; void removeListener(const CUDT* u); void registerConnector(const UDTSOCKET& id, CUDT* u, int ipv, const sockaddr* addr, uint64_t ttl); - void removeConnector(const UDTSOCKET& id); + void removeConnector(const UDTSOCKET& id, bool should_lock = true); void setNewEntry(CUDT* u); bool ifNewEntry(); diff --git a/srtcore/srt.h b/srtcore/srt.h index ab273af18..854a564f4 100644 --- a/srtcore/srt.h +++ b/srtcore/srt.h @@ -30,6 +30,22 @@ written by #define SRT_API UDT_API +// For feature tests if you need. +// You can use these constants with SRTO_MINVERSION option. +#define SRT_VERSION_FEAT_HSv5 0x010300 + + +// To construct version value +#define SRT_MAKE_VERSION(major, minor, patch) ((patch)+((minor)*0x100)+((major)*0x10000)) + +#ifdef __GNUG__ +#define SRT_ATR_UNUSED __attribute__((unused)) +#define SRT_ATR_DEPRECATED __attribute__((deprecated)) +#else +#define SRT_ATR_UNUSED +#define SRT_ATR_DEPRECATED +#endif + #ifdef __cplusplus extern "C" { #endif @@ -77,29 +93,38 @@ typedef enum SRT_SOCKOPT { SRTO_RCVDATA, // size of data available for recv SRTO_SENDER = 21, // Sender mode (independent of conn mode), for encryption, tsbpd handshake. SRTO_TSBPDMODE = 22, // Enable/Disable TsbPd. Enable -> Tx set origin timestamp, Rx deliver packet at origin time + delay - SRTO_TSBPDDELAY = 23, // TsbPd receiver delay (mSec) to absorb burst of missed packet retransmission - SRTO_LATENCY = 23, // ALIAS: SRTO_TSBPDDELAY + SRTO_LATENCY = 23, // DEPRECATED. SET: to both SRTO_RCVLATENCY and SRTO_PEERLATENCY. GET: same as SRTO_RCVLATENCY. + SRTO_TSBPDDELAY = 23, // ALIAS: SRTO_LATENCY SRTO_INPUTBW = 24, // Estimated input stream rate. SRTO_OHEADBW, // MaxBW ceiling based on % over input stream rate. Applies when UDT_MAXBW=0 (auto). SRTO_PASSPHRASE = 26, // Crypto PBKDF2 Passphrase size[0,10..64] 0:disable crypto SRTO_PBKEYLEN, // Crypto key len in bytes {16,24,32} Default: 16 (128-bit) SRTO_KMSTATE, // Key Material exchange status (UDT_SRTKmState) - SRTO_IPTTL = 29, // IP Time To Live - SRTO_IPTOS, // IP Type of Service + SRTO_IPTTL = 29, // IP Time To Live (passthru for system sockopt IPPROTO_IP/IP_TTL) + SRTO_IPTOS, // IP Type of Service (passthru for system sockopt IPPROTO_IP/IP_TOS) SRTO_TLPKTDROP = 31, // Enable receiver pkt drop - SRTO_TSBPDMAXLAG, // !!!IMPORTANT NOTE: obsolete parameter. Has no effect !!! + SRTO_TSBPDMAXLAG, // Decoder's tolerated lag past TspPD delay (decoder's buffer) SRTO_NAKREPORT = 33, // Enable receiver to send periodic NAK reports SRTO_VERSION = 34, // Local SRT Version SRTO_PEERVERSION, // Peer SRT Version (from SRT Handshake) SRTO_CONNTIMEO = 36, // Connect timeout in msec. Ccaller default: 3000, rendezvous (x 10) - SRTO_TWOWAYDATA = 37, - SRTO_SNDPBKEYLEN = 38, - SRTO_RCVPBKEYLEN, - SRTO_SNDPEERKMSTATE, - SRTO_RCVKMSTATE, - SRTO_LOSSMAXTTL, + // deprecated: SRTO_TWOWAYDATA (@c below) + SRTO_SNDPBKEYLEN = 38, // (DEPRECATED: use SRTO_PBKEYLEN) + SRTO_RCVPBKEYLEN, // (DEPRECATED: use SRTO_PBKEYLEN) + SRTO_SNDPEERKMSTATE, // (GET) the current state of the encryption at the peer side + SRTO_RCVKMSTATE, // (GET) the current state of the encryption at the agent side + SRTO_LOSSMAXTTL, // Maximum possible packet reorder tolerance (number of packets to receive after loss to send lossreport) + SRTO_RCVLATENCY, // TsbPd receiver delay (mSec) to absorb burst of missed packet retransmission + SRTO_PEERLATENCY, // Minimum value of the TsbPd receiver delay (mSec) for the opposite side (peer) + SRTO_MINVERSION, // Minimum SRT version needed for the peer (peers with less version will get connection reject) + SRTO_STREAMID // A string set to a socket and passed to the listener's accepted socket } SRT_SOCKOPT; + +// SRTO_TWOWAYDATA: not to be used. SRT connection is always bidirectional if +// both clients support HSv5 - that is, since version 1.3.0 +static const SRT_SOCKOPT SRTO_TWOWAYDATA SRT_ATR_DEPRECATED = (SRT_SOCKOPT)37; + // UDT error code // Using duplicated wrapper until backward-compatible apps using UDT // enum are destroyed. @@ -172,6 +197,8 @@ SRT_API extern int srt_bind_peerof(SRTSOCKET u, UDPSOCKET udpsock); SRT_API extern int srt_listen(SRTSOCKET u, int backlog); SRT_API extern SRTSOCKET srt_accept(SRTSOCKET u, struct sockaddr* addr, int* addrlen); SRT_API extern int srt_connect(SRTSOCKET u, const struct sockaddr* name, int namelen); +SRT_API extern int srt_rendezvous(UDTSOCKET u, const struct sockaddr* local_name, int local_namelen, + const struct sockaddr* remote_name, int remote_namelen); SRT_API extern int srt_close(SRTSOCKET u); SRT_API extern int srt_getpeername(SRTSOCKET u, struct sockaddr* name, int* namelen); SRT_API extern int srt_getsockname(SRTSOCKET u, struct sockaddr* name, int* namelen); @@ -198,7 +225,8 @@ SRT_API extern const char* srt_strerror(int code, int errnoval); SRT_API extern void srt_clearlasterror(void); // performance track -SRT_API extern int srt_perfmon(SRTSOCKET u, SRT_TRACEINFO * perf, int clear); +// srt_perfmon is deprecated - use srt_bstats, which provides the same stats plus more. +SRT_API extern int srt_perfmon(SRTSOCKET u, SRT_TRACEINFO * perf, int clear) SRT_ATR_DEPRECATED; SRT_API extern int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); // Socket Status (for problem tracking) diff --git a/srtcore/srt4udt.h b/srtcore/srt4udt.h index 80bdcf853..fb48c178a 100644 --- a/srtcore/srt4udt.h +++ b/srtcore/srt4udt.h @@ -29,25 +29,11 @@ written by #error "This is protected header, used by udt.h. This shouldn't be included directly" #endif -/* -* SRT_ENABLE_SRTCC_EMB: Embedded SRT Congestion Control -*/ -#define SRT_ENABLE_SRTCC_EMB 1 - /* * SRT_ENABLE_SRTCC_API: "C" application setting ("C" wrapper) */ //undef SRT_ENABLE_SRTCC_API 1 -/* -* SRT_ENABLE_TSBPD: TimeStamp-Based Packet Delivery -* Reproduce the sending pace at the receiver side using UDT packet timestamps -*/ -#define SRT_ENABLE_TSBPD 1 - -#ifdef SRT_ENABLE_TSBPD - -#define SRT_ENABLE_CTRLTSTAMP 1 /* Set control packet timestamp (required by TSBPD) */ #define SRT_ENABLE_TLPKTDROP 1 /* Too-Late Pkts Dropping: Sender drop unacked data too late to be sent and recver forget late missing data */ //undef SRT_ENABLE_ECN 1 /* Early Congestion Notification (for source bitrate control) */ #define SRT_ENABLE_SRCTIMESTAMP 1 /* Support timestamp carryover from one SRT connection (Rx) to the next (Tx) */ @@ -59,7 +45,6 @@ written by //undef SRT_DEBUG_TLPKTDROP_DROPSEQ 1 //undef SRT_DEBUG_SNDQ_HIGHRATE 1 -#endif /* SRT_ENABLE_TSBPD */ /* * SRT_ENABLE_FASTREXMIT @@ -90,24 +75,12 @@ written by */ #define SRT_ENABLE_NAKREPORT 1 -/* -* SRT_ENABLE_BSTATS -* Real bytes counter stats (instead of pkts * 1500) -*/ -#define SRT_ENABLE_BSTATS 1 - -#ifdef SRT_ENABLE_BSTATS - -#define SRT_ENABLE_INPUTRATE 1 /* Compute encoded TS bitrate (sender's input) */ -#define SRT_DATA_PKTHDR_SIZE (16+8+20) /* SRT+UDP+IP headers */ - #define SRT_ENABLE_RCVBUFSZ_MAVG 1 /* Recv buffer size moving average */ #define SRT_ENABLE_SNDBUFSZ_MAVG 1 /* Send buffer size moving average */ #define SRT_MAVG_SAMPLING_RATE 40 /* Max sampling rate */ #define SRT_ENABLE_LOSTBYTESCOUNT 1 -#endif /* SRT_ENABLE_BSTATS */ /* * SRT_ENABLE_LOWACKRATE @@ -121,12 +94,6 @@ written by */ #define SRT_ENABLE_IPOPTS 1 -/* -* SRT_ENABLE_HAICRYPT -* Encrypt/Decriypt -*/ -#define SRT_ENABLE_HAICRYPT 1 - /* * SRT_ENABLE_SND2WAYPROTECT * Protect sender-only from back handshake and traffic @@ -139,4 +106,7 @@ written by */ #define SRT_FIX_KEEPALIVE 1 + +#define SRT_ENABLE_CLOSE_SYNCH 0 + #endif /* SRT4UDT_H */ diff --git a/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp index 2afb81a39..94a1248c0 100644 --- a/srtcore/srt_c_api.cpp +++ b/srtcore/srt_c_api.cpp @@ -41,6 +41,31 @@ int srt_listen(UDTSOCKET u, int backlog) { return UDT::listen(u, backlog); } UDTSOCKET srt_accept(UDTSOCKET u, struct sockaddr * addr, int * addrlen) { return UDT::accept(u, addr, addrlen); } int srt_connect(UDTSOCKET u, const struct sockaddr * name, int namelen) { return UDT::connect(u, name, namelen); } +int srt_rendezvous(UDTSOCKET u, const struct sockaddr* local_name, int local_namelen, + const struct sockaddr* remote_name, int remote_namelen) +{ + bool yes = 1; + UDT::setsockopt(u, 0, UDT_RENDEZVOUS, &yes, sizeof yes); + + // Note: PORT is 16-bit and at the same location in both sockaddr_in and sockaddr_in6. + // Just as a safety precaution, check the structs. + if ( (local_name->sa_family != AF_INET && local_name->sa_family != AF_INET6) + || local_name->sa_family != remote_name->sa_family) + return SRT_EINVPARAM; + + sockaddr_in* local_sin = (sockaddr_in*)local_name; + sockaddr_in* remote_sin = (sockaddr_in*)remote_name; + + if (local_sin->sin_port != remote_sin->sin_port) + return SRT_EINVPARAM; + + int st = srt_bind(u, local_name, local_namelen); + if ( st != 0 ) + return st; + + return srt_connect(u, remote_name, remote_namelen); +} + int srt_close(UDTSOCKET u) { SRT_SOCKSTATUS st = srt_getsockstate(u); diff --git a/srtcore/threadname.h b/srtcore/threadname.h index 0663e7af1..09bba4ae7 100644 --- a/srtcore/threadname.h +++ b/srtcore/threadname.h @@ -80,6 +80,7 @@ class ThreadName public: static bool get(char*) { return false; } + static bool set(const char*) { return false; } ThreadName(const char*) { @@ -88,6 +89,7 @@ class ThreadName ~ThreadName() // just to make it "non-trivially-destructible" for compatibility with normal version { } + }; diff --git a/srtcore/udt.h b/srtcore/udt.h index 025a409d3..d325db1dd 100644 --- a/srtcore/udt.h +++ b/srtcore/udt.h @@ -234,14 +234,10 @@ enum UDT_SOCKOPT UDT_SNDDATA, // size of data in the sending buffer UDT_RCVDATA, // size of data available for recv SRT_SENDER = 21, // Set sender mode, independent of connection mode -#ifdef SRT_ENABLE_TSBPD SRT_TSBPDMODE = 22, // Enable/Disable TsbPd. Enable -> Tx set origin timestamp, Rx deliver packet at origin time + delay SRT_TSBPDDELAY, // TsbPd receiver delay (mSec) to absorb burst of missed packet retransmission -#endif -#ifdef SRT_ENABLE_INPUTRATE SRT_INPUTBW = 24, SRT_OHEADBW, -#endif SRT_PASSPHRASE = 26, // PBKDF2 passphrase size[0,10..80] 0:disable crypto SRT_PBKEYLEN, // PBKDF2 generated key len in bytes {16,24,32} Default: 16 (128-bit) SRT_KMSTATE, // Key Material exchange status (SRT_KM_STATE) @@ -261,12 +257,16 @@ enum UDT_SOCKOPT #ifdef SRT_ENABLE_CONNTIMEO SRT_CONNTIMEO = 36, #endif - SRT_TWOWAYDATA = 37, + //SRT_TWOWAYDATA = 37, SRT_SNDPBKEYLEN = 38, SRT_RCVPBKEYLEN, SRT_SNDPEERKMSTATE, SRT_RCVKMSTATE, SRT_LOSSMAXTTL, + SRT_RCVLATENCY, + SRT_PEERLATENCY, + SRT_MINVERSION, + SRT_STREAMID }; /* Binary backward compatibility obsolete options */ @@ -319,7 +319,6 @@ struct CPerfMon int byteAvailRcvBuf; // available UDT receiver buffer size }; -#ifdef SRT_ENABLE_BSTATS struct CBytePerfMon { // global measurements @@ -405,7 +404,6 @@ struct CBytePerfMon int msRcvTsbPdDelay; // Timestamp-based Packet Delivery Delay //< }; -#endif /* SRT_ENABLE_BSTATS */ //////////////////////////////////////////////////////////////////////////////// @@ -493,12 +491,10 @@ class UDT_API CUDTException int getErrorCode() const; -#ifdef HAI_PATCH /// Get the system network errno for the exception. /// @return errno. int getErrno() const; -#endif /* HAI_PATCH */ /// Clear the error code. void clear(); @@ -652,9 +648,7 @@ namespace UDT typedef CUDTException ERRORINFO; typedef UDT_SOCKOPT SOCKOPT; typedef CPerfMon TRACEINFO; -#ifdef SRT_ENABLE_BSTATS typedef CBytePerfMon TRACEBSTATS; -#endif typedef ud_set UDSET; UDT_API extern const UDTSOCKET INVALID_SOCK; @@ -691,12 +685,6 @@ UDT_API int64_t recvfile(UDTSOCKET u, std::fstream& ofs, int64_t& offset, int64_ UDT_API int64_t sendfile2(UDTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 364000); UDT_API int64_t recvfile2(UDTSOCKET u, const char* path, int64_t* offset, int64_t size, int block = 7280000); -#ifdef SRT_ENABLE_SRTCC_API -UDT_API int setsrtcc(UDTSOCKET u); -UDT_API int setsrtcc_maxbitrate(UDTSOCKET u, int maxbitrate); -UDT_API int setsrtcc_windowsize(UDTSOCKET u, int windowsize); -#endif /* SRT_ENABLE_SRTCC_API */ - // select and selectEX are DEPRECATED; please use epoll. UDT_API int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout); UDT_API int selectEx(const std::vector& fds, std::vector* readfds, @@ -707,10 +695,8 @@ UDT_API int epoll_add_usock(int eid, UDTSOCKET u, const int* events = NULL); UDT_API int epoll_add_ssock(int eid, SYSSOCKET s, const int* events = NULL); UDT_API int epoll_remove_usock(int eid, UDTSOCKET u); UDT_API int epoll_remove_ssock(int eid, SYSSOCKET s); -#ifdef HAI_PATCH UDT_API int epoll_update_usock(int eid, UDTSOCKET u, const int* events = NULL); UDT_API int epoll_update_ssock(int eid, SYSSOCKET s, const int* events = NULL); -#endif /* HAI_PATCH */ UDT_API int epoll_wait(int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds = NULL, std::set* wrfds = NULL); UDT_API int epoll_wait2(int eid, UDTSOCKET* readfds, int* rnum, UDTSOCKET* writefds, int* wnum, int64_t msTimeOut, @@ -720,9 +706,7 @@ UDT_API ERRORINFO& getlasterror(); UDT_API int getlasterror_code(); UDT_API const char* getlasterror_desc(); UDT_API int perfmon(UDTSOCKET u, TRACEINFO* perf, bool clear = true); -#ifdef SRT_ENABLE_BSTATS UDT_API int bstats(UDTSOCKET u, TRACEBSTATS* perf, bool clear = true); -#endif UDT_API UDTSTATUS getsockstate(UDTSOCKET u); UDT_API void setloglevel(logging::LogLevel::type ll); @@ -733,6 +717,9 @@ UDT_API void setlogstream(std::ostream& stream); UDT_API void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); UDT_API void setlogflags(int flags); +UDT_API bool setstreamid(UDTSOCKET u, const std::string& sid); +UDT_API std::string getstreamid(UDTSOCKET u); + } // namespace UDT #endif /* __cplusplus */ diff --git a/srtcore/utilities.h b/srtcore/utilities.h index 49db1018b..b1bff66a8 100644 --- a/srtcore/utilities.h +++ b/srtcore/utilities.h @@ -41,13 +41,34 @@ written by #if defined(__cplusplus) && __cplusplus > 199711L #define HAVE_CXX11 1 #define ATR_NOEXCEPT noexcept +#define ATR_CONSTEXPR constexpr +// Microsoft Visual Studio supports C++11, but not fully, +// and still did not change the value of __cplusplus. Treat +// this special way. +// _MSC_VER == 1800 means Microsoft Visual Studio 2013. +#elif defined(_MSC_VER) && _MSC_VER >= 1800 +#define HAVE_CXX11 1 +#define ATR_NOEXCEPT +#define ATR_CONSTEXPR #else +#define HAVE_CXX11 0 #define ATR_NOEXCEPT // throw() - bad idea +#define ATR_CONSTEXPR + +#if defined(REQUIRE_CXX11) && REQUIRE_CXX11 == 1 +#error "The currently compiled application required C++11, but your compiler doesn't support it." +#endif + #endif +// Windows warning disabler +#define _CRT_SECURE_NO_WARNINGS + #include +#include #include #include +#include #include #include #include @@ -115,6 +136,32 @@ struct Bits }; +//inline int32_t Bit(size_t b) { return 1 << b; } +// XXX This would work only with 'constexpr', but this is +// available only in C++11. In C++03 this can be only done +// using a macro. +// +// Actually this can be expressed in C++11 using a better technique, +// such as user-defined literals: +// 2_bit --> 1 >> 2 + +#ifdef BIT +#undef BIT +#endif +#define BIT(x) (1 << (x)) + + +// ------------------------------------------------------------ +// This is something that reminds a structure consisting of fields +// of the same type, implemented as an array. It's parametrized +// by the type of fields and the type, which's values should be +// used for indexing (preferably an enum type). Whatever type is +// used for indexing, it is converted to size_t for indexing the +// actual array. +// +// The user should use it as an array: ds[DS_NAME], stating +// that DS_NAME is of enum type passed as 3rd parameter. +// However trying to do ds[0] would cause a compile error. template struct DynamicStruct { @@ -168,8 +215,22 @@ inline bool IsSet(int32_t bitset, int32_t flagset) return (bitset & flagset) == flagset; } +inline void HtoNLA(uint32_t* dst, const uint32_t* src, size_t size) +{ + for (size_t i = 0; i < size; ++ i) + dst[i] = htonl(src[i]); +} + +inline void NtoHLA(uint32_t* dst, const uint32_t* src, size_t size) +{ + for (size_t i = 0; i < size; ++ i) + dst[i] = ntohl(src[i]); +} + #if HAVE_CXX11 +#include + // Replacement for a bare reference for passing a variable to be filled by a function call. // To pass a variable, just use the std::ref(variable). The call will be accepted if you // pass the result of ref(), but will be rejected if you just pass a variable. @@ -180,8 +241,20 @@ struct ref_t: public std::reference_wrapper ref_t() {} ref_t(const ref_t& i): base(i) {} ref_t(const base& i): base(i) {} + + ref_t& operator=(const ref_t&) = default; + + void operator=(const T& i) + { + this->get() = i; + } }; +template +inline auto Ref(In i) -> decltype(std::ref(i)) { return std::ref(i); } + +template +inline auto Move(In i) -> decltype(std::move(i)) { return std::move(i); } // Gluing string of any type, wrapper for operator << @@ -203,10 +276,105 @@ inline std::string Sprint(Args&&... args) return sout.str(); } -#endif +// We need to use UniquePtr, in the form of C++03 it will be a #define. +// Naturally will be used std::move() so that it can later painlessly +// switch to C++11. +template +using UniquePtr = std::unique_ptr; +#else -//////////////////////////////////////////////////////////////////////////////// +// Homecooked version of ref_t. It's a copy of std::reference_wrapper +// voided of unwanted properties and renamed to ref_t. + +template +class ref_t +{ + Type* m_data; + +public: + typedef Type type; + + explicit ref_t(Type& __indata) + : m_data(&__indata) + { } + + ref_t(const ref_t& inref) + : m_data(inref.m_data) + { } + + void operator=(const ref_t& inref) + { + m_data = inref.m_data; + } + + void operator=(const Type& src) + { + *m_data = src; + } + + operator Type&() const + { return this->get(); } + + Type& get() const + { return *m_data; } +}; + +template +ref_t Ref(Type& arg) +{ + return ref_t(arg); +} + +// The unique_ptr requires C++11, and the rvalue-reference feature, +// so here we're simulate the behavior using the old std::auto_ptr. + +// This is only to make a "move" call transparent and look ok towards +// the C++11 code. +template +std::auto_ptr_ref Move(const std::auto_ptr_ref& in) { return in; } + +// We need to provide also some fixes for this type that were not present in auto_ptr, +// but they are present in unique_ptr. + +// C++03 doesn't have a templated typedef, but still we need some things +// that can only function as a class. +template +class UniquePtr: public std::auto_ptr +{ + typedef std::auto_ptr Base; + +public: + + // This is a template - so method names must be declared explicitly + typedef typename Base::element_type element_type; + using Base::get; + using Base::reset; + + // All constructor declarations must be repeated. + // "Constructor delegation" is also only C++11 feature. + explicit UniquePtr(element_type* __p = 0) throw() : Base(__p) {} + UniquePtr(UniquePtr& __a) throw() : Base(__a) { } + template + UniquePtr(UniquePtr<_Tp1>& __a) throw() : Base(__a) {} + + UniquePtr& operator=(UniquePtr& __a) throw() { return Base::operator=(__a); } + template + UniquePtr& operator=(UniquePtr<_Tp1>& __a) throw() { return Base::operator=(__a); } + + // Good, now we need to add some parts of the API of unique_ptr. + + bool operator==(const UniquePtr& two) const { return get() == two.get(); } + bool operator!=(const UniquePtr& two) const { return get() != two.get(); } + + bool operator==(const element_type* two) const { return get() == two; } + bool operator!=(const element_type* two) const { return get() != two; } + + operator bool () { return 0!= get(); } +}; + + +#endif class CTimer { @@ -286,7 +454,11 @@ class CTimer class CGuard { public: - CGuard(pthread_mutex_t& lock); + /// Constructs CGuard, which locks the given mutex for + /// the scope where this object exists. + /// @param lock Mutex to lock + /// @param if_condition If this is false, CGuard will do completely nothing + CGuard(pthread_mutex_t& lock, bool if_condition = true); ~CGuard(); public: @@ -383,8 +555,8 @@ class CSeqNo } public: - static const int32_t m_iSeqNoTH; // threshold for comparing seq. no. - static const int32_t m_iMaxSeqNo; // maximum sequence number used in UDT + static const int32_t m_iSeqNoTH = 0x3FFFFFFF; // threshold for comparing seq. no. + static const int32_t m_iMaxSeqNo = 0x7FFFFFFF; // maximum sequence number used in UDT }; //////////////////////////////////////////////////////////////////////////////// @@ -398,7 +570,7 @@ class CAckNo {return (ackno == m_iMaxAckSeqNo) ? 0 : ackno + 1;} public: - static const int32_t m_iMaxAckSeqNo; // maximum ACK sub-sequence number used in UDT + static const int32_t m_iMaxAckSeqNo = 0x7FFFFFFF; // maximum ACK sub-sequence number used in UDT }; @@ -621,4 +793,60 @@ class DriftTracer }; +inline std::string FormatBinaryString(const uint8_t* bytes, size_t size) +{ + if ( size == 0 ) + return ""; + + char buf[256]; + std::ostringstream os; + + // I know, it's funny to use sprintf and ostringstream simultaneously, + // but " %02X" in iostream is: << " " << hex << uppercase << setw(2) << setfill('0') << VALUE << setw(1) + // Too noisy. OTOH ostringstream solves the problem of memory allocation + // for a string of unpredictable size. + sprintf(buf, "%02X", int(bytes[0])); + os << buf; + for (size_t i = 1; i < size; ++i) + { + sprintf(buf, " %02X", int(bytes[i])); + os << buf; + } + return os.str(); +} + + +// Version parsing +inline ATR_CONSTEXPR uint32_t SrtVersion(int major, int minor, int patch) +{ + return patch + minor*0x100 + major*0x10000; +} + +inline int32_t SrtParseVersion(const char* v) +{ + int major, minor, patch; + int result = sscanf(v, "%d.%d.%d", &major, &minor, &patch); + + if ( result != 3 ) + { + return 0; + fprintf(stderr, "Invalid version format for HAISRT_VERSION: %s - use m.n.p\n", v); + throw v; // Throwing exception, as this function will be run before main() + } + + return major*0x10000 + minor*0x100 + patch; +} + +inline std::string SrtVersionString(int version) +{ + int patch = version % 0x100; + int minor = (version/0x100)%0x100; + int major = version/0x10000; + + char buf[20]; + sprintf(buf, "%d.%d.%d", major, minor, patch); + return buf; +} + + #endif diff --git a/srtcore/window.cpp b/srtcore/window.cpp index 1a39667e8..71b84b452 100644 --- a/srtcore/window.cpp +++ b/srtcore/window.cpp @@ -158,7 +158,7 @@ void CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeW r_probeWindow[k] = 1000; //1 msec -> 1000 pkts/sec for (size_t i = 0; i < asize; ++ i) - r_bytesWindow[i] = (1500 - SRT_DATA_PKTHDR_SIZE); //based on 1 pkt/sec set in r_pktWindow[i] + r_bytesWindow[i] = CPacket::SRT_MAX_PAYLOAD_SIZE; //based on 1 pkt/sec set in r_pktWindow[i] } @@ -186,7 +186,6 @@ int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, cons { ++ count; //packet counter sum += *p; //usec counter -////#ifdef SRT_ENABLE_BSTATS bytes += (unsigned long)*bp; //byte counter } ++ p; //advance packet pointer @@ -196,7 +195,7 @@ int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, cons // claculate speed, or return 0 if not enough valid value if (count > (asize >> 1)) { - bytes += (SRT_DATA_PKTHDR_SIZE * count); //Add protocol headers to bytes received + bytes += (CPacket::SRT_DATA_HDR_SIZE * count); //Add protocol headers to bytes received bytesps = (unsigned long)ceil(1000000.0 / (double(sum) / double(bytes))); return (int)ceil(1000000.0 / (sum / count)); } @@ -205,26 +204,37 @@ int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, cons bytesps = 0; return 0; } -/* #else - } - ++ p; - } - - // claculate speed, or return 0 if not enough valid value - if (count > (ASIZE >> 1)) - return (int)ceil(1000000.0 / (sum / count)); - else - return 0; -#endif -*/ } int CPktTimeWindowTools::getBandwidth_in(const int* window, int* replica, size_t psize) { + // This calculation does more-less the following: + // + // 1. Having example window: + // - 50, 51, 100, 55, 80, 1000, 600, 1500, 1200, 10, 90 + // 2. This window is now sorted, but we only know the value in the middle: + // - 10, 50, 51, 55, 80, [[90]], 100, 600, 1000, 1200, 1500 + // 3. Now calculate: + // - lower: 90/8 = 11.25 + // - upper: 90*8 = 720 + // 4. Now calculate the arithmetic median from all these values, + // but drop those from outside the range: + // - 10, (11<) [ 50, 51, 55, 80, 90, 100, 600, ] (>720) 1000, 1200, 1500 + // 5. Calculate the median from the extracted range, + // NOTE: the median is actually repeated once, so size is +1. + // + // values = { 50, 51, 55, 80, 90, 100, 600 }; + // sum = 90 + accumulate(values); ==> 1026 + // median = sum/(1 + values.size()); ==> 147 + // + // For comparison: the overall arithmetic median from this window == 430 + // + // 6. Returned value = 1M/median + // get median value, but cannot change the original value order in the window std::copy(window, window + psize - 1, replica); std::nth_element(replica, replica + (psize / 2), replica + psize - 1); - //std::sort(replica, replica + psize); + //std::sort(replica, replica + psize); <--- was used for debug, just leave it as a mark int median = replica[psize / 2]; int count = 1; diff --git a/srtcore/window.h b/srtcore/window.h index aac058f33..b7e3c09e4 100644 --- a/srtcore/window.h +++ b/srtcore/window.h @@ -69,6 +69,7 @@ modified by #include #endif #include "udt.h" +#include "packet.h" namespace ACKWindowTools { @@ -257,9 +258,19 @@ class CPktTimeWindow: CPktTimeWindowTools // record the probing packets interval // Adjust the time for what a complete packet would have take int64_t timediff = m_CurrArrTime - m_ProbeTime; - int64_t timediff_times_pl_size = timediff * (1500 - SRT_DATA_PKTHDR_SIZE); - - m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? int(timediff_times_pl_size / pktsz) : int(timediff); + int64_t timediff_times_pl_size = timediff * CPacket::SRT_MAX_PAYLOAD_SIZE; + + // Let's take it simpler than it is coded here: + // (stating that a packet has never zero size) + // + // probe_case = (now - previous_packet_time) * SRT_MAX_PAYLOAD_SIZE / pktsz; + // + // Meaning: if the packet is fully packed, probe_case = timediff. + // Otherwise the timediff will be "converted" to a time that a fully packed packet "would take", + // provided the arrival time is proportional to the payload size and skipping + // the ETH+IP+UDP+SRT header part elliminates the constant packet delivery time influence. + // + m_aProbeWindow[m_iProbeWindowPtr] = pktsz ? timediff_times_pl_size / pktsz : int(timediff); // OLD CODE BEFORE BSTATS: // record the probing packets interval