From bdeb5d24336f001a0e3d38894eb89af192c0d2d1 Mon Sep 17 00:00:00 2001 From: Roman Diouskine Date: Fri, 21 Apr 2017 18:42:56 -0400 Subject: [PATCH] Initial commit --- CMakeLists.txt | 417 +++ CONTRIBUTING.md | 49 + COPYING | 502 +++ README.md | 86 + apps/UDT | 1 + apps/appcommon.hpp | 124 + apps/stransmit.cpp | 1624 ++++++++++ apps/suflip.cpp | 689 +++++ apps/testcapi.c | 72 + apps/uriparser-test.cpp | 1 + apps/uriparser.cpp | 277 ++ apps/uriparser.hpp | 84 + apps/utility-test.cpp | 47 + common/srt_compat.c | 187 ++ configure | 207 ++ configure-data.tcl | 267 ++ haicrypt/HEADERS.maf | 8 + haicrypt/hc_openssl_aes.c | 479 +++ haicrypt/hc_openssl_evp_cbc.c | 271 ++ haicrypt/hc_openssl_evp_ctr.c | 343 +++ haicrypt/hcrypt.c | 204 ++ haicrypt/hcrypt.h | 220 ++ haicrypt/hcrypt_ctx_rx.c | 207 ++ haicrypt/hcrypt_ctx_tx.c | 360 +++ haicrypt/hcrypt_rx.c | 154 + haicrypt/hcrypt_sa.c | 112 + haicrypt/hcrypt_tx.c | 190 ++ haicrypt/hcrypt_ut.c | 231 ++ haicrypt/hcrypt_xpt_srt.c | 179 ++ haicrypt/hcrypt_xpt_sta.c | 187 ++ include/haicrypt.h | 125 + include/hcrypt_ctx.h | 166 + include/hcrypt_msg.h | 163 + include/srt_compat.h | 173 ++ include/win/inttypes.h | 305 ++ include/win/stdint.h | 203 ++ include/win/syslog_defs.h | 45 + include/win/unistd.h | 52 + include/win/winporting.h | 63 + include/win/wintime.h | 42 + scripts/check-deps | 59 + scripts/haiUtil.cmake | 49 + scripts/haisrt.pc.in | 11 + scripts/sfplay | 40 + srtcore/HEADERS.maf | 8 + srtcore/Makefile | 63 + srtcore/api.cpp | 2788 +++++++++++++++++ srtcore/api.h | 295 ++ srtcore/buffer.cpp | 1717 +++++++++++ srtcore/buffer.h | 550 ++++ srtcore/cache.cpp | 123 + srtcore/cache.h | 266 ++ srtcore/ccc.cpp | 370 +++ srtcore/ccc.h | 219 ++ srtcore/channel.cpp | 467 +++ srtcore/channel.h | 184 ++ srtcore/common.cpp | 834 +++++ srtcore/common.h | 111 + srtcore/core.cpp | 5368 +++++++++++++++++++++++++++++++++ srtcore/core.h | 664 ++++ srtcore/csrtcc.cpp | 914 ++++++ srtcore/csrtcc.h | 336 +++ srtcore/epoll.cpp | 599 ++++ srtcore/epoll.h | 187 ++ srtcore/list.cpp | 784 +++++ srtcore/list.h | 255 ++ srtcore/logging.h | 543 ++++ srtcore/logging_api.h | 113 + srtcore/md5.cpp | 381 +++ srtcore/md5.h | 91 + srtcore/netinet_any.h | 136 + srtcore/packet.cpp | 532 ++++ srtcore/packet.h | 435 +++ srtcore/queue.cpp | 1416 +++++++++ srtcore/queue.h | 513 ++++ srtcore/srt.h | 255 ++ srtcore/srt4udt.h | 142 + srtcore/srt_c_api.cpp | 244 ++ srtcore/threadname.h | 94 + srtcore/udt.h | 749 +++++ srtcore/utilities.h | 624 ++++ srtcore/window.cpp | 250 ++ srtcore/window.h | 298 ++ srtcore/windows/win_time.cpp | 163 + submodules/pthread-win32/STUB | 9 + 85 files changed, 33365 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTING.md create mode 100644 COPYING create mode 100644 README.md create mode 120000 apps/UDT create mode 100644 apps/appcommon.hpp create mode 100644 apps/stransmit.cpp create mode 100644 apps/suflip.cpp create mode 100644 apps/testcapi.c create mode 120000 apps/uriparser-test.cpp create mode 100644 apps/uriparser.cpp create mode 100644 apps/uriparser.hpp create mode 100644 apps/utility-test.cpp create mode 100644 common/srt_compat.c create mode 100755 configure create mode 100644 configure-data.tcl create mode 100644 haicrypt/HEADERS.maf create mode 100644 haicrypt/hc_openssl_aes.c create mode 100644 haicrypt/hc_openssl_evp_cbc.c create mode 100644 haicrypt/hc_openssl_evp_ctr.c create mode 100644 haicrypt/hcrypt.c create mode 100644 haicrypt/hcrypt.h create mode 100644 haicrypt/hcrypt_ctx_rx.c create mode 100644 haicrypt/hcrypt_ctx_tx.c create mode 100644 haicrypt/hcrypt_rx.c create mode 100644 haicrypt/hcrypt_sa.c create mode 100644 haicrypt/hcrypt_tx.c create mode 100644 haicrypt/hcrypt_ut.c create mode 100644 haicrypt/hcrypt_xpt_srt.c create mode 100644 haicrypt/hcrypt_xpt_sta.c create mode 100644 include/haicrypt.h create mode 100644 include/hcrypt_ctx.h create mode 100644 include/hcrypt_msg.h create mode 100644 include/srt_compat.h create mode 100644 include/win/inttypes.h create mode 100644 include/win/stdint.h create mode 100644 include/win/syslog_defs.h create mode 100644 include/win/unistd.h create mode 100644 include/win/winporting.h create mode 100644 include/win/wintime.h create mode 100755 scripts/check-deps create mode 100644 scripts/haiUtil.cmake create mode 100644 scripts/haisrt.pc.in create mode 100755 scripts/sfplay create mode 100644 srtcore/HEADERS.maf create mode 100644 srtcore/Makefile create mode 100644 srtcore/api.cpp create mode 100644 srtcore/api.h create mode 100644 srtcore/buffer.cpp create mode 100644 srtcore/buffer.h create mode 100644 srtcore/cache.cpp create mode 100644 srtcore/cache.h create mode 100644 srtcore/ccc.cpp create mode 100644 srtcore/ccc.h create mode 100644 srtcore/channel.cpp create mode 100644 srtcore/channel.h create mode 100644 srtcore/common.cpp create mode 100644 srtcore/common.h create mode 100644 srtcore/core.cpp create mode 100644 srtcore/core.h create mode 100644 srtcore/csrtcc.cpp create mode 100644 srtcore/csrtcc.h create mode 100644 srtcore/epoll.cpp create mode 100644 srtcore/epoll.h create mode 100644 srtcore/list.cpp create mode 100644 srtcore/list.h create mode 100644 srtcore/logging.h create mode 100644 srtcore/logging_api.h create mode 100644 srtcore/md5.cpp create mode 100644 srtcore/md5.h create mode 100644 srtcore/netinet_any.h create mode 100644 srtcore/packet.cpp create mode 100644 srtcore/packet.h create mode 100644 srtcore/queue.cpp create mode 100644 srtcore/queue.h create mode 100644 srtcore/srt.h create mode 100644 srtcore/srt4udt.h create mode 100644 srtcore/srt_c_api.cpp create mode 100644 srtcore/threadname.h create mode 100644 srtcore/udt.h create mode 100644 srtcore/utilities.h create mode 100644 srtcore/window.cpp create mode 100644 srtcore/window.h create mode 100644 srtcore/windows/win_time.cpp create mode 100644 submodules/pthread-win32/STUB diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..dd4b88586 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,417 @@ +# +# 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 +# +cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) + +if ( CYGWIN AND NOT CYGWIN_USE_POSIX ) + set(WIN32 1) + set(CMAKE_LEGACY_CYGWIN_WIN32 1) + add_definitions(-DWIN32=1 -DCYGWIN=1) + message("HAVE CYGWIN. Setting backward compat CMAKE_LEGACY_CYGWIN_WIN32 and -DWIN32") +endif() + + +set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/scripts") +include(haiUtil) +include(FindPkgConfig) + + +set (SRT_VERSION "1.2.0") + +set (NAME_haisrt haisrt) +set (NAME_haicrypt haicrypt) + +if ( DEFINED WITH_SRT_NAME ) + set (NAME_haisrt ${WITH_SRT_NAME}) +endif() + +if ( DEFINED WITH_HAICRYPT_NAME ) + set (NAME_haicrypt ${WITH_HAICRYPT_NAME}) +endif() + +set_if(DARWIN ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +set_if(LINUX ${CMAKE_SYSTEM_NAME} MATCHES "Linux") + +# Prepare openssl variables: +# WITH_OPENSSL: general +# WITH_OPENSSL_INCLUDEDIR: /include +# WITH_OPENSSL_LIBDIR: /lib +# WITH_OPENSSL_LIBRARIES: empty as default, in which case it uses -lcrypto -lpthread + +set (HAVE_OPENSSL 0) +if (DEFINED WITH_OPENSSL_INCLUDEDIR AND (DEFINED WITH_OPENSSL_LIBDIR OR DEFINED WITH_OPENSSL_LDFLAGS)) + set (HAVE_OPENSSL 1) +else() + + if(DEFINED WITH_OPENSSL) + set (WITH_OPENSSL_INCLUDEDIR ${WITH_OPENSSL}/include) + set (WITH_OPENSSL_LIBDIR ${WITH_OPENSSL}/lib) + set (HAVE_OPENSSL 1) + endif() +endif() + + +if ( WITH_COMPILER_PREFIX ) + + set(CMAKE_CXX_COMPILER ${WITH_COMPILER_PREFIX}g++) + set(CMAKE_C_COMPILER ${WITH_COMPILER_PREFIX}gcc) + +endif() + +if ( CMAKE_CROSSCOMPILING ) + set (HAVE_CROSSCOMPILER 1) +endif() + + +set (cid ${CMAKE_CXX_COMPILER_ID}) +set (gnu_compat_compilers Intel Clang GNU) + +# Perfect function, however it somehow doesn't work on Cygwin with cmake 3.6.0 :D +#list(FIND "${gnu_compat_compilers}" ${CMAKE_CXX_COMPILER_ID} HAVE_COMPILER_GNU_COMPAT) + +set(HAVE_COMPILER_GNU_COMPAT 0) +foreach (gnid Intel Clang GNU) + if (${CMAKE_CXX_COMPILER_ID} STREQUAL ${gnid}) + set(HAVE_COMPILER_GNU_COMPAT 1) + break() + endif() +endforeach() + +#message( "In ${gnu_compat_compilers} found ${CMAKE_CXX_COMPILER_ID} at position ${i}") +if (${HAVE_COMPILER_GNU_COMPAT}) + message("COMPILER: GNU compat: ${CMAKE_CXX_COMPILER}") +else() + message("COMPILER: NOT GNU compat: ${CMAKE_CXX_COMPILER}") +endif() + + +if (NOT HAVE_OPENSSL) + message(FATAL_ERROR "OpenSSL location not provided. Please define at least WITH_OPENSSL for prefix path.") +endif() + +# Openssl should be added as a dependency for haisrt +if (NOT DEFINED WITH_OPENSSL_LIBRARIES) + if (NOT DEFINED WITH_OPENSSL_LDFLAGS) + message(FATAL_ERROR "Need defined one of WITH_OPENSSL_LIBRARIES (filenames), WITH_OPENSSL_LDFLAGS (-l flags)") + endif() + separate_arguments(WITH_OPENSSL_LDFLAGS) + +else() + # If so, then add each file from this list with the directory specified in _LIBDIR to the list + separate_arguments(WITH_OPENSSL_LIBRARIES) + adddirname(${WITH_OPENSSL_LIBDIR} "${WITH_OPENSSL_LIBRARIES}" WITH_OPENSSL_LDFLAGS) + unset (WITH_OPENSSL_LIBDIR) +endif() + +if (NOT ${WITH_OPENSSL} STREQUAL "/usr") + include_directories(${WITH_OPENSSL_INCLUDEDIR}) +endif() + +# Pthread - optional +# The following things must be specified on particular platform: +# - the -lpthread (or -pthread?) flag on Linux (as link-libraries) +# - the path to pthread API library on Windows (as link-libraries) + +if (DEFINED WITH_PTHREAD_INCLUDEDIR) + include_directories(${WITH_PTHREAD_INCLUDEDIR}) +endif() + + +if ( DISABLE_CXX11 ) + set (ENABLE_CXX11 0) +elseif( DEFINED ENABLE_CXX11 ) +else() + set (ENABLE_CXX11 1) +endif() + +if ( ENABLE_CXX11 EQUAL 0 ) + message("NOTE: Parts that require C++11 support will be disabled (stransmit)") +endif() + + +# Define Haisrt version +set_version_variables(SRT_VERSION ${SRT_VERSION}) +message( "VERSION: major=${SRT_VERSION_MAJOR} minor=${SRT_VERSION_MINOR} patch=${SRT_VERSION_PATCH}" ) + +set (PACKAGE haisrt) +set (PACKAGE_BUGREPORT to-be-set) + +if (ENABLE_DEBUG) + set (CMAKE_BUILD_TYPE "Debug") +endif() + +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + set (SRT_DEBUG_ENABLED 1) + set (SRT_DEBUG_OPT "-ggdb -O0 -D_DEBUG") + message("Using DEBUG mode") +else() + set (SRT_DEBUG_ENABLED 0) + set (SRT_DEBUG_OPT "-O2") + message("Using RELEASE mode") +endif() + +# Enuff. + +if ( ENABLE_PROFILE OR "$ENV{HAI_BUILD_PROFILE}" STREQUAL "1" ) + message("+++ Build will include PROFILE INFORMATION") + set (SRT_BUILD_PROFILE 1) +else() + set (SRT_BUILD_PROFILE 0) +endif() + +if (HAVE_COMPILER_GNU_COMPAT) + set (SRT_GCC_WARN "-Wall -Wextra") +else() + # cpp debugging on Windows :D + #set (SRT_GCC_WARN "/showIncludes") +endif() + +# Logging is always enabled when debug build, and can be +# additionally enabled by setting SRT_LOGGING_ENABLED env to 1. +if ( "$ENV{SRT_LOGGING_ENABLED}" STREQUAL "1" ) + set(ENABLE_LOGGING 1) +endif() + +if ( ENABLE_LOGGING OR SRT_DEBUG_ENABLED ) + list(APPEND SRT_EXTRA_FLAGS "-DENABLE_LOGGING=1") +endif() + +if ( ENABLE_DYNAMIC ) + set (srt_libspec SHARED) + set (srt_install LIBRARY) +else() + set (srt_libspec STATIC) + set (srt_install ARCHIVE) +endif() + + +set (SRT_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/include) + +set (SRT_SRC_HAICRYPT_DIR ${CMAKE_SOURCE_DIR}/haicrypt) +set (SRT_SRC_SRTCORE_DIR ${CMAKE_SOURCE_DIR}/srtcore) +set (SRT_SRC_COMMON_DIR ${CMAKE_SOURCE_DIR}/common) +set (SRT_SRC_TOOLS_DIR ${CMAKE_SOURCE_DIR}/tools) + +# This means: set LIBSRTCORE_SRCFILES [pfind *.c *.cpp $SRT_SRC_SRTCORE_DIR] +aux_source_directory(${SRT_SRC_SRTCORE_DIR} LIBSRTCORE_SRCFILES) +if(WIN32) + message( "WIN32 detected - including win_time.cpp" ) + + # Add Windows specific source files here. + # These should provide various POSIX/Linux features + # to Windows. + set(winspec_SOURCES ${SRT_SRC_SRTCORE_DIR}/windows/win_time.cpp) + + if (NOT DEFINED WITH_PTHREAD_LDFLAGS OR NOT DEFINED WITH_PTHREAD_INCLUDEDIR) + message(FATAL_ERROR "On Windows, PTHREAD library is 3rd party. WITH_PTHREAD_* variables must be specified.") + endif() +elseif(DARWIN) + message( "DARWIN detected") + add_definitions(-DOSX=1) +elseif(LINUX) + add_definitions(-DLINUX=1) + message( "LINUX detected" ) +else() + message(FATAL_ERROR "Unsupported system") +endif() +aux_source_directory(${SRT_SRC_HAICRYPT_DIR} LIBHAICRYPT_SRCFILES) + + +set (SOURCES_haisrtbase + ${winspec_SOURCES} # Adds ONLY ON WINDOWS its gettimeofday (used in haicrypt and srtcore) + ${SOURCES_haicrypt_dep} +) + +set (SOURCES_haisrt + ${LIBSRTCORE_SRCFILES} + ${SRT_SRC_COMMON_DIR}/srt_compat.c +) + +set (SOURCES_haicrypt + ${LIBHAICRYPT_SRCFILES} +) + +include_directories( + ${SRT_SRC_SRTCORE_DIR} + ${SRT_SRC_HAICRYPT_DIR} + ${SRT_INCLUDE_DIR} +) + +add_definitions( + -D_GNU_SOURCE + -DHAI_PATCH=1 + -DHAI_ENABLE_SRT=1 + -DHAICRYPT_USE_OPENSSL_EVP=1 + -DHAICRYPT_USE_OPENSSL_AES + -DSRT_VERSION="${SRT_VERSION}" +) + +if ( ${SRT_BUILD_PROFILE} ) + # They are actually cflags, not definitions, but CMake is stupid enough. + add_definitions(-g -pg) +endif() + + + +set (HEADERS_haisrt + ${SRT_SRC_SRTCORE_DIR}/srt.h + ${SRT_SRC_SRTCORE_DIR}/udt.h + ${SRT_SRC_SRTCORE_DIR}/srt4udt.h + ${SRT_SRC_SRTCORE_DIR}/logging_api.h + +) + +set (HEADERS_haicrypt + ${SRT_INCLUDE_DIR}/haicrypt.h + ${SRT_INCLUDE_DIR}/hcrypt_ctx.h + ${SRT_INCLUDE_DIR}/hcrypt_msg.h +) + +set (ALL_LIBS "") + +#message("LIBRARY SPECIFICATION: ${srt_libspec}") + +# HaiCrypt: cryptography wrapper, used by HaiSRT. + +#message("HaiCrypt: SRC: ${SOURCES_haicrypt} ${SOURCES_haisrtbase}") + +add_library(${NAME_haicrypt} ${srt_libspec} ${SOURCES_haicrypt} ${SOURCES_haisrtbase}) +if (DEFINED WITH_OPENSSL_LIBDIR) + set_target_properties(${NAME_haicrypt} PROPERTIES LINK_DIRECTORIES ${WITH_OPENSSL_LIBDIR}) +endif() +if (DEFINED WITH_OPENSSL_INCLUDEDIR) + target_include_directories(${NAME_haicrypt} PRIVATE ${WITH_OPENSSL_INCLUDEDIR}) +endif() +set (LDFLAGS_haicrypt_STATIC ${WITH_OPENSSL_LDFLAGS}) +target_link_libraries(${NAME_haicrypt} ${LDFLAGS_haicrypt_STATIC}) +get_target_property(LDFLAGS_haicrypt_RESULT ${NAME_haicrypt} LINK_LIBRARIES) +#message("HaiCrypt: LDFLAGS: ${LDFLAGS_haicrypt_RESULT}") + +install(TARGETS ${NAME_haicrypt} ${srt_install} DESTINATION lib) +install(FILES ${HEADERS_haicrypt} DESTINATION include/haicrypt) +list(APPEND ALL_LIBS ${NAME_haicrypt}) + +# HaiSRT: SRT library (UDT API only) +#message("HaiSRT: SRC: ${SOURCES_haisrt}") +add_library(${NAME_haisrt} ${srt_libspec} ${SOURCES_haisrt}) +set (LDFLAGS_haisrt ${WITH_PTHREAD_LDFLAGS}) + +# Not sure why it's required, but somehow only on Linux +if ( LINUX ) + list(APPEND LDFLAGS_haisrt -lrt) +endif() +list(APPEND LDFLAGS_srt_STATIC ${LDFLAGS_haisrt} ${LDFLAGS_haicrypt_STATIC}) +target_link_libraries(${NAME_haisrt} ${NAME_haicrypt} ${LDFLAGS_srt_STATIC}) + +get_target_property(LDFLAGS_srt_RESULT ${NAME_haisrt} LINK_LIBRARIES) +#message("HaiSRT: LDFLAGS: ${LDFLAGS_srt_RESULT}") + +install(TARGETS ${NAME_haisrt} ${srt_install} DESTINATION lib) +install(FILES ${HEADERS_haisrt} DESTINATION include/srt) +list(APPEND ALL_LIBS ${NAME_haisrt}) + +if ( ${SRT_BUILD_PROFILE} ) + target_link_libraries(${NAME_haisrt} -g -pg) +endif() + +if ( WIN32 ) + target_compile_definitions(${NAME_haisrt} PRIVATE -DUDT_EXPORTS) + link_libraries(Ws2_32.lib) + add_definitions(-DPTW32_STATIC_LIB) +endif() + + +set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SRT_DEBUG_OPT} ${SRT_EXTRA_FLAGS} ${SRT_GCC_WARN}") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SRT_DEBUG_OPT} ${SRT_EXTRA_FLAGS} ${SRT_GCC_WARN}") + + +# PC file generation. +if (NOT DEFINED INSTALLDIR) + set (INSTALLDIR ${CMAKE_INSTALL_PREFIX}) + get_filename_component(INSTALLDIR ${INSTALLDIR} ABSOLUTE) +endif() + +# SRT_VERSION is there +if ( ${srt_libspec} STREQUAL "SHARED" ) + if ( LINUX ) + set (IFNEEDED_SRT_LDFLAGS -pthread) + endif() + set (LIBS_srtapps ${NAME_haisrt} ${NAME_haicrypt}) +else() + # Well, cmake has a basic functionality problem. + # Even string(REPLACE) cannot do this - it replaces it with empty string!!! + #string(REPLACE ";" " " IFNEEDED_SRT_LDFLAGS ${IFNEEDED_SRT_LDFLAGS}) + set (IFNEEDED_SRT_LDFLAGS "") + # If there's separate_arguments, maybe there's also join_arguments? + foreach (OPT ${LDFLAGS_srt_STATIC}) + set (IFNEEDED_SRT_LDFLAGS "${IFNEEDED_SRT_LDFLAGS} ${OPT}") + endforeach() + set (LIBS_srtapps ${NAME_haisrt} ${NAME_haicrypt} ${LDFLAGS_srt_STATIC} ${LDFLAGS_haicrypt_STATIC}) +endif() + +# XXX +# These two flags are required if compiling a C application +# The problem is that pkg-config cannot return flags that are +# suitable for C or C++ only - just "cflags", and for C by default. +# This may cause trouble when you want to compile your app with static libstdc++; +# if your build requires it, you'd probably remove -lstdc++ from the list +# obtained by `pkg-config --libs`. +# +# Some sensible solution for that is desired. +if ( LINUX ) + set (IFNEEDED_SRT_LDFLAGS "${IFNEEDED_SRT_LDFLAGS} -lstdc++ -lm") +endif() + +configure_file(scripts/haisrt.pc.in haisrt.pc @ONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/haisrt.pc DESTINATION lib/pkgconfig) + +# Applications + +if ( HAVE_COMPILER_GNU_COMPAT ) + message("C++ VERSION: Setting C++11 compat flag for gnu compiler") + set (CFLAGS_CXX_STANDARD "-std=c++11") +else() + message("C++ VERSION: leaving default, not a GNU compiler, assuming C++11 or newer is default.") + set (CFLAGS_CXX_STANDARD "") +endif() + +if ( ENABLE_CXX11 ) + + add_executable(stransmit ${CMAKE_SOURCE_DIR}/apps/stransmit.cpp ${CMAKE_SOURCE_DIR}/apps/uriparser.cpp) + + # Test programs + add_executable(utility-test ${CMAKE_SOURCE_DIR}/apps/utility-test.cpp) + + # 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}") + target_link_libraries(stransmit ${LIBS_srtapps}) + install(TARGETS stransmit RUNTIME DESTINATION bin) + install(PROGRAMS scripts/sfplay DESTINATION bin) + +endif() + +if ( ENABLE_SUFLIP ) + set (SOURCES_suflip + ${CMAKE_SOURCE_DIR}/apps/suflip.cpp ${CMAKE_SOURCE_DIR}/apps/uriparser.cpp + ) + + set(LIBS_suflip ${NAME_haicrypt} ${NAME_haisrt}) + + add_executable(suflip ${SOURCES_suflip}) + target_link_libraries(suflip ${LIBS_suflip}) + install(TARGETS suflip RUNTIME DESTINATION bin) +endif () diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..e4862777d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +## License + +By contributing code to the [SRT project](https://github.com/Haivision/srt/), you agree to license your contribution under the [LGPLv2.1 License](COPYING). + +## Issues + +Open a GitHub issue for anything you find or any questions you have. + +## Comments + +Comment on any GitHub issue, open or closed. The only guidelines here are to be friendly and welcoming. If you see that a question has been asked and you think you know the answer, don't wait! + +## Pull Requests + +Submit a pull request at any time, whether an issue has been created or not. It may be helpful to discuss your goals in an issue first, though many things can best be shown with code. + +## Code Style + +Please follow existing style. + +## Attribution + +This contributing guide is adapted from [VVV's guide](https://github.com/Varying-Vagrant-Vagrants/VVV/blob/develop/.github/CONTRIBUTING.md). + +## Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +* (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +* (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +* (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +* (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. \ No newline at end of file diff --git a/COPYING b/COPYING new file mode 100644 index 000000000..4362b4915 --- /dev/null +++ b/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/README.md b/README.md new file mode 100644 index 000000000..7c4c0e75b --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +REQUIREMENTS: +============ + +* cmake (as build system) +* OpenSSL +* Pthreads (for POSIX systems it's builtin, for Windows there's a library) + +For Linux: +========== + +Install cmake and openssl-devel (or similar name) package. For pthreads +there should be -lpthreads linker flag added. + +## Ubunut 14 +``` +sudo apt-get update +sudo apt-get upgrade +sudo apt-get install tclsh pkg-config cmake libssl-dev build-essential +./configure +make +``` +## CentOS 7 +``` +sudo yum update +sudo yum install tcl pkgconfig openssl-devel cmake gcc gcc-c++ make automake +./configure +make +``` + +For Mac (Darwin, iOS): +===================== + +Install cmake and openssl with development files from "brew". Note that the +system version of OpenSSL is inappropriate, although you should be able to +use any newer version compiled from sources, if you prefer. + +For Windows: +============ + +1. Install cmake for Windows. The CMake GUI will help you configure the project. +Note that some variables must be provided explicitly. These are the default +recommended values (required until some solution for running the `configure` +script in Windows can be found): + + WITH_OPENSSL_INCLUDEDIR=C:/OpenSSL-Win64/include + WITH_OPENSSL_LIBDIR=C:/OpenSSL-win64/lib/VC/static + WITH_OPENSSL_LIBRARIES=libeay32MT.lib ssleay32MT.lib + WITH_PTHREAD_INCLUDEDIR=C:/pthread-win32/include + WITH_PTHREAD_LDFLAGS=C:/pthread-win32/lib/pthread_lib.lib + + +2. Please download and install OpenSSL for Windows. + +The 64-bit devel package can be downloaded from here: + + http://slproweb.com/download/Win64OpenSSL-1_0_2a.exe + +It's expected to be installed in `C:\OpenSSL-Win64` (see the above variables). + + +3. Compile and install Pthreads for Windows from this submodule: + + submodules/pthread-win32 + +Please follow the steps: + +a. Using Visual Studio 2013, please open this file: + + pthread_lib.2013.vcxproj + +b. Make sure to select configuration: `Release` and `x64`. + +c. Make sure that the `pthread_lib` project will be built. + +d. After building, find the `pthread_lib.lib` file (directory is probably: `bin\x64_MSVC2013.Release`). +Copy this file to `C:\pthread-win32\lib` (or whatever other location you configured in variables). + +e. Copy include files to `C:\pthread-win32\include` - the following ones: + + pthread.h + sched.h + semaphore.h + +(They are in the toplevel directory, there are actually no meaningful subdirs here) +(NOTE: the win32 is part of the project name. It will become 32 or 64 depending on selection) + diff --git a/apps/UDT b/apps/UDT new file mode 120000 index 000000000..f33ded826 --- /dev/null +++ b/apps/UDT @@ -0,0 +1 @@ +../udt4/app \ No newline at end of file diff --git a/apps/appcommon.hpp b/apps/appcommon.hpp new file mode 100644 index 000000000..94945f5c4 --- /dev/null +++ b/apps/appcommon.hpp @@ -0,0 +1,124 @@ +/* + * 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 + */ + + +#if WIN32 + +// Keep this below commented out. +// This is for a case when you need cpp debugging on Windows. +//#ifdef _WINSOCKAPI_ +//#error "You include somewhere, remove it. It causes conflicts" +//#endif + +#include +#include +#include +// WIN32 API does not have sleep() and usleep(), Although MINGW does. +#ifdef __MINGW32__ +#include +#else +extern "C" inline int sleep(int seconds) { Sleep(seconds * 1000); return 0; } +#endif +#else +#include +#include +#include +#include +#include +#endif + +#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 +// Even if it did support inet_pton(), it is only available on Windows Vista +// and later. Since we need to support WindowsXP and later in ORTHRUS. Many +// customers still use it, we will need to implement using something like +// WSAStringToAddress() which is available on Windows95 and later. +// Support for IPv6 was added on WindowsXP SP1. +// Header: winsock2.h +// Implementation: ws2_32.dll +// See: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms742214(v=vs.85).aspx +// http://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedInternet3b.html +#ifdef __MINGW32__ +static inline int inet_pton(int af, const char * src, void * dst) +{ + struct sockaddr_storage ss; + int ssSize = sizeof(ss); + char srcCopy[INET6_ADDRSTRLEN + 1]; + + ZeroMemory(&ss, sizeof(ss)); + + // work around stupid non-const API + strncpy(srcCopy, src, INET6_ADDRSTRLEN + 1); + srcCopy[INET6_ADDRSTRLEN] = '\0'; + + if (WSAStringToAddress( + srcCopy, af, NULL, (struct sockaddr *)&ss, &ssSize) != 0) + { + return 0; + } + + switch (af) + { + case AF_INET : + { + *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr; + return 1; + } + case AF_INET6 : + { + *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr; + return 1; + } + default : + { + // No-Op + } + } + + return 0; +} +#endif // __MINGW__ + +inline sockaddr_in CreateAddrInet(const std::string& name, unsigned short port) +{ + sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + + if ( name != "" ) + { + if ( inet_pton(AF_INET, name.c_str(), &sa.sin_addr) == 1 ) + return sa; + + // XXX RACY!!! Use getaddrinfo() instead. Check portability. + // Windows/Linux declare it. + // See: + // http://www.winsocketdotnetworkprogramming.com/winsock2programming/winsock2advancedInternet3b.html + hostent* he = gethostbyname(name.c_str()); + if ( !he || he->h_addrtype != AF_INET ) + throw std::invalid_argument("SrtSource: host not found: " + name); + + sa.sin_addr = *(in_addr*)he->h_addr_list[0]; + } + + return sa; +} diff --git a/apps/stransmit.cpp b/apps/stransmit.cpp new file mode 100644 index 000000000..4f523193f --- /dev/null +++ b/apps/stransmit.cpp @@ -0,0 +1,1624 @@ +/* + * 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 + */ + +// NOTE: This application uses C++11. + +// This program uses quite a simple architecture, which is mainly related to +// the way how it's invoked: stransmit (plus options). +// +// The media for and are filled by abstract classes +// named Source and Target respectively. Most important virtuals to +// be filled by the derived classes are Source::Read and Target::Write. +// +// For SRT please take a look at the SrtCommon class first. This contains +// everything that is needed for creating an SRT medium, that is, making +// a connection as listener, as caller, and as rendezvous. The listener +// and caller modes are built upon the same philosophy as those for +// BSD/POSIX socket API (bind/listen/accept or connect). +// +// The instance class is selected per details in the URI (usually scheme) +// and then this URI is used to configure the medium object. Medium-specific +// options are specified in the URI: SCHEME://HOST:PORT?opt1=val1&opt2=val2 etc. +// +// Options for connection are set by ConfigurePre and ConfigurePost. +// This is a philosophy that exists also in BSD/POSIX sockets, just not +// officially mentioned: +// - The "PRE" options must be set prior to connecting and can't be altered +// on a connected socket, however if set on a listening socket, they are +// derived by accept-ed socket. +// - The "POST" options can be altered any time on a connected socket. +// They MAY have also some meaning when set prior to connecting; such +// option is SRTO_RCVSYN, which makes connect/accept call asynchronous. +// Because of that this option is treated special way in this app. +// +// See 'srt_options' global variable for a list of all options. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "appcommon.hpp" // CreateAddrInet +#include "uriparser.hpp" // UriParser + +// NOTE: This is without "haisrt/" because it uses an internal path +// to the library. Application using the "installed" library should +// use +#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 } +}; + + + +void PrintSrtStats(int sid, const CPerfMon& 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; + +string Option(string deflt="") { return deflt; } + +template +string Option(string deflt, string key, Args... further_keys) +{ + map::iterator i = g_options.find(key); + if ( i == g_options.end() ) + return Option(deflt, 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; + +void OnINT_SetIntState(int) +{ + cerr << "\n-------- REQUESTED INTERRUPT!\n"; + int_state = true; + if ( throw_on_interrupt ) + throw std::runtime_error("Requested exception interrupt"); +} + +void OnAlarm_Interrupt(int) +{ + throw std::runtime_error("Watchdog bites hangup"); +} + +struct BandwidthGuard +{ + typedef std::chrono::steady_clock::time_point time_point; + size_t conf_bw; + time_point start_time, prev_time; + size_t report_count = 0; + double average_bw = 0; + size_t transfer_size = 0; + + BandwidthGuard(size_t band): conf_bw(band), start_time(std::chrono::steady_clock::now()), prev_time(start_time) {} + + void Checkpoint(size_t size, size_t toreport ) + { + using namespace std::chrono; + + time_point eop = steady_clock::now(); + auto dur = duration_cast(eop - start_time); + //auto this_dur = duration_cast(eop - prev_time); + + transfer_size += size; + average_bw = transfer_size*1000000.0/dur.count(); + //double this_bw = size*1000000.0/this_dur.count(); + + if ( toreport ) + { + // Show current bandwidth + ++report_count; + if ( report_count % toreport == toreport - 1 ) + { + cout.precision(10); + int abw = int(average_bw); + int abw_trunc = abw/1024; + int abw_frac = abw%1024; + char bufbw[64]; + sprintf(bufbw, "%d.%03d", abw_trunc, abw_frac); + cout << "+++/+++SRT TRANSFER: " << transfer_size << "B " + "DURATION: " << duration_cast(dur).count() << "ms SPEED: " << bufbw << "kB/s\n"; + } + } + + prev_time = eop; + + if ( transfer_size > SIZE_MAX/2 ) + { + transfer_size -= SIZE_MAX/2; + start_time = eop; + } + + if ( conf_bw == 0 ) + return; // don't guard anything + + // Calculate expected duration for the given size of bytes (in [ms]) + double expdur_ms = double(transfer_size)/conf_bw*1000; + + auto expdur = milliseconds(size_t(expdur_ms)); + // Now compare which is more + + if ( dur >= expdur ) // too slow, but there's nothing we can do. Exit now. + return; + + std::this_thread::sleep_for(expdur-dur); + } +}; + +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 ) +{ + vector args; + copy(argv+1, argv+argc, back_inserter(args)); + + // Check options + vector params; + + for (string a: args) + { + if ( a[0] == '-' ) + { + string key = a.substr(1); + size_t pos = key.find(':'); + if ( pos == string::npos ) + pos = key.find(' '); + string value = pos == string::npos ? "" : key.substr(pos+1); + key = key.substr(0, pos); + g_options[key] = value; + continue; + } + + params.push_back(a); + } + + if ( params.size() != 2 ) + { + cerr << "Usage: " << argv[0] << " [options] \n"; + cerr << "\t-t: - connection timeout\n"; + cerr << "\t-c: - max size of data read in one step\n"; + cerr << "\t-b: - set SRT bandwidth\n"; + cerr << "\t-r: - bandwidth report frequency\n"; + cerr << "\t-s: - frequency of status report\n"; + cerr << "\t-k - crash on error (aka developer mode)\n"; + cerr << "\t-v - verbose mode (prints also size of every data packet passed)\n"; + return 1; + } + + int timeout = stoi(Option("30", "t", "to", "timeout"), 0, 0); + size_t chunk = stoul(Option("0", "c", "chunk"), 0, 0); + 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_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); + + bool internal_log = Option("no", "loginternal") != "no"; + + std::ofstream logfile_stream; // leave unused if not set + + srt_setloglevel(ParseLogLevel(loglevel)); + set fas = ParseLogFA(logfa); + for (set::iterator i = fas.begin(); i != fas.end(); ++i) + srt_addlogfa(*i); + + char NAME[] = "SRTLIB"; + if ( internal_log ) + { + srt_setlogflags( 0 + | SRT_LOGF_DISABLE_TIME + | SRT_LOGF_DISABLE_SEVERITY + | SRT_LOGF_DISABLE_THREADNAME + | SRT_LOGF_DISABLE_EOL + ); + srt_setloghandler(NAME, TestLogHandler); + } + else if ( logfile != "" ) + { + logfile_stream.open(logfile.c_str()); + if ( !logfile_stream ) + { + cerr << "ERROR: Can't open '" << logfile << "' for writing - fallback to cerr\n"; + } + else + { + UDT::setlogstream(logfile_stream); + } + } + +#ifdef WIN32 +#define alarm(argument) (void)0 +#else + signal(SIGALRM, OnAlarm_Interrupt); +#endif + signal(SIGINT, OnINT_SetIntState); + signal(SIGTERM, OnINT_SetIntState); + + try + { + auto src = Source::Create(params[0]); + auto tar = Target::Create(params[1]); + + // Now loop until broken + BandwidthGuard bw(bandwidth); + + if ( transmit_verbose ) + { + cout << "STARTING TRANSMISSION: '" << params[0] << "' --> '" << params[1] << "'\n"; + } + + extern logging::Logger glog; + for (;;) + { + if ( timeout != -1 ) + { + alarm(timeout); + } + const bytevector& data = src->Read(chunk); + if ( transmit_verbose ) + cout << " << " << data.size() << " -> "; + if ( data.empty() && src->End() ) + { + if ( transmit_verbose ) + cout << "EOS\n"; + break; + } + tar->Write(data); + if ( timeout != -1 ) + { + alarm(0); + } + if ( tar->Broken() ) + { + if ( transmit_verbose ) + cout << " OUTPUT broken\n"; + break; + } + if ( transmit_verbose ) + cout << " sent\n"; + if ( int_state ) + { + cerr << "\n (interrupted on request)\n"; + break; + } + + bw.Checkpoint(chunk, bw_report); + } + alarm(0); + + } catch (...) { + if ( crashonx ) + throw; + + return 1; + } + + return 0; +} + +// Class utilities + +set true_names = { "1", "yes", "on", "true" }; +set false_names = { "0", "no", "off", "false" }; + +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 = 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); } + +struct OptionValue +{ + string s; + union { + int i; + int64_t l; + bool b; + }; + + const void* value = nullptr; + size_t size = 0; +}; + +struct SocketOption +{ + enum Type { STRING = 0, INT, INT64, BOOL }; + enum Binding { PRE = 0, POST }; + enum Domain { SYSTEM, SRT }; + + string name; + int protocol; + int symbol; + Type type; + Binding binding; + + template + bool apply(int socket, string value) const; + + template + bool applyt(int socket, string value) const; + + template + static int setso(int socket, int protocol, int symbol, const void* data, size_t size); + + template + static void extract(string value, OptionValue& val); +}; + +template<> +int SocketOption::setso(int socket, int /*ignored*/, int sym, const void* data, size_t size) +{ + return srt_setsockopt(socket, 0, SRT_SOCKOPT(sym), data, size); +} + +template<> +int SocketOption::setso(int socket, int proto, int sym, const void* data, size_t size) +{ + return ::setsockopt(socket, proto, sym, (const char *)data, size); +} + +template<> +inline void SocketOption::extract(string value, OptionValue& o) +{ + o.s = value; + o.value = o.s.data(); + o.size = o.s.size(); +} + +template<> +inline void SocketOption::extract(string value, OptionValue& o) +{ + try + { + o.i = stoi(value, 0, 0); + o.value = &o.i; + o.size = sizeof o.i; + return; + } + catch (...) // stoi throws + { + return; // do not change o + } +} + +template<> +inline void SocketOption::extract(string value, OptionValue& o) +{ + try + { + long long vall = stoll(value); + o.l = vall; // int64_t resolves to either 'long long', or 'long' being 64-bit integer + o.value = &o.l; + o.size = sizeof o.l; + return ; + } + catch (...) // stoll throws + { + return ; + } +} + +template<> +inline void SocketOption::extract(string value, OptionValue& o) +{ + bool val; + if ( false_names.count(value) ) + val = false; + else if ( true_names.count(value) ) + val = true; + else + return; + + o.b = val; + o.value = &o.b; + o.size = sizeof o.b; +} + +template +inline bool SocketOption::applyt(int socket, string value) const +{ + OptionValue o; // common meet point + extract(value, o); + int result = setso(socket, protocol, symbol, o.value, o.size); + return result != -1; +} + + +template +inline bool SocketOption::apply(int socket, string value) const +{ + switch ( type ) + { + case STRING: return applyt(socket, value); break; + case INT: return applyt(socket, value); break; + case INT64: return applyt(socket, value); break; + case BOOL: return applyt(socket, value); break; + } + return false; +} + +SocketOption srt_options [] { + { "maxbw", 0, SRTO_MAXBW, SocketOption::INT, SocketOption::PRE }, + { "pbkeylen", 0, SRTO_PBKEYLEN, SocketOption::INT, SocketOption::PRE }, + { "passphrase", 0, SRTO_PASSPHRASE, SocketOption::STRING, SocketOption::PRE }, + + { "mss", 0, SRTO_MSS, SocketOption::INT, SocketOption::PRE }, + { "fc", 0, SRTO_FC, SocketOption::INT, SocketOption::PRE }, + { "sndbuf", 0, SRTO_SNDBUF, SocketOption::INT, SocketOption::PRE }, + { "rcvbuf", 0, SRTO_RCVBUF, SocketOption::INT, SocketOption::PRE }, + { "ipttl", 0, SRTO_IPTTL, SocketOption::INT, SocketOption::PRE }, + { "iptos", 0, SRTO_IPTOS, SocketOption::INT, SocketOption::PRE }, + { "inputbw", 0, SRTO_INPUTBW, SocketOption::INT64, SocketOption::POST }, + { "oheadbw", 0, SRTO_OHEADBW, SocketOption::INT, SocketOption::POST }, + { "tsbpddelay", 0, SRTO_TSBPDDELAY, SocketOption::INT, SocketOption::PRE }, + { "tsbpdmaxlag", 0, SRTO_TSBPDMAXLAG, SocketOption::INT, SocketOption::PRE }, + { "nakreport", 0, SRTO_NAKREPORT, SocketOption::BOOL, SocketOption::PRE }, + { "conntimeo", 0, SRTO_CONNTIMEO, SocketOption::INT, SocketOption::PRE } +}; + +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); + } + + 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 yes = 1; + if ( m_tsbpdmode ) + { + result = srt_setsockopt(sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes); + 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; + } + + if ( m_options.count("passphrase") ) + { + if ( transmit_verbose ) + cout << "NOTE: using passphrase and 16-bit key\n"; + + // Insert default + if ( m_options.count("pbkeylen") == 0 ) + { + m_options["pbkeylen"] = m_output_direction ? "16" : "0"; + } + } + + // Let's pretend async mode is set this way. + // This is for asynchronous connect. + yes = m_blocking_mode; + result = srt_setsockopt(sock, 0, SRTO_RCVSYN, &yes, sizeof yes); + 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; + } + + for (auto o: srt_options) + { + if ( o.binding == SocketOption::PRE && 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 << "' (pre, " << (m_output_direction? "target":"source") << ") to " << value << endl; + else + cout << "NOTE: SRT/pre::" << o.name << "=" << value << 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 ) + { + 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 ) + Error(UDT::getlasterror(), "srt_bind"); + if ( transmit_verbose ) + { + cout << " listen... "; + cout.flush(); + } + stat = srt_listen(m_bindsock, 1); + if ( stat == SRT_ERROR ) + 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 ) + 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 ) + 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 ) + 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, true); + 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); + } + + ++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 = 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, 0); + 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; + 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) +{ + char prefix[100] = ""; + if ( opaque ) + strncpy(prefix, (char*)opaque, 99); + time_t now; + time(&now); + char buf[1024]; + struct tm local; + localtime_r(&now, &local); + size_t pos = strftime(buf, 1024, "[%c ", &local); + snprintf(buf+pos, 1024-pos, "%s:%d(%s)]{%d} %s", file, line, area, level, message); + + cerr << buf << endl; +} + diff --git a/apps/suflip.cpp b/apps/suflip.cpp new file mode 100644 index 000000000..6db1af3fd --- /dev/null +++ b/apps/suflip.cpp @@ -0,0 +1,689 @@ +/* + * 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 + */ + +// This is a simplified version of stransmit, which does not use C++11, +// however its functionality is limited to SRT to UDT only. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "appcommon.hpp" +#include "uriparser.hpp" + + +using namespace std; + +typedef std::vector bytevector; + +string true_names_i [] = { "1", "yes", "on", "true" }; +string false_names_i [] = { "0", "no", "off", "false" }; + +set true_names, false_names; + +struct InitializeMe +{ + InitializeMe() + { + copy(true_names_i, true_names_i+4, inserter(true_names, true_names.begin())); + copy(false_names_i, false_names_i+4, inserter(false_names, false_names.begin())); + } + +} g_moron; + +bool verbose = false; +volatile bool throw_on_interrupt = false; + + +class UdpCommon +{ +protected: + int m_sock; + sockaddr_in sadr; + string adapter; + map m_options; + + UdpCommon(): + m_sock(-1) + { + } + + void Setup(string host, int port, map attr) + { + m_sock = socket(AF_INET, SOCK_DGRAM, 0); + 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 + int res = setsockopt(m_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq)); + if ( res == SOCKET_ERROR || res == -1 ) + { + throw runtime_error("adding to multicast membership failed"); + } +#else + int res = setsockopt(m_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if ( res == -1 ) + { + throw runtime_error("adding to multicast membership failed"); + } +#endif + 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 ( 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 + } +}; + +struct Target +{ + +}; + +class UdpTarget: public Target, public UdpCommon +{ +public: + UdpTarget(string host, int port, const map& attr ) + { + Setup(host, port, attr); + } + + void Write(const bytevector& data) + { + int stat = sendto(m_sock, data.data(), data.size(), 0, (sockaddr*)&sadr, sizeof sadr); + if ( stat == -1 ) + { + perror("write"); + throw runtime_error("Error during write"); + } + } + + bool IsOpen() { return m_sock != -1; } + bool Broken() { return false; } +}; + + + + +class SrtCommon +{ +protected: + + bool m_output_direction; + bool m_blocking_mode; + int m_timeout; + map m_options; // All other options, as provided in the URI + UDTSOCKET m_sock; + UDTSOCKET m_bindsock; + 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; } + + SrtCommon(): + m_output_direction(false), + m_blocking_mode(true), + m_timeout(0), + m_sock(UDT::INVALID_SOCK), + m_bindsock(UDT::INVALID_SOCK) + { + } + + void Init(string host, int port, map par, bool dir_output) + { + m_output_direction = dir_output; + + // Application-specific options: mode, blocking, timeout, adapter + + 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") ) + { + if ( false_names.count(par.at("blocking")) ) + { + m_blocking_mode = false; + } + else + { + m_blocking_mode = true; + } + } + + par.erase("blocking"); + + if ( par.count("timeout") ) + { + m_timeout = atoi(par.at("timeout").c_str()); + par.erase("timeout"); + } + + string adapter = ""; // needed for rendezvous only + if ( par.count("adapter") ) + { + adapter = par.at("adapter"); + par.erase("adapter"); + } + + // Assign the others here. + m_options = par; + + if ( 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'"); + } + } + + virtual int ConfigurePost(UDTSOCKET sock) + { + bool yes = m_blocking_mode; + int result = 0; + if ( m_output_direction ) + { + result = UDT::setsockopt(sock, 0, UDT_SNDSYN, &yes, sizeof yes); + if ( result == -1 ) + return result; + + if ( m_timeout ) + return UDT::setsockopt(sock, 0, UDT_SNDTIMEO, &m_timeout, sizeof m_timeout); + } + else + { + result = UDT::setsockopt(sock, 0, UDT_RCVSYN, &yes, sizeof yes); + if ( result == -1 ) + return result; + + if ( m_timeout ) + return UDT::setsockopt(sock, 0, UDT_RCVTIMEO, &m_timeout, sizeof m_timeout); + } + + /* + 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 ( 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(UDTSOCKET sock) + { + int result = 0; + + int yes = 1; + result = UDT::setsockopt(sock, 0, SRT_TSBPDMODE, &yes, sizeof yes); + if ( result == -1 ) + return result; + + if ( m_options.count("passphrase") ) + { + if ( verbose ) + cout << "NOTE: using passphrase and 16-bit key\n"; + + // Insert default + if ( m_options.count("pbkeylen") == 0 ) + { + m_options["pbkeylen"] = m_output_direction ? "16" : "0"; + } + } + + // Let's pretend async mode is set this way. + // This is for asynchronous connect. + yes = m_blocking_mode; + 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); + + if ( verbose ) + { + cout << "PRE: blocking mode set: " << yes << " timeout " << m_timeout << endl; + } + + return 0; + } + + void OpenClient(string host, int port) + { + m_sock = UDT::socket(AF_INET, SOCK_DGRAM, 0); + if ( m_sock == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::socket"); + + int stat = ConfigurePre(m_sock); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "ConfigurePre"); + sockaddr_in sa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&sa; + if ( verbose ) + { + cout << "Connecting to " << host << ":" << port << " ... "; + cout.flush(); + } + stat = UDT::connect(m_sock, psa, sizeof sa); + if ( stat == UDT::ERROR ) + { + Error(UDT::getlasterror(), "UDT::connect"); + } + if ( verbose ) + cout << " connected.\n"; + stat = ConfigurePost(m_sock); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "ConfigurePost"); + } + + void Error(UDT::ERRORINFO& udtError, string src) + { + int udtResult = udtError.getErrorCode(); + if ( 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 = UDT::socket(AF_INET, SOCK_DGRAM, 0); + if ( m_bindsock == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::socket"); + + int stat = ConfigurePre(m_bindsock); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "ConfigurePre"); + + sockaddr_in sa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&sa; + if ( verbose ) + { + cout << "Binding a server on " << host << ":" << port << " ..."; + cout.flush(); + } + stat = UDT::bind(m_bindsock, psa, sizeof sa); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::bind"); + if ( verbose ) + { + cout << " listen... "; + cout.flush(); + } + stat = UDT::listen(m_bindsock, 1); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::listen"); + + sockaddr_in scl; + int sclen = sizeof scl; + if ( verbose ) + { + cout << " accept... "; + cout.flush(); + } + ::throw_on_interrupt = true; + m_sock = UDT::accept(m_bindsock, (sockaddr*)&scl, &sclen); + if ( m_sock == UDT::INVALID_SOCK ) + Error(UDT::getlasterror(), "UDT::accept"); + if ( 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 == UDT::ERROR ) + Error(UDT::getlasterror(), "ConfigurePost"); + } + + void OpenRendezvous(string adapter, string host, int port) + { + m_sock = UDT::socket(AF_INET, SOCK_DGRAM, 0); + if ( m_sock == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::socket"); + + bool yes = true; + UDT::setsockopt(m_sock, 0, UDT_RENDEZVOUS, &yes, sizeof yes); + + int stat = ConfigurePre(m_sock); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "ConfigurePre"); + + sockaddr_in localsa = CreateAddrInet(adapter, port); + sockaddr* plsa = (sockaddr*)&localsa; + if ( verbose ) + { + cout << "Binding a server on " << adapter << ":" << port << " ..."; + cout.flush(); + } + stat = UDT::bind(m_sock, plsa, sizeof localsa); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::bind"); + + sockaddr_in sa = CreateAddrInet(host, port); + sockaddr* psa = (sockaddr*)&sa; + if ( verbose ) + { + cout << "Connecting to " << host << ":" << port << " ... "; + cout.flush(); + } + stat = UDT::connect(m_sock, psa, sizeof sa); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "UDT::connect"); + if ( verbose ) + cout << " connected.\n"; + + stat = ConfigurePost(m_sock); + if ( stat == UDT::ERROR ) + Error(UDT::getlasterror(), "ConfigurePost"); + } + + ~SrtCommon() + { + if ( verbose ) + cout << "SrtCommon: DESTROYING CONNECTION, closing sockets\n"; + if ( m_sock != UDT::INVALID_SOCK ) + UDT::close(m_sock); + + if ( m_bindsock != UDT::INVALID_SOCK ) + UDT::close(m_bindsock); + } +}; + +// Just to seal up +struct Source +{ + +}; + +class SrtSource: public Source, public SrtCommon +{ + int srt_epoll; +public: + + SrtSource(string host, int port, const map& par) + { + Init(host, port, par, false); + + if ( !m_blocking_mode ) + { + srt_epoll = srt_epoll_create(); + if ( srt_epoll == SRT_ERROR ) + throw std::runtime_error("Can't create epoll in nonblocking mode"); + + int modes = SRT_EPOLL_IN; + srt_epoll_add_usock(srt_epoll, m_sock, &modes); + } + } + + bytevector Read(size_t chunk) + { + bytevector data(chunk); + bool ready = true; + int stat; + do + { + ::throw_on_interrupt = true; + stat = UDT::recvmsg(m_sock, data.data(), chunk); + ::throw_on_interrupt = false; + if ( stat == UDT::ERROR ) + { + 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)); + usleep(10000); + ready = false; + } + } + while (!ready); + + chunk = size_t(stat); + if ( chunk < data.size() ) + data.resize(chunk); + + return data; + } + + virtual int ConfigurePre(UDTSOCKET sock) + { + 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; + + + return 0; + } + + bool IsOpen() { return IsUsable(); } + bool End() { return IsBroken(); } +}; + +volatile bool int_state = false; + +void OnINT_SetIntState(int) +{ + cerr << "\n-------- REQUESTED INTERRUPT!\n"; + int_state = true; + if ( throw_on_interrupt ) + throw std::runtime_error("Requested exception interrupt"); +} + +void OnAlarm_Interrupt(int) +{ + throw std::runtime_error("Watchdog bites hangup"); +} + + +map g_options; + +int main( int argc, char** argv ) +{ + vector args; + copy(argv+1, argv+argc, back_inserter(args)); + + // Check options + vector params; + + for (vector::iterator ai = args.begin(); ai != args.end(); ++ai) + { + string& a = *ai; + + if ( a[0] == '-' ) + { + string key = a.substr(1); + size_t pos = key.find(':'); + if ( pos == string::npos ) + pos = key.find(' '); + string value = pos == string::npos ? "" : key.substr(pos+1); + key = key.substr(0, pos); + g_options[key] = value; + continue; + } + + params.push_back(a); + } + + if ( params.size() != 2 ) + { + cerr << "Usage: " << argv[0] << " [options] \n"; + return 1; + } + + signal(SIGINT, OnINT_SetIntState); + signal(SIGTERM, OnINT_SetIntState); + + UriParser su = params[0]; + UriParser tu = params[1]; + + if ( su.scheme() != "srt" || tu.scheme() != "udp" ) + { + cerr << "Source must be srt://... and target must be udp://...\n"; + return 1; + } + + if ( su.portno() < 1024 || tu.portno() < 1024 ) + { + cerr << "Port number must be > 1024\n"; + return 1; + } + + if ( g_options.count("v") ) + verbose = 1; + + bool crashonx = false; + + const size_t chunk = 1316; + + try + { + SrtSource src (su.host(), su.portno(), su.parameters()); + UdpTarget tar (tu.host(), tu.portno(), tu.parameters()); + + // Now loop until broken + for (;;) + { + const bytevector& data = src.Read(chunk); + if ( verbose ) + cout << " << " << data.size() << " -> "; + if ( data.empty() && src.End() ) + { + if ( verbose ) + cout << endl; + break; + } + tar.Write(data); + if ( tar.Broken() ) + { + if ( verbose ) + cout << " broken\n"; + break; + } + if ( verbose ) + cout << " sent\n"; + if ( int_state ) + { + cerr << "\n (interrupted on request)\n"; + break; + } + + } + + } catch (...) { + if ( crashonx ) + throw; + + return 1; + } + + return 0; +} + diff --git a/apps/testcapi.c b/apps/testcapi.c new file mode 100644 index 000000000..c27913189 --- /dev/null +++ b/apps/testcapi.c @@ -0,0 +1,72 @@ +/* + * 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 + */ + +#include +#include +#include + +#include + +int main( int argc, char** argv ) +{ + int ss, st; + struct sockaddr_in sa; + int yes = 1; + const char message [] = "This message should be sent to the other side"; + + srt_startup(); + + ss = srt_socket(AF_INET, SOCK_DGRAM, 0); + if ( ss == SRT_ERROR ) + { + fprintf(stderr, "srt_socket: %s\n", srt_getlasterror_str()); + return 1; + } + + sa.sin_port = htons(atoi(argv[2])); + if ( inet_pton(AF_INET, argv[1], &sa.sin_addr) != 1) + { + return 1; + } + + srt_setsockflag(ss, SRTO_SENDER, &yes, sizeof yes); + + st = srt_connect(ss, (struct sockaddr*)&sa, sizeof sa); + if ( st == SRT_ERROR ) + { + fprintf(stderr, "srt_connect: %s\n", srt_getlasterror_str()); + return 1; + } + + st = srt_sendmsg2(ss, message, sizeof message, NULL); + if ( st == SRT_ERROR ) + { + fprintf(stderr, "srt_sendmsg: %s\n", srt_getlasterror_str()); + return 1; + } + + st = srt_close(ss); + if ( st == SRT_ERROR ) + { + fprintf(stderr, "srt_close: %s\n", srt_getlasterror_str()); + return 1; + } + + srt_cleanup(); + return 0; +} diff --git a/apps/uriparser-test.cpp b/apps/uriparser-test.cpp new file mode 120000 index 000000000..f233f2349 --- /dev/null +++ b/apps/uriparser-test.cpp @@ -0,0 +1 @@ +uriparser.cpp \ No newline at end of file diff --git a/apps/uriparser.cpp b/apps/uriparser.cpp new file mode 100644 index 000000000..22aeaa86e --- /dev/null +++ b/apps/uriparser.cpp @@ -0,0 +1,277 @@ +/* + * 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 + */ + + +// STL includes +#include +#include + +#include "uriparser.hpp" + +#ifdef TEST +#define TEST1 1 +#endif + +#ifdef TEST1 +#include +#endif + + +using namespace std; + + +map types; + +struct UriParserInit +{ + UriParserInit() + { + types["file"] = UriParser::FILE; + types["srt"] = UriParser::SRT; + types["udp"] = UriParser::UDP; + types[""] = UriParser::UNKNOWN; + } +} 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) +{ +} + +string UriParser::proto(void) +{ + return m_proto; +} + +UriParser::Type UriParser::type() +{ + return m_uriType; +} + +string UriParser::host(void) +{ + return m_host; +} + +string UriParser::port(void) +{ + return m_port; +} + +unsigned short int UriParser::portno(void) +{ + // This returns port in numeric version. Fallback to 0. + try + { + int i = atoi(m_port.c_str()); + if ( i <= 0 || i > 65535 ) + return 0; + return i; + } + catch (...) + { + return 0; + } +} + +string UriParser::path(void) +{ + return m_path; +} + +string UriParser::queryValue(const string& strKey) +{ + return m_mapQuery[strKey]; +} + +void UriParser::Parse(const string& strUrl, DefaultExpect exp) +{ + int iQueryStart = -1; + + size_t idx = strUrl.find("?"); + if (idx != string::npos) + { + m_host = strUrl.substr(0, idx); + iQueryStart = idx + 1; + } + else + { + m_host = strUrl; + } + + idx = m_host.find("://"); + if (idx != string::npos) + { + m_proto = m_host.substr(0, idx); + m_host = m_host.substr(idx + 3, m_host.size() - (idx + 3)); + } + + idx = m_host.find("/"); + if (idx != string::npos) + { + m_path = m_host.substr(idx, m_host.size() - idx); + m_host = m_host.substr(0, idx); + } + + + // Check special things in the HOST entry. + size_t atp = m_host.find('@'); + if ( atp != string::npos ) + { + string realhost = m_host.substr(atp+1); + string prehost; + if ( atp > 0 ) + { + prehost = m_host.substr(0, atp-0); + size_t colon = prehost.find(':'); + if ( colon != string::npos ) + { + string pw = prehost.substr(colon+1); + string user; + if ( colon > 0 ) + user = prehost.substr(0, colon-0); + m_mapQuery["user"] = user; + m_mapQuery["password"] = pw; + } + else + { + m_mapQuery["user"] = prehost; + } + } + else + { + m_mapQuery["multicast"] = "1"; + } + m_host = realhost; + } + + idx = m_host.find(":"); + if (idx != string::npos) + { + m_port = m_host.substr(idx + 1, m_host.size() - (idx + 1)); + m_host = m_host.substr(0, idx); + } + + if ( m_port == "" && m_host != "" ) + { + // Check if the host-but-no-port has specified + // a single integer number. If so + // We need to use C86 strtol, cannot use C++11 + const char* beg = m_host.c_str(); + const char* end = m_host.c_str() + m_host.size(); + char* eos = 0; + long val = strtol(beg, &eos, 10); + if ( val > 0 && eos == end ) + { + m_port = m_host; + m_host = ""; + } + } + + string strQueryPair; + while (iQueryStart > -1) + { + idx = strUrl.find("&", iQueryStart); + if (idx != string::npos) + { + strQueryPair = strUrl.substr(iQueryStart, idx - iQueryStart); + iQueryStart = idx + 1; + } + else + { + strQueryPair = strUrl.substr(iQueryStart, strUrl.size() - iQueryStart); + iQueryStart = idx; + } + + idx = strQueryPair.find("="); + if (idx != string::npos) + { + m_mapQuery[strQueryPair.substr(0, idx)] = strQueryPair.substr(idx + 1, strQueryPair.size() - (idx + 1)); + } + } + + if ( m_proto == "file" ) + { + if ( m_path.size() > 3 && m_path.substr(0, 3) == "/./" ) + m_path = m_path.substr(3); + } + + // Post-parse fixes + // Treat empty protocol as a file. In this case, merge the host and path. + if ( exp == EXPECT_FILE && m_proto == "" && m_port == "" ) + { + m_proto = "file"; + m_path = m_host + m_path; + m_host = ""; + } + + m_uriType = types[m_proto]; // default-constructed UNKNOWN will be used if not found (although also inserted) +} + +#ifdef TEST + +using namespace std; + +int main( int argc, char** argv ) +{ + UriParser parser (argv[1]); + + cout << "PARSING URL: " << argv[1] << endl; + + cout << "PROTOCOL: " << parser.proto() << endl; + cout << "HOST: " << parser.host() << endl; + cout << "PORT: " << parser.portno() << endl; + cout << "PATH: " << parser.path() << endl; + cout << "PARAMETERS:\n"; + for (auto p: parser.parameters()) + cout << "\t" << p.first << " = " << p.second << endl; + + + return 0; +} +#endif diff --git a/apps/uriparser.hpp b/apps/uriparser.hpp new file mode 100644 index 000000000..80206bd3b --- /dev/null +++ b/apps/uriparser.hpp @@ -0,0 +1,84 @@ +/* + * 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 + */ + +#ifndef INC__URL_PARSER_H +#define INC__URL_PARSER_H + +#include +#include +#include + + +//++ +// UriParser +//-- + +class UriParser +{ +// Construction +public: + + enum DefaultExpect { EXPECT_FILE, EXPECT_HOST }; + + UriParser(const std::string& strUrl, DefaultExpect exp = EXPECT_FILE); + 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 scheme() { return proto(); } + std::string host(void); + std::string port(void); + unsigned short int portno(); + std::string hostport() { return host() + ":" + port(); } + std::string path(void); + std::string queryValue(const std::string& strKey); + const std::map& parameters() { return m_mapQuery; } + +private: + void Parse(const std::string& strUrl, DefaultExpect); + +// Overridables +public: + +// Overrides +public: + +// Data +private: + std::string m_origUri; + std::string m_proto; + std::string m_host; + std::string m_port; + std::string m_path; + Type m_uriType; + + std::map m_mapQuery; +}; + +//#define TEST1 1 + +#endif // _FMS_URL_PARSER_H_ diff --git a/apps/utility-test.cpp b/apps/utility-test.cpp new file mode 100644 index 000000000..295c753ce --- /dev/null +++ b/apps/utility-test.cpp @@ -0,0 +1,47 @@ +/* + * 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 + */ + +#include +#include +#include + +#include +#include +#include +#include + +int main( int argc __attribute__((unused)), char** argv __attribute__((unused))) +{ + 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; + + cout << "inorder: " << hex << MSGNO_PACKET_INORDER::mask << " (1 << " << dec << MSGNO_PACKET_INORDER::offset << ")" << endl; + cout << "msgno-seq mask: " << hex << MSGNO_SEQ::mask << endl; + cout << "3 wrapped into enckeyspec: " << hex << setw(8) << setfill('0') << MSGNO_ENCKEYSPEC::wrap(3) << " - mask: " << MSGNO_ENCKEYSPEC::mask << endl; + + cout << "SrtVersion test: 2.3.8 == 0x020308 -- SrtVersion(2, 3, 8) == 0x" << hex << setw(8) << setfill('0') << SrtVersion(2, 3, 8) << endl; + + cout << "SEQNO_CONTROL::mask: " << hex << SEQNO_CONTROL::mask << " SEQNO 0x80050000 has control = " << SEQNO_CONTROL::unwrap(0x80050000) + << " type = " << SEQNO_MSGTYPE::unwrap(0x80050000) << endl; + return 0; +} diff --git a/common/srt_compat.c b/common/srt_compat.c new file mode 100644 index 000000000..3aae4db76 --- /dev/null +++ b/common/srt_compat.c @@ -0,0 +1,187 @@ +/* + * 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 + */ + +// Implementation file for srt_compat.h +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +// Prevents from misconfiguration through preprocessor. + +#include + +#include +#include +#if !defined(_WIN32) \ + && !defined(__MACH__) +#include +#endif + +#if defined(__MACH__) + +#include +#include +#include +#include +#include +#include +#include +#include + + +int OSX_clock_gettime(clockid_t clock_id, struct timespec * ts) +{ + int result = -1; + + if (ts == NULL) + { + errno = EFAULT; + return -1; + } + + switch (clock_id) + { + case CLOCK_REALTIME : + { + struct timeval tv; + + result = gettimeofday(&tv, NULL); + if (result == 0) + { + ts->tv_sec = tv.tv_sec; + ts->tv_nsec = tv.tv_usec * 1000; + } + } + break; + + case CLOCK_MONOTONIC : + { + mach_timebase_info_data_t timebase_info; + memset(&timebase_info, 0, sizeof(timebase_info)); + static const uint64_t BILLION = UINT64_C(1000000000); + + (void)mach_timebase_info(&timebase_info); + if (timebase_info.numer <= 0 + || timebase_info.denom <= 0) + { + result = -1; + errno = EINVAL; + } + else + { + uint64_t monotonic_nanoseconds = mach_absolute_time() + * timebase_info.numer / timebase_info.denom; + + ts->tv_sec = monotonic_nanoseconds / BILLION; + ts->tv_nsec = monotonic_nanoseconds % BILLION; + if (ts->tv_sec < 0 + || ts->tv_nsec < 0) + { + result = -1; + errno = EINVAL; + } + else + { + result = 0; + } + } + } + break; + + #if 0 + // Warning This is probably slow!! + case CLOCK_CALENDAR : + { + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + ts->tv_sec = mts.tv_sec; + ts->tv_nsec = mts.tv_nsec; + + result = 0; + } + break; + #endif + + default : + { + result = -1; + errno = EINVAL; + } + } + + return result; +} + +#endif // (__MACH__) + + +extern const char * SysStrError(int errnum, char * buf, size_t buflen) +{ + if (buf == NULL || buflen <= 0) + { + errno = EFAULT; + return buf; + } + + buf[0] = '\0'; + +#if defined(_WIN32) || defined(WIN32) + LPVOID lpMsgBuf; + FormatMessage(0 + | FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errnum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf, 0, NULL); + char * result = strncpy(buf, (const char *)lpMsgBuf, buflen); + LocalFree(lpMsgBuf); + return result; +#elif (!defined(__GNU_LIBRARY__) && !defined(__GLIBC__) ) \ + || (( (_POSIX_C_SOURCE >= 200112L) || (_XOPEN_SOURCE >= 600)) && ! _GNU_SOURCE ) + // POSIX/XSI-compliant version. + // Overall general POSIX version: returns status. + // 0 for success, otherwise it's errno_value or -1 and errno_value is in '::errno'. + if (strerror_r(errnum, buf, buflen) != 0) + { + buf[0] = '\0'; + } + return buf; +#else + // GLIBC is non-standard under these conditions. + // GNU version: returns the pointer to the message. + // This is either equal to the local buffer (errmsg) + // or some system-wide storage, depending on kernel's caprice. + char * tBuffer = strerror_r(errnum, buf, buflen); + if (tBuffer != NULL + && tBuffer != buf) + { + return strncpy(buf, tBuffer, buflen); + } + else + { + return buf; + } +#endif +} diff --git a/configure b/configure new file mode 100755 index 000000000..fe1c2f5b2 --- /dev/null +++ b/configure @@ -0,0 +1,207 @@ +#!/usr/bin/tclsh + +# +# 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 +# + +# This is a general-purpose configure script, which is a user-friendly +# wrapper to call the cmake. Note that --help shows you only the options +# that are explicitly defined, but which exactly options are really +# supported, depends on what is defined in CMakeLists.txt. Just every +# case of --long-option is changed into LONG_OPTION cmake variable. +# However some of options MENTIONED IN THE HELP TEXT may be handled +# by the configure script before passing to cmake (and maybe turned +# into something else). +# +# The build-specific option processing is done in configure-data.tcl file. +# +# The idea is that CMakeLists.txt contains things that are highly +# customizable, but no system or option autodetection AWA "sensible +# defaults" are provided. This is done by this script. + + +set here [file dirname $argv0] + +set options "" +set toolchain_changers "" + +source $here/configure-data.tcl + +# Update alias with default alias +dict set alias --prefix --cmake-install-prefix= + +proc resolve opt { + set type arg + set pos [string first $opt =] + if { $pos == -1 } { + set type bool + set mark "" + } else { + set type arg + set mark [string range $opt $pos+1 end] + set opt [string range $opt 0 $pos-1] + } + set var [string toupper [string map {- _ + x} $opt]] + return [list --$opt $var $type $mark] +} + +foreach {o desc} $options { + lassign [resolve $o] optname optvar opttype optmark + set opt($optname) [list $optvar $opttype $optmark] + set info($optname) $desc +} + + +if { $argv == "--help" } { + puts stderr "Usage: ./configure \[options\]" + puts stderr "OPTIONS:" + foreach o [lsort [array names opt]] { + lassign $opt($o) unu type mark + set imark "" + if { $mark != "" } { + set imark "=$mark" + } + puts stderr "\t$o$imark - $info($o)" + } + + exit 1 +} + +if { [info proc init] != "" } { + init +} + +#parray opt + +set saveopt "" +set optkeys "" + +set dryrun 0 +set type "" + +foreach a $argv { + if { [info exists val] } { unset val } + + if { $saveopt != "" } { + set optval($saveopt) $a + set saveopt "" + continue + } + + if { [string range $a 0 1] != "--" } { + error "Unexpected argument '$a'. Options must start with --" + } + + if { $a == "--dryrun" } { + set dryrun 1 + continue + } + + set type "" + + if { [string first = $a] != -1 } { + lassign [split $a =] a val + } + + if { [dict exists $::alias $a] } { + set aname [dict get $::alias $a] + if { [string first = $aname] != -1 } { + lassign [split $aname =] a aval + set type arg + } + } + + if { ![info exists opt($a)] } { + #puts stderr "WARNING: Unknown option: $a" + # But still, simply turn the option to assign-based use. + lassign [resolve [string range $a 2 end]] oname var + if { ![info exists val] && $type == "" } { + set type bool + } + } else { + lassign $opt($a) var type + } + + if { $type == "bool" } { + if { ![info exists val] } { + set val 1 + } + set optval($a) $val + } elseif { [info exists val] } { + set optval($a) $val + } else { + set saveopt $a + } + + lappend optkeys $a +} + +if { $saveopt != "" } { + error "Extra unhandled argument: $saveopt" +} + +set cmakeopt "" + +if { [info proc preprocess] != "" } { + preprocess +} + +# Check if there were new values added not added to optkeys +foreach a [array names optval] { + if { $a ni $optkeys } { + lappend optkeys $a + } +} + + +foreach a $optkeys { + + if { ![info exists optval($a)] } { + continue ;# user action might have removed it. + } + + if { ![info exists opt($a)] } { + #puts stderr "WARNING: Unknown option: $a" + # But still, simply turn the option to assign-based use. + lassign [resolve [string range $a 2 end]] oname var + if { ![info exists val] && $type == "" } { + set type bool + } + } else { + lassign $opt($a) var type + } + + set val $optval($a) + lappend cmakeopt "-D$var=$val" +} + + +if { [info proc postprocess] != "" } { + postprocess +} + +#puts "VARSPEC: $cmakeopt" + +set cmd [list cmake $here {*}$cmakeopt] +puts "Running: $cmd" +if { !$dryrun} { + if { [catch {exec 2>@stderr >@stdout {*}$cmd} result] } { + puts "CONFIGURE: cmake reported error: $result" + } +} else { + puts "(not really - dry run)" +} diff --git a/configure-data.tcl b/configure-data.tcl new file mode 100644 index 000000000..12ee33f04 --- /dev/null +++ b/configure-data.tcl @@ -0,0 +1,267 @@ +# +# 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 +# + +# API description: + +# Expected variables: +# - options: dictionary "option-name" : "description" +# if there's '=' in option name, it expects an argument. Otherwise it's boolean. +# - alias: optional, you can make shortcuts to longer named options. Remember to use = in target name. +# +# Optional procedures: +# - preprocess: run before command-line arguments ($argv) are reviewed +# - postprocess: run after options are reviewed and all data filled in +# +# Available variables in postprocess: +# +# - optval (array): contains all option names with their assigned values +# - cmakeopt (scalar): a list of all options for "cmake" command line + + +set options { + enable-dynamic "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" + with-compiler-prefix= "set C/C++ toolchains gcc and g++" + with-openssl= "Prefix for OpenSSL installation (adds include,lib)" + with-openssl-includedir= "Use given path for OpenSSL header files" + with-openssl-libdir= "Use given path for OpenSSL library path" + with-openssl-libraries= "Use given file list instead of standard -lcrypto" + with-openssl-ldflags= "Use given -lDIR values for OpenSSL or absolute library filename" + with-pthread-includedir= "Use extra path for pthreads (usually for Windows)" + with-pthread-ldflags= "Use specific flags for pthreads (some platforms require -pthread)" +} + +# Just example. Available in the system. +set alias { + --prefix --cmake-install-prefix= +} + +proc pkg-config args { + return [string trim [exec pkg-config {*}$args]] +} + +proc flagval v { + set out "" + foreach o $v { + lappend out [string trim [string range $o 2 en]] + } + return $out +} + +proc preprocess {} { + + # Prepare windows basic path info + set ::CYGWIN 0 + set e [catch {exec uname -o} res] + # We have Cygwin, if uname -o returns "cygwin" and does not fail. + if { !$e && $res == "Cygwin" } { + set ::CYGWIN 1 + puts "CYGWIN DETECTED" + } + + set ::HAVE_LINUX [expr {$::tcl_platform(os) == "Linux"}] + set ::HAVE_DARWIN [expr {$::tcl_platform(os) == "Darwin"}] + + set ::CYGWIN_USE_POSIX 0 + if { "--cygwin-use-posix" in $::optkeys } { + set ::CYGWIN_USE_POSIX 1 + } + + set ::HAVE_WINDOWS 0 + if { $::tcl_platform(platform) == "windows" } { + puts "WINDOWS PLATFORM detected" + set ::HAVE_WINDOWS 1 + } + + if { $::CYGWIN && !$::CYGWIN_USE_POSIX } { + puts "CYGWIN - MINGW enforced" + # Make Cygwin tools see it right, to compile for MinGW + + if { "--with-compiler-prefix" ni $::optkeys } { + set ::optval(--with-compiler-prefix) /bin/x86_64-w64-mingw32- + } + + # Extract drive C: information + set drive_path [exec mount -p | tail -1 | cut {-d } -f 1] + set ::DRIVE_C $drive_path/c + set ::HAVE_WINDOWS 1 + } else { + + # Don't check for Windows, non-Windows parts will not use it. + set ::DRIVE_C C: + } + +} + +proc GetCompilerCommand {} { + # Expect that the compiler was set through: + # --with-compiler-prefix + # --cmake-c[++]-compiler + # (cmake-toolchain-file will set things up without the need to check things here) + + if { [info exists ::optval(--with-compiler-prefix)] } { + set prefix $::optval(--with-compiler-prefix) + return ${prefix}gcc + } + + if { [info exists ::optval(--cmake-c-compiler)] } { + return $::optval(--cmake-c-compiler) + } + + if { [info exists ::optval(--cmake-c++-compiler)] } { + return $::optval(--cmake-c++-compiler) + } + + if { [info exists ::optval(--cmake-cxx-compiler)] } { + return $::optval(--cmake-cxx-compiler) + } + + puts "NOTE: Cannot obtain compiler, assuming toolchain file will do what's necessary" + + return "" +} + +proc postprocess {} { + + set iscross 0 + + # Check if there was any option that changed the toolchain. If so, don't apply any autodetection-based toolchain change. + set all_options [array names ::optval] + set toolchain_changed no + foreach changer { + --with-compiler-prefix + --cmake-c-compiler + --cmake-c++-compiler + --cmake-cxx-compiler + --cmake-toolchain-file + } { + if { $changer in $all_options } { + puts "NOTE: toolchain changed by '$changer' option" + set toolchain_changed yes + break + } + } + + if { $toolchain_changed } { + # Check characteristics of the compiler - in particular, whether the target is different + # than the current target. + set cmd [GetCompilerCommand] + if { $cmd != "" } { + set gcc_version [exec $cmd -v 2>@1] + set target "" + foreach l [split $gcc_version \n] { + if { [string match Target:* $l] } { + set name [lindex $l 1] ;# [0]Target: [1]x86_64-some-things-further + set target [lindex [split $name -] 0] ;# [0]x86_64 [1]redhat [2]linux + break + } + } + + if { $target == "" } { + puts "NOTE: can't obtain target from gcc -v: $l" + } else { + if { $target != $::tcl_platform(machine) } { + puts "NOTE: foreign target type detected ($target) - setting CROSSCOMPILING flag" + lappend ::cmakeopt "-DHAVE_CROSSCOMPILER=1" + set iscross 1 + } + } + } + } + + # Check if --with-openssl and the others are defined. + + set have_openssl 0 + if { [lsearch -glob $::optkeys --with-openssl*] != -1 } { + set have_openssl 1 + } + + set have_pthread 0 + if { [lsearch -glob $::optkeys --with-pthread*] != -1 } { + set have_pthread 1 + } + + # Autodetect OpenSSL and pthreads + if { $::HAVE_WINDOWS } { + + if { !$have_openssl } { + lappend ::cmakeopt "-DWITH_OPENSSL_INCLUDEDIR=$::DRIVE_C/OpenSSL-Win64/include" + lappend ::cmakeopt "-DWITH_OPENSSL_LIBDIR=$::DRIVE_C/OpenSSL-Win64/lib/VC/static" + lappend ::cmakeopt "-DWITH_OPENSSL_LIBRARIES=libeay32MT.lib ssleay32MT.lib" + puts "Adding OpenSSL in $::DRIVE_C/OpenSSL-win64" + } else { + puts "HAVE_OPENSSL: [lsearch -inline $::optkeys --with-openssl*]" + } + + + if { !$have_pthread } { + lappend ::cmakeopt "-DWITH_PTHREAD_INCLUDEDIR=$::DRIVE_C/pthread-win32/include" + lappend ::cmakeopt "-DWITH_PTHREAD_LDFLAGS=$::DRIVE_C/pthread-win32/lib/pthread_lib.lib" + puts "Adding pthreads in $::DRIVE_C/pthread-win32" + } else { + puts "HAVE_PTHREADS: [lsearch -inline $::optkeys --with-pthread*]" + } + } + + if { $::HAVE_LINUX } { + # Extract Openssl from pkg-config + if { !$have_openssl } { + set openssl_libs [pkg-config --libs-only-l --libs-only-other openssl] + set openssl_libdir [pkg-config --libs-only-L openssl] + set openssl_inc [pkg-config --cflags-only-I openssl] + set openssl_prefix [pkg-config --variable=prefix openssl] + + if { $openssl_inc == "" } { + # This should be so. And if so, add only these two flags: + lappend ::cmakeopt "-DWITH_OPENSSL=$openssl_prefix" + } else { + lappend ::cmakeopt "-DWITH_OPENSSL_INCLUDEDIR=[flagval $openssl_inc]" + } + + if { $openssl_libdir != "" } { + lappend ::cmakeopt "-DWITH_OPENSSL_LIBDIR=[flagval $openssl_libdir]" + } + + lappend ::cmakeopt "-DWITH_OPENSSL_LDFLAGS=$openssl_libs" + } + + if { !$have_pthread } { + lappend ::cmakeopt "-DWITH_PTHREAD_LDFLAGS=-lpthread" + } + } + + if { $::HAVE_DARWIN } { + # ON Darwin there's a problem with linking against the Mac-provided OpenSSL. + # This must use brew-provided OpenSSL. + # + if { !$have_openssl } { + + set er [catch {exec brew info openssl} res] + if { $er } { + error "You must have OpenSSL installed from 'brew' tool. The standard Mac version is inappropriate." + } + + lappend ::cmakeopt "-DWITH_OPENSSL_INCLUDEDIR=/usr/local/opt/openssl/include" + lappend ::cmakeopt "-DWITH_OPENSSL_LDFLAGS=/usr/local/opt/openssl/lib/libcrypto.a" + } + } + +} + diff --git a/haicrypt/HEADERS.maf b/haicrypt/HEADERS.maf new file mode 100644 index 000000000..10b2c4e3d --- /dev/null +++ b/haicrypt/HEADERS.maf @@ -0,0 +1,8 @@ +# 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. +PUBLIC HEADERS +haicrypt.h +hcrypt_ctx.h +hcrypt_msg.h diff --git a/haicrypt/hc_openssl_aes.c b/haicrypt/hc_openssl_aes.c new file mode 100644 index 000000000..220b20ca0 --- /dev/null +++ b/haicrypt/hc_openssl_aes.c @@ -0,0 +1,479 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include "hcrypt.h" + +#ifdef HAICRYPT_USE_OPENSSL_AES +#include +#include /* PKCS5_xxx() */ +#include /* AES_xxx() */ +#include +#include + +//#include "hc_openssl_aes.h" + +typedef struct tag_hcOpenSSL_AES_data { + AES_KEY aes_key[2]; /* even/odd SEK */ + +#define HCRYPT_OPENSSL_OUTMSGMAX 6 + uint8_t * outbuf; /* output circle buffer */ + size_t outbuf_ofs; /* write offset in circle buffer */ + size_t outbuf_siz; /* circle buffer size */ +} hcOpenSSL_AES_data; + + +#include +#if (OPENSSL_VERSION_NUMBER < 0x0090808fL) //0.9.8h +/* +* AES_wrap_key()/AES_unwrap_key() introduced in openssl 0.9.8h +* Here is an implementation using AES native API for earlier versions +*/ +#include + +static const unsigned char default_iv[] = { + 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, +}; + +int AES_wrap_key(AES_KEY *key, const unsigned char *iv, + unsigned char *out, + const unsigned char *in, unsigned int inlen) + { + unsigned char *A, B[16], *R; + unsigned int i, j, t; + if ((inlen & 0x7) || (inlen < 8)) + return -1; + A = B; + t = 1; + memcpy(out + 8, in, inlen); + if (!iv) + iv = default_iv; + + memcpy(A, iv, 8); + + for (j = 0; j < 6; j++) + { + R = out + 8; + for (i = 0; i < inlen; i += 8, t++, R += 8) + { + memcpy(B + 8, R, 8); + AES_encrypt(B, B, key); + A[7] ^= (unsigned char)(t & 0xff); + if (t > 0xff) + { + A[6] ^= (unsigned char)((t >> 8) & 0xff); + A[5] ^= (unsigned char)((t >> 16) & 0xff); + A[4] ^= (unsigned char)((t >> 24) & 0xff); + } + memcpy(R, B + 8, 8); + } + } + memcpy(out, A, 8); + return inlen + 8; + } + +int AES_unwrap_key(AES_KEY *key, const unsigned char *iv, + unsigned char *out, + const unsigned char *in, unsigned int inlen) + { + unsigned char *A, B[16], *R; + unsigned int i, j, t; + inlen -= 8; + if (inlen & 0x7) + return -1; + if (inlen < 8) + return -1; + A = B; + t = 6 * (inlen >> 3); + memcpy(A, in, 8); + memcpy(out, in + 8, inlen); + for (j = 0; j < 6; j++) + { + R = out + inlen - 8; + for (i = 0; i < inlen; i += 8, t--, R -= 8) + { + A[7] ^= (unsigned char)(t & 0xff); + if (t > 0xff) + { + A[6] ^= (unsigned char)((t >> 8) & 0xff); + A[5] ^= (unsigned char)((t >> 16) & 0xff); + A[4] ^= (unsigned char)((t >> 24) & 0xff); + } + memcpy(B + 8, R, 8); + AES_decrypt(B, B, key); + memcpy(R, B + 8, 8); + } + } + if (!iv) + iv = default_iv; + if (memcmp(A, iv, 8)) + { + OPENSSL_cleanse(out, inlen); + return 0; + } + return inlen; + } + +#endif /* OPENSSL_VERSION_NUMBER */ + +static unsigned char *hcOpenSSL_AES_GetOutbuf(hcOpenSSL_AES_data *aes_data, size_t pfx_len, size_t out_len) +{ + unsigned char *out_buf; + + if ((pfx_len + out_len) > (aes_data->outbuf_siz - aes_data->outbuf_ofs)) { + /* Not enough room left, circle buffers */ + aes_data->outbuf_ofs = 0; + } + out_buf = &aes_data->outbuf[aes_data->outbuf_ofs]; + aes_data->outbuf_ofs += (pfx_len + out_len); + return(out_buf); +} + +static hcrypt_CipherData *hcOpenSSL_AES_Open(size_t max_len) +{ + hcOpenSSL_AES_data *aes_data; + unsigned char *membuf; + size_t memsiz, padded_len = hcryptMsg_PaddedLen(max_len, 128/8); + + HCRYPT_LOG(LOG_DEBUG, "%s", "Using OpenSSL AES\n"); + + memsiz = sizeof(*aes_data) + (HCRYPT_OPENSSL_OUTMSGMAX * padded_len); + aes_data = malloc(memsiz); + if (NULL == aes_data) { + HCRYPT_LOG(LOG_ERR, "malloc(%zd) failed\n", memsiz); + return(NULL); + } + membuf = (unsigned char *)aes_data; + membuf += sizeof(*aes_data); + + aes_data->outbuf = membuf; + aes_data->outbuf_siz = HCRYPT_OPENSSL_OUTMSGMAX * padded_len; + aes_data->outbuf_ofs = 0; +// membuf += aes_data->outbuf_siz; + + return((hcrypt_CipherData *)aes_data); +} + +static int hcOpenSSL_AES_Close(hcrypt_CipherData *cipher_data) +{ + if (NULL != cipher_data) { + free(cipher_data); + } + return(0); +} + +static int hcOpenSSL_AES_SetKey(hcrypt_CipherData *cipher_data, hcrypt_Ctx *ctx, unsigned char *key, size_t key_len) +{ + hcOpenSSL_AES_data *aes_data = (hcOpenSSL_AES_data *)cipher_data; + AES_KEY *aes_key = &aes_data->aes_key[hcryptCtx_GetKeyIndex(ctx)]; /* Ctx tells if it's for odd or even key */ + + if ((ctx->flags & HCRYPT_CTX_F_ENCRYPT) /* Encrypt key */ + || (ctx->mode == HCRYPT_CTX_MODE_AESCTR)) { /* CTR mode decrypts using encryption methods */ + if (AES_set_encrypt_key(key, key_len * 8, aes_key)) { + HCRYPT_LOG(LOG_ERR, "%s", "AES_set_encrypt_key(sek) failed\n"); + return(-1); + } + } else { /* Decrypt key */ + if (AES_set_decrypt_key(key, key_len * 8, aes_key)) { + HCRYPT_LOG(LOG_ERR, "%s", "AES_set_decrypt_key(sek) failed\n"); + return(-1); + } + } + return(0); +} + +static int hcOpenSSL_AES_Encrypt( + hcrypt_CipherData *cipher_data, + hcrypt_Ctx *ctx, + hcrypt_DataDesc *in_data, int nbin, + void *out_p[], size_t out_len_p[], int *nbout_p) +{ + hcOpenSSL_AES_data *aes_data = (hcOpenSSL_AES_data *)cipher_data; + unsigned char *out_msg; + int out_len = 0; //payload size + int pfx_len; + + ASSERT(NULL != ctx); + ASSERT(NULL != aes_data); + ASSERT((NULL != in_data) || (1 == nbin)); //Only one in_data[] supported + + /* + * Get message prefix length + * to reserve room for unencrypted message header in output buffer + */ + pfx_len = ctx->msg_info->pfx_len; + + /* Get buffer room from the internal circular output buffer */ + out_msg = hcOpenSSL_AES_GetOutbuf(aes_data, pfx_len, in_data[0].len); + + if (NULL != out_msg) { + switch(ctx->mode) { + case HCRYPT_CTX_MODE_AESCTR: /* Counter mode */ + { + /* Get current key (odd|even) from context */ + AES_KEY *aes_key = &aes_data->aes_key[hcryptCtx_GetKeyIndex(ctx)]; + unsigned char ctr[AES_BLOCK_SIZE]; + unsigned char iv[AES_BLOCK_SIZE]; + unsigned blk_ofs = 0; + + /* Get input packet index (in network order) */ + hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); + + /* + * Compute the Initial Vector + * IV (128-bit): + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0s | pki | ctr | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * XOR + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | nonce + + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * pki (32-bit): packet index + * ctr (16-bit): block counter + * nonce (112-bit): number used once (salt) + */ + memset(&ctr[0], 0, sizeof(ctr)); + hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); + + /* Encrypt packet payload in output message buffer */ + AES_ctr128_encrypt(in_data[0].payload, &out_msg[pfx_len], + in_data[0].len, aes_key, iv, ctr, &blk_ofs); + + /* Prepend packet prefix (clear text) in output buffer */ + memcpy(out_msg, in_data[0].pfx, pfx_len); + /* CTR mode output length is same as input, no padding */ + out_len = in_data[0].len; + break; + } + case HCRYPT_CTX_MODE_AESECB: /* Electronic Codebook mode (VF-AES) */ + { + int i; + int nb = in_data[0].len/AES_BLOCK_SIZE; + int nmore = in_data[0].len%AES_BLOCK_SIZE; + AES_KEY *aes_key = &aes_data->aes_key[hcryptCtx_GetKeyIndex(ctx)]; + + /* Encrypt packet payload, block by block, in output buffer */ + for (i=0; imsg_info->setPki(out_msg, 16 - nmore); + } + /* Prepend packet prefix (clear text) in output message buffer */ + memcpy(out_msg, in_data[0].pfx, pfx_len); + /* ECB mode output length is on AES block (128 bits) boundary */ + out_len = nb * AES_BLOCK_SIZE; + break; + } + case HCRYPT_CTX_MODE_CLRTXT: /* Clear text mode (transparent mode for tests) */ + memcpy(&out_msg[pfx_len], in_data[0].payload, in_data[0].len); + memcpy(out_msg, in_data[0].pfx, pfx_len); + out_len = in_data[0].len; + break; + default: + /* Unsupported cipher mode */ + return(-1); + } + } else { + /* input data too big */ + return(-1); + } + + if (out_len > 0) { + /* Encrypted messages have been produced */ + if (NULL == out_p) { + /* + * Application did not provided output buffer, + * so copy encrypted message back in input buffer + */ + memcpy(in_data[0].pfx, out_msg, pfx_len); + memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); + in_data[0].len = out_len; + } else { + /* + * Set output buffer array to internal circular buffers + */ + out_p[0] = out_msg; + out_len_p[0] = pfx_len + out_len; + *nbout_p = 1; + } + } else { + /* + * Nothing out + * This is not an error for implementations using deferred/async processing + * with co-processor, DSP, crypto hardware, etc. + * Submitted input data could be returned encrypted in a next call. + */ + if (nbout_p != NULL) *nbout_p = 0; + return(-1); + } + return(0); +} + + + +static int hcOpenSSL_AES_Decrypt(hcrypt_CipherData *cipher_data, hcrypt_Ctx *ctx, + hcrypt_DataDesc *in_data, int nbin, void *out_p[], size_t out_len_p[], int *nbout_p) +{ + hcOpenSSL_AES_data *aes_data = (hcOpenSSL_AES_data *)cipher_data; + unsigned char *out_txt; + int out_len; + int iret = 0; + + ASSERT(NULL != aes_data); + ASSERT(NULL != ctx); + ASSERT((NULL != in_data) || (1 == nbin)); //Only one in_data[] supported + + /* Reserve output buffer (w/no header) */ + out_txt = hcOpenSSL_AES_GetOutbuf(aes_data, 0, in_data[0].len); + + if (NULL != out_txt) { + switch(ctx->mode) { + case HCRYPT_CTX_MODE_AESCTR: + { + /* Get current key (odd|even) from context */ + AES_KEY *aes_key = &aes_data->aes_key[hcryptCtx_GetKeyIndex(ctx)]; + unsigned char ctr[AES_BLOCK_SIZE]; + unsigned char iv[AES_BLOCK_SIZE]; + unsigned blk_ofs = 0; + + /* Get input message index (in network order) */ + hcrypt_Pki pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); + + /* + * Compute the Initial Vector + * IV (128-bit): + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0s | pki | ctr | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * XOR + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | nonce + + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * pki (32-bit): packet index + * ctr (16-bit): block counter + * nonce (112-bit): number used once (salt) + */ + memset(&ctr[0], 0, sizeof(ctr)); + hcrypt_SetCtrIV((unsigned char *)&pki, ctx->salt, iv); + + /* Decrypt message (same as encrypt for CTR mode) */ + AES_ctr128_encrypt(in_data[0].payload, out_txt, + in_data[0].len, aes_key, iv, ctr, &blk_ofs); + out_len = in_data[0].len; + break; + } + case HCRYPT_CTX_MODE_AESECB: + { + int i; + int nb = in_data[0].len/AES_BLOCK_SIZE; + unsigned nbpad = ctx->msg_info->getPki(in_data[0].pfx, 0); //Patch + AES_KEY *aes_key = &aes_data->aes_key[hcryptCtx_GetKeyIndex(ctx)]; + + /* Decrypt message (same as encrypt for CTR mode) */ + for (i=0; i 0) { + if (NULL == out_p) { + /* Decrypt in-place (in input buffer) */ + memcpy(in_data[0].payload, out_txt, out_len); + in_data[0].len = out_len; + } else { + out_p[0] = out_txt; + out_len_p[0] = out_len; + *nbout_p = 1; + } + iret = 0; + } else { + if (NULL != nbout_p) *nbout_p = 0; + iret = -1; + } + +#if 0 + { /* Debug decryption errors */ + static int nberr = 0; + + if (out_txt[0] != 0x47){ + if ((++nberr == 1) + || ((nberr > 500) && (0 == ((((unsigned char *)&MSmsg->pki)[2] & 0x0F)|((unsigned char *)&MSmsg->pki)[3])))) { + HCRYPT_LOG(LOG_DEBUG, "keyindex=%d\n", hcryptCtx_GetKeyIndex(ctx)); + HCRYPT_PRINTKEY(ctx->sek, ctx->sek_len, "sek"); + HCRYPT_PRINTKEY(ctx->salt, ctx->salt_len, "salt"); + } + } else { + nberr = 0; + } + } +#endif + return(iret); +} + + +static hcrypt_Cipher hcOpenSSL_AES_cipher; + +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_AES(void) +{ + hcOpenSSL_AES_cipher.open = hcOpenSSL_AES_Open; + hcOpenSSL_AES_cipher.close = hcOpenSSL_AES_Close; + hcOpenSSL_AES_cipher.setkey = hcOpenSSL_AES_SetKey; + hcOpenSSL_AES_cipher.encrypt = hcOpenSSL_AES_Encrypt; + hcOpenSSL_AES_cipher.decrypt = hcOpenSSL_AES_Decrypt; + + return((HaiCrypt_Cipher)&hcOpenSSL_AES_cipher); +} + +#endif /* HAICRYPT_USE_OPENSSL_AES */ + diff --git a/haicrypt/hc_openssl_evp_cbc.c b/haicrypt/hc_openssl_evp_cbc.c new file mode 100644 index 000000000..f1cc11466 --- /dev/null +++ b/haicrypt/hc_openssl_evp_cbc.c @@ -0,0 +1,271 @@ +/* + * 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. + + 2014-07-18 (jdube) + HaiCrypt initial implementation. +*****************************************************************************/ + +#include "hcrypt.h" + +#ifdef HAICRYPT_USE_OPENSSL_EVP_CBC + +#include +#include + +#define HCRYPT_EVP_CBC_BLK_SZ AES_BLOCK_SIZE + +typedef struct tag_hcOpenSSL_EVP_CBC_data { + EVP_CIPHER_CTX evp_ctx[2]; + +#define HCRYPT_OPENSSL_EVP_CBC_OUTMSGMAX 6 + unsigned char * outbuf; /* output circle buffer */ + size_t outbuf_ofs; /* write offset in circle buffer */ + size_t outbuf_siz; /* circle buffer size */ +}hcOpenSSL_EVP_CBC_data; + +#if 0 //>>use engine +#include + +static int hcOpenSSL_EnginesLoaded = 0; +static ENGINE *hcOpenSSL_Engine = NULL; + +void hcOpenSSL_EnginesInit(void) +{ + if (!hcOpenSSL_EnginesLoaded) { +#if 1 + ENGINE *e = NULL; + ENGINE_load_cryptodev(); + + if (NULL == (e = ENGINE_by_id("cryptodev"))) { + HCRYPT_LOG(LOG_ERR, "%s", "Cryptodev not available\n"); + } else if(!ENGINE_set_default(e, ENGINE_METHOD_ALL)) { + HCRYPT_LOG(LOG_ERR, "%s", "Cannot use cryptodev\n"); + } else { + HCRYPT_LOG(LOG_DEBUG, "Using \"%s\" engine\n", ENGINE_get_id(e)); + hcOpenSSL_Engine = e; + } + ENGINE_free(e); +#else + HCRYPT_LOG(LOG_DEBUG, "%s", "Loading engines\n"); + /* Load all bundled ENGINEs into memory and make them visible */ + ENGINE_load_builtin_engines(); + /* Register all of them for every algorithm they collectively implement */ + ENGINE_register_all_complete(); +#endif + } + hcOpenSSL_EnginesLoaded++; +} + +void hcOpenSSL_EnginesExit(void) +{ + if ((0 < hcOpenSSL_EnginesLoaded) + && (0 == --hcOpenSSL_EnginesLoaded)) { + ENGINE_cleanup(); + HCRYPT_LOG(LOG_DEBUG, "%s", "Cleanup engines\n"); + } +} +#else +#define hcOpenSSL_Engine (NULL) +#define hcOpenSSL_EnginesInit() +#define hcOpenSSL_EnginesExit() +#endif + + +static unsigned char *hcOpenSSL_EVP_CBC_GetOutbuf(hcOpenSSL_EVP_CBC_data *evp_data, size_t hdr_len, size_t out_len) +{ + unsigned char *out_buf; + int nblks = (out_len + AES_BLOCK_SIZE -1) / AES_BLOCK_SIZE; + if ((hdr_len + (nblks * AES_BLOCK_SIZE)) > (evp_data->outbuf_siz - evp_data->outbuf_ofs)) { + /* Not enough room left, circle buffers */ + evp_data->outbuf_ofs = 0; + } + out_buf = &evp_data->outbuf[evp_data->outbuf_ofs]; + evp_data->outbuf_ofs += (hdr_len + (nblks * AES_BLOCK_SIZE)); + return(out_buf); +} + +static int hcOpenSSL_EVP_CBC_CipherData(EVP_CIPHER_CTX *evp_ctx, + unsigned char *in_ptr, size_t in_len, unsigned char *iv, unsigned char *out_ptr, size_t *out_len_ptr) +{ + int c_len = 0; + int f_len = 0; + + /* allows reusing of 'e' for multiple encryption cycles */ + if (!EVP_CipherInit_ex(evp_ctx, NULL, NULL, NULL, iv, -1)) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher init failed\n"); + } else if (!EVP_CipherUpdate(evp_ctx, out_ptr, &c_len, in_ptr, in_len)) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher init failed\n"); + } else if (!EVP_CipherFinal_ex(evp_ctx, &out_ptr[c_len], &f_len)) { + HCRYPT_LOG(LOG_ERR, "incomplete block (%zd/%d,%d)\n", in_len, c_len, f_len); + } + if (out_len_ptr) *out_len_ptr = c_len + f_len; + return(0); +} + + +static hcrypt_CipherData *hcOpenSSL_EVP_CBC_Open(size_t max_len) +{ + hcOpenSSL_EVP_CBC_data *evp_data; + unsigned char *membuf; + size_t padded_len = hcryptMsg_PaddedLen(max_len, 128/8); + size_t memsiz = sizeof(*evp_data) + + (HCRYPT_OPENSSL_EVP_CBC_OUTMSGMAX * padded_len); + + + HCRYPT_LOG(LOG_DEBUG, "%s", "Using OpenSSL EVP-CBC\n"); + + evp_data = malloc(memsiz); + if (NULL == evp_data) { + HCRYPT_LOG(LOG_ERR, "malloc(%zd) failed\n", memsiz); + return(NULL); + } + hcOpenSSL_EnginesInit(); + + membuf = (unsigned char *)evp_data; + membuf += sizeof(*evp_data); + + evp_data->outbuf = membuf; +// membuf += HCRYPT_OPENSSL_EVP_CBC_OUTMSGMAX * padded_len; + evp_data->outbuf_siz = HCRYPT_OPENSSL_EVP_CBC_OUTMSGMAX * padded_len; + evp_data->outbuf_ofs = 0; + + EVP_CIPHER_CTX_init(&evp_data->evp_ctx[0]); +// EVP_CIPHER_CTX_set_padding(&evp_data->evp_ctx[0], 0); + + EVP_CIPHER_CTX_init(&evp_data->evp_ctx[1]); +// EVP_CIPHER_CTX_set_padding(&evp_data->evp_ctx[1], 0); + + return((hcrypt_CipherData *)evp_data); +} + +static int hcOpenSSL_EVP_CBC_Close(hcrypt_CipherData *cipher_data) +{ + hcOpenSSL_EVP_CBC_data *evp_data = (hcOpenSSL_EVP_CBC_data *)cipher_data; + + if (NULL != evp_data) { + EVP_CIPHER_CTX_cleanup(&evp_data->evp_ctx[0]); + EVP_CIPHER_CTX_cleanup(&evp_data->evp_ctx[1]); + free(evp_data); + } + hcOpenSSL_EnginesExit(); + return(0); +} + +static int hcOpenSSL_EVP_CBC_SetKey(hcrypt_CipherData *cipher_data, hcrypt_Ctx *ctx, unsigned char *key, size_t key_len) +{ + hcOpenSSL_EVP_CBC_data *evp_data = (hcOpenSSL_EVP_CBC_data *)cipher_data; + EVP_CIPHER_CTX *evp_ctx = &evp_data->evp_ctx[hcryptCtx_GetKeyIndex(ctx)]; + int enc = (ctx->flags & HCRYPT_CTX_F_ENCRYPT); + const EVP_CIPHER *cipher = NULL; + + switch(key_len) { + case 128/8: + cipher = EVP_aes_128_cbc(); + break; + case 192/8: + cipher = EVP_aes_192_cbc(); + break; + case 256/8: + cipher = EVP_aes_256_cbc(); + break; + default: + HCRYPT_LOG(LOG_ERR, "%s", "invalid key length\n"); + return(-1); + } + EVP_CipherInit_ex(evp_ctx, cipher, hcOpenSSL_Engine, key, NULL, enc); + return(0); +} + +static int hcOpenSSL_EVP_CBC_Crypt(hcrypt_CipherData *cipher_data, hcrypt_Ctx *ctx, + hcrypt_DataDesc *in_data, int nbin, void *out_p[], size_t out_len_p[], int *nbout_p) +{ + hcOpenSSL_EVP_CBC_data *evp_data = (hcOpenSSL_EVP_CBC_data *)cipher_data; + unsigned char iv[HCRYPT_EVP_CBC_BLK_SZ]; + hcrypt_Pki pki; + unsigned char *out_msg; + size_t pfx_len; + size_t out_len; + int iret; + + ASSERT(NULL != evp_data); + ASSERT(NULL != ctx); + ASSERT((NULL != in_data) && (1 == nbin)); + ASSERT((NULL == out_p) || ((NULL != out_p) && (NULL != out_len_p) && (NULL != nbout_p))); + + /* Room for prefix in output buffer only required for encryption */ + pfx_len = ctx->flags & HCRYPT_CTX_F_ENCRYPT ? ctx->msg_info->pfx_len : 0; + +//>>set CBC eventually + if (HCRYPT_CTX_MODE_AESCTR != ctx->mode) { + HCRYPT_LOG(LOG_ERR, "invalid mode (%d) for cipher\n", ctx->mode); + return(-1); + } + + /* Compute IV */ + pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); + hcrypt_SetCtrIV(&pki, ctx->salt, iv); + + /* Reserve output buffer for cipher */ + out_msg = hcOpenSSL_EVP_CBC_GetOutbuf(evp_data, pfx_len, in_data[0].len); + + /* Encrypt */ + iret = hcOpenSSL_EVP_CBC_CipherData(&evp_data->evp_ctx[hcryptCtx_GetKeyIndex(ctx)], + in_data[0].payload, in_data[0].len, iv, &out_msg[pfx_len], &out_len); + if (iret) { + HCRYPT_LOG(LOG_ERR, "%s", "CBC_CipherData failed\n"); + return(iret); + } + + /* Output cipher text */ + if (out_len > 0) { + if (NULL == out_p) { + /* Copy output data back in input buffer */ + memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); + } else { + /* Copy header in output buffer if needed */ + if (pfx_len > 0) memcpy(out_msg, in_data[0].pfx, pfx_len); + out_p[0] = out_msg; + out_len_p[0] = pfx_len + out_len; + *nbout_p = 1; + } + iret = (int)out_len; + } else { + if (NULL != nbout_p) *nbout_p = 0; + iret = -1; + } + return(iret); +} + +static hcrypt_Cipher hcOpenSSL_EVP_CBC_cipher; + +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CBC(void) +{ + hcOpenSSL_EVP_CBC_cipher.open = hcOpenSSL_EVP_CBC_Open; + hcOpenSSL_EVP_CBC_cipher.close = hcOpenSSL_EVP_CBC_Close; + hcOpenSSL_EVP_CBC_cipher.setkey = hcOpenSSL_EVP_CBC_SetKey; + hcOpenSSL_EVP_CBC_cipher.encrypt = hcOpenSSL_EVP_CBC_Crypt; + hcOpenSSL_EVP_CBC_cipher.decrypt = hcOpenSSL_EVP_CBC_Crypt; + + return((HaiCrypt_Cipher)&hcOpenSSL_EVP_CBC_cipher); +} +#endif /* HAICRYPT_USE_OPENSSL_EVP_CBC */ diff --git a/haicrypt/hc_openssl_evp_ctr.c b/haicrypt/hc_openssl_evp_ctr.c new file mode 100644 index 000000000..1e1f76820 --- /dev/null +++ b/haicrypt/hc_openssl_evp_ctr.c @@ -0,0 +1,343 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. + 2014-07-15 (jdube) + Adaptation for EVP CTR mode +*****************************************************************************/ + +#include "hcrypt.h" + +#ifdef HAICRYPT_USE_OPENSSL_EVP_CTR + +#include +#include + +#define HCRYPT_EVP_CTR_BLK_SZ AES_BLOCK_SIZE + +typedef struct tag_hcOpenSSL_EVP_CTR_data { + EVP_CIPHER_CTX evp_ctx[2]; + +#ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR +#define HCRYPT_EVP_CTR_STREAM_SZ 2048 + unsigned char * ctr_stream; + size_t ctr_stream_len; /* Content size */ + size_t ctr_stream_siz; /* Allocated length */ +#endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + +#define HCRYPT_OPENSSL_EVP_CTR_OUTMSGMAX 6 + unsigned char * outbuf; /* output circle buffer */ + size_t outbuf_ofs; /* write offset in circle buffer */ + size_t outbuf_siz; /* circle buffer size */ +}hcOpenSSL_EVP_CTR_data; + +#ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR +static int hcOpenSSL_EVP_CTR_SetCtrStream(hcOpenSSL_EVP_CTR_data *aes_data, hcrypt_Ctx *ctx, size_t len, unsigned char *iv) +{ + /* Counter stream: + * 0 1 2 3 4 5 nblk + * +---+---+---+---+---+---+---+---+ + * |blk|blk|blk|blk|blk|blk|...|blk| + * +---+---+---+---+---+---+---+---+ + */ + + /* IV (128-bit): + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0s | pki | ctr | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * XOR + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | nonce + + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * pki (32-bit): packet index + * ctr (16-bit): block counter + * nonce (112-bit): number used once (salt) + */ + unsigned char ctr[HCRYPT_EVP_CTR_BLK_SZ]; + unsigned nblk; + + ASSERT(NULL != aes_data); + ASSERT(NULL != ctx); + + memcpy(ctr, iv, HCRYPT_EVP_CTR_BLK_SZ); + + nblk = (len + (HCRYPT_EVP_CTR_BLK_SZ-1))/HCRYPT_EVP_CTR_BLK_SZ; + if ((nblk * HCRYPT_EVP_CTR_BLK_SZ) <= aes_data->ctr_stream_siz) { + unsigned blk; + unsigned char *csp = &aes_data->ctr_stream[0]; + + for(blk = 0; blk < nblk; blk++) { + memcpy(csp, ctr, HCRYPT_EVP_CTR_BLK_SZ); + csp += HCRYPT_EVP_CTR_BLK_SZ; + if (0 == ++(ctr[HCRYPT_EVP_CTR_BLK_SZ-1])) ++(ctr[HCRYPT_EVP_CTR_BLK_SZ-2]); + } + aes_data->ctr_stream_len = nblk * HCRYPT_EVP_CTR_BLK_SZ; + } else { + HCRYPT_LOG(LOG_ERR, "packet too long(%zd)\n", len); + return(-1); + } + return(0); +} +#endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + +static unsigned char *hcOpenSSL_EVP_CTR_GetOutbuf(hcOpenSSL_EVP_CTR_data *evp_data, size_t hdr_len, size_t out_len) +{ + unsigned char *out_buf; + + if ((hdr_len + out_len) > (evp_data->outbuf_siz - evp_data->outbuf_ofs)) { + /* Not enough room left, circle buffers */ + evp_data->outbuf_ofs = 0; + } + out_buf = &evp_data->outbuf[evp_data->outbuf_ofs]; + evp_data->outbuf_ofs += (hdr_len + out_len); + return(out_buf); +} + +static int hcOpenSSL_EVP_CTR_CipherData(EVP_CIPHER_CTX *evp_ctx, + unsigned char *in_ptr, size_t in_len, unsigned char *iv, unsigned char *out_ptr, size_t *out_len_ptr) +{ + int c_len, f_len; + + /* allows reusing of 'e' for multiple encryption cycles */ + EVP_CipherInit_ex(evp_ctx, NULL, NULL, NULL, iv, -1); + EVP_CIPHER_CTX_set_padding(evp_ctx, 0); + + /* update ciphertext, c_len is filled with the length of ciphertext generated, + * cryptoPtr->cipher_in_len is the size of plain/cipher text in bytes + */ + EVP_CipherUpdate(evp_ctx, out_ptr, &c_len, in_ptr, in_len); + + /* update ciphertext with the final remaining bytes */ + /* Useless with pre-padding */ + f_len = 0; + if (0 == EVP_CipherFinal_ex(evp_ctx, &out_ptr[c_len], &f_len)) { + HCRYPT_LOG(LOG_ERR, "incomplete block (%d/%zd)\n", c_len, in_len); + } + if (out_len_ptr) *out_len_ptr = c_len + f_len; + return(0); +} + + +static hcrypt_CipherData *hcOpenSSL_EVP_CTR_Open(size_t max_len) +{ + hcOpenSSL_EVP_CTR_data *evp_data; + unsigned char *membuf; + size_t padded_len = hcryptMsg_PaddedLen(max_len, 128/8); + size_t memsiz = sizeof(*evp_data) +#ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR + + HCRYPT_EVP_CTR_STREAM_SZ +#endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + + (HCRYPT_OPENSSL_EVP_CTR_OUTMSGMAX * padded_len); + + HCRYPT_LOG(LOG_DEBUG, "%s", "Using OpenSSL EVP-CTR\n"); + + evp_data = malloc(memsiz); + if (NULL == evp_data) { + HCRYPT_LOG(LOG_ERR, "malloc(%zd) failed\n", memsiz); + return(NULL); + } + membuf = (unsigned char *)evp_data; + membuf += sizeof(*evp_data); + +#ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR + evp_data->ctr_stream = membuf; + membuf += HCRYPT_EVP_CTR_STREAM_SZ; + evp_data->ctr_stream_siz = HCRYPT_EVP_CTR_STREAM_SZ; + evp_data->ctr_stream_len = 0; +#endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + + evp_data->outbuf = membuf; + evp_data->outbuf_siz = HCRYPT_OPENSSL_EVP_CTR_OUTMSGMAX * padded_len; + evp_data->outbuf_ofs = 0; + + EVP_CIPHER_CTX_init(&evp_data->evp_ctx[0]); + EVP_CIPHER_CTX_set_padding(&evp_data->evp_ctx[0], 0); + + EVP_CIPHER_CTX_init(&evp_data->evp_ctx[1]); + EVP_CIPHER_CTX_set_padding(&evp_data->evp_ctx[1], 0); + + return((hcrypt_CipherData *)evp_data); +} + +static int hcOpenSSL_EVP_CTR_Close(hcrypt_CipherData *cipher_data) +{ + hcOpenSSL_EVP_CTR_data *evp_data = (hcOpenSSL_EVP_CTR_data *)cipher_data; + + if (NULL != evp_data) { + EVP_CIPHER_CTX_cleanup(&evp_data->evp_ctx[0]); + EVP_CIPHER_CTX_cleanup(&evp_data->evp_ctx[1]); + free(evp_data); + } + return(0); +} + +static int hcOpenSSL_EVP_CTR_SetKey(hcrypt_CipherData *cipher_data, hcrypt_Ctx *ctx, unsigned char *key, size_t key_len) +{ + hcOpenSSL_EVP_CTR_data *evp_data = (hcOpenSSL_EVP_CTR_data *)cipher_data; + EVP_CIPHER_CTX *evp_ctx = &evp_data->evp_ctx[hcryptCtx_GetKeyIndex(ctx)]; + int enc = ((ctx->flags & HCRYPT_CTX_F_ENCRYPT) || (ctx->mode == HCRYPT_CTX_MODE_AESCTR)); + const EVP_CIPHER *cipher = NULL; + + switch(key_len) { +#ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR + case 128/8: + cipher = EVP_aes_128_ecb(); + break; + case 192/8: + cipher = EVP_aes_192_ecb(); + break; + case 256/8: + cipher = EVP_aes_256_ecb(); + break; +#else /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + case 128/8: + cipher = EVP_aes_128_ctr(); + break; + case 192/8: + cipher = EVP_aes_192_ctr(); + break; + case 256/8: + cipher = EVP_aes_256_ctr(); + break; +#endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + default: + HCRYPT_LOG(LOG_ERR, "%s", "invalid key length\n"); + return(-1); + } + + EVP_CipherInit_ex(evp_ctx, cipher, NULL, key, NULL, enc); + return(0); +} + +static int hcOpenSSL_EVP_CTR_Crypt(hcrypt_CipherData *cipher_data, hcrypt_Ctx *ctx, + hcrypt_DataDesc *in_data, int nbin, void *out_p[], size_t out_len_p[], int *nbout_p) +{ + hcOpenSSL_EVP_CTR_data *evp_data = (hcOpenSSL_EVP_CTR_data *)cipher_data; + unsigned char iv[HCRYPT_EVP_CTR_BLK_SZ]; + hcrypt_Pki pki; + unsigned char *out_msg; + size_t pfx_len; + size_t out_len; + int iret; + + ASSERT(NULL != evp_data); + ASSERT(NULL != ctx); + ASSERT((NULL != in_data) && (1 == nbin)); + + /* Room for prefix in output buffer only required for encryption */ + pfx_len = ctx->flags & HCRYPT_CTX_F_ENCRYPT ? ctx->msg_info->pfx_len : 0; + + if (HCRYPT_CTX_MODE_AESCTR != ctx->mode) { + HCRYPT_LOG(LOG_ERR, "invalid mode (%d) for cipher\n", ctx->mode); + return(-1); + } + + /* Compute IV */ + pki = hcryptMsg_GetPki(ctx->msg_info, in_data[0].pfx, 1); + hcrypt_SetCtrIV(&pki, ctx->salt, iv); + + #ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR + /* Create CtrStream. May be longer than in_len (next cipher block size boundary) */ + iret = hcOpenSSL_EVP_CTR_SetCtrStream(evp_data, ctx, in_data[0].len, iv); + if (iret) { + return(iret); + } + /* Reserve output buffer for cipher */ + out_msg = hcOpenSSL_EVP_CTR_GetOutbuf(evp_data, pfx_len, evp_data->ctr_stream_len); + + /* Create KeyStream (encrypt CtrStream) */ + iret = hcOpenSSL_EVP_CTR_CipherData(&evp_data->evp_ctx[hcryptCtx_GetKeyIndex(ctx)], + evp_data->ctr_stream, evp_data->ctr_stream_len, NULL, + &out_msg[pfx_len], &out_len); + if (iret) { + HCRYPT_LOG(LOG_ERR, "%s", "CRYPTOEVP_CipherData failed\n"); + return(iret); + } + + #else /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + /* Reserve output buffer for cipher */ + out_msg = hcOpenSSL_EVP_CTR_GetOutbuf(evp_data, pfx_len, in_data[0].len); + + /* Encrypt */ + iret = hcOpenSSL_EVP_CTR_CipherData(&evp_data->evp_ctx[hcryptCtx_GetKeyIndex(ctx)], + in_data[0].payload, in_data[0].len, iv, + &out_msg[pfx_len], &out_len); + if (iret) { + HCRYPT_LOG(LOG_ERR, "%s", "CTR_CipherData failed\n"); + return(iret); + } + #endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + + /* Output clear or cipher text */ + if (out_len > 0) { + if (NULL == out_p) { + #ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR + /* XOR KeyStream with input text directly in input buffer */ + hcrypt_XorStream(in_data[0].payload, &out_msg[pfx_len], out_len); + #else /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + /* Copy output data back in input buffer */ + memcpy(in_data[0].payload, &out_msg[pfx_len], out_len); + #endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + } else { + /* Copy header in output buffer if needed */ + if (pfx_len > 0) memcpy(out_msg, in_data[0].pfx, pfx_len); + #ifdef HAICRYPT_USE_OPENSSL_EVP_ECB4CTR + hcrypt_XorStream(&out_msg[pfx_len], in_data[0].payload, out_len); + #endif /* HAICRYPT_USE_OPENSSL_EVP_ECB4CTR */ + out_p[0] = out_msg; + out_len_p[0] = pfx_len + out_len; + *nbout_p = 1; + } + iret = 0; + } else { + if (NULL != nbout_p) *nbout_p = 0; + iret = -1; + } + return(iret); +} + +static hcrypt_Cipher hcOpenSSL_EVP_CTR_cipher; + +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CTR(void) +{ + hcOpenSSL_EVP_CTR_cipher.open = hcOpenSSL_EVP_CTR_Open; + hcOpenSSL_EVP_CTR_cipher.close = hcOpenSSL_EVP_CTR_Close; + hcOpenSSL_EVP_CTR_cipher.setkey = hcOpenSSL_EVP_CTR_SetKey; + hcOpenSSL_EVP_CTR_cipher.encrypt = hcOpenSSL_EVP_CTR_Crypt; // Counter Mode encrypt and + hcOpenSSL_EVP_CTR_cipher.decrypt = hcOpenSSL_EVP_CTR_Crypt; // ...decrypt are the same + + return((HaiCrypt_Cipher)&hcOpenSSL_EVP_CTR_cipher); +} + +/* Backward compatible call when only CTR was available */ +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP(void) +{ + return(HaiCryptCipher_OpenSSL_EVP_CTR()); +} + +#endif /* HAICRYPT_USE_OPENSSL_EVP_CTR */ diff --git a/haicrypt/hcrypt.c b/haicrypt/hcrypt.c new file mode 100644 index 000000000..45698e0cb --- /dev/null +++ b/haicrypt/hcrypt.c @@ -0,0 +1,204 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include /* snprintf */ +#include /* NULL, malloc, free */ +#include /* memcpy, memset */ +#ifdef WIN32 + #include + #include +#else + #include /* timerclear */ +#endif + +#include "hcrypt.h" + +int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc) +{ + hcrypt_Session *crypto; + hcrypt_Cipher *cipher; + unsigned char *mem_buf; + size_t mem_siz, inbuf_siz; + int tx; + + *phhc = NULL; + + ASSERT(NULL != cfg); + + 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); + return(-1); + } else + if ((16 != cfg->key_len) /* SEK length */ + && (24 != cfg->key_len) + && (32 != cfg->key_len)) { + HCRYPT_LOG(LOG_ERR, "invalid key length (%zd)\n", cfg->key_len); + return(-1); + } else + if ((HAICRYPT_SECTYP_PASSPHRASE == cfg->secret.typ) + && ((0 == cfg->secret.len) || (sizeof(cfg->secret.str) < cfg->secret.len))) { /* KEK length */ + HCRYPT_LOG(LOG_ERR, "invalid secret passphrase length (%zd)\n", cfg->secret.len); + return(-1); + } else + if ((HAICRYPT_SECTYP_PRESHARED == cfg->secret.typ) + && (16 != cfg->key_len) /* SEK length */ + && (24 != cfg->key_len) + && (32 != cfg->key_len)) { + HCRYPT_LOG(LOG_ERR, "invalid pre-shared secret length (%zd)\n", cfg->secret.len); + return(-1); + } else + if ((HAICRYPT_SECTYP_PRESHARED == cfg->secret.typ) + && (cfg->key_len > cfg->secret.len)) { + HCRYPT_LOG(LOG_ERR, "preshared secret length (%zd) smaller than key length (%zd)\n", + cfg->secret.len, cfg->key_len); + return(-1); + } else + if (NULL == cfg->cipher) { + HCRYPT_LOG(LOG_ERR, "%s\n", "no cipher specified"); + return(-1); + } else + if (0 == cfg->data_max_len) { + HCRYPT_LOG(LOG_ERR, "%s\n", "no data_max_len specified"); + return(-1); + } + + cipher = (hcrypt_Cipher *)cfg->cipher; + tx = HAICRYPT_CFG_F_TX & cfg->flags; + HCRYPT_PRINTKEY(cfg->secret.str, cfg->secret.len, "cfgkey"); + + /* + * If cipher has no special input buffer alignment requirement, + * handle it in the crypto session. + */ + inbuf_siz = 0; + if (NULL == cipher->getinbuf) { + inbuf_siz = hcryptMsg_PaddedLen(cfg->data_max_len, 128/8); + } + + /* Allocate crypto session control struct */ + mem_siz = sizeof(hcrypt_Session) // structure + + inbuf_siz; + + crypto = malloc(mem_siz); + if (NULL == crypto){ + HCRYPT_LOG(LOG_ERR, "%s\n", "malloc failed"); + return(-1); + } + mem_buf = (unsigned char *)crypto; + mem_buf += sizeof(*crypto); + memset(crypto, 0, sizeof(*crypto)); + + if (inbuf_siz) { + crypto->inbuf = mem_buf; + crypto->inbuf_siz = inbuf_siz; + mem_buf += inbuf_siz; + } + + crypto->cipher = cfg->cipher; + + /* Setup transport packet info */ + switch (cfg->xport) { + case HAICRYPT_XPT_STANDALONE: + crypto->se = HCRYPT_SE_TSUDP; + crypto->msg_info = hcryptMsg_STA_MsgInfo(); + break; + case HAICRYPT_XPT_SRT: + crypto->se = HCRYPT_SE_TSSRT; + crypto->msg_info = hcryptMsg_SRT_MsgInfo(); + break; + default: + HCRYPT_LOG(LOG_ERR, "invalid xport: %d\n", cfg->xport); + free(crypto); + return(-1); + } + + timerclear(&crypto->km.tx_last); + crypto->km.tx_period.tv_sec = cfg->km_tx_period_ms / 1000; + crypto->km.tx_period.tv_usec = (cfg->km_tx_period_ms % 1000) * 1000; + + crypto->km.refresh_rate = cfg->km_refresh_rate_pkt; + crypto->km.pre_announce = cfg->km_pre_announce_pkt; + + /* Indentify each context */ + crypto->ctx_pair[0].flags = HCRYPT_MSG_F_eSEK | (tx ? HCRYPT_CTX_F_ENCRYPT : 0); + crypto->ctx_pair[1].flags = HCRYPT_MSG_F_oSEK | (tx ? HCRYPT_CTX_F_ENCRYPT : 0); + /* Point to each other */ + crypto->ctx_pair[0].alt = &crypto->ctx_pair[1]; + crypto->ctx_pair[1].alt = &crypto->ctx_pair[0]; + + crypto->cipher_data = crypto->cipher->open(cfg->data_max_len); + if (NULL == crypto->cipher_data) { + free(crypto); + return(-1); + } + if (tx) { /* Encoder */ + /* Configure initial context */ + if (hcryptCtx_Tx_Init(crypto, &crypto->ctx_pair[0], cfg) + || hcryptCtx_Tx_Init(crypto, &crypto->ctx_pair[1], cfg)) { + free(crypto); + return(-1); + } + /* Generate keys for first (default) context */ + if (hcryptCtx_Tx_Rekey(crypto, &crypto->ctx_pair[0])) { + free(crypto); + return(-1); + } + crypto->ctx = &crypto->ctx_pair[0]; + crypto->ctx->flags |= (HCRYPT_CTX_F_ANNOUNCE | HCRYPT_CTX_F_TTSEND); + crypto->ctx->status = HCRYPT_CTX_S_ACTIVE; + } else { /* Decoder */ + /* Configure contexts */ + if (hcryptCtx_Rx_Init(crypto, &crypto->ctx_pair[0], cfg) + || hcryptCtx_Rx_Init(crypto, &crypto->ctx_pair[1], cfg)) { + free(crypto); + return(-1); + } + } + + *phhc = (void *)crypto; + return(0); +} + +int HaiCrypt_Close(HaiCrypt_Handle hhc) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + int rc = -1; + + if (crypto) { + if (crypto->cipher && crypto->cipher->close) crypto->cipher->close(crypto->cipher_data); + free(crypto); + rc = 0; + } + HCRYPT_LOG_EXIT(); + return rc; +} diff --git a/haicrypt/hcrypt.h b/haicrypt/hcrypt.h new file mode 100644 index 000000000..28dfbe0cd --- /dev/null +++ b/haicrypt/hcrypt.h @@ -0,0 +1,220 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. + 2014-03-26 (jsantiago) + OS-X Build. + 2014-03-27 (jdube) + Remove dependency on internal Crypto API. + 2016-07-22 (jsantiago) + MINGW-W64 Build. +*****************************************************************************/ + +#ifndef HCRYPT_H +#define HCRYPT_H + +#include + +#ifdef WIN32 + #include + #include + #if defined(_MSC_VER) + #pragma warning(disable:4267) + #pragma warning(disable:4018) + #endif +#else + #include +#endif + +#include "haicrypt.h" +#include "hcrypt_msg.h" +#include "hcrypt_ctx.h" + +//#define HCRYPT_DEV 1 /* Development: should not be defined in committed code */ + +#ifdef HAICRYPT_SUPPORT_CRYPTO_API +/* See CRYPTOFEC_OBJECT in session structure */ +#define CRYPTO_API_SERVER 1 /* Enable handler's structures */ +#include "crypto_api.h" +#endif /* HAICRYPT_SUPPORT_CRYPTO_API */ + +typedef struct { +#ifdef HAICRYPT_SUPPORT_CRYPTO_API + /* + * Resv matches internal upper layer handle (crypto_api) + * They are not used in HaiCrypt. + * This make 3 layers using the same handle. + * To get rid of this dependency for a portable HaiCrypt, + * revise caller (crypto_hc.c) to allocate its own buffer. + */ + CRYPTOFEC_OBJECT resv; /* See above comment */ +#endif /* HAICRYPT_SUPPORT_CRYPTO_API */ + + hcrypt_Ctx ctx_pair[2]; /* Even(0)/Odd(1) crypto contexts */ + hcrypt_Ctx * ctx; /* Current context */ + + hcrypt_Cipher * cipher; + hcrypt_CipherData * cipher_data; + + unsigned char * inbuf; /* allocated if cipher has no getinbuf() func */ + size_t inbuf_siz; + + int se; /* Stream Encapsulation (HCRYPT_SE_xxx) */ + hcrypt_MsgInfo * msg_info; + + struct { + struct timeval tx_period; /* Keying Material tx period (milliseconds) */ + struct timeval tx_last; /* Keying Material last tx time */ + unsigned int refresh_rate; /* SEK use period */ + unsigned int pre_announce; /* Pre/Post next/old SEK announce */ + }km; +} hcrypt_Session; + + +#define HCRYPT_LOG_INIT() +#define HCRYPT_LOG_EXIT() +#define HCRYPT_LOG(lvl, fmt, ...) + +#ifdef HCRYPT_DEV +#define HCRYPT_PRINTKEY(key, len, tag) HCRYPT_LOG(LOG_DEBUG, \ + "%s[%d]=0x%02x%02x..%02x%02x\n", tag, len, \ + (key)[0], (key)[1], (key)[(len)-2], (key)[(len)-1]) +#else /* HCRYPT_DEV */ +#define HCRYPT_PRINTKEY(key,len,tag) +#endif /* HCRYPT_DEV */ + +#ifndef ASSERT +#include +#define ASSERT(c) assert(c) +#endif + +#ifdef HAICRYPT_USE_OPENSSL_AES +#include /* OPENSSL_VERSION_NUMBER */ +#include /* PKCS5_ */ +#include /* RAND_bytes */ +#include /* AES_ */ + +#define hcrypt_Prng(rn, len) (RAND_bytes(rn, len) <= 0 ? -1 : 0) + +#if (OPENSSL_VERSION_NUMBER < 0x0090808fL) //0.9.8h + /* + * AES_wrap_key()/AES_unwrap_key() introduced in openssl 0.9.8h + * Use internal implementation (in hc_openssl_aes.c) for earlier versions + */ +int AES_wrap_key(AES_KEY *key, const unsigned char *iv, unsigned char *out, + const unsigned char *in, unsigned int inlen); +int AES_unwrap_key(AES_KEY *key, const unsigned char *iv, unsigned char *out, + const unsigned char *in, unsigned int inlen); +#endif /* OPENSSL_VERSION_NUMBER */ + +#define hcrypt_WrapKey(kek, wrap, key, keylen) (((int)(keylen + HAICRYPT_WRAPKEY_SIGN_SZ) \ + == AES_wrap_key(kek, NULL, wrap, key, keylen)) ? 0 : -1) +#define hcrypt_UnwrapKey(kek, key, wrap, wraplen) (((int)(wraplen - HAICRYPT_WRAPKEY_SIGN_SZ) \ + == AES_unwrap_key(kek, NULL, key, wrap, wraplen)) ? 0 : -1) + +#else /* HAICRYPT_USE_OPENSSL_AES */ +#error No Prng and key wrapper defined + +#endif /* HAICRYPT_USE_OPENSSL_AES */ + + +#ifdef HAICRYPT_USE_OPENSSL_EVP +#include // OPENSSL_VERSION_NUMBER + +#define HAICRYPT_USE_OPENSSL_EVP_CTR 1 + /* + * CTR mode is the default mode for HaiCrypt (standalone and SRT) + */ +#ifdef HAICRYPT_USE_OPENSSL_EVP_CTR +#if (OPENSSL_VERSION_NUMBER < 0x10001000L) + /* + * CTR mode for EVP API introduced in openssl 1.0.1 + * Implement it using ECB mode for earlier versions + */ +#define HAICRYPT_USE_OPENSSL_EVP_ECB4CTR 1 +#endif + HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CTR(void); +#endif + +//undef HAICRYPT_USE_OPENSSL_EVP_CBC 1 /* CBC mode (for crypto engine tests) */ + /* + * CBC mode for crypto engine tests + * Default CTR mode not supported on Linux cryptodev (API to hardware crypto engines) + * Not officially support nor interoperable with any haicrypt peer + */ +#ifdef HAICRYPT_USE_OPENSSL_EVP_CBC + HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CBC(void); +#endif /* HAICRYPT_USE_OPENSSL_EVP_CBC */ + +#endif /* HAICRYPT_USE_OPENSSL_EVP */ + + +/* HaiCrypt-TP CTR mode IV (128-bit): + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0s | pki | ctr | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * XOR + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | nonce + + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * pki (32-bit): packet index + * ctr (16-bit): block counter + * nonce (112-bit): number used once (salt) + */ +#define hcrypt_SetCtrIV(pki, nonce, iv) do { \ + memset(&(iv)[0], 0, 128/8); \ + memcpy(&(iv)[10], (pki), HCRYPT_PKI_SZ); \ + hcrypt_XorStream(&(iv)[0], (nonce), 112/8); \ + } while(0) + +#define hcrypt_XorStream(dst, strm, len) do { \ + int __XORSTREAMi; \ + for (__XORSTREAMi = 0 \ + ;__XORSTREAMi < (int)(len) \ + ;__XORSTREAMi += 1) { \ + (dst)[__XORSTREAMi] ^= (strm)[__XORSTREAMi]; \ + } \ + } while(0) + + +int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, 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_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx); +int hcryptCtx_Tx_Refresh(hcrypt_Session *crypto); +int hcryptCtx_Tx_PreSwitch(hcrypt_Session *crypto); +int hcryptCtx_Tx_Switch(hcrypt_Session *crypto); +int hcryptCtx_Tx_PostSwitch(hcrypt_Session *crypto); +int hcryptCtx_Tx_AsmKM(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *alt_sek); +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_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 new file mode 100644 index 000000000..8546700a7 --- /dev/null +++ b/haicrypt/hcrypt_ctx_rx.c @@ -0,0 +1,207 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include /* memcpy */ +#include "hcrypt.h" + +int hcryptCtx_Rx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, 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)) { + return(-1); + } + return(0); +} + +int hcryptCtx_Rx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *sek, size_t sek_len) +{ + if (crypto->cipher->setkey(crypto->cipher_data, ctx, sek, sek_len)) { + HCRYPT_LOG(LOG_ERR, "cipher setkey[%d](sek) failed\n", hcryptCtx_GetKeyIndex(ctx)); + return(-1); + } + memcpy(ctx->sek, sek, sek_len); + ctx->sek_len = sek_len; + + HCRYPT_LOG(LOG_INFO, "updated context[%d]\n", hcryptCtx_GetKeyIndex(ctx)); + HCRYPT_PRINTKEY(ctx->sek, ctx->sek_len, "sek"); + ctx->status = HCRYPT_CTX_S_KEYED; + return(0); +} + +/* Parse Keying Material message */ +int hcryptCtx_Rx_ParseKM(hcrypt_Session *crypto, unsigned char *km_msg, size_t msg_len) +{ + size_t sek_len, salt_len; + unsigned char seks[HAICRYPT_KEY_MAX_SZ * 2]; + int sek_cnt; + size_t kek_len = 0; + hcrypt_Ctx *ctx; + int do_pbkdf = 0; + + if (NULL == crypto) { + HCRYPT_LOG(LOG_INFO, "%s", "Invalid params\n"); + return(-1); + } + + /* Validate message content */ + { + if (msg_len <= HCRYPT_MSG_KM_OFS_SALT) { + HCRYPT_LOG(LOG_WARNING, "KMmsg length too small (%zd)\n", msg_len); + return(-1); + } + salt_len = hcryptMsg_KM_GetSaltLen(km_msg); + sek_len = hcryptMsg_KM_GetSekLen(km_msg); + + if ((salt_len > HAICRYPT_SALT_SZ) + || (sek_len > HAICRYPT_KEY_MAX_SZ)) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported salt/key length\n"); + return(-1); + } + if ((16 != sek_len) + && (24 != sek_len) + && (32 != sek_len)) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported key length\n"); + return(-1); + } + if (hcryptMsg_KM_HasBothSek(km_msg)) { + sek_cnt = 2; + } else { + sek_cnt = 1; + } + if (msg_len != (HCRYPT_MSG_KM_OFS_SALT + salt_len + (sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ)) { + HCRYPT_LOG(LOG_WARNING, "KMmsg length inconsistent (%zd,%zd,%zd)\n", + salt_len, sek_len, msg_len); + return(-1); + } + + /* Check options support */ + if ((HCRYPT_CIPHER_AES_CTR != km_msg[HCRYPT_MSG_KM_OFS_CIPHER]) + || (HCRYPT_AUTH_NONE != km_msg[HCRYPT_MSG_KM_OFS_AUTH])) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg unsupported option\n"); + return(-1); + } + + if (crypto->se != km_msg[HCRYPT_MSG_KM_OFS_SE]) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg invalid SE\n"); + return(-1); + } + + /* Check KEKI here and pick right key */ + //>>todo + /* + * We support no key exchange, + * KEK is preshared or derived from a passphrase + */ + } + + /* Pick the context updated by this KMmsg */ + if (hcryptMsg_KM_HasBothSek(km_msg) && (NULL != crypto->ctx)) { + ctx = crypto->ctx->alt; /* 2 SEK KM, start with inactive ctx */ + } else { + ctx = &crypto->ctx_pair[hcryptMsg_KM_GetKeyIndex(km_msg)]; + } + if (NULL == ctx) { + HCRYPT_LOG(LOG_WARNING, "%s", "KMmsg invalid flags (no SEK)\n"); + return(-1); + } + + /* Check Salt and get if new */ + if ((salt_len != ctx->salt_len) + || (0 != memcmp(ctx->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len))) { + /* Salt changed (or 1st KMmsg received) */ + memcpy(ctx->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len); + ctx->salt_len = salt_len; + do_pbkdf = 1; /* Impact on password derived kek */ + } + + /* Check SEK length and get if new */ + if (sek_len != ctx->sek_len) { + /* Key length changed or 1st KMmsg received */ + ctx->sek_len = sek_len; + do_pbkdf = 1; /* Impact on password derived kek */ + } + + /* + * Regenerate KEK if it is password derived + * and Salt or SEK length changed + */ + if (ctx->cfg.pwd_len && do_pbkdf) { + if (hcryptCtx_GenSecret(crypto, ctx)) { + return(-1); + } + ctx->status = HCRYPT_CTX_S_SARDY; + kek_len = sek_len; /* KEK changed */ + } + + /* Unwrap SEK(s) and set in context */ + if (0 > hcrypt_UnwrapKey(&ctx->aes_kek, seks, + &km_msg[HCRYPT_MSG_KM_OFS_SALT + salt_len], + (sek_cnt * sek_len) + HAICRYPT_WRAPKEY_SIGN_SZ)) { + 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 + */ + hcryptCtx_Rx_Rekey(crypto, ctx, + ((2 == sek_cnt) && (ctx->flags & HCRYPT_MSG_F_oSEK)) ? &seks[sek_len] : &seks[0], + sek_len); + + /* + * Refresh KMmsg cache to detect Keying Material changes + */ + ctx->KMmsg_len = msg_len; + memcpy(ctx->KMmsg_cache, km_msg, msg_len); + + /* update other (alternate) context if both SEK provided */ + if (2 == sek_cnt) { + hcrypt_Ctx *alt = ctx->alt; + + memcpy(alt->salt, &km_msg[HCRYPT_MSG_KM_OFS_SALT], salt_len); + alt->salt_len = salt_len; + + if (kek_len) { /* New or changed KEK */ + memcpy(&alt->aes_kek, &ctx->aes_kek, sizeof(alt->aes_kek)); + alt->status = HCRYPT_CTX_S_SARDY; + } + + hcryptCtx_Rx_Rekey(crypto, alt, + ((2 == sek_cnt) && (alt->flags & HCRYPT_MSG_F_oSEK)) ? &seks[sek_len] : &seks[0], + sek_len); + + alt->KMmsg_len = msg_len; + memcpy(alt->KMmsg_cache, km_msg, msg_len); + } + return(0); +} diff --git a/haicrypt/hcrypt_ctx_tx.c b/haicrypt/hcrypt_ctx_tx.c new file mode 100644 index 000000000..879ab3295 --- /dev/null +++ b/haicrypt/hcrypt_ctx_tx.c @@ -0,0 +1,360 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include /* memcpy */ +#ifdef WIN32 + #include + #include + #include +#else + #include +#endif +#include "hcrypt.h" + +int hcryptCtx_Tx_Init(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Cfg *cfg) +{ + ctx->cfg.key_len = cfg->key_len; + + 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)) { + return(-1); + } + return(0); +} + +int hcryptCtx_Tx_Rekey(hcrypt_Session *crypto, hcrypt_Ctx *ctx) +{ + int iret; + + ASSERT(HCRYPT_CTX_S_SARDY <= ctx->status); + + /* Generate Salt */ + ctx->salt_len = HAICRYPT_SALT_SZ; + if (0 > (iret = hcrypt_Prng(ctx->salt, ctx->salt_len))) { + HCRYPT_LOG(LOG_ERR, "PRNG(salt[%zd]) failed\n", ctx->salt_len); + return(iret); + } + + /* Generate SEK */ + ctx->sek_len = ctx->cfg.key_len; + if (0 > (iret = hcrypt_Prng(ctx->sek, ctx->sek_len))) { + HCRYPT_LOG(LOG_ERR, "PRNG(sek[%zd] failed\n", ctx->sek_len); + return(iret); + } + + /* Set SEK in cipher */ + if (crypto->cipher->setkey(crypto->cipher_data, ctx, ctx->sek, ctx->sek_len)) { + HCRYPT_LOG(LOG_ERR, "cipher setkey(sek[%zd]) failed\n", ctx->sek_len); + return(-1); + } + + HCRYPT_LOG(LOG_NOTICE, "rekeyed crypto context[%d]\n", (ctx->flags & HCRYPT_CTX_F_xSEK)/2); + HCRYPT_PRINTKEY(ctx->sek, ctx->sek_len, "sek"); + + /* Regenerate KEK if Password-based (uses newly generated salt and sek_len) */ + if ((0 < ctx->cfg.pwd_len) + && (0 > (iret = hcryptCtx_GenSecret(crypto, ctx)))) { + return(iret); + } + + /* Assemble the new Keying Material message */ + if (0 != (iret = hcryptCtx_Tx_AsmKM(crypto, ctx, NULL))) { + return(iret); + } + if ((HCRYPT_CTX_S_KEYED <= ctx->alt->status) + && hcryptMsg_KM_HasBothSek(ctx->alt->KMmsg_cache)) { + /* + * previous context KM announced in alternate (odd/even) KM, + * reassemble it without our KM + */ + hcryptCtx_Tx_AsmKM(crypto, ctx->alt, NULL); + } + + /* Initialize the Media Stream message prefix cache */ + ctx->msg_info->resetCache(ctx->MSpfx_cache, HCRYPT_MSG_PT_MS, ctx->flags & HCRYPT_CTX_F_xSEK); + ctx->pkt_cnt = 1; + + ctx->status = HCRYPT_CTX_S_KEYED; + return(0); +} + +/* + * Refresh the alternate context from the current. + * Regenerates the SEK but keep the salt, doing so also + * preserve the KEK generated from secret password and salt. + */ + +int hcryptCtx_Tx_Refresh(hcrypt_Session *crypto) +{ + hcrypt_Ctx *ctx = crypto->ctx; + hcrypt_Ctx *new_ctx; + int iret; + + ASSERT(NULL != ctx); + ASSERT(HCRYPT_CTX_S_ACTIVE == ctx->status); + + /* Pick the alternative (inactive) context */ + new_ctx = ctx->alt; + + ASSERT(HCRYPT_CTX_S_SARDY <= new_ctx->status); + + /* Keep same KEK, configuration, and salt */ + memcpy(&new_ctx->aes_kek, &ctx->aes_kek, sizeof(new_ctx->aes_kek)); + memcpy(&new_ctx->cfg, &ctx->cfg, sizeof(new_ctx->cfg)); + + new_ctx->salt_len = ctx->salt_len; + memcpy(new_ctx->salt, ctx->salt, HAICRYPT_SALT_SZ); + + /* Generate new SEK */ + new_ctx->sek_len = new_ctx->cfg.key_len; + if (0 > hcrypt_Prng(new_ctx->sek, new_ctx->sek_len)) { + HCRYPT_LOG(LOG_ERR, "PRNG(sek[%zd] failed\n", new_ctx->sek_len); + return(-1); + } + /* Cipher's dependent key */ + if (crypto->cipher->setkey(crypto->cipher_data, new_ctx, new_ctx->sek, new_ctx->sek_len)) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher setkey(sek) failed\n"); + return(-1); + } + + HCRYPT_PRINTKEY(new_ctx->sek, new_ctx->sek_len, "sek"); + + /* Assemble the new KMmsg with new and current SEK */ + if (0 != (iret = hcryptCtx_Tx_AsmKM(crypto, new_ctx, ctx->sek))) { + return(iret); + } + + /* Initialize the message prefix cache */ + new_ctx->msg_info->resetCache(new_ctx->MSpfx_cache, HCRYPT_MSG_PT_MS, new_ctx->flags & HCRYPT_MSG_F_xSEK); + new_ctx->pkt_cnt = 0; + + new_ctx->status = HCRYPT_CTX_S_KEYED; + return(0); +} + +/* + * Prepare context switch + * both odd & even keys announced + */ +int hcryptCtx_Tx_PreSwitch(hcrypt_Session *crypto) +{ + hcrypt_Ctx *ctx = crypto->ctx; + + ASSERT(NULL != ctx); + ASSERT(HCRYPT_CTX_S_ACTIVE == ctx->status); + ASSERT(HCRYPT_CTX_S_KEYED == ctx->alt->status); + + ctx->alt->flags |= HCRYPT_CTX_F_ANNOUNCE; + ctx->alt->flags |= HCRYPT_CTX_F_TTSEND; //Send now + + /* Stop announcing current context if next one contains its key */ + if (hcryptMsg_KM_HasBothSek(ctx->alt->KMmsg_cache)) { + ctx->flags &= ~HCRYPT_CTX_F_ANNOUNCE; + } + return(0); +} + +int hcryptCtx_Tx_Switch(hcrypt_Session *crypto) +{ + hcrypt_Ctx *ctx = crypto->ctx; + + ASSERT(HCRYPT_CTX_S_KEYED <= ctx->alt->status); + + ctx->status = HCRYPT_CTX_S_DEPRECATED; + ctx->alt->status = HCRYPT_CTX_S_ACTIVE; + + ctx->alt->flags |= HCRYPT_CTX_F_ANNOUNCE; // Already cleared if new KM has both SEK + crypto->ctx = ctx->alt; + return(0); +} + +int hcryptCtx_Tx_PostSwitch(hcrypt_Session *crypto) +{ + hcrypt_Ctx *ctx = crypto->ctx; + hcrypt_Ctx *old_ctx = ctx->alt; + + /* Stop announcing old context (if announced) */ + old_ctx->flags &= ~HCRYPT_CTX_F_ANNOUNCE; + old_ctx->status = HCRYPT_CTX_S_SARDY; + + /* If current context KM announce both, reassemble it */ + if (hcryptMsg_KM_HasBothSek(ctx->KMmsg_cache)) { + hcryptCtx_Tx_AsmKM(crypto, ctx, NULL); + } + return(0); +} + +/* Assemble Keying Material message */ +int hcryptCtx_Tx_AsmKM(hcrypt_Session *crypto, hcrypt_Ctx *ctx, unsigned char *alt_sek) +{ + unsigned char *km_msg; + size_t msg_len; + int sek_cnt = (NULL == alt_sek ? 1 : 2); + unsigned char sek_buf[HAICRYPT_KEY_MAX_SZ * 2]; + unsigned char *seks; + + if (NULL == ctx) { + HCRYPT_LOG(LOG_ERR, "%s", "crypto context undefined\n"); + return(-1); + } + + msg_len = HCRYPT_MSG_KM_OFS_SALT + + ctx->salt_len + + (ctx->sek_len * sek_cnt) + + HAICRYPT_WRAPKEY_SIGN_SZ; + + km_msg = &ctx->KMmsg_cache[0]; + ctx->KMmsg_len = 0; + + memset(km_msg, 0, msg_len); + ctx->msg_info->resetCache(km_msg, HCRYPT_MSG_PT_KM, + 2 == sek_cnt ? HCRYPT_MSG_F_xSEK : (ctx->flags & HCRYPT_MSG_F_xSEK)); + + /* crypto->KMmsg_cache[4..7]: KEKI=0 */ + km_msg[HCRYPT_MSG_KM_OFS_CIPHER] = HCRYPT_CIPHER_AES_CTR; + km_msg[HCRYPT_MSG_KM_OFS_AUTH] = HCRYPT_AUTH_NONE; + km_msg[HCRYPT_MSG_KM_OFS_SE] = crypto->se; + hcryptMsg_KM_SetSaltLen(km_msg, ctx->salt_len); + hcryptMsg_KM_SetSekLen(km_msg, ctx->sek_len); + + memcpy(&km_msg[HCRYPT_MSG_KM_OFS_SALT], ctx->salt, ctx->salt_len); + + if (2 == sek_cnt) { + /* Even SEK first in dual SEK KMmsg */ + if (HCRYPT_MSG_F_eSEK & ctx->flags) { + memcpy(&sek_buf[0], ctx->sek, ctx->sek_len); + memcpy(&sek_buf[ctx->sek_len], alt_sek, ctx->sek_len); + } else { + memcpy(&sek_buf[0], alt_sek, ctx->sek_len); + memcpy(&sek_buf[ctx->sek_len], ctx->sek, ctx->sek_len); + } + seks = sek_buf; + } else { + seks = ctx->sek; + } + if (0 > hcrypt_WrapKey(&ctx->aes_kek, + &km_msg[HCRYPT_MSG_KM_OFS_SALT + ctx->salt_len], + seks, sek_cnt * ctx->sek_len)) { + + HCRYPT_LOG(LOG_ERR, "%s", "wrap key failed\n"); + return(-1); + } + ctx->KMmsg_len = msg_len; + return(0); +} + +int hcryptCtx_Tx_ManageKM(hcrypt_Session *crypto) +{ + hcrypt_Ctx *ctx = crypto->ctx; + + ASSERT(NULL != ctx); + + if ((ctx->pkt_cnt > crypto->km.refresh_rate) + || (ctx->pkt_cnt == 0)) { //rolled over + /* + * End of crypto period for current SEK, + * switch to other (even/odd) SEK + */ + HCRYPT_LOG(LOG_INFO, "KM[%d] Activated\n", + (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2); + + hcryptCtx_Tx_Switch(crypto); + + } else + if ((ctx->pkt_cnt > (crypto->km.refresh_rate - crypto->km.pre_announce)) + && !(ctx->alt->flags & HCRYPT_CTX_F_ANNOUNCE)) { + /* + * End of crypto period approach for this SEK, + * prepare next SEK for announcement + */ + hcryptCtx_Tx_Refresh(crypto); + + HCRYPT_LOG(LOG_INFO, "KM[%d] Pre-announced\n", + (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2); + + hcryptCtx_Tx_PreSwitch(crypto); + + } else + if ((ctx->alt->status == HCRYPT_CTX_S_DEPRECATED) + && (ctx->pkt_cnt > crypto->km.pre_announce)) { + /* + * Deprecated SEK is no longer needed (for late packets), + * decommission it + */ + HCRYPT_LOG(LOG_INFO, "KM[%d] Deprecated\n", + (ctx->alt->flags & HCRYPT_CTX_F_xSEK)/2); + + hcryptCtx_Tx_PostSwitch(crypto); + } + + /* Check if it is time to send Keying Material */ + if (timerisset(&crypto->km.tx_period)) { /* tx_period=0.0 -> out-of-stream Keying Material distribution */ + struct timeval now, nxt_tx; + + gettimeofday(&now, NULL); + timeradd(&crypto->km.tx_last, &crypto->km.tx_period, &nxt_tx); + if (timercmp(&now, &nxt_tx, >)) { + if (crypto->ctx_pair[0].flags & HCRYPT_CTX_F_ANNOUNCE) crypto->ctx_pair[0].flags |= HCRYPT_CTX_F_TTSEND; + if (crypto->ctx_pair[1].flags & HCRYPT_CTX_F_ANNOUNCE) crypto->ctx_pair[1].flags |= HCRYPT_CTX_F_TTSEND; + } + } + + return(0); +} + +int hcryptCtx_Tx_InjectKM(hcrypt_Session *crypto, + void *out_p[], size_t out_len_p[], int maxout) +{ + int i, nbout = 0; + + ASSERT(maxout >= 2); + for (i=0; i<2; i++) { + if (crypto->ctx_pair[i].flags & HCRYPT_CTX_F_TTSEND) { /* Time To Send */ + HCRYPT_LOG(LOG_DEBUG, "Send KMmsg[%d] len=%zd\n", i, + crypto->ctx_pair[i].KMmsg_len); + /* Send Keying Material */ + out_p[nbout] = crypto->ctx_pair[i].KMmsg_cache; + out_len_p[nbout] = crypto->ctx_pair[i].KMmsg_len; + nbout++; + crypto->ctx_pair[i].flags &= ~HCRYPT_CTX_F_TTSEND; + } + } + if (nbout) { + struct timeval now; + gettimeofday(&now, NULL); + crypto->km.tx_last = now; + } + return(nbout); +} + + diff --git a/haicrypt/hcrypt_rx.c b/haicrypt/hcrypt_rx.c new file mode 100644 index 000000000..ffabaca52 --- /dev/null +++ b/haicrypt/hcrypt_rx.c @@ -0,0 +1,154 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include /* NULL */ +#include /* memcmp */ +#include "hcrypt.h" + +int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, + unsigned char *in_pfx, unsigned char *data, size_t data_len) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + hcrypt_Ctx *ctx; + int nb = -1; + + if ((NULL == crypto) + || (NULL == data)) { + HCRYPT_LOG(LOG_ERR, "%s", "invalid parameters\n"); + return(nb); + } + + ctx = &crypto->ctx_pair[hcryptMsg_GetKeyIndex(crypto->msg_info, in_pfx)]; + + ASSERT(NULL != ctx); /* Header check should prevent this error */ + ASSERT(NULL != crypto->cipher); /* Header check should prevent this error */ + + crypto->ctx = ctx; /* Context of last received msg */ + if (NULL == crypto->cipher->decrypt) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher had no decryptor\n"); + } else if (ctx->status >= HCRYPT_CTX_S_KEYED) { + hcrypt_DataDesc indata; + indata.pfx = in_pfx; + indata.payload = data; + indata.len = data_len; + + if (0 > (nb = crypto->cipher->decrypt(crypto->cipher_data, ctx, &indata, 1, NULL, NULL, NULL))) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher failed\n"); + } else { + nb = indata.len; + } + } else { /* No key received yet */ + nb = 0; + } + return(nb); +} + +int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, + unsigned char *in_msg, size_t in_len, + void *out_p[], size_t out_len_p[], int maxout) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + hcrypt_Ctx *ctx; + int nbout = maxout; + int msg_type; + + if ((NULL == crypto) + || (NULL == in_msg)) { + + HCRYPT_LOG(LOG_ERR, "%s", "invalid parameters\n"); + return(-1); + } + + /* Validate HaiCrypt message */ + if (0 > (msg_type = crypto->msg_info->parseMsg(in_msg))) { + return(-1); + } + + switch(msg_type) { + case HCRYPT_MSG_PT_MS: /* MSmsg */ + ctx = &crypto->ctx_pair[hcryptMsg_GetKeyIndex(crypto->msg_info, in_msg)]; + + if ((NULL == out_p) + || (NULL == out_len_p)) { + HCRYPT_LOG(LOG_ERR, "%s", "invalid parameters\n"); + return(-1); + } + ASSERT(NULL != ctx); /* Header check should prevent this error */ + ASSERT(NULL != crypto->cipher); /* Header check should prevent this error */ + + crypto->ctx = ctx; /* Context of last received msg */ + if (NULL == crypto->cipher->decrypt) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher had no decryptor\n"); + nbout = -1; + } else if (ctx->status >= HCRYPT_CTX_S_KEYED) { + hcrypt_DataDesc indata; + indata.pfx = in_msg; + indata.payload = &in_msg[crypto->msg_info->pfx_len]; + indata.len = in_len - crypto->msg_info->pfx_len; + + if (crypto->cipher->decrypt(crypto->cipher_data, ctx, &indata, 1, out_p, out_len_p, &nbout)) { + HCRYPT_LOG(LOG_ERR, "%s", "cipher failed\n"); + nbout = -1; + } + } else { /* No key received yet */ + nbout = 0; + } + break; + + case HCRYPT_MSG_PT_KM: /* KMmsg */ + /* Even or Both SEKs check with even context */ + ctx = &crypto->ctx_pair[hcryptMsg_GetKeyIndex(crypto->msg_info, in_msg)]; + + ASSERT(NULL != ctx); /* Header check should prevent this error */ + + if ((ctx->status < HCRYPT_CTX_S_KEYED) /* No key deciphered yet */ + || (in_len != ctx->KMmsg_len) /* or not same size */ + || (0 != memcmp(ctx->KMmsg_cache, in_msg, in_len))) { /* or different */ + + nbout = hcryptCtx_Rx_ParseKM(crypto, in_msg, in_len); + //-2: unmatched shared secret + //-1: other failures + //0: success + } else { + nbout = 0; + } + if (NULL != out_p) out_p[0] = NULL; + if (NULL != out_len_p) out_len_p[0] = 0; + break; + + default: + HCRYPT_LOG(LOG_WARNING, "%s", "unknown packet type\n"); + nbout = 0; + break; + } + + return(nbout); +} + + diff --git a/haicrypt/hcrypt_sa.c b/haicrypt/hcrypt_sa.c new file mode 100644 index 000000000..cac58dceb --- /dev/null +++ b/haicrypt/hcrypt_sa.c @@ -0,0 +1,112 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. +*****************************************************************************/ + +/* + * For now: + * Pre-shared or password derived KEK (Key Encrypting Key) + * Future: + * Certificate-based association + */ +#include /* memcpy */ +#include "hcrypt.h" + +int hcryptCtx_SetSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx, HaiCrypt_Secret *secret) +{ + int iret; + (void)crypto; + + switch(secret->typ) { + case HAICRYPT_SECTYP_PRESHARED: + ASSERT(secret->len <= HAICRYPT_KEY_MAX_SZ); + ctx->cfg.pwd_len = 0; + /* KEK: Key Encrypting Key */ + if (HCRYPT_CTX_F_ENCRYPT & ctx->flags) { + iret = AES_set_encrypt_key(secret->str, secret->len * 8, &ctx->aes_kek); + } else { + iret = AES_set_decrypt_key(secret->str, secret->len * 8, &ctx->aes_kek); + } + if (0 > iret) { + HCRYPT_LOG(LOG_ERR, "AES_set_%s_key(kek[%zd]) failed (rc=%d)\n", + HCRYPT_CTX_F_ENCRYPT & ctx->flags ? "encrypt" : "decrypt", + secret->len, iret); + return(-1); + } + ctx->status = HCRYPT_CTX_S_SARDY; + break; + + case HAICRYPT_SECTYP_PASSPHRASE: + ASSERT(secret->len <= sizeof(ctx->cfg.pwd)); + memcpy(ctx->cfg.pwd, secret->str, secret->len); + ctx->cfg.pwd_len = secret->len; + /* KEK will be derived from password with Salt */ + ctx->status = HCRYPT_CTX_S_SARDY; + break; + + default: + HCRYPT_LOG(LOG_ERR, "Unknown secret type %d\n", + secret->typ); + return(-1); + } + return(0); +} + +int hcryptCtx_GenSecret(hcrypt_Session *crypto, hcrypt_Ctx *ctx) +{ + /* + * KEK need same length as the key it protects (SEK) + * KEK = PBKDF2(Pwd, LSB(64, Salt), Iter, Klen) + */ + unsigned char kek[HAICRYPT_KEY_MAX_SZ]; + size_t kek_len = ctx->sek_len; + size_t pbkdf_salt_len = (ctx->salt_len >= HAICRYPT_PBKDF2_SALT_LEN + ? HAICRYPT_PBKDF2_SALT_LEN + : ctx->salt_len); + int iret; + (void)crypto; + + PKCS5_PBKDF2_HMAC_SHA1(ctx->cfg.pwd, ctx->cfg.pwd_len, + &ctx->salt[ctx->salt_len - pbkdf_salt_len], pbkdf_salt_len, + HAICRYPT_PBKDF2_ITER_CNT, kek_len, kek); + + HCRYPT_PRINTKEY(ctx->cfg.pwd, ctx->cfg.pwd_len, "pwd"); + HCRYPT_PRINTKEY(kek, kek_len, "kek"); + + /* KEK: Key Encrypting Key */ + if (HCRYPT_CTX_F_ENCRYPT & ctx->flags) { + if (0 > (iret = AES_set_encrypt_key(kek, kek_len * 8, &ctx->aes_kek))) { + HCRYPT_LOG(LOG_ERR, "AES_set_encrypt_key(pdkek[%zd]) failed (rc=%d)\n", kek_len, iret); + return(-1); + } + } else { + if (0 > (iret = AES_set_decrypt_key(kek, kek_len * 8, &ctx->aes_kek))) { + HCRYPT_LOG(LOG_ERR, "AES_set_decrypt_key(pdkek[%zd]) failed (rc=%d)\n", kek_len, iret); + return(-1); + } + } + return(0); +} + diff --git a/haicrypt/hcrypt_tx.c b/haicrypt/hcrypt_tx.c new file mode 100644 index 000000000..d42d4cac3 --- /dev/null +++ b/haicrypt/hcrypt_tx.c @@ -0,0 +1,190 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include +#include /* NULL */ +#include /* memcpy */ +#ifdef WIN32 + #include + #include + #include +#else + #include /* htonl */ +#endif +#include "hcrypt.h" + +int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_pp) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + int pad_factor = (HCRYPT_CTX_MODE_AESECB == crypto->ctx->mode ? 128/8 : 1); + + ASSERT(NULL != crypto); + ASSERT(NULL != crypto->cipher); + + if (NULL != crypto->cipher->getinbuf) { + ASSERT(NULL != crypto->cipher_data); + if (0 >= crypto->cipher->getinbuf(crypto->cipher_data, crypto->msg_info->pfx_len, + data_len, pad_factor, in_pp)) { + *in_pp = NULL; + return(-1); + } + } else { +#ifndef WIN32 + ASSERT(crypto->inbuf != NULL); +#endif + size_t in_len = crypto->msg_info->pfx_len + hcryptMsg_PaddedLen(data_len, pad_factor); + *in_pp = crypto->inbuf; + if (in_len > crypto->inbuf_siz) { + *in_pp = NULL; + return(-1); + } + } + return(crypto->msg_info->pfx_len); +} + +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; + int nbout = 0; + + if ((NULL == crypto) + || (NULL == crypto->ctx) + || (NULL == out_p) + || (NULL == out_len_p)) { + HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + return(-1); + } + + /* Manage Key Material (refresh, announce, decommission) */ + hcryptCtx_Tx_ManageKM(crypto); + + if (NULL == (ctx = crypto->ctx)) { + HCRYPT_LOG(LOG_ERR, "%s", "crypto context not defined\n"); + return(-1); + } + ASSERT(ctx->status == HCRYPT_CTX_S_ACTIVE); + + nbout = hcryptCtx_Tx_InjectKM(crypto, out_p, out_len_p, maxout); + return(nbout); +} + +int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + hcrypt_Ctx *ctx; + + if ((NULL == crypto) + || (NULL == (ctx = crypto->ctx))){ + HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + return(-1); + } + return(hcryptCtx_GetKeyFlags(ctx)); +} + +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; + int nbout = 0; + + if ((NULL == crypto) + || (NULL == (ctx = crypto->ctx))){ + HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + return(-1); + } + /* Get/Set packet index */ + ctx->msg_info->indexMsg(in_pfx, ctx->MSpfx_cache); + + /* Encrypt */ + { + hcrypt_DataDesc indata; + indata.pfx = in_pfx; + indata.payload = in_data; + indata.len = in_len; + + if (0 > (nbout = crypto->cipher->encrypt(crypto->cipher_data, ctx, &indata, 1, NULL, NULL, NULL))) { + HCRYPT_LOG(LOG_ERR, "%s", "encrypt failed\n"); + return(nbout); + } + } + ctx->pkt_cnt++; + + return(nbout); +} + +int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, + unsigned char *in_msg, size_t in_len, + void *out_p[], size_t out_len_p[], int maxout) +{ + hcrypt_Session *crypto = (hcrypt_Session *)hhc; + hcrypt_Ctx *ctx; + int nb, nbout = 0; + + if ((NULL == crypto) + || (NULL == crypto->ctx) + || (NULL == out_p) + || (NULL == out_len_p)) { + HCRYPT_LOG(LOG_ERR, "%s", "invalid params\n"); + return(-1); + } + + /* Manage Key Material (refresh, announce, decommission) */ + hcryptCtx_Tx_ManageKM(crypto); + + if (NULL == (ctx = crypto->ctx)) { + HCRYPT_LOG(LOG_ERR, "%s", "crypto context not defined\n"); + return(-1); + } + ASSERT(ctx->status == HCRYPT_CTX_S_ACTIVE); + + nbout += hcryptCtx_Tx_InjectKM(crypto, out_p, out_len_p, maxout); + + /* Get packet index */ + ctx->msg_info->indexMsg(in_msg, ctx->MSpfx_cache); + + /* Encrypt */ + nb = maxout - nbout; + { + hcrypt_DataDesc indata; + indata.pfx = in_msg; + indata.payload = &in_msg[ctx->msg_info->pfx_len]; + indata.len = in_len - ctx->msg_info->pfx_len; + + if (crypto->cipher->encrypt(crypto->cipher_data, ctx, &indata, 1, &out_p[nbout], &out_len_p[nbout], &nb)) { + HCRYPT_LOG(LOG_ERR, "%s", "encrypt failed\n"); + return(nbout); + } + } + nbout += nb; + ctx->pkt_cnt++; + + return(nbout); +} diff --git a/haicrypt/hcrypt_ut.c b/haicrypt/hcrypt_ut.c new file mode 100644 index 000000000..2e1599b3e --- /dev/null +++ b/haicrypt/hcrypt_ut.c @@ -0,0 +1,231 @@ +/* + * 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. + + 2011-07-11 (jdube) + HaiCrypt initial implementation. +*****************************************************************************/ + +#include /* memcpy */ +#include +#include "hcrypt.h" + +#ifndef WIN32 + +/* RFC6070 PBKDF2 Tests Vectors */ + +static struct TestVector { + size_t pwd_len; + const char *pwd; + size_t salt_len; + const unsigned char *salt; + int cnt; + size_t dk_len; + unsigned char dk[32]; +} tv[] = { + { /* 1 */ + .pwd_len = 8, .pwd = "password", + .salt_len = 4, .salt = (unsigned char *)"salt", + .cnt = 1, + .dk_len = 20, + .dk = { + 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, + 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06, + 0x2f, 0xe0, 0x37, 0xa6 + } + }, + { /* 2 */ + .pwd_len = 8, .pwd = "password", + .salt_len = 4, .salt = (unsigned char *)"salt", + .cnt = 2, + .dk_len = 20, + .dk = { + 0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, + 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0, + 0xd8, 0xde, 0x89, 0x57 + } + }, + { /* 3 */ + .pwd_len = 8, .pwd = "password", + .salt_len = 4, .salt = (unsigned char *)"salt", + .cnt = 4096, + .dk_len = 20, + .dk = { + 0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a, + 0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0, + 0x65, 0xa4, 0x29, 0xc1 + } + }, + { /* 4 */ + .pwd_len = 8, .pwd = "password", + .salt_len = 4, .salt = (unsigned char *)"salt", + .cnt = 16777216, + .dk_len = 20, + .dk = { + 0xee, 0xfe, 0x3d, 0x61, 0xcd, 0x4d, 0xa4, 0xe4, + 0xe9, 0x94, 0x5b, 0x3d, 0x6b, 0xa2, 0x15, 0x8c, + 0x26, 0x34, 0xe9, 0x84 + } + }, + { /* 5 */ + .pwd_len = 24, .pwd = "passwordPASSWORDpassword", + .salt_len = 36, .salt = (unsigned char *)"saltSALTsaltSALTsaltSALTsaltSALTsalt", + .cnt = 4096, + .dk_len = 25, + .dk = { + 0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b, + 0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a, + 0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70, + 0x38 + } + }, + { /* 6 */ + .pwd_len = 9, .pwd = "pass\0word", + .salt_len = 5, .salt = (unsigned char *)"sa\0lt", + .cnt = 4096, + .dk_len = 16, + .dk = { + 0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d, + 0xcc, 0x37, 0xd7, 0xf0, 0x34, 0x25, 0xe0, 0xc3 + } + }, +}; + +#include + +static int hc_ut_pbkdf2(unsigned verbose) +{ + int i; + int nbt = sizeof(tv)/sizeof(tv[0]); + int nbe = 0; + unsigned char dk[32]; + struct timeval tstart, tstop, tdiff; + + for (i=0; i HaiCrypt_Tx_Data(hcrypto, &pkt[0], &pkt[16], UT_PKTSZ)) nbe++; + if (0 == (i % 1000)) { + printf("\b\b\b\b\b\b%6d", i); + fflush(stdout); + } + } + gettimeofday(&tstop, NULL); + timersub(&tstop, &tstart, &tdiff); + printf("\nhaicrypt: encrypted %ld packets in %lu.%06lu sec (%ld.%03ld kbps)\n", + UT_NBPKTS, tdiff.tv_sec, (unsigned long)tdiff.tv_usec, + (((UT_NBPKTS * UT_PKTSZ*10)/((tdiff.tv_sec*10) + (tdiff.tv_usec/100))) / 1000), + (((UT_NBPKTS * UT_PKTSZ*10)/((tdiff.tv_sec*10) + (tdiff.tv_usec/100))) % 1000)); + + HaiCrypt_Close(hcrypto); + return(nbe); +} + +int main(int argc, char *argv[]) +{ + + int nbe = 0; + (void)argc; + (void)argv; + nbe += hc_ut_encrypt_ctr_speed(); + nbe += hc_ut_pbkdf2(1); + + printf("haicrypt unit test %s: %d errors found\n", nbe ? "failed" : "passed", nbe); + return(nbe); +} + +#endif // WIN32 diff --git a/haicrypt/hcrypt_xpt_srt.c b/haicrypt/hcrypt_xpt_srt.c new file mode 100644 index 000000000..f9c98c097 --- /dev/null +++ b/haicrypt/hcrypt_xpt_srt.c @@ -0,0 +1,179 @@ +/* + * 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. + + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include /* memset, memcpy */ +#ifdef WIN32 + #include + #include +#else + #include /* htonl, ntohl */ +#endif +#include "hcrypt.h" + +/* + * HaiCrypt SRT (Secure Reliable Transport) Media Stream (MS) Msg Prefix: + * This is UDT data header with Crypto Key Flags (KF) added. + * Header is in 32bit host order words in the context of the functions of this handler. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x00 |0| Packet Sequence Number (pki) | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x04 |FF |o|KF | Message Number | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x08 | Time Stamp | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x0C | Destination Socket ID) | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * | Payload... | + */ + + +/* + * HaiCrypt Standalone Transport Keying Material (KM) Msg header kept in SRT + * Message and cache maintained in network order + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x00 |0|Vers | PT | Sign | resv | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * ... . + */ + +#define HCRYPT_MSG_SRT_HDR_SZ 16 +#define HCRYPT_MSG_SRT_PFX_SZ 16 + +#define HCRYPT_MSG_SRT_OFS_PKI 0 +#define HCRYPT_MSG_SRT_OFS_MSGNO 4 +#define HCRYPT_MSG_SRT_SHF_KFLGS 27 //shift + +static hcrypt_MsgInfo _hcMsg_SRT_MsgInfo; + +static unsigned hcryptMsg_SRT_GetKeyFlags(unsigned char *msg) +{ + uint32_t msgno; + memcpy(&msgno, &msg[HCRYPT_MSG_SRT_OFS_MSGNO], sizeof(msgno)); //header is in host order + return((unsigned)((msgno >> HCRYPT_MSG_SRT_SHF_KFLGS) & HCRYPT_MSG_F_xSEK)); +} + +static hcrypt_Pki hcryptMsg_SRT_GetPki(unsigned char *msg, int nwkorder) +{ + hcrypt_Pki pki; + memcpy(&pki, &msg[HCRYPT_MSG_SRT_OFS_PKI], sizeof(pki)); //header is in host order + return (nwkorder ? htonl(pki) : pki); +} + +static void hcryptMsg_SRT_SetPki(unsigned char *msg, hcrypt_Pki pki) +{ + memcpy(&msg[HCRYPT_MSG_SRT_OFS_PKI], &pki, sizeof(pki)); //header is in host order +} + +static void hcryptMsg_SRT_ResetCache(unsigned char *pfx_cache, unsigned pkt_type, unsigned kflgs) +{ + switch(pkt_type) { + case HCRYPT_MSG_PT_MS: /* Media Stream */ + /* Nothing to do, header filled by protocol */ + break; + case HCRYPT_MSG_PT_KM: /* Keying Material */ + pfx_cache[HCRYPT_MSG_KM_OFS_VERSION] = (unsigned char)((HCRYPT_MSG_VERSION << 4) | pkt_type); // version || PT + pfx_cache[HCRYPT_MSG_KM_OFS_SIGN] = (unsigned char)((HCRYPT_MSG_SIGN >> 8) & 0xFF); // Haivision PnP Mfr ID + pfx_cache[HCRYPT_MSG_KM_OFS_SIGN+1] = (unsigned char)(HCRYPT_MSG_SIGN & 0xFF); + pfx_cache[HCRYPT_MSG_KM_OFS_KFLGS] = (unsigned char)kflgs; //HCRYPT_MSG_F_xxx + break; + default: + break; + } +} + +static void hcryptMsg_SRT_IndexMsg(unsigned char *msg, unsigned char *pfx_cache) +{ + (void)msg; + (void)pfx_cache; + return; //nothing to do, header and index maintained by SRT +} + +static int hcryptMsg_SRT_ParseMsg(unsigned char *msg) +{ + int rc; + + if ((HCRYPT_MSG_VERSION == hcryptMsg_KM_GetVersion(msg)) /* Version 1 */ + && (HCRYPT_MSG_PT_KM == hcryptMsg_KM_GetPktType(msg)) /* Keying Material */ + && (HCRYPT_MSG_SIGN == hcryptMsg_KM_GetSign(msg))) { /* 'HAI' PnP Mfr ID */ + rc = HCRYPT_MSG_PT_KM; + } else { + //Assume it's data. + //SRT does not call this for MS msg + rc = HCRYPT_MSG_PT_MS; + } + + switch(rc) { + case HCRYPT_MSG_PT_MS: + if (hcryptMsg_HasNoSek(&_hcMsg_SRT_MsgInfo, msg) + || hcryptMsg_HasBothSek(&_hcMsg_SRT_MsgInfo, msg)) { + HCRYPT_LOG(LOG_ERR, "invalid MS msg flgs: %02x\n", + hcryptMsg_GetKeyIndex(&_hcMsg_SRT_MsgInfo, msg)); + return(-1); + } + break; + case HCRYPT_MSG_PT_KM: + if (HCRYPT_SE_TSSRT != hcryptMsg_KM_GetSE(msg)) { //Check Stream Encapsulation (SE) + HCRYPT_LOG(LOG_ERR, "invalid KM msg SE: %d\n", + hcryptMsg_KM_GetSE(msg)); + return(-1); + } + if (hcryptMsg_KM_HasNoSek(msg)) { + HCRYPT_LOG(LOG_ERR, "invalid KM msg flgs: %02x\n", + hcryptMsg_KM_GetKeyIndex(msg)); + return(-1); + } + break; + default: + HCRYPT_LOG(LOG_ERR, "invalid pkt type: %d\n", rc); + rc = 0; /* unknown packet type */ + break; + } + return(rc); /* -1: error, 0: unknown: >0: PT */ +} + +static hcrypt_MsgInfo _hcMsg_SRT_MsgInfo; + +hcrypt_MsgInfo *hcryptMsg_SRT_MsgInfo(void) +{ + _hcMsg_SRT_MsgInfo.hdr_len = HCRYPT_MSG_SRT_HDR_SZ; + _hcMsg_SRT_MsgInfo.pfx_len = HCRYPT_MSG_SRT_PFX_SZ; + _hcMsg_SRT_MsgInfo.getKeyFlags = hcryptMsg_SRT_GetKeyFlags; + _hcMsg_SRT_MsgInfo.getPki = hcryptMsg_SRT_GetPki; + _hcMsg_SRT_MsgInfo.setPki = hcryptMsg_SRT_SetPki; + _hcMsg_SRT_MsgInfo.resetCache = hcryptMsg_SRT_ResetCache; + _hcMsg_SRT_MsgInfo.indexMsg = hcryptMsg_SRT_IndexMsg; + _hcMsg_SRT_MsgInfo.parseMsg = hcryptMsg_SRT_ParseMsg; + + return(&_hcMsg_SRT_MsgInfo); +} + diff --git a/haicrypt/hcrypt_xpt_sta.c b/haicrypt/hcrypt_xpt_sta.c new file mode 100644 index 000000000..fcfaa4b53 --- /dev/null +++ b/haicrypt/hcrypt_xpt_sta.c @@ -0,0 +1,187 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#include /* memset, memcpy */ +#ifdef WIN32 + #include + #include +#else + #include /* htonl, ntohl */ +#endif +#include "hcrypt.h" + +/* + * HaiCrypt Standalone Transport Media Stream (MS) Data Msg Prefix: + * Cache maintained in network order + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x00 |0|Vers | PT | Sign | resv |KF | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x04 | pki | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * | payload... | + */ + +/* + * HaiCrypt Standalone Transport Keying Material (KM) Msg (no prefix, use KM Msg directly): + * Cache maintained in network order + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * 0x00 |0|Vers | PT | Sign | resv | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * ... . + */ + +#define HCRYPT_MSG_STA_HDR_SZ 4 +#define HCRYPT_MSG_STA_PKI_SZ 4 +#define HCRYPT_MSG_STA_PFX_SZ (HCRYPT_MSG_STA_HDR_SZ + HCRYPT_MSG_STA_PKI_SZ) + +#define HCRYPT_MSG_STA_OFS_VERSION HCRYPT_MSG_KM_OFS_VERSION +#define HCRYPT_MSG_STA_OFS_PT HCRYPT_MSG_KM_OFS_PT +#define HCRYPT_MSG_STA_OFS_SIGN HCRYPT_MSG_KM_OFS_SIGN +#define HCRYPT_MSG_STA_OFS_KFLGS HCRYPT_MSG_KM_OFS_KFLGS + +#define HCRYPT_MSG_STA_OFS_PKI HCRYPT_MSG_STA_HDR_SZ + +#define hcryptMsg_STA_GetVersion(msg) (((msg)[HCRYPT_MSG_STA_OFS_VERSION]>>4)& 0xF) +#define hcryptMsg_STA_GetPktType(msg) (((msg)[HCRYPT_MSG_STA_OFS_PT]) & 0xF) +#define hcryptMsg_STA_GetSign(msg) (((msg)[HCRYPT_MSG_STA_OFS_SIGN]<<8) | (msg)[HCRYPT_MSG_STA_OFS_SIGN+1]) + +static hcrypt_MsgInfo _hcMsg_STA_MsgInfo; + +static unsigned hcryptMsg_STA_GetKeyFlags(unsigned char *msg) +{ + return((unsigned)(msg[HCRYPT_MSG_STA_OFS_KFLGS] & HCRYPT_MSG_F_xSEK)); +} + +static hcrypt_Pki hcryptMsg_STA_GetPki(unsigned char *msg, int nwkorder) +{ + hcrypt_Pki pki; + memcpy(&pki, &msg[HCRYPT_MSG_STA_OFS_PKI], sizeof(pki)); //header is in host order + return (nwkorder ? pki : ntohl(pki)); +} + +static void hcryptMsg_STA_SetPki(unsigned char *msg, hcrypt_Pki pki) +{ + hcrypt_Pki nwk_pki = htonl(pki); + memcpy(&msg[HCRYPT_MSG_STA_OFS_PKI], &nwk_pki, sizeof(nwk_pki)); //header is in host order +} + +static void hcryptMsg_STA_ResetCache(unsigned char *pfx_cache, unsigned pkt_type, unsigned kflgs) +{ + pfx_cache[HCRYPT_MSG_STA_OFS_VERSION] = (unsigned char)((HCRYPT_MSG_VERSION << 4) | pkt_type); // version || PT + pfx_cache[HCRYPT_MSG_STA_OFS_SIGN] = (unsigned char)((HCRYPT_MSG_SIGN >> 8) & 0xFF); // Haivision PnP Mfr ID + pfx_cache[HCRYPT_MSG_STA_OFS_SIGN+1] = (unsigned char)(HCRYPT_MSG_SIGN & 0xFF); + + switch(pkt_type) { + case HCRYPT_MSG_PT_MS: + pfx_cache[HCRYPT_MSG_STA_OFS_KFLGS] = (unsigned char)kflgs; //HCRYPT_MSG_F_xxx + hcryptMsg_STA_SetPki(pfx_cache, 0); + break; + case HCRYPT_MSG_PT_KM: + pfx_cache[HCRYPT_MSG_KM_OFS_KFLGS] = (unsigned char)kflgs; //HCRYPT_MSG_F_xxx + break; + default: + break; + } +} + +static void hcryptMsg_STA_IndexMsg(unsigned char *msg, unsigned char *pfx_cache) +{ + hcrypt_Pki pki = hcryptMsg_STA_GetPki(pfx_cache, 0); //Get in host order + memcpy(msg, pfx_cache, HCRYPT_MSG_STA_PFX_SZ); + hcryptMsg_SetPki(&_hcMsg_STA_MsgInfo, pfx_cache, ++pki); +} + +static time_t _tLastLogTime = 0; + +static int hcryptMsg_STA_ParseMsg(unsigned char *msg) +{ + int rc; + + if ((HCRYPT_MSG_VERSION != hcryptMsg_STA_GetVersion(msg)) /* Version 1 */ + || (HCRYPT_MSG_SIGN != hcryptMsg_STA_GetSign(msg))) { /* 'HAI' PnP Mfr ID */ + time_t tCurrentTime = time(NULL); + // invalid data + if ((tCurrentTime - _tLastLogTime) >= 2 || (0 == _tLastLogTime)) + { + _tLastLogTime = tCurrentTime; + HCRYPT_LOG(LOG_ERR, "invalid msg hdr: 0x%02x %02x%02x %02x\n", + msg[0], msg[1], msg[2], msg[3]); + } + return(-1); /* Invalid packet */ + } + rc = hcryptMsg_STA_GetPktType(msg); + switch(rc) { + case HCRYPT_MSG_PT_MS: + if (hcryptMsg_HasNoSek(&_hcMsg_STA_MsgInfo, msg) + || hcryptMsg_HasBothSek(&_hcMsg_STA_MsgInfo, msg)) { + HCRYPT_LOG(LOG_ERR, "invalid MS msg flgs: %02x\n", + hcryptMsg_GetKeyIndex(&_hcMsg_STA_MsgInfo, msg)); + return(-1); + } + break; + case HCRYPT_MSG_PT_KM: + if (HCRYPT_SE_TSUDP != hcryptMsg_KM_GetSE(msg)) { + HCRYPT_LOG(LOG_ERR, "invalid KM msg SE: %d\n", + hcryptMsg_KM_GetSE(msg)); + } else if (hcryptMsg_KM_HasNoSek(msg)) { + HCRYPT_LOG(LOG_ERR, "invalid KM msg flgs: %02x\n", + hcryptMsg_KM_GetKeyIndex(msg)); + return(-1); + } + break; + default: + HCRYPT_LOG(LOG_ERR, "invalid pkt type: %d\n", rc); + rc = 0; /* unknown packet type */ + break; + } + return(rc); /* -1: error, 0: unknown: >0: PT */ +} + +static hcrypt_MsgInfo _hcMsg_STA_MsgInfo; + +hcrypt_MsgInfo *hcryptMsg_STA_MsgInfo(void) +{ + _hcMsg_STA_MsgInfo.hdr_len = HCRYPT_MSG_STA_HDR_SZ; + _hcMsg_STA_MsgInfo.pfx_len = HCRYPT_MSG_STA_PFX_SZ; + _hcMsg_STA_MsgInfo.getKeyFlags = hcryptMsg_STA_GetKeyFlags; + _hcMsg_STA_MsgInfo.getPki = hcryptMsg_STA_GetPki; + _hcMsg_STA_MsgInfo.setPki = hcryptMsg_STA_SetPki; + _hcMsg_STA_MsgInfo.resetCache = hcryptMsg_STA_ResetCache; + _hcMsg_STA_MsgInfo.indexMsg = hcryptMsg_STA_IndexMsg; + _hcMsg_STA_MsgInfo.parseMsg = hcryptMsg_STA_ParseMsg; + + return(&_hcMsg_STA_MsgInfo); +} + diff --git a/include/haicrypt.h b/include/haicrypt.h new file mode 100644 index 000000000..7b895cfcd --- /dev/null +++ b/include/haicrypt.h @@ -0,0 +1,125 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#ifndef HAICRYPT_H +#define HAICRYPT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Define (in Makefile) the HaiCrypt ciphers compiled in + */ +//#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_Cipher HaiCryptCipher_OpenSSL_EVP(void); /* OpenSSL EVP interface (default to EVP_CTR) */ +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CBC(void); /* OpenSSL EVP interface for AES-CBC */ +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_EVP_CTR(void); /* OpenSSL EVP interface for AES-CTR */ +HaiCrypt_Cipher HaiCryptCipher_OpenSSL_AES(void); /* OpenSSL AES direct interface */ + + +#define HAICRYPT_CIPHER_BLK_SZ 16 /* AES Block Size */ + +#define HAICRYPT_PWD_MAX_SZ 80 /* MAX password (for Password-based Key Derivation) */ +#define HAICRYPT_KEY_MAX_SZ 32 /* MAX key */ +#define HAICRYPT_SECRET_MAX_SZ (HAICRYPT_PWD_MAX_SZ > HAICRYPT_KEY_MAX_SZ ? HAICRYPT_PWD_MAX_SZ : HAICRYPT_KEY_MAX_SZ) + + +#define HAICRYPT_SALT_SZ 16 + +#define HAICRYPT_WRAPKEY_SIGN_SZ 8 /* RFC3394 AES KeyWrap signature size */ + +#define HAICRYPT_PBKDF2_SALT_LEN 8 /* PKCS#5 PBKDF2 Password based key derivation salt length */ +#define HAICRYPT_PBKDF2_ITER_CNT 2048 /* PKCS#5 PBKDF2 Password based key derivation iteration count */ + +#define HAICRYPT_TS_PKT_SZ 188 /* Transport Stream packet size */ + +typedef struct { +#define HAICRYPT_SECTYP_UNDEF 0 +#define HAICRYPT_SECTYP_PRESHARED 1 /* Preshared KEK */ +#define HAICRYPT_SECTYP_PASSPHRASE 2 /* Password */ + unsigned typ; + size_t len; + unsigned char str[HAICRYPT_SECRET_MAX_SZ]; +}HaiCrypt_Secret; + +typedef struct { +#define HAICRYPT_CFG_F_TX 0x01 /* !TX -> RX */ +#define HAICRYPT_CFG_F_CRYPTO 0x02 /* Perform crypto Tx:Encrypt Rx:Decrypt */ +#define HAICRYPT_CFG_F_FEC 0x04 /* Do Forward Error Correction */ + unsigned flags; + + HaiCrypt_Secret secret; /* Security Association */ + + HaiCrypt_Cipher cipher; /* Media Stream cipher implementation */ +#define HAICRYPT_DEF_KEY_LENGTH 16 /* default key length (bytes) */ + size_t key_len; /* SEK length (bytes) */ +#define HAICRYPT_DEF_DATA_MAX_LENGTH 1500 /* default packet data length (bytes) */ + size_t data_max_len; /* Maximum data_len passed to HaiCrypt (bytes) */ + +#define HAICRYPT_XPT_STANDALONE 0 +#define HAICRYPT_XPT_SRT 1 + int xport; + +#define HAICRYPT_DEF_KM_TX_PERIOD 1000 /* Keying Material Default Tx Period (msec) */ + unsigned int km_tx_period_ms; /* Keying Material Tx period (msec) */ +#define HAICRYPT_DEF_KM_REFRESH_RATE 0x1000000 /* Keying Material Default Refresh Rate (pkts) */ + unsigned int km_refresh_rate_pkt; /* Keying Material Refresh Rate (pkts) */ +#define HAICRYPT_DEF_KM_PRE_ANNOUNCE 0x1000 /* Keying Material Default Pre/Post Announce (pkts) */ + unsigned int km_pre_announce_pkt; /* Keying Material Pre/Post Announce (pkts) */ +}HaiCrypt_Cfg; + +typedef void *HaiCrypt_Handle; + +int HaiCrypt_SetLogLevel(int level, int logfa); + +int HaiCrypt_Create(HaiCrypt_Cfg *cfg, HaiCrypt_Handle *phhc); +int HaiCrypt_Close(HaiCrypt_Handle hhc); +int HaiCrypt_Tx_GetBuf(HaiCrypt_Handle hhc, size_t data_len, unsigned char **in_p); +int HaiCrypt_Tx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, + void *out_p[], size_t out_len_p[], int maxout); +int HaiCrypt_Rx_Process(HaiCrypt_Handle hhc, unsigned char *in, size_t in_len, + void *out_p[], size_t out_len_p[], int maxout); + +int HaiCrypt_Tx_GetKeyFlags(HaiCrypt_Handle hhc); +int HaiCrypt_Tx_ManageKeys(HaiCrypt_Handle hhc, void *out_p[], size_t out_len_p[], int maxout); +int HaiCrypt_Tx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); +int HaiCrypt_Rx_Data(HaiCrypt_Handle hhc, unsigned char *pfx, unsigned char *data, size_t data_len); + +#ifdef __cplusplus +} +#endif + +#endif /* HAICRYPT_H */ diff --git a/include/hcrypt_ctx.h b/include/hcrypt_ctx.h new file mode 100644 index 000000000..f734302ad --- /dev/null +++ b/include/hcrypt_ctx.h @@ -0,0 +1,166 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#ifndef HCRYPT_CTX_H +#define HCRYPT_CTX_H + +#include +#include //AES_KEY for kek + +#if !defined(HAISRT_VERSION_INT) +#include "haicrypt.h" +#include "hcrypt_msg.h" +#else +// Included by haisrt.h or similar +#include "haisrt/haicrypt.h" +#include "haisrt/hcrypt_msg.h" +#endif + +typedef struct { + unsigned char *pfx; //Prefix described by transport msg info (in ctx) + unsigned char *payload; + size_t len; //Payload size +}hcrypt_DataDesc; + + +typedef struct tag_hcrypt_Ctx { + struct tag_hcrypt_Ctx * alt; /* Alternative ctx (even/odd) */ + +#define HCRYPT_CTX_F_MSG 0x00FF /* Aligned wiht message header flags */ +#define HCRYPT_CTX_F_eSEK HCRYPT_MSG_F_eSEK +#define HCRYPT_CTX_F_oSEK HCRYPT_MSG_F_oSEK +#define HCRYPT_CTX_F_xSEK HCRYPT_MSG_F_xSEK + +#define HCRYPT_CTX_F_ENCRYPT 0x0100 /* 0:decrypt 1:encrypt */ +#define HCRYPT_CTX_F_ANNOUNCE 0x0200 /* Announce KM */ +#define HCRYPT_CTX_F_TTSEND 0x0400 /* time to send */ + unsigned flags; + +#define HCRYPT_CTX_S_INIT 1 +#define HCRYPT_CTX_S_SARDY 2 /* Security Association (KEK) ready */ +#define HCRYPT_CTX_S_KEYED 3 /* Media Stream Encrypting Key (SEK) ready */ +#define HCRYPT_CTX_S_ACTIVE 4 /* Announced and in use */ +#define HCRYPT_CTX_S_DEPRECATED 5 /* Still announced but no longer used */ + unsigned status; + +#define HCRYPT_CTX_MODE_CLRTXT 0 /* NULL cipher (for tests) */ +#define HCRYPT_CTX_MODE_AESECB 1 /* Electronic Code Book mode */ +#define HCRYPT_CTX_MODE_AESCTR 2 /* Counter mode */ +#define HCRYPT_CTX_MODE_AESCBC 3 /* Cipher-block chaining mode */ + unsigned mode; + + struct { + size_t key_len; + size_t pwd_len; + char pwd[HAICRYPT_PWD_MAX_SZ]; + } cfg; + + size_t salt_len; + unsigned char salt[HAICRYPT_SALT_SZ]; + + size_t sek_len; + unsigned char sek[HAICRYPT_KEY_MAX_SZ]; + + AES_KEY aes_kek; + + hcrypt_MsgInfo * msg_info; /* Transport message handler */ + unsigned pkt_cnt; /* Key usage counter */ + +#define HCRYPT_CTX_MAX_KM_PFX_SZ 16 + size_t KMmsg_len; + unsigned char KMmsg_cache[HCRYPT_CTX_MAX_KM_PFX_SZ + HCRYPT_MSG_KM_MAX_SZ]; + +#define HCRYPT_CTX_MAX_MS_PFX_SZ 16 + unsigned char MSpfx_cache[HCRYPT_CTX_MAX_MS_PFX_SZ]; +} hcrypt_Ctx; + +typedef void *hcrypt_CipherData; + +typedef struct { + /* + * open: + * Create a cipher instance + * Allocate output buffers + */ + hcrypt_CipherData *(*open)( + size_t max_len); /* Maximum packet length that will be encrypted/decrypted */ + + /* + * close: + * Release any cipher resources + */ + int (*close)( + hcrypt_CipherData *crypto_data); /* Cipher handle, internal data */ + + /* + * setkey: + * Set the Odd or Even, Encryption or Decryption key. + * Context (ctx) tells if it's for Odd or Even key (hcryptCtx_GetKeyIndex(ctx)) + * A Context flags (ctx->flags) also tells if this is an encryption or decryption context (HCRYPT_CTX_F_ENCRYPT) + */ + int (*setkey)( + hcrypt_CipherData *crypto_data, /* Cipher handle, internal data */ + hcrypt_Ctx *ctx, /* HaiCrypt Context (cipher, keys, Odd/Even, etc..) */ + unsigned char *key, size_t key_len); /* New Key */ + + /* + * encrypt: + * Submit a list of nbin clear transport packets (hcrypt_DataDesc *in_data) to encryption + * returns *nbout encrypted data packets of length out_len_p[] into out_p[] + * + * If cipher implements deferred encryption (co-processor, async encryption), + * it may return no encrypted packets, or encrypted packets for clear text packets of a previous call. + */ + int (*encrypt)( + hcrypt_CipherData *crypto_data, /* Cipher handle, internal data */ + hcrypt_Ctx *ctx, /* HaiCrypt Context (cipher, keys, Odd/Even, etc..) */ + hcrypt_DataDesc *in_data, int nbin, /* Clear text transport packets: header and payload */ + void *out_p[], size_t out_len_p[], int *nbout); /* Encrypted packets */ + + /* + * decrypt: + * Submit a list of nbin encrypted transport packets (hcrypt_DataDesc *in_data) to decryption + * returns *nbout clear text data packets of length out_len_p[] into out_p[] + * + * If cipher implements deferred decryption (co-processor, async encryption), + * it may return no decrypted packets, or decrypted packets for encrypted packets of a previous call. + */ + int (*decrypt)( + hcrypt_CipherData *crypto_data, /* Cipher handle, internal data */ + hcrypt_Ctx *ctx, /* HaiCrypt Context (cipher, keys, Odd/Even, etc..) */ + hcrypt_DataDesc *in_data, int nbin, /* Clear text transport packets: header and payload */ + void *out_p[], size_t out_len_p[], int *nbout); /* Encrypted packets */ + + int (*getinbuf)(hcrypt_CipherData *crypto_data, size_t hdr_len, size_t in_len, unsigned int pad_factor, unsigned char **in_pp); +} hcrypt_Cipher; + +#define hcryptCtx_GetKeyFlags(ctx) ((ctx)->flags & HCRYPT_CTX_F_xSEK) +#define hcryptCtx_GetKeyIndex(ctx) (((ctx)->flags & HCRYPT_CTX_F_xSEK)>>1) + +#endif /* HCRYPT_CTX_H */ diff --git a/include/hcrypt_msg.h b/include/hcrypt_msg.h new file mode 100644 index 000000000..6e84bd244 --- /dev/null +++ b/include/hcrypt_msg.h @@ -0,0 +1,163 @@ +/* + * 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. + + 2011-06-23 (jdube) + HaiCrypt initial implementation. + 2014-03-11 (jdube) + Adaptation for SRT. +*****************************************************************************/ + +#ifndef HCRYPT_MSG_H +#define HCRYPT_MSG_H + +/* + * HaiCrypt Transport Message Header info + */ + + +#ifndef HCRYPT_DSP +#include + +typedef uint32_t hcrypt_Pki; +#endif /* HCRYPT_DSP */ + + +#define HCRYPT_MSG_VERSION 1 /* Current HaiCrypt version */ + +#define HCRYPT_MSG_SIGN (('H'-'@')<<10 | ('A'-'@')<<5 | ('I'-'@')) /* Haivision PnP Mfr ID 'HAI' */ + +#define HCRYPT_PKI_SZ 4 /* Packet Index size (CTR mode cipher) */ + +#define HCRYPT_MSG_PT_MS 1 /* Media stream */ +#define HCRYPT_MSG_PT_KM 2 /* Keying Material */ +#define HCRYPT_MSG_PT_RESV7 7 /* Reserved to dicriminate MPEG-TS packet (SyncByte=0x47) */ + + +#define HCRYPT_MSG_F_eSEK 0x01 /* Even Stream Encrypting Key */ +#define HCRYPT_MSG_F_oSEK 0x02 /* Odd Stream Encrypting Key */ +#define HCRYPT_MSG_F_xSEK 0x03 /* Both Stream Encrypting Keys */ + +typedef struct { + int hdr_len; // data and control common prefix portion + int pfx_len; // Message Prefix len. Also payload offset + unsigned (*getKeyFlags)(unsigned char *msg); + hcrypt_Pki (*getPki)(unsigned char *msg, int nwko); + void (*setPki)(unsigned char *msg, hcrypt_Pki); + void (*resetCache)(unsigned char *pfx_cache, unsigned pkt_type, unsigned flags); + void (*indexMsg)(unsigned char *msg, unsigned char *pfx_cache); + int (*parseMsg)(unsigned char *msg); +}hcrypt_MsgInfo; + + +#define hcryptMsg_GetKeyIndex(mi,msg) ((mi)->getKeyFlags(msg)>>1) +#define hcryptMsg_GetPki(mi,msg,nwko) ((mi)->getPki(msg,nwko)) +#define hcryptMsg_SetPki(mi,msg,pki) (mi)->setPki(msg, pki) + +#define hcryptMsg_HasEvenSek(mi,msg) ((mi)->getKeyFlags(msg) & HCRYPT_MSG_F_eSEK) +#define hcryptMsg_HasOddSek(mi,msg) ((mi)->getKeyFlags(msg) & HCRYPT_MSG_F_oSEK) +#define hcryptMsg_HasBothSek(mi,msg) (HCRYPT_MSG_F_xSEK == ((mi)->getKeyFlags(msg) & HCRYPT_MSG_F_xSEK)) +#define hcryptMsg_HasNoSek(mi,msg) (0 == ((mi)->getKeyFlags(msg) & HCRYPT_MSG_F_xSEK)) + +#define hcryptMsg_PaddedLen(len, fact) ((((len)+(fact)-1)/(fact))*(fact)) + + +/* + * HaiCrypt KMmsg (Keying Material): + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + *+0x00 |0|Vers | PT | Sign | resv |KF | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + *+0x04 | KEKI | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + *+0x08 | Cipher | Auth | SE | Resv1 | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + *+0x0C | Resv2 | Slen/4 | Klen/4 | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + *+0x10 | Salt | + * | ... | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + * | Wrap | + * | ... | + * +-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-|-+-+-+-+-+-+-+-+ + */ + + + +#define HCRYPT_MSG_KM_OFS_VERSION 0 +#define HCRYPT_MSG_KM_OFS_PT 0 +#define HCRYPT_MSG_KM_OFS_SIGN 1 +#define HCRYPT_MSG_KM_OFS_KFLGS 3 +#define HCRYPT_MSG_KM_RSH_KFLGS 0 /* Right shift (in byte) */ + +#define HCRYPT_MSG_KM_OFS_KEKI 4 +#define HCRYPT_MSG_KM_OFS_CIPHER 8 +#define HCRYPT_MSG_KM_OFS_AUTH 9 +#define HCRYPT_MSG_KM_OFS_SE 10 +#define HCRYPT_MSG_KM_OFS_RESV2 12 +#define HCRYPT_MSG_KM_OFS_SLEN 14 +#define HCRYPT_MSG_KM_OFS_KLEN 15 +#define HCRYPT_MSG_KM_OFS_SALT 16 + +#define HCRYPT_MSG_KM_MAX_SZ (0 \ + + HCRYPT_MSG_KM_OFS_SALT \ + + HAICRYPT_SALT_SZ \ + + (HAICRYPT_KEY_MAX_SZ * 2) \ + + HAICRYPT_WRAPKEY_SIGN_SZ) + +#define HCRYPT_CIPHER_NONE 0 +#define HCRYPT_CIPHER_AES_ECB 1 +#define HCRYPT_CIPHER_AES_CTR 2 +#define HCRYPT_CIPHER_AES_CBC 3 + +#define HCRYPT_AUTH_NONE 0 + +#define HCRYPT_SE_TSUDP 1 + hcrypt_MsgInfo * hcryptMsg_STA_MsgInfo(void); +#define HCRYPT_SE_TSSRT 2 + hcrypt_MsgInfo * hcryptMsg_SRT_MsgInfo(void); + +#define hcryptMsg_KM_GetVersion(msg) (((msg)[HCRYPT_MSG_KM_OFS_VERSION]>>4)& 0xF) +#define hcryptMsg_KM_GetPktType(msg) (((msg)[HCRYPT_MSG_KM_OFS_PT]) & 0xF) +#define hcryptMsg_KM_GetSign(msg) (((msg)[HCRYPT_MSG_KM_OFS_SIGN]<<8) | (msg)[HCRYPT_MSG_KM_OFS_SIGN+1]) + +#define hcryptMsg_KM_GetKeyIndex(msg) (((msg)[HCRYPT_MSG_KM_OFS_KFLGS] & HCRYPT_MSG_F_xSEK)>>1) + +#define hcryptMsg_KM_HasEvenSek(msg) ((msg)[HCRYPT_MSG_KM_OFS_KFLGS] & HCRYPT_MSG_F_eSEK) +#define hcryptMsg_KM_HasOddSek(msg) ((msg)[HCRYPT_MSG_KM_OFS_KFLGS] & HCRYPT_MSG_F_oSEK) +#define hcryptMsg_KM_HasBothSek(msg) (HCRYPT_MSG_F_xSEK == ((msg)[HCRYPT_MSG_KM_OFS_KFLGS] & HCRYPT_MSG_F_xSEK)) +#define hcryptMsg_KM_HasNoSek(msg) (0 == ((msg)[HCRYPT_MSG_KM_OFS_KFLGS] & HCRYPT_MSG_F_xSEK)) + +#define hcryptMsg_KM_GetCipher(msg) ((msg)[HCRYPT_MSG_KM_OFS_CIPHER]) +#define hcryptMsg_KM_GetAuth(msg) ((msg)[HCRYPT_MSG_KM_OFS_AUTH]) +#define hcryptMsg_KM_GetSE(msg) ((msg)[HCRYPT_MSG_KM_OFS_SE]) + +#define hcryptMsg_KM_GetSaltLen(msg) (size_t)((msg)[HCRYPT_MSG_KM_OFS_SLEN] * 4) +#define hcryptMsg_KM_GetSekLen(msg) (size_t)((msg)[HCRYPT_MSG_KM_OFS_KLEN] * 4) + +#define hcryptMsg_KM_SetSaltLen(msg,len)do {(msg)[HCRYPT_MSG_KM_OFS_SLEN] = (len)/4;} while(0) +#define hcryptMsg_KM_SetSekLen(msg,len) do {(msg)[HCRYPT_MSG_KM_OFS_KLEN] = (len)/4;} while(0) + + +#endif /* HCRYPT_MSG_H */ diff --git a/include/srt_compat.h b/include/srt_compat.h new file mode 100644 index 000000000..fecdfb23c --- /dev/null +++ b/include/srt_compat.h @@ -0,0 +1,173 @@ +/* + * 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 HAISRT_COMPAT_H__ +#define HAISRT_COMPAT_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// MacOS X has a monotonic clock, but it's not POSIX. +#if defined(__MACH__) + +#include + +// NOTE: clock_gettime() and clock_res() were added in OSX-10.12, IOS-10.0, +// TVOS-10.0, and WATCHOS-3.0. But XCode8 makes this very difficult to +// conditionally check for availability of the function. It will create +// a weak binding to clock_gettime() and clock_res() which will not be +// resolved at runtime unless actually run under OSX-10.12 even if +// -mmacosx-version-min= or MACOSX_DEPLOYMENT_TARGET= are less than 10.12. +// This is particularly problematic because the weak symbol will be NULL +// and cause an application crash when calling either of these functions. +// Attempting to work around this issue. +// See: +// https://bugs.erlang.org/browse/ERL-256 +// https://curl.haxx.se/mail/lib-2016-09/0043.html + +#if defined(CLOCK_REALTIME) +#define __SRT_OSX_CLOCK_GETTIME_AVAILABILITY 1 +#else +typedef enum +{ + CLOCK_REALTIME = 0, + CLOCK_MONOTONIC = 6, + // CONSIDER: CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID. + // Mentioned in Linux manpage, but not described as Linux-specific. +} clockid_t; +#endif + +// OS-X does not have clock_gettime(2). This implements work arounds. +// https://developer.apple.com/library/mac/qa/qa1398/_index.html +// http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x + +int OSX_clock_gettime(clockid_t clock_id, struct timespec * ts); + +static inline int OSX_clock_getres(clockid_t clock_id, struct timespec * ts) +{ + if (0) + { + (void)clock_id; + } + + ts->tv_sec = 0; + ts->tv_nsec = 1; + + return 0; +} + +static inline int OSXCall_clock_gettime(clockid_t clock_id, struct timespec * ts) +{ +#if defined(__SRT_OSX_CLOCK_GETTIME_AVAILABILITY) \ + && (__SRT_OSX_CLOCK_GETTIME_AVAILABILITY == 1) + if (clock_gettime != NULL) + { + return clock_gettime(clock_id, ts); + } +#endif + + return OSX_clock_gettime(clock_id, ts); +} + +static inline int OSXCall_clock_getres(clockid_t clock_id, struct timespec * ts) +{ +#if defined(__SRT_OSX_CLOCK_GETTIME_AVAILABILITY) \ + && (__SRT_OSX_CLOCK_GETTIME_AVAILABILITY == 1) + if (clock_getres != NULL) + { + return clock_getres(clock_id, ts); + } +#endif + + return OSX_clock_getres(clock_id, ts); +} + +#define SysClockGetTime OSXCall_clock_gettime +#define SysClockGetRes OSXCall_clock_getres + +static inline int pthread_condattr_setclock( + pthread_condattr_t * attr, clockid_t clock_id) +{ + if (0) + { + (void)attr; + (void)clock_id; + } + + errno = ENOSYS; + + return -1; +} + +static inline size_t SysStrnlen(const char * s, size_t maxlen) +{ + const char* pos = memchr(s, 0, maxlen); + return pos ? pos - s : maxlen; +} + +#if !defined(MAC_OS_X_VERSION_MAX_ALLOWED) + #error "FIXME!!" +#endif +#if (MAC_OS_X_VERSION_MAX_ALLOWED <= 101200) + #if defined(strnlen) + #undef strnlen + #endif + #define strnlen(s, maxlen) SysStrnlen(s, maxlen) +#endif + +#endif // (__MACH__) + + +#ifndef SysClockGetTime +#define SysClockGetTime clock_gettime +#endif + +#ifndef SysClockGetRes +#define SysClockGetRes clock_res +#endif + +/* Ensures that we store the error in the buffer and return the bufer. */ +const char * SysStrError(int errnum, char * buf, size_t buflen); + +#ifdef __cplusplus +} // extern C + + +// Extra C++ stuff. Included only in C++ mode. + + +#include +inline std::string SysStrError(int errnum) +{ + char buf[1024]; + return SysStrError(errnum, buf, 1024); +} + +#endif + +#endif // HAISRT_COMPAT_H__ diff --git a/include/win/inttypes.h b/include/win/inttypes.h new file mode 100644 index 000000000..41c334771 --- /dev/null +++ b/include/win/inttypes.h @@ -0,0 +1,305 @@ +// ISO C9x compliant inttypes.h for Microsoft Visual Studio +// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 +// +// Copyright (c) 2006 Alexander Chemeris +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. 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. +// +// 3. The name of the author may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _MSC_VER // [ +#error "Use this header only with Microsoft Visual C++ compilers!" +#endif // _MSC_VER ] + +#ifndef _MSC_INTTYPES_H_ // [ +#define _MSC_INTTYPES_H_ + +#if _MSC_VER > 1000 +#pragma once +#endif + +#include + +// 7.8 Format conversion of integer types + +typedef struct { + intmax_t quot; + intmax_t rem; +} imaxdiv_t; + +// 7.8.1 Macros for format specifiers + +#if !defined(__cplusplus) || defined(__STDC_FORMAT_MACROS) // [ See footnote 185 at page 198 + +// The fprintf macros for signed integers are: +#define PRId8 "d" +#define PRIi8 "i" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIdLEAST16 "hd" +#define PRIiLEAST16 "hi" +#define PRIdFAST16 "hd" +#define PRIiFAST16 "hi" + +#define PRId32 "I32d" +#define PRIi32 "I32i" +#define PRIdLEAST32 "I32d" +#define PRIiLEAST32 "I32i" +#define PRIdFAST32 "I32d" +#define PRIiFAST32 "I32i" + +#define PRId64 "I64d" +#define PRIi64 "I64i" +#define PRIdLEAST64 "I64d" +#define PRIiLEAST64 "I64i" +#define PRIdFAST64 "I64d" +#define PRIiFAST64 "I64i" + +#define PRIdMAX "I64d" +#define PRIiMAX "I64i" + +#define PRIdPTR "Id" +#define PRIiPTR "Ii" + +// The fprintf macros for unsigned integers are: +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" + +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" +#define PRIoLEAST16 "ho" +#define PRIuLEAST16 "hu" +#define PRIxLEAST16 "hx" +#define PRIXLEAST16 "hX" +#define PRIoFAST16 "ho" +#define PRIuFAST16 "hu" +#define PRIxFAST16 "hx" +#define PRIXFAST16 "hX" + +#define PRIo32 "I32o" +#define PRIu32 "I32u" +#define PRIx32 "I32x" +#define PRIX32 "I32X" +#define PRIoLEAST32 "I32o" +#define PRIuLEAST32 "I32u" +#define PRIxLEAST32 "I32x" +#define PRIXLEAST32 "I32X" +#define PRIoFAST32 "I32o" +#define PRIuFAST32 "I32u" +#define PRIxFAST32 "I32x" +#define PRIXFAST32 "I32X" + +#define PRIo64 "I64o" +#define PRIu64 "I64u" +#define PRIx64 "I64x" +#define PRIX64 "I64X" +#define PRIoLEAST64 "I64o" +#define PRIuLEAST64 "I64u" +#define PRIxLEAST64 "I64x" +#define PRIXLEAST64 "I64X" +#define PRIoFAST64 "I64o" +#define PRIuFAST64 "I64u" +#define PRIxFAST64 "I64x" +#define PRIXFAST64 "I64X" + +#define PRIoMAX "I64o" +#define PRIuMAX "I64u" +#define PRIxMAX "I64x" +#define PRIXMAX "I64X" + +#define PRIoPTR "Io" +#define PRIuPTR "Iu" +#define PRIxPTR "Ix" +#define PRIXPTR "IX" + +// The fscanf macros for signed integers are: +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" + +#define SCNd16 "hd" +#define SCNi16 "hi" +#define SCNdLEAST16 "hd" +#define SCNiLEAST16 "hi" +#define SCNdFAST16 "hd" +#define SCNiFAST16 "hi" + +#define SCNd32 "ld" +#define SCNi32 "li" +#define SCNdLEAST32 "ld" +#define SCNiLEAST32 "li" +#define SCNdFAST32 "ld" +#define SCNiFAST32 "li" + +#define SCNd64 "I64d" +#define SCNi64 "I64i" +#define SCNdLEAST64 "I64d" +#define SCNiLEAST64 "I64i" +#define SCNdFAST64 "I64d" +#define SCNiFAST64 "I64i" + +#define SCNdMAX "I64d" +#define SCNiMAX "I64i" + +#ifdef _WIN64 // [ +# define SCNdPTR "I64d" +# define SCNiPTR "I64i" +#else // _WIN64 ][ +# define SCNdPTR "ld" +# define SCNiPTR "li" +#endif // _WIN64 ] + +// The fscanf macros for unsigned integers are: +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNX8 "X" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNXLEAST8 "X" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNXFAST8 "X" + +#define SCNo16 "ho" +#define SCNu16 "hu" +#define SCNx16 "hx" +#define SCNX16 "hX" +#define SCNoLEAST16 "ho" +#define SCNuLEAST16 "hu" +#define SCNxLEAST16 "hx" +#define SCNXLEAST16 "hX" +#define SCNoFAST16 "ho" +#define SCNuFAST16 "hu" +#define SCNxFAST16 "hx" +#define SCNXFAST16 "hX" + +#define SCNo32 "lo" +#define SCNu32 "lu" +#define SCNx32 "lx" +#define SCNX32 "lX" +#define SCNoLEAST32 "lo" +#define SCNuLEAST32 "lu" +#define SCNxLEAST32 "lx" +#define SCNXLEAST32 "lX" +#define SCNoFAST32 "lo" +#define SCNuFAST32 "lu" +#define SCNxFAST32 "lx" +#define SCNXFAST32 "lX" + +#define SCNo64 "I64o" +#define SCNu64 "I64u" +#define SCNx64 "I64x" +#define SCNX64 "I64X" +#define SCNoLEAST64 "I64o" +#define SCNuLEAST64 "I64u" +#define SCNxLEAST64 "I64x" +#define SCNXLEAST64 "I64X" +#define SCNoFAST64 "I64o" +#define SCNuFAST64 "I64u" +#define SCNxFAST64 "I64x" +#define SCNXFAST64 "I64X" + +#define SCNoMAX "I64o" +#define SCNuMAX "I64u" +#define SCNxMAX "I64x" +#define SCNXMAX "I64X" + +#ifdef _WIN64 // [ +# define SCNoPTR "I64o" +# define SCNuPTR "I64u" +# define SCNxPTR "I64x" +# define SCNXPTR "I64X" +#else // _WIN64 ][ +# define SCNoPTR "lo" +# define SCNuPTR "lu" +# define SCNxPTR "lx" +# define SCNXPTR "lX" +#endif // _WIN64 ] + +#endif // __STDC_FORMAT_MACROS ] + +// 7.8.2 Functions for greatest-width integer types + +// 7.8.2.1 The imaxabs function +#define imaxabs _abs64 + +// 7.8.2.2 The imaxdiv function + +// This is modified version of div() function from Microsoft's div.c found +// in %MSVC.NET%\crt\src\div.c +#ifdef STATIC_IMAXDIV // [ +static +#else // STATIC_IMAXDIV ][ +_inline +#endif // STATIC_IMAXDIV ] +imaxdiv_t __cdecl imaxdiv(intmax_t numer, intmax_t denom) +{ + imaxdiv_t result; + + result.quot = numer / denom; + result.rem = numer % denom; + + if (numer < 0 && result.rem > 0) { + // did division wrong; must fix up + ++result.quot; + result.rem -= denom; + } + + return result; +} + +// 7.8.2.3 The strtoimax and strtoumax functions +#define strtoimax _strtoi64 +#define strtoumax _strtoui64 + +// 7.8.2.4 The wcstoimax and wcstoumax functions +#define wcstoimax _wcstoi64 +#define wcstoumax _wcstoui64 + + +#endif // _MSC_INTTYPES_H_ ] diff --git a/include/win/stdint.h b/include/win/stdint.h new file mode 100644 index 000000000..cca9c8551 --- /dev/null +++ b/include/win/stdint.h @@ -0,0 +1,203 @@ +/* stdint.h standard header */ +#pragma once +#ifndef _STDINT +#define _STDINT +#ifndef RC_INVOKED +#include + +/* NB: assumes + byte has 8 bits + long is 32 bits + pointer can convert to and from long long + long long is longest type + */ + +_C_STD_BEGIN + /* TYPE DEFINITIONS */ +typedef signed char int8_t; +typedef short int16_t; +typedef int int32_t; + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; + +typedef signed char int_least8_t; +typedef short int_least16_t; +typedef int int_least32_t; + +typedef unsigned char uint_least8_t; +typedef unsigned short uint_least16_t; +typedef unsigned int uint_least32_t; + +typedef signed char int_fast8_t; +typedef int int_fast16_t; +typedef int int_fast32_t; + +typedef unsigned char uint_fast8_t; +typedef unsigned int uint_fast16_t; +typedef unsigned int uint_fast32_t; + +#ifndef _INTPTR_T_DEFINED + #define _INTPTR_T_DEFINED + #ifdef _WIN64 +typedef __int64 intptr_t; + #else /* _WIN64 */ +typedef _W64 int intptr_t; + #endif /* _WIN64 */ +#endif /* _INTPTR_T_DEFINED */ + +#ifndef _UINTPTR_T_DEFINED + #define _UINTPTR_T_DEFINED + #ifdef _WIN64 +typedef unsigned __int64 uintptr_t; + #else /* _WIN64 */ +typedef _W64 unsigned int uintptr_t; + #endif /* _WIN64 */ +#endif /* _UINTPTR_T_DEFINED */ + +typedef _Longlong int64_t; +typedef _ULonglong uint64_t; + +typedef _Longlong int_least64_t; +typedef _ULonglong uint_least64_t; + +typedef _Longlong int_fast64_t; +typedef _ULonglong uint_fast64_t; + +typedef _Longlong intmax_t; +typedef _ULonglong uintmax_t; + + /* LIMIT MACROS */ +#ifndef INT8_MIN +#define INT8_MIN (-0x7f - _C2) +#define INT16_MIN (-0x7fff - _C2) +#define INT32_MIN (-0x7fffffff - _C2) + +#define INT8_MAX 0x7f +#define INT16_MAX 0x7fff +#define INT32_MAX 0x7fffffff +#define UINT8_MAX 0xff +#define UINT16_MAX 0xffff +#define UINT32_MAX 0xffffffff + +#define INT_LEAST8_MIN (-0x7f - _C2) +#define INT_LEAST16_MIN (-0x7fff - _C2) +#define INT_LEAST32_MIN (-0x7fffffff - _C2) + +#define INT_LEAST8_MAX 0x7f +#define INT_LEAST16_MAX 0x7fff +#define INT_LEAST32_MAX 0x7fffffff +#define UINT_LEAST8_MAX 0xff +#define UINT_LEAST16_MAX 0xffff +#define UINT_LEAST32_MAX 0xffffffff + +#define INT_FAST8_MIN (-0x7f - _C2) +#define INT_FAST16_MIN (-0x7fff - _C2) +#define INT_FAST32_MIN (-0x7fffffff - _C2) + +#define INT_FAST8_MAX 0x7f +#define INT_FAST16_MAX 0x7fff +#define INT_FAST32_MAX 0x7fffffff +#define UINT_FAST8_MAX 0xff +#define UINT_FAST16_MAX 0xffff +#define UINT_FAST32_MAX 0xffffffff + + #if _INTPTR == 0 || _INTPTR == 1 +#define INTPTR_MAX 0x7fffffff +#define INTPTR_MIN (-INTPTR_MAX - _C2) +#define UINTPTR_MAX 0xffffffff + + #else /* _INTPTR == 2 */ +#define INTPTR_MIN (-_LLONG_MAX - _C2) +#define INTPTR_MAX _LLONG_MAX +#define UINTPTR_MAX _ULLONG_MAX +#endif /* _INTPTR */ + +#define INT8_C(x) (x) +#define INT16_C(x) (x) +#define INT32_C(x) ((x) + (INT32_MAX - INT32_MAX)) + +#define UINT8_C(x) (x) +#define UINT16_C(x) (x) +#define UINT32_C(x) ((x) + (UINT32_MAX - UINT32_MAX)) + +#ifdef _WIN64 + #define PTRDIFF_MIN INT64_MIN + #define PTRDIFF_MAX INT64_MAX +#else /* _WIN64 */ + #define PTRDIFF_MIN INT32_MIN + #define PTRDIFF_MAX INT32_MAX +#endif /* _WIN64 */ + +#define SIG_ATOMIC_MIN INT32_MIN +#define SIG_ATOMIC_MAX INT32_MAX + +#ifndef SIZE_MAX + #ifdef _WIN64 + #define SIZE_MAX UINT64_MAX + #else /* _WIN64 */ + #define SIZE_MAX UINT32_MAX + #endif /* _WIN64 */ +#endif /* SIZE_MAX */ + +//#define WCHAR_MIN 0x0000 +//#define WCHAR_MAX 0xffff +// +//#define WINT_MIN 0x0000 +//#define WINT_MAX 0xffff + + #define INT64_MIN (-0x7fffffffffffffff - _C2) + #define INT64_MAX 0x7fffffffffffffff + #define UINT64_MAX 0xffffffffffffffffU + + #define INT_LEAST64_MIN (-0x7fffffffffffffff - _C2) + #define INT_LEAST64_MAX 0x7fffffffffffffff + #define UINT_LEAST64_MAX 0xffffffffffffffffU + + #define INT_FAST64_MIN (-0x7fffffffffffffff - _C2) + #define INT_FAST64_MAX 0x7fffffffffffffff + #define UINT_FAST64_MAX 0xffffffffffffffffU + + #define INTMAX_MIN (-0x7fffffffffffffff - _C2) + #define INTMAX_MAX 0x7fffffffffffffff + #define UINTMAX_MAX 0xffffffffffffffffU + +#define INT64_C(x) ((x) + (INT64_MAX - INT64_MAX)) +#define UINT64_C(x) ((x) + (UINT64_MAX - UINT64_MAX)) +#define INTMAX_C(x) INT64_C(x) +#define UINTMAX_C(x) UINT64_C(x) + +#endif // defines provided together with INT8_MIN + +_C_STD_END +#endif /* RC_INVOKED */ +#endif /* _STDINT */ + + #if defined(_STD_USING) +using _CSTD int8_t; using _CSTD int16_t; +using _CSTD int32_t; using _CSTD int64_t; + +using _CSTD uint8_t; using _CSTD uint16_t; +using _CSTD uint32_t; using _CSTD uint64_t; + +using _CSTD int_least8_t; using _CSTD int_least16_t; +using _CSTD int_least32_t; using _CSTD int_least64_t; +using _CSTD uint_least8_t; using _CSTD uint_least16_t; +using _CSTD uint_least32_t; using _CSTD uint_least64_t; + +using _CSTD intmax_t; using _CSTD uintmax_t; + +using _CSTD uintptr_t; +using _CSTD intptr_t; + +using _CSTD int_fast8_t; using _CSTD int_fast16_t; +using _CSTD int_fast32_t; using _CSTD int_fast64_t; +using _CSTD uint_fast8_t; using _CSTD uint_fast16_t; +using _CSTD uint_fast32_t; using _CSTD uint_fast64_t; + #endif /* defined(_STD_USING) */ + +/* + * Copyright (c) 1992-2009 by P.J. Plauger. ALL RIGHTS RESERVED. + * Consult your license regarding permissions and restrictions. +V5.20:0009 */ diff --git a/include/win/syslog_defs.h b/include/win/syslog_defs.h new file mode 100644 index 000000000..6d761b5e5 --- /dev/null +++ b/include/win/syslog_defs.h @@ -0,0 +1,45 @@ +#ifndef INC__WINDOWS_SYSLOG_DEFS_H +#define INC__WINDOWS_SYSLOG_DEFS_H + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +#define LOG_PRIMASK 0x07 + +#define LOG_PRI(p) ((p) & LOG_PRIMASK) +#define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) + +#define LOG_KERN (0<<3) +#define LOG_USER (1<<3) +#define LOG_MAIL (2<<3) +#define LOG_DAEMON (3<<3) +#define LOG_AUTH (4<<3) +#define LOG_SYSLOG (5<<3) +#define LOG_LPR (6<<3) +#define LOG_NEWS (7<<3) +#define LOG_UUCP (8<<3) +#define LOG_CRON (9<<3) +#define LOG_AUTHPRIV (10<<3) +#define LOG_FTP (11<<3) + +/* Codes through 15 are reserved for system use */ +#define LOG_LOCAL0 (16<<3) +#define LOG_LOCAL1 (17<<3) +#define LOG_LOCAL2 (18<<3) +#define LOG_LOCAL3 (19<<3) +#define LOG_LOCAL4 (20<<3) +#define LOG_LOCAL5 (21<<3) +#define LOG_LOCAL6 (22<<3) +#define LOG_LOCAL7 (23<<3) + +#define LOG_NFACILITIES 24 +#define LOG_FACMASK 0x03f8 +#define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) + +#endif diff --git a/include/win/unistd.h b/include/win/unistd.h new file mode 100644 index 000000000..9c61cad8a --- /dev/null +++ b/include/win/unistd.h @@ -0,0 +1,52 @@ +#ifndef _UNISTD_H +#define _UNISTD_H 1 + +/* This file intended to serve as a drop-in replacement for + * unistd.h on Windows + * Please add functionality as neeeded + */ + +#include +#include +//#include /* getopt at: https://gist.github.com/ashelly/7776712*/ +#include /* for getpid() and the exec..() family */ +#include /* for _getcwd() and _chdir() */ + +#define srandom srand +#define random rand + +/* Values for the second argument to access. + These may be OR'd together. */ +#define R_OK 4 /* Test for read permission. */ +#define W_OK 2 /* Test for write permission. */ +//#define X_OK 1 /* execute permission - unsupported in windows*/ +#define F_OK 0 /* Test for existence. */ + +#define access _access +#define dup2 _dup2 +#define execve _execve +#define ftruncate _chsize +#define unlink _unlink +#define fileno _fileno +#define getcwd _getcwd +#define chdir _chdir +#define isatty _isatty +#define lseek _lseek +/* read, write, and close are NOT being #defined here, because while there are file handle specific versions for Windows, they probably don't work for sockets. You need to look at your app and consider whether to call e.g. closesocket(). */ + +#define ssize_t int + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +/* should be in some equivalent to */ +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; + +#endif /* unistd.h */ diff --git a/include/win/winporting.h b/include/win/winporting.h new file mode 100644 index 000000000..098ed2d3b --- /dev/null +++ b/include/win/winporting.h @@ -0,0 +1,63 @@ +#ifndef _WINPORTING_H_ +#define _WINPORTING_H_ + +// NOTE: This file has been borrowed from LCM project +// http://lcm-proj.github.io/ + +#if !defined(__MINGW32__) +#define strtoll _strtoi64 +#define strdup _strdup +#define mode_t int +#define snprintf _snprintf +//#define PATH_MAX MAX_PATH +#define fseeko _fseeki64 +#define ftello _ftelli64 +//#define socklen_t int +#define in_addr_t in_addr +#define SHUT_RDWR SD_BOTH +#define HUGE HUGE_VAL +#define O_NONBLOCK 0x4000 +#define F_GETFL 3 +#define F_SETFL 4 +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Microsoft implementation of these structures has the +// pointer and length in reversed positions. +typedef struct iovec +{ + ULONG iov_len; + char *iov_base; +} iovec; + +typedef struct msghdr +{ + struct sockaddr *msg_name; + int msg_namelen; + struct iovec *msg_iov; + ULONG msg_iovlen; + int msg_controllen; + char *msg_control; + ULONG msg_flags; +} msghdr; + +//typedef long int ssize_t; + +//int inet_aton(const char *cp, struct in_addr *inp); + +int fcntl (int fd, int flag1, ...); + +size_t recvmsg ( SOCKET s, struct msghdr *msg, int flags ); +size_t sendmsg ( SOCKET s, const struct msghdr *msg, int flags ); + +#ifdef __cplusplus +} +#endif + +#endif // _WINPORTING_H_ diff --git a/include/win/wintime.h b/include/win/wintime.h new file mode 100644 index 000000000..cc37b52c6 --- /dev/null +++ b/include/win/wintime.h @@ -0,0 +1,42 @@ +#ifndef INC__WIN_WINTIME +#define INC__WIN_WINTIME + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CLOCK_REALTIME +#define CLOCK_REALTIME 1 +#endif + +int clock_gettime(int X, struct timespec *ts); + +#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) + #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 +#else + #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL +#endif + + +#ifndef _TIMEZONE_DEFINED /* also in sys/time.h */ +#define _TIMEZONE_DEFINED +struct timezone +{ + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; +#endif + +void timeradd(struct timeval *a, struct timeval *b, struct timeval *result); + +int gettimeofday(struct timeval* tp, struct timezone* tz); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/scripts/check-deps b/scripts/check-deps new file mode 100755 index 000000000..f1e6fea9f --- /dev/null +++ b/scripts/check-deps @@ -0,0 +1,59 @@ +#!/bin/bash +# Now Check if Tcl is installed, run it if so. The backslash extends the comment and hides the line below against Tcl interpreter \ +exec tclsh "$0" "$@" || echo "Please install 'tcl' package first - it's required to run any other scripts here." && exit 1 + +# +# 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 +# + +set ok 1 + +set nothave [catch {set cmake [exec cmake --version]}] + +if { $nothave } { + puts "CMake version >= 2.6 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" + 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 + +if { $ok } { + puts "All dependencies satisfied, you should be good to go." + exit 0 +} + +puts "Please fix the above findings before compiling" +exit 1 + + diff --git a/scripts/haiUtil.cmake b/scripts/haiUtil.cmake new file mode 100644 index 000000000..0dd4e3b98 --- /dev/null +++ b/scripts/haiUtil.cmake @@ -0,0 +1,49 @@ +# +# 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 +# + + +# Useful for combinging paths + +function(adddirname prefix lst out_lst) + set(output) + foreach(item ${lst}) + list(APPEND output "${prefix}/${item}") + endforeach() + set(${out_lst} ${output} PARENT_SCOPE) +endfunction() + +# Splits a version formed as "major.minor.patch" recorded in variable 'prefix' +# and writes it into variables started with 'prefix' and ended with _MAJOR, _MINOR and _PATCH. +MACRO(set_version_variables prefix value) + string(REPLACE "." ";" VERSION_LIST ${value}) + list(GET VERSION_LIST 0 ${prefix}_MAJOR) + list(GET VERSION_LIST 1 ${prefix}_MINOR) + list(GET VERSION_LIST 2 ${prefix}_PATCH) + set(${prefix}_DEFINESTR "") +ENDMACRO(set_version_variables) + +# Sets given variable to 1, if the condition that follows it is satisfied. +# Otherwise set it to 0. +MACRO(set_if varname) + IF(${ARGN}) + SET(${varname} 1) + ELSE(${ARGN}) + SET(${varname} 0) + ENDIF(${ARGN}) +ENDMACRO(set_if) + diff --git a/scripts/haisrt.pc.in b/scripts/haisrt.pc.in new file mode 100644 index 000000000..c103f2ddd --- /dev/null +++ b/scripts/haisrt.pc.in @@ -0,0 +1,11 @@ +prefix=@INSTALLDIR@ +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: haisrt +Description: SRT library set +Version: @HAISRT_VERSION@ +Libs: -L${libdir} -l@NAME_haisrt@ -l@NAME_haicrypt@ @IFNEEDED_HAISRTBASE@ @IFNEEDED_HAISRT_LDFLAGS@ +Cflags: -I${includedir} + diff --git a/scripts/sfplay b/scripts/sfplay new file mode 100755 index 000000000..acf7ef9a9 --- /dev/null +++ b/scripts/sfplay @@ -0,0 +1,40 @@ +#!/bin/bash + +# +# 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 +# + +FFPLAY=`type -p ffplay || echo none` +if [[ $FFPLAY == "none" ]]; then + echo >&2 "ERROR: ffplay not available to call. Please install ffplay first." + exit 1 +fi + +DIRNAME=`dirname $0` +if [[ ! -x $DIRNAME/stransmit ]]; then + echo >&2 "ERROR: you need 'stransmit' tool from SRT package in the same directory as this script." + exit 1 +fi + +SRCLOC=$1 +if [[ -z $SRCLOC ]]; then + echo >&2 "Usage: `basename $0` " + exit 1 +fi + +$DIRNAME/stransmit "$1" file://con/ | ffplay - + diff --git a/srtcore/HEADERS.maf b/srtcore/HEADERS.maf new file mode 100644 index 000000000..af2fbccb4 --- /dev/null +++ b/srtcore/HEADERS.maf @@ -0,0 +1,8 @@ +# This file is informational only. The statements in CMakeLists.txt (or whatever else build system) +# should rely on the information found here. If possible, it can retrieve it automatically. +PUBLIC HEADERS +srt.h +udt.h +logging_api.h +PROTECTED HEADERS +srt4udt.h diff --git a/srtcore/Makefile b/srtcore/Makefile new file mode 100644 index 000000000..4cef6c63a --- /dev/null +++ b/srtcore/Makefile @@ -0,0 +1,63 @@ +C++ = g++ + +ifndef os + os = LINUX +endif + +ifndef arch + arch = IA32 +endif + +CCFLAGS = -fPIC -Wall -Wextra -D$(os) -finline-functions -O3 -fno-strict-aliasing -fvisibility=hidden + +ifdef HAI_PATCH +C++ = $(CROSS_COMPILE)g++ +CCFLAGS += -DHAI_PATCH=1 +endif + +ifeq ($(arch), IA32) + CCFLAGS += -DIA32 +endif + +ifeq ($(arch), POWERPC) + CCFLAGS += -mcpu=powerpc +endif + +ifeq ($(arch), SPARC) + CCFLAGS += -DSPARC +endif + +ifeq ($(arch), IA64) + CCFLAGS += -DIA64 +endif + +ifeq ($(arch), AMD64) + CCFLAGS += -DAMD64 +endif + +OBJS = api.o buffer.o cache.o ccc.o channel.o common.o core.o epoll.o list.o md5.o packet.o queue.o window.o +DIR = $(shell pwd) + +all: libudt.so libudt.a udt + +%.o: %.cpp %.h udt.h + $(C++) $(CCFLAGS) $< -c + +libudt.so: $(OBJS) +ifneq ($(os), OSX) + $(C++) -shared -o $@ $^ +else + $(C++) -dynamiclib -o libudt.dylib -lstdc++ -lpthread -lm $^ +endif + +libudt.a: $(OBJS) + ar -rcs $@ $^ + +udt: + cp udt.h udt + +clean: + rm -f *.o *.so *.dylib *.a udt + +install: + export LD_LIBRARY_PATH=$(DIR):$$LD_LIBRARY_PATH diff --git a/srtcore/api.cpp b/srtcore/api.cpp new file mode 100644 index 000000000..d8bb4b6d0 --- /dev/null +++ b/srtcore/api.cpp @@ -0,0 +1,2788 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 07/09/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#include +#include +#include + +#ifdef WIN32 + #include + #include + #ifdef LEGACY_WIN32 + #include + #endif + #include +#else + #include +#endif +#include +#include "api.h" +#include "core.h" +#include "logging.h" +#include "threadname.h" + +using namespace std; + +extern logging::LogConfig logger_config; + +extern logging::Logger mglog; + +CUDTSocket::CUDTSocket(): +m_Status(UDT_INIT), +m_TimeStamp(0), +m_iIPversion(0), +m_pSelfAddr(NULL), +m_pPeerAddr(NULL), +m_SocketID(0), +m_ListenSocket(0), +m_PeerID(0), +m_iISN(0), +m_pUDT(NULL), +m_pQueuedSockets(NULL), +m_pAcceptSockets(NULL), +m_AcceptCond(), +m_AcceptLock(), +m_uiBackLog(0), +m_iMuxID(-1) +{ + pthread_mutex_init(&m_AcceptLock, NULL); + pthread_cond_init(&m_AcceptCond, NULL); + pthread_mutex_init(&m_ControlLock, NULL); +} + +CUDTSocket::~CUDTSocket() +{ + if (m_iIPversion == AF_INET) + { + delete (sockaddr_in*)m_pSelfAddr; + delete (sockaddr_in*)m_pPeerAddr; + } + else + { + delete (sockaddr_in6*)m_pSelfAddr; + delete (sockaddr_in6*)m_pPeerAddr; + } + + delete m_pUDT; + m_pUDT = NULL; + + delete m_pQueuedSockets; + delete m_pAcceptSockets; + + pthread_mutex_destroy(&m_AcceptLock); + pthread_cond_destroy(&m_AcceptCond); + pthread_mutex_destroy(&m_ControlLock); +} + +//////////////////////////////////////////////////////////////////////////////// + +CUDTUnited::CUDTUnited(): +m_Sockets(), +m_ControlLock(), +m_IDLock(), +m_SocketIDGenerator(0), +m_TLSError(), +m_mMultiplexer(), +m_MultiplexerLock(), +m_pCache(NULL), +m_bClosing(false), +m_GCStopLock(), +m_GCStopCond(), +m_InitLock(), +m_iInstanceCount(0), +m_bGCStatus(false), +m_GCThread(), +m_ClosedSockets() +{ + // Socket ID MUST start from a random value + srand((unsigned int)CTimer::getTime()); + m_SocketIDGenerator = 1 + (int)((1 << 30) * (double(rand()) / RAND_MAX)); + + pthread_mutex_init(&m_ControlLock, NULL); + pthread_mutex_init(&m_IDLock, NULL); + pthread_mutex_init(&m_InitLock, NULL); + + pthread_key_create(&m_TLSError, TLSDestroy); + + m_pCache = new CCache; +} + +CUDTUnited::~CUDTUnited() +{ + pthread_mutex_destroy(&m_ControlLock); + pthread_mutex_destroy(&m_IDLock); + pthread_mutex_destroy(&m_InitLock); + + pthread_key_delete(m_TLSError); + + delete m_pCache; +} + +std::string CUDTUnited::CONID(UDTSOCKET sock) const +{ + if ( sock == 0 ) + return ""; + + std::ostringstream os; + os << "%" << sock << ":"; + return os.str(); +} + +int CUDTUnited::startup() +{ + CGuard gcinit(m_InitLock); + + if (m_iInstanceCount++ > 0) + return 0; + + // Global initialization code + #ifdef WIN32 + WORD wVersionRequested; + WSADATA wsaData; + wVersionRequested = MAKEWORD(2, 2); + + if (0 != WSAStartup(wVersionRequested, &wsaData)) + throw CUDTException(MJ_SETUP, MN_NONE, WSAGetLastError()); + #endif + + //init CTimer::EventLock + + if (m_bGCStatus) + return true; + + m_bClosing = false; + pthread_mutex_init(&m_GCStopLock, NULL); + pthread_cond_init(&m_GCStopCond, NULL); + + ThreadName tn("SRT:GC"); + pthread_create(&m_GCThread, NULL, garbageCollect, this); + + m_bGCStatus = true; + + return 0; +} + +int CUDTUnited::cleanup() +{ + CGuard gcinit(m_InitLock); + + if (--m_iInstanceCount > 0) + return 0; + + //destroy CTimer::EventLock + + if (!m_bGCStatus) + return 0; + + m_bClosing = true; + pthread_cond_signal(&m_GCStopCond); + pthread_join(m_GCThread, NULL); + pthread_mutex_destroy(&m_GCStopLock); + pthread_cond_destroy(&m_GCStopCond); + + m_bGCStatus = false; + + // Global destruction code + #ifdef WIN32 + WSACleanup(); + #endif + + return 0; +} + +UDTSOCKET CUDTUnited::newSocket(int af, int type) +{ + if ((type != SOCK_STREAM) && (type != SOCK_DGRAM)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + CUDTSocket* ns = NULL; + + try + { + ns = new CUDTSocket; + ns->m_pUDT = new CUDT; + if (af == AF_INET) + { + ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in); + ((sockaddr_in*)(ns->m_pSelfAddr))->sin_port = 0; + } + else + { + ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in6); + ((sockaddr_in6*)(ns->m_pSelfAddr))->sin6_port = 0; + } + } + catch (...) + { + delete ns; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + + CGuard::enterCS(m_IDLock); + ns->m_SocketID = -- m_SocketIDGenerator; + CGuard::leaveCS(m_IDLock); + + ns->m_Status = UDT_INIT; + ns->m_ListenSocket = 0; + ns->m_pUDT->m_SocketID = ns->m_SocketID; + ns->m_pUDT->m_iSockType = (type == SOCK_STREAM) ? UDT_STREAM : UDT_DGRAM; + ns->m_pUDT->m_iIPversion = ns->m_iIPversion = af; + ns->m_pUDT->m_pCache = m_pCache; + + // protect the m_Sockets structure. + CGuard::enterCS(m_ControlLock); + try + { + LOGC(mglog.Debug) << CONID(ns->m_SocketID) << "newSocket: mapping socket " << ns->m_SocketID; + m_Sockets[ns->m_SocketID] = ns; + } + catch (...) + { + //failure and rollback + CGuard::leaveCS(m_ControlLock); + delete ns; + ns = NULL; + } + CGuard::leaveCS(m_ControlLock); + + if (!ns) + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + + return ns->m_SocketID; +} + +int CUDTUnited::newConnection(const UDTSOCKET listen, const sockaddr* peer, CHandShake* hs) +{ + CUDTSocket* ns = NULL; + CUDTSocket* ls = locate(listen); + + if (!ls) + return -1; + + // if this connection has already been processed + if ((ns = locate(peer, hs->m_iID, hs->m_iISN)) != NULL) + { + if (ns->m_pUDT->m_bBroken) + { + // last connection from the "peer" address has been broken + ns->m_Status = UDT_CLOSED; + ns->m_TimeStamp = CTimer::getTime(); + + CGuard::enterCS(ls->m_AcceptLock); + ls->m_pQueuedSockets->erase(ns->m_SocketID); + ls->m_pAcceptSockets->erase(ns->m_SocketID); + CGuard::leaveCS(ls->m_AcceptLock); + } + else + { + // connection already exist, this is a repeated connection request + // respond with existing HS information + + hs->m_iISN = ns->m_pUDT->m_iISN; + hs->m_iMSS = ns->m_pUDT->m_iMSS; + hs->m_iFlightFlagSize = ns->m_pUDT->m_iFlightFlagSize; + hs->m_iReqType = URQ_CONCLUSION; + hs->m_iID = ns->m_SocketID; + + return 0; + + //except for this situation a new connection should be started + } + } + + // exceeding backlog, refuse the connection request + if (ls->m_pQueuedSockets->size() >= ls->m_uiBackLog) + return -1; + + try + { + ns = new CUDTSocket; + ns->m_pUDT = new CUDT(*(ls->m_pUDT)); + if (ls->m_iIPversion == AF_INET) + { + ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in); + ((sockaddr_in*)(ns->m_pSelfAddr))->sin_port = 0; + ns->m_pPeerAddr = (sockaddr*)(new sockaddr_in); + memcpy(ns->m_pPeerAddr, peer, sizeof(sockaddr_in)); + } + else + { + ns->m_pSelfAddr = (sockaddr*)(new sockaddr_in6); + ((sockaddr_in6*)(ns->m_pSelfAddr))->sin6_port = 0; + ns->m_pPeerAddr = (sockaddr*)(new sockaddr_in6); + memcpy(ns->m_pPeerAddr, peer, sizeof(sockaddr_in6)); + } + } + catch (...) + { + delete ns; + return -1; + } + + CGuard::enterCS(m_IDLock); + ns->m_SocketID = -- m_SocketIDGenerator; + LOGC(mglog.Debug).form("newConnection: generated socket id %d\n", ns->m_SocketID); + CGuard::leaveCS(m_IDLock); + + ns->m_ListenSocket = listen; + ns->m_iIPversion = ls->m_iIPversion; + ns->m_pUDT->m_SocketID = ns->m_SocketID; + ns->m_PeerID = hs->m_iID; + ns->m_iISN = hs->m_iISN; + + int error = 0; + + // These can throw exception only when the memory allocation failed. + // CUDT::connect() translates exception into CUDTException. + // CUDT::open() may only throw original std::bad_alloc from new. + // This is only to make the library extra safe (when your machine lacks + // 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); + } + 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; + } + + ns->m_Status = UDT_CONNECTED; + + // copy address information of local node + ns->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(ns->m_pSelfAddr); + CIPAddress::pton(ns->m_pSelfAddr, ns->m_pUDT->m_piSelfIP, ns->m_iIPversion); + + // protect the m_Sockets structure. + 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); + } + catch (...) + { + error = 2; + } + CGuard::leaveCS(m_ControlLock); + + CGuard::enterCS(ls->m_AcceptLock); + try + { + ls->m_pQueuedSockets->insert(ns->m_SocketID); + } + catch (...) + { + error = 3; + } + CGuard::leaveCS(ls->m_AcceptLock); + + // acknowledge users waiting for new connections on the listening socket + m_EPoll.update_events(listen, ls->m_pUDT->m_sPollID, UDT_EPOLL_IN, true); + + CTimer::triggerEvent(); + + ERR_ROLLBACK: + // XXX the exact value of 'error' is ignored + if (error > 0) + { + ns->m_pUDT->close(); + ns->m_Status = UDT_CLOSED; + ns->m_TimeStamp = CTimer::getTime(); + + return -1; + } + + // wake up a waiting accept() call + pthread_mutex_lock(&(ls->m_AcceptLock)); + pthread_cond_signal(&(ls->m_AcceptCond)); + pthread_mutex_unlock(&(ls->m_AcceptLock)); + + return 1; +} + +CUDT* CUDTUnited::lookup(const UDTSOCKET u) +{ + // protects the m_Sockets structure + CGuard cg(m_ControlLock); + + map::iterator i = m_Sockets.find(u); + + if ((i == m_Sockets.end()) || (i->second->m_Status == UDT_CLOSED)) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + return i->second->m_pUDT; +} + +UDTSTATUS CUDTUnited::getStatus(const UDTSOCKET u) +{ + // protects the m_Sockets structure + CGuard cg(m_ControlLock); + + map::iterator i = m_Sockets.find(u); + + if (i == m_Sockets.end()) + { + if (m_ClosedSockets.find(u) != m_ClosedSockets.end()) + return UDT_CLOSED; + + return UDT_NONEXIST; + } + CUDTSocket* s = i->second; + + if (s->m_pUDT->m_bBroken) + return UDT_BROKEN; + + // Connecting timed out + if ((s->m_Status == UDT_CONNECTING) && !s->m_pUDT->m_bConnecting) + return UDT_BROKEN; + + return s->m_Status; +} + +int CUDTUnited::bind(const UDTSOCKET u, const sockaddr* name, int namelen) +{ + CUDTSocket* s = locate(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CGuard cg(s->m_ControlLock); + + // cannot bind a socket more than once + if (UDT_INIT != s->m_Status) + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + + // check the size of SOCKADDR structure + if (s->m_iIPversion == AF_INET) + { + if (namelen != sizeof(sockaddr_in)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + else + { + if (namelen != sizeof(sockaddr_in6)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + s->m_pUDT->open(); + updateMux(s, name); + s->m_Status = UDT_OPENED; + + // copy address information of local node + s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr); + + return 0; +} + +int CUDTUnited::bind(UDTSOCKET u, UDPSOCKET udpsock) +{ + CUDTSocket* s = locate(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CGuard cg(s->m_ControlLock); + + // cannot bind a socket more than once + if (UDT_INIT != s->m_Status) + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + + sockaddr_in name4; + sockaddr_in6 name6; + sockaddr* name; + socklen_t namelen; + + if (s->m_iIPversion == AF_INET) + { + namelen = sizeof(sockaddr_in); + name = (sockaddr*)&name4; + } + else + { + namelen = sizeof(sockaddr_in6); + name = (sockaddr*)&name6; + } + + if (::getsockname(udpsock, name, &namelen) == -1) + throw CUDTException(MJ_NOTSUP, MN_INVAL); + + s->m_pUDT->open(); + updateMux(s, name, &udpsock); + s->m_Status = UDT_OPENED; + + // copy address information of local node + s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr); + + return 0; +} + +int CUDTUnited::listen(const UDTSOCKET u, int backlog) +{ + if (backlog <= 0 ) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + // Don't search for the socket if it's already -1; this never is a valid socket. + if ( u == UDT::INVALID_SOCK ) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CUDTSocket* s = locate(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CGuard cg(s->m_ControlLock); + + // NOTE: since now the socket is protected against simultaneous access. + // In the meantime the socket might have been closed, which means that + // it could have changed the state. It could be also set listen in another + // thread, so check it out. + + // do nothing if the socket is already listening + if (s->m_Status == UDT_LISTENING) + return 0; + + // a socket can listen only if is in OPENED status + if (s->m_Status != UDT_OPENED) + throw CUDTException(MJ_NOTSUP, MN_ISUNBOUND, 0); + + // [[using assert(s->m_Status == OPENED)]]; + + // listen is not supported in rendezvous connection setup + if (s->m_pUDT->m_bRendezvous) + throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); + + s->m_uiBackLog = backlog; + + try + { + s->m_pQueuedSockets = new set; + s->m_pAcceptSockets = new set; + } + catch (...) + { + delete s->m_pQueuedSockets; + delete s->m_pAcceptSockets; // XXX If this was exception-interrupted, then nothing is allocated! + + // XXX Translated std::bad_alloc into CUDTException specifying memory allocation failure... + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + + // [[using assert(s->m_Status == OPENED)]]; // (still, unchanged) + + s->m_pUDT->setListenState(); // propagates CUDTException, if thrown, remains in OPENED state if so. + s->m_Status = UDT_LISTENING; + + return 0; +} + +UDTSOCKET CUDTUnited::accept(const UDTSOCKET listen, sockaddr* addr, int* addrlen) +{ + if ((addr) && (!addrlen)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + CUDTSocket* ls = locate(listen); + + if (ls == NULL) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + // the "listen" socket must be in LISTENING status + if (UDT_LISTENING != ls->m_Status) + throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); + + // no "accept" in rendezvous connection setup + if (ls->m_pUDT->m_bRendezvous) + throw CUDTException(MJ_NOTSUP, MN_ISRENDEZVOUS, 0); + + UDTSOCKET u = CUDT::INVALID_SOCK; + bool accepted = false; + + // !!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 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()); + ls->m_pAcceptSockets->insert(ls->m_pAcceptSockets->end(), u); // why suggest the position - it is std::set! + 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) + { + // non-blocking receiving, no connection available + if (!ls->m_pUDT->m_bSynRecving) + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + + // listening socket is closed + throw CUDTException(MJ_NOTSUP, MN_NOLISTEN, 0); + } + + if ((addr != NULL) && (addrlen != NULL)) + { + CUDTSocket* s = locate(u); + if (s == NULL) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CGuard cg(s->m_ControlLock); + + if (AF_INET == s->m_iIPversion) + *addrlen = sizeof(sockaddr_in); + else + *addrlen = sizeof(sockaddr_in6); + + // copy address information of peer node + memcpy(addr, s->m_pPeerAddr, *addrlen); + } + + return u; +} + +int CUDTUnited::connect(const UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +{ + CUDTSocket* s = locate(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + 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)? + if (AF_INET == s->m_iIPversion) + { + if (namelen != sizeof(sockaddr_in)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + else + { + if (namelen != sizeof(sockaddr_in6)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + + // a socket can "connect" only if it is in INIT or OPENED status + if (UDT_INIT == s->m_Status) + { + if (!s->m_pUDT->m_bRendezvous) + { + s->m_pUDT->open(); + updateMux(s); // <<---- updateMux -> C(Snd|Rcv)Queue::init -> pthread_create(...C(Snd|Rcv)Queue::worker...) + s->m_Status = UDT_OPENED; + } + else + throw CUDTException(MJ_NOTSUP, MN_ISRENDUNBOUND, 0); + } + else if (UDT_OPENED != s->m_Status) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + + // connect_complete() may be called before connect() returns. + // So we need to update the status before connect() is called, + // otherwise the status may be overwritten with wrong value (CONNECTED vs. CONNECTING). + s->m_Status = UDT_CONNECTING; + + /* + * In blocking mode, connect can block for up to 30 seconds for 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); + } + catch (CUDTException& e) + { + // 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; + } + + + // The same thing is done USING InvertedLock! + ////if (s->m_pUDT->m_bSynRecving) + //// CGuard::enterCS(s->m_ControlLock); + + // record peer address + delete s->m_pPeerAddr; + if (AF_INET == s->m_iIPversion) + { + s->m_pPeerAddr = (sockaddr*)(new sockaddr_in); + memcpy(s->m_pPeerAddr, name, sizeof(sockaddr_in)); + } + else + { + s->m_pPeerAddr = (sockaddr*)(new sockaddr_in6); + memcpy(s->m_pPeerAddr, name, sizeof(sockaddr_in6)); + } + + // CGuard destructor will delete cg and unlock s->m_ControlLock + + return 0; +} + +void CUDTUnited::connect_complete(const UDTSOCKET u) +{ + CUDTSocket* s = locate(u); + if (!s) + 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 by garbage collection and will cause leak + s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr(s->m_pSelfAddr); + CIPAddress::pton(s->m_pSelfAddr, s->m_pUDT->m_piSelfIP, s->m_iIPversion); + + s->m_Status = UDT_CONNECTED; +} + +int CUDTUnited::close(const UDTSOCKET u) +{ + CUDTSocket* s = locate(u); + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + CGuard socket_cg(s->m_ControlLock); + + if (s->m_Status == UDT_LISTENING) + { + if (s->m_pUDT->m_bBroken) + return 0; + + s->m_TimeStamp = CTimer::getTime(); + s->m_pUDT->m_bBroken = true; + + // NOTE: (changed by Sektor) + // Leave all the closing activities for garbageCollect to happen, + // however remove the listener from the RcvQueue IMMEDIATELY. + // This is because the listener socket is useless anyway and should + // not be used for anything NEW since now. + + // But there's no reason to destroy the world by occupying the + // listener slot in the RcvQueue. + + { + CGuard cg(s->m_pUDT->m_ConnectionLock); + s->m_pUDT->m_bListening = false; + s->m_pUDT->m_pRcvQueue->removeListener(s->m_pUDT); + } + + // broadcast all "accept" waiting + pthread_mutex_lock(&(s->m_AcceptLock)); + 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(); + + return 0; +} + +int CUDTUnited::getpeername(const UDTSOCKET u, sockaddr* name, int* namelen) +{ + if (UDT_CONNECTED != getStatus(u)) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + CUDTSocket* s = locate(u); + + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (!s->m_pUDT->m_bConnected || s->m_pUDT->m_bBroken) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + if (AF_INET == s->m_iIPversion) + *namelen = sizeof(sockaddr_in); + else + *namelen = sizeof(sockaddr_in6); + + // copy address information of peer node + memcpy(name, s->m_pPeerAddr, *namelen); + + return 0; +} + +int CUDTUnited::getsockname(const UDTSOCKET u, sockaddr* name, int* namelen) +{ + CUDTSocket* s = locate(u); + + if (!s) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (s->m_pUDT->m_bBroken) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + + if (UDT_INIT == s->m_Status) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + if (AF_INET == s->m_iIPversion) + *namelen = sizeof(sockaddr_in); + else + *namelen = sizeof(sockaddr_in6); + + // copy address information of local node + memcpy(name, s->m_pSelfAddr, *namelen); + + return 0; +} + +int CUDTUnited::select(ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout) +{ + uint64_t entertime = CTimer::getTime(); + + uint64_t to; + if (!timeout) + to = 0xFFFFFFFFFFFFFFFFULL; + else + to = timeout->tv_sec * 1000000 + timeout->tv_usec; + + // initialize results + int count = 0; + set rs, ws, es; + + // retrieve related UDT sockets + vector ru, wu, eu; + CUDTSocket* s; + if (readfds) + for (set::iterator i1 = readfds->begin(); i1 != readfds->end(); ++ i1) + { + if (UDT_BROKEN == getStatus(*i1)) + { + rs.insert(*i1); + ++ count; + } + else if (!(s = locate(*i1))) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + else + ru.push_back(s); + } + if (writefds) + for (set::iterator i2 = writefds->begin(); i2 != writefds->end(); ++ i2) + { + if (UDT_BROKEN == getStatus(*i2)) + { + ws.insert(*i2); + ++ count; + } + else if (!(s = locate(*i2))) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + else + wu.push_back(s); + } + if (exceptfds) + for (set::iterator i3 = exceptfds->begin(); i3 != exceptfds->end(); ++ i3) + { + if (UDT_BROKEN == getStatus(*i3)) + { + es.insert(*i3); + ++ count; + } + else if (!(s = locate(*i3))) + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL, 0); + else + eu.push_back(s); + } + + do + { + // query read sockets + for (vector::iterator j1 = ru.begin(); j1 != ru.end(); ++ j1) + { + s = *j1; + + 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)) + || (s->m_pUDT->m_bListening && (s->m_pQueuedSockets->size() > 0)) + || (s->m_Status == UDT_CLOSED)) + { + rs.insert(s->m_SocketID); + ++ count; + } + } + + // query write sockets + for (vector::iterator j2 = wu.begin(); j2 != wu.end(); ++ j2) + { + s = *j2; + + if ((s->m_pUDT->m_bConnected && (s->m_pUDT->m_pSndBuffer->getCurrBufSize() < s->m_pUDT->m_iSndBufSize)) + || s->m_pUDT->m_bBroken || !s->m_pUDT->m_bConnected || (s->m_Status == UDT_CLOSED)) + { + ws.insert(s->m_SocketID); + ++ count; + } + } + + // query exceptions on sockets + for (vector::iterator j3 = eu.begin(); j3 != eu.end(); ++ j3) + { + // check connection request status, not supported now + } + + if (0 < count) + break; + + CTimer::waitForEvent(); + } while (to > CTimer::getTime() - entertime); + + if (readfds) + *readfds = rs; + + if (writefds) + *writefds = ws; + + if (exceptfds) + *exceptfds = es; + + return count; +} + +int CUDTUnited::selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, int64_t msTimeOut) +{ + uint64_t entertime = CTimer::getTime(); + + uint64_t to; + if (msTimeOut >= 0) + to = msTimeOut * 1000; + else + to = 0xFFFFFFFFFFFFFFFFULL; + + // initialize results + int count = 0; + if (readfds) + readfds->clear(); + if (writefds) + writefds->clear(); + if (exceptfds) + exceptfds->clear(); + + do + { + for (vector::const_iterator i = fds.begin(); i != fds.end(); ++ i) + { + CUDTSocket* s = locate(*i); + + if ((!s) || s->m_pUDT->m_bBroken || (s->m_Status == UDT_CLOSED)) + { + if (exceptfds) + { + exceptfds->push_back(*i); + ++ count; + } + continue; + } + + if (readfds) + { + 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))) + { + readfds->push_back(s->m_SocketID); + ++ count; + } + } + + if (writefds) + { + if (s->m_pUDT->m_bConnected && (s->m_pUDT->m_pSndBuffer->getCurrBufSize() < s->m_pUDT->m_iSndBufSize)) + { + writefds->push_back(s->m_SocketID); + ++ count; + } + } + } + + if (count > 0) + break; + + CTimer::waitForEvent(); + } while (to > CTimer::getTime() - entertime); + + return count; +} + +int CUDTUnited::epoll_create() +{ + return m_EPoll.create(); +} + +int CUDTUnited::epoll_add_usock(const int eid, const UDTSOCKET u, const int* events) +{ + CUDTSocket* s = locate(u); + int ret = -1; + if (s) + { + ret = m_EPoll.add_usock(eid, u, events); + s->m_pUDT->addEPoll(eid); + } + else + { + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + } + + return ret; +} + +int CUDTUnited::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +{ + return m_EPoll.add_ssock(eid, s, events); +} + +int CUDTUnited::epoll_update_usock(const int eid, const UDTSOCKET u, const int* events) +{ + CUDTSocket* s = locate(u); + int ret = -1; + if (s) + { + ret = m_EPoll.update_usock(eid, u, events); + s->m_pUDT->addEPoll(eid); + } + else + { + throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + } + + return ret; +} + +int CUDTUnited::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +{ + return m_EPoll.update_ssock(eid, s, events); +} + +int CUDTUnited::epoll_remove_usock(const int eid, const UDTSOCKET u) +{ + int ret = m_EPoll.remove_usock(eid, u); + + CUDTSocket* s = locate(u); + if (s) + { + s->m_pUDT->removeEPoll(eid); + } + //else + //{ + // throw CUDTException(MJ_NOTSUP, MN_SIDINVAL); + //} + + return ret; +} + +int CUDTUnited::epoll_remove_ssock(const int eid, const SYSSOCKET s) +{ + return m_EPoll.remove_ssock(eid, s); +} + +int CUDTUnited::epoll_wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) +{ + return m_EPoll.wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); +} + +int CUDTUnited::epoll_release(const int eid) +{ + return m_EPoll.release(eid); +} + +CUDTSocket* CUDTUnited::locate(const UDTSOCKET u) +{ + CGuard cg(m_ControlLock); + + map::iterator i = m_Sockets.find(u); + + if ((i == m_Sockets.end()) || (i->second->m_Status == UDT_CLOSED)) + return NULL; + + return i->second; +} + +CUDTSocket* CUDTUnited::locate(const sockaddr* peer, const UDTSOCKET id, int32_t isn) +{ + CGuard cg(m_ControlLock); + + map >::iterator i = m_PeerRec.find(CUDTSocket::getPeerSpec(id, isn)); + if (i == m_PeerRec.end()) + return NULL; + + for (set::iterator j = i->second.begin(); j != i->second.end(); ++ j) + { + map::iterator k = m_Sockets.find(*j); + // this socket might have been closed and moved m_ClosedSockets + if (k == m_Sockets.end()) + continue; + + if (CIPAddress::ipcmp(peer, k->second->m_pPeerAddr, k->second->m_iIPversion)) + return k->second; + } + + return NULL; +} + +void CUDTUnited::checkBrokenSockets() +{ + CGuard cg(m_ControlLock); + + // set of sockets To Be Closed and To Be Removed + vector tbc; + vector tbr; + + for (map::iterator i = m_Sockets.begin(); i != m_Sockets.end(); ++ i) + { + CUDTSocket* s = i->second; + +// LOGC(mglog.Debug).form("checking EXISTING socket: %d\n", i->first); + // check broken connection + if (s->m_pUDT->m_bBroken) + { + if (s->m_Status == UDT_LISTENING) + { + uint64_t elapsed = CTimer::getTime() - s->m_TimeStamp; + // for a listening socket, it should wait an extra 3 seconds in case a client is connecting + if (elapsed < 3000000) // XXX MAKE A SYMBOLIC CONSTANT HERE! + { +// LOGC(mglog.Debug).form("STILL KEEPING socket %d (listener, too early, w8 %fs)\n", i->first, double(elapsed)/1000000); + continue; + } + } + else if ((s->m_pUDT->m_pRcvBuffer != NULL) + // FIXED: calling isRcvDataAvailable() just to get the information whether there are any + // data waiting in the buffer, NOT WHETHER THEY ARE ALSO READY TO PLAY at the time when + // this function is called (isRcvDataReady also checks if the available data is "ready to play"). + && s->m_pUDT->m_pRcvBuffer->isRcvDataAvailable() + && (s->m_pUDT->m_iBrokenCounter -- > 0)) + { +// LOGC(mglog.Debug).form("STILL KEEPING socket (still have data): %d\n", i->first); + // if there is still data in the receiver buffer, wait longer + continue; + } + +// LOGC(mglog.Debug).form("moving socket to CLOSED: %d\n", i->first); + + //close broken connections and start removal timer + s->m_Status = UDT_CLOSED; + s->m_TimeStamp = CTimer::getTime(); + tbc.push_back(i->first); + m_ClosedSockets[i->first] = s; + + // remove from listener's queue + map::iterator ls = m_Sockets.find(s->m_ListenSocket); + if (ls == m_Sockets.end()) + { + ls = m_ClosedSockets.find(s->m_ListenSocket); + if (ls == m_ClosedSockets.end()) + continue; + } + + CGuard::enterCS(ls->second->m_AcceptLock); + ls->second->m_pQueuedSockets->erase(s->m_SocketID); + ls->second->m_pAcceptSockets->erase(s->m_SocketID); + CGuard::leaveCS(ls->second->m_AcceptLock); + } + } + + for (map::iterator j = m_ClosedSockets.begin(); j != m_ClosedSockets.end(); ++ j) + { +// LOGC(mglog.Debug).form("checking CLOSED socket: %d\n", j->first); + if (j->second->m_pUDT->m_ullLingerExpiration > 0) + { + // asynchronous close: + if ((!j->second->m_pUDT->m_pSndBuffer) || (0 == j->second->m_pUDT->m_pSndBuffer->getCurrBufSize()) || (j->second->m_pUDT->m_ullLingerExpiration <= CTimer::getTime())) + { + j->second->m_pUDT->m_ullLingerExpiration = 0; + j->second->m_pUDT->m_bClosing = true; + j->second->m_TimeStamp = CTimer::getTime(); + } + } + + // timeout 1 second to destroy a socket AND it has been removed from RcvUList + if ((CTimer::getTime() - j->second->m_TimeStamp > 1000000) && ((!j->second->m_pUDT->m_pRNode) || !j->second->m_pUDT->m_pRNode->m_bOnList)) + { +// LOGC(mglog.Debug).form("will unref socket: %d\n", j->first); + tbr.push_back(j->first); + } + } + + // move closed sockets to the ClosedSockets structure + for (vector::iterator k = tbc.begin(); k != tbc.end(); ++ k) + m_Sockets.erase(*k); + + // remove those timeout sockets + for (vector::iterator l = tbr.begin(); l != tbr.end(); ++ l) + removeSocket(*l); +} + +void CUDTUnited::removeSocket(const UDTSOCKET u) +{ + map::iterator i = m_ClosedSockets.find(u); + + // invalid socket ID + if (i == m_ClosedSockets.end()) + return; + + // decrease multiplexer reference count, and remove it if necessary + const int mid = i->second->m_iMuxID; + + if (i->second->m_pQueuedSockets) + { + CGuard cg(i->second->m_AcceptLock); + + // if it is a listener, close all un-accepted sockets in its queue and remove them later + for (set::iterator q = i->second->m_pQueuedSockets->begin(); q != i->second->m_pQueuedSockets->end(); ++ q) + { + m_Sockets[*q]->m_pUDT->m_bBroken = true; + m_Sockets[*q]->m_pUDT->close(); + m_Sockets[*q]->m_TimeStamp = CTimer::getTime(); + m_Sockets[*q]->m_Status = UDT_CLOSED; + m_ClosedSockets[*q] = m_Sockets[*q]; + m_Sockets.erase(*q); + } + + } + + // remove from peer rec + map >::iterator j = m_PeerRec.find(i->second->getPeerSpec()); + if (j != m_PeerRec.end()) + { + j->second.erase(u); + if (j->second.empty()) + m_PeerRec.erase(j); + } + + /* + * Socket may be deleted while still having ePoll events set that would remains forever + * causing epoll_wait to unblock continuously for inexistent sockets. + * Get rid of all events for this socket. + */ + m_EPoll.update_events(u, i->second->m_pUDT->m_sPollID, UDT_EPOLL_IN|UDT_EPOLL_OUT|UDT_EPOLL_ERR, false); + + // delete this one + i->second->m_pUDT->close(); + delete i->second; + m_ClosedSockets.erase(i); + + map::iterator m; + m = m_mMultiplexer.find(mid); + if (m == m_mMultiplexer.end()) + { + //something is wrong!!! + return; + } + + m->second.m_iRefCount --; +// LOGC(mglog.Debug).form("unrefing underlying socket for %u: %u\n", u, m->second.m_iRefCount); + if (0 == m->second.m_iRefCount) + { + m->second.m_pChannel->close(); + delete m->second.m_pSndQueue; + delete m->second.m_pRcvQueue; + delete m->second.m_pTimer; + delete m->second.m_pChannel; + m_mMultiplexer.erase(m); + } +} + +void CUDTUnited::setError(CUDTException* e) +{ + delete (CUDTException*)pthread_getspecific(m_TLSError); + pthread_setspecific(m_TLSError, e); +} + +CUDTException* CUDTUnited::getError() +{ + if(!pthread_getspecific(m_TLSError)) + pthread_setspecific(m_TLSError, new CUDTException); + return (CUDTException*)pthread_getspecific(m_TLSError); +} + + +void CUDTUnited::updateMux(CUDTSocket* s, const sockaddr* addr, const UDPSOCKET* udpsock) +{ + CGuard cg(m_ControlLock); + + if ((s->m_pUDT->m_bReuseAddr) && (addr)) + { + int port = (AF_INET == s->m_pUDT->m_iIPversion) ? ntohs(((sockaddr_in*)addr)->sin_port) : ntohs(((sockaddr_in6*)addr)->sin6_port); + + // find a reusable address + for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++ i) + { + if ((i->second.m_iIPversion == s->m_pUDT->m_iIPversion) && (i->second.m_iMSS == s->m_pUDT->m_iMSS) +#ifdef SRT_ENABLE_IPOPTS + && (i->second.m_iIpTTL == s->m_pUDT->m_iIpTTL) && (i->second.m_iIpToS == s->m_pUDT->m_iIpToS) +#endif + && i->second.m_bReusable) + { + if (i->second.m_iPort == port) + { +// LOGC(mglog.Debug).form("reusing multiplexer for port %hd\n", port); + // reuse the existing multiplexer + ++ i->second.m_iRefCount; + s->m_pUDT->m_pSndQueue = i->second.m_pSndQueue; + s->m_pUDT->m_pRcvQueue = i->second.m_pRcvQueue; + s->m_iMuxID = i->second.m_iID; + return; + } + } + } + } + + // a new multiplexer is needed + CMultiplexer m; + m.m_iMSS = s->m_pUDT->m_iMSS; + m.m_iIPversion = s->m_pUDT->m_iIPversion; +#ifdef SRT_ENABLE_IPOPTS + m.m_iIpTTL = s->m_pUDT->m_iIpTTL; + m.m_iIpToS = s->m_pUDT->m_iIpToS; +#endif + m.m_iRefCount = 1; + m.m_bReusable = s->m_pUDT->m_bReuseAddr; + m.m_iID = s->m_SocketID; + + m.m_pChannel = new CChannel(s->m_pUDT->m_iIPversion); +#ifdef SRT_ENABLE_IPOPTS + m.m_pChannel->setIpTTL(s->m_pUDT->m_iIpTTL); + m.m_pChannel->setIpToS(s->m_pUDT->m_iIpToS); +#endif + m.m_pChannel->setSndBufSize(s->m_pUDT->m_iUDPSndBufSize); + m.m_pChannel->setRcvBufSize(s->m_pUDT->m_iUDPRcvBufSize); + + try + { + if (udpsock) + m.m_pChannel->open(*udpsock); + else + m.m_pChannel->open(addr); + } + catch (CUDTException& e) + { + m.m_pChannel->close(); + delete m.m_pChannel; + throw e; + } + + sockaddr* sa = (AF_INET == s->m_pUDT->m_iIPversion) ? (sockaddr*) new sockaddr_in : (sockaddr*) new sockaddr_in6; + m.m_pChannel->getSockAddr(sa); + m.m_iPort = (AF_INET == s->m_pUDT->m_iIPversion) ? ntohs(((sockaddr_in*)sa)->sin_port) : ntohs(((sockaddr_in6*)sa)->sin6_port); + if (AF_INET == s->m_pUDT->m_iIPversion) delete (sockaddr_in*)sa; else delete (sockaddr_in6*)sa; + + m.m_pTimer = new CTimer; + + m.m_pSndQueue = new CSndQueue; + m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer); + m.m_pRcvQueue = new CRcvQueue; + m.m_pRcvQueue->init(32, s->m_pUDT->m_iPayloadSize, m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer); + + m_mMultiplexer[m.m_iID] = m; + + s->m_pUDT->m_pSndQueue = m.m_pSndQueue; + s->m_pUDT->m_pRcvQueue = m.m_pRcvQueue; + 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) +{ + CGuard cg(m_ControlLock); + + int port = (AF_INET == ls->m_iIPversion) ? ntohs(((sockaddr_in*)ls->m_pSelfAddr)->sin_port) : ntohs(((sockaddr_in6*)ls->m_pSelfAddr)->sin6_port); + + // find the listener's address + for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++ i) + { + if (i->second.m_iPort == port) + { + LOGC(mglog.Debug).form("updateMux: reusing multiplexer for port %hd\n", port); + // reuse the existing multiplexer + ++ i->second.m_iRefCount; + s->m_pUDT->m_pSndQueue = i->second.m_pSndQueue; + s->m_pUDT->m_pRcvQueue = i->second.m_pRcvQueue; + s->m_iMuxID = i->second.m_iID; + return; + } + } +} + +void* CUDTUnited::garbageCollect(void* p) +{ + CUDTUnited* self = (CUDTUnited*)p; + + THREAD_STATE_INIT("SRT Collector"); + + CGuard gcguard(self->m_GCStopLock); + + while (!self->m_bClosing) + { + INCREMENT_THREAD_ITERATIONS(); + self->checkBrokenSockets(); + +//#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; + + pthread_cond_timedwait(&self->m_GCStopCond, &self->m_GCStopLock, &timeout); + } + + // remove all sockets and multiplexers + CGuard::enterCS(self->m_ControlLock); + for (map::iterator i = self->m_Sockets.begin(); i != self->m_Sockets.end(); ++ i) + { + i->second->m_pUDT->m_bBroken = true; + i->second->m_pUDT->close(); + i->second->m_Status = UDT_CLOSED; + i->second->m_TimeStamp = CTimer::getTime(); + self->m_ClosedSockets[i->first] = i->second; + + // remove from listener's queue + map::iterator ls = self->m_Sockets.find(i->second->m_ListenSocket); + if (ls == self->m_Sockets.end()) + { + ls = self->m_ClosedSockets.find(i->second->m_ListenSocket); + if (ls == self->m_ClosedSockets.end()) + continue; + } + + CGuard::enterCS(ls->second->m_AcceptLock); + ls->second->m_pQueuedSockets->erase(i->second->m_SocketID); + ls->second->m_pAcceptSockets->erase(i->second->m_SocketID); + CGuard::leaveCS(ls->second->m_AcceptLock); + } + self->m_Sockets.clear(); + + for (map::iterator j = self->m_ClosedSockets.begin(); j != self->m_ClosedSockets.end(); ++ j) + { + j->second->m_TimeStamp = 0; + } + CGuard::leaveCS(self->m_ControlLock); + + while (true) + { + self->checkBrokenSockets(); + + CGuard::enterCS(self->m_ControlLock); + bool empty = self->m_ClosedSockets.empty(); + CGuard::leaveCS(self->m_ControlLock); + + if (empty) + break; + + CTimer::sleep(); + } + + THREAD_EXIT(); + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// + +int CUDT::startup() +{ + return s_UDTUnited.startup(); +} + +int CUDT::cleanup() +{ + return s_UDTUnited.cleanup(); +} + +UDTSOCKET CUDT::socket(int af, int type, int) +{ + if (!s_UDTUnited.m_bGCStatus) + s_UDTUnited.startup(); + + try + { + return s_UDTUnited.newSocket(af, type); + } + catch (CUDTException& e) + { + s_UDTUnited.setError(new CUDTException(e)); + return INVALID_SOCK; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return INVALID_SOCK; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "socket: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return INVALID_SOCK; + } +} + +int CUDT::bind(UDTSOCKET u, const sockaddr* name, int namelen) +{ + try + { + return s_UDTUnited.bind(u, name, namelen); + } + catch (CUDTException& e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "bind: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::bind(UDTSOCKET u, UDPSOCKET udpsock) +{ + try + { + return s_UDTUnited.bind(u, udpsock); + } + catch (CUDTException& e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "bind/udp: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::listen(UDTSOCKET u, int backlog) +{ + try + { + return s_UDTUnited.listen(u, backlog); + } + catch (CUDTException& e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "listen: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +UDTSOCKET CUDT::accept(UDTSOCKET u, sockaddr* addr, int* addrlen) +{ + try + { + return s_UDTUnited.accept(u, addr, addrlen); + } + catch (CUDTException& e) + { + s_UDTUnited.setError(new CUDTException(e)); + return INVALID_SOCK; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "accept: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return INVALID_SOCK; + } +} + +int CUDT::connect(UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn) +{ + try + { + return s_UDTUnited.connect(u, name, namelen, forced_isn); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "connect: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::close(UDTSOCKET u) +{ + try + { + return s_UDTUnited.close(u); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "close: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::getpeername(UDTSOCKET u, sockaddr* name, int* namelen) +{ + try + { + return s_UDTUnited.getpeername(u, name, namelen); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "getpeername: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::getsockname(UDTSOCKET u, sockaddr* name, int* namelen) +{ + try + { + return s_UDTUnited.getsockname(u, name, namelen);; + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "getsockname: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::getsockopt(UDTSOCKET u, int, UDT_SOCKOPT optname, void* optval, int* optlen) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + udt->getOpt(optname, optval, *optlen); + return 0; + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "getsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::setsockopt(UDTSOCKET u, int, UDT_SOCKOPT optname, const void* optval, int optlen) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + udt->setOpt(optname, optval, optlen); + return 0; + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "setsockopt: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::send(UDTSOCKET u, const char* buf, int len, int) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->send(buf, len); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "send: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::recv(UDTSOCKET u, char* buf, int len, int) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->recv(buf, len); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "recv: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +#ifdef SRT_ENABLE_SRCTIMESTAMP +int CUDT::sendmsg(UDTSOCKET u, const char* buf, int len, int ttl, bool inorder, uint64_t srctime) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->sendmsg(buf, len, ttl, inorder, srctime); +#else +int CUDT::sendmsg(UDTSOCKET u, const char* buf, int len, int ttl, bool inorder) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->sendmsg(buf, len, ttl, inorder); +#endif + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "sendmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::recvmsg(UDTSOCKET u, char* buf, int len) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->recvmsg(buf, len); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +#ifdef SRT_ENABLE_SRCTIMESTAMP +int CUDT::recvmsg(UDTSOCKET u, char* buf, int len, uint64_t& srctime) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->recvmsg(buf, len, srctime); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "recvmsg: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} +#endif + +int64_t CUDT::sendfile(UDTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->sendfile(ifs, offset, size, block); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "sendfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int64_t CUDT::recvfile(UDTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + return udt->recvfile(ofs, offset, size, block); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "recvfile: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::select(int, ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout) +{ + if ((!readfds) && (!writefds) && (!exceptfds)) + { + s_UDTUnited.setError(new CUDTException(MJ_NOTSUP, MN_INVAL, 0)); + return ERROR; + } + + try + { + return s_UDTUnited.select(readfds, writefds, exceptfds, timeout); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "select: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, int64_t msTimeOut) +{ + if ((!readfds) && (!writefds) && (!exceptfds)) + { + s_UDTUnited.setError(new CUDTException(MJ_NOTSUP, MN_INVAL, 0)); + return ERROR; + } + + try + { + return s_UDTUnited.selectEx(fds, readfds, writefds, exceptfds, msTimeOut); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (bad_alloc&) + { + s_UDTUnited.setError(new CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "selectEx: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN)); + return ERROR; + } +} + +int CUDT::epoll_create() +{ + try + { + return s_UDTUnited.epoll_create(); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_create: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_add_usock(const int eid, const UDTSOCKET u, const int* events) +{ + try + { + return s_UDTUnited.epoll_add_usock(eid, u, events); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_add_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_add_ssock(const int eid, const SYSSOCKET s, const int* events) +{ + try + { + return s_UDTUnited.epoll_add_ssock(eid, s, events); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_add_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_update_usock(const int eid, const UDTSOCKET u, const int* events) +{ + try + { + return s_UDTUnited.epoll_update_usock(eid, u, events); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_update_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_update_ssock(const int eid, const SYSSOCKET s, const int* events) +{ + try + { + return s_UDTUnited.epoll_update_ssock(eid, s, events); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_update_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + + +int CUDT::epoll_remove_usock(const int eid, const UDTSOCKET u) +{ + try + { + return s_UDTUnited.epoll_remove_usock(eid, u); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_remove_usock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_remove_ssock(const int eid, const SYSSOCKET s) +{ + try + { + return s_UDTUnited.epoll_remove_ssock(eid, s); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_remove_ssock: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) +{ + try + { + return s_UDTUnited.epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_wait: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +int CUDT::epoll_release(const int eid) +{ + try + { + return s_UDTUnited.epoll_release(eid); + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "epoll_release: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +CUDTException& CUDT::getlasterror() +{ + return *s_UDTUnited.getError(); +} + +int CUDT::perfmon(UDTSOCKET u, CPerfMon* perf, bool clear) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + udt->sample(perf, clear); + return 0; + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "perfmon: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} + +#ifdef SRT_ENABLE_BSTATS +int CUDT::bstats(UDTSOCKET u, CBytePerfMon* perf, bool clear) +{ + try + { + CUDT* udt = s_UDTUnited.lookup(u); + udt->bstats(perf, clear); + return 0; + } + catch (CUDTException e) + { + s_UDTUnited.setError(new CUDTException(e)); + return ERROR; + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "bstats: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return ERROR; + } +} +#endif + +CUDT* CUDT::getUDTHandle(UDTSOCKET u) +{ + try + { + return s_UDTUnited.lookup(u); + } + catch (std::exception& ee) + { + return NULL; + } +} + +vector CUDT::existingSockets() +{ + vector out; + for (std::map::iterator i = s_UDTUnited.m_Sockets.begin(); + i != s_UDTUnited.m_Sockets.end(); ++i) + { + out.push_back(i->first); + } + return out; +} + +UDTSTATUS CUDT::getsockstate(UDTSOCKET u) +{ + try + { + return s_UDTUnited.getStatus(u); + } + catch (std::exception& ee) + { +LOGC(mglog.Fatal) << "getsockstate: UNEXPECTED EXCEPTION: " << typeid(ee).name() << ": " << ee.what(); + s_UDTUnited.setError(new CUDTException(MJ_UNKNOWN, MN_NONE, 0)); + return UDT_NONEXIST; + } +} + + +//////////////////////////////////////////////////////////////////////////////// + +namespace UDT +{ + +int startup() +{ + return CUDT::startup(); +} + +int cleanup() +{ + return CUDT::cleanup(); +} + +UDTSOCKET socket(int af, int type, int protocol) +{ + return CUDT::socket(af, type, protocol); +} + +int bind(UDTSOCKET u, const struct sockaddr* name, int namelen) +{ + return CUDT::bind(u, name, namelen); +} + +int bind2(UDTSOCKET u, UDPSOCKET udpsock) +{ + return CUDT::bind(u, udpsock); +} + +int listen(UDTSOCKET u, int backlog) +{ + return CUDT::listen(u, backlog); +} + +UDTSOCKET accept(UDTSOCKET u, struct sockaddr* addr, int* addrlen) +{ + return CUDT::accept(u, addr, addrlen); +} + +int connect(UDTSOCKET u, const struct sockaddr* name, int namelen) +{ + return CUDT::connect(u, name, namelen, 0); +} + +int close(UDTSOCKET u) +{ + return CUDT::close(u); +} + +int getpeername(UDTSOCKET u, struct sockaddr* name, int* namelen) +{ + return CUDT::getpeername(u, name, namelen); +} + +int getsockname(UDTSOCKET u, struct sockaddr* name, int* namelen) +{ + return CUDT::getsockname(u, name, namelen); +} + +int getsockopt(UDTSOCKET u, int level, SOCKOPT optname, void* optval, int* optlen) +{ + return CUDT::getsockopt(u, level, optname, optval, optlen); +} + +int setsockopt(UDTSOCKET u, int level, SOCKOPT optname, const void* optval, int optlen) +{ + return CUDT::setsockopt(u, level, optname, optval, optlen); +} + +// DEVELOPER API + +int connect_debug(UDTSOCKET u, const struct sockaddr* name, int namelen, int32_t forced_isn) +{ + return CUDT::connect(u, name, namelen, forced_isn); +} + + +#ifdef SRT_ENABLE_SRTCC_API +#include +int setsrtcc(UDTSOCKET u) +{ + return CUDT::setsockopt(u, SOL_SOCKET, UDT_CC, new CCCFactory, sizeof(CCCFactory)); +} + +int setsrtcc_maxbitrate(UDTSOCKET u, int maxbitrate) +{ + CSRTCongestionBlast *pSRTCC; + int optlen = sizeof(pSRTCC); + + if (0 != CUDT::getsockopt(u, SOL_SOCKET, UDT_CC, &pSRTCC, &optlen)) + return -1; + + pSRTCC->SetMaxBitrate(maxbitrate); //Mbps + return 0; +} + +int setsrtcc_windowsize(UDTSOCKET u, int windowsize) +{ + CSRTCongestionBlast *pSRTCC; + int optlen = sizeof(pSRTCC); + + if (0 != CUDT::getsockopt(u, SOL_SOCKET, UDT_CC, &pSRTCC, &optlen)) + return -1; + + pSRTCC->SetWindowSize(windowsize); //Mbps + return 0; +} +#endif /* SRT_ENABLE_SRTCC_API */ + +int send(UDTSOCKET u, const char* buf, int len, int flags) +{ + return CUDT::send(u, buf, len, flags); +} + +int recv(UDTSOCKET u, char* buf, int len, int flags) +{ + return CUDT::recv(u, buf, len, flags); +} + +#ifdef SRT_ENABLE_SRCTIMESTAMP + +int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl, bool inorder, uint64_t srctime) +{ + return CUDT::sendmsg(u, buf, len, ttl, inorder, srctime); +} + +// This version is available ADDITIONALLY to that without srctime +int recvmsg(UDTSOCKET u, char* buf, int len, uint64_t& srctime) +{ + return CUDT::recvmsg(u, buf, len, srctime); +} + +#else +int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl, bool inorder, uint64_t /*ignored*/) +{ + return CUDT::sendmsg(u, buf, len, ttl, inorder); +} + +#endif + +int recvmsg(UDTSOCKET u, char* buf, int len) +{ + return CUDT::recvmsg(u, buf, len); +} + +int64_t sendfile(UDTSOCKET u, fstream& ifs, int64_t& offset, int64_t size, int block) +{ + return CUDT::sendfile(u, ifs, offset, size, block); +} + +int64_t recvfile(UDTSOCKET u, fstream& ofs, int64_t& offset, int64_t size, int block) +{ + return CUDT::recvfile(u, ofs, offset, size, block); +} + +int64_t sendfile2(UDTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) +{ + fstream ifs(path, ios::binary | ios::in); + int64_t ret = CUDT::sendfile(u, ifs, *offset, size, block); + ifs.close(); + return ret; +} + +int64_t recvfile2(UDTSOCKET u, const char* path, int64_t* offset, int64_t size, int block) +{ + fstream ofs(path, ios::binary | ios::out); + int64_t ret = CUDT::recvfile(u, ofs, *offset, size, block); + ofs.close(); + return ret; +} + +int select(int nfds, UDSET* readfds, UDSET* writefds, UDSET* exceptfds, const struct timeval* timeout) +{ + return CUDT::select(nfds, readfds, writefds, exceptfds, timeout); +} + +int selectEx(const vector& fds, vector* readfds, vector* writefds, vector* exceptfds, int64_t msTimeOut) +{ + return CUDT::selectEx(fds, readfds, writefds, exceptfds, msTimeOut); +} + +int epoll_create() +{ + return CUDT::epoll_create(); +} + +int epoll_add_usock(int eid, UDTSOCKET u, const int* events) +{ + return CUDT::epoll_add_usock(eid, u, events); +} + +int epoll_add_ssock(int eid, SYSSOCKET s, const int* events) +{ + return CUDT::epoll_add_ssock(eid, s, events); +} + +int epoll_update_usock(int eid, UDTSOCKET u, const int* events) +{ + return CUDT::epoll_update_usock(eid, u, events); +} + +int epoll_update_ssock(int eid, SYSSOCKET s, const int* events) +{ + return CUDT::epoll_update_ssock(eid, s, events); +} + +int epoll_remove_usock(int eid, UDTSOCKET u) +{ + return CUDT::epoll_remove_usock(eid, u); +} + +int epoll_remove_ssock(int eid, SYSSOCKET s) +{ + return CUDT::epoll_remove_ssock(eid, s); +} + +int epoll_wait(int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) +{ + return CUDT::epoll_wait(eid, readfds, writefds, msTimeOut, lrfds, lwfds); +} + + +#ifdef HAI_PATCH + +#define SET_RESULT(val, num, fds, it) \ + if (val != NULL) \ + { \ + if (val->empty()) \ + { \ + if (num) *num = 0; \ + } \ + else \ + { \ + 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; \ + } \ + } \ + } +#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) +{ + if ( !val ) + return; + + if (*num > int(val->size())) + *num = val->size(); + int count = 0; + for (it = val->begin(); it != val->end(); ++ it) + { + if (count >= *num) + break; + fds[count ++] = *it; + } +} + +int epoll_wait2(int eid, UDTSOCKET* readfds, int* rnum, UDTSOCKET* writefds, int* wnum, int64_t msTimeOut, + SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum) +{ + // This API is an alternative format for epoll_wait, created for compatability with other languages. + // Users need to pass in an array for holding the returned sockets, with the maximum array length + // stored in *rnum, etc., which will be updated with returned number of sockets. + + set readset; + set writeset; + set lrset; + set lwset; + set* rval = NULL; + set* wval = NULL; + set* lrval = NULL; + set* lwval = NULL; + if ((readfds != NULL) && (rnum != NULL)) + rval = &readset; + if ((writefds != NULL) && (wnum != NULL)) + wval = &writeset; + if ((lrfds != NULL) && (lrnum != NULL)) + lrval = &lrset; + if ((lwfds != NULL) && (lwnum != NULL)) + lwval = &lwset; + + 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); + } + return ret; +} + +int epoll_release(int eid) +{ + return CUDT::epoll_release(eid); +} + +ERRORINFO& getlasterror() +{ + return CUDT::getlasterror(); +} + +int getlasterror_code() +{ + return CUDT::getlasterror().getErrorCode(); +} + +const char* getlasterror_desc() +{ + return CUDT::getlasterror().getErrorMessage(); +} + +int getlasterror_errno() +{ + return CUDT::getlasterror().getErrno(); +} + +// Get error string of a given error code +const char* geterror_desc(int code, int err) +{ + //static CUDTException e; //>>Need something better than static here. + // Yeah, of course. Here you are: + CUDTException e (CodeMajor(code/1000), CodeMinor(code%1000), err); + return(e.getErrorMessage()); +} + + +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) +{ + return CUDT::getsockstate(u); +} + +void setloglevel(logging::LogLevel::type ll) +{ + CGuard gg(logger_config.mutex); + logger_config.max_level = ll; +} + +void addlogfa(logging::LogFA fa) +{ + CGuard gg(logger_config.mutex); + logger_config.enabled_fa.insert(fa); +} + +void dellogfa(logging::LogFA fa) +{ + CGuard gg(logger_config.mutex); + logger_config.enabled_fa.erase(fa); +} + +void resetlogfa(set fas) +{ + CGuard gg(logger_config.mutex); + set enfas; + copy(fas.begin(), fas.end(), std::inserter(enfas, enfas.begin())); + logger_config.enabled_fa = enfas; +} + +void setlogstream(std::ostream& stream) +{ + CGuard gg(logger_config.mutex); + 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; +} + +void setlogflags(int flags) +{ + CGuard gg(logger_config.mutex); + logger_config.flags = flags; +} + +} // namespace UDT diff --git a/srtcore/api.h b/srtcore/api.h new file mode 100644 index 000000000..19ed02040 --- /dev/null +++ b/srtcore/api.h @@ -0,0 +1,295 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2010, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 09/28/2010 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_API_H__ +#define __UDT_API_H__ + + +#include +#include +#include +#include +#include "udt.h" +#include "packet.h" +#include "queue.h" +#include "cache.h" +#include "epoll.h" + +class CUDT; + +class CUDTSocket +{ +public: + CUDTSocket(); + ~CUDTSocket(); + + UDTSTATUS m_Status; //< current socket state + + uint64_t m_TimeStamp; //< time when the socket is closed + + int m_iIPversion; //< IP version + sockaddr* m_pSelfAddr; //< pointer to the local address of the socket + sockaddr* m_pPeerAddr; //< pointer to the peer address of the socket + + UDTSOCKET m_SocketID; //< socket ID + UDTSOCKET m_ListenSocket; //< ID of the listener socket; 0 means this is an independent socket + + UDTSOCKET m_PeerID; //< peer socket ID + int32_t m_iISN; //< initial sequence number, used to tell different connection from same IP:port + + CUDT* m_pUDT; //< pointer to the UDT entity + + std::set* m_pQueuedSockets; //< set of connections waiting for accept() + std::set* m_pAcceptSockets; //< set of accept()ed connections + + pthread_cond_t m_AcceptCond; //< used to block "accept" call + pthread_mutex_t m_AcceptLock; //< mutex associated to m_AcceptCond + + unsigned int m_uiBackLog; //< maximum number of connections in queue + + int m_iMuxID; //< multiplexer ID + + pthread_mutex_t m_ControlLock; //< lock this socket exclusively for control APIs: bind/listen/connect + + static int64_t getPeerSpec(UDTSOCKET id, int32_t isn) + { + return (id << 30) + isn; + } + int64_t getPeerSpec() + { + return getPeerSpec(m_PeerID, m_iISN); + } + +private: + CUDTSocket(const CUDTSocket&); + CUDTSocket& operator=(const CUDTSocket&); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CUDTUnited +{ +friend class CUDT; +friend class CRendezvousQueue; + +public: + CUDTUnited(); + ~CUDTUnited(); + +public: + + std::string CONID(UDTSOCKET sock) const; + + /// initialize the UDT library. + /// @return 0 if success, otherwise -1 is returned. + + int startup(); + + /// release the UDT library. + /// @return 0 if success, otherwise -1 is returned. + + int cleanup(); + + /// Create a new UDT socket. + /// @param [in] af IP version, IPv4 (AF_INET) or IPv6 (AF_INET6). + /// @param [in] type socket type, SOCK_STREAM or SOCK_DGRAM + /// @return The new UDT socket ID, or INVALID_SOCK. + + UDTSOCKET newSocket(int af, int type); + + /// Create a new UDT connection. + /// @param [in] listen the listening UDT socket; + /// @param [in] peer peer address. + /// @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); + + /// look up the UDT entity according to its ID. + /// @param [in] u the UDT socket ID. + /// @return Pointer to the UDT entity. + + CUDT* lookup(const UDTSOCKET u); + + /// Check the status of the UDT socket. + /// @param [in] u the UDT socket ID. + /// @return UDT socket status, or NONEXIST if not found. + + UDTSTATUS getStatus(const UDTSOCKET u); + + // socket APIs + + int bind(const UDTSOCKET u, const sockaddr* name, int namelen); + int bind(const UDTSOCKET u, UDPSOCKET udpsock); + int listen(const UDTSOCKET u, int backlog); + UDTSOCKET accept(const UDTSOCKET listen, sockaddr* addr, int* addrlen); + int connect(const UDTSOCKET u, const sockaddr* name, int namelen, int32_t forced_isn); + int close(const UDTSOCKET u); + int getpeername(const UDTSOCKET u, sockaddr* name, int* namelen); + int getsockname(const UDTSOCKET u, sockaddr* name, int* namelen); + int select(ud_set* readfds, ud_set* writefds, ud_set* exceptfds, const timeval* timeout); + int selectEx(const std::vector& fds, std::vector* readfds, std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); + int epoll_create(); + int epoll_add_usock(const int eid, const UDTSOCKET u, const int* events = NULL); + 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); + + /// record the UDT exception. + /// @param [in] e pointer to a UDT exception instance. + + void setError(CUDTException* e); + + /// look up the most recent UDT exception. + /// @return pointer to a UDT exception instance. + + CUDTException* getError(); + +private: +// void init(); + +private: + std::map m_Sockets; // stores all the socket structures + + pthread_mutex_t m_ControlLock; // used to synchronize UDT API + + pthread_mutex_t m_IDLock; // used to synchronize ID generation + UDTSOCKET m_SocketIDGenerator; // seed to generate a new unique socket ID + + std::map > m_PeerRec;// record sockets from peers to avoid repeated connection request, int64_t = (socker_id << 30) + isn + +private: + pthread_key_t m_TLSError; // thread local error record (last error) + static void TLSDestroy(void* e) {if (NULL != e) delete (CUDTException*)e;} + +private: + void connect_complete(const UDTSOCKET u); + 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); + +private: + std::map m_mMultiplexer; // UDP multiplexer + pthread_mutex_t m_MultiplexerLock; + +private: + CCache* m_pCache; // UDT network information cache + +private: + volatile bool m_bClosing; + pthread_mutex_t m_GCStopLock; + pthread_cond_t m_GCStopCond; + + pthread_mutex_t m_InitLock; + int m_iInstanceCount; // number of startup() called by application + bool m_bGCStatus; // if the GC thread is working (true) + + pthread_t m_GCThread; + static void* garbageCollect(void*); + + std::map m_ClosedSockets; // temporarily store closed sockets + + void checkBrokenSockets(); + void removeSocket(const UDTSOCKET u); + +private: + CEPoll m_EPoll; // handling epoll data structures and events + +private: + CUDTUnited(const CUDTUnited&); + CUDTUnited& operator=(const CUDTUnited&); +}; + +// Debug support +inline std::string SockaddrToString(const sockaddr* sadr) +{ + char buf[1024]; + + void* addr = + sadr->sa_family == AF_INET ? + (void*)&((sockaddr_in*)sadr)->sin_addr + : sadr->sa_family == AF_INET6 ? + (void*)&((sockaddr_in6*)sadr)->sin6_addr + : 0; + if ( !addr ) + return "unknown"; + + if ( !inet_ntop(sadr->sa_family, addr, buf, 1024) ) + strcpy(buf, "unknown"); + + char* p = buf + strlen(buf); + if ( p - buf > 1000 ) + return buf; + sprintf(p, ":%d", ntohs(((sockaddr_in*)sadr)->sin_port)); // TRICK: sin_port and sin6_port have the same offset and size + + return buf; +} + + +#endif diff --git a/srtcore/buffer.cpp b/srtcore/buffer.cpp new file mode 100644 index 000000000..355086e81 --- /dev/null +++ b/srtcore/buffer.cpp @@ -0,0 +1,1717 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 03/12/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#include +#include +#include "buffer.h" +#include "packet.h" +#include "core.h" // provides some constants +#include "logging.h" + +using namespace std; + +extern logging::Logger mglog, dlog, tslog; + +CSndBuffer::CSndBuffer(int size, int mss): +m_BufLock(), +m_pBlock(NULL), +m_pFirstBlock(NULL), +m_pCurrBlock(NULL), +m_pLastBlock(NULL), +m_pBuffer(NULL), +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 +,m_LastSamplingTime(0) +,m_iCountMAvg(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; + m_pBuffer->m_pcData = new char [m_iSize * m_iMSS]; + m_pBuffer->m_iSize = m_iSize; + m_pBuffer->m_pNext = NULL; + + // circular linked list for out bound packets + m_pBlock = new Block; + Block* pb = m_pBlock; + for (int i = 1; i < m_iSize; ++ i) + { + pb->m_pNext = new Block; + pb->m_iMsgNoBitset = 0; + pb = pb->m_pNext; + } + pb->m_pNext = m_pBlock; + + pb = m_pBlock; + char* pc = m_pBuffer->m_pcData; + for (int i = 0; i < m_iSize; ++ i) + { + pb->m_pcData = pc; + pb = pb->m_pNext; + pc += m_iMSS; + } + + m_pFirstBlock = m_pCurrBlock = m_pLastBlock = m_pBlock; + + pthread_mutex_init(&m_BufLock, NULL); +} + +CSndBuffer::~CSndBuffer() +{ + Block* pb = m_pBlock->m_pNext; + while (pb != m_pBlock) + { + Block* temp = pb; + pb = pb->m_pNext; + delete temp; + } + delete m_pBlock; + + while (m_pBuffer != NULL) + { + Buffer* temp = m_pBuffer; + m_pBuffer = m_pBuffer->m_pNext; + delete [] temp->m_pcData; + delete temp; + } + + pthread_mutex_destroy(&m_BufLock); +} + +#ifdef SRT_ENABLE_SRCTIMESTAMP +void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order, uint64_t srctime) +#else +void CSndBuffer::addBuffer(const char* data, int len, int ttl, bool order) +#endif +{ + int size = len / m_iMSS; + if ((len % m_iMSS) != 0) + size ++; + + // dynamically increase sender buffer + while (size + m_iCount >= m_iSize) + increase(); + + uint64_t time = CTimer::getTime(); + int32_t inorder = order ? MSGNO_PACKET_INORDER::mask : 0; + + LOGC(dlog.Debug) << CONID() << "CSndBuffer: adding " << size << " packets (" << len << " bytes) to send"; + + Block* s = m_pLastBlock; + for (int i = 0; i < size; ++ i) + { + int pktlen = len - i * m_iMSS; + if (pktlen > m_iMSS) + pktlen = m_iMSS; + + memcpy(s->m_pcData, data + i * m_iMSS, pktlen); + s->m_iLength = pktlen; + + s->m_iMsgNoBitset = m_iNextMsgNo | inorder; + if (i == 0) + s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); + if (i == size - 1) + s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); + // NOTE: if i is neither 0 nor size-1, it resuls with PB_SUBSEQUENT. + // if i == 0 == size-1, it results with PB_SOLO. + // Packets assigned to one message can be: + // [PB_FIRST] [PB_SUBSEQUENT] [PB_SUBSEQUENT] [PB_LAST] - 4 packets per message + // [PB_FIRST] [PB_LAST] - 2 packets per message + // [PB_SOLO] - 1 packet per message + +#ifdef SRT_ENABLE_SRCTIMESTAMP + s->m_SourceTime = srctime; +#endif + s->m_OriginTime = time; + s->m_iTTL = ttl; + + // XXX unchecked condition: s->m_pNext == NULL. + // Should never happen, as the call to increase() should ensure enough buffers. + s = s->m_pNext; + } + m_pLastBlock = s; + + 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); +#endif + + CGuard::leaveCS(m_BufLock); + + m_iNextMsgNo ++; + + // MSG_SEQ::mask has a form: 00000011111111... + // At least it's known that it's from some index inside til the end (to bit 0). + // If this value has been reached in a step of incrementation, it means that the + // maximum value has been reached. Casting to int32_t to ensure the same sign + // in comparison, although it's far from reaching the sign bit. + if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) + m_iNextMsgNo = 1; +} + +#ifdef SRT_ENABLE_INPUTRATE +void CSndBuffer::setInputRateSmpPeriod(int period) +{ + m_InRatePeriod = (uint64_t)period; //(usec) 0=no input rate calculation +} + +void CSndBuffer::updInputRate(uint64_t time, int pkts, int bytes) +{ + if (m_InRatePeriod == 0) + ;//no input rate calculation + else if (m_InRateStartTime == 0) + m_InRateStartTime = time; + else + { + m_iInRatePktsCount += pkts; + m_iInRateBytesCount += bytes; + if ((time - m_InRateStartTime) > m_InRatePeriod) { + //Payload average size + m_iAvgPayloadSz = m_iInRateBytesCount / m_iInRatePktsCount; + //Required Byte/sec rate (payload + headers) + m_iInRateBytesCount += (m_iInRatePktsCount * SRT_DATA_PKTHDR_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", + m_iInRateBytesCount, m_iInRatePktsCount, m_iAvgPayloadSz, (m_iInRateBps*8)/1000, + (unsigned long long)(time - m_InRateStartTime)); + + m_iInRatePktsCount = 0; + m_iInRateBytesCount = 0; + m_InRateStartTime = time; + } + } +} + +int CSndBuffer::getInputRate(int& payloadsz, int& period) +{ + uint64_t time = CTimer::getTime(); + + if ((m_InRatePeriod != 0) + && (m_InRateStartTime != 0) + && ((time - m_InRateStartTime) > m_InRatePeriod)) + { + //Packet size with headers + if (m_iInRatePktsCount == 0) + m_iAvgPayloadSz = 0; + else + m_iAvgPayloadSz = m_iInRateBytesCount / m_iInRatePktsCount; + + //include packet headers: SRT + UDP + IP + int64_t llBytesCount = (int64_t)m_iInRateBytesCount + (m_iInRatePktsCount * (CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE)); + //Byte/sec rate + m_iInRateBps = (int)((llBytesCount * 1000000) / (time - m_InRateStartTime)); + m_iInRatePktsCount = 0; + m_iInRateBytesCount = 0; + m_InRateStartTime = time; + } + payloadsz = m_iAvgPayloadSz; + period = (int)m_InRatePeriod; + return(m_iInRateBps); +} +#endif /* SRT_ENABLE_INPUTRATE */ + +int CSndBuffer::addBufferFromFile(fstream& ifs, int len) +{ + int size = len / m_iMSS; + if ((len % m_iMSS) != 0) + size ++; + + // dynamically increase sender buffer + while (size + m_iCount >= m_iSize) + increase(); + + Block* s = m_pLastBlock; + int total = 0; + for (int i = 0; i < size; ++ i) + { + if (ifs.bad() || ifs.fail() || ifs.eof()) + break; + + int pktlen = len - i * m_iMSS; + if (pktlen > m_iMSS) + pktlen = m_iMSS; + + ifs.read(s->m_pcData, pktlen); + if ((pktlen = ifs.gcount()) <= 0) + break; + + // currently file transfer is only available in streaming mode, message is always in order, ttl = infinite + s->m_iMsgNoBitset = m_iNextMsgNo | MSGNO_PACKET_INORDER::mask; + if (i == 0) + s->m_iMsgNoBitset |= PacketBoundaryBits(PB_FIRST); + if (i == size - 1) + s->m_iMsgNoBitset |= PacketBoundaryBits(PB_LAST); + // NOTE: PB_FIRST | PB_LAST == PB_SOLO. + // none of PB_FIRST & PB_LAST == PB_SUBSEQUENT. + + s->m_iLength = pktlen; + s->m_iTTL = -1; + s = s->m_pNext; + + total += pktlen; + } + m_pLastBlock = s; + + CGuard::enterCS(m_BufLock); + m_iCount += size; +#ifdef SRT_ENABLE_BSTATS + m_iBytesCount += total; +#endif + + CGuard::leaveCS(m_BufLock); + + m_iNextMsgNo ++; + if (m_iNextMsgNo == int32_t(MSGNO_SEQ::mask)) + m_iNextMsgNo = 1; + + 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; + + *data = m_pCurrBlock->m_pcData; + int readlen = m_pCurrBlock->m_iLength; + + 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; + + LOGC(dlog.Debug) << CONID() << "CSndBuffer: extracting packet size=" << readlen << " to send"; + + 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; + + for (int i = 0; i < offset; ++ i) + p = p->m_pNext; + + // Check if the block that is the next candidate to send (m_pCurrBlock pointing) is stale. + + // If so, then inform the caller that it should first take care of the whole + // message (all blocks with that message id). Shift the m_pCurrBlock pointer + // to the position past the last of them. Then return -1 and set the + // msgno_bitset return reference to the message id that should be dropped as + // a whole. + + // After taking care of that, the caller should immediately call this function again, + // this time possibly in order to find the real data to be sent. + + // if found block is stale + if ((p->m_iTTL >= 0) && ((CTimer::getTime() - p->m_OriginTime) / 1000 > (uint64_t)p->m_iTTL)) + { + int32_t msgno = p->getMsgSeq(); + msglen = 1; + p = p->m_pNext; + bool move = false; + while (msgno == p->getMsgSeq()) + { + if (p == m_pCurrBlock) + move = true; + p = p->m_pNext; + if (move) + m_pCurrBlock = p; + msglen ++; + } + + // If readData returns -1, then msgno_bitset is understood as a Message ID to drop. + // This means that in this case it should be written by the message sequence value only + // (not the whole 4-byte bitset written at PH_MSGNO). + msgno_bitset = msgno; + return -1; + } + + *data = p->m_pcData; + int readlen = p->m_iLength; + 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; +} + +void CSndBuffer::ackData(int offset) +{ + CGuard bufferguard(m_BufLock); + +#ifdef SRT_ENABLE_BSTATS + bool move = false; + for (int i = 0; i < offset; ++ i) { + m_iBytesCount -= m_pFirstBlock->m_iLength; + 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 + + m_iCount -= offset; + +#ifdef SRT_ENABLE_SNDBUFSZ_MAVG + updAvgBufSize(CTimer::getTime()); +#endif + + CTimer::triggerEvent(); +} + +int CSndBuffer::getCurrBufSize() const +{ + return m_iCount; +} + +#ifdef SRT_ENABLE_BSTATS +#ifdef SRT_ENABLE_SNDBUFSZ_MAVG + +int CSndBuffer::getAvgBufSize(int &bytes, int ×pan) +{ + CGuard bufferguard(m_BufLock); /* Consistency of pkts vs. bytes vs. spantime */ + + /* update stats in case there was no add/ack activity lately */ + updAvgBufSize(CTimer::getTime()); + + bytes = m_iBytesCountMAvg; + timespan = m_TimespanMAvg; + return(m_iCountMAvg); +} + +void CSndBuffer::updAvgBufSize(uint64_t now) +{ + uint64_t elapsed = (now - m_LastSamplingTime) / 1000; //ms since last sampling + + if ((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 > elapsed) + return; + + if (1000000 < elapsed) + { + /* No sampling in last 1 sec, initialize average */ + m_iCountMAvg = getCurrBufSize(m_iBytesCountMAvg, m_TimespanMAvg); + m_LastSamplingTime = now; + } + else //((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 <= elapsed) + { + /* + * weight last average value between -1 sec and last sampling time (LST) + * and new value between last sampling time and now + * |elapsed| + * +----------------------------------+-------+ + * -1 LST 0(now) + */ + int instspan; + int bytescount; + int count = getCurrBufSize(bytescount, instspan); + + LOGC(dlog.Debug).form("updAvgBufSize: %6d: %6d %6d %6d ms\n", (int)elapsed, count, bytescount, instspan); + + m_iCountMAvg = (int)(((count * (1000 - elapsed)) + (count * elapsed)) / 1000); + m_iBytesCountMAvg = (int)(((bytescount * (1000 - elapsed)) + (bytescount * elapsed)) / 1000); + m_TimespanMAvg = (int)(((instspan * (1000 - elapsed)) + (instspan * elapsed)) / 1000); + m_LastSamplingTime = now; + } +} + +#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ + +int CSndBuffer::getCurrBufSize(int &bytes, int ×pan) +{ + bytes = m_iBytesCount; + /* + * Timespan can be less then 1000 us (1 ms) if few packets. + * Also, if there is only one pkt in buffer, the time difference will be 0. + * Therefore, always add 1 ms if not empty. + */ +#ifdef SRT_ENABLE_CBRTIMESTAMP + timespan = 0 < m_iCount ? ((m_LastOriginTime - m_pFirstBlock->m_SourceTime) / 1000) + 1 : 0; +#else + timespan = 0 < m_iCount ? ((m_LastOriginTime - m_pFirstBlock->m_OriginTime) / 1000) + 1 : 0; +#endif + + return m_iCount; +} + +#ifdef SRT_ENABLE_TLPKTDROP +int CSndBuffer::dropLateData(int &bytes, uint64_t latetime) +{ + int dpkts = 0; + int dbytes = 0; + bool move = false; + + CGuard bufferguard(m_BufLock); + for (int i = 0; i < m_iCount && m_pFirstBlock->m_OriginTime < latetime; ++ i) + { + dpkts++; + dbytes += m_pFirstBlock->m_iLength; + + if (m_pFirstBlock == m_pCurrBlock) move = true; + m_pFirstBlock = m_pFirstBlock->m_pNext; + } + if (move) m_pCurrBlock = m_pFirstBlock; + m_iCount -= dpkts; + + m_iBytesCount -= dbytes; + bytes = dbytes; + +#ifdef SRT_ENABLE_SNDBUFSZ_MAVG + updAvgBufSize(CTimer::getTime()); +#endif /* SRT_ENABLE_SNDBUFSZ_MAVG */ + +// CTimer::triggerEvent(); + return(dpkts); +} +#endif /* SRT_ENABLE_TLPKTDROP */ +#endif /* SRT_ENABLE_BSTATS */ + +void CSndBuffer::increase() +{ + int unitsize = m_pBuffer->m_iSize; + + // new physical buffer + Buffer* nbuf = NULL; + try + { + nbuf = new Buffer; + nbuf->m_pcData = new char [unitsize * m_iMSS]; + } + catch (...) + { + delete nbuf; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + nbuf->m_iSize = unitsize; + nbuf->m_pNext = NULL; + + // insert the buffer at the end of the buffer list + Buffer* p = m_pBuffer; + while (p->m_pNext != NULL) + p = p->m_pNext; + p->m_pNext = nbuf; + + // new packet blocks + Block* nblk = NULL; + try + { + nblk = new Block; + } + catch (...) + { + delete nblk; + throw CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0); + } + Block* pb = nblk; + for (int i = 1; i < unitsize; ++ i) + { + pb->m_pNext = new Block; + pb = pb->m_pNext; + } + + // insert the new blocks onto the existing one + pb->m_pNext = m_pLastBlock->m_pNext; + m_pLastBlock->m_pNext = nblk; + + pb = nblk; + char* pc = nbuf->m_pcData; + for (int i = 0; i < unitsize; ++ i) + { + pb->m_pcData = pc; + pb = pb->m_pNext; + pc += m_iMSS; + } + + m_iSize += unitsize; +} + +//////////////////////////////////////////////////////////////////////////////// + +/* +* RcvBuffer (circular buffer): +* +* |<------------------- m_iSize ----------------------------->| +* | |<--- acked pkts -->|<--- m_iMaxPos --->| | +* | | | | | +* +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +* | 0 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |...| 0 | m_pUnit[] +* +---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+ +* | | | | +* | | \__last pkt received +* | \___ m_iLastAckPos: last ack sent +* \___ m_iStartPos: first message to read +* +* m_pUnit[i]->m_iFlag: 0:free, 1:good, 2:passack, 3:dropped +* +* thread safety: +* m_iStartPos: CUDT::m_RecvLock +* m_iLastAckPos: CUDT::m_AckLock +* m_iMaxPos: none? (modified on add and ack +*/ + + +// 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), +m_iSize(bufsize), +m_pUnitQueue(queue), +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) +,m_bTsbPdWrapCheck(false) +//,m_iTsbPdDrift(0) +//,m_TsbPdDriftSum(0) +//,m_iTsbPdDriftNbSamples(0) +#ifdef SRT_ENABLE_RCVBUFSZ_MAVG +,m_LastSamplingTime(0) +,m_TimespanMAvg(0) +,m_iCountMAvg(0) +,m_iBytesCountMAvg(0) +#endif +#endif +{ + m_pUnit = new CUnit* [m_iSize]; + for (int i = 0; i < m_iSize; ++ i) + m_pUnit[i] = NULL; + +#ifdef SRT_DEBUG_TSBPD_DRIFT + memset(m_TsbPdDriftHisto100us, 0, sizeof(m_TsbPdDriftHisto100us)); + memset(m_TsbPdDriftHisto1ms, 0, sizeof(m_TsbPdDriftHisto1ms)); +#endif + +#ifdef SRT_ENABLE_BSTATS + pthread_mutex_init(&m_BytesCountLock, NULL); +#endif /* SRT_ENABLE_BSTATS */ +} + +CRcvBuffer::~CRcvBuffer() +{ + for (int i = 0; i < m_iSize; ++ i) + { + if (m_pUnit[i] != NULL) + { + m_pUnit[i]->m_iFlag = CUnit::FREE; + -- m_pUnitQueue->m_iCount; + } + } + + 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) +{ + /* + * Byte counter changes from both sides (Recv & Ack) of the buffer + * so the higher level lock is not enough for thread safe op. + * + * pkts are... + * added (bytes>0, acked=false), + * acked (bytes>0, acked=true), + * removed (bytes<0, acked=n/a) + */ + CGuard cg(m_BytesCountLock); + + if (!acked) //adding new pkt in RcvBuffer + { + m_iBytesCount += bytes; /* added or removed bytes from rcv buffer */ + if (bytes > 0) /* Assuming one pkt when adding bytes */ + m_iAvgPayloadSz = ((m_iAvgPayloadSz * (100 - 1)) + bytes) / 100; + } + else // acking/removing pkts to/from buffer + { + m_iAckedPktsCount += pkts; /* acked or removed pkts from rcv buffer */ + m_iAckedBytesCount += bytes; /* acked or removed bytes from rcv buffer */ + + 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; + + return 0; +} + +int CRcvBuffer::readBuffer(char* data, int len) +{ + int p = m_iStartPos; + 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) + unitsize = rs; + + 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)) + { + CUnit* tmp = m_pUnit[p]; + m_pUnit[p] = NULL; + tmp->m_iFlag = CUnit::FREE; + -- m_pUnitQueue->m_iCount; + + if (++ p == m_iSize) + p = 0; + + m_iNotch = 0; + } + else + m_iNotch += rs; + + 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; +} + +int CRcvBuffer::readBufferToFile(fstream& ofs, int len) +{ + int p = m_iStartPos; + int lastack = m_iLastAckPos; + int rs = len; + + while ((p != lastack) && (rs > 0)) + { + int unitsize = m_pUnit[p]->m_Packet.getLength() - m_iNotch; + if (unitsize > rs) + unitsize = rs; + + ofs.write(m_pUnit[p]->m_Packet.m_pcData + m_iNotch, unitsize); + if (ofs.fail()) + break; + + if ((rs > unitsize) || (rs == m_pUnit[p]->m_Packet.getLength() - m_iNotch)) + { + CUnit* tmp = m_pUnit[p]; + m_pUnit[p] = NULL; + tmp->m_iFlag = CUnit::FREE; + -- m_pUnitQueue->m_iCount; + + if (++ p == m_iSize) + p = 0; + + m_iNotch = 0; + } + else + m_iNotch += rs; + + 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; +} + +void CRcvBuffer::ackData(int len) +{ +#ifdef SRT_ENABLE_BSTATS + { + int pkts = 0; + int bytes = 0; + for (int i = m_iLastAckPos, n = (m_iLastAckPos + len) % m_iSize; i != n; i = (i + 1) % m_iSize) + { + if (m_pUnit[i] != NULL) + { + pkts++; + bytes += m_pUnit[i]->m_Packet.getLength(); + } + } + if (pkts > 0) countBytes(pkts, bytes, true); + } +#endif + m_iLastAckPos = (m_iLastAckPos + len) % m_iSize; + m_iMaxPos -= len; + if (m_iMaxPos < 0) + m_iMaxPos = 0; + + CTimer::triggerEvent(); +} + +#ifdef SRT_ENABLE_TSBPD +#ifdef SRT_ENABLE_TLPKTDROP + +void CRcvBuffer::skipData(int len) +{ + /* + * Caller need protect both AckLock and RecvLock + * to move both m_iStartPos and m_iLastAckPost + */ + if (m_iStartPos == m_iLastAckPos) + m_iStartPos = (m_iStartPos + len) % m_iSize; + m_iLastAckPos = (m_iLastAckPos + len) % m_iSize; + m_iMaxPos -= len; + if (m_iMaxPos < 0) + m_iMaxPos = 0; +} + +bool CRcvBuffer::getRcvFirstMsg(uint64_t& tsbpdtime, bool& passack, int32_t& skipseqno, CPacket** pppkt) +{ + skipseqno = -1; + + /* Check the acknowledged packets */ + if (getRcvReadyMsg(tsbpdtime, pppkt)) + { + passack = false; + return true; + } + else if (tsbpdtime != 0) + { + passack = false; + return false; + } + + /* + * No acked packets ready but caller want to know next packet to wait for + * Check the not yet acked packets that may be stuck by missing packet(s). + */ + bool haslost = false; + tsbpdtime = 0; + passack = true; + skipseqno = -1; + + for (int i = m_iLastAckPos, n = (m_iLastAckPos + m_iMaxPos) % m_iSize; i != n; i = (i + 1) % m_iSize) + { + if ( !m_pUnit[i] + || m_pUnit[i]->m_iFlag != CUnit::GOOD ) + { + /* There are packets in the sequence not received yet */ + haslost = true; + } + else + { + /* We got the 1st valid packet */ + tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); + if (tsbpdtime <= CTimer::getTime()) + { + /* Packet ready to play */ + if (haslost) + { + /* + * Packet stuck on non-acked side because of missing packets. + * Tell 1st valid packet seqno so caller can skip (drop) the missing packets. + */ + skipseqno = m_pUnit[i]->m_Packet.m_iSeqNo; + } + return true; + } + return false; + } + } + return false; +} +#endif /* SRT_ENABLE_TLPKTDROP */ + +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; + + /* Skip any invalid skipped/dropped packets */ + if (m_pUnit[i] == NULL) + { + if (++ m_iStartPos == m_iSize) + m_iStartPos = 0; + continue; + } + + if ( pppkt ) + *pppkt = &m_pUnit[i]->m_Packet; + + if (m_pUnit[i]->m_iFlag != CUnit::GOOD) + { + freeunit = true; + } + else + { + tsbpdtime = getPktTsbPdTime(m_pUnit[i]->m_Packet.getMsgTimeStamp()); + if (tsbpdtime > CTimer::getTime()) + return false; + + if (m_pUnit[i]->m_Packet.getMsgCryptoFlags() != 0) + freeunit = true; /* packet not decrypted */ + else + return true; + } + + if (freeunit) + { + 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; + + if (++m_iStartPos == m_iSize) + m_iStartPos = 0; + } + } +#ifdef SRT_ENABLE_BSTATS + /* removed skipped, dropped, undecryptable bytes from rcv buffer */ + countBytes(-rmpkts, -rmbytes, true); +#endif + return false; +} + + +/* +* Return receivable data status (packet timestamp ready to play if TsbPd mode) +* Return playtime (tsbpdtime) of 1st packet in queue, ready to play or not +*/ +/* +* Return data ready to be received (packet timestamp ready to play if TsbPd mode) +* Using getRcvDataSize() to know if there is something to read as it was widely +* used in the code (core.cpp) is expensive in TsbPD mode, hence this simpler function +* that only check if first packet in queue is ready. +*/ +bool CRcvBuffer::isRcvDataReady(uint64_t& tsbpdtime, CPacket** pppkt) +{ + tsbpdtime = 0; + + if (m_bTsbPdMode) + { + CPacket* pkt = getRcvReadyPacket(); + if ( pkt ) + { + /* + * Acknowledged data is available, + * Only say ready if time to deliver. + * Report the timestamp, ready or not. + */ + if ( pppkt ) + *pppkt = pkt; + tsbpdtime = getPktTsbPdTime(pkt->getMsgTimeStamp()); + if (tsbpdtime <= CTimer::getTime()) + return true; + } + return false; + } + + return isRcvDataAvailable(); +} + +// XXX This function may be called only after checking +// if m_bTsbPdMode. +CPacket* CRcvBuffer::getRcvReadyPacket() +{ + for (int i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) + { + /* + * Skip missing packets that did not arrive in time. + */ + if ( m_pUnit[i] && m_pUnit[i]->m_iFlag == CUnit::GOOD ) + return &m_pUnit[i]->m_Packet; + } + + return 0; +} + +bool CRcvBuffer::isRcvDataReady() +{ + uint64_t tsbpdtime; + + 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" + return m_iSize - getRcvDataSize() - 1; +} + +int CRcvBuffer::getRcvDataSize() const +{ + if (m_iLastAckPos >= m_iStartPos) + return m_iLastAckPos - m_iStartPos; + + return m_iSize + m_iLastAckPos - m_iStartPos; +} + + +#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) +{ + timespan = m_TimespanMAvg; + bytes = m_iBytesCountMAvg; + return(m_iCountMAvg); +} + +/* Update moving average of acked data pkts, bytes, and timespan (ms) of the receive buffer */ +void CRcvBuffer::updRcvAvgDataSize(uint64_t now) +{ + uint64_t elapsed = (now - m_LastSamplingTime) / 1000; //ms since last sampling + + if ((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 > elapsed) + return; /* Last sampling too recent, skip */ + + if (1000000 < elapsed) + { + /* No sampling in last 1 sec, initialize/reset moving average */ + m_iCountMAvg = getRcvDataSize(m_iBytesCountMAvg, m_TimespanMAvg); + m_LastSamplingTime = now; + + LOGC(dlog.Debug).form("getRcvDataSize: %6d %6d %6d ms elapsed:%5llu ms\n", m_iCountMAvg, m_iBytesCountMAvg, m_TimespanMAvg, (unsigned long long)elapsed); + } + else if ((1000000 / SRT_MAVG_SAMPLING_RATE) / 1000 <= elapsed) + { + /* + * Weight last average value between -1 sec and last sampling time (LST) + * and new value between last sampling time and now + * |elapsed| + * +----------------------------------+-------+ + * -1 LST 0(now) + */ + int instspan; + int bytescount; + int count = getRcvDataSize(bytescount, instspan); + + m_iCountMAvg = (int)(((count * (1000 - elapsed)) + (count * elapsed)) / 1000); + m_iBytesCountMAvg = (int)(((bytescount * (1000 - elapsed)) + (bytescount * elapsed)) / 1000); + m_TimespanMAvg = (int)(((instspan * (1000 - elapsed)) + (instspan * elapsed)) / 1000); + m_LastSamplingTime = now; + + LOGC(dlog.Debug).form("getRcvDataSize: %6d %6d %6d ms elapsed: %5llu ms\n", count, bytescount, instspan, (unsigned long long)elapsed); + } +} +#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ + +/* Return acked data pkts, bytes, and timespan (ms) of the receive buffer */ +int CRcvBuffer::getRcvDataSize(int &bytes, int ×pan) +{ + timespan = 0; + if (m_bTsbPdMode) + { + /* skip invalid entries */ + int i,n; + for (i = m_iStartPos, n = m_iLastAckPos; i != n; i = (i + 1) % m_iSize) + { + if ((NULL != m_pUnit[i]) && (CUnit::GOOD == m_pUnit[i]->m_iFlag)) + break; + } + + /* Get a valid startpos */ + int startpos = i; + int endpos = n; + + if (m_iLastAckPos != startpos) + { + /* + * |<--- DataSpan ---->|<- m_iMaxPos ->| + * +---+---+---+---+---+---+---+---+---+---+---+--- + * | | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | | m_pUnits[] + * +---+---+---+---+---+---+---+---+---+---+---+--- + * | | + * \_ m_iStartPos \_ m_iLastAckPos + * + * m_pUnits[startpos] shall be valid (->m_iFlag==CUnit::GOOD). + * If m_pUnits[m_iLastAckPos-1] is not valid (NULL or ->m_iFlag!=CUnit::GOOD), + * it means m_pUnits[m_iLastAckPos] is valid since a valid unit is needed to skip. + * Favor m_pUnits[m_iLastAckPos] if valid over [m_iLastAckPos-1] to include the whole acked interval. + */ + if ((m_iMaxPos <= 0) + || (!m_pUnit[m_iLastAckPos]) + || (m_pUnit[m_iLastAckPos]->m_iFlag != CUnit::GOOD)) + { + endpos = (m_iLastAckPos == 0 ? m_iSize - 1 : m_iLastAckPos - 1); + } + + if ((NULL != m_pUnit[endpos]) && (NULL != m_pUnit[startpos])) + { + uint64_t startstamp = getPktTsbPdTime(m_pUnit[startpos]->m_Packet.getMsgTimeStamp()); + uint64_t endstamp = getPktTsbPdTime(m_pUnit[endpos]->m_Packet.getMsgTimeStamp()); + /* + * There are sampling conditions where spantime is < 0 (big unsigned value). + * It has been observed after changing the SRT latency from 450 to 200 on the sender. + * + * Possible packet order corruption when dropping packet, + * cause by bad thread protection when adding packet in queue + * was later discovered and fixed. Security below kept. + * + * DateTime RecvRate LostRate DropRate AvailBw RTT RecvBufs PdDelay + * 2014-12-08T15:04:25-0500 4712 110 0 96509 33.710 393 450 + * 2014-12-08T15:04:35-0500 4512 95 0 107771 33.493 1496542976 200 + * 2014-12-08T15:04:40-0500 4213 106 3 107352 53.657 9499425 200 + * 2014-12-08T15:04:45-0500 4575 104 0 102194 53.614 59666 200 + * 2014-12-08T15:04:50-0500 4475 124 0 100543 53.526 505 200 + */ + if (endstamp > startstamp) + timespan = (int)((endstamp - startstamp) / 1000); + } + /* + * Timespan can be less then 1000 us (1 ms) if few packets. + * Also, if there is only one pkt in buffer, the time difference will be 0. + * Therefore, always add 1 ms if not empty. + */ + if (0 < m_iAckedPktsCount) + timespan += 1; + } + } + LOGC(dlog.Debug).form("getRcvDataSize: %6d %6d %6d ms\n", m_iAckedPktsCount, m_iAckedBytesCount, timespan); + bytes = m_iAckedBytesCount; + 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) + if ((m_pUnit[i] != NULL) + && (m_pUnit[i]->m_Packet.getMsgSeq(using_rexmit_flag) == msgno)) + m_pUnit[i]->m_iFlag = CUnit::DROPPED; +} + +#ifdef SRT_ENABLE_TSBPD +uint64_t CRcvBuffer::getTsbPdTimeBase(uint32_t timestamp) +{ + /* + * Packet timestamps wrap around every 01h11m35s (32-bit in usec) + * When added to the peer start time (base time), + * wrapped around timestamps don't provide a valid local packet delevery time. + * + * A wrap check period starts 30 seconds before the wrap point. + * In this period, timestamps smaller than 30 seconds are considered to have wrapped around (then adjusted). + * The wrap check period ends 30 seconds after the wrap point, afterwhich time base has been adjusted. + */ + uint64_t carryover = 0; + + // This function should generally return the timebase for the given timestamp. + // It's assumed that the timestamp, for which this function is being called, + // is received as monotonic clock. This function then traces the changes in the + // timestamps passed as argument and catches the moment when the 64-bit timebase + // should be increased by a "segment length" (MAX_TIMESTAMP+1). + + // The checks will be provided for the following split: + // [INITIAL30][FOLLOWING30]....[LAST30] <-- == CPacket::MAX_TIMESTAMP + // + // The following actions should be taken: + // 1. Check if this is [LAST30]. If so, ENTER TSBPD-wrap-check state + // 2. Then, it should turn into [INITIAL30] at some point. If so, use carryover MAX+1. + // 3. Then it should switch to [FOLLOWING30]. If this is detected, + // - EXIT TSBPD-wrap-check state + // - save the carryover as the current time base. + + if (m_bTsbPdWrapCheck) + { + // Wrap check period. + + if (timestamp < TSBPD_WRAP_PERIOD) + { + carryover = uint64_t(CPacket::MAX_TIMESTAMP) + 1; + } + // + else if ((timestamp >= TSBPD_WRAP_PERIOD) + && (timestamp <= (TSBPD_WRAP_PERIOD * 2))) + { + /* Exiting wrap check period (if for packet delivery head) */ + m_bTsbPdWrapCheck = false; + m_ullTsbPdTimeBase += uint64_t(CPacket::MAX_TIMESTAMP) + 1; + tslog.Debug("tsppd wrap period ends"); + } + } + // Check if timestamp is in the last 30 seconds before reaching the MAX_TIMESTAMP. + else if (timestamp > (CPacket::MAX_TIMESTAMP - TSBPD_WRAP_PERIOD)) + { + /* Approching wrap around point, start wrap check period (if for packet delivery head) */ + m_bTsbPdWrapCheck = true; + tslog.Debug("tsppd wrap period begins"); + } + return(m_ullTsbPdTimeBase + carryover); +} + +uint64_t CRcvBuffer::getPktTsbPdTime(uint32_t timestamp) +{ + return(getTsbPdTimeBase(timestamp) + m_uTsbPdDelay + timestamp + m_DriftTracer.drift()); +} + +int CRcvBuffer::setRcvTsbPdMode(uint64_t timebase, uint32_t delay) +{ + m_bTsbPdMode = true; + m_bTsbPdWrapCheck = false; + + // Timebase passed here comes is calculated as: + // >>> CTimer::getTime() - ctrlpkt->m_iTimeStamp + // where ctrlpkt is the packet with SRT_CMD_HSREQ message. + // + // This function is called in the HSREQ reception handler only. + m_ullTsbPdTimeBase = timebase; + // XXX Note that this is completely wrong. + // At least this solution this way won't work with application-supplied + // timestamps. For that case the timestamps should be taken exclusively + // from the data packets because in case of application-supplied timestamps + // they come from completely different server and undergo different rules + // of network latency and drift. + m_uTsbPdDelay = delay; + return 0; +} + +#ifdef SRT_DEBUG_TSBPD_DRIFT +void CRcvBuffer::printDriftHistogram(int64_t iDrift) +{ + /* + * Build histogram of drift values + * First line (ms): <=-10.0 -9.0 ... -1.0 - 0.0 + 1.0 ... 9.0 >=10.0 + * Second line (ms): -0.9 ... -0.1 - 0.0 + 0.1 ... 0.9 + * 0 0 0 0 0 0 0 0 0 0 - 0 + 0 0 0 1 0 0 0 0 0 0 + * 0 0 0 0 0 0 0 0 0 - 0 + 0 0 0 0 0 0 0 0 0 + */ + iDrift /= 100; // uSec to 100 uSec (0.1ms) + if (-10 < iDrift && iDrift < 10) + { + /* Fill 100us histogram -900 .. 900 us 100 us increments */ + m_TsbPdDriftHisto100us[10 + iDrift]++; + } + else + { + /* Fill 1ms histogram <=-10.0, -9.0 .. 9.0, >=10.0 ms in 1 ms increments */ + iDrift /= 10; // 100uSec to 1ms + if (-10 < iDrift && iDrift < 10) m_TsbPdDriftHisto1ms[10 + iDrift]++; + else if (iDrift <= -10) m_TsbPdDriftHisto1ms[0]++; + else m_TsbPdDriftHisto1ms[20]++; + } + + if ((m_iTsbPdDriftNbSamples % TSBPD_DRIFT_PRT_SAMPLES) == 0) + { + int *histo = m_TsbPdDriftHisto1ms; + + fprintf(stderr, "%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d - %4d + ", + histo[0],histo[1],histo[2],histo[3],histo[4], + histo[5],histo[6],histo[7],histo[8],histo[9],histo[10]); + fprintf(stderr, "%4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n", + histo[11],histo[12],histo[13],histo[14],histo[15], + histo[16],histo[17],histo[18],histo[19],histo[20]); + + histo = m_TsbPdDriftHisto100us; + fprintf(stderr, " %4d %4d %4d %4d %4d %4d %4d %4d %4d - %4d + ", + histo[1],histo[2],histo[3],histo[4],histo[5], + histo[6],histo[7],histo[8],histo[9],histo[10]); + fprintf(stderr, "%4d %4d %4d %4d %4d %4d %4d %4d %4d\n", + histo[11],histo[12],histo[13],histo[14],histo[15], + histo[16],histo[17],histo[18],histo[19]); + } +} + +void CRcvBuffer::printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg) +{ + char szTime[32] = {}; + uint64_t now = CTimer::getTime(); + time_t tnow = (time_t)(now/1000000); + strftime(szTime, sizeof(szTime), "%H:%M:%S", localtime(&tnow)); + fprintf(stderr, "%s.%03d: tsbpd offset=%d drift=%d usec\n", + szTime, (int)((now%1000000)/1000), tsbPdOffset, tsbPdDriftAvg); + memset(m_TsbPdDriftHisto100us, 0, sizeof(m_TsbPdDriftHisto100us)); + memset(m_TsbPdDriftHisto1ms, 0, sizeof(m_TsbPdDriftHisto1ms)); +} +#endif /* SRT_DEBUG_TSBPD_DRIFT */ + +void CRcvBuffer::addRcvTsbPdDriftSample(uint32_t timestamp) +{ + if (!m_bTsbPdMode) // Not checked unless in TSBPD mode + return; + /* + * TsbPD time drift correction + * TsbPD time slowly drift over long period depleting decoder buffer or raising latency + * Re-evaluate the time adjustment value using a receiver control packet (ACK-ACK). + * ACK-ACK timestamp is RTT/2 ago (in sender's time base) + * Data packet have origin time stamp which is older when retransmitted so not suitable for this. + * + * Every TSBPD_DRIFT_MAX_SAMPLES packets, the average drift is calculated + * if -TSBPD_DRIFT_MAX_VALUE < avgTsbPdDrift < TSBPD_DRIFT_MAX_VALUE uSec, pass drift value to RcvBuffer to adjust delevery time. + * if outside this range, adjust this->TsbPdTimeOffset and RcvBuffer->TsbPdTimeBase by +-TSBPD_DRIFT_MAX_VALUE uSec + * to maintain TsbPdDrift values in reasonable range (-5ms .. +5ms). + */ + + // Note important thing: this function is being called _EXCLUSIVELY_ in the handler + // of UMSG_ACKACK command reception. This means that the timestamp used here comes + // from the CONTROL domain, not DATA domain (timestamps from DATA domain may be + // either schedule time or a time supplied by the application). + + int64_t iDrift = CTimer::getTime() - (getTsbPdTimeBase(timestamp) + timestamp); + bool updated = m_DriftTracer.update(iDrift); + +#ifdef SRT_DEBUG_TSBPD_DRIFT + printDriftHistogram(iDrift); +#endif /* SRT_DEBUG_TSBPD_DRIFT */ + + if ( updated ) + { +#ifdef SRT_DEBUG_TSBPD_DRIFT + printDriftOffset(m_DriftTracer.overdrift(), m_DriftTracer.drift()); +#endif /* SRT_DEBUG_TSBPD_DRIFT */ + + m_ullTsbPdTimeBase += m_DriftTracer.overdrift(); + } + +} + +int CRcvBuffer::readMsg(char* data, int len) +{ + uint64_t tsbpdtime; + 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; + + if (getRcvReadyMsg(tsbpdtime)) + { + empty = false; + p = q = m_iStartPos; + +#ifdef SRT_DEBUG_TSBPD_OUTJITTER + uint64_t now = CTimer::getTime(); + if ((now - tsbpdtime)/10 < 10) + m_ulPdHisto[0][(now - tsbpdtime)/10]++; + else if ((now - tsbpdtime)/100 < 10) + m_ulPdHisto[1][(now - tsbpdtime)/100]++; + else if ((now - tsbpdtime)/1000 < 10) + m_ulPdHisto[2][(now - tsbpdtime)/1000]++; + else + m_ulPdHisto[3][1]++; +#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ + } + } + else +#endif + { + tsbpdtime = 0; + if (scanMsg(p, q, passack)) + empty = false; + + } + + if (empty) + return 0; + + int rs = len; + while (p != (q + 1) % m_iSize) + { + int unitsize = m_pUnit[p]->m_Packet.getLength(); + if ((rs >= 0) && (unitsize > rs)) + unitsize = rs; + + if (unitsize > 0) + { + 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 + { + static uint64_t prev_now; + static uint64_t prev_srctime; + + int32_t seq = m_pUnit[p]->m_Packet.m_iSeqNo; + + uint64_t nowtime = CTimer::getTime(); + //CTimer::rdtsc(nowtime); + uint64_t srctime = getPktTsbPdTime(m_pUnit[p]->m_Packet.getMsgTimeStamp()); + + int64_t timediff = nowtime - srctime; + int64_t nowdiff = prev_now ? (nowtime - prev_now) : 0; + uint64_t srctimediff = prev_srctime ? (srctime - prev_srctime) : 0; + + LOGC(dlog.Debug) << CONID() << "readMsg: DELIVERED seq=" << seq << " T=" << logging::FormatTime(srctime) << " in " << (timediff/1000.0) << "ms - " + "TIME-PREVIOUS: PKT: " << (srctimediff/1000.0) << " LOCAL: " << (nowdiff/1000.0); + + prev_now = nowtime; + prev_srctime = srctime; + } +#endif + } + + if (!passack) + { + CUnit* tmp = m_pUnit[p]; + m_pUnit[p] = NULL; + tmp->m_iFlag = CUnit::FREE; + -- m_pUnitQueue->m_iCount; + } + else + m_pUnit[p]->m_iFlag = CUnit::PASSACK; + + if (++ p == m_iSize) + p = 0; + } + + if (!passack) + m_iStartPos = (q + 1) % m_iSize; + + return len - rs; +} + + +bool CRcvBuffer::scanMsg(int& p, int& q, bool& passack) +{ + // empty buffer + 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) + { + if (NULL == m_pUnit[m_iStartPos]) + { + if (++ m_iStartPos == m_iSize) + m_iStartPos = 0; + continue; + } + + // Note: PB_FIRST | PB_LAST == PB_SOLO. + // testing if boundary() & PB_FIRST tests if the msg is first OR solo. + if ( m_pUnit[m_iStartPos]->m_iFlag == CUnit::GOOD + && m_pUnit[m_iStartPos]->m_Packet.getMsgBoundary() & PB_FIRST ) + { + bool good = true; + + // look ahead for the whole message + for (int i = m_iStartPos; i != m_iLastAckPos;) + { + if (!m_pUnit[i] || m_pUnit[i]->m_iFlag != CUnit::GOOD) + { + good = false; + break; + } + + // Likewise, boundary() & PB_LAST will be satisfied for last OR solo. + if ( m_pUnit[i]->m_Packet.getMsgBoundary() & PB_LAST ) + break; + + if (++ i == m_iSize) + i = 0; + } + + if (good) + break; + } + + 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 + passack = m_iStartPos == m_iLastAckPos; + bool found = false; + + // looking for the first message +#if defined(HAI_PATCH) //>>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)) + { + switch (m_pUnit[q]->m_Packet.getMsgBoundary()) + { + case PB_SOLO: // 11 + p = q; + found = true; + break; + + case PB_FIRST: // 10 + p = q; + break; + + case PB_LAST: // 01 + if (p != -1) + found = true; + break; + + case PB_SUBSEQUENT: + ; // do nothing + } + } + else + { + // a hole in this message, not valid, restart search + p = -1; + } + + if (found) + { + // the msg has to be ack'ed or it is allowed to read out of order, and was not read before + if (!passack || !m_pUnit[q]->m_Packet.getMsgOrderFlag()) + break; + + found = false; + } + + if (++ q == m_iSize) + q = 0; + + if (q == m_iLastAckPos) + passack = true; + } + + // no msg found + if (!found) + { + // if the message is larger than the receiver buffer, return part of the message + if ((p != -1) && ((q + 1) % m_iSize == p)) + found = true; + } + + return found; +} diff --git a/srtcore/buffer.h b/srtcore/buffer.h new file mode 100644 index 000000000..0a0be274d --- /dev/null +++ b/srtcore/buffer.h @@ -0,0 +1,550 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 05/05/2009 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_BUFFER_H__ +#define __UDT_BUFFER_H__ + + +#include "udt.h" +#include "list.h" +#include "queue.h" +#include "utilities.h" +#include + +class CSndBuffer +{ +public: + + // XXX There's currently no way to access the socket ID set for + // whatever the buffer is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } + + CSndBuffer(int size = 32, int mss = 1500); + ~CSndBuffer(); + + /// Insert a user buffer into the sending list. + /// @param [in] data pointer to the user data block. + /// @param [in] len size of the block. + /// @param [in] ttl time to live in milliseconds + /// @param [in] order if the block should be delivered in order, for DGRAM only + +#ifdef SRT_ENABLE_SRCTIMESTAMP + void addBuffer(const char* data, int len, int ttl = -1, bool order = false, uint64_t srctime = 0); +#else + void addBuffer(const char* data, int len, int ttl = -1, bool order = false); +#endif + + /// Read a block of data from file and insert it into the sending list. + /// @param [in] ifs input file stream. + /// @param [in] len size of the block. + /// @return actual size of data added from the file. + + int addBufferFromFile(std::fstream& ifs, int len); + + /// 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. + /// @param [out] origintime origin time stamp of the message + /// @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. + /// @param [out] msgno message number of the packet. + /// @param [out] origintime origin time stamp of the message + /// @param [out] msglen length of the message + /// @return Actual length of data read. + + 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. + + void ackData(int offset); + + /// Read size of data still in the sending list. + /// @return Current size of the data in the sending list. + + int getCurrBufSize() const; + +#ifdef SRT_ENABLE_TLPKTDROP + 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(); + +private: + pthread_mutex_t m_BufLock; // used to synchronize buffer operation + + struct Block + { + char* m_pcData; // pointer to the data block + int m_iLength; // length of the block + + int32_t m_iMsgNoBitset; // message number + uint64_t m_OriginTime; // original request time +#ifdef SRT_ENABLE_SRCTIMESTAMP + uint64_t m_SourceTime; +#endif + int m_iTTL; // time to live (milliseconds) + + Block* m_pNext; // next block + + int32_t getMsgSeq() + { + // NOTE: this extracts message ID with regard to REXMIT flag. + // This is valid only for message ID that IS GENERATED in this instance, + // not provided by the peer. This can be otherwise sent to the peer - it doesn't matter + // for the peer that it uses LESS bits to represent the message. + return m_iMsgNoBitset & MSGNO_SEQ::mask; + } + + } *m_pBlock, *m_pFirstBlock, *m_pCurrBlock, *m_pLastBlock; + + // m_pBlock: The head pointer + // m_pFirstBlock: The first block + // m_pCurrBlock: The current block + // m_pLastBlock: The last block (if first == last, buffer is empty) + + struct Buffer + { + char* m_pcData; // buffer + int m_iSize; // size + Buffer* m_pNext; // next buffer + } *m_pBuffer; // physical buffer + + int32_t m_iNextMsgNo; // next message number + + int m_iSize; // buffer size (number of packets) + int m_iMSS; // maximum seqment/packet size + + int m_iCount; // number of used blocks + +#ifdef SRT_ENABLE_BSTATS + int m_iBytesCount; // number of payload bytes in queue + uint64_t m_LastOriginTime; + +#ifdef SRT_ENABLE_SNDBUFSZ_MAVG + uint64_t m_LastSamplingTime; + int m_iCountMAvg; + 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&); + CSndBuffer& operator=(const CSndBuffer&); +}; + +//////////////////////////////////////////////////////////////////////////////// + + +class CRcvBuffer +{ +public: + + // XXX There's currently no way to access the socket ID set for + // whatever the queue is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } + + CRcvBuffer(CUnitQueue* queue, int bufsize = 65536); + ~CRcvBuffer(); + + /// Write data into the buffer. + /// @param [in] unit pointer to a data unit containing new packet + /// @param [in] offset offset from last ACK point. + /// @return 0 is success, -1 if data is repeated. + + int addData(CUnit* unit, int offset); + + /// Read data into a user buffer. + /// @param [in] data pointer to user buffer. + /// @param [in] len length of user buffer. + /// @return size of data read. + + int readBuffer(char* data, int len); + + /// Read data directly into file. + /// @param [in] file C++ file stream. + /// @param [in] len expected length of data to write into the file. + /// @return size of data read. + + int readBufferToFile(std::fstream& ofs, int len); + + /// Update the ACK point of the buffer. + /// @param [in] len size of data to be acknowledged. + /// @return 1 if a user buffer is fulfilled, otherwise 0. + + void ackData(int len); + + /// Query how many buffer space left for data receiving. + /// @return size of available buffer space (including user buffer) for data receiving. + + int getAvailBufSize() const; + + /// Query how many data has been continuously received (for reading) and ready to play (tsbpdtime < now). + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay + /// @return size of valid (continous) data for reading. + + int getRcvDataSize() const; + +#ifdef SRT_ENABLE_BSTATS + /// Query how many data was received and acknowledged. + /// @param bytes [out] bytes + /// @param spantime [out] spantime + /// @return size in pkts of acked data. + + int getRcvDataSize(int &bytes, int &spantime); +#if SRT_ENABLE_RCVBUFSZ_MAVG + + /// Query a 1 sec moving average of how many data was received and acknowledged. + /// @param bytes [out] bytes + /// @param spantime [out] spantime + /// @return size in pkts of acked data. + + int getRcvAvgDataSize(int &bytes, int &spantime); + + /// Query how many data of the receive buffer is acknowledged. + /// @param now [in] current time in us. + /// @return none. + + void updRcvAvgDataSize(uint64_t now); +#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ + + /// Query the received average payload size. + /// @return size (bytes) of payload size + + int getRcvAvgPayloadSize() const; +#endif /* SRT_ENABLE_BSTATS */ + + + /// Mark the message to be dropped from the message list. + /// @param [in] msgno message number. + /// @param [in] using_rexmit_flag whether the MSGNO field uses rexmit flag (if not, one more bit is part of the msgno value) + + void dropMsg(int32_t msgno, bool using_rexmit_flag); + + /// read a message. + /// @param [out] data buffer to write the message into. + /// @param [in] len size of the buffer. + /// @return actuall size of data read. + + 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. + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay + /// @return actuall size of data read. + + int readMsg(char* data, int len, uint64_t& tsbpdtime); + + /// Query how many messages are available now. + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay + /// @return number of messages available for recvmsg. + + // XXX This seems to be a little bit kind-of dissonance + // between the intended code and the reality. This function + // had to return the number of messages available for processing. + // However it turned out that actually: + // - no one is interested with how many messages are available + // - stating that "none" or "any" suffices for all cases + // - so getRcvMsgNum has more-less always the same definition as below + // Which means that there's no difference between + // (getRcvMsgNum() > 0) and (isRcvDataReady()) expression. + // These functions are now blocked as it doesn't make sense completely. + + // int getRcvMsgNum(uint64_t& tsbpdtime) { return isRcvDataReady(tsbpdtime)? 1 : 0; } + // int getRcvMsgNum() { return isRcvDataReady()? 1 : 0; } + + /// Query if data is ready to read (tsbpdtime <= now if TsbPD is active). + /// @param [out] tsbpdtime localtime-based (uSec) packet time stamp including buffering delay + /// of next packet in recv buffer, ready or not. + /// @return true if ready to play, false otherwise (tsbpdtime may be !0 in + /// both cases). + + bool isRcvDataReady(uint64_t& tsbpdtime, CPacket** pppkt = 0); + bool isRcvDataReady(); + bool isRcvDataAvailable() + { + return m_iLastAckPos != m_iStartPos; + } + CPacket* getRcvReadyPacket(); + bool isReadyToPlay(const CPacket* p, uint64_t& tsbpdtime); + + /// Set TimeStamp-Based Packet Delivery Rx Mode + /// @param [in] timebase localtime base (uSec) of packet time stamps including buffering delay + /// @param [in] delay aggreed TsbPD delay + /// @return 0 + + int setRcvTsbPdMode(uint64_t timebase, uint32_t delay); + + /// Add packet timestamp for drift caclculation and compensation + /// @param [in] timestamp packet time stamp + + void addRcvTsbPdDriftSample(uint32_t timestamp); + +#ifdef SRT_DEBUG_TSBPD_DRIFT + void printDriftHistogram(int64_t iDrift); + void printDriftOffset(int tsbPdOffset, int tsbPdDriftAvg); +#endif + +#ifdef SRT_ENABLE_TLPKTDROP + /// Get information on the 1st message in queue. + // Parameters (of the 1st packet queue, ready to play or not): + /// @param tsbpdtime [out] localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if none + /// @param passack [out] true if 1st ready packet is not yet acknowleged + /// @param skipseqno [out] -1 or seq number of 1st unacknowledged pkt ready to play preceeded by missing packets. + /// @retval true 1st packet ready to play (tsbpdtime <= now). Not yet acknowledged if passack == true + /// @retval false IF tsbpdtime = 0: rcv buffer empty; ELSE: + /// IF skipseqno != -1, packet ready to play preceeded by missing packets.; + /// IF skipseqno == -1, no missing packet but 1st not ready to play. + + + bool getRcvFirstMsg(uint64_t& tsbpdtime, bool& passack, int32_t& skipseqno, CPacket** pppkt=0); + + /// Update the ACK point of the buffer. + /// @param len [in] size of data to be skip & acknowledged. + + void skipData(int len); + +#endif /* SRT_ENABLE_TLPKTDROP */ + +private: + /// Adjust receive queue to 1st ready to play message (tsbpdtime < now). + // Parameters (of the 1st packet queue, ready to play or not): + /// @param tsbpdtime [out] localtime-based (uSec) packet time stamp including buffering delay of 1st packet or 0 if none + /// @retval true 1st packet ready to play without discontinuity (no hole) + /// @retval false tsbpdtime = 0: no packet ready to play + + + bool getRcvReadyMsg(uint64_t& tsbpdtime, CPacket** pppkt = 0); + + /// Get packet delivery local time base (adjusted for wrap around) + /// @param timestamp [in] packet timestamp (relative to peer StartTime), wrapping around every ~72 min + /// @return local delivery time (usec) + + uint64_t getTsbPdTimeBase(uint32_t timestamp); + + /// Get packet local delivery time + /// @param [in] timestamp packet timestamp (relative to peer StartTime), wrapping around every ~72 min + /// @return local delivery time (usec) + +public: + 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); + +private: + CUnit** m_pUnit; // pointer to the protocol buffer + int m_iSize; // size of the protocol buffer + CUnitQueue* m_pUnitQueue; // the shared unit queue + + int m_iStartPos; // the head position for I/O (inclusive) + int m_iLastAckPos; // the last ACKed position (exclusive) + // EMPTY: m_iStartPos = m_iLastAckPos FULL: m_iStartPos = m_iLastAckPos + 1 + int m_iMaxPos; // the furthest data position + + 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 + // Note: m_ullTsbPdTimeBase cumulates values from: + // 1. Initial SRT_CMD_HSREQ packet returned value diff to current time: + // == (NOW - PACKET_TIMESTAMP), at the time of HSREQ reception + // 2. Timestamp overflow (@c CRcvBuffer::getTsbPdTimeBase), when overflow on packet detected + // += CPacket::MAX_TIMESTAMP+1 (it's a hex round value, usually 0x1*e8). + // 3. Time drift (CRcvBuffer::addRcvTsbPdDriftSample, executed exclusively + // from UMSG_ACKACK handler). This is updated with (positive or negative) TSBPD_DRIFT_MAX_VALUE + // once the value of average drift exceeds this value in whatever direction. + // += (+/-)CRcvBuffer::TSBPD_DRIFT_MAX_VALUE + // + // XXX Application-supplied timestamps won't work therefore. This requires separate + // calculation of all these things above. + + bool m_bTsbPdWrapCheck; // true: check packet time stamp wrap around + static const uint32_t TSBPD_WRAP_PERIOD = (30*1000000); //30 seconds (in usec) + + static const int TSBPD_DRIFT_MAX_VALUE = 5000; // Max drift (usec) above which TsbPD Time Offset is adjusted + static const int TSBPD_DRIFT_MAX_SAMPLES = 1000; // Number of samples (UMSG_ACKACK packets) to perform drift caclulation and compensation + //int m_iTsbPdDrift; // recent drift in the packet time stamp + //int64_t m_TsbPdDriftSum; // Sum of sampled drift + //int m_iTsbPdDriftNbSamples; // Number of samples in sum and histogram + DriftTracer m_DriftTracer; +#ifdef SRT_ENABLE_RCVBUFSZ_MAVG + uint64_t m_LastSamplingTime; + int m_TimespanMAvg; + int m_iCountMAvg; + int m_iBytesCountMAvg; +#endif /* SRT_ENABLE_RCVBUFSZ_MAVG */ +#ifdef SRT_DEBUG_TSBPD_DRIFT + int m_TsbPdDriftHisto100us[22]; // Histogram of 100us TsbPD drift (-1.0 .. +1.0 ms in 0.1ms increment) + int m_TsbPdDriftHisto1ms[22]; // Histogram of TsbPD drift (-10.0 .. +10.0 ms, in 1.0 ms increment) + static const int TSBPD_DRIFT_PRT_SAMPLES = 200; // Number of samples (UMSG_ACKACK packets) to print hostogram +#endif /* SRT_DEBUG_TSBPD_DRIFT */ + +#ifdef SRT_DEBUG_TSBPD_OUTJITTER + unsigned long m_ulPdHisto[4][10]; +#endif /* SRT_DEBUG_TSBPD_OUTJITTER */ +#endif /* SRT_ENABLE_TSBPD */ + +private: + CRcvBuffer(); + CRcvBuffer(const CRcvBuffer&); + CRcvBuffer& operator=(const CRcvBuffer&); +}; + + +#endif diff --git a/srtcore/cache.cpp b/srtcore/cache.cpp new file mode 100644 index 000000000..ea0aad190 --- /dev/null +++ b/srtcore/cache.cpp @@ -0,0 +1,123 @@ +/***************************************************************************** +Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 05/05/2009 +*****************************************************************************/ + +#ifdef WIN32 + #include + #include + #ifdef LEGACY_WIN32 + #include + #endif +#endif + +#include +#include "cache.h" +#include "core.h" + +using namespace std; + +CInfoBlock& CInfoBlock::operator=(const CInfoBlock& obj) +{ + std::copy(obj.m_piIP, obj.m_piIP + 3, m_piIP); + m_iIPversion = obj.m_iIPversion; + m_ullTimeStamp = obj.m_ullTimeStamp; + m_iRTT = obj.m_iRTT; + m_iBandwidth = obj.m_iBandwidth; + m_iLossRate = obj.m_iLossRate; + m_iReorderDistance = obj.m_iReorderDistance; + m_dInterval = obj.m_dInterval; + m_dCWnd = obj.m_dCWnd; + + return *this; +} + +bool CInfoBlock::operator==(const CInfoBlock& obj) +{ + if (m_iIPversion != obj.m_iIPversion) + return false; + + else if (m_iIPversion == AF_INET) + return (m_piIP[0] == obj.m_piIP[0]); + + for (int i = 0; i < 4; ++ i) + { + if (m_piIP[i] != obj.m_piIP[i]) + return false; + } + + return true; +} + +CInfoBlock* CInfoBlock::clone() +{ + CInfoBlock* obj = new CInfoBlock; + + std::copy(m_piIP, m_piIP + 3, obj->m_piIP); + obj->m_iIPversion = m_iIPversion; + obj->m_ullTimeStamp = m_ullTimeStamp; + obj->m_iRTT = m_iRTT; + obj->m_iBandwidth = m_iBandwidth; + obj->m_iLossRate = m_iLossRate; + obj->m_iReorderDistance = m_iReorderDistance; + obj->m_dInterval = m_dInterval; + obj->m_dCWnd = m_dCWnd; + + return obj; +} + +int CInfoBlock::getKey() +{ + if (m_iIPversion == AF_INET) + return m_piIP[0]; + + return m_piIP[0] + m_piIP[1] + m_piIP[2] + m_piIP[3]; +} + +void CInfoBlock::convert(const sockaddr* addr, int ver, uint32_t ip[]) +{ + if (ver == AF_INET) + { + ip[0] = ((sockaddr_in*)addr)->sin_addr.s_addr; + ip[1] = ip[2] = ip[3] = 0; + } + else + { + memcpy((char*)ip, (char*)((sockaddr_in6*)addr)->sin6_addr.s6_addr, 16); + } +} diff --git a/srtcore/cache.h b/srtcore/cache.h new file mode 100644 index 000000000..bf3d630f0 --- /dev/null +++ b/srtcore/cache.h @@ -0,0 +1,266 @@ +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/27/2011 +*****************************************************************************/ + +#ifndef __UDT_CACHE_H__ +#define __UDT_CACHE_H__ + +#include +#include + +#include "common.h" +#include "udt.h" + +class CCacheItem +{ +public: + virtual ~CCacheItem() {} + +public: + virtual CCacheItem& operator=(const CCacheItem&) = 0; + + // The "==" operator SHOULD only compare key values. + virtual bool operator==(const CCacheItem&) = 0; + + /// get a deep copy clone of the current item + /// @return Pointer to the new item, or NULL if failed. + + virtual CCacheItem* clone() = 0; + + /// get a random key value between 0 and MAX_INT to be used for the hash in cache + /// @return A random hash key. + + virtual int getKey() = 0; + + // If there is any shared resources between the cache item and its clone, + // the shared resource should be released by this function. + virtual void release() {} +}; + +template class CCache +{ +public: + CCache(int size = 1024): + m_iMaxSize(size), + m_iHashSize(size * 3), + m_iCurrSize(0) + { + m_vHashPtr.resize(m_iHashSize); + CGuard::createMutex(m_Lock); + } + + ~CCache() + { + clear(); + CGuard::releaseMutex(m_Lock); + } + +public: + /// find the matching item in the cache. + /// @param [in,out] data storage for the retrieved item; initially it must carry the key information + /// @return 0 if found a match, otherwise -1. + + int lookup(T* data) + { + CGuard cacheguard(m_Lock); + + int key = data->getKey(); + if (key < 0) + return -1; + if (key >= m_iMaxSize) + key %= m_iHashSize; + + const ItemPtrList& item_list = m_vHashPtr[key]; + for (typename ItemPtrList::const_iterator i = item_list.begin(); i != item_list.end(); ++ i) + { + if (*data == ***i) + { + // copy the cached info + *data = ***i; + return 0; + } + } + + return -1; + } + + /// update an item in the cache, or insert one if it doesn't exist; oldest item may be removed + /// @param [in] data the new item to updated/inserted to the cache + /// @return 0 if success, otherwise -1. + + int update(T* data) + { + CGuard cacheguard(m_Lock); + + int key = data->getKey(); + if (key < 0) + return -1; + if (key >= m_iMaxSize) + key %= m_iHashSize; + + T* curr = NULL; + + ItemPtrList& item_list = m_vHashPtr[key]; + for (typename ItemPtrList::iterator i = item_list.begin(); i != item_list.end(); ++ i) + { + if (*data == ***i) + { + // update the existing entry with the new value + ***i = *data; + curr = **i; + + // remove the current entry + m_StorageList.erase(*i); + item_list.erase(i); + + // re-insert to the front + m_StorageList.push_front(curr); + item_list.push_front(m_StorageList.begin()); + + return 0; + } + } + + // create new entry and insert to front + curr = data->clone(); + m_StorageList.push_front(curr); + item_list.push_front(m_StorageList.begin()); + + ++ m_iCurrSize; + if (m_iCurrSize >= m_iMaxSize) + { + // Cache overflow, remove oldest entry. + T* last_data = m_StorageList.back(); + int last_key = last_data->getKey() % m_iHashSize; + + item_list = m_vHashPtr[last_key]; + for (typename ItemPtrList::iterator i = item_list.begin(); i != item_list.end(); ++ i) + { + if (*last_data == ***i) + { + item_list.erase(i); + break; + } + } + + last_data->release(); + delete last_data; + m_StorageList.pop_back(); + -- m_iCurrSize; + } + + return 0; + } + + /// Specify the cache size (i.e., max number of items). + /// @param [in] size max cache size. + + void setSizeLimit(int size) + { + m_iMaxSize = size; + m_iHashSize = size * 3; + m_vHashPtr.resize(m_iHashSize); + } + + /// Clear all entries in the cache, restore to initialization state. + + void clear() + { + for (typename std::list::iterator i = m_StorageList.begin(); i != m_StorageList.end(); ++ i) + { + (*i)->release(); + delete *i; + } + m_StorageList.clear(); + for (typename std::vector::iterator i = m_vHashPtr.begin(); i != m_vHashPtr.end(); ++ i) + i->clear(); + m_iCurrSize = 0; + } + +private: + std::list m_StorageList; + typedef typename std::list::iterator ItemPtr; + typedef std::list ItemPtrList; + std::vector m_vHashPtr; + + int m_iMaxSize; + int m_iHashSize; + int m_iCurrSize; + + pthread_mutex_t m_Lock; + +private: + CCache(const CCache&); + CCache& operator=(const CCache&); +}; + + +class CInfoBlock +{ +public: + uint32_t m_piIP[4]; // IP address, machine read only, not human readable format + int m_iIPversion; // IP version + uint64_t m_ullTimeStamp; // last update time + int m_iRTT; // RTT + int m_iBandwidth; // estimated bandwidth + int m_iLossRate; // average loss rate + int m_iReorderDistance; // packet reordering distance + double m_dInterval; // inter-packet time, congestion control + double m_dCWnd; // congestion window size, congestion control + +public: + virtual ~CInfoBlock() {} + virtual CInfoBlock& operator=(const CInfoBlock& obj); + virtual bool operator==(const CInfoBlock& obj); + virtual CInfoBlock* clone(); + virtual int getKey(); + virtual void release() {} + +public: + + /// convert sockaddr structure to an integer array + /// @param [in] addr network address + /// @param [in] ver IP version + /// @param [out] ip the result machine readable IP address in integer array + + static void convert(const sockaddr* addr, int ver, uint32_t ip[]); +}; + + +#endif diff --git a/srtcore/ccc.cpp b/srtcore/ccc.cpp new file mode 100644 index 000000000..0de78becf --- /dev/null +++ b/srtcore/ccc.cpp @@ -0,0 +1,370 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 02/21/2013 +modified by + Haivision Systems Inc. +*****************************************************************************/ + + +#include "core.h" +#include "ccc.h" +#include +#include + +CCC::CCC(): +m_iSYNInterval(CUDT::m_iSYNInterval), +m_dPktSndPeriod(1.0), +m_dCWndSize(16.0), +m_iBandwidth(), +m_dMaxCWndSize(), +m_iMSS(), +m_iSndCurrSeqNo(), +m_iRcvRate(), +m_iRTT(), +m_pcParam(NULL), +m_iPSize(0), +m_UDT(), +m_iACKPeriod(0), +m_iACKInterval(0), +m_bUserDefinedRTO(false), +m_iRTO(-1), +m_PerfInfo() +{ +} + +CCC::~CCC() +{ + delete [] m_pcParam; +} + +void CCC::setACKTimer(int msINT) +{ + m_iACKPeriod = msINT > m_iSYNInterval ? m_iSYNInterval : msINT; +} + +void CCC::setACKInterval(int pktINT) +{ + m_iACKInterval = pktINT; +} + +void CCC::setRTO(int usRTO) +{ + m_bUserDefinedRTO = true; + m_iRTO = usRTO; +} + +void CCC::sendCustomMsg(CPacket& pkt) const +{ + CUDT* u = CUDT::getUDTHandle(m_UDT); + + if (NULL != u) + { + pkt.m_iID = u->m_PeerID; +#ifdef SRT_ENABLE_CTRLTSTAMP + pkt.m_iTimeStamp = int(CTimer::getTime() - u->m_StartTime); +#endif + u->m_pSndQueue->sendto(u->m_pPeerAddr, pkt); + } +} + +const CPerfMon* CCC::getPerfInfo() +{ + try + { + CUDT* u = CUDT::getUDTHandle(m_UDT); + if (NULL != u) + u->sample(&m_PerfInfo, false); + } + catch (...) + { + return NULL; + } + + return &m_PerfInfo; +} + +void CCC::setMSS(int mss) +{ + m_iMSS = mss; +} + +void CCC::setBandwidth(int bw) +{ + m_iBandwidth = bw; +} + +void CCC::setSndCurrSeqNo(int32_t seqno) +{ + m_iSndCurrSeqNo = seqno; +} + +void CCC::setRcvRate(int rcvrate) +{ + m_iRcvRate = rcvrate; +} + +void CCC::setMaxCWndSize(int cwnd) +{ + m_dMaxCWndSize = cwnd; +} + +void CCC::setRTT(int rtt) +{ + m_iRTT = rtt; +} + +void CCC::setUserParam(const char* param, int size) +{ + delete [] m_pcParam; + m_pcParam = new char[size]; + memcpy(m_pcParam, param, size); + m_iPSize = size; +} + +// +CUDTCC::CUDTCC(): +m_iRCInterval(), +m_LastRCTime(), +m_bSlowStart(), +m_iLastAck(), +m_bLoss(), +m_iLastDecSeq(), +m_dLastDecPeriod(), +m_iNAKCount(), +m_iDecRandom(), +m_iAvgNAKNum(), +m_iDecCount() +{ +} + +void CUDTCC::init() +{ + m_iRCInterval = m_iSYNInterval; + m_LastRCTime = CTimer::getTime(); + setACKTimer(m_iRCInterval); + + m_bSlowStart = true; + m_iLastAck = m_iSndCurrSeqNo; + m_bLoss = false; + m_iLastDecSeq = CSeqNo::decseq(m_iLastAck); + m_dLastDecPeriod = 1; + m_iAvgNAKNum = 0; + m_iNAKCount = 0; + m_iDecRandom = 1; + + m_dCWndSize = 16; + m_dPktSndPeriod = 1; +} + +void CUDTCC::onACK(int32_t ack) +{ + int64_t B = 0; + double inc = 0; + // Note: 1/24/2012 + // The minimum increase parameter is increased from "1.0 / m_iMSS" to 0.01 + // because the original was too small and caused sending rate to stay at low level + // for long time. + const double min_inc = 0.01; + + uint64_t currtime = CTimer::getTime(); + if (currtime - m_LastRCTime < (uint64_t)m_iRCInterval) + return; + + m_LastRCTime = currtime; + +#ifdef SRT_ENABLE_BSTATS + //m_iRcvRate is bytes/sec + if (m_bSlowStart) + { + m_dCWndSize += CSeqNo::seqlen(m_iLastAck, ack); + m_iLastAck = ack; + + if (m_dCWndSize > m_dMaxCWndSize) + { + m_bSlowStart = false; + if (m_iRcvRate > 0) + m_dPktSndPeriod = 1000000.0 / ((m_iRcvRate + m_iMSS - 1) / m_iMSS); + else + m_dPktSndPeriod = (m_iRTT + m_iRCInterval) / m_dCWndSize; + } + } + else + m_dCWndSize = ((m_iRcvRate + m_iMSS -1) / m_iMSS) / 1000000.0 * (m_iRTT + m_iRCInterval) + 16; +#else + if (m_bSlowStart) + { + m_dCWndSize += CSeqNo::seqlen(m_iLastAck, ack); + m_iLastAck = ack; + + if (m_dCWndSize > m_dMaxCWndSize) + { + m_bSlowStart = false; + if (m_iRcvRate > 0) + m_dPktSndPeriod = 1000000.0 / m_iRcvRate; + else + m_dPktSndPeriod = (m_iRTT + m_iRCInterval) / m_dCWndSize; + } + } + else + m_dCWndSize = m_iRcvRate / 1000000.0 * (m_iRTT + m_iRCInterval) + 16; +#endif + + // During Slow Start, no rate increase + if (m_bSlowStart) + return; + + if (m_bLoss) + { + m_bLoss = false; + return; + } + + //m_iBandwidth is pkts/sec + B = (int64_t)(m_iBandwidth - 1000000.0 / m_dPktSndPeriod); + if ((m_dPktSndPeriod > m_dLastDecPeriod) && ((m_iBandwidth / 9) < B)) + B = m_iBandwidth / 9; + if (B <= 0) + inc = min_inc; + else + { + // inc = max(10 ^ ceil(log10( B * MSS * 8 ) * Beta / MSS, 1/MSS) + // Beta = 1.5 * 10^(-6) + + inc = pow(10.0, ceil(log10(B * m_iMSS * 8.0))) * 0.0000015 / m_iMSS; + + if (inc < min_inc) + inc = min_inc; + } + + m_dPktSndPeriod = (m_dPktSndPeriod * m_iRCInterval) / (m_dPktSndPeriod * inc + m_iRCInterval); +} + +void CUDTCC::onLoss(const int32_t* losslist, int) +{ + //Slow Start stopped, if it hasn't yet + if (m_bSlowStart) + { + m_bSlowStart = false; + if (m_iRcvRate > 0) + { + // Set the sending rate to the receiving rate. +#ifdef SRT_ENABLE_BSTATS + //Need average packet size here for better send period + m_dPktSndPeriod = 1000000.0 / ((m_iRcvRate + m_iMSS - 1) / m_iMSS); +#else + m_dPktSndPeriod = 1000000.0 / m_iRcvRate; +#endif + return; + } + // If no receiving rate is observed, we have to compute the sending + // rate according to the current window size, and decrease it + // using the method below. + m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval); + } + + m_bLoss = true; + + if (CSeqNo::seqcmp(losslist[0] & 0x7FFFFFFF, m_iLastDecSeq) > 0) + { + m_dLastDecPeriod = m_dPktSndPeriod; + m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.125); + + m_iAvgNAKNum = (int)ceil(m_iAvgNAKNum * 0.875 + m_iNAKCount * 0.125); + m_iNAKCount = 1; + m_iDecCount = 1; + + m_iLastDecSeq = m_iSndCurrSeqNo; + + // remove global synchronization using randomization + srand(m_iLastDecSeq); + m_iDecRandom = (int)ceil(m_iAvgNAKNum * (double(rand()) / RAND_MAX)); + if (m_iDecRandom < 1) + m_iDecRandom = 1; + } + else if ((m_iDecCount ++ < 5) && (0 == (++ m_iNAKCount % m_iDecRandom))) + { + // 0.875^5 = 0.51, rate should not be decreased by more than half within a congestion period + m_dPktSndPeriod = ceil(m_dPktSndPeriod * 1.125); + m_iLastDecSeq = m_iSndCurrSeqNo; + } +} + +void CUDTCC::onTimeout() +{ + if (m_bSlowStart) + { + m_bSlowStart = false; + if (m_iRcvRate > 0) +#ifdef SRT_ENABLE_BSTATS + // Need average packet size here + m_dPktSndPeriod = 1000000.0 / ((m_iRcvRate + m_iMSS - 1) / m_iMSS); +#else + m_dPktSndPeriod = 1000000.0 / m_iRcvRate; +#endif + else + m_dPktSndPeriod = m_dCWndSize / (m_iRTT + m_iRCInterval); + } + else + { + /* + m_dLastDecPeriod = m_dPktSndPeriod; + m_dPktSndPeriod = ceil(m_dPktSndPeriod * 2); + m_iLastDecSeq = m_iLastAck; + */ + } +} diff --git a/srtcore/ccc.h b/srtcore/ccc.h new file mode 100644 index 000000000..5bf93032c --- /dev/null +++ b/srtcore/ccc.h @@ -0,0 +1,219 @@ +/***************************************************************************** +Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 02/28/2012 +*****************************************************************************/ + + +#ifndef __UDT_CCC_H__ +#define __UDT_CCC_H__ + + +#include "udt.h" +#include "packet.h" + + +class UDT_API CCC +{ +friend class CUDT; + +public: + CCC(); + virtual ~CCC(); + +private: + CCC(const CCC&); + CCC& operator=(const CCC&) {return *this;} + +public: + + /// Callback function to be called (only) at the start of a UDT connection. + /// note that this is different from CCC(), which is always called. + + virtual void init() {} + + /// Callback function to be called when a UDT connection is closed. + + virtual void close() {} + + /// Callback function to be called when an ACK packet is received. + /// @param [in] ackno the data sequence number acknowledged by this ACK. + + virtual void onACK(int32_t) {} + + /// Callback function to be called when a loss report is received. + /// @param [in] losslist list of sequence number of packets, in the format describled in packet.cpp. + /// @param [in] size length of the loss list. + + virtual void onLoss(const int32_t*, int) {} + + /// Callback function to be called when a timeout event occurs. + + virtual void onTimeout() {} + + /// Callback function to be called when a data is sent. + /// @param [in] seqno the data sequence number. + /// @param [in] size the payload size. + + virtual void onPktSent(const CPacket*) {} + + /// Callback function to be called when a data is received. + /// @param [in] seqno the data sequence number. + /// @param [in] size the payload size. + + virtual void onPktReceived(const CPacket*) {} + + /// Callback function to Process a user defined packet. + /// @param [in] pkt the user defined packet. + + virtual void processCustomMsg(const CPacket*) {} + +protected: + + /// Set periodical acknowldging and the ACK period. + /// @param [in] msINT the period to send an ACK. + + void setACKTimer(int msINT); + + /// Set packet-based acknowldging and the number of packets to send an ACK. + /// @param [in] pktINT the number of packets to send an ACK. + + void setACKInterval(int pktINT); + + /// Set RTO value. + /// @param [in] msRTO RTO in macroseconds. + + void setRTO(int usRTO); + + /// Send a user defined control packet. + /// @param [in] pkt user defined packet. + + void sendCustomMsg(CPacket& pkt) const; + + /// retrieve performance information. + /// @return Pointer to a performance info structure. + + const CPerfMon* getPerfInfo(); + + /// Set user defined parameters. + /// @param [in] param the paramters in one buffer. + /// @param [in] size the size of the buffer. + + void setUserParam(const char* param, int size); + +private: + void setMSS(int mss); + void setMaxCWndSize(int cwnd); + void setBandwidth(int bw); + void setSndCurrSeqNo(int32_t seqno); + void setRcvRate(int rcvrate); + void setRTT(int rtt); + +protected: + const int32_t& m_iSYNInterval; // UDT constant parameter, SYN + + double m_dPktSndPeriod; // Packet sending period, in microseconds + double m_dCWndSize; // Congestion window size, in packets + + int m_iBandwidth; // estimated bandwidth, packets per second + double m_dMaxCWndSize; // maximum cwnd size, in packets + + int m_iMSS; // Maximum Packet Size, including all packet headers + int32_t m_iSndCurrSeqNo; // current maximum seq no sent out + int m_iRcvRate; // packet arrive rate at receiver side, packets per second + int m_iRTT; // current estimated RTT, microsecond + + char* m_pcParam; // user defined parameter + int m_iPSize; // size of m_pcParam + +private: + UDTSOCKET m_UDT; // The UDT entity that this congestion control algorithm is bound to + + 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 + + CPerfMon m_PerfInfo; // protocol statistics information +}; + +class CCCVirtualFactory +{ +public: + virtual ~CCCVirtualFactory() {} + + virtual CCC* create() = 0; + virtual CCCVirtualFactory* clone() = 0; +}; + +template +class CCCFactory: public CCCVirtualFactory +{ +public: + virtual ~CCCFactory() {} + + virtual CCC* create() {return new T;} + virtual CCCVirtualFactory* clone() {return new CCCFactory;} +}; + +class CUDTCC: public CCC +{ +public: + CUDTCC(); + +public: + virtual void init(); + virtual void onACK(int32_t); + virtual void onLoss(const int32_t*, int); + virtual void onTimeout(); + +private: + int m_iRCInterval; // UDT Rate control interval + uint64_t m_LastRCTime; // last rate increase time + bool m_bSlowStart; // if in slow start phase + int32_t m_iLastAck; // last ACKed seq no + bool m_bLoss; // if loss happened since last rate increase + int32_t m_iLastDecSeq; // max pkt seq no sent out when last decrease happened + double m_dLastDecPeriod; // value of pktsndperiod when last decrease happened + int m_iNAKCount; // NAK counter + int m_iDecRandom; // random threshold on decrease by number of loss events + int m_iAvgNAKNum; // average number of NAKs per congestion + int m_iDecCount; // number of decreases in a congestion epoch +}; + +#endif diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp new file mode 100644 index 000000000..24ef3b225 --- /dev/null +++ b/srtcore/channel.cpp @@ -0,0 +1,467 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +****************************************************************************/ + +/**************************************************************************** +written by + Yunhong Gu, last updated 01/27/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef WIN32 + #if __APPLE__ + #include "TargetConditionals.h" + #endif + #include + #include + #include + #include + #include + #include + #include + #include +#else + #include + #include + #ifdef LEGACY_WIN32 + #include + #endif +#endif + +#include +#include // Logging +#include + +#include "channel.h" +#include "packet.h" +#include "logging.h" + +#ifdef WIN32 + typedef int socklen_t; +#endif + +#ifndef WIN32 + #define NET_ERROR errno +#else + #define NET_ERROR WSAGetLastError() +#endif + +using namespace std; + + +extern logging::Logger mglog; + +CChannel::CChannel(): +m_iIPversion(AF_INET), +m_iSockAddrSize(sizeof(sockaddr_in)), +m_iSocket(), +#ifdef SRT_ENABLE_IPOPTS +m_iIpTTL(-1), +m_iIpToS(-1), +#endif +m_iSndBufSize(65536), +m_iRcvBufSize(65536) +{ +} + +CChannel::CChannel(int version): +m_iIPversion(version), +m_iSocket(), +#ifdef SRT_ENABLE_IPOPTS +m_iIpTTL(-1), +m_iIpToS(-1), +#endif +m_iSndBufSize(65536), +m_iRcvBufSize(65536) +{ + m_iSockAddrSize = (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); +} + +CChannel::~CChannel() +{ +} + +void CChannel::open(const sockaddr* addr) +{ + // construct an socket + m_iSocket = ::socket(m_iIPversion, SOCK_DGRAM, 0); + + #ifdef WIN32 + if (INVALID_SOCKET == m_iSocket) + #else + if (m_iSocket < 0) + #endif + throw CUDTException(MJ_SETUP, MN_NONE, NET_ERROR); + + if (NULL != addr) + { + socklen_t namelen = m_iSockAddrSize; + + if (0 != ::bind(m_iSocket, addr, namelen)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + } + else + { + //sendto or WSASendTo will also automatically bind the socket + addrinfo hints; + addrinfo* res; + + memset(&hints, 0, sizeof(struct addrinfo)); + + hints.ai_flags = AI_PASSIVE; + hints.ai_family = m_iIPversion; + hints.ai_socktype = SOCK_DGRAM; + + if (0 != ::getaddrinfo(NULL, "0", &hints, &res)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + + if (0 != ::bind(m_iSocket, res->ai_addr, res->ai_addrlen)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + + ::freeaddrinfo(res); + } + + setUDPSockOpt(); +} + +void CChannel::open(UDPSOCKET udpsock) +{ + m_iSocket = udpsock; + setUDPSockOpt(); +} + +void CChannel::setUDPSockOpt() +{ + #if defined(BSD) || defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + // BSD system will fail setsockopt if the requested buffer size exceeds system maximum value + int maxsize = 64000; + if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_iRcvBufSize, sizeof(int))) + ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&maxsize, sizeof(int)); + if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_iSndBufSize, sizeof(int))) + ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&maxsize, sizeof(int)); + #else + // for other systems, if requested is greated than maximum, the maximum value will be automactally used + if ((0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char*)&m_iRcvBufSize, sizeof(int))) || + (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char*)&m_iSndBufSize, sizeof(int)))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + #endif + +#ifdef SRT_ENABLE_IPOPTS + if ((-1 != m_iIpTTL) + && (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (const char*)&m_iIpTTL, sizeof(m_iIpTTL)))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + + if ((-1 != m_iIpToS) + && (0 != ::setsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (const char*)&m_iIpToS, sizeof(m_iIpToS)))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); +#endif + + timeval tv; + tv.tv_sec = 0; + #if defined (BSD) || defined (OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + // Known BSD bug as the day I wrote this code. + // A small time out value will cause the socket to block forever. + tv.tv_usec = 10000; + #else + tv.tv_usec = 100; + #endif + + #ifdef UNIX + // Set non-blocking I/O + // UNIX does not support SO_RCVTIMEO + int opts = ::fcntl(m_iSocket, F_GETFL); + if (-1 == ::fcntl(m_iSocket, F_SETFL, opts | O_NONBLOCK)) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + #elif defined(WIN32) + DWORD ot = 1; //milliseconds + if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&ot, sizeof(DWORD))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + #else + // Set receiving time-out value + if (0 != ::setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(timeval))) + throw CUDTException(MJ_SETUP, MN_NORES, NET_ERROR); + #endif +} + +void CChannel::close() const +{ + #ifndef WIN32 + ::close(m_iSocket); + #else + ::closesocket(m_iSocket); + #endif +} + +int CChannel::getSndBufSize() +{ + socklen_t size = sizeof(socklen_t); + ::getsockopt(m_iSocket, SOL_SOCKET, SO_SNDBUF, (char *)&m_iSndBufSize, &size); + return m_iSndBufSize; +} + +int CChannel::getRcvBufSize() +{ + socklen_t size = sizeof(socklen_t); + ::getsockopt(m_iSocket, SOL_SOCKET, SO_RCVBUF, (char *)&m_iRcvBufSize, &size); + return m_iRcvBufSize; +} + +void CChannel::setSndBufSize(int size) +{ + m_iSndBufSize = size; +} + +void CChannel::setRcvBufSize(int size) +{ + m_iRcvBufSize = size; +} + +#ifdef SRT_ENABLE_IPOPTS +int CChannel::getIpTTL() const +{ + socklen_t size = sizeof(m_iIpTTL); + ::getsockopt(m_iSocket, IPPROTO_IP, IP_TTL, (char *)&m_iIpTTL, &size); + return m_iIpTTL; +} + +int CChannel::getIpToS() const +{ + socklen_t size = sizeof(m_iIpToS); + ::getsockopt(m_iSocket, IPPROTO_IP, IP_TOS, (char *)&m_iIpToS, &size); + return m_iIpToS; +} + +void CChannel::setIpTTL(int ttl) +{ + m_iIpTTL = ttl; +} + +void CChannel::setIpToS(int tos) +{ + m_iIpToS = tos; +} + +#endif + +void CChannel::getSockAddr(sockaddr* addr) const +{ + socklen_t namelen = m_iSockAddrSize; + ::getsockname(m_iSocket, addr, &namelen); +} + +void CChannel::getPeerAddr(sockaddr* addr) const +{ + socklen_t namelen = m_iSockAddrSize; + ::getpeername(m_iSocket, addr, &namelen); +} + + +int CChannel::sendto(const sockaddr* addr, CPacket& packet) const +{ + // convert control information into network order + 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)); + + // convert packet header into network order + //for (int j = 0; j < 4; ++ j) + // packet.m_nHeader[j] = htonl(packet.m_nHeader[j]); + uint32_t* p = packet.m_nHeader; + for (int j = 0; j < 4; ++ j) + { + *p = htonl(*p); + ++ p; + } + + #ifndef WIN32 + msghdr mh; + mh.msg_name = (sockaddr*)addr; + mh.msg_namelen = m_iSockAddrSize; + mh.msg_iov = (iovec*)packet.m_PacketVector; + mh.msg_iovlen = 2; + mh.msg_control = NULL; + mh.msg_controllen = 0; + mh.msg_flags = 0; + + int res = ::sendmsg(m_iSocket, &mh, 0); + #else + DWORD size = CPacket::HDR_SIZE + packet.getLength(); + int addrsize = m_iSockAddrSize; + int res = ::WSASendTo(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, 0, addr, addrsize, NULL, NULL); + res = (0 == res) ? size : -1; + #endif + + // convert back into local host order + //for (int k = 0; k < 4; ++ k) + // packet.m_nHeader[k] = ntohl(packet.m_nHeader[k]); + p = packet.m_nHeader; + for (int k = 0; k < 4; ++ k) + { + *p = ntohl(*p); + ++ p; + } + + if (packet.isControl()) + { + for (int l = 0, n = packet.getLength() / 4; l < n; ++ l) + *((uint32_t *)packet.m_pcData + l) = ntohl(*((uint32_t *)packet.m_pcData + l)); + } + + return res; +} + +int CChannel::recvfrom(sockaddr* addr, CPacket& packet) const +{ + #ifndef WIN32 + msghdr mh; + mh.msg_name = addr; + mh.msg_namelen = m_iSockAddrSize; + mh.msg_iov = packet.m_PacketVector; + mh.msg_iovlen = 2; + mh.msg_control = NULL; + mh.msg_controllen = 0; + mh.msg_flags = 0; + + #ifdef UNIX + fd_set set; + timeval tv; + FD_ZERO(&set); + FD_SET(m_iSocket, &set); + tv.tv_sec = 0; + tv.tv_usec = 10000; + ::select(m_iSocket+1, &set, NULL, &set, &tv); + #endif + + int res = ::recvmsg(m_iSocket, &mh, 0); + #else + DWORD size = CPacket::HDR_SIZE + packet.getLength(); + DWORD flag = 0; + int addrsize = m_iSockAddrSize; + + int res = ::WSARecvFrom(m_iSocket, (LPWSABUF)packet.m_PacketVector, 2, &size, &flag, addr, &addrsize, NULL, NULL); + res = (0 == res) ? size : -1; + #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. + if ( res == -1 ) + { +#if ENABLE_LOGGING + int err = NET_ERROR; + if ( err != EAGAIN ) // For EAGAIN, this isn't an error, just a useless call. + { + LOGC(mglog.Debug) << CONID() << "(sys)recvmsg: " << SysStrError(err) << " [" << err << "]"; + } +#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 ) + { + LOGC(mglog.Debug) << CONID() << "POSSIBLE ATTACK: received too short packet with " << res << " bytes"; + goto Return_error; + } + + // Fix for an issue found at Tenecent. + // By some not well known reason, Linux kernel happens to copy only 20 bytes of + // UDP payload and set the MSG_TRUNC flag, whereas pcap shows that full UDP + // packet arrived at the network device, and the free space in a buffer is + // always the same and >1332 bytes. Nice of it to set this flag, though. + // + // 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". + // + // 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 + // because the packet is partially lost and this loss is irrecoverable. + if ( mh.msg_flags != 0 ) + { + LOGC(mglog.Debug) << CONID() << "NET ERROR: packet size=" << res + << " msg_flags=0x" << hex << mh.msg_flags << ", possibly MSG_TRUNC (0x" << hex << int(MSG_TRUNC) << ")"; + goto Return_error; + } + + packet.setLength(res - CPacket::HDR_SIZE); + + // convert back into local host order + //for (int i = 0; i < 4; ++ i) + // packet.m_nHeader[i] = ntohl(packet.m_nHeader[i]); + { + uint32_t* p = packet.m_nHeader; + for (size_t i = 0; i < CPacket::PH_SIZE; ++ i) + { + *p = ntohl(*p); + ++ p; + } + } + + if (packet.isControl()) + { + for (size_t j = 0, n = packet.getLength() / sizeof (uint32_t); j < n; ++ j) + *((uint32_t *)packet.m_pcData + j) = ntohl(*((uint32_t *)packet.m_pcData + j)); + } + + return packet.getLength(); + +Return_error: + packet.setLength(-1); + return -1; +} diff --git a/srtcore/channel.h b/srtcore/channel.h new file mode 100644 index 000000000..d0ece37a0 --- /dev/null +++ b/srtcore/channel.h @@ -0,0 +1,184 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/27/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_CHANNEL_H__ +#define __UDT_CHANNEL_H__ + + +#include "udt.h" +#include "packet.h" + + +class CChannel +{ +public: + + // XXX There's currently no way to access the socket ID set for + // whatever the channel is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } + + CChannel(); + CChannel(int version); + ~CChannel(); + + /// Open a UDP channel. + /// @param addr [in] The local address that UDP will use. + + void open(const sockaddr* addr = NULL); + + /// Open a UDP channel based on an existing UDP socket. + /// @param udpsock [in] UDP socket descriptor. + + void open(UDPSOCKET udpsock); + + /// Disconnect and close the UDP entity. + + void close() const; + + /// Get the UDP sending buffer size. + /// @return Current UDP sending buffer size. + + int getSndBufSize(); + + /// Get the UDP receiving buffer size. + /// @return Current UDP receiving buffer size. + + int getRcvBufSize(); + + /// Set the UDP sending buffer size. + /// @param size [in] expected UDP sending buffer size. + + void setSndBufSize(int size); + + /// Set the UDP receiving buffer size. + /// @param size [in] expected UDP receiving buffer size. + + void setRcvBufSize(int size); + + /// Query the socket address that the channel is using. + /// @param addr [out] pointer to store the returned socket address. + + void getSockAddr(sockaddr* addr) const; + + /// Query the peer side socket address that the channel is connect to. + /// @param addr [out] pointer to store the returned socket address. + + void getPeerAddr(sockaddr* addr) const; + + /// Send a packet to the given address. + /// @param addr [in] pointer to the destination address. + /// @param packet [in] reference to a CPacket entity. + /// @return Actual size of data sent. + + int sendto(const sockaddr* addr, CPacket& packet) const; + + /// Receive a packet from the channel and record the source address. + /// @param addr [in] pointer to the source address. + /// @param packet [in] reference to a CPacket entity. + /// @return Actual size of data received. + + int recvfrom(sockaddr* addr, CPacket& packet) const; + +#ifdef SRT_ENABLE_IPOPTS + /// Set the IP TTL. + /// @param ttl [in] IP Time To Live. + /// @return none. + + void setIpTTL(int ttl); + + /// Set the IP Type of Service. + /// @param tos [in] IP Type of Service. + + void setIpToS(int tos); + + /// Get the IP TTL. + /// @param ttl [in] IP Time To Live. + /// @return TTL. + + int getIpTTL() const; + + /// Get the IP Type of Service. + /// @return ToS. + + int getIpToS() const; +#endif + +private: + void setUDPSockOpt(); + +private: + int m_iIPversion; // IP version + int m_iSockAddrSize; // socket address structure size (pre-defined to avoid run-time test) + + UDPSOCKET m_iSocket; // socket descriptor +#ifdef SRT_ENABLE_IPOPTS + int m_iIpTTL; + int m_iIpToS; +#endif + int m_iSndBufSize; // UDP sending buffer size + int m_iRcvBufSize; // UDP receiving buffer size +}; + + +#endif diff --git a/srtcore/common.cpp b/srtcore/common.cpp new file mode 100644 index 000000000..5ffabfdde --- /dev/null +++ b/srtcore/common.cpp @@ -0,0 +1,834 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2016, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 07/25/2010 +modified by + Haivision Systems Inc. +*****************************************************************************/ + + +#ifndef WIN32 + #include + #include + #include + #if __APPLE__ + #include "TargetConditionals.h" + #endif + #if defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + #include + #endif +#else + #include + #include + #ifdef LEGACY_WIN32 + #include + #endif + #include +#endif + +#include +#include +#include +#include "md5.h" +#include "common.h" + +#include // SysStrError + +bool CTimer::m_bUseMicroSecond = false; +uint64_t CTimer::s_ullCPUFrequency = CTimer::readCPUFrequency(); + +pthread_mutex_t CTimer::m_EventLock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t CTimer::m_EventCond = PTHREAD_COND_INITIALIZER; + +CTimer::CTimer(): +m_ullSchedTime(), +m_TickCond(), +m_TickLock() +{ + pthread_mutex_init(&m_TickLock, NULL); + pthread_cond_init(&m_TickCond, NULL); +} + +CTimer::~CTimer() +{ + pthread_mutex_destroy(&m_TickLock); + pthread_cond_destroy(&m_TickCond); +} + +void CTimer::rdtsc(uint64_t &x) +{ + if (m_bUseMicroSecond) + { + x = getTime(); + return; + } + + #ifdef IA32 + uint32_t lval, hval; + //asm volatile ("push %eax; push %ebx; push %ecx; push %edx"); + //asm volatile ("xor %eax, %eax; cpuid"); + asm volatile ("rdtsc" : "=a" (lval), "=d" (hval)); + //asm volatile ("pop %edx; pop %ecx; pop %ebx; pop %eax"); + x = hval; + x = (x << 32) | lval; + #elif defined(IA64) + asm ("mov %0=ar.itc" : "=r"(x) :: "memory"); + #elif defined(AMD64) + uint32_t lval, hval; + asm ("rdtsc" : "=a" (lval), "=d" (hval)); + x = hval; + x = (x << 32) | lval; + #elif defined(WIN32) + //HANDLE hCurThread = ::GetCurrentThread(); + //DWORD_PTR dwOldMask = ::SetThreadAffinityMask(hCurThread, 1); + BOOL ret = QueryPerformanceCounter((LARGE_INTEGER *)&x); + //SetThreadAffinityMask(hCurThread, dwOldMask); + if (!ret) + x = getTime() * s_ullCPUFrequency; + #elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + x = mach_absolute_time(); + #else + // use system call to read time clock for other archs + x = getTime(); + #endif +} + +uint64_t CTimer::readCPUFrequency() +{ + uint64_t frequency = 1; // 1 tick per microsecond. + + #if defined(IA32) || defined(IA64) || defined(AMD64) + uint64_t t1, t2; + + rdtsc(t1); + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 100000000; + nanosleep(&ts, NULL); + rdtsc(t2); + + // CPU clocks per microsecond + frequency = (t2 - t1) / 100000; + #elif defined(WIN32) + int64_t ccf; + if (QueryPerformanceFrequency((LARGE_INTEGER *)&ccf)) + frequency = ccf / 1000000; + #elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + mach_timebase_info_data_t info; + mach_timebase_info(&info); + frequency = info.denom * 1000ULL / info.numer; + #endif + + // Fall back to microsecond if the resolution is not high enough. + if (frequency < 10) + { + frequency = 1; + m_bUseMicroSecond = true; + } + return frequency; +} + +uint64_t CTimer::getCPUFrequency() +{ + return s_ullCPUFrequency; +} + +void CTimer::sleep(uint64_t interval) +{ + uint64_t t; + rdtsc(t); + + // sleep next "interval" time + sleepto(t + interval); +} + +void CTimer::sleepto(uint64_t nexttime) +{ + // Use class member such that the method can be interrupted by others + m_ullSchedTime = nexttime; + + uint64_t t; + rdtsc(t); + + while (t < m_ullSchedTime) + { +#ifndef NO_BUSY_WAITING +#ifdef IA32 + __asm__ volatile ("pause; rep; nop; nop; nop; nop; nop;"); +#elif IA64 + __asm__ volatile ("nop 0; nop 0; nop 0; nop 0; nop 0;"); +#elif AMD64 + __asm__ volatile ("nop; nop; nop; nop; nop;"); +#endif +#else + timeval now; + timespec timeout; + gettimeofday(&now, 0); + if (now.tv_usec < 990000) + { + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = (now.tv_usec + 10000) * 1000; + } + else + { + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = (now.tv_usec + 10000 - 1000000) * 1000; + } + THREAD_PAUSED(); + pthread_mutex_lock(&m_TickLock); + pthread_cond_timedwait(&m_TickCond, &m_TickLock, &timeout); + pthread_mutex_unlock(&m_TickLock); + THREAD_RESUMED(); +#endif + + rdtsc(t); + } +} + +void CTimer::interrupt() +{ + // schedule the sleepto time to the current CCs, so that it will stop + rdtsc(m_ullSchedTime); + tick(); +} + +void CTimer::tick() +{ + pthread_cond_signal(&m_TickCond); +} + +uint64_t CTimer::getTime() +{ + //For Cygwin and other systems without microsecond level resolution, uncomment the following three lines + //uint64_t x; + //rdtsc(x); + //return x / s_ullCPUFrequency; + //Specific fix may be necessary if rdtsc is not available either. + + timeval t; + gettimeofday(&t, 0); + return t.tv_sec * 1000000ULL + t.tv_usec; +} + +void CTimer::triggerEvent() +{ + pthread_cond_signal(&m_EventCond); +} + +void CTimer::waitForEvent() +{ + timeval now; + timespec timeout; + gettimeofday(&now, 0); + if (now.tv_usec < 990000) + { + timeout.tv_sec = now.tv_sec; + timeout.tv_nsec = (now.tv_usec + 10000) * 1000; + } + else + { + timeout.tv_sec = now.tv_sec + 1; + timeout.tv_nsec = (now.tv_usec + 10000 - 1000000) * 1000; + } + pthread_mutex_lock(&m_EventLock); + pthread_cond_timedwait(&m_EventCond, &m_EventLock, &timeout); + pthread_mutex_unlock(&m_EventLock); +} + +void CTimer::sleep() +{ + #ifndef WIN32 + usleep(10); + #else + Sleep(1); + #endif +} + + +// +// Automatically lock in constructor +CGuard::CGuard(pthread_mutex_t& lock): +m_Mutex(lock), +m_iLocked() +{ + m_iLocked = pthread_mutex_lock(&m_Mutex); +} + +// Automatically unlock in destructor +CGuard::~CGuard() +{ + if (0 == m_iLocked) + pthread_mutex_unlock(&m_Mutex); +} + +int CGuard::enterCS(pthread_mutex_t& lock) +{ + return pthread_mutex_lock(&lock); +} + +int CGuard::leaveCS(pthread_mutex_t& lock) +{ + return pthread_mutex_unlock(&lock); +} + +void CGuard::createMutex(pthread_mutex_t& lock) +{ + pthread_mutex_init(&lock, NULL); +} + +void CGuard::releaseMutex(pthread_mutex_t& lock) +{ + pthread_mutex_destroy(&lock); +} + +void CGuard::createCond(pthread_cond_t& cond) +{ + pthread_cond_init(&cond, NULL); +} + +void CGuard::releaseCond(pthread_cond_t& cond) +{ + pthread_cond_destroy(&cond); +} + +// +CUDTException::CUDTException(CodeMajor major, CodeMinor minor, int err): +m_iMajor(major), +m_iMinor(minor) +{ + if (-1 == err) + #ifndef WIN32 + m_iErrno = errno; + #else + m_iErrno = GetLastError(); + #endif + else + m_iErrno = err; +} + +CUDTException::CUDTException(const CUDTException& e): +m_iMajor(e.m_iMajor), +m_iMinor(e.m_iMinor), +m_iErrno(e.m_iErrno), +m_strMsg() +{ +} + +CUDTException::~CUDTException() +{ +} + +const char* CUDTException::getErrorMessage() +{ + // translate "Major:Minor" code into text message. + + switch (m_iMajor) + { + case MJ_SUCCESS: + m_strMsg = "Success"; + break; + + case MJ_SETUP: + m_strMsg = "Connection setup failure"; + + switch (m_iMinor) + { + case MN_TIMEOUT: + m_strMsg += ": connection time out"; + break; + + case MN_REJECTED: + m_strMsg += ": connection rejected"; + 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: + m_strMsg += ": abort for security reasons"; + break; + + default: + break; + } + + break; + + case MJ_CONNECTION: + switch (m_iMinor) + { + case MN_CONNLOST: + m_strMsg = "Connection was broken"; + break; + + case MN_NOCONN: + m_strMsg = "Connection does not exist"; + break; + + default: + break; + } + + break; + + case MJ_SYSTEMRES: + m_strMsg = "System resource failure"; + + switch (m_iMinor) + { + case MN_THREAD: + m_strMsg += ": unable to create new threads"; + break; + + case MN_MEMORY: + m_strMsg += ": unable to allocate buffers"; + break; + + default: + break; + } + + break; + + case MJ_FILESYSTEM: + m_strMsg = "File system failure"; + + switch (m_iMinor) + { + case MN_SEEKGFAIL: + m_strMsg += ": cannot seek read position"; + break; + + case MN_READFAIL: + m_strMsg += ": failure in read"; + break; + + case MN_SEEKPFAIL: + m_strMsg += ": cannot seek write position"; + break; + + case MN_WRITEFAIL: + m_strMsg += ": failure in write"; + break; + + default: + break; + } + + break; + + case MJ_NOTSUP: + m_strMsg = "Operation not supported"; + + switch (m_iMinor) + { + case MN_ISBOUND: + m_strMsg += ": Cannot do this operation on a BOUND socket"; + break; + + case MN_ISCONNECTED: + m_strMsg += ": Cannot do this operation on a CONNECTED socket"; + break; + + case MN_INVAL: + m_strMsg += ": Bad parameters"; + break; + + case MN_SIDINVAL: + m_strMsg += ": Invalid socket ID"; + break; + + case MN_ISUNBOUND: + m_strMsg += ": Cannot do this operation on an UNBOUND socket"; + break; + + case MN_NOLISTEN: + m_strMsg += ": Socket is not in listening state"; + break; + + case MN_ISRENDEZVOUS: + m_strMsg += ": Listen/accept is not supported in rendezous connection setup"; + break; + + case MN_ISRENDUNBOUND: + m_strMsg += ": Cannot call connect on UNBOUND socket in rendezvous connection setup"; + break; + + case MN_ISSTREAM: + m_strMsg += ": This operation is not supported in SOCK_STREAM mode"; + break; + + case MN_ISDGRAM: + m_strMsg += ": This operation is not supported in SOCK_DGRAM mode"; + break; + + case MN_BUSY: + m_strMsg += ": Another socket is already listening on the same port"; + 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: + m_strMsg += ": Invalid epoll ID"; + break; + + default: + break; + } + + break; + + case MJ_AGAIN: + m_strMsg = "Non-blocking call failure"; + + switch (m_iMinor) + { + case MN_WRAVAIL: + m_strMsg += ": no buffer available for sending"; + break; + + case MN_RDAVAIL: + m_strMsg += ": no data available for reading"; + break; + + case MN_XMTIMEOUT: + m_strMsg += ": transmission timed out"; + break; + +#ifdef SRT_ENABLE_ECN + case MN_CONGESTION: + m_strMsg += ": early congestion notification"; + break; +#endif /* SRT_ENABLE_ECN */ + default: + break; + } + + break; + + case MJ_PEERERROR: + m_strMsg = "The peer side has signalled an error"; + + break; + + default: + m_strMsg = "Unknown error"; + } + + // Adding "errno" information + if ((MJ_SUCCESS != m_iMajor) && (0 < m_iErrno)) + { + m_strMsg += ": " + SysStrError(m_iErrno); + } + + // period + #ifndef WIN32 + m_strMsg += "."; + #endif + + return m_strMsg.c_str(); +} + +#define UDT_XCODE(mj, mn) (int(mj)*1000)+int(mn) + +int CUDTException::getErrorCode() const +{ + return UDT_XCODE(m_iMajor, m_iMinor); +} + +int CUDTException::getErrno() const +{ + return m_iErrno; +} + + +void CUDTException::clear() +{ + m_iMajor = MJ_SUCCESS; + m_iMinor = MN_NONE; + m_iErrno = 0; +} + +// XXX Move these into udt.h +const int CUDTException::SUCCESS = 0; +const int CUDTException::ECONNSETUP = UDT_XCODE(MJ_SETUP, 0); +const int CUDTException::ENOSERVER = UDT_XCODE(MJ_SETUP, MN_TIMEOUT); +const int CUDTException::ECONNREJ = UDT_XCODE(MJ_SETUP, MN_REJECTED); +const int CUDTException::ESOCKFAIL = UDT_XCODE(MJ_SETUP, MN_NORES); +const int CUDTException::ESECFAIL = UDT_XCODE(MJ_SETUP, MN_SECURITY); +const int CUDTException::ECONNFAIL = 2000; +const int CUDTException::ECONNLOST = 2001; +const int CUDTException::ENOCONN = 2002; +const int CUDTException::ERESOURCE = 3000; +const int CUDTException::ETHREAD = 3001; +const int CUDTException::ENOBUF = 3002; +const int CUDTException::EFILE = 4000; +const int CUDTException::EINVRDOFF = 4001; +const int CUDTException::ERDPERM = 4002; +const int CUDTException::EINVWROFF = 4003; +const int CUDTException::EWRPERM = 4004; +const int CUDTException::EINVOP = 5000; +const int CUDTException::EBOUNDSOCK = 5001; +const int CUDTException::ECONNSOCK = 5002; +const int CUDTException::EINVPARAM = 5003; +const int CUDTException::EINVSOCK = 5004; +const int CUDTException::EUNBOUNDSOCK = 5005; +const int CUDTException::ENOLISTEN = 5006; +const int CUDTException::ERDVNOSERV = 5007; +const int CUDTException::ERDVUNBOUND = 5008; +const int CUDTException::ESTREAMILL = 5009; +const int CUDTException::EDGRAMILL = 5010; +const int CUDTException::EDUPLISTEN = 5011; +const int CUDTException::ELARGEMSG = 5012; +const int CUDTException::EINVPOLLID = 5013; +const int CUDTException::EASYNCFAIL = 6000; +const int CUDTException::EASYNCSND = 6001; +const int CUDTException::EASYNCRCV = 6002; +const int CUDTException::ETIMEOUT = 6003; +#ifdef SRT_ENABLE_ECN +const int CUDTException::ECONGEST = 6004; +#endif /* SRT_ENABLE_ECN */ +const int CUDTException::EPEERERR = 7000; +const int CUDTException::EUNKNOWN = -1; + +#undef UDT_XCODE + +// +bool CIPAddress::ipcmp(const sockaddr* addr1, const sockaddr* addr2, int ver) +{ + if (AF_INET == ver) + { + sockaddr_in* a1 = (sockaddr_in*)addr1; + sockaddr_in* a2 = (sockaddr_in*)addr2; + + if ((a1->sin_port == a2->sin_port) && (a1->sin_addr.s_addr == a2->sin_addr.s_addr)) + return true; + } + else + { + sockaddr_in6* a1 = (sockaddr_in6*)addr1; + sockaddr_in6* a2 = (sockaddr_in6*)addr2; + + if (a1->sin6_port == a2->sin6_port) + { + for (int i = 0; i < 16; ++ i) + if (*((char*)&(a1->sin6_addr) + i) != *((char*)&(a2->sin6_addr) + i)) + return false; + + return true; + } + } + + return false; +} + +void CIPAddress::ntop(const sockaddr* addr, uint32_t ip[4], int ver) +{ + if (AF_INET == ver) + { + sockaddr_in* a = (sockaddr_in*)addr; + ip[0] = a->sin_addr.s_addr; + } + else + { + sockaddr_in6* a = (sockaddr_in6*)addr; + ip[3] = (a->sin6_addr.s6_addr[15] << 24) + (a->sin6_addr.s6_addr[14] << 16) + (a->sin6_addr.s6_addr[13] << 8) + a->sin6_addr.s6_addr[12]; + ip[2] = (a->sin6_addr.s6_addr[11] << 24) + (a->sin6_addr.s6_addr[10] << 16) + (a->sin6_addr.s6_addr[9] << 8) + a->sin6_addr.s6_addr[8]; + ip[1] = (a->sin6_addr.s6_addr[7] << 24) + (a->sin6_addr.s6_addr[6] << 16) + (a->sin6_addr.s6_addr[5] << 8) + a->sin6_addr.s6_addr[4]; + ip[0] = (a->sin6_addr.s6_addr[3] << 24) + (a->sin6_addr.s6_addr[2] << 16) + (a->sin6_addr.s6_addr[1] << 8) + a->sin6_addr.s6_addr[0]; + } +} + +void CIPAddress::pton(sockaddr* addr, const uint32_t ip[4], int ver) +{ + if (AF_INET == ver) + { + sockaddr_in* a = (sockaddr_in*)addr; + a->sin_addr.s_addr = ip[0]; + } + else + { + sockaddr_in6* a = (sockaddr_in6*)addr; + for (int i = 0; i < 4; ++ i) + { + a->sin6_addr.s6_addr[i * 4] = ip[i] & 0xFF; + a->sin6_addr.s6_addr[i * 4 + 1] = (unsigned char)((ip[i] & 0xFF00) >> 8); + a->sin6_addr.s6_addr[i * 4 + 2] = (unsigned char)((ip[i] & 0xFF0000) >> 16); + a->sin6_addr.s6_addr[i * 4 + 3] = (unsigned char)((ip[i] & 0xFF000000) >> 24); + } + } +} + +using namespace std; + + +static string ShowIP4(const sockaddr_in* sin) +{ + ostringstream os; + union + { + in_addr sinaddr; + unsigned char ip[4]; + }; + sinaddr = sin->sin_addr; + + os << int(ip[0]); + os << "."; + os << int(ip[1]); + os << "."; + os << int(ip[2]); + os << "."; + os << int(ip[3]); + return os.str(); +} + +static string ShowIP6(const sockaddr_in6* sin) +{ + ostringstream os; + os.setf(ios::uppercase); + + bool sep = false; + for (size_t i = 0; i < 16; ++i) + { + int v = sin->sin6_addr.s6_addr[i]; + if ( v ) + { + if ( sep ) + os << ":"; + + os << hex << v; + sep = true; + } + } + + return os.str(); +} + +string CIPAddress::show(const sockaddr* adr) +{ + if ( adr->sa_family == AF_INET ) + return ShowIP4((const sockaddr_in*)adr); + else if ( adr->sa_family == AF_INET6 ) + return ShowIP6((const sockaddr_in6*)adr); + else + return "(unsupported sockaddr type)"; +} + +// +void CMD5::compute(const char* input, unsigned char result[16]) +{ + md5_state_t state; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)input, strlen(input)); + md5_finish(&state, result); +} + +std::string MessageTypeStr(UDTMessageType mt, uint32_t extt) +{ + using std::string; + + static string udt_types [] = { + "handshake", + "keepalive", + "ack", + "lossreport", + "cgwarning", //4 + "shutdown", + "ackack", + "dropreq", + "peererror", //8 + }; + + static string srt_types [] = { + "", + "hsreq", + "hsrsp", + "kmreq", + "kmrsp", + }; + +#define LEN(arr) (sizeof (arr)/(sizeof ((arr)[0]))) + + if ( mt == UMSG_EXT ) + { + // Returrn "EXT:" with srt message name + string val = "SRT:"; + if ( extt <= 0 || extt > LEN(srt_types) ) + return "EXT:unknown"; + + return val + srt_types[extt]; + } + + if ( size_t(mt) > LEN(udt_types) ) + return "unknown"; + +#undef LEN + + return udt_types[mt]; +} diff --git a/srtcore/common.h b/srtcore/common.h new file mode 100644 index 000000000..9b8eda8ec --- /dev/null +++ b/srtcore/common.h @@ -0,0 +1,111 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2009, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 08/01/2009 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_COMMON_H__ +#define __UDT_COMMON_H__ + + +#include +#ifndef WIN32 + #include + #include +#else + // #include + //#include +#endif +#include +#include "udt.h" +#include "utilities.h" + +enum UDTSockType +{ + UDT_UNDEFINED = 0, // initial trap representation + UDT_STREAM = 1, + UDT_DGRAM +}; + + +/// The message types used by UDT protocol. This is a part of UDT +/// protocol and should never be changed. +enum UDTMessageType +{ + UMSG_HANDSHAKE = 0, //< Connection Handshake. Control: see @a CHandShake. + UMSG_KEEPALIVE = 1, //< Keep-alive. + UMSG_ACK = 2, //< Acknowledgement. Control: past-the-end sequence number up to which packets have been received. + UMSG_LOSSREPORT = 3, //< Negative Acknowledgement (NACK). Control: Loss list. + UMSG_CGWARNING = 4, //< Congestion warning. + UMSG_SHUTDOWN = 5, //< Shutdown. + UMSG_ACKACK = 6, //< Acknowledgement of Acknowledgement. Add info: The ACK sequence number + UMSG_DROPREQ = 7, //< Message Drop Request. Add info: Message ID. Control Info: (first, last) number of the message. + UMSG_PEERERROR = 8, //< Signal from the Peer side. Add info: Error code. + // ... add extra code types here + UMSG_END_OF_TYPES, + UMSG_EXT = 0x7FFF //< For the use of user-defined control packets. +}; + +// For debug +std::string MessageTypeStr(UDTMessageType mt, uint32_t extt = 0); + +//////////////////////////////////////////////////////////////////////////////// + + +#endif diff --git a/srtcore/core.cpp b/srtcore/core.cpp new file mode 100644 index 000000000..55bb34ed9 --- /dev/null +++ b/srtcore/core.cpp @@ -0,0 +1,5368 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 02/28/2012 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef WIN32 + #include + #include + #include + #include + #include + #include +#else + #include + #include + #ifdef LEGACY_WIN32 + #include + #endif +#endif +#include +#include +#include "queue.h" +#include "core.h" +#include "logging.h" + +#ifdef SRT_ENABLE_SRTCC_EMB +#include "csrtcc.h" +//#define CSRTCC CSRTCC +#endif /* SRT_ENABLE_SRTCC_EMB */ + +using namespace std; + +struct AllFaOn +{ + set allfa; + + AllFaOn() + { + allfa.insert(SRT_LOGFA_BSTATS); + allfa.insert(SRT_LOGFA_CONTROL); + allfa.insert(SRT_LOGFA_DATA); + allfa.insert(SRT_LOGFA_TSBPD); + allfa.insert(SRT_LOGFA_REXMIT); + } +} logger_fa_all; + +logging::LogConfig 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"); + +CUDTUnited CUDT::s_UDTUnited; + +const UDTSOCKET CUDT::INVALID_SOCK = -1; +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; + +//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) +#endif +#endif /* SRT_ENABLE_TSBPD */ + +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; +#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; +#endif + + m_pSndQueue = NULL; + m_pRcvQueue = NULL; + m_pPeerAddr = NULL; + m_pSNode = NULL; + m_pRNode = NULL; + + // Initilize mutex and condition variables + initSynch(); +} + +CUDT::CUDT() +{ + construct(); + + (void)SRT_DEF_VERSION; + + // Default UDT configurations + m_iMSS = 1500; + m_bSynSending = true; + m_bSynRecving = true; + m_iFlightFlagSize = 25600; + m_iSndBufSize = 8192; + m_iRcvBufSize = 8192; //Rcv buffer MUST NOT be bigger than Flight Flag size + m_Linger.l_onoff = 1; + m_Linger.l_linger = 180; + m_iUDPSndBufSize = 65536; + m_iUDPRcvBufSize = m_iRcvBufSize * m_iMSS; + m_iSockType = UDT_STREAM; + m_iIPversion = AF_INET; + m_bRendezvous = false; +#ifdef SRT_ENABLE_CONNTIMEO + m_iConnTimeOut = 3000; +#endif + m_iSndTimeOut = -1; + m_iRcvTimeOut = -1; + m_bReuseAddr = true; + m_llMaxBW = -1; +#ifdef SRT_ENABLE_IPOPTS + m_iIpTTL = -1; + m_iIpToS = -1; +#endif + m_CryptoSecret.len = 0; + m_iSndCryptoKeyLen = 0; + //Cfg + m_bDataSender = false; //Sender only if true: does not recv data + m_bTwoWayData = false; +#ifdef SRT_ENABLE_TSBPD + m_bTsbPdMode = false; //Enable TsbPd on sender + m_iTsbPdDelay = 20; //Receiver TsbPd delay (mSec) +#ifdef SRT_ENABLE_TLPKTDROP + m_bTLPktDrop = true; //Too-late Packet Drop +#endif /* SRT_ENABLE_TLPKTDROP */ + //Runtime + m_bTsbPdSnd = false; + m_SndTsbPdDelay = 0; + m_bTsbPdRcv = false; + m_RcvTsbPdDelay = 0; +#ifdef SRT_ENABLE_TLPKTDROP + m_bTLPktDropSnd = 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 + m_bOpened = false; + m_bListening = false; + m_bConnecting = false; + m_bConnected = false; + m_bClosing = false; + m_bShutdown = false; + m_bBroken = false; + m_bPeerHealth = true; + m_ullLingerExpiration = 0; +} + +CUDT::CUDT(const CUDT& ancestor) +{ + construct(); + + // Default UDT configurations + m_iMSS = ancestor.m_iMSS; + m_bSynSending = ancestor.m_bSynSending; + m_bSynRecving = ancestor.m_bSynRecving; + m_iFlightFlagSize = ancestor.m_iFlightFlagSize; + m_iSndBufSize = ancestor.m_iSndBufSize; + m_iRcvBufSize = ancestor.m_iRcvBufSize; + m_Linger = ancestor.m_Linger; + m_iUDPSndBufSize = ancestor.m_iUDPSndBufSize; + m_iUDPRcvBufSize = ancestor.m_iUDPRcvBufSize; + m_iSockType = ancestor.m_iSockType; + m_iIPversion = ancestor.m_iIPversion; + m_bRendezvous = ancestor.m_bRendezvous; +#ifdef SRT_ENABLE_CONNTIMEO + m_iConnTimeOut = ancestor.m_iConnTimeOut; +#endif + m_iSndTimeOut = ancestor.m_iSndTimeOut; + m_iRcvTimeOut = ancestor.m_iRcvTimeOut; + m_bReuseAddr = true; // this must be true, because all accepted sockets shared the same port with the listener + m_llMaxBW = ancestor.m_llMaxBW; +#ifdef SRT_ENABLE_IPOPTS + m_iIpTTL = ancestor.m_iIpTTL; + m_iIpToS = ancestor.m_iIpToS; +#endif +#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; +#ifdef SRT_ENABLE_TLPKTDROP + m_bTLPktDrop = ancestor.m_bTLPktDrop; +#endif /* SRT_ENABLE_TLPKTDROP */ + //Runtime + m_bTsbPdSnd = false; + m_SndTsbPdDelay = 0; + m_bTsbPdRcv = false; + m_RcvTsbPdDelay = 0; +#ifdef SRT_ENABLE_TLPKTDROP + m_bTLPktDropSnd = false; +#endif /* SRT_ENABLE_TLPKTDROP */ +#endif /* SRT_ENABLE_TSBPD */ +#ifdef SRT_ENABLE_NAKREPORT + m_bRcvNakReport = ancestor.m_bRcvNakReport; + m_iMinNakInterval = ancestor.m_iMinNakInterval; + m_iNakReportAccel = ancestor.m_iNakReportAccel; +#endif /* SRT_ENABLE_NAKREPORT */ + + 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 + m_bOpened = false; + m_bListening = false; + m_bConnecting = false; + m_bConnected = false; + m_bClosing = false; + m_bShutdown = false; + m_bBroken = false; + m_bPeerHealth = true; + m_ullLingerExpiration = 0; +} + +CUDT::~CUDT() +{ + // release mutex/condtion variables + destroySynch(); + + //Wipeout critical data + memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret)); + + // destroy the data structures + delete m_pSndBuffer; + delete m_pRcvBuffer; + delete m_pSndLossList; + delete m_pRcvLossList; + delete m_pCCFactory; + delete m_pCC; + delete m_pPeerAddr; + delete m_pSNode; + delete m_pRNode; +} + +// This function is to make it possible for both C and C++ +// API to accept both bool and int types for boolean options. +// (it's not that C couldn't use , it's that people +// often forget to use correct type). +static bool bool_int_value(const void* optval, int optlen) +{ + if ( optlen == sizeof(bool) ) + { + return *(bool*)optval; + } + + if ( optlen == sizeof(int) ) + { + return *(int*)optval; + } + return false; +} + +void CUDT::setOpt(UDT_SOCKOPT optName, const void* optval, int optlen) +{ + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + CGuard cg(m_ConnectionLock); + CGuard sendguard(m_SendLock); + CGuard recvguard(m_RecvLock); + + switch (optName) + { + case UDT_MSS: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + + if (*(int*)optval < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + m_iMSS = *(int*)optval; + + // Packet size cannot be greater than UDP buffer size + if (m_iMSS > m_iUDPSndBufSize) + m_iMSS = m_iUDPSndBufSize; + if (m_iMSS > m_iUDPRcvBufSize) + m_iMSS = m_iUDPRcvBufSize; + + break; + + case UDT_SNDSYN: + m_bSynSending = bool_int_value(optval, optlen); + break; + + case UDT_RCVSYN: + m_bSynRecving = bool_int_value(optval, optlen); + break; + + 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(); + + break; + + case UDT_FC: + if (m_bConnecting || m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + + if (*(int*)optval < 1) + throw CUDTException(MJ_NOTSUP, MN_INVAL); + + // Mimimum recv flight flag size is 32 packets + if (*(int*)optval > 32) + m_iFlightFlagSize = *(int*)optval; + else + m_iFlightFlagSize = 32; + + break; + + case UDT_SNDBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + + if (*(int*)optval <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + m_iSndBufSize = *(int*)optval / (m_iMSS - CPacket::UDP_HDR_SIZE); + + break; + + case UDT_RCVBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + + if (*(int*)optval <= 0) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + { + // This weird cast through int is required because + // API requires 'int', and internals require 'size_t'; + // their size is different on 64-bit systems. + size_t val = size_t(*(int*)optval); + + // Mimimum recv buffer size is 32 packets + size_t mssin_size = m_iMSS - CPacket::UDP_HDR_SIZE; + + // XXX This magic 32 deserves some constant + if (val > mssin_size * 32) + m_iRcvBufSize = val / mssin_size; + else + m_iRcvBufSize = 32; + + // recv buffer MUST not be greater than FC size + if (m_iRcvBufSize > m_iFlightFlagSize) + m_iRcvBufSize = m_iFlightFlagSize; + } + + break; + + case UDT_LINGER: + m_Linger = *(linger*)optval; + break; + + case UDP_SNDBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + + m_iUDPSndBufSize = *(int*)optval; + + if (m_iUDPSndBufSize < m_iMSS) + m_iUDPSndBufSize = m_iMSS; + + break; + + case UDP_RCVBUF: + if (m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0); + + m_iUDPRcvBufSize = *(int*)optval; + + if (m_iUDPRcvBufSize < m_iMSS) + m_iUDPRcvBufSize = m_iMSS; + + break; + + case 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_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_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; + +#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; +#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; + +#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; +#endif /* SRT_ENABLE_TLPKTDROP */ +#endif /* SRT_ENABLE_TSBPD */ + + 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); + + 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); + + 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; + +#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; +#endif /* SRT_ENABLE_NAKREPORT */ + +#ifdef SRT_ENABLE_CONNTIMEO + case SRT_CONNTIMEO: + m_iConnTimeOut = *(int*)optval; + break; +#endif + +#if SRT_BELATED_LOSSREPORT + case SRT_LOSSMAXTTL: + m_iMaxReorderTolerance = *(int*)optval; + break; +#endif + + default: + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + } +} + +void CUDT::getOpt(UDT_SOCKOPT optName, void* optval, int& optlen) +{ + CGuard cg(m_ConnectionLock); + + switch (optName) + { + case UDT_MSS: + *(int*)optval = m_iMSS; + optlen = sizeof(int); + break; + + case UDT_SNDSYN: + *(bool*)optval = m_bSynSending; + optlen = sizeof(bool); + break; + + case UDT_RCVSYN: + *(bool*)optval = m_bSynRecving; + 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); + break; + + case UDT_SNDBUF: + *(int*)optval = m_iSndBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE); + optlen = sizeof(int); + break; + + case UDT_RCVBUF: + *(int*)optval = m_iRcvBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE); + optlen = sizeof(int); + break; + + case UDT_LINGER: + if (optlen < (int)(sizeof(linger))) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + *(linger*)optval = m_Linger; + optlen = sizeof(linger); + break; + + case UDP_SNDBUF: + *(int*)optval = m_iUDPSndBufSize; + optlen = sizeof(int); + break; + + case UDP_RCVBUF: + *(int*)optval = m_iUDPRcvBufSize; + optlen = sizeof(int); + break; + + case UDT_RENDEZVOUS: + *(bool *)optval = m_bRendezvous; + optlen = sizeof(bool); + break; + + case UDT_SNDTIMEO: + *(int*)optval = m_iSndTimeOut; + optlen = sizeof(int); + break; + + case UDT_RCVTIMEO: + *(int*)optval = m_iRcvTimeOut; + optlen = sizeof(int); + break; + + case UDT_REUSEADDR: + *(bool *)optval = m_bReuseAddr; + optlen = sizeof(bool); + break; + + case UDT_MAXBW: + *(int64_t*)optval = m_llMaxBW; + optlen = sizeof(int64_t); + break; + + case UDT_STATE: + *(int32_t*)optval = s_UDTUnited.getStatus(m_SocketID); + optlen = sizeof(int32_t); + break; + + case UDT_EVENT: + { + int32_t event = 0; + if (m_bBroken) + 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; + } + *(int32_t*)optval = event; + optlen = sizeof(int32_t); + break; + } + + case UDT_SNDDATA: + if (m_pSndBuffer) + *(int32_t*)optval = m_pSndBuffer->getCurrBufSize(); + else + *(int32_t*)optval = 0; + optlen = sizeof(int32_t); + 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); + break; + +#ifdef SRT_ENABLE_IPOPTS + case SRT_IPTTL: + if (m_bOpened) + *(int32_t*)optval = m_pSndQueue->getIpTTL(); + else + *(int32_t*)optval = m_iIpTTL; + break; + + case SRT_IPTOS: + if (m_bOpened) + *(int32_t*)optval = m_pSndQueue->getIpToS(); + else + *(int32_t*)optval = m_iIpToS; + break; +#endif + + case SRT_SENDER: + *(int32_t*)optval = m_bDataSender; + 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; + optlen = sizeof(int32_t); + break; + + case SRT_TSBPDDELAY: + *(int32_t*)optval = m_iTsbPdDelay; + optlen = sizeof(int32_t); + break; + +#ifdef SRT_ENABLE_TLPKTDROP + case SRT_TSBPDMAXLAG: + //Obsolete: preserve binary compatibility. + *(int32_t*)optval = 0; + optlen = sizeof(int32_t); + break; + + case SRT_TLPKTDROP: + *(int32_t*)optval = m_bTLPktDrop; + 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; + else +#endif + *(int32_t*)optval = (m_bDataSender || m_bTwoWayData) ? m_iSndCryptoKeyLen : 0; + optlen = sizeof(int32_t); + break; + + case SRT_SNDPBKEYLEN: +#ifdef SRT_ENABLE_SRTCC_EMB + if (m_pSRTCC) + *(int32_t*)optval = m_pSRTCC->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; + 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 */ + /* + * Was SRT_KMSTATE (receiver's decryption state) before TWOWAY support, + * 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; + 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; + else +#endif + *(int32_t*)optval = SRT_KM_S_UNSECURED; + optlen = sizeof(int32_t); + break; + +#ifdef SRT_ENABLE_NAKREPORT + case SRT_RCVNAKREPORT: + *(bool*)optval = m_bRcvNakReport; + optlen = sizeof(bool); + 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; + optlen = sizeof(int32_t); + break; + + case SRT_PEERVERSION: + if (m_pSRTCC) + *(int32_t*)optval = m_pSRTCC->getPeerSrtVersion(); + else + *(int32_t*)optval = 0; + optlen = sizeof(int32_t); + break; +#endif + +#ifdef SRT_ENABLE_CONNTIMEO + case SRT_CONNTIMEO: + *(int*)optval = m_iConnTimeOut; + optlen = sizeof(int); + break; +#endif + + default: + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + } +} + +void CUDT::clearData() +{ + // Initial sequence number, loss, acknowledgement, etc. + m_iPktSize = m_iMSS - CPacket::UDP_HDR_SIZE; + m_iPayloadSize = m_iPktSize - CPacket::HDR_SIZE; + + 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; + + // trace information + m_StartTime = CTimer::getTime(); + m_llSentTotal = m_llRecvTotal = m_iSndLossTotal = m_iRcvLossTotal = m_iRetransTotal = m_iSentACKTotal = m_iRecvACKTotal = m_iSentNAKTotal = m_iRecvNAKTotal = 0; + m_LastSampleTime = CTimer::getTime(); + m_llTraceSent = m_llTraceRecv = m_iTraceSndLoss = m_iTraceRcvLoss = m_iTraceRetrans = m_iSentACK = m_iRecvACK = m_iSentNAK = m_iRecvNAK = 0; + m_iTraceReorderDistance = 0; + m_fTraceBelatedTime = 0.0; + m_iTraceRcvBelated = 0; + +#ifdef SRT_ENABLE_TLPKTDROP + m_iSndDropTotal = 0; + m_iTraceSndDrop = 0; + m_iRcvDropTotal = 0; + m_iTraceRcvDrop = 0; +#endif /* SRT_ENABLE_TLPKTDROP */ + + m_iRcvUndecryptTotal = 0; + m_iTraceRcvUndecrypt = 0; + +#ifdef SRT_ENABLE_BSTATS + m_ullBytesSentTotal = 0; + m_ullBytesRecvTotal = 0; + m_ullBytesRetransTotal = 0; + m_ullTraceBytesSent = 0; + m_ullTraceBytesRecv = 0; + m_ullTraceBytesRetrans = 0; +#ifdef SRT_ENABLE_TLPKTDROP + m_ullSndBytesDropTotal = 0; + m_ullRcvBytesDropTotal = 0; + m_ullTraceSndBytesDrop = 0; + m_ullTraceRcvBytesDrop = 0; +#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; +#ifdef SRT_ENABLE_TLPKTDROP + m_bTLPktDropSnd = false; +#endif /* SRT_ENABLE_TLPKTDROP */ +#endif /* SRT_ENABLE_TSBPD */ + +#ifdef SRT_ENABLE_NAKREPORT + m_bSndPeerNakReport = false; +#endif /* SRT_ENABLE_NAKREPORT */ + + m_bPeerRexmitFlag = false; + + m_llSndDuration = m_llSndDurationTotal = 0; + +} + +void CUDT::open() +{ + CGuard cg(m_ConnectionLock); + + clearData(); + + // structures for queue + if (m_pSNode == NULL) + m_pSNode = new CSNode; + m_pSNode->m_pUDT = this; + m_pSNode->m_llTimeStamp = 1; + m_pSNode->m_iHeapLoc = -1; + + if (m_pRNode == NULL) + m_pRNode = new CRNode; + m_pRNode->m_pUDT = this; + m_pRNode->m_llTimeStamp = 1; + m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL; + m_pRNode->m_bOnList = false; + + m_iRTT = 10 * m_iSYNInterval; + m_iRTTVar = m_iRTT >> 1; + m_ullCPUFrequency = CTimer::getCPUFrequency(); + + // set up the timers + m_ullSYNInt = m_iSYNInterval * m_ullCPUFrequency; + + // set minimum NAK and EXP timeout to 300ms +#ifdef SRT_ENABLE_NAKREPORT + if (m_bRcvNakReport) + m_ullMinNakInt = m_iMinNakInterval * m_ullCPUFrequency; + else +#endif + m_ullMinNakInt = 300000 * m_ullCPUFrequency; + m_ullMinExpInt = 300000 * m_ullCPUFrequency; + + m_ullACKInt = m_ullSYNInt; + m_ullNAKInt = m_ullMinNakInt; + + uint64_t currtime; + CTimer::rdtsc(currtime); + m_ullLastRspTime = currtime; + m_ullNextACKTime = currtime + m_ullSYNInt; + m_ullNextNAKTime = currtime + m_ullNAKInt; +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; +#endif /* SRT_ENABLE_FASTREXMIT */ +#ifdef SRT_ENABLE_CBRTIMESTAMP + m_ullSndLastCbrTime = currtime; +#endif +#ifdef SRT_FIX_KEEPALIVE + m_ullLastSndTime = currtime; +#endif + + m_iPktCount = 0; + m_iLightACKCount = 1; + + m_ullTargetTime = 0; + m_ullTimeDiff = 0; + + // Now UDT is opened. + m_bOpened = true; +} + +void CUDT::setListenState() +{ + CGuard cg(m_ConnectionLock); + + if (!m_bOpened) + throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + + if (m_bConnecting || m_bConnected) + throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0); + + // listen can be called more than once + if (m_bListening) + return; + + // if there is already another socket listening on the same port + if (m_pRcvQueue->setListener(this) < 0) + throw CUDTException(MJ_NOTSUP, MN_BUSY, 0); + + m_bListening = true; +} + +void CUDT::connect(const sockaddr* serv_addr, int32_t forced_isn) +{ + CGuard cg(m_ConnectionLock); + + 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 + 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; + } + + 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 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(); +#endif + + LOGC(mglog.Debug) << CONID() << "CUDT::connect: sending UDT handshake for socket=" << m_ConnReq.m_iID; + +#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(); + + m_bConnecting = true; +#endif /* HAI_PATCH */ + + // asynchronous connect, return immediately + if (!m_bSynRecving) + { + delete [] reqdata; + return; + } + + // 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); + + CUDTException e; + + 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 */ + } + + response.setLength(m_iPayloadSize); + if (m_pRcvQueue->recvfrom(m_SocketID, response) > 0) + { + if (processConnectResponse(response) <= 0) + break; + + // new request/response should be sent out immediately on receving a response + m_llLastReqTime = 0; + } + + if (CTimer::getTime() > ttl) + { + // timeout + e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0); + break; + } + } + + delete [] reqdata; + delete [] resdata; + + 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() << "CUDT::connect: handshake exchange succeeded"; +} + +int CUDT::processConnectResponse(const CPacket& response) 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 + + if (!m_bConnecting) + return -1; + + /* 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. + 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)) + { + //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; + } + + if ( !response.isControl(UMSG_HANDSHAKE) ) + return -1; + + m_ConnRes.deserialize(response.m_pcData, response.getLength()); + + 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; + + 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; + } + } + else + { + // set cookie + if (m_ConnRes.m_iReqType == URQ_INDUCTION) + { + m_ConnReq.m_iReqType = URQ_CONCLUSION; + m_ConnReq.m_iCookie = m_ConnRes.m_iCookie; + m_llLastReqTime = 0; + return 1; + } + } + +POST_CONNECT: + // Remove from rendezvous queue + m_pRcvQueue->removeConnector(m_SocketID); + + // 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; +#endif +#ifdef SRT_ENABLE_TLPKTDROP + 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); + + // 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); + } + + 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; + } + + setupCC(); + + // And, I am connected too. + m_bConnecting = false; + m_bConnected = true; + + // register this socket for receiving data packets + m_pRNode->m_bOnList = true; + m_pRcvQueue->setNewEntry(this); + + // 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 0; +} + +#ifdef SRT_ENABLE_TSBPD +/* +* Timestamp-based Packet Delivery (TsbPd) thread +* This thread runs only if TsbPd mode is enabled +* Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay. +*/ +void* CUDT::tsbpd(void* param) +{ + CUDT* self = (CUDT*)param; + + THREAD_STATE_INIT("SRT Packet Delivery"); + + CGuard::enterCS(self->m_RecvLock); + self->m_bTsbPdAckWakeup = true; + while (!self->m_bClosing) + { + CPacket* rdpkt = 0; + uint64_t tsbpdtime = 0; + bool rxready = false; + + CGuard::enterCS(self->m_AckLock); + +#ifdef SRT_ENABLE_RCVBUFSZ_MAVG + self->m_pRcvBuffer->updRcvAvgDataSize(CTimer::getTime()); +#endif + +#ifdef SRT_ENABLE_TLPKTDROP + if (self->m_bTLPktDrop) + { + int32_t skiptoseqno = -1; + bool passack = true; //Get next packet to wait for even if not acked + + rxready = self->m_pRcvBuffer->getRcvFirstMsg(tsbpdtime, passack, skiptoseqno, &rdpkt); + /* + * rxready: packet at head of queue ready to play if true + * tsbpdtime: timestamp of packet at head of queue, ready or not. 0 if none. + * passack: ready head of queue not yet acknowledged if true + * skiptoseqno: sequence number of packet at head of queue if ready to play but + * some preceeding packets are missing (need to be skipped). -1 if none. + */ + if (rxready) + { + /* Packet ready to play according to time stamp but... */ + int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno); + + if (skiptoseqno != -1 && seqlen > 0) + { + /* + * skiptoseqno != -1, + * packet ready to play but preceeded by missing packets (hole). + */ + + /* Update drop/skip stats */ + self->m_iRcvDropTotal += seqlen; + self->m_iTraceRcvDrop += seqlen; + /* Estimate dropped/skipped bytes from average payload */ + int avgpayloadsz = self->m_pRcvBuffer->getRcvAvgPayloadSize(); + self->m_ullRcvBytesDropTotal += seqlen * avgpayloadsz; + self->m_ullTraceRcvBytesDrop += seqlen * avgpayloadsz; + + self->unlose(self->m_iRcvLastSkipAck, CSeqNo::decseq(skiptoseqno)); //remove(from,to-inclusive) + self->m_pRcvBuffer->skipData(seqlen); + + self->m_iRcvLastSkipAck = skiptoseqno; + + uint64_t now = CTimer::getTime(); + + int64_t timediff = 0; + if ( tsbpdtime ) + timediff = int64_t(now) - int64_t(tsbpdtime); + + 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"; + + tsbpdtime = 0; //Next sent ack will unblock + rxready = false; + } + else if (passack) + { + /* Packets ready to play but not yet acknowledged (should occurs withing 10ms) */ + rxready = false; + tsbpdtime = 0; //Next sent ack will unblock + } /* else packet ready to play */ + } /* else packets not ready to play */ + } else +#endif /* SRT_ENABLE_TLPKTDROP */ + { + rxready = self->m_pRcvBuffer->isRcvDataReady(tsbpdtime, &rdpkt); + } + CGuard::leaveCS(self->m_AckLock); + + if (rxready) + { + int seq=0; + if ( rdpkt ) + seq = rdpkt->getSeqNo(); + LOGC(tslog.Debug) << self->CONID() << "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 + */ + if (self->m_bSynRecving) + { + pthread_cond_signal(&self->m_RecvDataCond); + } + /* + * Set EPOLL_IN to wakeup any thread waiting on epoll + */ + self->s_UDTUnited.m_EPoll.update_events(self->m_SocketID, self->m_sPollID, UDT_EPOLL_IN, true); + tsbpdtime = 0; + } + + if (tsbpdtime != 0) + { + /* + * Buffer at head of queue is not ready to play. + * Schedule wakeup when it will be. + */ + self->m_bTsbPdAckWakeup = false; + THREAD_PAUSED(); + timespec locktime; + locktime.tv_sec = tsbpdtime / 1000000; + locktime.tv_nsec = (tsbpdtime % 1000000) * 1000; + int seq = 0; + 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"; + pthread_cond_timedwait(&self->m_RcvTsbPdCond, &self->m_RecvLock, &locktime); + THREAD_RESUMED(); + } + else + { + /* + * We have just signaled epoll; or + * receive queue is empty; or + * next buffer to deliver is not in receive queue (missing packet in sequence). + * + * Block until woken up by one of the following event: + * - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time if any) + * - New buffers ACKed + * - Closing the connection + */ + self->m_bTsbPdAckWakeup = true; + THREAD_PAUSED(); + pthread_cond_wait(&self->m_RcvTsbPdCond, &self->m_RecvLock); + THREAD_RESUMED(); + } + } + CGuard::leaveCS(self->m_RecvLock); + THREAD_EXIT(); + return NULL; +} +#endif /* SRT_ENABLE_TSBPD */ + +void CUDT::acceptAndRespond(const sockaddr* peer, CHandShake* hs) +{ + CGuard cg(m_ConnectionLock); + + // Uses the smaller MSS between the peers + if (hs->m_iMSS > m_iMSS) + hs->m_iMSS = m_iMSS; + else + m_iMSS = hs->m_iMSS; + + // exchange info for maximum flow window size + m_iFlowWindowSize = hs->m_iFlightFlagSize; + hs->m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize)? m_iRcvBufSize : m_iFlightFlagSize; + + m_iPeerISN = hs->m_iISN; + + m_iRcvLastAck = hs->m_iISN; +#ifdef ENABLE_LOGGING + m_iDebugPrevLastAck = m_iRcvLastAck; +#endif +#ifdef SRT_ENABLE_TLPKTDROP + m_iRcvLastSkipAck = m_iRcvLastAck; +#endif /* SRT_ENABLE_TLPKTDROP */ + m_iRcvLastAckAck = hs->m_iISN; + m_iRcvCurrSeqNo = hs->m_iISN - 1; + + m_PeerID = hs->m_iID; + hs->m_iID = m_SocketID; + + // use peer's ISN and send it back for security check + m_iISN = hs->m_iISN; + + m_iLastDecSeq = m_iISN - 1; + m_iSndLastAck = m_iISN; + m_iSndLastDataAck = m_iISN; +#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(); + + // this is a reponse handshake + hs->m_iReqType = URQ_CONCLUSION; + + // 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; + + // 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); + } + + CInfoBlock ib; + ib.m_iIPversion = m_iIPversion; + CInfoBlock::convert(peer, m_iIPversion, ib.m_piIP); + if (m_pCache->lookup(&ib) >= 0) + { + m_iRTT = ib.m_iRTT; + m_iBandwidth = ib.m_iBandwidth; + } + + setupCC(); + + m_pPeerAddr = (AF_INET == m_iIPversion) ? (sockaddr*)new sockaddr_in : (sockaddr*)new sockaddr_in6; + memcpy(m_pPeerAddr, peer, (AF_INET == m_iIPversion) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); + + // And of course, it is connected. + m_bConnected = true; + + // register this socket for receiving data packets + m_pRNode->m_bOnList = true; + m_pRcvQueue->setNewEntry(this); + + //send the response to the peer, see listen() for more discussions about this + 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; +} + +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) + { + m_pSRTCC->setMaxBW(m_llMaxBW); //Bytes/sec +#ifdef SRT_ENABLE_INPUTRATE + m_pSndBuffer->setInputRateSmpPeriod(0); //Disable input rate sampling + } + 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 + { + m_pSndBuffer->setInputRateSmpPeriod(500000); //Enable input rate sampling (fast start) +#endif /* SRT_ENABLE_INPUTRATE */ + } + + 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 */ +#ifdef SRT_ENABLE_NAKREPORT + /* + * Enable receiver's Periodic NAK Reports + */ + m_pSRTCC->setRcvNakReport(m_bRcvNakReport); + m_ullMinNakInt = m_iMinNakInterval * m_ullCPUFrequency; +#endif /* SRT_ENABLE_NAKREPORT */ +#endif /* SRT_ENABLE_SRTCC_EMB */ + + m_pCC->init(); + + m_ullInterval = (uint64_t)(m_pCC->m_dPktSndPeriod * m_ullCPUFrequency); + m_dCongestionWindow = m_pCC->m_dCWndSize; + return; +} + +void CUDT::close() +{ + if (!m_bOpened) + { + return; + } + + if (m_Linger.l_onoff != 0) + { + uint64_t entertime = CTimer::getTime(); + + 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 + if (m_ullLingerExpiration >= entertime) + break; + + if (!m_bSynSending) + { + // if this socket enables asynchronous sending, return immediately and let GC to close it later + if (m_ullLingerExpiration == 0) + m_ullLingerExpiration = entertime + m_Linger.l_linger * 1000000ULL; + + return; + } + + #ifndef WIN32 + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 1000000; + nanosleep(&ts, NULL); + #else + Sleep(1); + #endif + } + } + + // remove this socket from the snd queue + if (m_bConnected) + m_pSndQueue->m_pSndUList->remove(this); + +#ifdef HAI_PATCH + /* + * update_events below useless + * removing usock for EPolls right after (remove_usocks) clears it (in other HAI patch). + * + * What is in EPoll shall be the responsibility of the application, if it want local close event, + * it would remove the socket from the EPoll after close. + */ +#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 + try + { + for (set::iterator i = m_sPollID.begin(); i != m_sPollID.end(); ++ i) + s_UDTUnited.m_EPoll.remove_usock(*i, m_SocketID); + } + catch (...) + { + } + + if (!m_bOpened) + { + return; + } + + // Inform the threads handler to stop. + m_bClosing = true; + + CGuard cg(m_ConnectionLock); + + // Signal the sender and recver if they are waiting for data. + releaseSynch(); + + if (m_bListening) + { + m_bListening = false; + m_pRcvQueue->removeListener(this); + } + else if (m_bConnecting) + { + m_pRcvQueue->removeConnector(m_SocketID); + } + + if (m_bConnected) + { + if (!m_bShutdown) + sendCtrl(UMSG_SHUTDOWN); + + m_pCC->close(); + + // Store current connection information. + CInfoBlock ib; + ib.m_iIPversion = m_iIPversion; + CInfoBlock::convert(m_pPeerAddr, m_iIPversion, ib.m_piIP); + ib.m_iRTT = m_iRTT; + ib.m_iBandwidth = m_iBandwidth; + m_pCache->update(&ib); + + m_bConnected = false; + } + + // 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(); + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_SRTCC_EMB */ + + // CLOSED. + m_bOpened = false; +} + +int CUDT::send(const char* data, int len) +{ + if (m_iSockType == UDT_DGRAM) + throw CUDTException(MJ_NOTSUP, MN_ISDGRAM, 0); + + // throw an exception if not connected + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + if (len <= 0) + return 0; + + CGuard sendguard(m_SendLock); + + if (m_pSndBuffer->getCurrBufSize() == 0) + { + // delay the EXP timer to avoid mis-fired timeout + uint64_t currtime; + CTimer::rdtsc(currtime); +#if !defined(SRT_FIX_KEEPALIVE) + m_ullLastRspTime = currtime; +#endif +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; +#endif /* SRT_ENABLE_FASTREXMIT */ + } + if (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) + { + if (!m_bSynSending) + throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); + else + { + // wait here during a blocking sending + pthread_mutex_lock(&m_SendBlockLock); + if (m_iSndTimeOut < 0) + { + while (!m_bBroken && m_bConnected && !m_bClosing && (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) && m_bPeerHealth) + pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock); + } + else + { + uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * 1000ULL; + timespec locktime; + + locktime.tv_sec = exptime / 1000000; + locktime.tv_nsec = (exptime % 1000000) * 1000; + + while (!m_bBroken && m_bConnected && !m_bClosing && (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) && m_bPeerHealth && (CTimer::getTime() < exptime)) + pthread_cond_timedwait(&m_SendBlockCond, &m_SendBlockLock, &locktime); + } + pthread_mutex_unlock(&m_SendBlockLock); + + // check the connection status + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + else if (!m_bPeerHealth) + { + m_bPeerHealth = true; + throw CUDTException(MJ_PEERERROR); + } + } + } + + if (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) + { + if (m_iSndTimeOut >= 0) + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + + return 0; + } + + int size = (m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iPayloadSize; + if (size > len) + size = len; + + // record total time used for sending + if (m_pSndBuffer->getCurrBufSize() == 0) + m_llSndDurationCounter = CTimer::getTime(); + + // insert the user buffer into the sending list + m_pSndBuffer->addBuffer(data, size); + + // insert this socket to snd list if it is not on the list yet + m_pSndQueue->m_pSndUList->update(this, false); + + if (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) + { + // write is not available any more + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false); + } + + return size; +} + +int CUDT::recv(char* data, int len) +{ + if (m_iSockType == UDT_DGRAM) + throw CUDTException(MJ_NOTSUP, MN_ISDGRAM, 0); + + // throw an exception if not connected + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + if (len <= 0) + return 0; + + CGuard recvguard(m_RecvLock); + +#ifdef SRT_ENABLE_TSBPD + if (!m_pRcvBuffer->isRcvDataReady()) + { + if (!m_bSynRecving) + { + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + else + { + /* Kick TsbPd thread to schedule next wakeup (if running) */ + if (m_iRcvTimeOut < 0) + { + while (!m_bBroken && m_bConnected && !m_bClosing && !m_pRcvBuffer->isRcvDataReady()) + { + //Do not block forever, check connection status each 1 sec. + uint64_t exptime = CTimer::getTime() + 1000000ULL; + timespec locktime; + + locktime.tv_sec = exptime / 1000000; + locktime.tv_nsec = (exptime % 1000000) * 1000; + pthread_cond_timedwait(&m_RecvDataCond, &m_RecvLock, &locktime); + } + } + 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_RecvLock, &locktime); + if (CTimer::getTime() >= exptime) + break; + } + } + } + } + +#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); + else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + int res = m_pRcvBuffer->readBuffer(data, len); + +#ifdef SRT_ENABLE_TSBPD + /* Kick TsbPd thread to schedule next wakeup (if running) */ + if (m_bTsbPdRcv) + { + LOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup"); + pthread_cond_signal(&m_RcvTsbPdCond); + } +#endif /* SRT_ENABLE_TSBPD */ + + + if (!m_pRcvBuffer->isRcvDataReady()) + { + // read is not available any more + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + } + + if ((res <= 0) && (m_iRcvTimeOut >= 0)) + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + + return res; +} + +#ifdef SRT_ENABLE_SRCTIMESTAMP +int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder, uint64_t srctime) +#else +int CUDT::sendmsg(const char* data, int len, int msttl, bool inorder) +#endif +{ +#if defined(SRT_ENABLE_TLPKTDROP) || defined(SRT_ENABLE_ECN) + bool bCongestion = false; +#endif /* SRT_ENABLE_ECN */ + + if (m_iSockType == UDT_STREAM) + throw CUDTException(MJ_NOTSUP, MN_ISSTREAM, 0); + + // throw an exception if not connected + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + if (len <= 0) + return 0; + + if (len > m_iSndBufSize * m_iPayloadSize) + throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0); + + CGuard sendguard(m_SendLock); + + if (m_pSndBuffer->getCurrBufSize() == 0) + { + // delay the EXP timer to avoid mis-fired timeout + uint64_t currtime; + CTimer::rdtsc(currtime); +#if !defined(SRT_FIX_KEEPALIVE) + m_ullLastRspTime = currtime; +#endif +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; +#endif /* SRT_ENABLE_FASTREXMIT */ + } + +#if defined(SRT_ENABLE_TLPKTDROP) || defined(SRT_ENABLE_ECN) + if (m_bTLPktDropSnd) + { + int bytes, timespan; + m_pSndBuffer->getCurrBufSize(bytes, timespan); + +#ifdef SRT_ENABLE_TLPKTDROP + // high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms) + // Minimum value must accomodate an I-Frame (~8 x average frame size) + // >>need picture rate or app to set min treshold + // >>using 1 sec for worse case 1 frame using all bit budget. + // picture rate would be useful in auto SRT setting for min latency +#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); + if (timespan > msecThreshold) + { + // protect packet retransmission + CGuard::enterCS(m_AckLock); + int dbytes; + int dpkts = m_pSndBuffer->dropLateData(dbytes, CTimer::getTime() - (msecThreshold * 1000)); + if (dpkts > 0) { + m_iTraceSndDrop += dpkts; + m_iSndDropTotal += dpkts; + m_ullTraceSndBytesDrop += dbytes; + m_ullSndBytesDropTotal += dbytes; + + int32_t realack = m_iSndLastDataAck; + int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts); + + m_iSndLastAck = fakeack; + m_iSndLastDataAck = fakeack; + m_pSndLossList->remove(CSeqNo::decseq(m_iSndLastDataAck)); + /* If we dropped packets not yet sent, advance current position */ + // THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1) + if (CSeqNo::seqcmp(m_iSndCurrSeqNo, CSeqNo::decseq(m_iSndLastDataAck)) < 0) + { + m_iSndCurrSeqNo = CSeqNo::decseq(m_iSndLastDataAck); + } + LOGC(dlog.Debug).form("drop,now %llu,%d-%d seqs,%d pkts,%d bytes,%d ms", + (unsigned long long)CTimer::getTime(), + realack, m_iSndCurrSeqNo, + dpkts, dbytes, timespan); + } + bCongestion = true; + CGuard::leaveCS(m_AckLock); + } else +#endif /* SRT_ENABLE_TLPKTDROP */ + if ((uint32_t)timespan > (m_SndTsbPdDelay/2)) + { + LOGC(mglog.Debug).form("cong, NOW: %llu, BYTES %d, TMSPAN %d", (unsigned long long)CTimer::getTime(), bytes, timespan); + bCongestion = true; + } + } +#endif /* SRT_ENABLE_TLPKTDROP || SRT_ENABLE_ECN */ + + + if ((m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iPayloadSize < len) + { + //>>We should not get here if SRT_ENABLE_TLPKTDROP + if (!m_bSynSending) + throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0); + else + { + // wait here during a blocking sending + pthread_mutex_lock(&m_SendBlockLock); + if (m_iSndTimeOut < 0) + { + while (!m_bBroken && m_bConnected && !m_bClosing && ((m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iPayloadSize < len)) + pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock); + } + else + { + uint64_t exptime = CTimer::getTime() + m_iSndTimeOut * 1000ULL; + timespec locktime; + + locktime.tv_sec = exptime / 1000000; + locktime.tv_nsec = (exptime % 1000000) * 1000; + + while (!m_bBroken && m_bConnected && !m_bClosing && ((m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iPayloadSize < len) && (CTimer::getTime() < exptime)) + pthread_cond_timedwait(&m_SendBlockCond, &m_SendBlockLock, &locktime); + } + pthread_mutex_unlock(&m_SendBlockLock); + + // check the connection status + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + } +#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, + * we test twice if this code is outside the else section. + * This fix move it in the else (blocking-mode) section + */ + if ((m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iPayloadSize < len) + { + if (m_iSndTimeOut >= 0) + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + + 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 + if (m_pSndBuffer->getCurrBufSize() == 0) + m_llSndDurationCounter = CTimer::getTime(); + + // insert the user buffer into the sending list +#ifdef SRT_ENABLE_SRCTIMESTAMP +#ifdef SRT_ENABLE_CBRTIMESTAMP + if (srctime == 0) + { + uint64_t currtime; + CTimer::rdtsc(currtime); + + m_ullSndLastCbrTime = max(currtime, m_ullSndLastCbrTime + m_ullInterval); + srctime = m_ullSndLastCbrTime / m_ullCPUFrequency; + } +#endif + m_pSndBuffer->addBuffer(data, len, msttl, inorder, srctime); + LOGC(dlog.Debug) << CONID() << "sock:SENDING srctime: " << srctime << " DATA SIZE: " << len; + +#else /* SRT_ENABLE_SRCTIMESTAMP */ + m_pSndBuffer->addBuffer(data, len, msttl, inorder); +#endif /* SRT_ENABLE_SRCTIMESTAMP */ + + + // insert this socket to the snd list if it is not on the list yet +#if defined(SRT_ENABLE_TLPKTDROP) || defined(SRT_ENABLE_ECN) + m_pSndQueue->m_pSndUList->update(this, bCongestion ? true : false); +#else + m_pSndQueue->m_pSndUList->update(this, false); +#endif + + if (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) + { + // write is not available any more + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false); + } + +#ifdef SRT_ENABLE_ECN + if (bCongestion) + throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0); +#endif /* SRT_ENABLE_ECN */ + return len; +} + +#ifdef SRT_ENABLE_TSBPD + +int CUDT::recvmsg(char* data, int len) +{ +#ifdef SRT_ENABLE_SRCTIMESTAMP + uint64_t srctime; + return(CUDT::recvmsg(data, len, srctime)); +} + +int CUDT::recvmsg(char* data, int len, uint64_t& srctime) +{ +#endif /* SRT_ENABLE_SRCTIMESTAMP */ + 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); + + /* XXX DEBUG STUFF - enable when required + char charbool[2] = {'0', '1'}; + char ptrn [] = "RECVMSG/BEGIN BROKEN 1 CONN 1 CLOSING 1 SYNCR 1 NMSG "; + int pos [] = {21, 28, 38, 46, 53}; + ptrn[pos[0]] = charbool[m_bBroken]; + ptrn[pos[1]] = charbool[m_bConnected]; + ptrn[pos[2]] = charbool[m_bClosing]; + ptrn[pos[3]] = charbool[m_bSynRecving]; + int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum()); + strcpy(ptrn + pos[4] + wrtlen, "\n"); + fputs(ptrn, stderr); + // */ + + if (m_bBroken || m_bClosing) + { + int res = m_pRcvBuffer->readMsg(data, len); + + /* Kick TsbPd thread to schedule next wakeup (if running) */ + if (m_bTsbPdRcv) + pthread_cond_signal(&m_RcvTsbPdCond); + + if (!m_pRcvBuffer->isRcvDataReady()) + { + // read is not available any more + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + } + + if (res == 0) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else + return res; + } + + if (!m_bSynRecving) + { + +#ifdef SRT_ENABLE_SRCTIMESTAMP + int res = m_pRcvBuffer->readMsg(data, len, srctime); +#else + int res = m_pRcvBuffer->readMsg(data, len); +#endif + if (res == 0) + { + // read is not available any more + + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPdRcv) + pthread_cond_signal(&m_RcvTsbPdCond); + + // Shut up EPoll if no more messages in non-blocking mode + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0); + } + else + { + if (!m_pRcvBuffer->isRcvDataReady()) + { + // Kick TsbPd thread to schedule next wakeup (if running) + if (m_bTsbPdRcv) + pthread_cond_signal(&m_RcvTsbPdCond); + + // Shut up EPoll if no more messages in non-blocking mode + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + + // After signaling the tsbpd for ready data, report the bandwidth. + double bw = m_iBandwidth * m_iPayloadSize * 8.0 / 1000000.0; + LOGC(mglog.Debug) << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth << ")"; + } + return res; + } + } + + int res = 0; + bool timeout = false; + //Do not block forever, check connection status each 1 sec. + uint64_t recvtmo = m_iRcvTimeOut < 0 ? 1000 : m_iRcvTimeOut; + + do + { + if (!m_bBroken && m_bConnected && !m_bClosing && !timeout && (!m_pRcvBuffer->isRcvDataReady())) + { + /* Kick TsbPd thread to schedule next wakeup (if running) */ + if (m_bTsbPdRcv) + { + LOGP(tslog.Debug, "recvmsg: KICK tsbpd()"); + pthread_cond_signal(&m_RcvTsbPdCond); + } + + do + { + uint64_t exptime = CTimer::getTime() + (recvtmo * 1000ULL); + timespec locktime; + + locktime.tv_sec = exptime / 1000000; + locktime.tv_nsec = (exptime % 1000000) * 1000; + if (pthread_cond_timedwait(&m_RecvDataCond, &m_RecvLock, &locktime) == ETIMEDOUT) + { + if (!(m_iRcvTimeOut < 0)) + timeout = true; + LOGP(tslog.Debug, "recvmsg: DATA COND: expired -- trying to get data anyway"); + } + else + { + LOGP(tslog.Debug, "recvmsg: DATA COND: KICKED."); + } + } while (!m_bBroken && m_bConnected && !m_bClosing && !timeout && (!m_pRcvBuffer->isRcvDataReady())); + } + + /* XXX DEBUG STUFF - enable when required + char charbool[2] = {'0', '1'}; + char ptrn [] = "RECVMSG/GO-ON BROKEN 1 CONN 1 CLOSING 1 TMOUT 1 NMSG "; + int pos [] = {21, 28, 38, 46, 53}; + ptrn[pos[0]] = charbool[m_bBroken]; + ptrn[pos[1]] = charbool[m_bConnected]; + ptrn[pos[2]] = charbool[m_bClosing]; + ptrn[pos[3]] = charbool[timeout]; + int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum()); + strcpy(ptrn + pos[4] + wrtlen, "\n"); + fputs(ptrn, stderr); + // */ + +#ifdef SRT_ENABLE_SRCTIMESTAMP + res = m_pRcvBuffer->readMsg(data, len, srctime); +#else + res = m_pRcvBuffer->readMsg(data, len); +#endif + + 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); + + 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; + + if (pthread_cond_timedwait(&m_RecvDataCond, &m_RecvDataLock, &locktime) == ETIMEDOUT) + timeout = true; + res = m_pRcvBuffer->readMsg(data, len); + } + pthread_mutex_unlock(&m_RecvDataLock); + + 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); + + 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) && (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) +{ + if (m_iSockType == UDT_DGRAM) + throw CUDTException(MJ_NOTSUP, MN_ISDGRAM, 0); + + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + + if (size <= 0) + return 0; + + CGuard sendguard(m_SendLock); + + if (m_pSndBuffer->getCurrBufSize() == 0) + { + // delay the EXP timer to avoid mis-fired timeout + uint64_t currtime; + CTimer::rdtsc(currtime); +#if !defined(SRT_FIX_KEEPALIVE) + m_ullLastRspTime = currtime; +#endif +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; +#endif /* SRT_ENABLE_FASTREXMIT */ + } + + int64_t tosend = size; + int unitsize; + + // positioning... + try + { + ifs.seekg((streamoff)offset); + } + catch (...) + { + // XXX It would be nice to note that this is reported + // by exception only if explicitly requested by setting + // the exception flags in the stream. + throw CUDTException(MJ_FILESYSTEM, MN_SEEKGFAIL); + } + + // sending block by block + while (tosend > 0) + { + if (ifs.fail()) + throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); + + if (ifs.eof()) + break; + + unitsize = int((tosend >= block) ? block : tosend); + + pthread_mutex_lock(&m_SendBlockLock); + while (!m_bBroken && m_bConnected && !m_bClosing && (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) && m_bPeerHealth) + pthread_cond_wait(&m_SendBlockCond, &m_SendBlockLock); + pthread_mutex_unlock(&m_SendBlockLock); + + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + else if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + else if (!m_bPeerHealth) + { + // reset peer health status, once this error returns, the app should handle the situation at the peer side + m_bPeerHealth = true; + throw CUDTException(MJ_PEERERROR); + } + + // record total time used for sending + if (m_pSndBuffer->getCurrBufSize() == 0) + m_llSndDurationCounter = CTimer::getTime(); + + int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize); + + if (sentsize > 0) + { + tosend -= sentsize; + offset += sentsize; + } + + // insert this socket to snd list if it is not on the list yet + m_pSndQueue->m_pSndUList->update(this, false); + } + + if (m_iSndBufSize <= m_pSndBuffer->getCurrBufSize()) + { + // write is not available any more + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, false); + } + + return size - tosend; +} + +int64_t CUDT::recvfile(fstream& ofs, int64_t& offset, int64_t size, int block) +{ + if (m_iSockType == UDT_DGRAM) + throw CUDTException(MJ_NOTSUP, MN_ISDGRAM, 0); + + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + if (size <= 0) + return 0; + + CGuard recvguard(m_RecvLock); + + int64_t torecv = size; + int unitsize = block; + int recvsize; + + // positioning... + try + { + ofs.seekp((streamoff)offset); + } + catch (...) + { + // XXX It would be nice to note that this is reported + // by exception only if explicitly requested by setting + // the exception flags in the stream. + throw CUDTException(MJ_FILESYSTEM, MN_SEEKPFAIL); + } + + // receiving... "recvfile" is always blocking + while (torecv > 0) + { + if (ofs.fail()) + { + // send the sender a signal so it will not be blocked forever + int32_t err_code = CUDTException::EFILE; + sendCtrl(UMSG_PEERERROR, &err_code); + + throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL); + } + + pthread_mutex_lock(&m_RecvDataLock); + while (!m_bBroken && m_bConnected && !m_bClosing && !m_pRcvBuffer->isRcvDataReady()) + pthread_cond_wait(&m_RecvDataCond, &m_RecvDataLock); + pthread_mutex_unlock(&m_RecvDataLock); + + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady()) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + unitsize = int((torecv >= block) ? block : torecv); + recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize); + + if (recvsize > 0) + { + torecv -= recvsize; + offset += recvsize; + } + } + + if (!m_pRcvBuffer->isRcvDataReady()) + { + // read is not available any more + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, false); + } + + return size - torecv; +} + +void CUDT::sample(CPerfMon* perf, bool clear) +{ + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + uint64_t currtime = CTimer::getTime(); + perf->msTimeStamp = (currtime - m_StartTime) / 1000; + + perf->pktSent = m_llTraceSent; + perf->pktRecv = m_llTraceRecv; + perf->pktSndLoss = m_iTraceSndLoss; + perf->pktRcvLoss = m_iTraceRcvLoss; + perf->pktRetrans = m_iTraceRetrans; + perf->pktSentACK = m_iSentACK; + perf->pktRecvACK = m_iRecvACK; + perf->pktSentNAK = m_iSentNAK; + perf->pktRecvNAK = m_iRecvNAK; + perf->usSndDuration = m_llSndDuration; + perf->pktReorderDistance = m_iTraceReorderDistance; + perf->pktRcvAvgBelatedTime = m_fTraceBelatedTime; + perf->pktRcvBelated = m_iTraceRcvBelated; + + perf->pktSentTotal = m_llSentTotal; + perf->pktRecvTotal = m_llRecvTotal; + perf->pktSndLossTotal = m_iSndLossTotal; + perf->pktRcvLossTotal = m_iRcvLossTotal; + perf->pktRetransTotal = m_iRetransTotal; + perf->pktSentACKTotal = m_iSentACKTotal; + perf->pktRecvACKTotal = m_iRecvACKTotal; + perf->pktSentNAKTotal = m_iSentNAKTotal; + perf->pktRecvNAKTotal = m_iRecvNAKTotal; + perf->usSndDurationTotal = m_llSndDurationTotal; + + double interval = double(currtime - m_LastSampleTime); + + perf->mbpsSendRate = double(m_llTraceSent) * m_iPayloadSize * 8.0 / interval; + perf->mbpsRecvRate = double(m_llTraceRecv) * m_iPayloadSize * 8.0 / interval; + + perf->usPktSndPeriod = m_ullInterval / double(m_ullCPUFrequency); + perf->pktFlowWindow = m_iFlowWindowSize; + perf->pktCongestionWindow = (int)m_dCongestionWindow; + perf->pktFlightSize = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) - 1; + perf->msRTT = m_iRTT/1000.0; + perf->mbpsBandwidth = m_iBandwidth * m_iPayloadSize * 8.0 / 1000000.0; + + if (pthread_mutex_trylock(&m_ConnectionLock) == 0) + { + perf->byteAvailSndBuf = (m_pSndBuffer == NULL) ? 0 + : (m_iSndBufSize - m_pSndBuffer->getCurrBufSize()) * m_iMSS; + perf->byteAvailRcvBuf = (m_pRcvBuffer == NULL) ? 0 + : m_pRcvBuffer->getAvailBufSize() * m_iMSS; + + pthread_mutex_unlock(&m_ConnectionLock); + } + else + { + perf->byteAvailSndBuf = 0; + perf->byteAvailRcvBuf = 0; + } + + if (clear) + { + m_llTraceSent = m_llTraceRecv = m_iTraceSndLoss = m_iTraceRcvLoss = m_iTraceRetrans = m_iSentACK = m_iRecvACK = m_iSentNAK = m_iRecvNAK = 0; + m_llSndDuration = 0; + m_iTraceRcvRetrans = 0; + m_LastSampleTime = currtime; + } +} + +#ifdef SRT_ENABLE_BSTATS +void CUDT::bstats(CBytePerfMon* perf, bool clear) +{ + if (!m_bConnected) + throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0); + if (m_bBroken || m_bClosing) + throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0); + + /* + * RecvLock to protect consistency (pkts vs. bytes vs. timespan) of Recv buffer stats. + * Send buffer stats protected in send buffer class + */ + CGuard recvguard(m_RecvLock); + + uint64_t currtime = CTimer::getTime(); + perf->msTimeStamp = (currtime - m_StartTime) / 1000; + + perf->pktSent = m_llTraceSent; + perf->pktRecv = m_llTraceRecv; + perf->pktSndLoss = m_iTraceSndLoss; + perf->pktRcvLoss = m_iTraceRcvLoss; + perf->pktRetrans = m_iTraceRetrans; + perf->pktSentACK = m_iSentACK; + perf->pktRecvACK = m_iRecvACK; + perf->pktSentNAK = m_iSentNAK; + perf->pktRecvNAK = m_iRecvNAK; + perf->usSndDuration = m_llSndDuration; + //>new + /* perf byte counters include all headers (SRT+UDP+IP) */ + const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE; + perf->byteSent = m_ullTraceBytesSent + (m_llTraceSent * pktHdrSize); + perf->byteRecv = m_ullTraceBytesRecv + (m_llTraceRecv * pktHdrSize); + perf->byteRetrans = m_ullTraceBytesRetrans + (m_iTraceRetrans * pktHdrSize); +#ifdef SRT_ENABLE_LOSTBYTESCOUNT + perf->byteRcvLoss = m_ullTraceRcvBytesLoss + (m_iTraceRcvLoss * pktHdrSize); +#endif + +#ifdef SRT_ENABLE_TLPKTDROP + perf->pktSndDrop = m_iTraceSndDrop; + perf->pktRcvDrop = m_iTraceRcvDrop + m_iTraceRcvUndecrypt; + perf->byteSndDrop = m_ullTraceSndBytesDrop + (m_iTraceSndDrop * pktHdrSize); + perf->byteRcvDrop = m_ullTraceRcvBytesDrop + (m_iTraceRcvDrop * pktHdrSize) + m_ullTraceRcvBytesUndecrypt; +#else + perf->pktSndDrop = 0; + perf->pktRcvDrop = 0; + perf->byteSndDrop = 0; + perf->byteRcvDrop = 0; +#endif + + perf->pktRcvUndecrypt = m_iTraceRcvUndecrypt; + perf->byteRcvUndecrypt = m_ullTraceRcvBytesUndecrypt; + + //< + + perf->pktSentTotal = m_llSentTotal; + perf->pktRecvTotal = m_llRecvTotal; + perf->pktSndLossTotal = m_iSndLossTotal; + perf->pktRcvLossTotal = m_iRcvLossTotal; + perf->pktRetransTotal = m_iRetransTotal; + perf->pktSentACKTotal = m_iSentACKTotal; + perf->pktRecvACKTotal = m_iRecvACKTotal; + perf->pktSentNAKTotal = m_iSentNAKTotal; + perf->pktRecvNAKTotal = m_iRecvNAKTotal; + perf->usSndDurationTotal = m_llSndDurationTotal; + //>new + perf->byteSentTotal = m_ullBytesSentTotal + (m_llSentTotal * pktHdrSize); + perf->byteRecvTotal = m_ullBytesRecvTotal + (m_llRecvTotal * pktHdrSize); + perf->byteRetransTotal = m_ullBytesRetransTotal + (m_iRetransTotal * pktHdrSize); +#ifdef SRT_ENABLE_LOSTBYTESCOUNT + perf->byteRcvLossTotal = m_ullRcvBytesLossTotal + (m_iRcvLossTotal * pktHdrSize); +#endif +#ifdef SRT_ENABLE_TLPKTDROP + perf->pktSndDropTotal = m_iSndDropTotal; + perf->pktRcvDropTotal = m_iRcvDropTotal + m_iRcvUndecryptTotal; + perf->byteSndDropTotal = m_ullSndBytesDropTotal + (m_iSndDropTotal * pktHdrSize); + perf->byteRcvDropTotal = m_ullRcvBytesDropTotal + (m_iRcvDropTotal * pktHdrSize) + m_ullRcvBytesUndecryptTotal; +#else + perf->pktSndDropTotal = 0; + perf->pktRcvDropTotal = 0; + perf->byteSndDropTotal = 0; + perf->byteRcvDropTotal = 0; +#endif + perf->pktRcvUndecryptTotal = m_iRcvUndecryptTotal; + perf->byteRcvUndecryptTotal = m_ullRcvBytesUndecryptTotal; + //< + + double interval = double(currtime - m_LastSampleTime); + + //>mod + perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval; + perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval; + //< + + perf->usPktSndPeriod = m_ullInterval / double(m_ullCPUFrequency); + perf->pktFlowWindow = m_iFlowWindowSize; + perf->pktCongestionWindow = (int)m_dCongestionWindow; + perf->pktFlightSize = CSeqNo::seqlen(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) - 1; + perf->msRTT = (double)m_iRTT/1000.0; + //>new +#ifdef SRT_ENABLE_TSBPD + perf->msSndTsbPdDelay = m_bTsbPdSnd ? m_SndTsbPdDelay : 0; + perf->msRcvTsbPdDelay = m_bTsbPdRcv ? m_RcvTsbPdDelay : 0; +#endif + 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 + //< + uint32_t availbw = (uint64_t)(m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth); + + perf->mbpsBandwidth = (double)(availbw * (m_iPayloadSize + pktHdrSize) * 8.0 / 1000000.0); + + if (pthread_mutex_trylock(&m_ConnectionLock) == 0) + { + if (m_pSndBuffer != NULL) { + //new> + #ifdef SRT_ENABLE_SNDBUFSZ_MAVG + perf->pktSndBuf = m_pSndBuffer->getAvgBufSize(perf->byteSndBuf, perf->msSndBuf); + #else + perf->pktSndBuf = m_pSndBuffer->getCurrBufSize(perf->byteSndBuf, perf->msSndBuf); + #endif + perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize); + //< + perf->byteAvailSndBuf = (m_iSndBufSize - perf->pktSndBuf) * m_iMSS; + } else { + perf->byteAvailSndBuf = 0; + //new> + perf->pktSndBuf = 0; + perf->byteSndBuf = 0; + perf->msSndBuf = 0; + //< + } + + if (m_pRcvBuffer != NULL) { + 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; + //new> + perf->pktRcvBuf = 0; + perf->byteRcvBuf = 0; + perf->msRcvBuf = 0; + //< + } + + pthread_mutex_unlock(&m_ConnectionLock); + } + else + { + perf->byteAvailSndBuf = 0; + perf->byteAvailRcvBuf = 0; + //new> + perf->pktSndBuf = 0; + perf->byteSndBuf = 0; + perf->msSndBuf = 0; + + perf->byteRcvBuf = 0; + perf->msRcvBuf = 0; + //< + } + + if (clear) + { +#ifdef SRT_ENABLE_TLPKTDROP + m_iTraceSndDrop = 0; + m_iTraceRcvDrop = 0; + m_ullTraceSndBytesDrop = 0; + m_ullTraceRcvBytesDrop = 0; +#endif /* SRT_ENABLE_TLPKTDROP */ + m_iTraceRcvUndecrypt = 0; + m_ullTraceRcvBytesUndecrypt = 0; + //new> + m_ullTraceBytesSent = m_ullTraceBytesRecv = m_ullTraceBytesRetrans = 0; + //< + m_llTraceSent = m_llTraceRecv = m_iTraceSndLoss = m_iTraceRcvLoss = m_iTraceRetrans = m_iSentACK = m_iRecvACK = m_iSentNAK = m_iRecvNAK = 0; + m_llSndDuration = 0; + 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_ullInterval < (uint64_t)minSP) + m_ullInterval = (uint64_t)minSP; +#endif /* SRT_ENABLE_SRTCC_EMB && SRT_ENABLE_INPUTRATE */ + +#if 0//debug + static int callcnt = 0; + if (!(callcnt++ % 250)) fprintf(stderr, "SndPeriod=%llu\n", (unsigned long long)m_ullInterval/m_ullCPUFrequency); +#endif +} + +void CUDT::initSynch() +{ + pthread_mutex_init(&m_SendBlockLock, NULL); + pthread_cond_init(&m_SendBlockCond, NULL); + pthread_mutex_init(&m_RecvDataLock, NULL); + pthread_cond_init(&m_RecvDataCond, NULL); + pthread_mutex_init(&m_SendLock, NULL); + pthread_mutex_init(&m_RecvLock, NULL); + pthread_mutex_init(&m_RcvLossLock, NULL); + pthread_mutex_init(&m_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 */ +} + +void CUDT::destroySynch() +{ + pthread_mutex_destroy(&m_SendBlockLock); + pthread_cond_destroy(&m_SendBlockCond); + pthread_mutex_destroy(&m_RecvDataLock); + pthread_cond_destroy(&m_RecvDataCond); + pthread_mutex_destroy(&m_SendLock); + pthread_mutex_destroy(&m_RecvLock); + pthread_mutex_destroy(&m_RcvLossLock); + pthread_mutex_destroy(&m_AckLock); + pthread_mutex_destroy(&m_ConnectionLock); +#ifdef SRT_ENABLE_TSBPD + pthread_cond_destroy(&m_RcvTsbPdCond); +#endif /* SRT_ENABLE_TSBPD */ +} + +void CUDT::releaseSynch() +{ + // wake up user calls + pthread_mutex_lock(&m_SendBlockLock); + pthread_cond_signal(&m_SendBlockCond); + pthread_mutex_unlock(&m_SendBlockLock); + + pthread_mutex_lock(&m_SendLock); + pthread_mutex_unlock(&m_SendLock); + + pthread_mutex_lock(&m_RecvDataLock); + pthread_cond_signal(&m_RecvDataCond); + pthread_mutex_unlock(&m_RecvDataLock); + +#ifdef SRT_ENABLE_TSBPD + pthread_mutex_lock(&m_RecvLock); + pthread_cond_signal(&m_RcvTsbPdCond); + pthread_mutex_unlock(&m_RecvLock); + if (!pthread_equal(m_RcvTsbPdThread, pthread_t())) + { + pthread_join(m_RcvTsbPdThread, NULL); + m_RcvTsbPdThread = pthread_t(); + } +#endif + pthread_mutex_lock(&m_RecvLock); + pthread_mutex_unlock(&m_RecvLock); +} + +#if ENABLE_LOGGING +static void DebugAck(int prev, int ack, string CONID) +{ + if ( !prev ) + { + LOGC(mglog.Debug).form("ACK %d", ack); + return; + } + + prev = CSeqNo::incseq(prev); + int diff = CSeqNo::seqcmp(ack, prev); + if ( diff < 0 ) + { + LOGC(mglog.Error).form("ACK %d-%d (%d)", prev, ack, 1+CSeqNo::seqcmp(ack, prev)); + return; + } + + bool shorted = diff > 100; // sanity + if ( shorted ) + ack = CSeqNo::incseq(prev, 100); + + ostringstream ackv; + for (; prev != ack; prev = CSeqNo::incseq(prev)) + ackv << prev << " "; + if ( shorted ) + ackv << "..."; + LOGC(mglog.Debug) << CONID << "ACK (" << (diff+1) << "): " << ackv.str() << ack; +} +#else +static inline void DebugAck(int, int, string) {} +#endif + +void CUDT::sendCtrl(UDTMessageType pkttype, void* lparam, void* rparam, int size) +{ + CPacket ctrlpkt; + 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; + +#if ENABLE_LOGGING + struct SaveBack + { + int& target; + const int& source; + + ~SaveBack() + { + target = source; + } + } l_saveback = { m_iDebugPrevLastAck, m_iRcvLastAck }; + (void)l_saveback; //kill compiler warning: unused variable `l_saveback` [-Wunused-variable] + + local_prevack = m_iDebugPrevLastAck; +#endif + + switch (pkttype) + { + case UMSG_ACK: //010 - Acknowledgement + { + int32_t ack; + + // If there is no loss, the ACK is the current largest sequence number plus 1; + // Otherwise it is the smallest sequence number in the receiver loss list. + if (m_pRcvLossList->getLossLength() == 0) + ack = CSeqNo::incseq(m_iRcvCurrSeqNo); + else + ack = m_pRcvLossList->getFirstLostSeq(); + + if (m_iRcvLastAckAck == ack) + break; + + // send out a lite ACK + // to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number + if (size == SEND_LITE_ACK) + { + ctrlpkt.pack(pkttype, NULL, &ack, size); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + DebugAck(local_prevack, ack, CONID()); + break; + } + + uint64_t currtime; + CTimer::rdtsc(currtime); + + // There are new received packets to acknowledge, update related information. +#ifdef SRT_ENABLE_TLPKTDROP + /* tsbpd thread may also call ackData when skipping packet so protect code */ + CGuard::enterCS(m_AckLock); +#endif + + // IF ack > m_iRcvLastAck + if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0) + { + int acksize = CSeqNo::seqoff(m_iRcvLastSkipAck, ack); + + m_iRcvLastAck = ack; +#ifdef SRT_ENABLE_TLPKTDROP + m_iRcvLastSkipAck = ack; + + // XXX Unknown as to whether it matters. + // This if (acksize) causes that ackData() won't be called. + // With size == 0 it wouldn't do anything except calling CTimer::triggerEvent(). + // This, again, signals the condition, CTimer::m_EventCond. + // This releases CTimer::waitForEvent() call used in CUDTUnited::selectEx(). + // Preventing to call this on zero size makes sense, if it prevents false alerts. + if (acksize) + m_pRcvBuffer->ackData(acksize); + CGuard::leaveCS(m_AckLock); +#else + m_pRcvBuffer->ackData(acksize); +#endif + + // If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond, + // signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which + // will signal m_RecvDataCond when there's time to play particular + // data packet. + +#ifdef SRT_ENABLE_TSBPD + if (m_bTsbPdRcv) + { + /* Newly acknowledged data, signal TsbPD thread */ + pthread_mutex_lock(&m_RecvLock); + if (m_bTsbPdAckWakeup) + pthread_cond_signal(&m_RcvTsbPdCond); + pthread_mutex_unlock(&m_RecvLock); + } + else +#endif /* SRT_ENABLE_TSBPD */ + { + if (m_bSynRecving) + { + // signal a waiting "recv" call if there is any data available + pthread_mutex_lock(&m_RecvDataLock); + pthread_cond_signal(&m_RecvDataCond); + pthread_mutex_unlock(&m_RecvDataLock); + } + // acknowledge any waiting epolls to read + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN, true); + } +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::enterCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + } + else if (ack == m_iRcvLastAck) + { + // If the ACK was just sent already AND elapsed time did not exceed RTT, + if ((currtime - m_ullLastAckTime) < ((m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency)) + { +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + break; + } + } + else + { + // Not possible (m_iRcvCurrSeqNo+1 < m_iRcvLastAck ?) +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + break; + } + + // [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]] + + // Send out the ACK only if has not been received by the sender before + if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0) + { + // NOTE: SRT_ENABLE_BSTATS turns on extra fields above size 6 + // also known as ACKD_TOTAL_SIZE_VER100. + int32_t data[ACKD_TOTAL_SIZE]; + + // Pay no attention to this stupidity. CAckNo::incack does exactly + // the same thing as CSeqNo::incseq - and it wouldn't work otherwise. + m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo); + data[ACKD_RCVLASTACK] = m_iRcvLastAck; + data[ACKD_RTT] = m_iRTT; + data[ACKD_RTTVAR] = m_iRTTVar; + data[ACKD_BUFFERLEFT] = m_pRcvBuffer->getAvailBufSize(); + // a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock + if (data[ACKD_BUFFERLEFT] < 2) + data[ACKD_BUFFERLEFT] = 2; + + if (currtime - m_ullLastAckTime > m_ullSYNInt) + { +#ifdef SRT_ENABLE_BSTATS + int rcvRate; + int 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 */ + //>>Patch while incompatible (1.0.2) receiver floating around + if ( version == SrtVersion(1, 0, 2) ) + { + data[ACKD_RCVRATE] = rcvRate; //bytes/sec + data[ACKD_XMRATE] = data[ACKD_BANDWIDTH] * m_iPayloadSize; //bytes/sec + ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102; + } + else if (version >= SrtVersion(1, 0, 3)) + { + data[ACKD_RCVRATE] = rcvRate; //bytes/sec + ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101; + + } + 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 + { + ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_UDTBASE); + } + + 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()); + + m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck); + + ++ m_iSentACK; + ++ m_iSentACKTotal; + } +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + break; + } + + case UMSG_ACKACK: //110 - Acknowledgement of Acknowledgement + ctrlpkt.pack(pkttype, lparam); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + break; + + case UMSG_LOSSREPORT: //011 - Loss Report + { + // Explicitly defined lost sequences + if (rparam) + { + int32_t* lossdata = (int32_t*)rparam; + + size_t bytes = sizeof(*lossdata)*size; + ctrlpkt.pack(pkttype, NULL, lossdata, bytes); + + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + ++ m_iSentNAK; + ++ m_iSentNAKTotal; + } + // Call with no arguments - get loss list from internal data. + else if (m_pRcvLossList->getLossLength() > 0) + { + // this is periodically NAK report; make sure NAK cannot be sent back too often + + // read loss list from the local receiver loss list + int32_t* data = new int32_t[m_iPayloadSize / 4]; + int losslen; + m_pRcvLossList->getLossArray(data, losslen, m_iPayloadSize / 4); + + if (0 < losslen) + { + ctrlpkt.pack(pkttype, NULL, data, losslen * 4); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + ++ m_iSentNAK; + ++ m_iSentNAKTotal; + } + + delete [] data; + } + + // update next NAK time, which should wait enough time for the retansmission, but not too long + m_ullNAKInt = (m_iRTT + 4 * m_iRTTVar) * m_ullCPUFrequency; +#ifdef SRT_ENABLE_NAKREPORT + /* + * duB: + * The RTT accounts for the time for the last NAK to reach sender and start resending lost pkts. + * The rcv_speed add the time to resend all the pkts in the loss list. + * + * For realtime Transport Stream content, pkts/sec is not a good indication of time to transmit + * since packets are not filled to m_iMSS and packet size average is lower than (7*188) + * for low bit rates. + * If NAK report is lost, another cycle (RTT) is requred which is bad for low latency so we + * accelerate the NAK Reports frequency, at the cost of possible duplicate resend. + * Finally, the UDT4 native minimum NAK interval (m_ullMinNakInt) is 300 ms which is too high + * (~10 i30 video frames) to maintain low latency. + */ + m_ullNAKInt /= m_iNakReportAccel; +#else + int rcv_speed = m_RcvTimeWindow.getPktRcvSpeed(); + if (rcv_speed > 0) + m_ullNAKInt += (m_pRcvLossList->getLossLength() * 1000000ULL / rcv_speed) * m_ullCPUFrequency; +#endif + if (m_ullNAKInt < m_ullMinNakInt) + m_ullNAKInt = m_ullMinNakInt; + + break; + } + + case UMSG_CGWARNING: //100 - Congestion Warning + ctrlpkt.pack(pkttype); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + CTimer::rdtsc(m_ullLastWarningTime); + + break; + + case UMSG_KEEPALIVE: //001 - Keep-alive + ctrlpkt.pack(pkttype); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + break; + + case UMSG_HANDSHAKE: //000 - Handshake + ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake)); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + break; + + case UMSG_SHUTDOWN: //101 - Shutdown + ctrlpkt.pack(pkttype); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + break; + + case UMSG_DROPREQ: //111 - Msg drop request + ctrlpkt.pack(pkttype, lparam, rparam, 8); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + break; + + case UMSG_PEERERROR: //1000 - acknowledge the peer side a special error + ctrlpkt.pack(pkttype, lparam); + ctrlpkt.m_iID = m_PeerID; + nbsent = m_pSndQueue->sendto(m_pPeerAddr, ctrlpkt); + + break; + + case UMSG_EXT: //0x7FFF - Resevered for future use + break; + + default: + break; + } +#ifdef SRT_FIX_KEEPALIVE + if (nbsent) + m_ullLastSndTime = currtime; +#endif +} + +void CUDT::processCtrl(CPacket& ctrlpkt) +{ + // Just heard from the peer, reset the expiration count. + m_iEXPCount = 1; + uint64_t currtime; + CTimer::rdtsc(currtime); + m_ullLastRspTime = currtime; + bool using_rexmit_flag = m_bPeerRexmitFlag; + + LOGC(mglog.Debug) << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " (" + << MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") SID=" << ctrlpkt.m_iID; + + switch (ctrlpkt.getType()) + { + case UMSG_ACK: //010 - Acknowledgement + { + int32_t ack; + int32_t* ackdata = (int32_t*)ctrlpkt.m_pcData; + + // process a lite ACK + if (ctrlpkt.getLength() == SEND_LITE_ACK) + { + ack = *ackdata; + if (CSeqNo::seqcmp(ack, m_iSndLastAck) >= 0) + { + m_iFlowWindowSize -= CSeqNo::seqoff(m_iSndLastAck, ack); + LOGC(mglog.Debug) << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ack << " [ACK=" << m_iSndLastAck << "] (FLW: " << m_iFlowWindowSize << ") [LITE]"; + + m_iSndLastAck = ack; +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; // Reset re-transmit count since last ACK +#endif /* SRT_ENABLE_FASTREXMIT */ + } + + break; + } + + // read ACK seq. no. + ack = ctrlpkt.getAckSeqNo(); + + // 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)) + { + sendCtrl(UMSG_ACKACK, &ack); + m_iSndLastAck2 = ack; + m_ullSndLastAck2Time = now; + } + + // Got data ACK + ack = ackdata[ACKD_RCVLASTACK]; + +#ifdef SRT_ENABLE_TLPKTDROP + // protect packet retransmission + CGuard::enterCS(m_AckLock); + + // check the validation of the ack + int seqdiff = CSeqNo::seqcmp(ack, CSeqNo::incseq(m_iSndCurrSeqNo)); + if (seqdiff> 0) + { + CGuard::leaveCS(m_AckLock); + //this should not happen: attack or bug + LOGC(glog.Error) << CONID() << "ATTACK/ISE: incoming ack seq " << ack << " exceeds current " << m_iSndCurrSeqNo << " by " << seqdiff << "!"; + m_bBroken = true; + m_iBrokenCounter = 0; + break; + } + + if (CSeqNo::seqcmp(ack, m_iSndLastAck) >= 0) + { + // Update Flow Window Size, must update before and together with m_iSndLastAck + m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ack; +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; // Reset re-transmit count since last ACK +#endif /* SRT_ENABLE_FASTREXMIT */ + } + + /* + * We must not ignore full ack received by peer + * if data has been artificially acked by late packet drop. + * Therefore, a distinct ack state is used for received Ack (iSndLastFullAck) + * and ack position in send buffer (m_iSndLastDataAck). + * Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update) + * occures, we drop received acks (as duplicates) and do not update stats like RTT, + * which may go crazy and stay there, preventing proper stream recovery. + */ + + if (CSeqNo::seqoff(m_iSndLastFullAck, ack) <= 0) + { + // discard it if it is a repeated ACK + CGuard::leaveCS(m_AckLock); + break; + } + m_iSndLastFullAck = ack; + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, ack); + // IF distance between m_iSndLastDataAck and ack is nonempty... + if (offset > 0) { + // acknowledge the sending buffer (remove data that predate 'ack') + m_pSndBuffer->ackData(offset); + + // record total time used for sending + m_llSndDuration += currtime - m_llSndDurationCounter; + m_llSndDurationTotal += currtime - m_llSndDurationCounter; + m_llSndDurationCounter = currtime; + + LOGC(mglog.Debug) << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ack << " [ACK=" << m_iSndLastAck << "] BUFr=" << m_iFlowWindowSize + << " RTT=" << ackdata[ACKD_RTT] << " RTT*=" << ackdata[ACKD_RTTVAR] + << " BW=" << ackdata[ACKD_BANDWIDTH] << " Vrec=" << ackdata[ACKD_RCVSPEED]; + // update sending variables + m_iSndLastDataAck = ack; + + // remove any loss that predates 'ack' (not to be considered loss anymore) + m_pSndLossList->remove(CSeqNo::decseq(m_iSndLastDataAck)); + } + +#else /* SRT_ENABLE_TLPKTDROP */ + + // check the validation of the ack + if (CSeqNo::seqcmp(ack, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0) + { + //this should not happen: attack or bug + m_bBroken = true; + m_iBrokenCounter = 0; + break; + } + + if (CSeqNo::seqcmp(ack, m_iSndLastAck) >= 0) + { + // Update Flow Window Size, must update before and together with m_iSndLastAck + m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT]; + m_iSndLastAck = ack; +#ifdef SRT_ENABLE_FASTREXMIT + m_ullLastRspAckTime = currtime; + m_iReXmitCount = 1; // Reset re-transmit count since last ACK +#endif /* SRT_ENABLE_FASTREXMIT */ + } + + // protect packet retransmission + CGuard::enterCS(m_AckLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, ack); + if (offset <= 0) + { + // discard it if it is a repeated ACK + CGuard::leaveCS(m_AckLock); + break; + } + + // acknowledge the sending buffer + m_pSndBuffer->ackData(offset); + + // record total time used for sending + m_llSndDuration += currtime - m_llSndDurationCounter; + m_llSndDurationTotal += currtime - m_llSndDurationCounter; + m_llSndDurationCounter = currtime; + + // update sending variables + m_iSndLastDataAck = ack; + m_pSndLossList->remove(CSeqNo::decseq(m_iSndLastDataAck)); + +#endif /* SRT_ENABLE_TLPKTDROP */ + + CGuard::leaveCS(m_AckLock); + + pthread_mutex_lock(&m_SendBlockLock); + if (m_bSynSending) + pthread_cond_signal(&m_SendBlockCond); + pthread_mutex_unlock(&m_SendBlockLock); + + // acknowledde any waiting epolls to write + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_OUT, true); + + // insert this socket to snd list if it is not on the list yet + m_pSndQueue->m_pSndUList->update(this, false); + + size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING + bool wrongsize = acksize % ACKD_FIELD_SIZE; + acksize = acksize / ACKD_FIELD_SIZE; // ACTUAL VALUE + + if ( wrongsize ) + { + // Issue a log, but don't do anything but skipping the "odd" bytes from the payload. + LOGC(mglog.Error) << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to " << acksize << " fields"; + } + + // Start with checking the base size. + if ( acksize < ACKD_TOTAL_SIZE_UDTBASE ) + { + LOGC(mglog.Error) << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!"; + // Ack is already interpreted, just skip further parts. + break; + } + // This check covers fields up to ACKD_BUFFERLEFT. + + // Update RTT + //m_iRTT = ackdata[ACKD_RTT]; + //m_iRTTVar = ackdata[ACKD_RTTVAR]; + int rtt = ackdata[ACKD_RTT]; + 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): + * ACKD_RCVLASTACK + * ACKD_RTT + * ACKD_RTTVAR + * ACKD_BUFFERLEFT + * SRT extension version 1.0.0: + * ACKD_RCVSPEED + * ACKD_BANDWIDTH + * SRT extension version 1.0.2 (bstats): + * ACKD_RCVRATE + * SRT extension version 1.0.4: + * 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 */ + /* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */ + int bytesps = ackdata[ACKD_RCVRATE]; + + if (bytesps > 0) + m_iDeliveryRate = (m_iDeliveryRate * 7 + bytesps) >> 3; + + if (ackdata[ACKD_BANDWIDTH] > 0) + 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 if (acksize > ACKD_TOTAL_SIZE_UDTBASE) // This embraces range (...UDTBASE - ...VER100) + { + // Peer provides only pkts/sec stats, convert to bits/sec for DeliveryRate + int pktps; + + if ((pktps = ackdata[ACKD_RCVSPEED]) > 0) + m_iDeliveryRate = (m_iDeliveryRate * 7 + (pktps * m_iPayloadSize)) >> 3; + + if (ackdata[ACKD_BANDWIDTH] > 0) + 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); + } +#endif /* SRT_ENABLE_BSTATS */ + + m_pCC->onACK(ack); + CCUpdate(); + + ++ m_iRecvACK; + ++ m_iRecvACKTotal; + + break; + } + + case UMSG_ACKACK: //110 - Acknowledgement of Acknowledgement + { + int32_t ack; + int rtt = -1; + + // update RTT + rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack); + if (rtt <= 0) + break; + + //if increasing delay detected... + // sendCtrl(4); + + // RTT EWMA + 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) + m_iRcvLastAckAck = ack; + + break; + } + + case UMSG_LOSSREPORT: //011 - Loss Report + { + int32_t* losslist = (int32_t *)(ctrlpkt.m_pcData); + + m_pCC->onLoss(losslist, ctrlpkt.getLength() / 4); + CCUpdate(); + + bool secure = true; + +#ifdef SRT_ENABLE_TLPKTDROP + // protect packet retransmission + CGuard::enterCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + + + // decode loss list message and insert loss into the sender loss list + for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++ i) + { + if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST)) + { + // Then it's this is a specification with HI in a consecutive cell. + int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]); + int32_t losslist_hi = losslist[i+1]; + // specification means that the consecutive cell has been already interpreted. + ++ i; + + LOGC(mglog.Debug).form("received UMSG_LOSSREPORT: %d-%d (%d packets)...", losslist_lo, losslist_hi, CSeqNo::seqcmp(losslist_hi, losslist_lo)+1); + + if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) || (CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0)) + { + // seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq + secure = false; + // XXX leaveCS: really necessary? 'break' will break the 'for' loop, not the 'switch' statement. + // and the leaveCS is done again next to the 'for' loop end. +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + break; + } + + int num = 0; + if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0) + num = m_pSndLossList->insert(losslist_lo, losslist_hi); + else if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0) + { + // This should be theoretically impossible because this would mean + // that the received packet loss report informs about the loss that predates + // the ACK sequence. + // However, this can happen if the packet reordering has caused the earlier sent + // LOSSREPORT will be delivered after later sent ACK. Whatever, ACK should be + // more important, so simply drop the part that predates ACK. + num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi); + } + + m_iTraceSndLoss += num; + m_iSndLossTotal += num; + + } + else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0) + { + LOGC(mglog.Debug).form("received UMSG_LOSSREPORT: %d (1 packet)...", losslist[i]); + + if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0) + { + //seq_a must not be greater than the most recent sent seq + secure = false; +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + break; + } + + int num = m_pSndLossList->insert(losslist[i], losslist[i]); + + m_iTraceSndLoss += num; + m_iSndLossTotal += num; + } + } +#ifdef SRT_ENABLE_TLPKTDROP + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + + if (!secure) + { + LOGC(mglog.Debug).form("WARNING: out-of-band LOSSREPORT received; considered bug or attack"); + //this should not happen: attack or bug + m_bBroken = true; + m_iBrokenCounter = 0; + break; + } + + // the lost packet (retransmission) should be sent out immediately + m_pSndQueue->m_pSndUList->update(this); + + ++ m_iRecvNAK; + ++ m_iRecvNAKTotal; + + break; + } + + case UMSG_CGWARNING: //100 - Delay Warning + // One way packet delay is increasing, so decrease the sending rate + m_ullInterval = (uint64_t)ceil(m_ullInterval * 1.125); + m_iLastDecSeq = m_iSndCurrSeqNo; + + break; + + case UMSG_KEEPALIVE: //001 - Keep-alive + // The only purpose of keep-alive packet is to tell that the peer is still alive + // nothing needs to be done. + + break; + + case UMSG_HANDSHAKE: //000 - Handshake + { + CHandShake req; + req.deserialize(ctrlpkt.m_pcData, ctrlpkt.getLength()); + 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 + + CHandShake initdata; + initdata.m_iISN = m_iISN; + initdata.m_iMSS = m_iMSS; + initdata.m_iFlightFlagSize = m_iFlightFlagSize; + 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; + } + + break; + } + + case UMSG_SHUTDOWN: //101 - Shutdown + m_bShutdown = true; + m_bClosing = true; + m_bBroken = true; + m_iBrokenCounter = 60; + + // Signal the sender and recver if they are waiting for data. + releaseSynch(); + // Unblock any call so they learn the connection_broken error + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_ERR, true); + + CTimer::triggerEvent(); + + break; + + case UMSG_DROPREQ: //111 - Msg drop request +#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)); + + // move forward with current recv seq no. + if ((CSeqNo::seqcmp(*(int32_t*)ctrlpkt.m_pcData, CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0) + && (CSeqNo::seqcmp(*(int32_t*)(ctrlpkt.m_pcData + 4), m_iRcvCurrSeqNo) > 0)) + { + m_iRcvCurrSeqNo = *(int32_t*)(ctrlpkt.m_pcData + 4); + } + + break; + + case UMSG_PEERERROR: // 1000 - An error has happened to the peer side + //int err_type = packet.getAddInfo(); + + // currently only this error is signalled from the peer side + // if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately + // giving the app a chance to fix the issue + + m_bPeerHealth = false; + + break; + + case UMSG_EXT: //0x7FFF - reserved and user defined messages + LOGC(mglog.Debug).form("CONTROL EXT MSG RECEIVED: %08X\n", ctrlpkt.getExtendedType()); +#if SRT_ENABLE_SND2WAYPROTECT + if (((ctrlpkt.getExtendedType() == SRT_CMD_HSREQ) || (ctrlpkt.getExtendedType() == SRT_CMD_KMREQ)) + && (m_bDataSender)) + { + /* + * SRT 1.1.2 and earlier sender can assert if accepting HSREQ or KMREQ. + * Drop connection. + */ + LOGC(mglog.Error).form("Error: receiving %s control message in SRT sender-only side: %s.", + ctrlpkt.getExtendedType() == SRT_CMD_HSREQ ? "HSREQ" : "KMREQ", "breaking connection"); + m_bBroken = true; + m_iBrokenCounter = 0; + 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(); + + 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(); + + if (m_bTsbPdSnd) + { + /* We are TsbPd sender */ + m_SndTsbPdDelay = m_pSRTCC->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); +#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); +#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 */ + + break; + + default: + break; + } +} + +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; + + uint64_t entertime; + CTimer::rdtsc(entertime); + +#if 0//debug: TimeDiff histogram + static int lldiffhisto[23] = {0}; + static int llnodiff = 0; + if (m_ullTargetTime != 0) + { + int ofs = 11 + ((entertime - m_ullTargetTime)/(int64_t)m_ullCPUFrequency)/1000; + if (ofs < 0) ofs = 0; + else if (ofs > 22) ofs = 22; + lldiffhisto[ofs]++; + } + else if(m_ullTargetTime == 0) + { + llnodiff++; + } + static int callcnt = 0; + if (!(callcnt++ % 5000)) { + fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n", + lldiffhisto[0],lldiffhisto[1],lldiffhisto[2],lldiffhisto[3],lldiffhisto[4],lldiffhisto[5], + lldiffhisto[6],lldiffhisto[7],lldiffhisto[8],lldiffhisto[9],lldiffhisto[10],lldiffhisto[11]); + fprintf(stderr, "%6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d %6d\n", + lldiffhisto[12],lldiffhisto[13],lldiffhisto[14],lldiffhisto[15],lldiffhisto[16],lldiffhisto[17], + lldiffhisto[18],lldiffhisto[19],lldiffhisto[20],lldiffhisto[21],lldiffhisto[21],llnodiff); + } +#endif + if ((0 != m_ullTargetTime) && (entertime > m_ullTargetTime)) + m_ullTimeDiff += entertime - m_ullTargetTime; + + string reason; + + // Loss retransmission always has higher priority. + packet.m_iSeqNo = m_pSndLossList->getLostSeq(); + if (packet.m_iSeqNo >= 0) + { + // protect m_iSndLastDataAck from updating by ACK processing + CGuard ackguard(m_AckLock); + + int offset = CSeqNo::seqoff(m_iSndLastDataAck, packet.m_iSeqNo); + if (offset < 0) + return 0; + + 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) + { + int32_t seqpair[2]; + seqpair[0] = packet.m_iSeqNo; + seqpair[1] = CSeqNo::incseq(seqpair[0], msglen); + sendCtrl(UMSG_DROPREQ, &packet.m_iMsgNo, seqpair, 8); + + // only one msg drop request is necessary + m_pSndLossList->remove(seqpair[1]); + + // skip all dropped packets + if (CSeqNo::seqcmp(m_iSndCurrSeqNo, CSeqNo::incseq(seqpair[1])) < 0) + m_iSndCurrSeqNo = CSeqNo::incseq(seqpair[1]); + + return 0; + } + // NOTE: This is just a sanity check. Returning 0 is impossible to happen + // in case of retransmission. If the offset was a positive value, then the + // block must exist in the old blocks because it wasn't yet cut off by ACK + // and has been already recorded as sent (otherwise the peer wouldn't send + // back the loss report). May something happen here in case when the send + // loss record has been updated by the FASTREXMIT. + else if (payload == 0) + return 0; + + + ++ m_iTraceRetrans; + ++ m_iRetransTotal; +#ifdef SRT_ENABLE_BSTATS + m_ullTraceBytesRetrans += payload; + m_ullBytesRetransTotal += payload; +#endif + + //* + + // Alright, gr8. Despite the contextual interpretation of packet.m_iMsgNo around + // CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular + // case we can be sure that this is exactly the value of PH_MSGNO as a bitset. + // So, set here the rexmit flag if the peer understands it. + if ( m_bPeerRexmitFlag ) + { + packet.m_iMsgNo |= PACKET_SND_REXMIT; + } + // */ + reason = "reXmit"; + } + else + { + // If no loss, pack a new packet. + + // 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))) + { + kflg = m_pSRTCC->getSndCryptoFlags(); +#ifdef SRT_ENABLE_TSBPD + 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); + + packet.m_iSeqNo = m_iSndCurrSeqNo; + + // every 16 (0xF) packets, a packet pair is sent + if ((packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0) + probe = true; + } + else + { + m_ullTargetTime = 0; + m_ullTimeDiff = 0; + ts = 0; + return 0; + } + } + 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))); + m_ullTargetTime = 0; + m_ullTimeDiff = 0; + ts = 0; + return 0; + } + + reason = "normal"; + } + +#ifdef SRT_ENABLE_TSBPD + if (m_bTsbPdSnd) + { + /* + * When timestamp is carried over in this sending stream from a received stream, + * it may be older than the session start time causing a negative packet time + * that may block the receiver's Timestamp-based Packet Delivery. + * XXX Isn't it then better to not decrease it by m_StartTime? As long as it + * doesn't screw up the start time on the other side. + */ + if (origintime >= m_StartTime) + packet.m_iTimeStamp = int(origintime - m_StartTime); + else + packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); + } + else +#endif /* SRT_ENABLE_TSBPD */ + + 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())) + { + /* Encryption failed */ + //>>Add stats for crypto failure + ts = 0; + return -1; //Encryption failed + } + payload = packet.getLength(); /* Cipher may change length */ + reason += " (encrypted)"; + } + +#if ENABLE_LOGGING // Required because of referring to MessageFlagStr() + LOGC(mglog.Debug) << CONID() << "packData: " << reason << " packet seq=" << packet.m_iSeqNo + << " (ACK=" << m_iSndLastAck << " ACKDATA=" << m_iSndLastDataAck + << " MSG/FLAGS: " << packet.MessageFlagStr() << ")"; +#endif + +#endif + +#ifdef SRT_FIX_KEEPALIVE + m_ullLastSndTime = entertime; +#endif /* SRT_FIX_KEEPALIVE */ + + m_pCC->onPktSent(&packet); + //m_pSndTimeWindow->onPktSent(packet.m_iTimeStamp); + +#ifdef SRT_ENABLE_BSTATS + m_ullTraceBytesSent += payload; + m_ullBytesSentTotal += payload; +#endif + ++ m_llTraceSent; + ++ m_llSentTotal; + + if (probe) + { + // sends out probing packet pair + ts = entertime; + probe = false; + } + else + { + #ifndef NO_BUSY_WAITING + ts = entertime + m_ullInterval; + #else + if (m_ullTimeDiff >= m_ullInterval) + { + ts = entertime; + m_ullTimeDiff -= m_ullInterval; + } + else + { + ts = entertime + m_ullInterval - m_ullTimeDiff; + m_ullTimeDiff = 0; + } + #endif + } + + m_ullTargetTime = ts; + + return payload; +} + +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 */ + // 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())) + { + LOGP(mglog.Debug, "Spawning TSBPD thread"); + int st = 0; + { + ThreadName tn("SRT:TsbPd"); + st = pthread_create(&m_RcvTsbPdThread, NULL, CUDT::tsbpd, this); + } + if ( st != 0 ) + return -1; + } +#endif /* SRT_ENABLE_TSBPD */ + + int pktrexmitflag = m_bPeerRexmitFlag ? (int)packet.getRexmitFlag() : 2; + static const string rexmitstat [] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"}; + string rexmit_reason; + + + if ( pktrexmitflag == 1 ) // rexmitted + { + m_iTraceRcvRetrans++; + +#if ENABLE_LOGGING + // Check if packet was retransmitted on request or on ack timeout + // Search the sequence in the loss record. + rexmit_reason = " by "; + if ( !m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo) ) + //if ( m_DebugLossRecords.find(packet.m_iSeqNo) ) // m_DebugLossRecords not turned on + rexmit_reason += "REQUEST"; + else + { + rexmit_reason += "ACK-TMOUT"; + /* + if ( !m_DebugLossRecords.exists(packet.m_iSeqNo) ) + { + rexmit_reason += "(seems/"; + char buf[100] = "empty"; + int32_t base = m_DebugLossRecords.base(); + if ( base != -1 ) + sprintf(buf, "%d", base); + rexmit_reason += buf; + rexmit_reason += ")"; + } + */ + } +#endif + } + + + LOGC(dlog.Debug) << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength() << " seq=" << packet.getSeqNo(); + // << "(" << rexmitstat[pktrexmitflag] << rexmit_reason << ")"; + + m_pCC->onPktReceived(&packet); + ++ m_iPktCount; + + int pktsz = packet.getLength(); +#ifdef SRT_ENABLE_BSTATS + // update time information + m_RcvTimeWindow.onPktArrival(pktsz); + + // 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(pktsz); + + 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 + * Prevent TsbPd thread from modifying Ack position while adding data + * 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); +#else /* SRT_ENABLE_TLPKTDROP */ + int32_t offset = CSeqNo::seqoff(m_iRcvLastAck, packet.m_iSeqNo); +#endif /* SRT_ENABLE_TLPKTDROP */ + + bool excessive = false; + string exc_type = "EXPECTED"; + if ((offset < 0)) + { + exc_type = "BELATED"; + excessive = true; + m_iTraceRcvBelated++; + uint64_t tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp()); + uint64_t bltime = CountIIR( + uint64_t(m_fTraceBelatedTime)*1000, + CTimer::getTime() - tsbpdtime, 0.2); + m_fTraceBelatedTime = double(bltime)/1000.0; + } + else + { + + int avail_bufsize = m_pRcvBuffer->getAvailBufSize(); + if (offset >= avail_bufsize) + { + LOGC(mglog.Error) << CONID() << "No room to store incoming packet: offset=" << offset << " avail=" << avail_bufsize; + return -1; + } + + if (m_pRcvBuffer->addData(unit, offset) < 0) + { + // addData returns -1 if at the m_iLastAckPos+offset position there already is a packet. + // So this packet is "redundant". + exc_type = "UNACKED"; + excessive = true; + } + } + + LOGC(mglog.Debug) << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset + << (excessive ? " EXCESSIVE" : " ACCEPTED") + << " (" << exc_type << "/" << rexmitstat[pktrexmitflag] << rexmit_reason << ")"; + + if ( excessive ) + { + return -1; + } + + if (packet.getMsgCryptoFlags()) + { +#ifdef SRT_ENABLE_SRTCC_EMB + EncryptionStatus rc = m_pSRTCC ? packet.decrypt(m_pSRTCC->getRcvCryptoCtx()) : ENCS_NOTSUP; + if ( rc != ENCS_CLEAR ) + { + /* + * Could not decrypt + * Keep packet in received buffer + * Crypto flags are still set + * It will be acknowledged + */ + m_iTraceRcvUndecrypt += 1; + m_ullTraceRcvBytesUndecrypt += pktsz; + m_iRcvUndecryptTotal += 1; + m_ullRcvBytesUndecryptTotal += pktsz; + } +#endif /* SRT_ENABLE_SRTCC_EMB */ + } + +#ifdef SRT_ENABLE_TSBPD + } /* End of offsetcg */ + + if (m_bClosing) { + /* + * RcvQueue worker thread can call processData while closing (or close while processData) + * This race condition exists in the UDT design but the protection against TsbPd thread + * (with AckLock) and decryption enlarged the probability window. + * Application can crash deep in decrypt stack since crypto context is deleted in close. + * RcvQueue worker thread will not necessarily be deleted with this connection as it can be + * used by others (socket multiplexer). + */ + return(-1); + } +#endif /* SRT_ENABLE_TSBPD */ + +#if SRT_BELATED_LOSSREPORT + // If the peer doesn't understand REXMIT flag, send rexmit request + // always immediately. + int initial_loss_ttl = 0; + if ( m_bPeerRexmitFlag ) + initial_loss_ttl = m_iReorderTolerance; +#endif + + if (packet.getMsgCryptoFlags()) + { + /* + * Crypto flags not cleared means that decryption failed + * Do no ask loss packets retransmission + */ + ; + LOGC(mglog.Debug) << CONID() << "ERROR: packet not decrypted, dropping data."; + } + else + // 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); + +#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 +#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)); + } + + 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 */ + +#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 */ + } + +#ifdef SRT_BELATED_LOSSREPORT + // Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it. + + // PERFORMANCE CONSIDERATIONS: + // This list is quite inefficient as a data type and finding the candidate to send UMSG_LOSSREPORT + // is linear time. On the other hand, there are some special cases that are important for performance: + // - only the first (plus some following) could have had TTL drown to 0 + // - the only (little likely) possibility that the next-to-first record has TTL=0 is when there was + // a loss range split (due to unlose() of one sequence) + // - first found record with TTL>0 means end of "ready to LOSSREPORT" records + // So: + // All you have to do is: + // - start with first element and continue with next elements, as long as they have TTL=0 + // If so, send the loss report and remove this element. + // - Since the first element that has TTL>0, iterate until the end of container and decrease TTL. + // + // This will be efficient becase the loop to increment one field (without any condition check) + // can be quite well optimized. + + vector lossdata; + { + CGuard lg(m_RcvLossLock); + + // XXX There was a mysterious crash around m_FreshLoss. When the initial_loss_ttl is 0 + // (that is, "belated loss report" feature is off), don't even touch m_FreshLoss. + if ( initial_loss_ttl && !m_FreshLoss.empty() ) + { + deque::iterator i = m_FreshLoss.begin(); + + // Phase 1: take while TTL <= 0. + // There can be more than one record with the same TTL, if it has happened before + // that there was an 'unlost' (@c unlose) sequence that has split one detected loss + // into two records. + for( ; i != m_FreshLoss.end() && i->ttl <= 0; ++i ) + { + LOGC(mglog.Debug).form("Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT", + i->seq[0], i->seq[1], CSeqNo::seqcmp(i->seq[1], i->seq[0])+1); + addLossRecord(lossdata, i->seq[0], i->seq[1]); + } + + // Remove elements that have been processed and prepared for lossreport. + if ( i != m_FreshLoss.begin() ) + { + m_FreshLoss.erase(m_FreshLoss.begin(), i); + i = m_FreshLoss.begin(); + } + + if ( m_FreshLoss.empty() ) + LOGC(mglog.Debug).form("NO MORE FRESH LOSS RECORDS."); + else + LOGC(mglog.Debug).form("STILL %zu FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d", m_FreshLoss.size(), + i->seq[0], i->seq[1], 1+CSeqNo::seqcmp(i->seq[1], i->seq[0]), + i->ttl); + + // Phase 2: rest of the records should have TTL decreased. + for ( ; i != m_FreshLoss.end(); ++i ) + --i->ttl; + } + + } + if ( !lossdata.empty() ) + sendCtrl(UMSG_LOSSREPORT, NULL, lossdata.data(), lossdata.size()); +#endif + + // This is not a regular fixed size packet... + //an irregular sized packet usually indicates the end of a message, so send an ACK immediately + if (pktsz != m_iPayloadSize) +#ifdef SRT_ENABLE_LOWACKRATE + if (m_iSockType == UDT_STREAM) +#endif + CTimer::rdtsc(m_ullNextACKTime); + + // Update the current largest sequence number that has been received. + // Or it is a retransmitted packet, remove it from receiver loss list. +#if SRT_BELATED_LOSSREPORT + bool was_orderly_sent = true; +#endif + if (CSeqNo::seqcmp(packet.m_iSeqNo, m_iRcvCurrSeqNo) > 0) + { + m_iRcvCurrSeqNo = packet.m_iSeqNo; // Latest possible received + } + else + { + unlose(packet); // was BELATED or RETRANSMITTED packet. +#if SRT_BELATED_LOSSREPORT + was_orderly_sent = pktrexmitflag; +#endif + } + +#if SRT_BELATED_LOSSREPORT + // was_orderly_sent means either of: + // - packet was sent in order (first if branch above) + // - packet was sent as old, but was a retransmitted packet + + if ( m_bPeerRexmitFlag && was_orderly_sent ) + { + ++m_iConsecOrderedDelivery; + if ( m_iConsecOrderedDelivery >= 50 ) + { + m_iConsecOrderedDelivery = 0; + if ( m_iReorderTolerance > 0 ) + { + m_iReorderTolerance--; + m_iTraceReorderDistance--; + LOGC(mglog.Debug).form( "ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d", m_iReorderTolerance); + } + } + } + +#endif + + return 0; +} + +/// This function is called when a packet has arrived, which was behind the current +/// received sequence - that is, belated or retransmitted. Try to remove the packet +/// from both loss records: the general loss record and the fresh loss record. +/// +/// Additionally, check - if supported by the peer - whether the "latecoming" packet +/// has been sent due to retransmission or due to reordering, by checking the rexmit +/// support flag and rexmit flag itself. If this packet was surely ORIGINALLY SENT +/// it means that the current network connection suffers of packet reordering. This +/// way try to introduce a dynamic tolerance by calculating the difference between +/// the current packet reception sequence and this packet's sequence. This value +/// will be set to the tolerance value, which means that later packet retransmission +/// will not be required immediately, but only after receiving N next packets that +/// do not include the lacking packet. +/// The tolerance is not increased infinitely - it's bordered by m_iMaxReorderTolerance. +/// This value can be set in options - SRT_LOSSMAXTTL. +void CUDT::unlose(const CPacket& packet) +{ + CGuard lg(m_RcvLossLock); + int32_t sequence = packet.m_iSeqNo; + m_pRcvLossList->remove(sequence); + +#if SRT_BELATED_LOSSREPORT + + bool has_increased_tolerance = false; + bool was_reordered = false; + + if ( m_bPeerRexmitFlag ) + { + // If the peer understands the REXMIT flag, it means that the REXMIT flag is contained + // in the PH_MSGNO field. + + // The packet is considered coming originally (just possibly out of order), if REXMIT + // flag is NOT set. + was_reordered = !packet.getRexmitFlag(); + if ( was_reordered ) + { + LOGC(mglog.Debug).form("received out-of-band packet seq %d", sequence); + + int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo)); + m_iTraceReorderDistance = max(seqdiff, m_iTraceReorderDistance); + if ( seqdiff > m_iReorderTolerance ) + { + int prev = m_iReorderTolerance; + m_iReorderTolerance = min(seqdiff, m_iMaxReorderTolerance); + LOGC(mglog.Debug).form("Belated by %d seqs - Reorder tolerance %s %d", seqdiff, + (prev == m_iReorderTolerance) ? "REMAINS with" : "increased to", m_iReorderTolerance); + has_increased_tolerance = true; // Yes, even if reorder tolerance is already at maximum - this prevents decreasing tolerance. + } + } + else + { + LOGC(mglog.Debug) << CONID() << "received reXmitted packet seq=" << sequence; + } + } + else + { + LOGC(mglog.Debug).form("received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence); + } + + + int initial_loss_ttl = 0; + if ( m_bPeerRexmitFlag ) + initial_loss_ttl = m_iReorderTolerance; + + // Don't do anything if "belated loss report" feature is not used. + // In that case the FreshLoss list isn't being filled in at all, the + // loss report is sent directly. + + // Note that this condition blocks two things being done in this function: + // - remove given sequence from the fresh loss record + // (in this case it's empty anyway) + // - decrease current reorder tolerance based on whether packets come in order + // (current reorder tolerance is 0 anyway) + if ( !initial_loss_ttl ) + return; + + size_t i = 0; + int had_ttl = 0; + for (i = 0; i < m_FreshLoss.size(); ++i) + { + had_ttl = m_FreshLoss[i].ttl; + switch ( m_FreshLoss[i].revoke(sequence) ) + { + case CRcvFreshLoss::NONE: + continue; // Not found. Search again. + + case CRcvFreshLoss::STRIPPED: + goto breakbreak; // Found and the modification is applied. We're done here. + + case CRcvFreshLoss::DELETE: + // No more elements. Kill it. + m_FreshLoss.erase(m_FreshLoss.begin() + i); + // Every loss is unique. We're done here. + goto breakbreak; + + case CRcvFreshLoss::SPLIT: + // Oh, this will be more complicated. This means that it was in between. + { + // So create a new element that will hold the upper part of the range, + // and this one modify to be the lower part of the range. + + // Keep the current end-of-sequence value for the second element + int32_t next_end = m_FreshLoss[i].seq[1]; + + // seq-1 set to the end of this element + m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence); + // seq+1 set to the begin of the next element + int32_t next_begin = CSeqNo::incseq(sequence); + + // Use position of the NEXT element because insertion happens BEFORE pointed element. + // Use the same TTL (will stay the same in the other one). + m_FreshLoss.insert(m_FreshLoss.begin() + i + 1, CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl)); + } + goto breakbreak; + } + } + + // Could have made the "return" instruction instead of goto, but maybe there will be something + // to add in future, so keeping that. +breakbreak: ; + + if (i != m_FreshLoss.size()) + { + LOGC(mglog.Debug).form("sequence %d removed from belated lossreport record", sequence); + } + + if ( was_reordered ) + { + m_iConsecOrderedDelivery = 0; + if ( has_increased_tolerance ) + { + m_iConsecEarlyDelivery = 0; // reset counter + } + else if ( had_ttl > 2 ) + { + ++m_iConsecEarlyDelivery; // otherwise, and if it arrived quite earlier, increase counter + LOGC(mglog.Debug).form("... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery); + + // After 10 consecutive + if ( m_iConsecEarlyDelivery >= 10 ) + { + m_iConsecEarlyDelivery = 0; + if ( m_iReorderTolerance > 0 ) + { + m_iReorderTolerance--; + m_iTraceReorderDistance--; + LOGC(mglog.Debug).form("... reached %d times - decreasing tolerance to %d", m_iConsecEarlyDelivery, m_iReorderTolerance); + } + } + + } + // If hasn't increased tolerance, but the packet appeared at TTL less than 2, do nothing. + } + +#endif + +} + +void CUDT::unlose(int32_t from, int32_t to) +{ + CGuard lg(m_RcvLossLock); + m_pRcvLossList->remove(from, to); + + LOGC(mglog.Debug).form("TLPKTDROP seq %d-%d (%d packets)", from, to, CSeqNo::seqoff(from, to)); + +#if SRT_BELATED_LOSSREPORT + int initial_loss_ttl = 0; + if ( m_bPeerRexmitFlag ) + initial_loss_ttl = m_iReorderTolerance; + + if ( !initial_loss_ttl ) + return; + + // It's highly unlikely that this is waiting to send a belated UMSG_LOSSREPORT, + // so treat it rather as a sanity check. + + // It's enough to check if the first element of the list starts with a sequence older than 'to'. + // If not, just do nothing. + + size_t delete_index = 0; + for (size_t i = 0; i < m_FreshLoss.size(); ++i) + { + CRcvFreshLoss::Emod result = m_FreshLoss[i].revoke(from, to); + switch ( result ) + { + case CRcvFreshLoss::DELETE: + delete_index = i+1; // PAST THE END + continue; // There may be further ranges that are included in this one, so check on. + + case CRcvFreshLoss::NONE: + case CRcvFreshLoss::STRIPPED: + break; // THIS BREAKS ONLY 'switch', not 'for'! + + case CRcvFreshLoss::SPLIT: ; // This function never returns it. It's only a compiler shut-up. + } + + break; // Now this breaks also FOR. + } + + m_FreshLoss.erase(m_FreshLoss.begin(), m_FreshLoss.begin() + delete_index); // with delete_index == 0 will do nothing +#endif +} + +// XXX This is quite a mystery, why this function has a return value +// and what the purpose for it was. There's just one call of this +// function in the whole code and in that call the return value is +// ignored. Actually this call happens in the CRcvQueue::worker thread, +// where it makes a response for incoming UDP packet that might be +// a connection request. Should any error occur in this process, there +// is no way to "report error" that happened here. Basing on that +// these values in original UDT code were quite like the values +// for m_iReqType, they have been changed to URQ_* symbols, which +// may mean that the intent for the return value was to send this +// value back as a control packet back to the connector. +int CUDT::processConnectRequest(const sockaddr* addr, CPacket& packet) +{ + LOGC(mglog.Note).form("listen"); + if (m_bClosing){ + LOGC(mglog.Error).form("listen reject: 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"); + return int(URQ_ERROR_REJECT); + } + + if (packet.getLength() != CHandShake::m_iContentSize){ + LOGC(mglog.Error).form("listen invalid: invalif lengh %d!= %d",packet.getLength(),CHandShake::m_iContentSize); + 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 + { + unsigned char cookie[16]; + int32_t cookie_val; + }; + CMD5::compute(cookiestr.str().c_str(), cookie); + + if (hs.m_iReqType == URQ_INDUCTION) + { + // XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp + // is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies + // only the first 4 bytes. Moreover, it's dangerous on some platforms because the char + // array need not be aligned to int32_t - changed to union in a hope that using int32_t + // inside a union will enforce whole union to be aligned to int32_t. + hs.m_iCookie = cookie_val; + packet.m_iID = hs.m_iID; + int size = packet.getLength(); + hs.serialize(packet.m_pcData, size); +#ifdef SRT_ENABLE_CTRLTSTAMP + packet.m_iTimeStamp = int(CTimer::getTime() - m_StartTime); +#endif + m_pSndQueue->sendto(addr, packet); + return 0; // XXX URQ_RENDEZVOUS, oh really? + } + else + { + if (hs.m_iCookie != cookie_val) + { + timestamp --; + cookiestr << clienthost << ":" << clientport << ":" << timestamp; + CMD5::compute(cookiestr.str().c_str(), 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! + } + } + } + + int32_t id = hs.m_iID; + + // When a peer side connects in... + if ( packet.isControl(UMSG_HANDSHAKE) ) + { + 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); + } + + // 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); + } + } + } + LOGC(mglog.Note).form("listen ret: %d", hs.m_iReqType); + + return hs.m_iReqType; +} + +void CUDT::addLossRecord(std::vector& lr, int32_t lo, int32_t hi) +{ + if ( lo == hi ) + lr.push_back(lo); + else + { + lr.push_back(lo | LOSSDATA_SEQNO_RANGE_FIRST); + lr.push_back(hi); + } +} + +void CUDT::checkTimers() +{ + // update CC parameters + CCUpdate(); + //uint64_t minint = (uint64_t)(m_ullCPUFrequency * m_pSndTimeWindow->getMinPktSndInt() * 0.9); + //if (m_ullInterval < minint) + // m_ullInterval = minint; + + uint64_t currtime; + CTimer::rdtsc(currtime); + + // 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 + << " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount; +#endif + + if ((currtime > m_ullNextACKTime) || ((m_pCC->m_iACKInterval > 0) && (m_pCC->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; + else + m_ullNextACKTime = currtime + m_ullACKInt; + + m_iPktCount = 0; + m_iLightACKCount = 1; + } + else if (m_iSelfClockInterval * m_iLightACKCount <= m_iPktCount) + { + //send a "light" ACK + sendCtrl(UMSG_ACK, NULL, NULL, SEND_LITE_ACK); + ++ m_iLightACKCount; + } + +#ifdef SRT_ENABLE_NAKREPORT + /* + * Enable NAK reports for SRT. + * Retransmission based on timeout is bandwidth consuming, + * not knowing what to retransmit when the only NAK sent by receiver is lost, + * all packets past last ACK are retransmitted (SRT_ENABLE_FASTREXMIT). + */ + if ((currtime > m_ullNextNAKTime) && m_bRcvNakReport && (m_pRcvLossList->getLossLength() > 0)) + { + // NAK timer expired, and there is loss to be reported. + sendCtrl(UMSG_LOSSREPORT); + + CTimer::rdtsc(currtime); + m_ullNextNAKTime = currtime + m_ullNAKInt; + } +#else + // we are not sending back repeated NAK anymore and rely on the sender's EXP for retransmission + //if ((m_pRcvLossList->getLossLength() > 0) && (currtime > m_ullNextNAKTime)) + //{ + // // NAK timer expired, and there is loss to be reported. + // sendCtrl(UMSG_LOSSREPORT); + // + // CTimer::rdtsc(currtime); + // m_ullNextNAKTime = currtime + m_ullNAKInt; + //} +#endif + + uint64_t next_exp_time; + if (m_pCC->m_bUserDefinedRTO) + next_exp_time = m_ullLastRspTime + m_pCC->m_iRTO * m_ullCPUFrequency; + else + { + uint64_t exp_int = (m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + m_iSYNInterval) * m_ullCPUFrequency; + if (exp_int < m_iEXPCount * m_ullMinExpInt) + exp_int = m_iEXPCount * m_ullMinExpInt; + next_exp_time = m_ullLastRspTime + exp_int; + } + + 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 + if ((m_iEXPCount > 16) && (currtime - m_ullLastRspTime > 5000000 * m_ullCPUFrequency)) + { + // + // Connection is broken. + // UDT does not signal any information about this instead of to stop quietly. + // Application will detect this when it calls any UDT methods next time. + // + LOGC(mglog.Debug).form("connection expired after: %llu", (unsigned long long)(currtime - m_ullLastRspTime)/m_ullCPUFrequency); + m_bClosing = true; + m_bBroken = true; + m_iBrokenCounter = 30; + + // update snd U list to remove this socket + m_pSndQueue->m_pSndUList->update(this); + + releaseSynch(); + + // app can call any UDT API to learn the connection_broken error + s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR, true); + + CTimer::triggerEvent(); + + return; + } + + // sender: Insert all the packets sent after last received acknowledgement into the sender loss list. + // recver: Send out a keep-alive packet + if (m_pSndBuffer->getCurrBufSize() > 0) + { +#ifdef SRT_ENABLE_FASTREXMIT + /* + * Do nothing here, UDT retransmits unacknowledged packet only when nothing in the loss list. + * This does not work well for real-time data that is delayed too much. + * See fast retransmit handling later in function + */ + ; +#else /* SRT_ENABLE_FASTREXMIT */ +#ifdef SRT_ENABLE_TLPKTDROP + // protect packet retransmission + CGuard::enterCS(m_AckLock); +#endif + + // FASTREXMIT works only under the following conditions: + // - the "ACK window" is nonempty (there are some packets sent, but not ACK-ed) + // - the sender loss list is empty (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track) + // Otherwise the rexmit will be done EXCLUSIVELY basing on the received LOSSREPORTs. + if ((CSeqNo::incseq(m_iSndCurrSeqNo) != m_iSndLastAck) && (m_pSndLossList->getLossLength() == 0)) + { + // resend all unacknowledged packets on timeout, but only if there is no packet in the loss list + int32_t csn = m_iSndCurrSeqNo; + int num = m_pSndLossList->insert(m_iSndLastAck, csn); + if (num > 0) { +// HAIVISION KULABYTE MODIFIED - MARC + m_iTraceSndLoss += 1; // num; + m_iSndLossTotal += 1; // num; +// HAIVISION KULABYTE MODIFIED - MARC + + LOGC(mglog.Debug) << CONID() << "ENFORCED reXmit by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn + << " (" << CSeqNo::seqcmp(csn, m_iSndLastAck) << " packets)"; + } + } +#ifdef SRT_ENABLE_TLPKTDROP + // protect packet retransmission + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + + m_pCC->onTimeout(); + CCUpdate(); + + // immediately restart transmission + m_pSndQueue->m_pSndUList->update(this); +#endif /* SRT_ENABLE_FASTREXMIT */ + } + else + { +#if !defined(SRT_FIX_KEEPALIVE) + sendCtrl(UMSG_KEEPALIVE); +#endif + LOGC(mglog.Debug) << CONID() << "(FIX) NOT SENDING KEEPALIVE"; + } + ++ m_iEXPCount; +#if !defined(SRT_FIX_KEEPALIVE) + /* + * duB: + * It seems there is confusion of the direction of the Response here. + * LastRspTime is supposed to be when receiving (data/ctrl) from peer + * as shown in processCtrl and processData, + * Here we set because we sent something? + * + * Disabling this code that prevent quick reconnection when peer disappear + */ + // Reset last response time since we just sent a heart-beat. + m_ullLastRspTime = currtime; +#endif + } +#ifdef SRT_ENABLE_FASTREXMIT + // sender: Insert some packets sent after last received acknowledgement into the sender loss list. + // This handles retransmission on timeout for lost NAK for peer sending only one NAK when loss detected. + // Not required if peer send Periodic NAK Reports. + if ((1) +#ifdef SRT_ENABLE_NAKREPORT + && !m_bSndPeerNakReport +#endif + && m_pSndBuffer->getCurrBufSize() > 0) + { + uint64_t exp_int = (m_iReXmitCount * (m_iRTT + 4 * m_iRTTVar + 2 * m_iSYNInterval) + m_iSYNInterval) * m_ullCPUFrequency; + + if (currtime > (m_ullLastRspAckTime + exp_int)) + { +#ifdef SRT_ENABLE_TLPKTDROP + // protect packet retransmission + CGuard::enterCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + if ((CSeqNo::seqoff(m_iSndLastAck, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0)) + { + // resend all unacknowledged packets on timeout + int32_t csn = m_iSndCurrSeqNo; + int num = m_pSndLossList->insert(m_iSndLastAck, csn); +#if ENABLE_LOGGING + LOGC(mglog.Debug) << CONID() << "ENFORCED reXmit by ACK-TMOUT PREPARED: " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn + << " (" << CSeqNo::seqcmp(csn, m_iSndLastAck) << " packets)"; + + LOGC(mglog.Debug).form( "timeout lost: pkts=%d rtt+4*var=%d cnt=%d diff=%llu", num, + m_iRTT + 4 * m_iRTTVar, m_iReXmitCount, (unsigned long long)(currtime - (m_ullLastRspAckTime + exp_int))); +#endif + if (num > 0) { +// HAIVISION KULABYTE MODIFIED - MARC + m_iTraceSndLoss += 1; // num; + m_iSndLossTotal += 1; // num; +// HAIVISION KULABYTE MODIFIED - MARC + } + } +#ifdef SRT_ENABLE_TLPKTDROP + // protect packet retransmission + CGuard::leaveCS(m_AckLock); +#endif /* SRT_ENABLE_TLPKTDROP */ + + ++m_iReXmitCount; + + m_pCC->onTimeout(); + CCUpdate(); + + // immediately restart transmission + m_pSndQueue->m_pSndUList->update(this); + } + } +#endif /* SRT_ENABLE_FASTREXMIT */ + +#ifdef SRT_FIX_KEEPALIVE +// uint64_t exp_int = (m_iRTT + 4 * m_iRTTVar + m_iSYNInterval) * m_ullCPUFrequency; + if (currtime > m_ullLastSndTime + (1000000 * m_ullCPUFrequency)) + { + sendCtrl(UMSG_KEEPALIVE); + LOGP(mglog.Debug, "KEEPALIVE"); + } +#endif /* SRT_FIX_KEEPALIVE */ +} + +void CUDT::addEPoll(const int eid) +{ + CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock); + m_sPollID.insert(eid); + CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); + + if (!m_bConnected || m_bBroken || m_bClosing) + return; + +#ifdef SRT_ENABLE_TSBPD + 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 */ + 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); + } +} + +void CUDT::removeEPoll(const int eid) +{ + // clear IO events notifications; + // since this happens after the epoll ID has been removed, they cannot be set again + set remove; + remove.insert(eid); + s_UDTUnited.m_EPoll.update_events(m_SocketID, remove, UDT_EPOLL_IN | UDT_EPOLL_OUT, false); + + CGuard::enterCS(s_UDTUnited.m_EPoll.m_EPollLock); + m_sPollID.erase(eid); + CGuard::leaveCS(s_UDTUnited.m_EPoll.m_EPollLock); +} diff --git a/srtcore/core.h b/srtcore/core.h new file mode 100644 index 000000000..46774857b --- /dev/null +++ b/srtcore/core.h @@ -0,0 +1,664 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 02/28/2012 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#define SRT_BELATED_LOSSREPORT 1 + + +#ifndef __UDT_CORE_H__ +#define __UDT_CORE_H__ + +#include +#include + +#include "udt.h" +#include "common.h" +#include "list.h" +#include "buffer.h" +#include "window.h" +#include "packet.h" +#include "channel.h" +#include "api.h" +#include "ccc.h" +#include "cache.h" +#include "queue.h" +#include "utilities.h" + +#include + +extern logging::Logger + glog, + blog, + mglog, + dlog, + tslog, + rxlog; + + + + + + +template +inline T CountIIR(T base, T newval, double factor) +{ + if ( base == 0.0 ) + return newval; + + T diff = newval - base; + return base+(diff*factor); +} + +// XXX Probably a better rework for that can be done - this can be +// turned into a serializable structure, just like it's for CHandShake. +enum AckDataItem +{ + ACKD_RCVLASTACK = 0, + ACKD_RTT = 1, + ACKD_RTTVAR = 2, + ACKD_BUFFERLEFT = 3, + ACKD_TOTAL_SIZE_UDTBASE = 4, + + // 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 + ACKD_XMRATE = 7, // XXX This is a weird compat stuff. Version 1.1.3 defines it as ACKD_BANDWIDTH*m_iPayloadSize when set. Never got. + // XXX NOTE: field number 7 may be used for something in future, need to confirm destruction of all !compat 1.0.2 version + + 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); + +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 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; + +private: // constructor and desctructor + + 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); +#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); +#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); +#endif + static UDTSTATUS getsockstate(UDTSOCKET u); + +public: // internal API + static CUDT* getUDTHandle(UDTSOCKET u); + static std::vector existingSockets(); + +private: + /// 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 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 + + int processConnectResponse(const CPacket& pkt) ATR_NOEXCEPT; + + void processRendezvous(const CPacket& pkt) ATR_NOEXCEPT + { + CGuard cg(m_ConnectionLock); // FIX + processConnectResponse(pkt); + } + + /// 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 acceptAndRespond(const sockaddr* peer, CHandShake* hs); + + /// Close the opened UDT entity. + + void close(); + + /// 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. + + 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); +#else + 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. + +#ifdef SRT_ENABLE_SRCTIMESTAMP + int recvmsg(char* data, int len, uint64_t& srctime); +#endif + 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. + + 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. + + 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". + + 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". + + 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. + + void sample(CPerfMon* perf, bool clear = true); +#ifdef SRT_ENABLE_BSTATS + void bstats(CBytePerfMon* perf, bool clear = true); +#endif + + /// 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 + +public: + static const UDTSOCKET INVALID_SOCK; // invalid socket descriptor + static const int ERROR; // socket api error returned value + + std::string CONID() const + { +#if ENABLE_LOGGING + std::ostringstream os; + os << "%" << m_SocketID << ":"; + return os.str(); +#else + return ""; +#endif + } + +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 + +private: // Packet sizes + 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 +#ifdef SRT_ENABLE_CONNTIMEO + 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) +#ifdef SRT_ENABLE_IPOPTS + 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 +#ifdef SRT_ENABLE_TLPKTDROP + 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 +#ifdef SRT_ENABLE_NAKREPORT + 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 + +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 + +private: // Sending related data + 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 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 +#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 +#ifdef SRT_ENABLE_CBRTIMESTAMP + int64_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; +#ifdef SRT_ENABLE_TLPKTDROP + bool m_bTLPktDropSnd; // 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 +#endif /* SRT_ENABLE_NAKREPORT */ +#ifdef SRT_ENABLE_FASTREXMIT + int32_t m_iReXmitCount; // Re-Transmit Count since last ACK +#endif /* SRT_ENABLE_FASTREXMIT */ + + void CCUpdate(); + +private: // Receiving related data + 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_ACKWindow; //< ACK history window + CPktTimeWindow<16, 64> m_RcvTimeWindow; //< Packet arrival time window + + int32_t m_iRcvLastAck; //< Last sent ACK +#ifdef ENABLE_LOGGING + int32_t m_iDebugPrevLastAck; +#endif +#ifdef SRT_ENABLE_TLPKTDROP + 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_ullLastWarningTime; // Last time that a warning message is sent + + int32_t m_iPeerISN; // Initial Sequence Number of the peer side + +#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 */ + +private: // synchronization: mutexes and conditions + 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_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_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) + + void initSynch(); + void destroySynch(); + void releaseSynch(); + +private: // Common connection Congestion Control setup + void setupCC(void); + +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); + +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 +#ifdef SRT_ENABLE_TLPKTDROP + 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 +#ifdef SRT_ENABLE_TLPKTDROP + 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 +#ifdef SRT_ENABLE_TLPKTDROP + 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 +#ifdef SRT_ENABLE_TLPKTDROP + 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 + +private: // Timers + uint64_t m_ullCPUFrequency; // CPU clock frequency, used for Timer, ticks per microsecond + + 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; + + 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 +#endif /* SRT_ENABLE_FASTREXMIT */ +#ifdef SRT_FIX_KEEPALIVE + 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 + + int m_iPktCount; // packet counter for ACK + int m_iLightACKCount; // light ACK counter + + uint64_t m_ullTargetTime; // scheduled time of next packet sending + + 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 + +private: // for epoll + std::set m_sPollID; // set of epoll ID to trigger + void addEPoll(const int eid); + void removeEPoll(const int eid); +}; + + +#endif diff --git a/srtcore/csrtcc.cpp b/srtcore/csrtcc.cpp new file mode 100644 index 000000000..92abec29e --- /dev/null +++ b/srtcore/csrtcc.cpp @@ -0,0 +1,914 @@ +/***************************************************************************** + * 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)); + + switch(SRT_VERSION_MAJ(m_PeerSrtVersion)) { +#ifdef SRT_VERSION_MAJ2 /* Whenever versions >= 2.0.0 implemented */ + case SRT_VERSION_MAJ1: /* Still supported older versions */ + //>>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 +#endif + case SRT_VERSION_UNK: /* Unknown peer version: first attempt */ + default: + /* 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; + } + break; + + case SRT_CMD_HSRSP: + memset(srtdata, 0, sizeof(srtdata)); + + switch(SRT_VERSION_MAJ(m_PeerSrtVersion)) { +#ifdef SRT_VERSION_MAJ2 /* Whenever versions >= 2.0.0 implemented */ + case SRT_VERSION_MAJ1: /* Still supported older versions */ + //>>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 1.x.x compatible version + // move 1.x.x handshake code here when default becomes 2.x.x + //break; //>>fall through until 2.x.x implemented +#endif /* SRT_VERSION_MAJ2 */ + default: + /* 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 = 3; + + 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; + } + 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 = 20; //msec + + m_dCWndSize = 1000; + + //Data + m_bRcvTsbPdMode = false; +#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 new file mode 100644 index 000000000..b5cf18cbd --- /dev/null +++ b/srtcore/csrtcc.h @@ -0,0 +1,336 @@ +/***************************************************************************** + * 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 + +#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; + 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; + + char buf[20]; + sprintf(buf, "%d.%d.%d", major, minor, patch); + return buf; +} + +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 new file mode 100644 index 000000000..2f0b1a09e --- /dev/null +++ b/srtcore/epoll.cpp @@ -0,0 +1,599 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/01/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifdef LINUX + #include + #include +#endif +#if __APPLE__ + #include "TargetConditionals.h" +#endif +#if defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + #include + #include + #include + #include +#endif +#if defined(__ANDROID__) || defined(ANDROID) + #include +#endif +#include +#include +#include +#include + +#include "common.h" +#include "epoll.h" +#include "udt.h" + +using namespace std; + +CEPoll::CEPoll(): +m_iIDSeed(0) +{ + CGuard::createMutex(m_EPollLock); +} + +CEPoll::~CEPoll() +{ + CGuard::releaseMutex(m_EPollLock); +} + +int CEPoll::create() +{ + CGuard pg(m_EPollLock); + + int localid = 0; + + #ifdef LINUX + localid = epoll_create(1024); + /* Possible reasons of -1 error: +EMFILE: The per-user limit on the number of epoll instances imposed by /proc/sys/fs/epoll/max_user_instances was encountered. +ENFILE: The system limit on the total number of open files has been reached. +ENOMEM: There was insufficient memory to create the kernel object. + */ + if (localid < 0) + throw CUDTException(MJ_SETUP, MN_NONE, errno); + #elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + localid = kqueue(); + if (localid < 0) + throw CUDTException(MJ_SETUP, MN_NONE, errno); + #else + // on Solaris, use /dev/poll + // on Windows, select + #endif + + if (++ m_iIDSeed >= 0x7FFFFFFF) + m_iIDSeed = 0; + + CEPollDesc desc; + desc.m_iID = m_iIDSeed; + desc.m_iLocalID = localid; + m_mPolls[desc.m_iID] = desc; + + return desc.m_iID; +} + +int CEPoll::add_usock(const int eid, const UDTSOCKET& u, const int* events) +{ + CGuard pg(m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + + if (!events || (*events & UDT_EPOLL_IN)) + p->second.m_sUDTSocksIn.insert(u); + if (!events || (*events & UDT_EPOLL_OUT)) + p->second.m_sUDTSocksOut.insert(u); + // Connecting timeout not signalled without EPOLL_ERR + if (!events || (*events & UDT_EPOLL_ERR)) + p->second.m_sUDTSocksEx.insert(u); + + return 0; +} + +int CEPoll::add_ssock(const int eid, const SYSSOCKET& s, const int* events) +{ + CGuard pg(m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + +#ifdef LINUX + epoll_event ev; + memset(&ev, 0, sizeof(epoll_event)); + + if (NULL == events) + ev.events = EPOLLIN | EPOLLOUT | EPOLLERR; + else + { + ev.events = 0; + if (*events & UDT_EPOLL_IN) + ev.events |= EPOLLIN; + if (*events & UDT_EPOLL_OUT) + ev.events |= EPOLLOUT; + if (*events & UDT_EPOLL_ERR) + ev.events |= EPOLLERR; + } + + ev.data.fd = s; + if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_ADD, s, &ev) < 0) + throw CUDTException(); +#elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + struct kevent ke[2]; + int num = 0; + + if (NULL == events) + { + EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); + EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + } + else + { + if (*events & UDT_EPOLL_IN) + { + EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); + } + if (*events & UDT_EPOLL_OUT) + { + EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + } + } + if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0) + throw CUDTException(); +#else +#warning "Unsupported system for epoll. The epoll_add_ssock() API call won't work on this platform." +#endif + + p->second.m_sLocals.insert(s); + + return 0; +} + +int CEPoll::remove_usock(const int eid, const UDTSOCKET& u) +{ + CGuard pg(m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + + 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. + * Clear them. + */ + p->second.m_sUDTReads.erase(u); + p->second.m_sUDTWrites.erase(u); + p->second.m_sUDTExcepts.erase(u); +#endif /* HAI_PATCH */ + + return 0; +} + +int CEPoll::remove_ssock(const int eid, const SYSSOCKET& s) +{ + CGuard pg(m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + +#ifdef LINUX + epoll_event ev; // ev is ignored, for compatibility with old Linux kernel only. + if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_DEL, s, &ev) < 0) + throw CUDTException(); +#elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + struct kevent ke; + + // + // Since I don't know what was set before + // Just clear out both read and write + // + EV_SET(&ke, s, EVFILT_READ, EV_DELETE, 0, 0, NULL); + kevent(p->second.m_iLocalID, &ke, 1, NULL, 0, NULL); + EV_SET(&ke, s, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + kevent(p->second.m_iLocalID, &ke, 1, NULL, 0, NULL); +#endif + + p->second.m_sLocals.erase(s); + + return 0; +} +#ifdef HAI_PATCH // 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); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + + if (!events || (*events & UDT_EPOLL_IN)) + p->second.m_sUDTSocksIn.insert(u); + else + { + p->second.m_sUDTSocksIn.erase(u); + /* + * We are no longer interested in this event from this socket + * If some are up, they will unblock EPoll forever. + * Clear them. + */ + p->second.m_sUDTReads.erase(u); + } + + if (!events || (*events & UDT_EPOLL_OUT)) + p->second.m_sUDTSocksOut.insert(u); + else + { + p->second.m_sUDTSocksOut.erase(u); + p->second.m_sUDTWrites.erase(u); + } + if (!events || (*events & UDT_EPOLL_ERR)) + p->second.m_sUDTSocksEx.insert(u); + else + { + p->second.m_sUDTSocksEx.erase(u); + p->second.m_sUDTExcepts.erase(u); + } + + return 0; +} + +int CEPoll::update_ssock(const int eid, const SYSSOCKET& s, const int* events) +{ + CGuard pg(m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + +#ifdef LINUX + epoll_event ev; + memset(&ev, 0, sizeof(epoll_event)); + + if (NULL == events) + ev.events = EPOLLIN | EPOLLOUT | EPOLLERR; + else + { + ev.events = 0; + if (*events & UDT_EPOLL_IN) + ev.events |= EPOLLIN; + if (*events & UDT_EPOLL_OUT) + ev.events |= EPOLLOUT; + if (*events & UDT_EPOLL_ERR) + ev.events |= EPOLLERR; + } + + ev.data.fd = s; + if (::epoll_ctl(p->second.m_iLocalID, EPOLL_CTL_MOD, s, &ev) < 0) + throw CUDTException(); +#elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + struct kevent ke[2]; + int num = 0; + + // + // Since I don't know what was set before + // Just clear out both read and write + // + EV_SET(&ke[0], s, EVFILT_READ, EV_DELETE, 0, 0, NULL); + kevent(p->second.m_iLocalID, ke, 1, NULL, 0, NULL); + EV_SET(&ke[0], s, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); + kevent(p->second.m_iLocalID, ke, 1, NULL, 0, NULL); + if (NULL == events) + { + EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); + EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + } + else + { + if (*events & UDT_EPOLL_IN) + { + EV_SET(&ke[num++], s, EVFILT_READ, EV_ADD, 0, 0, NULL); + } + if (*events & UDT_EPOLL_OUT) + { + EV_SET(&ke[num++], s, EVFILT_WRITE, EV_ADD, 0, 0, NULL); + } + } + if (kevent(p->second.m_iLocalID, ke, num, NULL, 0, NULL) < 0) + throw CUDTException(); +#endif +// Assuming add is used if not inserted +// p->second.m_sLocals.insert(s); + + return 0; +} +#endif /* HAI_PATCH */ + +int CEPoll::wait(const int eid, set* readfds, set* writefds, int64_t msTimeOut, set* lrfds, set* lwfds) +{ + // if all fields is NULL and waiting time is infinite, then this would be a deadlock + if (!readfds && !writefds && !lrfds && lwfds && (msTimeOut < 0)) + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + + // Clear these sets in case the app forget to do it. + if (readfds) readfds->clear(); + if (writefds) writefds->clear(); + if (lrfds) lrfds->clear(); + if (lwfds) lwfds->clear(); + + int total = 0; + + int64_t entertime = CTimer::getTime(); + while (true) + { + CGuard::enterCS(m_EPollLock); + + map::iterator p = m_mPolls.find(eid); + if (p == m_mPolls.end()) + { + CGuard::leaveCS(m_EPollLock); + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + } + + if (p->second.m_sUDTSocksIn.empty() && p->second.m_sUDTSocksOut.empty() && p->second.m_sLocals.empty() && (msTimeOut < 0)) + { + // no socket is being monitored, this may be a deadlock + CGuard::leaveCS(m_EPollLock); + throw CUDTException(MJ_NOTSUP, MN_INVAL); + } + + // Sockets with exceptions are returned to both read and write sets. + if ((NULL != readfds) && (!p->second.m_sUDTReads.empty() || !p->second.m_sUDTExcepts.empty())) + { + *readfds = p->second.m_sUDTReads; + for (set::const_iterator i = p->second.m_sUDTExcepts.begin(); i != p->second.m_sUDTExcepts.end(); ++ i) + readfds->insert(*i); + total += p->second.m_sUDTReads.size() + p->second.m_sUDTExcepts.size(); + } + if ((NULL != writefds) && (!p->second.m_sUDTWrites.empty() || !p->second.m_sUDTExcepts.empty())) + { + *writefds = p->second.m_sUDTWrites; + for (set::const_iterator i = p->second.m_sUDTExcepts.begin(); i != p->second.m_sUDTExcepts.end(); ++ i) + writefds->insert(*i); + total += p->second.m_sUDTWrites.size() + p->second.m_sUDTExcepts.size(); + } + + if (lrfds || lwfds) + { + #ifdef LINUX + const int max_events = p->second.m_sLocals.size(); + epoll_event ev[max_events]; + int nfds = ::epoll_wait(p->second.m_iLocalID, ev, max_events, 0); + + for (int i = 0; i < nfds; ++ i) + { + if ((NULL != lrfds) && (ev[i].events & EPOLLIN)) + { + lrfds->insert(ev[i].data.fd); + ++ total; + } + if ((NULL != lwfds) && (ev[i].events & EPOLLOUT)) + { + lwfds->insert(ev[i].data.fd); + ++ total; + } + } + #elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + #if defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + // + // for iOS setting a timeout of 1ms for kevent and not doing CTimer::waitForEvent(); in the code below + // gives us a 10% cpu boost. + // + struct timespec tmout = {0, 1000000}; + #else + struct timespec tmout = {0, 0}; + #endif + const int max_events = p->second.m_sLocals.size(); + struct kevent ke[max_events]; + + int nfds = kevent(p->second.m_iLocalID, NULL, 0, ke, max_events, &tmout); + + for (int i = 0; i < nfds; ++ i) + { + if ((NULL != lrfds) && (ke[i].filter == EVFILT_READ)) + { + lrfds->insert(ke[i].ident); + ++ total; + } + if ((NULL != lwfds) && (ke[i].filter == EVFILT_WRITE)) + { + lwfds->insert(ke[i].ident); + ++ total; + } + } + #else + //currently "select" is used for all non-Linux platforms. + //faster approaches can be applied for specific systems in the future. + + //"select" has a limitation on the number of sockets + SYSSOCKET max_fd = 0; + + fd_set readfds; + fd_set writefds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + for (set::const_iterator i = p->second.m_sLocals.begin(); i != p->second.m_sLocals.end(); ++ i) + { + if (lrfds) + FD_SET(*i, &readfds); + if (lwfds) + FD_SET(*i, &writefds); + if (*i > max_fd) + max_fd = *i; + } + + timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + if (::select(max_fd + 1, &readfds, &writefds, NULL, &tv) > 0) + { + for (set::const_iterator i = p->second.m_sLocals.begin(); i != p->second.m_sLocals.end(); ++ i) + { + if (lrfds && FD_ISSET(*i, &readfds)) + { + lrfds->insert(*i); + ++ total; + } + if (lwfds && FD_ISSET(*i, &writefds)) + { + lwfds->insert(*i); + ++ total; + } + } + } + #endif + } + + CGuard::leaveCS(m_EPollLock); + + if (total > 0) + return total; + + if ((msTimeOut >= 0) && (int64_t(CTimer::getTime() - entertime) >= msTimeOut * 1000LL)) + throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0); + + #if defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + #else + CTimer::waitForEvent(); + #endif + } + + return 0; +} + +int CEPoll::release(const int eid) +{ + CGuard pg(m_EPollLock); + + map::iterator i = m_mPolls.find(eid); + if (i == m_mPolls.end()) + throw CUDTException(MJ_NOTSUP, MN_EIDINVAL); + + #ifdef LINUX + // release local/system epoll descriptor + ::close(i->second.m_iLocalID); + #elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + ::close(i->second.m_iLocalID); + #endif + + m_mPolls.erase(i); + + return 0; +} + +namespace +{ + +void update_epoll_sets(const UDTSOCKET& uid, const set& watch, set& result, bool enable) +{ + if (enable && (watch.find(uid) != watch.end())) + { + result.insert(uid); + } + else if (!enable) + { + result.erase(uid); + } +} + +} // namespace + +int CEPoll::update_events(const UDTSOCKET& uid, std::set& eids, int events, bool enable) +{ + CGuard pg(m_EPollLock); + + map::iterator p; + + vector lost; + for (set::iterator i = eids.begin(); i != eids.end(); ++ i) + { + p = m_mPolls.find(*i); + if (p == m_mPolls.end()) + { + lost.push_back(*i); + } + else + { + if ((events & UDT_EPOLL_IN) != 0) + update_epoll_sets(uid, p->second.m_sUDTSocksIn, p->second.m_sUDTReads, enable); + if ((events & UDT_EPOLL_OUT) != 0) + update_epoll_sets(uid, p->second.m_sUDTSocksOut, p->second.m_sUDTWrites, enable); + if ((events & UDT_EPOLL_ERR) != 0) + update_epoll_sets(uid, p->second.m_sUDTSocksEx, p->second.m_sUDTExcepts, enable); + } + } + + for (vector::iterator i = lost.begin(); i != lost.end(); ++ i) + eids.erase(*i); + + return 0; +} diff --git a/srtcore/epoll.h b/srtcore/epoll.h new file mode 100644 index 000000000..c33960c2e --- /dev/null +++ b/srtcore/epoll.h @@ -0,0 +1,187 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2010, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 08/20/2010 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_EPOLL_H__ +#define __UDT_EPOLL_H__ + + +#include +#include +#include "udt.h" + + +struct CEPollDesc +{ + int m_iID; // epoll ID + std::set m_sUDTSocksOut; // set of UDT sockets waiting for write events + std::set m_sUDTSocksIn; // set of UDT sockets waiting for read events + std::set m_sUDTSocksEx; // set of UDT sockets waiting for exceptions + + int m_iLocalID; // local system epoll ID + std::set m_sLocals; // set of local (non-UDT) descriptors + + std::set m_sUDTWrites; // UDT sockets ready for write + std::set m_sUDTReads; // UDT sockets ready for read + std::set m_sUDTExcepts; // UDT sockets with exceptions (connection broken, etc.) +}; + +class CEPoll +{ +friend class CUDT; +friend class CRendezvousQueue; + +public: + CEPoll(); + ~CEPoll(); + +public: // for CUDTUnited API + + /// create a new EPoll. + /// @return new EPoll ID if success, otherwise an error number. + + int create(); + + /// add a UDT socket to an EPoll. + /// @param [in] eid EPoll ID. + /// @param [in] u UDT Socket ID. + /// @param [in] events events to watch. + /// @return 0 if success, otherwise an error number. + + int add_usock(const int eid, const UDTSOCKET& u, const int* events = NULL); + + /// add a system socket to an EPoll. + /// @param [in] eid EPoll ID. + /// @param [in] s system Socket ID. + /// @param [in] events events to watch. + /// @return 0 if success, otherwise an error number. + + int add_ssock(const int eid, const SYSSOCKET& s, const int* events = NULL); + + /// remove a UDT socket event from an EPoll; socket will be removed if no events to watch. + /// @param [in] eid EPoll ID. + /// @param [in] u UDT socket ID. + /// @return 0 if success, otherwise an error number. + + int remove_usock(const int eid, const UDTSOCKET& u); + + /// remove a system socket event from an EPoll; socket will be removed if no events to watch. + /// @param [in] eid EPoll ID. + /// @param [in] s system socket ID. + /// @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. + /// @param [in] events events to watch. + /// @return 0 if success, otherwise an error number. + + int update_usock(const int eid, const UDTSOCKET& u, const int* events = NULL); + + /// update a system socket events from an EPoll. + /// @param [in] eid EPoll ID. + /// @param [in] u UDT socket ID. + /// @param [in] events events to watch. + /// @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. + /// @param [out] readfds UDT sockets available for reading. + /// @param [out] writefds UDT sockets available for writing. + /// @param [in] msTimeOut timeout threshold, in milliseconds. + /// @param [out] lrfds system file descriptors for reading. + /// @param [out] lwfds system file descriptors for writing. + /// @return number of sockets available for IO. + + int wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds, std::set* lwfds); + + /// close and release an EPoll. + /// @param [in] eid EPoll ID. + /// @return 0 if success, otherwise an error number. + + int release(const int eid); + +public: // for CUDT to acknowledge IO status + + /// Update events available for a UDT socket. + /// @param [in] uid UDT socket ID. + /// @param [in] eids EPoll IDs to be set + /// @param [in] events Combination of events to update + /// @param [in] enable true -> enable, otherwise disable + /// @return 0 if success, otherwise an error number + + int update_events(const UDTSOCKET& uid, std::set& eids, int events, bool enable); + +private: + int m_iIDSeed; // seed to generate a new ID + pthread_mutex_t m_SeedLock; + + std::map m_mPolls; // all epolls + pthread_mutex_t m_EPollLock; +}; + + +#endif diff --git a/srtcore/list.cpp b/srtcore/list.cpp new file mode 100644 index 000000000..ca57548c1 --- /dev/null +++ b/srtcore/list.cpp @@ -0,0 +1,784 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/22/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#include "list.h" +#include "packet.h" + +CSndLossList::CSndLossList(int size): +m_caSeq(), +m_iHead(-1), +m_iLength(0), +m_iSize(size), +m_iLastInsertPos(-1), +m_ListLock() +{ + m_caSeq = new Seq[size]; + + // -1 means there is no data in the node + for (int i = 0; i < size; ++ i) + { + m_caSeq[i].data1 = -1; + m_caSeq[i].data2 = -1; + } + + // sender list needs mutex protection + pthread_mutex_init(&m_ListLock, 0); +} + +CSndLossList::~CSndLossList() +{ + delete [] m_caSeq; + pthread_mutex_destroy(&m_ListLock); +} + +int CSndLossList::insert(int32_t seqno1, int32_t seqno2) +{ + CGuard listguard(m_ListLock); + + if (0 == m_iLength) + { + // insert data into an empty list + + m_iHead = 0; + m_caSeq[m_iHead].data1 = seqno1; + if (seqno2 != seqno1) + m_caSeq[m_iHead].data2 = seqno2; + + m_caSeq[m_iHead].next = -1; + m_iLastInsertPos = m_iHead; + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); + + return m_iLength; + } + + // otherwise find the position where the data can be inserted + int origlen = m_iLength; + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno1); + int loc = (m_iHead + offset + m_iSize) % m_iSize; + + if (offset < 0) + { + // Insert data prior to the head pointer + + m_caSeq[loc].data1 = seqno1; + if (seqno2 != seqno1) + m_caSeq[loc].data2 = seqno2; + + // new node becomes head + m_caSeq[loc].next = m_iHead; + m_iHead = loc; + m_iLastInsertPos = loc; + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); + } + else if (offset > 0) + { + if (seqno1 == m_caSeq[loc].data1) + { + m_iLastInsertPos = loc; + + // first seqno is equivlent, compare the second + if (-1 == m_caSeq[loc].data2) + { + if (seqno2 != seqno1) + { + m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; + m_caSeq[loc].data2 = seqno2; + } + } + else if (CSeqNo::seqcmp(seqno2, m_caSeq[loc].data2) > 0) + { + // new seq pair is longer than old pair, e.g., insert [3, 7] to [3, 5], becomes [3, 7] + m_iLength += CSeqNo::seqlen(m_caSeq[loc].data2, seqno2) - 1; + m_caSeq[loc].data2 = seqno2; + } + else + // Do nothing if it is already there + return 0; + } + else + { + // searching the prior node + int i; + if ((-1 != m_iLastInsertPos) && (CSeqNo::seqcmp(m_caSeq[m_iLastInsertPos].data1, seqno1) < 0)) + i = m_iLastInsertPos; + else + i = m_iHead; + + while ((-1 != m_caSeq[i].next) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].next].data1, seqno1) < 0)) + i = m_caSeq[i].next; + + if ((-1 == m_caSeq[i].data2) || (CSeqNo::seqcmp(m_caSeq[i].data2, seqno1) < 0)) + { + m_iLastInsertPos = loc; + + // no overlap, create new node + m_caSeq[loc].data1 = seqno1; + if (seqno2 != seqno1) + m_caSeq[loc].data2 = seqno2; + + m_caSeq[loc].next = m_caSeq[i].next; + m_caSeq[i].next = loc; + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); + } + else + { + m_iLastInsertPos = i; + + // overlap, coalesce with prior node, insert(3, 7) to [2, 5], ... becomes [2, 7] + if (CSeqNo::seqcmp(m_caSeq[i].data2, seqno2) < 0) + { + m_iLength += CSeqNo::seqlen(m_caSeq[i].data2, seqno2) - 1; + m_caSeq[i].data2 = seqno2; + + loc = i; + } + else + return 0; + } + } + } + else + { + m_iLastInsertPos = m_iHead; + + // insert to head node + if (seqno2 != seqno1) + { + if (-1 == m_caSeq[loc].data2) + { + m_iLength += CSeqNo::seqlen(seqno1, seqno2) - 1; + m_caSeq[loc].data2 = seqno2; + } + else if (CSeqNo::seqcmp(seqno2, m_caSeq[loc].data2) > 0) + { + m_iLength += CSeqNo::seqlen(m_caSeq[loc].data2, seqno2) - 1; + m_caSeq[loc].data2 = seqno2; + } + else + return 0; + } + else + return 0; + } + + // coalesce with next node. E.g., [3, 7], ..., [6, 9] becomes [3, 9] + while ((-1 != m_caSeq[loc].next) && (-1 != m_caSeq[loc].data2)) + { + int i = m_caSeq[loc].next; + + if (CSeqNo::seqcmp(m_caSeq[i].data1, CSeqNo::incseq(m_caSeq[loc].data2)) <= 0) + { + // coalesce if there is overlap + if (-1 != m_caSeq[i].data2) + { + if (CSeqNo::seqcmp(m_caSeq[i].data2, m_caSeq[loc].data2) > 0) + { + if (CSeqNo::seqcmp(m_caSeq[loc].data2, m_caSeq[i].data1) >= 0) + m_iLength -= CSeqNo::seqlen(m_caSeq[i].data1, m_caSeq[loc].data2); + + m_caSeq[loc].data2 = m_caSeq[i].data2; + } + else + m_iLength -= CSeqNo::seqlen(m_caSeq[i].data1, m_caSeq[i].data2); + } + else + { + if (m_caSeq[i].data1 == CSeqNo::incseq(m_caSeq[loc].data2)) + m_caSeq[loc].data2 = m_caSeq[i].data1; + else + m_iLength --; + } + + m_caSeq[i].data1 = -1; + m_caSeq[i].data2 = -1; + m_caSeq[loc].next = m_caSeq[i].next; + } + else + break; + } + + return m_iLength - origlen; +} + +void CSndLossList::remove(int32_t seqno) +{ + CGuard listguard(m_ListLock); + + if (0 == m_iLength) + return; + + // Remove all from the head pointer to a node with a larger seq. no. or the list is empty + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno); + int loc = (m_iHead + offset + m_iSize) % m_iSize; + + if (0 == offset) + { + // It is the head. Remove the head and point to the next node + loc = (loc + 1) % m_iSize; + + if (-1 == m_caSeq[m_iHead].data2) + loc = m_caSeq[m_iHead].next; + else + { + m_caSeq[loc].data1 = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[m_iHead].data2, CSeqNo::incseq(seqno)) > 0) + m_caSeq[loc].data2 = m_caSeq[m_iHead].data2; + + m_caSeq[m_iHead].data2 = -1; + + m_caSeq[loc].next = m_caSeq[m_iHead].next; + } + + m_caSeq[m_iHead].data1 = -1; + + if (m_iLastInsertPos == m_iHead) + m_iLastInsertPos = -1; + + m_iHead = loc; + + m_iLength --; + } + else if (offset > 0) + { + int h = m_iHead; + + if (seqno == m_caSeq[loc].data1) + { + // target node is not empty, remove part/all of the seqno in the node. + int temp = loc; + loc = (loc + 1) % m_iSize; + + if (-1 == m_caSeq[temp].data2) + m_iHead = m_caSeq[temp].next; + else + { + // remove part, e.g., [3, 7] becomes [], [4, 7] after remove(3) + m_caSeq[loc].data1 = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[temp].data2, m_caSeq[loc].data1) > 0) + m_caSeq[loc].data2 = m_caSeq[temp].data2; + m_iHead = loc; + m_caSeq[loc].next = m_caSeq[temp].next; + m_caSeq[temp].next = loc; + m_caSeq[temp].data2 = -1; + } + } + else + { + // target node is empty, check prior node + int i = m_iHead; + while ((-1 != m_caSeq[i].next) && (CSeqNo::seqcmp(m_caSeq[m_caSeq[i].next].data1, seqno) < 0)) + i = m_caSeq[i].next; + + loc = (loc + 1) % m_iSize; + + if (-1 == m_caSeq[i].data2) + m_iHead = m_caSeq[i].next; + else if (CSeqNo::seqcmp(m_caSeq[i].data2, seqno) > 0) + { + // remove part/all seqno in the prior node + m_caSeq[loc].data1 = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[i].data2, m_caSeq[loc].data1) > 0) + m_caSeq[loc].data2 = m_caSeq[i].data2; + + m_caSeq[i].data2 = seqno; + + m_caSeq[loc].next = m_caSeq[i].next; + m_caSeq[i].next = loc; + + m_iHead = loc; + } + else + m_iHead = m_caSeq[i].next; + } + + // Remove all nodes prior to the new head + while (h != m_iHead) + { + if (m_caSeq[h].data2 != -1) + { + m_iLength -= CSeqNo::seqlen(m_caSeq[h].data1, m_caSeq[h].data2); + m_caSeq[h].data2 = -1; + } + else + m_iLength --; + + m_caSeq[h].data1 = -1; + + if (m_iLastInsertPos == h) + m_iLastInsertPos = -1; + + h = m_caSeq[h].next; + } + } +} + +int CSndLossList::getLossLength() +{ + CGuard listguard(m_ListLock); + + return m_iLength; +} + +int32_t CSndLossList::getLostSeq() +{ + if (0 == m_iLength) + return -1; + + CGuard listguard(m_ListLock); + + if (0 == m_iLength) + return -1; + + if (m_iLastInsertPos == m_iHead) + m_iLastInsertPos = -1; + + // return the first loss seq. no. + int32_t seqno = m_caSeq[m_iHead].data1; + + // head moves to the next node + if (-1 == m_caSeq[m_iHead].data2) + { + //[3, -1] becomes [], and head moves to next node in the list + m_caSeq[m_iHead].data1 = -1; + m_iHead = m_caSeq[m_iHead].next; + } + else + { + // shift to next node, e.g., [3, 7] becomes [], [4, 7] + int loc = (m_iHead + 1) % m_iSize; + + m_caSeq[loc].data1 = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[m_iHead].data2, m_caSeq[loc].data1) > 0) + m_caSeq[loc].data2 = m_caSeq[m_iHead].data2; + + m_caSeq[m_iHead].data1 = -1; + m_caSeq[m_iHead].data2 = -1; + + m_caSeq[loc].next = m_caSeq[m_iHead].next; + m_iHead = loc; + } + + m_iLength --; + + return seqno; +} + +//////////////////////////////////////////////////////////////////////////////// + +CRcvLossList::CRcvLossList(int size): +m_caSeq(), +m_iHead(-1), +m_iTail(-1), +m_iLength(0), +m_iSize(size) +{ + m_caSeq = new Seq[m_iSize]; + + // -1 means there is no data in the node + for (int i = 0; i < size; ++ i) + { + m_caSeq[i].data1 = -1; + m_caSeq[i].data2 = -1; + } +} + +CRcvLossList::~CRcvLossList() +{ + delete [] m_caSeq; +} + +void CRcvLossList::insert(int32_t seqno1, int32_t seqno2) +{ + // Data to be inserted must be larger than all those in the list + // guaranteed by the UDT receiver + + if (0 == m_iLength) + { + // insert data into an empty list + m_iHead = 0; + m_iTail = 0; + m_caSeq[m_iHead].data1 = seqno1; + if (seqno2 != seqno1) + m_caSeq[m_iHead].data2 = seqno2; + + m_caSeq[m_iHead].next = -1; + m_caSeq[m_iHead].prior = -1; + m_iLength += CSeqNo::seqlen(seqno1, seqno2); + + return; + } + + // otherwise searching for the position where the node should be + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno1); + int loc = (m_iHead + offset) % m_iSize; + + if ((-1 != m_caSeq[m_iTail].data2) && (CSeqNo::incseq(m_caSeq[m_iTail].data2) == seqno1)) + { + // coalesce with prior node, e.g., [2, 5], [6, 7] becomes [2, 7] + loc = m_iTail; + m_caSeq[loc].data2 = seqno2; + } + else + { + // create new node + m_caSeq[loc].data1 = seqno1; + + if (seqno2 != seqno1) + m_caSeq[loc].data2 = seqno2; + + m_caSeq[m_iTail].next = loc; + m_caSeq[loc].prior = m_iTail; + m_caSeq[loc].next = -1; + m_iTail = loc; + } + + m_iLength += CSeqNo::seqlen(seqno1, seqno2); +} + +bool CRcvLossList::remove(int32_t seqno) +{ + if (0 == m_iLength) + return false; + + // locate the position of "seqno" in the list + int offset = CSeqNo::seqoff(m_caSeq[m_iHead].data1, seqno); + if (offset < 0) + return false; + + int loc = (m_iHead + offset) % m_iSize; + + if (seqno == m_caSeq[loc].data1) + { + // This is a seq. no. that starts the loss sequence + + if (-1 == m_caSeq[loc].data2) + { + // there is only 1 loss in the sequence, delete it from the node + if (m_iHead == loc) + { + m_iHead = m_caSeq[m_iHead].next; + if (-1 != m_iHead) + m_caSeq[m_iHead].prior = -1; + } + else + { + m_caSeq[m_caSeq[loc].prior].next = m_caSeq[loc].next; + if (-1 != m_caSeq[loc].next) + m_caSeq[m_caSeq[loc].next].prior = m_caSeq[loc].prior; + else + m_iTail = m_caSeq[loc].prior; + } + + m_caSeq[loc].data1 = -1; + } + else + { + // there are more than 1 loss in the sequence + // move the node to the next and update the starter as the next loss inSeqNo(seqno) + + // find next node + int i = (loc + 1) % m_iSize; + + // remove the "seqno" and change the starter as next seq. no. + m_caSeq[i].data1 = CSeqNo::incseq(m_caSeq[loc].data1); + + // process the sequence end + if (CSeqNo::seqcmp(m_caSeq[loc].data2, CSeqNo::incseq(m_caSeq[loc].data1)) > 0) + m_caSeq[i].data2 = m_caSeq[loc].data2; + + // remove the current node + m_caSeq[loc].data1 = -1; + m_caSeq[loc].data2 = -1; + + // update list pointer + m_caSeq[i].next = m_caSeq[loc].next; + m_caSeq[i].prior = m_caSeq[loc].prior; + + if (m_iHead == loc) + m_iHead = i; + else + m_caSeq[m_caSeq[i].prior].next = i; + + if (m_iTail == loc) + m_iTail = i; + else + m_caSeq[m_caSeq[i].next].prior = i; + } + + m_iLength --; + + return true; + } + + // There is no loss sequence in the current position + // the "seqno" may be contained in a previous node + + // searching previous node + int i = (loc - 1 + m_iSize) % m_iSize; + while (-1 == m_caSeq[i].data1) + i = (i - 1 + m_iSize) % m_iSize; + + // not contained in this node, return + if ((-1 == m_caSeq[i].data2) || (CSeqNo::seqcmp(seqno, m_caSeq[i].data2) > 0)) + return false; + + if (seqno == m_caSeq[i].data2) + { + // it is the sequence end + + if (seqno == CSeqNo::incseq(m_caSeq[i].data1)) + m_caSeq[i].data2 = -1; + else + m_caSeq[i].data2 = CSeqNo::decseq(seqno); + } + else + { + // split the sequence + + // construct the second sequence from CSeqNo::incseq(seqno) to the original sequence end + // located at "loc + 1" + loc = (loc + 1) % m_iSize; + + m_caSeq[loc].data1 = CSeqNo::incseq(seqno); + if (CSeqNo::seqcmp(m_caSeq[i].data2, m_caSeq[loc].data1) > 0) + m_caSeq[loc].data2 = m_caSeq[i].data2; + + // the first (original) sequence is between the original sequence start to CSeqNo::decseq(seqno) + if (seqno == CSeqNo::incseq(m_caSeq[i].data1)) + m_caSeq[i].data2 = -1; + else + m_caSeq[i].data2 = CSeqNo::decseq(seqno); + + // update the list pointer + m_caSeq[loc].next = m_caSeq[i].next; + m_caSeq[i].next = loc; + m_caSeq[loc].prior = i; + + if (m_iTail == i) + m_iTail = loc; + else + m_caSeq[m_caSeq[loc].next].prior = loc; + } + + m_iLength --; + + return true; +} + +bool CRcvLossList::remove(int32_t seqno1, int32_t seqno2) +{ + if (seqno1 <= seqno2) + { + for (int32_t i = seqno1; i <= seqno2; ++ i) + remove(i); + } + else + { + for (int32_t j = seqno1; j < CSeqNo::m_iMaxSeqNo; ++ j) + remove(j); + for (int32_t k = 0; k <= seqno2; ++ k) + remove(k); + } + + return true; +} + +bool CRcvLossList::find(int32_t seqno1, int32_t seqno2) const +{ + if (0 == m_iLength) + return false; + + int p = m_iHead; + + while (-1 != p) + { + if ((CSeqNo::seqcmp(m_caSeq[p].data1, seqno1) == 0) || + ((CSeqNo::seqcmp(m_caSeq[p].data1, seqno1) > 0) && (CSeqNo::seqcmp(m_caSeq[p].data1, seqno2) <= 0)) || + ((CSeqNo::seqcmp(m_caSeq[p].data1, seqno1) < 0) && (m_caSeq[p].data2 != -1) && CSeqNo::seqcmp(m_caSeq[p].data2, seqno1) >= 0)) + return true; + + p = m_caSeq[p].next; + } + + return false; +} + +int CRcvLossList::getLossLength() const +{ + return m_iLength; +} + +int CRcvLossList::getFirstLostSeq() const +{ + if (0 == m_iLength) + return -1; + + return m_caSeq[m_iHead].data1; +} + +void CRcvLossList::getLossArray(int32_t* array, int& len, int limit) +{ + len = 0; + + int i = m_iHead; + + while ((len < limit - 1) && (-1 != i)) + { + array[len] = m_caSeq[i].data1; + if (-1 != m_caSeq[i].data2) + { + // there are more than 1 loss in the sequence + array[len] |= LOSSDATA_SEQNO_RANGE_FIRST; + ++ len; + array[len] = m_caSeq[i].data2; + } + + ++ len; + + i = m_caSeq[i].next; + } +} + + +CRcvFreshLoss::CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_age): ttl(initial_age) +{ + CTimer::rdtsc(timestamp); + seq[0] = seqlo; + seq[1] = seqhi; +} + + +CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t sequence) +{ + int32_t diffbegin = CSeqNo::seqcmp(sequence, seq[0]); + int32_t diffend = CSeqNo::seqcmp(sequence, seq[1]); + + if ( diffbegin < 0 || diffend > 0 ) + { + return NONE; // not within the range at all. + } + + if ( diffbegin == 0 ) + { + if ( diffend == 0 ) // exactly at begin and end + { + return DELETE; + } + + // only exactly at begin. Shrink the range + seq[0] = CSeqNo::incseq(seq[0]); + return STRIPPED; + } + + if ( diffend == 0 ) // exactly at end + { + seq[1] = CSeqNo::decseq(seq[1]); + return STRIPPED; + } + + return SPLIT; +} + +CRcvFreshLoss::Emod CRcvFreshLoss::revoke(int32_t lo, int32_t hi) +{ + // This should only if the range lo-hi is anyhow covered by seq[0]-seq[1]. + + // Note: if the checked item contains sequences that are OLDER + // than the oldest sequence in this range, they should be deleted, + // even though this wasn't explicitly requested. + + // LOHI: + // ITEM: <--- delete + // If the sequence range is older than the range to be revoked, + // delete it anyway. + if ( CSeqNo::seqcmp(lo, seq[1]) > 0 ) + return DELETE; + + // LOHI: + // ITEM: <-- NOTFOUND + // This element is newer than the given sequence, so match failed. + if ( CSeqNo::seqcmp(hi, seq[0]) < 0 ) + return NONE; + + // LOHI: + // ITEM: + // RESULT: + // 2. If the 'hi' is in the middle (less than seq[1]), delete partially. + // That is, take care of this range for itself and return STRIPPED. + if ( CSeqNo::seqcmp(hi, seq[1]) < 0 ) + { + seq[0] = CSeqNo::incseq(hi); + return STRIPPED; + } + + // LOHI: + // ITEM: + // RESULT: DELETE. + // 3. Otherwise delete the record, even if this was covering only part of this range. + // This is not possible that the sequences OLDER THAN THIS are not required to be + // revoken together with this one. + + return DELETE; +} diff --git a/srtcore/list.h b/srtcore/list.h new file mode 100644 index 000000000..3d895cf95 --- /dev/null +++ b/srtcore/list.h @@ -0,0 +1,255 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/22/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_LIST_H__ +#define __UDT_LIST_H__ + + +#include "udt.h" +#include "common.h" + + +class CSndLossList +{ +public: + CSndLossList(int size = 1024); + ~CSndLossList(); + + /// Insert a seq. no. into the sender loss list. + /// @param [in] seqno1 sequence number starts. + /// @param [in] seqno2 sequence number ends. + /// @return number of packets that are not in the list previously. + + int insert(int32_t seqno1, int32_t seqno2); + + /// Remove ALL the seq. no. that are not greater than the parameter. + /// @param [in] seqno sequence number. + + void remove(int32_t seqno); + + /// Read the loss length. + /// @return The length of the list. + + int getLossLength(); + + /// Read the first (smallest) loss seq. no. in the list and remove it. + /// @return The seq. no. or -1 if the list is empty. + + int32_t getLostSeq(); + +private: + struct Seq + { + int32_t data1; // sequence number starts + int32_t data2; // seqnence number ends + int next; // next node in the list + }* m_caSeq; + + int m_iHead; // first node + int m_iLength; // loss length + int m_iSize; // size of the static array + int m_iLastInsertPos; // position of last insert node + + pthread_mutex_t m_ListLock; // used to synchronize list operation + +private: + CSndLossList(const CSndLossList&); + CSndLossList& operator=(const CSndLossList&); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CRcvLossList +{ +public: + CRcvLossList(int size = 1024); + ~CRcvLossList(); + + /// Insert a series of loss seq. no. between "seqno1" and "seqno2" into the receiver's loss list. + /// @param [in] seqno1 sequence number starts. + /// @param [in] seqno2 seqeunce number ends. + + void insert(int32_t seqno1, int32_t seqno2); + + /// Remove a loss seq. no. from the receiver's loss list. + /// @param [in] seqno sequence number. + /// @return if the packet is removed (true) or no such lost packet is found (false). + + bool remove(int32_t seqno); + + /// Remove all packets between seqno1 and seqno2. + /// @param [in] seqno1 start sequence number. + /// @param [in] seqno2 end sequence number. + /// @return if the packet is removed (true) or no such lost packet is found (false). + + bool remove(int32_t seqno1, int32_t seqno2); + + /// Find if there is any lost packets whose sequence number falling seqno1 and seqno2. + /// @param [in] seqno1 start sequence number. + /// @param [in] seqno2 end sequence number. + /// @return True if found; otherwise false. + + bool find(int32_t seqno1, int32_t seqno2) const; + + /// Read the loss length. + /// @return the length of the list. + + int getLossLength() const; + + /// Read the first (smallest) seq. no. in the list. + /// @return the sequence number or -1 if the list is empty. + + int getFirstLostSeq() const; + + /// Get a encoded loss array for NAK report. + /// @param [out] array the result list of seq. no. to be included in NAK. + /// @param [out] len physical length of the result array. + /// @param [in] limit maximum length of the array. + + void getLossArray(int32_t* array, int& len, int limit); + +private: + struct Seq + { + int32_t data1; // sequence number starts + int32_t data2; // sequence number ends + int next; // next node in the list + int prior; // prior node in the list; + }* m_caSeq; + + int m_iHead; // first node in the list + int m_iTail; // last node in the list; + int m_iLength; // loss length + int m_iSize; // size of the static array + +private: + CRcvLossList(const CRcvLossList&); + CRcvLossList& operator=(const CRcvLossList&); +public: + + struct iterator + { + int32_t head; + Seq* seq; + + iterator(Seq* str, int32_t v): head(v), seq(str) {} + + iterator next() const + { + if ( head == -1 ) + return *this; // should report error, but we can only throw exception, so simply ignore it. + + return iterator(seq, seq[head].next); + } + + iterator& operator++() + { + *this = next(); + return *this; + } + + iterator operator++(int) + { + iterator old (seq, head); + *this = next(); + return old; + } + + bool operator==(const iterator& second) const + { + // Ignore seq - should be the same and this is only a sanity check. + return head == second.head; + } + + bool operator!=(const iterator& second) const { return !(*this == second); } + + std::pair operator*() + { + return std::make_pair(seq[head].data1, seq[head].data2); + } + }; + + iterator begin() { return iterator(m_caSeq, m_iHead); } + iterator end() { return iterator(m_caSeq, -1); } + +}; + +struct CRcvFreshLoss +{ + int32_t seq[2]; + int ttl; + uint64_t timestamp; + + CRcvFreshLoss(int32_t seqlo, int32_t seqhi, int initial_ttl); + + enum Emod { + NONE, //< the given sequence was not found in this range + STRIPPED, //< it was equal to first or last, already taken care of + SPLIT, //< found in the middle, you have to split this range into two + DELETE //< This was a range of one element exactly equal to sequence. Simply delete it. + }; + + Emod revoke(int32_t sequence); + Emod revoke(int32_t lo, int32_t hi); +}; + +#endif diff --git a/srtcore/logging.h b/srtcore/logging.h new file mode 100644 index 000000000..ff4fd36b8 --- /dev/null +++ b/srtcore/logging.h @@ -0,0 +1,543 @@ +/***************************************************************************** + * 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__SRT_LOGGING_H +#define INC__SRT_LOGGING_H + + +#include +#include +#include +#include +#include +#include +#include +#if HAVE_CXX11 +#include +#endif + +#include "utilities.h" +#include "threadname.h" +#include "logging_api.h" + +#ifdef __GNUC__ +#define PRINTF_LIKE __attribute__((format(printf,2,3))) +#endif + +// Usage: LOGC(mglog.Debug) << param1 << param2 << param3; +#define LOGC(logdes) logdes().setloc(__FILE__, __LINE__, __FUNCTION__) +// LOGP is C++11 only OR with only one string argument. +// Usage: LOGP(mglog.Debug, param1, param2, param3); +#define LOGP(logdes, ...) logdes.printloc(__FILE__, __LINE__, __FUNCTION__,##__VA_ARGS__) + +namespace logging +{ + +struct LogConfig +{ + std::set enabled_fa; + LogLevel::type max_level; + std::ostream* log_stream; + SRT_LOG_HANDLER_FN* loghandler_fn; + void* loghandler_opaque; + pthread_mutex_t mutex; + int flags; + + LogConfig(const std::set& initial_fa): + enabled_fa(initial_fa), + max_level(LogLevel::warning), + log_stream(&std::cerr) + { + pthread_mutex_init(&mutex, 0); + } + LogConfig(const std::set efa, LogLevel::type l, std::ostream* ls): + enabled_fa(efa), max_level(l), log_stream(ls) + { + pthread_mutex_init(&mutex, 0); + } + + ~LogConfig() + { + pthread_mutex_destroy(&mutex); + } + + void lock() { pthread_mutex_lock(&mutex); } + void unlock() { pthread_mutex_unlock(&mutex); } +}; + + +struct LogDispatcher +{ + int fa; + LogLevel::type level; + std::string prefix; + bool enabled; + LogConfig* src_config; + int flags; // copy of config flags as this must be accessed once. + pthread_mutex_t mutex; + + LogDispatcher(int functional_area, LogLevel::type log_level, const std::string& pfx, LogConfig* config): + fa(functional_area), + level(log_level), + prefix(pfx), + enabled(false), + src_config(config) + { + pthread_mutex_init(&mutex, 0); + } + + ~LogDispatcher() + { + pthread_mutex_destroy(&mutex); + } + + bool CheckEnabled(); + LogDispatcher(bool v): enabled(v) {} + + void CreateLogLinePrefix(std::ostringstream&); + void SendLogLine(const char* file, int line, const std::string& area, const std::string& sl); + + // log.Debug("This is the ", nth, " time"); <--- C++11 only. + // log.Debug() << "This is the " << nth << " time"; <--- C++03 available. + +#if HAVE_CXX11 + + template + void PrintLogLine(const char* file, int line, const std::string& area, Args&&... args); + + template + void operator()(Arg1&& arg1, Args&&... args) + { + if ( CheckEnabled() ) + { + PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg1, args...); + } + } + + template + void printloc(const char* file, int line, const std::string& area, Arg1&& arg1, Args&&... args) + { + if ( CheckEnabled() ) + { + PrintLogLine(file, line, area, arg1, args...); + } + } +#else + template + void PrintLogLine(const char* file, int line, const std::string& area, const Arg& arg); + + // For old C++ standard provide only with one argument. + template + void operator()(const Arg& arg) + { + if ( CheckEnabled() ) + { + PrintLogLine("UNKNOWN.c++", 0, "UNKNOWN", arg); + } + } + + void printloc(const char* file, int line, const std::string& area, const std::string& arg1) + { + if ( CheckEnabled() ) + { + PrintLogLine(file, line, area, arg1); + } + } +#endif + +#if ENABLE_LOGGING + + struct Proxy; + + Proxy operator()(); +#else + + // Dummy proxy that does nothing + struct DummyProxy + { + DummyProxy(LogDispatcher&) + { + } + + template + DummyProxy& operator<<(const T& ) // predicted for temporary objects + { + return *this; + } + + DummyProxy& form(const char*, ...) + { + return *this; + } + + DummyProxy& setloc(const char* , int , std::string) + { + return *this; + } + }; + + DummyProxy operator()() + { + return DummyProxy(*this); + } + +#endif + +}; + +#if ENABLE_LOGGING + +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 + // becomes enabled or disabled in the middle of the log, we don't + // want it to be partially printed anyway. + bool that_enabled; + int flags; + + // CACHE!!! + const char* i_file; + int i_line; + std::string area; + + Proxy& setloc(const char* f, int l, std::string a) + { + i_file = f; + i_line = l; + area = a; + return *this; + } + + // Left for future. Not sure if it's more convenient + // to use this to translate __PRETTY_FUNCTION__ to + // something short, or just let's leave __FUNCTION__ + // or better __func__. + std::string ExtractName(std::string pretty_function) + { + if ( pretty_function == "" ) + return ""; + size_t pos = pretty_function.find('('); + if ( pos == std::string::npos ) + return pretty_function; // return unchanged. + + pretty_function = pretty_function.substr(0, pos); + + // There are also template instantiations where the instantiating + // parameters are encrypted inside. Therefore, search for the first + // open < and if found, search for symmetric >. + + int depth = 1; + pos = pretty_function.find('<'); + if ( pos != std::string::npos ) + { + size_t end = pos+1; + for(;;) + { + ++pos; + if ( pos == pretty_function.size() ) + { + --pos; + break; + } + if ( pretty_function[pos] == '<' ) + { + ++depth; + continue; + } + + if ( pretty_function[pos] == '>' ) + { + --depth; + if ( depth <= 0 ) + break; + continue; + } + } + + std::string afterpart = pretty_function.substr(pos+1); + pretty_function = pretty_function.substr(0, end) + ">" + afterpart; + } + + // Now see how many :: can be found in the name. + // If this occurs more than once, take the last two. + pos = pretty_function.rfind("::"); + + if ( pos == std::string::npos || pos < 2 ) + return pretty_function; // return whatever this is. No scope name. + + // Find the next occurrence of :: - if found, copy up to it. If not, + // return whatever is found. + pos -= 2; + pos = pretty_function.rfind("::", pos); + if ( pos == std::string::npos ) + return pretty_function; // nothing to cut + + return pretty_function.substr(pos+2); + } + + Proxy(LogDispatcher& guy): that(guy), that_enabled(that.CheckEnabled()) + { + flags = that.flags; + if ( that_enabled ) + { + // Create logger prefix + that.CreateLogLinePrefix(os); + } + } + + // Copy constructor is needed due to noncopyable ostringstream. + // This is used only in creation of the default object, so just + // use the default values, just copy the location cache. + Proxy(const Proxy& p): that(p.that), area(p.area) + { + i_file = p.i_file; + i_line = p.i_line; + that_enabled = false; + flags = that.flags; + } + + + template + Proxy& operator<<(const T& arg) // predicted for temporary objects + { + if ( that_enabled ) + { + os << arg; + } + return *this; + } + + ~Proxy() + { + if ( that_enabled ) + { + if ( (flags & SRT_LOGF_DISABLE_EOL) == 0 ) + os << std::endl; + that.SendLogLine(i_file, i_line, area, os.str()); + } + // Needed in destructor? + //os.clear(); + //os.str(""); + } + + Proxy& form(const char* fmts, ...) PRINTF_LIKE + { + if ( !that_enabled ) + return *this; + + if ( !fmts || fmts[0] == '\0' ) + return *this; + + char buf[512]; + va_list ap; + va_start(ap, fmts); + vsprintf(buf, fmts, ap); + va_end(ap); + size_t len = strlen(buf); + if ( buf[len-1] == '\n' ) + { + // Remove EOL character, should it happen to be at the end. + // The EOL will be added at the end anyway. + buf[len-1] = '\0'; + } + + os << buf; + return *this; + } +}; + +inline LogDispatcher::Proxy LogDispatcher::operator()() +{ + LogDispatcher& that = *this; + + Proxy proxy = that; + return proxy; +} + +#endif + +class Logger +{ + std::string m_prefix; + int m_fa; + //bool enabled = false; + LogConfig* m_config; + +public: + + LogDispatcher Debug; + LogDispatcher Note; + LogDispatcher Warn; + LogDispatcher Error; + LogDispatcher Fatal; + + Logger(int functional_area, LogConfig* config, std::string globprefix = std::string()): + m_prefix( globprefix == "" ? globprefix : ": " + globprefix), + m_fa(functional_area), + m_config(config), + Debug ( m_fa, LogLevel::debug, " D" + m_prefix, m_config ), + Note ( m_fa, LogLevel::note, ".N" + m_prefix, m_config ), + Warn ( m_fa, LogLevel::warning, "!W" + m_prefix, m_config ), + Error ( m_fa, LogLevel::error, "*E" + m_prefix, m_config ), + Fatal ( m_fa, LogLevel::fatal, "!!FATAL!!" + m_prefix, m_config ) + { + } + +}; + +inline bool LogDispatcher::CheckEnabled() +{ + // Don't use enabler caching. Check enabled state every time. + bool enabled = false; + src_config->lock(); + + // If the thread is interrupted during any of this process, in worst case + // we'll just overwrite the already set values with the same. + enabled = src_config->enabled_fa.count(fa) && level <= src_config->max_level; + flags = src_config->flags; + + src_config->unlock(); + + return enabled; +} + +inline std::string FormatTime(uint64_t time) +{ + time_t sec = time/1000000; + time_t usec = time%1000000; + + time_t tt = sec; + struct tm tm = *localtime(&tt); + + char tmp_buf[512]; + strftime(tmp_buf, 512, "%T.", &tm); + std::string out = tmp_buf; + sprintf(tmp_buf, "%06ld", usec); + out += tmp_buf; + return out; +} + +inline void LogDispatcher::CreateLogLinePrefix(std::ostringstream& serr) +{ + using namespace std; + + char tmp_buf[512]; + if ( (flags & SRT_LOGF_DISABLE_TIME) == 0 ) + { + // Not necessary if sending through the queue. + timeval tv; + gettimeofday(&tv, 0); + time_t t = tv.tv_sec; + struct tm tm = *localtime(&t); + strftime(tmp_buf, 512, "%T.", &tm); + + serr << tmp_buf << setw(6) << setfill('0') << tv.tv_usec; + } + + // Note: ThreadName::get needs a buffer of size min. ThreadName::BUFSIZE + string out_prefix; + if ( (flags & SRT_LOGF_DISABLE_SEVERITY) == 0 ) + { + out_prefix = prefix; + } + + if ( (flags & SRT_LOGF_DISABLE_THREADNAME) == 0 && ThreadName::get(tmp_buf) ) + { + serr << "/" << tmp_buf << out_prefix << ": "; + } + else + { + serr << out_prefix << ": "; + } +} + +#if HAVE_CXX11 + +//extern std::mutex Debug_mutex; + +inline void PrintArgs(std::ostream&) {} + +template +inline void PrintArgs(std::ostream& serr, Arg1&& arg1, Args&&... args) +{ + serr << arg1; + PrintArgs(serr, args...); +} + +template +inline void LogDispatcher::PrintLogLine(const char* file, int line, const std::string& area, Args&&... args) +{ + std::ostringstream serr; + CreateLogLinePrefix(serr); + PrintArgs(serr, args...); + + if ( (flags & SRT_LOGF_DISABLE_EOL) == 0 ) + serr << std::endl; + + // Not sure, but it wasn't ever used. + SendLogLine(file, line, area, serr.str()); +} + +#else + +template +inline void LogDispatcher::PrintLogLine(const char* file, int line, const std::string& area, const Arg& arg) +{ + std::ostringstream serr; + CreateLogLinePrefix(serr); + serr << arg; + + if ( (flags & SRT_LOGF_DISABLE_EOL) == 0 ) + serr << std::endl; + + // Not sure, but it wasn't ever used. + SendLogLine(file, line, area, serr.str()); +} + +#endif + +inline void LogDispatcher::SendLogLine(const char* file, int line, const std::string& area, const std::string& msg) +{ + src_config->lock(); + if ( src_config->loghandler_fn ) + { + (*src_config->loghandler_fn)(src_config->loghandler_opaque, int(level), file, line, area.c_str(), msg.c_str()); + } + else if ( src_config->log_stream ) + { + (*src_config->log_stream) << msg; + } + src_config->unlock(); +} + +} + +#endif diff --git a/srtcore/logging_api.h b/srtcore/logging_api.h new file mode 100644 index 000000000..3a65e83b5 --- /dev/null +++ b/srtcore/logging_api.h @@ -0,0 +1,113 @@ +/***************************************************************************** + * 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__SRT_LOGGING_API_H +#define INC__SRT_LOGGING_API_H + +// These are required for access functions: +// - adding FA (requires set) +// - setting a log stream (requires iostream) +#ifdef __cplusplus +#include +#include +#endif + +#include +#include + +// Syslog is included so that it provides log level names. +// Haivision log standard requires the same names plus extra one: +#ifndef LOG_DEBUG_TRACE +#define LOG_DEBUG_TRACE 8 +#endif +// It's unused anyway, just for the record. +#define SRT_LOG_LEVEL_MIN LOG_CRIT +#define SRT_LOG_LEVEL_MAX LOG_DEBUG + +// Flags +#define SRT_LOGF_DISABLE_TIME 1 +#define SRT_LOGF_DISABLE_THREADNAME 2 +#define SRT_LOGF_DISABLE_SEVERITY 4 +#define SRT_LOGF_DISABLE_EOL 8 + +// Handler type. +typedef void SRT_LOG_HANDLER_FN(void* opaque, int level, const char* file, int line, const char* area, const char* message); + +#ifdef __cplusplus +namespace logging +{ + + +struct LogFA +{ +private: + int value; +public: + operator int() const { return value; } + + LogFA(int v): value(v) + { + // Generally this was what it has to be used for. + // Unfortunately it couldn't be agreed with the + //logging_fa_all.insert(v); + } +}; + +const LogFA LOGFA_GENERAL = 0; + + + +namespace LogLevel +{ + // There are 3 general levels: + + // A. fatal - this means the application WILL crash. + // B. unexpected: + // - error: this was unexpected for the library + // - warning: this was expected by the library, but may be harmful for the application + // C. expected: + // - note: a significant, but rarely occurring event + // - debug: may occur even very often and enabling it can harm performance + + enum type + { + fatal = LOG_CRIT, + // Fatal vs. Error: with Error, you can still continue. + error = LOG_ERR, + // Error vs. Warning: Warning isn't considered a problem for the library. + warning = LOG_WARNING, + // Warning vs. Note: Note means something unusual, but completely correct behavior. + note = LOG_NOTICE, + // Note vs. Debug: Debug may occur even multiple times in a millisecond. + // (Well, worth noting that Error and Warning potentially also can). + debug = LOG_DEBUG + }; +} + +class Logger; + +} +#endif + +#endif diff --git a/srtcore/md5.cpp b/srtcore/md5.cpp new file mode 100644 index 000000000..d6fd5d370 --- /dev/null +++ b/srtcore/md5.cpp @@ -0,0 +1,381 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.cpp,v 1.3 2008/01/20 22:52:04 lilyco Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} diff --git a/srtcore/md5.h b/srtcore/md5.h new file mode 100644 index 000000000..f7402e7e2 --- /dev/null +++ b/srtcore/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.2 2007/12/24 05:58:37 lilyco Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/srtcore/netinet_any.h b/srtcore/netinet_any.h new file mode 100644 index 000000000..59042af79 --- /dev/null +++ b/srtcore/netinet_any.h @@ -0,0 +1,136 @@ +/***************************************************************************** + * 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__NETINET_ANY_H +#define INC__NETINET_ANY_H + +#include +#include + +// This is a smart structure that this moron who has designed BSD sockets +// should have defined in the first place. + +struct sockaddr_any +{ + union + { + sockaddr_in sin; + sockaddr_in6 sin6; + sockaddr sa; + }; + socklen_t len; + + sockaddr_any(int domain = AF_INET) + { + memset(this, 0, sizeof *this); + sa.sa_family = domain; + len = size(); + } + + size_t size() const + { + switch (sa.sa_family) + { + case AF_INET: return sizeof sin; + case AF_INET6: return sizeof sin6; + + default: return 0; // fallback, impossible + } + } + + int family() const { return sa.sa_family; } + + // port is in exactly the same location in both sin and sin6 + // and has the same size. This is actually yet another common + // field, just not mentioned in the sockaddr structure. + uint16_t& r_port() { return sin.sin_port; } + uint16_t r_port() const { return sin.sin_port; } + int hport() const { return ntohs(sin.sin_port); } + + void hport(int value) + { + // Port is fortunately located at the same position + // in both sockaddr_in and sockaddr_in6 and has the + // same size. + sin.sin_port = htons(value); + } + + sockaddr* operator&() { return &sa; } + + template struct TypeMap; + + template + typename TypeMap::type& get(); + + struct Equal + { + bool operator()(const sockaddr_any& c1, const sockaddr_any& c2) + { + return memcmp(&c1, &c2, sizeof(c1)) == 0; + } + }; + + struct EqualAddress + { + bool operator()(const sockaddr_any& c1, const sockaddr_any& c2) + { + if ( c1.sa.sa_family == AF_INET ) + { + return c1.sin.sin_addr.s_addr == c2.sin.sin_addr.s_addr; + } + + if ( c1.sa.sa_family == AF_INET6 ) + { + return memcmp(&c1.sin6.sin6_addr, &c2.sin6.sin6_addr, sizeof (in6_addr)) == 0; + } + + return false; + } + + }; + + bool equal_address(const sockaddr_any& rhs) const + { + return EqualAddress()(*this, rhs); + } + + struct Less + { + bool operator()(const sockaddr_any& c1, const sockaddr_any& c2) + { + return memcmp(&c1, &c2, sizeof(c1)) < 0; + } + }; + +}; + +template<> struct sockaddr_any::TypeMap { typedef sockaddr_in type; }; +template<> struct sockaddr_any::TypeMap { typedef sockaddr_in6 type; }; + +template <> +inline sockaddr_any::TypeMap::type& sockaddr_any::get() { return sin; } +template <> +inline sockaddr_any::TypeMap::type& sockaddr_any::get() { return sin6; } + +#endif diff --git a/srtcore/packet.cpp b/srtcore/packet.cpp new file mode 100644 index 000000000..8494bda2d --- /dev/null +++ b/srtcore/packet.cpp @@ -0,0 +1,532 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 02/12/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + + +////////////////////////////////////////////////////////////////////////////// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Packet Header | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// ~ Data / Control Information Field ~ +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |0| Sequence Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |ff |o|kf |r| Message Number | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Time Stamp | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Destination Socket ID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// bit 0: +// 0: Data Packet +// 1: Control Packet +// bit ff: +// 11: solo message packet +// 10: first packet of a message +// 01: last packet of a message +// bit o: +// 0: in order delivery not required +// 1: in order delivery required +// bit kf: HaiCrypt Key Flags +// 00: not encrypted +// 01: encrypted with even key +// 10: encrypted with odd key +// bit r: retransmission flag (set to 1 if this packet was sent again) +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |1| Type | Reserved | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Additional Info | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Time Stamp | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Destination Socket ID | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// bit 1-15: Message type -- see @a UDTMessageType +// 0: Protocol Connection Handshake (UMSG_HANDSHAKE} +// Add. Info: Undefined +// Control Info: Handshake information (see @a CHandShake) +// 1: Keep-alive (UMSG_KEEPALIVE) +// Add. Info: Undefined +// Control Info: None +// 2: Acknowledgement (UMSG_ACK) +// Add. Info: The ACK sequence number +// Control Info: The sequence number to which (but not include) all the previous packets have beed received +// Optional: RTT +// RTT Variance +// available receiver buffer size (in bytes) +// advertised flow window size (number of packets) +// estimated bandwidth (number of packets per second) +// 3: Negative Acknowledgement (UMSG_LOSSREPORT) +// Add. Info: Undefined +// Control Info: Loss list (see loss list coding below) +// 4: Congestion/Delay Warning (UMSG_CGWARNING) +// Add. Info: Undefined +// Control Info: None +// 5: Shutdown (UMSG_SHUTDOWN) +// Add. Info: Undefined +// Control Info: None +// 6: Acknowledgement of Acknowledement (UMSG_ACKACK) +// Add. Info: The ACK sequence number +// Control Info: None +// 7: Message Drop Request (UMSG_DROPREQ) +// Add. Info: Message ID +// Control Info: first sequence number of the message +// last seqeunce number of the message +// 8: Error Signal from the Peer Side (UMSG_PEERERROR) +// Add. Info: Error code +// Control Info: None +// 0x7FFF: Explained by bits 16 - 31 (UMSG_EXT) +// +// bit 16 - 31: +// This space is used for future expansion or user defined control packets. +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |1| Sequence Number a (first) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |0| Sequence Number b (last) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |0| Sequence Number (single) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Loss List Field Coding: +// For any consectutive lost seqeunce numbers that the differnece between +// the last and first is more than 1, only record the first (a) and the +// the last (b) sequence numbers in the loss list field, and modify the +// the first bit of a to 1. +// For any single loss or consectutive loss less than 2 packets, use +// the original sequence numbers in the field. + + +#include +#include "packet.h" + + +const int CHandShake::m_iContentSize = 48; + + +// Set up the aliases in the constructure +CPacket::CPacket(): +__pad(), +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; +} + +CPacket::~CPacket() +{ +} + +int CPacket::getLength() const +{ + return m_PacketVector[PV_DATA].iov_len; +} + +void CPacket::setLength(int 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 additional information and control information field + switch (pkttype) + { + case UMSG_ACK: //0010 - Acknowledgement (ACK) + // ACK packet seq. no. + if (NULL != lparam) + m_nHeader[PH_MSGNO] = *(int32_t *)lparam; + + // data ACK seq. no. + // optional: RTT (microsends), RTT variance (microseconds) advertised flow window size (packets), and estimated link capacity (packets per second) + m_PacketVector[PV_DATA].iov_base = (char *)rparam; + m_PacketVector[PV_DATA].iov_len = size; + + break; + + case UMSG_ACKACK: //0110 - Acknowledgement of Acknowledgement (ACK-2) + // ACK packet seq. no. + m_nHeader[PH_MSGNO] = *(int32_t *)lparam; + + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].iov_base = (char *)&__pad; //NULL; + m_PacketVector[PV_DATA].iov_len = 4; //0; + + break; + + case UMSG_LOSSREPORT: //0011 - Loss Report (NAK) + // loss list + m_PacketVector[PV_DATA].iov_base = (char *)rparam; + m_PacketVector[PV_DATA].iov_len = size; + + break; + + case UMSG_CGWARNING: //0100 - Congestion Warning + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].iov_base = (char *)&__pad; //NULL; + m_PacketVector[PV_DATA].iov_len = 4; //0; + + break; + + case UMSG_KEEPALIVE: //0001 - Keep-alive + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].iov_base = (char *)&__pad; //NULL; + m_PacketVector[PV_DATA].iov_len = 4; //0; + + break; + + case UMSG_HANDSHAKE: //0000 - Handshake + // control info filed is handshake info + m_PacketVector[PV_DATA].iov_base = (char *)rparam; + m_PacketVector[PV_DATA].iov_len = size; //sizeof(CHandShake); + + break; + + case UMSG_SHUTDOWN: //0101 - Shutdown + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].iov_base = (char *)&__pad; //NULL; + m_PacketVector[PV_DATA].iov_len = 4; //0; + + break; + + case UMSG_DROPREQ: //0111 - Message Drop Request + // msg id + m_nHeader[PH_MSGNO] = *(int32_t *)lparam; + + //first seq no, last seq no + m_PacketVector[PV_DATA].iov_base = (char *)rparam; + m_PacketVector[PV_DATA].iov_len = size; + + break; + + case UMSG_PEERERROR: //1000 - Error Signal from the Peer Side + // Error type + m_nHeader[PH_MSGNO] = *(int32_t *)lparam; + + // control info field should be none + // but "writev" does not allow this + m_PacketVector[PV_DATA].iov_base = (char *)&__pad; //NULL; + m_PacketVector[PV_DATA].iov_len = 4; //0; + + break; + + case UMSG_EXT: //0x7FFF - Reserved for user defined control packets + // for extended control packet + // "lparam" contains the extended type information for bit 16 - 31 + // "rparam" is the control information + m_nHeader[PH_SEQNO] |= *(int32_t *)lparam; + + if (NULL != rparam) + { + m_PacketVector[PV_DATA].iov_base = (char *)rparam; + m_PacketVector[PV_DATA].iov_len = size; + } + else + { + m_PacketVector[PV_DATA].iov_base = (char *)&__pad; + m_PacketVector[PV_DATA].iov_len = 4; + } + + break; + + default: + break; + } +} + +iovec* CPacket::getPacketVector() +{ + return m_PacketVector; +} + +UDTMessageType CPacket::getType() const +{ + return UDTMessageType(SEQNO_MSGTYPE::unwrap(m_nHeader[PH_SEQNO])); +} + +int CPacket::getExtendedType() const +{ + return SEQNO_EXTTYPE::unwrap(m_nHeader[PH_SEQNO]); +} + +int32_t CPacket::getAckSeqNo() const +{ + // read additional information field + // This field is used only in UMSG_ACK and UMSG_ACKACK, + // so 'getAckSeqNo' symbolically defines the only use of it + // in case of CONTROL PACKET. + return m_nHeader[PH_MSGNO]; +} + +uint16_t CPacket::getControlFlags() const +{ + // This returns exactly the "extended type" value, + // which is not used at all in case when the standard + // type message is interpreted. This can be used to pass + // additional special flags. + return SEQNO_EXTTYPE::unwrap(m_nHeader[PH_SEQNO]); +} + +PacketBoundary CPacket::getMsgBoundary() const +{ + return PacketBoundary(MSGNO_PACKET_BOUNDARY::unwrap(m_nHeader[PH_MSGNO])); +} + +bool CPacket::getMsgOrderFlag() const +{ + return MSGNO_PACKET_INORDER::unwrap(m_nHeader[PH_MSGNO]); +} + +int32_t CPacket::getMsgSeq(bool has_rexmit) const +{ + if ( has_rexmit ) + { + return MSGNO_SEQ::unwrap(m_nHeader[PH_MSGNO]); + } + else + { + return MSGNO_SEQ_OLD::unwrap(m_nHeader[PH_MSGNO]); + } +} + +bool CPacket::getRexmitFlag() const +{ + // return false; // + return MSGNO_REXMIT::unwrap(m_nHeader[PH_MSGNO]); +} + +EncryptionKeySpec CPacket::getMsgCryptoFlags() const +{ + return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(m_nHeader[PH_MSGNO])); +} + +EncryptionStatus CPacket::encrypt(HaiCrypt_Handle hcrypto) +{ + if ( !hcrypto ) + 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 ) + { + // -1: encryption failure + // 0: key not received yet + return ENCS_FAILED; + } else if (rc > 0) { + m_PacketVector[PV_DATA].iov_len = rc; + } + return ENCS_CLEAR; +} + +EncryptionStatus CPacket::decrypt(HaiCrypt_Handle hcrypto) +{ + if (getMsgCryptoFlags() == EK_NOENC) + 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) + + int rc = HaiCrypt_Rx_Data(hcrypto, (uint8_t *)m_nHeader.raw(), (uint8_t *)m_pcData, m_PacketVector[PV_DATA].iov_len); + if ( rc <= 0 ) + { + // -1: decryption failure + // 0: key not received yet + 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 */ + + // Decryption succeeded. Update flags. + m_nHeader[PH_MSGNO] &= ~MSGNO_ENCKEYSPEC::mask; // sets EK_NOENC to ENCKEYSPEC bits. + + 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 +{ + CPacket* pkt = new CPacket; + memcpy(pkt->m_nHeader, m_nHeader, HDR_SIZE); + pkt->m_pcData = new char[m_PacketVector[PV_DATA].iov_len]; + memcpy(pkt->m_pcData, m_pcData, m_PacketVector[PV_DATA].iov_len); + pkt->m_PacketVector[PV_DATA].iov_len = m_PacketVector[PV_DATA].iov_len; + + return pkt; +} + +#if ENABLE_LOGGING +std::string CPacket::MessageFlagStr() +{ + using namespace std; + + stringstream out; + + static const string boundary [] = { "PB_SUBSEQUENT", "PB_LAST", "PB_FIRST", "PB_SOLO" }; + static const string order [] = { "ORD_RELAXED", "ORD_REQUIRED" }; + static const string crypto [] = { "EK_NOENC", "EK_EVEN", "EK_ODD", "EK*ERROR" }; + static const string rexmit [] = { "SN_ORIGINAL", "SN_REXMIT" }; + + out << boundary[int(getMsgBoundary())] << " "; + out << order[int(getMsgOrderFlag())] << " "; + out << crypto[int(getMsgCryptoFlags())] << " "; + out << rexmit[int(getRexmitFlag())]; + + 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 new file mode 100644 index 000000000..377549699 --- /dev/null +++ b/srtcore/packet.h @@ -0,0 +1,435 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/02/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_PACKET_H__ +#define __UDT_PACKET_H__ + + +#include "udt.h" +#include "common.h" +#include "utilities.h" + +#include + +#ifdef WIN32 + struct iovec + { + int iov_len; + char* iov_base; + }; +#endif + +/// To define packets in order in the buffer. This is public due to being used in buffer. +enum PacketBoundary +{ + PB_SUBSEQUENT = 0, // 00 +/// 01: last packet of a message + PB_LAST = 1, // 01 +/// 10: first packet of a message + PB_FIRST = 2, // 10 +/// 11: solo message packet + PB_SOLO = 3, // 11 +}; + +// Breakdown of the PM_SEQNO field in the header: +// C| X X ... X, where: +typedef Bits<31> SEQNO_CONTROL; +// 1|T T T T T T T T T T T T T T T|E E...E +typedef Bits<30, 16> SEQNO_MSGTYPE; +typedef Bits<15, 0> SEQNO_EXTTYPE; +// 0|S S ... S +typedef Bits<30, 0> SEQNO_VALUE; + +// This bit cannot be used by SEQNO anyway, so it's additionally used +// in LOSSREPORT data specification to define that this value is the +// BEGIN value for a SEQNO range (to distinguish it from a SOLO loss SEQNO value). +const int32_t LOSSDATA_SEQNO_RANGE_FIRST = SEQNO_CONTROL::mask; + +// Just cosmetics for readability. +const int32_t LOSSDATA_SEQNO_RANGE_LAST = 0, LOSSDATA_SEQNO_SOLO = 0; + +inline int32_t CreateControlSeqNo(UDTMessageType type) +{ + return SEQNO_CONTROL::mask | SEQNO_MSGTYPE::wrap(size_t(type)); +} + +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 +typedef Bits<31, 30> MSGNO_PACKET_BOUNDARY; +typedef Bits<29> MSGNO_PACKET_INORDER; +typedef Bits<28, 27> MSGNO_ENCKEYSPEC; +#if 1 // can block rexmit flag +// New bit breakdown - rexmit flag supported. +typedef Bits<26> MSGNO_REXMIT; +typedef Bits<25, 0> MSGNO_SEQ; +// Old bit breakdown - no rexmit flag +typedef Bits<26, 0> MSGNO_SEQ_OLD; +// This symbol is for older SRT version, where the peer does not support the MSGNO_REXMIT flag. +// The message should be extracted as PMASK_MSGNO_SEQ, if REXMIT is supported, and PMASK_MSGNO_SEQ_OLD otherwise. + +const uint32_t PACKET_SND_NORMAL = 0, PACKET_SND_REXMIT = MSGNO_REXMIT::mask; + +#else +// Old bit breakdown - no rexmit flag +typedef Bits<26, 0> MSGNO_SEQ; +#endif + + +// constexpr in C++11 ! +inline int32_t PacketBoundaryBits(PacketBoundary o) { return MSGNO_PACKET_BOUNDARY::wrap(int32_t(o)); } + + +enum EncryptionKeySpec +{ + EK_NOENC = 0, + EK_EVEN = 1, + EK_ODD = 2 +}; + +enum EncryptionStatus +{ + ENCS_CLEAR = 0, + ENCS_FAILED = -1, + ENCS_NOTSUP = -2 +}; + +const int32_t PMASK_MSGNO_ENCKEYSPEC = MSGNO_ENCKEYSPEC::mask; +inline int32_t EncryptionKeyBits(EncryptionKeySpec f) +{ + return MSGNO_ENCKEYSPEC::wrap(int32_t(f)); +} +inline EncryptionKeySpec GetEncryptionKeySpec(int32_t msgno) +{ + return EncryptionKeySpec(MSGNO_ENCKEYSPEC::unwrap(msgno)); +} + +const int32_t PUMASK_SEQNO_PROBE = 0xF; + + +class CChannel; + +class CPacket +{ +friend class CChannel; +friend class CSndQueue; +friend class CRcvQueue; + +public: + CPacket(); + ~CPacket(); + + /// Get the payload or the control information field length. + /// @return the payload or the control information field length. + + int 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); + + /// Pack a Control packet. + /// @param pkttype [in] packet type filed. + /// @param lparam [in] pointer to the first data structure, explained by the packet type. + /// @param rparam [in] pointer to the second data structure, explained by the packet type. + /// @param size [in] size of rparam, in number of bytes; + + void pack(UDTMessageType pkttype, void* lparam = NULL, void* rparam = NULL, int size = 0); + + /// Read the packet vector. + /// @return Pointer to the packet vector. + + iovec* getPacketVector(); + + uint32_t* getHeader() { return m_nHeader; } + + /// Read the packet flag. + /// @return packet flag (0 or 1). + + // XXX DEPRECATED. Use isControl() instead + ATR_DEPRECATED + int getFlag() const + { + return isControl() ? 1 : 0; + } + + /// Read the packet type. + /// @return packet type filed (000 ~ 111). + + UDTMessageType getType() const; + + bool isControl(UDTMessageType type) const + { + return isControl() && type == getType(); + } + + bool isControl() const + { + // read bit 0 + return SEQNO_CONTROL::unwrap(m_nHeader[PH_SEQNO]); + } + + /// Read the extended packet type. + /// @return extended packet type filed (0x000 ~ 0xFFF). + + int getExtendedType() const; + + /// Read the ACK-2 seq. no. + /// @return packet header field (bit 16~31). + + int32_t getAckSeqNo() const; + uint16_t getControlFlags() const; + + // Note: this will return a stupid value, if the packet + // contains the control message + int32_t getSeqNo() const + { + return m_nHeader[PH_SEQNO]; + } + + /// Read the message boundary flag bit. + /// @return packet header field [1] (bit 0~1). + + PacketBoundary getMsgBoundary() const; + + /// Read the message inorder delivery flag bit. + /// @return packet header field [1] (bit 2). + + bool getMsgOrderFlag() const; + + /// Read the rexmit flag (true if the packet was sent due to retransmission). + /// If the peer does not support retransmission flag, the current agent cannot use it as well + /// (because the peer will understand this bit as a part of MSGNO field). + + bool getRexmitFlag() const; + + /// Read the message sequence number. + /// @return packet header field [1] + + int32_t getMsgSeq(bool has_rexmit) const; + + /// Read the message crypto key bits. + /// @return packet header field [1] (bit 3~4). + + EncryptionKeySpec getMsgCryptoFlags() const; + + /// Encrypt packet if crypto context present + /// @param hcrypto HaiCrypt handle. + /// @retval -1 encryption enabled and failed. + /// @retval 0 encryption deferred (parallel processing). + /// @retval >0 bytes in packet (clear text, encrypted current or older (deferred) packet). + + + EncryptionStatus encrypt(HaiCrypt_Handle hcrypto); + + /// Decrypt packet if crypto context present + /// @param hcrypto HaiCrypt handle. + /// @retval -1 packet encrypted but no crypto context or decryption failed. + /// @retval 0 decryption deferred (parallel processing). + /// @retval >0 bytes in packet (clear text oo decrypted current or older (deferred) packet). + + + 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). + + uint32_t getMsgTimeStamp() const; + +#ifdef SRT_DEBUG_TSBPD_WRAP //Receiver + static const uint32_t MAX_TIMESTAMP = 0x07FFFFFF; //27 bit fast wraparound for tests (~2m15s) +#else + static const uint32_t MAX_TIMESTAMP = 0xFFFFFFFF; //Full 32 bit (01h11m35s) +#endif + +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. + + CPacket* clone() const; + + enum PacketHeaderFields + { + PH_FIRST = 0, // Must be first, this is for loops + PH_SEQNO = 0, //< sequence number + PH_MSGNO = 1, //< message number + PH_TIMESTAMP = 2, //< time stamp + PH_ID = 3, //< socket ID + // Must be the last value - this is size of all, not a field id + PH_SIZE = 4 + }; + + //static const size_t PH_SIZE = 4; + enum PacketVectorFields + { + PV_HEADER = 0, + PV_DATA = 1, + + PV_SIZE = 2 + }; + +protected: + // Length in bytes + + // DynamicStruct is the same as array of given type and size, just it + // enforces that you index it using a symbol from symbolic enum type, not by a bare integer. + + typedef DynamicStruct HEADER_TYPE; + HEADER_TYPE m_nHeader; //< The 128-bit header field + + //uint32_t m_nHeader[PH_SIZE]; //< The 128-bit header field + iovec m_PacketVector[PV_SIZE]; //< The 2-demension vector of UDT packet [header, data] + + int32_t __pad; + +protected: + CPacket& operator=(const CPacket&); + +public: + + int32_t& m_iSeqNo; // alias: sequence number + int32_t& m_iMsgNo; // alias: message number + int32_t& m_iTimeStamp; // alias: timestamp + int32_t& m_iID; // alias: socket ID + char*& m_pcData; // alias: data/control information + + //static const int m_iPktHdrSize; // packet header size + static const size_t HDR_SIZE = sizeof(HEADER_TYPE); // packet header size + + // Used in many computations + 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(); + + int serialize(char* buf, int& size); + int deserialize(const char* buf, int 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 int m_iContentSize; // Size of hand shake data + +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 +}; + + +#endif diff --git a/srtcore/queue.cpp b/srtcore/queue.cpp new file mode 100644 index 000000000..f75a9b971 --- /dev/null +++ b/srtcore/queue.cpp @@ -0,0 +1,1416 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 05/05/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifdef WIN32 + #include + #include + #ifdef LEGACY_WIN32 + #include + #endif +#endif +#include + +#include "common.h" +#include "core.h" +#include "netinet_any.h" +#include "threadname.h" +#include "logging.h" +#include "queue.h" + +using namespace std; + +CUnitQueue::CUnitQueue(): +m_pQEntry(NULL), +m_pCurrQueue(NULL), +m_pLastQueue(NULL), +m_iSize(0), +m_iCount(0), +m_iMSS(), +m_iIPversion() +{ +} + +CUnitQueue::~CUnitQueue() +{ + CQEntry* p = m_pQEntry; + + while (p != NULL) + { + delete [] p->m_pUnit; + delete [] p->m_pBuffer; + + CQEntry* q = p; + if (p == m_pLastQueue) + p = NULL; + else + p = p->m_pNext; + delete q; + } +} + +int CUnitQueue::init(int size, int mss, int version) +{ + CQEntry* tempq = NULL; + CUnit* tempu = NULL; + char* tempb = NULL; + + try + { + tempq = new CQEntry; + tempu = new CUnit [size]; + tempb = new char [size * mss]; + } + catch (...) + { + delete tempq; + delete [] tempu; + delete [] tempb; + + return -1; + } + + for (int i = 0; i < size; ++ i) + { + tempu[i].m_iFlag = CUnit::FREE; + tempu[i].m_Packet.m_pcData = tempb + i * mss; + } + tempq->m_pUnit = tempu; + tempq->m_pBuffer = tempb; + tempq->m_iSize = size; + + m_pQEntry = m_pCurrQueue = m_pLastQueue = tempq; + m_pQEntry->m_pNext = m_pQEntry; + + m_pAvailUnit = m_pCurrQueue->m_pUnit; + + m_iSize = size; + m_iMSS = mss; + m_iIPversion = version; + + return 0; +} + +int CUnitQueue::increase() +{ + // adjust/correct m_iCount + int real_count = 0; + CQEntry* p = m_pQEntry; + while (p != NULL) + { + CUnit* u = p->m_pUnit; + for (CUnit* end = u + p->m_iSize; u != end; ++ u) + if (u->m_iFlag != CUnit::FREE) + ++ real_count; + + if (p == m_pLastQueue) + p = NULL; + else + p = p->m_pNext; + } + m_iCount = real_count; + if (double(m_iCount) / m_iSize < 0.9) + return -1; + + CQEntry* tempq = NULL; + CUnit* tempu = NULL; + char* tempb = NULL; + + // all queues have the same size + int size = m_pQEntry->m_iSize; + + try + { + tempq = new CQEntry; + tempu = new CUnit [size]; + tempb = new char [size * m_iMSS]; + } + catch (...) + { + delete tempq; + delete [] tempu; + delete [] tempb; + + return -1; + } + + for (int i = 0; i < size; ++ i) + { + tempu[i].m_iFlag = CUnit::FREE; + tempu[i].m_Packet.m_pcData = tempb + i * m_iMSS; + } + tempq->m_pUnit = tempu; + tempq->m_pBuffer = tempb; + tempq->m_iSize = size; + + m_pLastQueue->m_pNext = tempq; + m_pLastQueue = tempq; + m_pLastQueue->m_pNext = m_pQEntry; + + m_iSize += size; + + return 0; +} + +int CUnitQueue::shrink() +{ + // currently queue cannot be shrunk. + return -1; +} + +CUnit* CUnitQueue::getNextAvailUnit() +{ + if (m_iCount * 10 > m_iSize * 9) + increase(); + + if (m_iCount >= m_iSize) + return NULL; + + CQEntry* entrance = m_pCurrQueue; + + do + { + for (CUnit* sentinel = m_pCurrQueue->m_pUnit + m_pCurrQueue->m_iSize - 1; m_pAvailUnit != sentinel; ++ m_pAvailUnit) + if (m_pAvailUnit->m_iFlag == CUnit::FREE) + return m_pAvailUnit; + + if (m_pCurrQueue->m_pUnit->m_iFlag == CUnit::FREE) + { + m_pAvailUnit = m_pCurrQueue->m_pUnit; + return m_pAvailUnit; + } + + m_pCurrQueue = m_pCurrQueue->m_pNext; + m_pAvailUnit = m_pCurrQueue->m_pUnit; + } while (m_pCurrQueue != entrance); + + increase(); + + return NULL; +} + + +CSndUList::CSndUList(): + m_pHeap(NULL), + m_iArrayLength(4096), + m_iLastEntry(-1), + m_ListLock(), + m_pWindowLock(NULL), + m_pWindowCond(NULL), + m_pTimer(NULL) +{ + m_pHeap = new CSNode*[m_iArrayLength]; + pthread_mutex_init(&m_ListLock, NULL); +} + +CSndUList::~CSndUList() +{ + delete [] m_pHeap; + pthread_mutex_destroy(&m_ListLock); +} + +void CSndUList::insert(int64_t ts, const CUDT* u) +{ + CGuard listguard(m_ListLock); + + // increase the heap array size if necessary + if (m_iLastEntry == m_iArrayLength - 1) + { + CSNode** temp = NULL; + + try + { + temp = new CSNode*[m_iArrayLength * 2]; + } + catch(...) + { + return; + } + + memcpy(temp, m_pHeap, sizeof(CSNode*) * m_iArrayLength); + m_iArrayLength *= 2; + delete [] m_pHeap; + m_pHeap = temp; + } + + insert_(ts, u); +} + +void CSndUList::update(const CUDT* u, bool reschedule) +{ + CGuard listguard(m_ListLock); + + CSNode* n = u->m_pSNode; + + if (n->m_iHeapLoc >= 0) + { + if (!reschedule) + return; + + if (n->m_iHeapLoc == 0) + { + n->m_llTimeStamp = 1; + m_pTimer->interrupt(); + return; + } + + remove_(u); + } + + insert_(1, u); +} + +int CSndUList::pop(sockaddr*& addr, CPacket& pkt) +{ + CGuard listguard(m_ListLock); + + if (-1 == m_iLastEntry) + return -1; + + // no pop until the next schedulled time + uint64_t ts; + CTimer::rdtsc(ts); + if (ts < m_pHeap[0]->m_llTimeStamp) + return -1; + + CUDT* u = m_pHeap[0]->m_pUDT; + remove_(u); + + if (!u->m_bConnected || u->m_bBroken) + return -1; + + // pack a packet from the socket + if (u->packData(pkt, ts) <= 0) + return -1; + + addr = u->m_pPeerAddr; + + // insert a new entry, ts is the next processing time + if (ts > 0) + insert_(ts, u); + + return 1; +} + +void CSndUList::remove(const CUDT* u) +{ + CGuard listguard(m_ListLock); + + remove_(u); +} + +uint64_t CSndUList::getNextProcTime() +{ + CGuard listguard(m_ListLock); + + if (-1 == m_iLastEntry) + return 0; + + return m_pHeap[0]->m_llTimeStamp; +} + +void CSndUList::insert_(int64_t ts, const CUDT* u) +{ + CSNode* n = u->m_pSNode; + + // do not insert repeated node + if (n->m_iHeapLoc >= 0) + return; + + m_iLastEntry ++; + m_pHeap[m_iLastEntry] = n; + n->m_llTimeStamp = ts; + + int q = m_iLastEntry; + int p = q; + while (p != 0) + { + p = (q - 1) >> 1; + if (m_pHeap[p]->m_llTimeStamp > m_pHeap[q]->m_llTimeStamp) + { + CSNode* t = m_pHeap[p]; + m_pHeap[p] = m_pHeap[q]; + m_pHeap[q] = t; + t->m_iHeapLoc = q; + q = p; + } + else + break; + } + + n->m_iHeapLoc = q; + + // an earlier event has been inserted, wake up sending worker + if (n->m_iHeapLoc == 0) + m_pTimer->interrupt(); + + // first entry, activate the sending queue + if (0 == m_iLastEntry) + { + pthread_mutex_lock(m_pWindowLock); + pthread_cond_signal(m_pWindowCond); + pthread_mutex_unlock(m_pWindowLock); + } +} + +void CSndUList::remove_(const CUDT* u) +{ + CSNode* n = u->m_pSNode; + + if (n->m_iHeapLoc >= 0) + { + // remove the node from heap + m_pHeap[n->m_iHeapLoc] = m_pHeap[m_iLastEntry]; + m_iLastEntry --; + m_pHeap[n->m_iHeapLoc]->m_iHeapLoc = n->m_iHeapLoc; + + int q = n->m_iHeapLoc; + int p = q * 2 + 1; + while (p <= m_iLastEntry) + { + if ((p + 1 <= m_iLastEntry) && (m_pHeap[p]->m_llTimeStamp > m_pHeap[p + 1]->m_llTimeStamp)) + p ++; + + if (m_pHeap[q]->m_llTimeStamp > m_pHeap[p]->m_llTimeStamp) + { + CSNode* t = m_pHeap[p]; + m_pHeap[p] = m_pHeap[q]; + m_pHeap[p]->m_iHeapLoc = p; + m_pHeap[q] = t; + m_pHeap[q]->m_iHeapLoc = q; + + q = p; + p = q * 2 + 1; + } + else + break; + } + + n->m_iHeapLoc = -1; + } + + // the only event has been deleted, wake up immediately + if (0 == m_iLastEntry) + m_pTimer->interrupt(); +} + +// +CSndQueue::CSndQueue(): +m_WorkerThread(), +m_pSndUList(NULL), +m_pChannel(NULL), +m_pTimer(NULL), +m_WindowLock(), +m_WindowCond(), +m_bClosing(false), +m_ExitCond() +{ + pthread_cond_init(&m_WindowCond, NULL); + pthread_mutex_init(&m_WindowLock, NULL); +} + +CSndQueue::~CSndQueue() +{ + m_bClosing = true; + + if(m_pTimer != NULL) + { + m_pTimer->interrupt(); + } + + pthread_mutex_lock(&m_WindowLock); + pthread_cond_signal(&m_WindowCond); + pthread_mutex_unlock(&m_WindowLock); + if (!pthread_equal(m_WorkerThread, pthread_t())) + pthread_join(m_WorkerThread, NULL); + pthread_cond_destroy(&m_WindowCond); + pthread_mutex_destroy(&m_WindowLock); + + delete m_pSndUList; +} + +void CSndQueue::init(CChannel* c, CTimer* t) +{ + m_pChannel = c; + m_pTimer = t; + m_pSndUList = new CSndUList; + m_pSndUList->m_pWindowLock = &m_WindowLock; + m_pSndUList->m_pWindowCond = &m_WindowCond; + m_pSndUList->m_pTimer = m_pTimer; + + ThreadName tn("SRT:SndQ:worker"); + if (0 != pthread_create(&m_WorkerThread, NULL, CSndQueue::worker, this)) + { + m_WorkerThread = pthread_t(); + throw CUDTException(MJ_SYSTEMRES, MN_THREAD); + } +} + +#ifdef SRT_ENABLE_IPOPTS +int CSndQueue::getIpTTL() const +{ + return m_pChannel ? m_pChannel->getIpTTL() : -1; +} + +int CSndQueue::getIpToS() const +{ + return m_pChannel ? m_pChannel->getIpToS() : -1; +} +#endif + +void* CSndQueue::worker(void* param) +{ + CSndQueue* self = (CSndQueue*)param; + + THREAD_STATE_INIT("SRT Tx Queue"); + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + CTimer::rdtsc(self->m_ullDbgTime); + self->m_ullDbgPeriod = 5000000LL * CTimer::getCPUFrequency(); + self->m_ullDbgTime += self->m_ullDbgPeriod; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + + while (!self->m_bClosing) + { + uint64_t ts = self->m_pSndUList->getNextProcTime(); + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lIteration++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + + if (ts > 0) + { + // wait until next processing time of the first socket on the list + uint64_t currtime; + CTimer::rdtsc(currtime); + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + if (self->m_ullDbgTime <= currtime) { + fprintf(stdout, "SndQueue %lu slt:%lu nrp:%lu snt:%lu nrt:%lu ctw:%lu\n", + self->m_WorkerStats.lIteration, + self->m_WorkerStats.lSleepTo, + self->m_WorkerStats.lNotReadyPop, + self->m_WorkerStats.lSendTo, + self->m_WorkerStats.lNotReadyTs, + self->m_WorkerStats.lCondWait); + memset(&self->m_WorkerStats, 0, sizeof(self->m_WorkerStats)); + self->m_ullDbgTime = currtime + self->m_ullDbgPeriod; + } +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + + THREAD_PAUSED(); + if (currtime < ts) + { + self->m_pTimer->sleepto(ts); + +#if defined(HAI_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lSleepTo++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + } + THREAD_RESUMED(); + + // it is time to send the next pkt + sockaddr* addr; + CPacket pkt; + if (self->m_pSndUList->pop(addr, pkt) < 0) + { + continue; + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lNotReadyPop++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + } + if ( pkt.isControl() ) + { + LOGC(mglog.Debug) << self->CONID() << "chn:SENDING: " << MessageTypeStr(pkt.getType(), pkt.getExtendedType()); + } + else + { + LOGC(dlog.Debug) << self->CONID() << "chn:SENDING SIZE " << pkt.getLength() << " SEQ: " << pkt.getSeqNo(); + } + self->m_pChannel->sendto(addr, pkt); + +#if defined(HAI_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lSendTo++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + } + else + { +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lNotReadyTs++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + + // wait here if there is no sockets with data to be sent + THREAD_PAUSED(); + pthread_mutex_lock(&self->m_WindowLock); + if (!self->m_bClosing && (self->m_pSndUList->m_iLastEntry < 0)) { + pthread_cond_wait(&self->m_WindowCond, &self->m_WindowLock); + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE) + self->m_WorkerStats.lCondWait++; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + } + THREAD_RESUMED(); + pthread_mutex_unlock(&self->m_WindowLock); + } + } + + THREAD_EXIT(); + return NULL; +} + +int CSndQueue::sendto(const sockaddr* addr, CPacket& packet) +{ + // send out the packet immediately (high priority), this is a control packet + m_pChannel->sendto(addr, packet); + return packet.getLength(); +} + + +// +CRcvUList::CRcvUList(): +m_pUList(NULL), +m_pLast(NULL) +{ +} + +CRcvUList::~CRcvUList() +{ +} + +void CRcvUList::insert(const CUDT* u) +{ + CRNode* n = u->m_pRNode; + CTimer::rdtsc(n->m_llTimeStamp); + + if (NULL == m_pUList) + { + // empty list, insert as the single node + n->m_pPrev = n->m_pNext = NULL; + m_pLast = m_pUList = n; + + return; + } + + // always insert at the end for RcvUList + n->m_pPrev = m_pLast; + n->m_pNext = NULL; + m_pLast->m_pNext = n; + m_pLast = n; +} + +void CRcvUList::remove(const CUDT* u) +{ + CRNode* n = u->m_pRNode; + + if (!n->m_bOnList) + return; + + if (NULL == n->m_pPrev) + { + // n is the first node + m_pUList = n->m_pNext; + if (NULL == m_pUList) + m_pLast = NULL; + else + m_pUList->m_pPrev = NULL; + } + else + { + n->m_pPrev->m_pNext = n->m_pNext; + if (NULL == n->m_pNext) + { + // n is the last node + m_pLast = n->m_pPrev; + } + else + n->m_pNext->m_pPrev = n->m_pPrev; + } + + n->m_pNext = n->m_pPrev = NULL; +} + +void CRcvUList::update(const CUDT* u) +{ + CRNode* n = u->m_pRNode; + + if (!n->m_bOnList) + return; + + CTimer::rdtsc(n->m_llTimeStamp); + + // if n is the last node, do not need to change + if (NULL == n->m_pNext) + return; + + if (NULL == n->m_pPrev) + { + m_pUList = n->m_pNext; + m_pUList->m_pPrev = NULL; + } + else + { + n->m_pPrev->m_pNext = n->m_pNext; + n->m_pNext->m_pPrev = n->m_pPrev; + } + + n->m_pPrev = m_pLast; + n->m_pNext = NULL; + m_pLast->m_pNext = n; + m_pLast = n; +} + +// +CHash::CHash(): +m_pBucket(NULL), +m_iHashSize(0) +{ +} + +CHash::~CHash() +{ + for (int i = 0; i < m_iHashSize; ++ i) + { + CBucket* b = m_pBucket[i]; + while (NULL != b) + { + CBucket* n = b->m_pNext; + delete b; + b = n; + } + } + + delete [] m_pBucket; +} + +void CHash::init(int size) +{ + m_pBucket = new CBucket* [size]; + + for (int i = 0; i < size; ++ i) + m_pBucket[i] = NULL; + + m_iHashSize = size; +} + +CUDT* CHash::lookup(int32_t id) +{ + // simple hash function (% hash table size); suitable for socket descriptors + CBucket* b = m_pBucket[id % m_iHashSize]; + + while (NULL != b) + { + if (id == b->m_iID) + return b->m_pUDT; + b = b->m_pNext; + } + + return NULL; +} + +void CHash::insert(int32_t id, CUDT* u) +{ + CBucket* b = m_pBucket[id % m_iHashSize]; + + CBucket* n = new CBucket; + n->m_iID = id; + n->m_pUDT = u; + n->m_pNext = b; + + m_pBucket[id % m_iHashSize] = n; +} + +void CHash::remove(int32_t id) +{ + CBucket* b = m_pBucket[id % m_iHashSize]; + CBucket* p = NULL; + + while (NULL != b) + { + if (id == b->m_iID) + { + if (NULL == p) + m_pBucket[id % m_iHashSize] = b->m_pNext; + else + p->m_pNext = b->m_pNext; + + delete b; + + return; + } + + p = b; + b = b->m_pNext; + } +} + + +// +CRendezvousQueue::CRendezvousQueue(): +m_lRendezvousID(), +m_RIDVectorLock() +{ + pthread_mutex_init(&m_RIDVectorLock, NULL); +} + +CRendezvousQueue::~CRendezvousQueue() +{ + pthread_mutex_destroy(&m_RIDVectorLock); + + for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++ i) + { + if (AF_INET == i->m_iIPversion) + delete (sockaddr_in*)i->m_pPeerAddr; + else + delete (sockaddr_in6*)i->m_pPeerAddr; + } + + m_lRendezvousID.clear(); +} + +void CRendezvousQueue::insert(const UDTSOCKET& id, CUDT* u, int ipv, const sockaddr* addr, uint64_t ttl) +{ + CGuard vg(m_RIDVectorLock); + + CRL r; + r.m_iID = id; + r.m_pUDT = u; + r.m_iIPversion = ipv; + r.m_pPeerAddr = (AF_INET == ipv) ? (sockaddr*)new sockaddr_in : (sockaddr*)new sockaddr_in6; + memcpy(r.m_pPeerAddr, addr, (AF_INET == ipv) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6)); + r.m_ullTTL = ttl; + + m_lRendezvousID.push_back(r); +} + +void CRendezvousQueue::remove(const UDTSOCKET& id) +{ + CGuard vg(m_RIDVectorLock); + + for (list::iterator i = m_lRendezvousID.begin(); i != m_lRendezvousID.end(); ++ i) + { + if (i->m_iID == id) + { + if (AF_INET == i->m_iIPversion) + delete (sockaddr_in*)i->m_pPeerAddr; + else + delete (sockaddr_in6*)i->m_pPeerAddr; + + m_lRendezvousID.erase(i); + + return; + } + } +} + +CUDT* CRendezvousQueue::retrieve(const sockaddr* addr, UDTSOCKET& id) +{ + CGuard vg(m_RIDVectorLock); + + // 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))) + { + id = i->m_iID; + return i->m_pUDT; + } + } + + return NULL; +} + +void CRendezvousQueue::updateConnStatus() +{ + 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 */ + { + // avoid sending too many requests, at most 1 request per 250ms + if (CTimer::getTime() - i->m_pUDT->m_llLastReqTime > 250000) + { + if (CTimer::getTime() >= i->m_ullTTL) + { + // 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) + * and may crash here on next pass. + */ + if (AF_INET == i->m_iIPversion) + delete (sockaddr_in*)i->m_pPeerAddr; + else + 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; + } +#ifdef HAI_PATCH + i++; +#endif /* HAI_PATCH */ + } +} + +// +CRcvQueue::CRcvQueue(): + m_WorkerThread(), + m_UnitQueue(), + m_pRcvUList(NULL), + m_pHash(NULL), + m_pChannel(NULL), + m_pTimer(NULL), + m_iPayloadSize(), + m_bClosing(false), + m_ExitCond(), + m_LSLock(), + m_pListener(NULL), + m_pRendezvousQueue(NULL), + m_vNewEntry(), + m_IDLock(), + m_mBuffer(), + m_PassLock(), + m_PassCond() +{ + pthread_mutex_init(&m_PassLock, NULL); + pthread_cond_init(&m_PassCond, NULL); + pthread_mutex_init(&m_LSLock, NULL); + pthread_mutex_init(&m_IDLock, NULL); +} + +CRcvQueue::~CRcvQueue() +{ + m_bClosing = true; + if (!pthread_equal(m_WorkerThread, pthread_t())) + pthread_join(m_WorkerThread, NULL); + pthread_mutex_destroy(&m_PassLock); + pthread_cond_destroy(&m_PassCond); + pthread_mutex_destroy(&m_LSLock); + pthread_mutex_destroy(&m_IDLock); + + delete m_pRcvUList; + delete m_pHash; + delete m_pRendezvousQueue; + + // remove all queued messages + for (map >::iterator i = m_mBuffer.begin(); i != m_mBuffer.end(); ++ i) + { + while (!i->second.empty()) + { + CPacket* pkt = i->second.front(); + delete [] pkt->m_pcData; + delete pkt; + i->second.pop(); + } + } +} + +void CRcvQueue::init(int qsize, int payload, int version, int hsize, CChannel* cc, CTimer* t) +{ + m_iPayloadSize = payload; + + m_UnitQueue.init(qsize, payload, version); + + m_pHash = new CHash; + m_pHash->init(hsize); + + m_pChannel = cc; + m_pTimer = t; + + m_pRcvUList = new CRcvUList; + m_pRendezvousQueue = new CRendezvousQueue; + + ThreadName tn("SRT:RcvQ:worker"); + if (0 != pthread_create(&m_WorkerThread, NULL, CRcvQueue::worker, this)) + { + m_WorkerThread = pthread_t(); + throw CUDTException(MJ_SYSTEMRES, MN_THREAD); + } +} + +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; + while (!self->m_bClosing) + { + if ( self->worker_RetrieveUnit(id, unit, &sa) ) + { + if ( id < 0 ) + { + // User error on peer. May log something, but generally can only ignore it. + // XXX Think maybe about sending some "connection rejection response". + LOGC(mglog.Debug) << self->CONID() << "RECEIVED negative socket id '" << id << "', rejecting (POSSIBLE ATTACK)"; + continue; + } + + // 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); + } + 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); + } + } + +//TIMER_CHECK: + // take care of the timing event for all UDT sockets + + uint64_t currtime; + CTimer::rdtsc(currtime); + + CRNode* ul = self->m_pRcvUList->m_pUList; + uint64_t ctime = currtime - 100000 * CTimer::getCPUFrequency(); + while ((NULL != ul) && (ul->m_llTimeStamp < ctime)) + { + CUDT* u = ul->m_pUDT; + + if (u->m_bConnected && !u->m_bBroken && !u->m_bClosing) + { + u->checkTimers(); + self->m_pRcvUList->update(u); + } + else + { + // the socket must be removed from Hash table first, then RcvUList + self->m_pHash->remove(u->m_SocketID); + self->m_pRcvUList->remove(u); + u->m_pRNode->m_bOnList = false; + } + + ul = self->m_pRcvUList->m_pUList; + } + + // Check connection requests status for all sockets in the RendezvousQueue. + self->m_pRendezvousQueue->updateConnStatus(); + } + + /* + 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) +{ +#ifdef NO_BUSY_WAITING + m_pTimer->tick(); +#endif + + // check waiting list, if new socket, insert it to the list + while (ifNewEntry()) + { + CUDT* ne = getNewEntry(); + if (ne) + { + m_pRcvUList->insert(ne); + m_pHash->insert(ne->m_SocketID, ne); + } + } + // find next available slot for incoming packet + unit = m_UnitQueue.getNextAvailUnit(); + if (!unit) + { + // no space, skip this packet + CPacket temp; + temp.m_pcData = new char[m_iPayloadSize]; + temp.setLength(m_iPayloadSize); + THREAD_PAUSED(); + 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; + +#endif + delete [] temp.m_pcData; + return false; + } + + 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) + { + THREAD_RESUMED(); + return false; + } + THREAD_RESUMED(); + id = unit->m_Packet.m_iID; + return true; +} + +void CRcvQueue::worker_ProcessConnectionRequest(CUnit* unit, const sockaddr* addr) +{ + // 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. + bool have_listener = false; + { + CGuard cg(m_LSLock); + if (m_pListener) + { + 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 + // function is being called, so it's hard to say what the + // returned value had to serve for. + + // The tricky part is that this is something done "under the hood"; + // if any problem occurs during this process, then this will simply + // drop the connection request. The only user process that is connected + // to it is accept() call (or connect() in case of rendezvous), but + // the system cannot return an error from accept() just because some + // user was attempting to connect, but was too stupid to properly + // formulate the connection request. + + // The only thing that could be done in case when the "listen" call + // fails, is to probably send a short information packet (once; it's + // not so important to make it reach the target) that the connection + // has been rejected due to incorrectly formulated request. However + // just in order to send anything in response, the actual sender must + // be properly known, and this isn't the case of incorrectly formulated + // connection request. So, we can only say sorry to ourselves and + // do nothing. + + have_listener = true; + } + } + + if ( !have_listener ) + worker_TryConnectRendezvous(0, unit, addr); // 0 id because this is connection request + else + { + LOGC(mglog.Note) << CONID() << "listener received connection request from: " << SockaddrToString(addr); + } +} + +void 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); + } + + // 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)"; + // This came not from the address that is the peer associated + // with the socket. Reject. + return; + } + + if (!u->m_bConnected || u->m_bBroken || u->m_bClosing) + { + // The socket is currently in the process of being disconnected + // or destroyed. Ignore. + // XXX send UMSG_SHUTDOWN in this case? + // XXX May it require mutex protection? + return; + } + + if (unit->m_Packet.isControl()) + u->processCtrl(unit->m_Packet); + else + u->processData(unit); + + u->checkTimers(); + m_pRcvUList->update(u); +} + +void CRcvQueue::worker_TryConnectRendezvous(int32_t id, CUnit* unit, const sockaddr* addr) +{ + CUDT* u = m_pRendezvousQueue->retrieve(addr, 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"; + else + LOGC(mglog.Debug) << CONID() << "Rendezvous: no sockets expect socket " << id << " from " << CIPAddress::show(addr) << " - POSSIBLE ATTACK"; + return; + } + + // 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()); + } +} + + +int CRcvQueue::recvfrom(int32_t id, CPacket& packet) +{ + CGuard bufferlock(m_PassLock); + + map >::iterator i = m_mBuffer.find(id); + + if (i == m_mBuffer.end()) + { + uint64_t now = CTimer::getTime(); + timespec timeout; + + timeout.tv_sec = now / 1000000 + 1; + timeout.tv_nsec = (now % 1000000) * 1000; + + pthread_cond_timedwait(&m_PassCond, &m_PassLock, &timeout); + + i = m_mBuffer.find(id); + if (i == m_mBuffer.end()) + { + packet.setLength(-1); + return -1; + } + } + + // retrieve the earliest packet + CPacket* newpkt = i->second.front(); + + if (packet.getLength() < newpkt->getLength()) + { + packet.setLength(-1); + return -1; + } + + // copy packet content + // XXX Check if this wouldn't be better done by providing + // copy constructor for DynamicStruct. + memcpy(packet.m_nHeader, newpkt->m_nHeader, CPacket::HDR_SIZE); + memcpy(packet.m_pcData, newpkt->m_pcData, newpkt->getLength()); + packet.setLength(newpkt->getLength()); + + delete [] newpkt->m_pcData; + delete newpkt; + + // remove this message from queue, + // if no more messages left for this socket, release its data structure + i->second.pop(); + if (i->second.empty()) + m_mBuffer.erase(i); + + return packet.getLength(); +} + +int CRcvQueue::setListener(CUDT* u) +{ + CGuard lslock(m_LSLock); + + if (NULL != m_pListener) + return -1; + + m_pListener = u; + return 0; +} + +void CRcvQueue::removeListener(const CUDT* u) +{ + CGuard lslock(m_LSLock); + + if (u == m_pListener) + m_pListener = NULL; +} + +void CRcvQueue::registerConnector(const UDTSOCKET& id, CUDT* u, int ipv, const sockaddr* addr, uint64_t ttl) +{ + m_pRendezvousQueue->insert(id, u, ipv, addr, ttl); +} + +void CRcvQueue::removeConnector(const UDTSOCKET& id) +{ + m_pRendezvousQueue->remove(id); + + 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); + } +} + +void CRcvQueue::setNewEntry(CUDT* u) +{ + CGuard listguard(m_IDLock); + m_vNewEntry.push_back(u); +} + +bool CRcvQueue::ifNewEntry() +{ + return !(m_vNewEntry.empty()); +} + +CUDT* CRcvQueue::getNewEntry() +{ + CGuard listguard(m_IDLock); + + if (m_vNewEntry.empty()) + return NULL; + + CUDT* u = (CUDT*)*(m_vNewEntry.begin()); + m_vNewEntry.erase(m_vNewEntry.begin()); + + return u; +} + +void CRcvQueue::storePkt(int32_t id, CPacket* pkt) +{ + CGuard bufferlock(m_PassLock); + + map >::iterator i = m_mBuffer.find(id); + + if (i == m_mBuffer.end()) + { + m_mBuffer[id].push(pkt); + pthread_cond_signal(&m_PassCond); + } + else + { + //avoid storing too many packets, in case of malfunction or attack + if (i->second.size() > 16) + return; + + i->second.push(pkt); + } +} diff --git a/srtcore/queue.h b/srtcore/queue.h new file mode 100644 index 000000000..f0bc84a8a --- /dev/null +++ b/srtcore/queue.h @@ -0,0 +1,513 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/12/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + + +#ifndef __UDT_QUEUE_H__ +#define __UDT_QUEUE_H__ + +#include "channel.h" +#include "common.h" +#include "packet.h" +#include +#include +#include +#include + +class CUDT; + +struct CUnit +{ + CPacket m_Packet; // packet + enum Flag { FREE = 0, GOOD = 1, PASSACK = 2, DROPPED = 3 }; + Flag m_iFlag; // 0: free, 1: occupied, 2: msg read but not freed (out-of-order), 3: msg dropped +}; + +class CUnitQueue +{ +friend class CRcvQueue; +friend class CRcvBuffer; + +public: + CUnitQueue(); + ~CUnitQueue(); + +public: + + /// Initialize the unit queue. + /// @param [in] size queue size + /// @param [in] mss maximum segment size + /// @param [in] version IP version + /// @return 0: success, -1: failure. + + int init(int size, int mss, int version); + + /// Increase (double) the unit queue size. + /// @return 0: success, -1: failure. + + int increase(); + + /// Decrease (halve) the unit queue size. + /// @return 0: success, -1: failure. + + int shrink(); + + /// find an available unit for incoming packet. + /// @return Pointer to the available unit, NULL if not found. + + CUnit* getNextAvailUnit(); + +private: + struct CQEntry + { + CUnit* m_pUnit; // unit queue + char* m_pBuffer; // data buffer + int m_iSize; // size of each queue + + CQEntry* m_pNext; + } + *m_pQEntry, // pointer to the first unit queue + *m_pCurrQueue, // pointer to the current available queue + *m_pLastQueue; // pointer to the last unit queue + + CUnit* m_pAvailUnit; // recent available unit + + int m_iSize; // total size of the unit queue, in number of packets + int m_iCount; // total number of valid packets in the queue + + int m_iMSS; // unit buffer size + int m_iIPversion; // IP version + +private: + CUnitQueue(const CUnitQueue&); + CUnitQueue& operator=(const CUnitQueue&); +}; + +struct CSNode +{ + CUDT* m_pUDT; // Pointer to the instance of CUDT socket + uint64_t m_llTimeStamp; // Time Stamp + + int m_iHeapLoc; // location on the heap, -1 means not on the heap +}; + +class CSndUList +{ +friend class CSndQueue; + +public: + CSndUList(); + ~CSndUList(); + +public: + + /// Insert a new UDT instance into the list. + /// @param [in] ts time stamp: next processing time + /// @param [in] u pointer to the UDT instance + + void insert(int64_t ts, const CUDT* u); + + /// Update the timestamp of the UDT instance on the list. + /// @param [in] u pointer to the UDT instance + /// @param [in] resechedule if the timestampe shoudl be rescheduled + + void update(const CUDT* u, bool reschedule = true); + + /// Retrieve the next packet and peer address from the first entry, and reschedule it in the queue. + /// @param [out] addr destination address of the next packet + /// @param [out] pkt the next packet to be sent + /// @return 1 if successfully retrieved, -1 if no packet found. + + int pop(sockaddr*& addr, CPacket& pkt); + + /// Remove UDT instance from the list. + /// @param [in] u pointer to the UDT instance + + void remove(const CUDT* u); + + /// Retrieve the next scheduled processing time. + /// @return Scheduled processing time of the first UDT socket in the list. + + uint64_t getNextProcTime(); + +private: + void insert_(int64_t ts, const CUDT* u); + void remove_(const CUDT* u); + +private: + CSNode** m_pHeap; // The heap array + int m_iArrayLength; // physical length of the array + int m_iLastEntry; // position of last entry on the heap array + + pthread_mutex_t m_ListLock; + + pthread_mutex_t* m_pWindowLock; + pthread_cond_t* m_pWindowCond; + + CTimer* m_pTimer; + +private: + CSndUList(const CSndUList&); + CSndUList& operator=(const CSndUList&); +}; + +struct CRNode +{ + CUDT* m_pUDT; // Pointer to the instance of CUDT socket + uint64_t m_llTimeStamp; // Time Stamp + + CRNode* m_pPrev; // previous link + CRNode* m_pNext; // next link + + bool m_bOnList; // if the node is already on the list +}; + +class CRcvUList +{ +public: + CRcvUList(); + ~CRcvUList(); + +public: + + /// Insert a new UDT instance to the list. + /// @param [in] u pointer to the UDT instance + + void insert(const CUDT* u); + + /// Remove the UDT instance from the list. + /// @param [in] u pointer to the UDT instance + + void remove(const CUDT* u); + + /// Move the UDT instance to the end of the list, if it already exists; otherwise, do nothing. + /// @param [in] u pointer to the UDT instance + + void update(const CUDT* u); + +public: + CRNode* m_pUList; // the head node + +private: + CRNode* m_pLast; // the last node + +private: + CRcvUList(const CRcvUList&); + CRcvUList& operator=(const CRcvUList&); +}; + +class CHash +{ +public: + CHash(); + ~CHash(); + +public: + + /// Initialize the hash table. + /// @param [in] size hash table size + + void init(int size); + + /// Look for a UDT instance from the hash table. + /// @param [in] id socket ID + /// @return Pointer to a UDT instance, or NULL if not found. + + CUDT* lookup(int32_t id); + + /// Insert an entry to the hash table. + /// @param [in] id socket ID + /// @param [in] u pointer to the UDT instance + + void insert(int32_t id, CUDT* u); + + /// Remove an entry from the hash table. + /// @param [in] id socket ID + + void remove(int32_t id); + +private: + struct CBucket + { + int32_t m_iID; // Socket ID + CUDT* m_pUDT; // Socket instance + + CBucket* m_pNext; // next bucket + } **m_pBucket; // list of buckets (the hash table) + + int m_iHashSize; // size of hash table + +private: + CHash(const CHash&); + CHash& operator=(const CHash&); +}; + +class CRendezvousQueue +{ +public: + CRendezvousQueue(); + ~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(); + +private: + struct CRL + { + UDTSOCKET m_iID; // UDT socket ID (self) + CUDT* m_pUDT; // UDT instance + int m_iIPversion; // IP version + sockaddr* m_pPeerAddr; // UDT sonnection peer address + uint64_t m_ullTTL; // the time that this request expires + }; + std::list m_lRendezvousID; // The sockets currently in rendezvous mode + + pthread_mutex_t m_RIDVectorLock; +}; + +class CSndQueue +{ +friend class CUDT; +friend class CUDTUnited; + +public: + CSndQueue(); + ~CSndQueue(); + +public: + + // XXX There's currently no way to access the socket ID set for + // whatever the queue is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } + + /// Initialize the sending queue. + /// @param [in] c UDP channel to be associated to the queue + /// @param [in] t Timer + + void init(CChannel* c, CTimer* t); + + /// Send out a packet to a given address. + /// @param [in] addr destination address + /// @param [in] packet packet to be sent out + /// @return Size of data sent out. + + int sendto(const sockaddr* addr, CPacket& packet); + +#ifdef SRT_ENABLE_IPOPTS + /// Get the IP TTL. + /// @param [in] ttl IP Time To Live. + /// @return TTL. + + int getIpTTL() const; + + /// Get the IP Type of Service. + /// @return ToS. + + int getIpToS() const; +#endif + +private: + static void* worker(void* param); + pthread_t m_WorkerThread; + + +private: + CSndUList* m_pSndUList; // List of UDT instances for data sending + CChannel* m_pChannel; // The UDP channel for data sending + CTimer* m_pTimer; // Timing facility + + pthread_mutex_t m_WindowLock; + pthread_cond_t m_WindowCond; + + volatile bool m_bClosing; // closing the worker + pthread_cond_t m_ExitCond; + +#if defined(SRT_DEBUG_SNDQ_HIGHRATE)//>>debug high freq worker + uint64_t m_ullDbgPeriod; + uint64_t m_ullDbgTime; + struct { + unsigned long lIteration; // + unsigned long lSleepTo; //SleepTo + unsigned long lNotReadyPop; //Continue + unsigned long lSendTo; + unsigned long lNotReadyTs; + unsigned long lCondWait; //block on m_WindowCond + } m_WorkerStats; +#endif /* SRT_DEBUG_SNDQ_HIGHRATE */ + +private: + CSndQueue(const CSndQueue&); + CSndQueue& operator=(const CSndQueue&); +}; + +class CRcvQueue +{ +friend class CUDT; +friend class CUDTUnited; + +public: + CRcvQueue(); + ~CRcvQueue(); + +public: + + // XXX There's currently no way to access the socket ID set for + // whatever the queue is currently working for. Required to find + // some way to do this, possibly by having a "reverse pointer". + // Currently just "unimplemented". + std::string CONID() const { return ""; } + + /// Initialize the receiving queue. + /// @param [in] size queue size + /// @param [in] mss maximum packet size + /// @param [in] version IP version + /// @param [in] hsize hash table size + /// @param [in] c UDP channel to be associated to the queue + /// @param [in] t timer + + void init(int size, int payload, int version, int hsize, CChannel* c, CTimer* t); + + /// Read a packet for a specific UDT socket id. + /// @param [in] id Socket ID + /// @param [out] packet received packet + /// @return Data size of the packet + + int recvfrom(int32_t id, CPacket& 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); + +private: + CUnitQueue m_UnitQueue; // The received packet queue + + CRcvUList* m_pRcvUList; // List of UDT instances that will read packets from the queue + CHash* m_pHash; // Hash table for UDT socket looking up + CChannel* m_pChannel; // UDP channel for receving packets + CTimer* m_pTimer; // shared timer with the snd queue + + int m_iPayloadSize; // packet payload size + + volatile bool m_bClosing; // closing the worker + pthread_cond_t m_ExitCond; + +private: + int setListener(CUDT* u); + 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 setNewEntry(CUDT* u); + bool ifNewEntry(); + CUDT* getNewEntry(); + + void storePkt(int32_t id, CPacket* pkt); + +private: + pthread_mutex_t m_LSLock; + CUDT* m_pListener; // pointer to the (unique, if any) listening UDT entity + CRendezvousQueue* m_pRendezvousQueue; // The list of sockets in rendezvous mode + + std::vector m_vNewEntry; // newly added entries, to be inserted + pthread_mutex_t m_IDLock; + + std::map > m_mBuffer; // temporary buffer for rendezvous connection request + pthread_mutex_t m_PassLock; + pthread_cond_t m_PassCond; + +private: + CRcvQueue(const CRcvQueue&); + CRcvQueue& operator=(const CRcvQueue&); +}; + +struct CMultiplexer +{ + CSndQueue* m_pSndQueue; // The sending queue + CRcvQueue* m_pRcvQueue; // The receiving queue + CChannel* m_pChannel; // The UDP channel for sending and receiving + CTimer* m_pTimer; // The timer + + int m_iPort; // The UDP port number of this multiplexer + int m_iIPversion; // IP version +#ifdef SRT_ENABLE_IPOPTS + int m_iIpTTL; + int m_iIpToS; +#endif + int m_iMSS; // Maximum Segment Size + int m_iRefCount; // number of UDT instances that are associated with this multiplexer + bool m_bReusable; // if this one can be shared with others + + int m_iID; // multiplexer ID +}; + +#endif diff --git a/srtcore/srt.h b/srtcore/srt.h new file mode 100644 index 000000000..841191c70 --- /dev/null +++ b/srtcore/srt.h @@ -0,0 +1,255 @@ +/***************************************************************************** + * 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__SRTC_H +#define INC__SRTC_H + +#include "udt.h" +#include + +#define SRT_API UDT_API + +#ifdef __cplusplus +extern "C" { +#endif + +typedef UDTSOCKET SRTSOCKET; // UDTSOCKET is a typedef to int anyway, and it's not even in UDT namespace :) + + +// Values returned by srt_getsockstate() +typedef enum SRT_SOCKSTATUS { + SRTS_INIT = 1, + SRTS_OPENED, + SRTS_LISTENING, + SRTS_CONNECTING, + SRTS_CONNECTED, + SRTS_BROKEN, + SRTS_CLOSING, + SRTS_CLOSED, + SRTS_NONEXIST +} SRT_SOCKSTATUS; + +// This is a duplicate enum. Must be kept in sync with the original UDT enum for +// backward compatibility until all compat is destroyed. +typedef enum SRT_SOCKOPT { + + SRTO_MSS, // the Maximum Transfer Unit + SRTO_SNDSYN, // if sending is blocking + SRTO_RCVSYN, // if receiving is blocking + SRTO_CC, // custom congestion control algorithm + SRTO_FC, // Flight flag size (window size) + SRTO_SNDBUF, // maximum buffer in sending queue + SRTO_RCVBUF, // UDT receiving buffer size + SRTO_LINGER, // waiting for unsent data when closing + SRTO_UDP_SNDBUF, // UDP sending buffer size + SRTO_UDP_RCVBUF, // UDP receiving buffer size + SRTO_MAXMSG, // maximum datagram message size + SRTO_MSGTTL, // time-to-live of a datagram message + SRTO_RENDEZVOUS, // rendezvous connection mode + SRTO_SNDTIMEO, // send() timeout + SRTO_RCVTIMEO, // recv() timeout + SRTO_REUSEADDR, // reuse an existing port or create a new one + SRTO_MAXBW, // maximum bandwidth (bytes per second) that the connection can use + SRTO_STATE, // current socket state, see UDTSTATUS, read only + SRTO_EVENT, // current available events associated with the socket + SRTO_SNDDATA, // size of data in the sending buffer + 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, // TsbPd receiver delay (mSec) to absorb burst of missed packet retransmission + 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_TLPKTDROP = 31, // Enable receiver pkt drop + 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, +} SRT_SOCKOPT; + +// UDT error code +// Using duplicated wrapper until backward-compatible apps using UDT +// enum are destroyed. +typedef enum SRT_ERRNO { + SRT_ESUCCESS = 0, + SRT_ECONNSETUP = (int)UDT_ECONNSETUP, + SRT_ENOSERVER = (int)UDT_ENOSERVER, + SRT_ECONNREJ = (int)UDT_ECONNREJ, + SRT_ESOCKFAIL = (int)UDT_ESOCKFAIL, + SRT_ESECFAIL = (int)UDT_ESECFAIL, + SRT_ECONNFAIL = (int)UDT_ECONNFAIL, + SRT_ECONNLOST = (int)UDT_ECONNLOST, + SRT_ENOCONN = (int)UDT_ENOCONN, + SRT_ERESOURCE = (int)UDT_ERESOURCE, + SRT_ETHREAD = (int)UDT_ETHREAD, + SRT_ENOBUF = (int)UDT_ENOBUF, + SRT_EFILE = (int)UDT_EFILE, + SRT_EINVRDOFF = (int)UDT_EINVRDOFF, + SRT_ERDPERM = (int)UDT_ERDPERM, + SRT_EINVWROFF = (int)UDT_EINVWROFF, + SRT_EWRPERM = (int)UDT_EWRPERM, + SRT_EINVOP = (int)UDT_EINVOP, + SRT_EBOUNDSOCK = (int)UDT_EBOUNDSOCK, + SRT_ECONNSOCK = (int)UDT_ECONNSOCK, + SRT_EINVPARAM = (int)UDT_EINVPARAM, + SRT_EINVSOCK = (int)UDT_EINVSOCK, + SRT_EUNBOUNDSOCK = (int)UDT_EUNBOUNDSOCK, + SRT_ENOLISTEN = (int)UDT_ENOLISTEN, + SRT_ERDVNOSERV = (int)UDT_ERDVNOSERV, + SRT_ERDVUNBOUND = (int)UDT_ERDVUNBOUND, + SRT_ESTREAMILL = (int)UDT_ESTREAMILL, + SRT_EDGRAMILL = (int)UDT_EDGRAMILL, + SRT_EDUPLISTEN = (int)UDT_EDUPLISTEN, + SRT_ELARGEMSG = (int)UDT_ELARGEMSG, + SRT_EINVPOLLID = (int)UDT_EINVPOLLID, + SRT_EASYNCFAIL = (int)UDT_EASYNCFAIL, + SRT_EASYNCSND = (int)UDT_EASYNCSND, + SRT_EASYNCRCV = (int)UDT_EASYNCRCV, + SRT_ETIMEOUT = (int)UDT_ETIMEOUT, + SRT_ECONGEST = (int)UDT_ECONGEST, + SRT_EPEERERR = (int)UDT_EPEERERR, + SRT_EUNKNOWN = -1 +} SRT_ERRNO; + +typedef struct CPerfMon SRT_TRACEINFO; +typedef struct CBytePerfMon SRT_TRACEBSTATS; + +// This structure is only a kind-of wannabe. The only use of it is currently +// the 'srctime', however the functionality of application-supplied timestamps +// also doesn't work properly. Left for future until the problems are solved. +// This may prove useful as currently there's no way to tell the application +// that TLPKTDROP facility has dropped some data in favor of timely delivery. +typedef struct SRT_MsgCtrl_ { + int flags; + int boundary; //0:mid pkt, 1(01b):end of frame, 2(11b):complete frame, 3(10b): start of frame + uint64_t srctime; //source timestamp (usec), 0LL: use internal time +} SRT_MSGCTRL; + +static const SRTSOCKET SRT_INVALID_SOCK = -1; +static const int SRT_ERROR = -1; + +// library initialization +SRT_API extern int srt_startup(void); +SRT_API extern int srt_cleanup(void); + +// socket operations +SRT_API extern SRTSOCKET srt_socket(int af, int type, int protocol); +SRT_API extern int srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); +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_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); +SRT_API extern int srt_getsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, void* optval, int* optlen); +SRT_API extern int srt_setsockopt(SRTSOCKET u, int level /*ignored*/, SRT_SOCKOPT optname, const void* optval, int optlen); +SRT_API extern int srt_getsockflag(UDTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen); +SRT_API extern int srt_setsockflag(UDTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen); +/* Don't use it, not proven to work +SRT_API extern int srt_send(SRTSOCKET u, const char* buf, int len, int flags); +SRT_API extern int srt_recv(SRTSOCKET u, char* buf, int len, int flags); +*/ + +// The sendmsg/recvmsg and their 2 counterpart require MAXIMUM the size of SRT payload size (1316). +// Any data over that size will be ignored. +SRT_API extern int srt_sendmsg(SRTSOCKET u, const char* buf, int len, int ttl/* = -1*/, int inorder/* = false*/); +SRT_API extern int srt_recvmsg(SRTSOCKET u, char* buf, int len); +SRT_API extern int srt_sendmsg2(SRTSOCKET u, const char* buf, int len, SRT_MSGCTRL *mctrl); +SRT_API extern int srt_recvmsg2(SRTSOCKET u, char *buf, int len, SRT_MSGCTRL *mctrl); + +// last error detection +SRT_API extern const char* srt_getlasterror_str(void); +SRT_API extern int srt_getlasterror(int* errno_loc); +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_API extern int srt_bstats(SRTSOCKET u, SRT_TRACEBSTATS * perf, int clear); + +// Socket Status (for problem tracking) +SRT_API extern SRT_SOCKSTATUS srt_getsockstate(SRTSOCKET u); + +// event mechanism +// select and selectEX are DEPRECATED; please use epoll. +typedef enum SRT_EPOLL_OPT +{ + // this values are defined same as linux epoll.h + // so that if system values are used by mistake, they should have the same effect + SRT_EPOLL_IN = 0x1, + SRT_EPOLL_OUT = 0x4, + SRT_EPOLL_ERR = 0x8 +} SRT_EPOLL_OPT; + +#ifdef __cplusplus +// In C++ these enums cannot be treated as int and glued by operator |. +// Unless this operator is defined. +inline SRT_EPOLL_OPT operator|(SRT_EPOLL_OPT a1, SRT_EPOLL_OPT a2) +{ + return SRT_EPOLL_OPT( (int)a1 | (int)a2 ); +} +#endif + +SRT_API extern int srt_epoll_create(void); +SRT_API extern int srt_epoll_add_usock(int eid, SRTSOCKET u, const int* events); +SRT_API extern int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int* events); +SRT_API extern int srt_epoll_remove_usock(int eid, SRTSOCKET u); +SRT_API extern int srt_epoll_remove_ssock(int eid, SYSSOCKET s); +SRT_API extern int srt_epoll_update_usock(int eid, SRTSOCKET u, const int* events); +SRT_API extern int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int* events); +///SRT_API extern int srt_epoll_wait(int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, +/// std::set* lrfds = NULL, std::set* wrfds = NULL); +SRT_API extern int srt_epoll_wait(int eid, SRTSOCKET* readfds, int* rnum, SRTSOCKET* writefds, int* wnum, int64_t msTimeOut, + SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum); +SRT_API extern int srt_epoll_release(int eid); + +// Logging control + +SRT_API void srt_setloglevel(int ll); +SRT_API void srt_addlogfa(int fa); +SRT_API void srt_dellogfa(int fa); +SRT_API void srt_resetlogfa(const int* fara, size_t fara_size); +// This isn't predicted, will be only available in SRT C++ API. +// For the time being, until this API is ready, use UDT::setlogstream. +// SRT_API void srt_setlogstream(std::ostream& stream); +SRT_API void srt_setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); +SRT_API void srt_setlogflags(int flags); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/srtcore/srt4udt.h b/srtcore/srt4udt.h new file mode 100644 index 000000000..80bdcf853 --- /dev/null +++ b/srtcore/srt4udt.h @@ -0,0 +1,142 @@ +/***************************************************************************** + * 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 SRT4UDT_H +#define SRT4UDT_H + +#ifndef __UDT_H__ +#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) */ +#define SRT_ENABLE_CBRTIMESTAMP 1 /* Set timestamp for Constant Bit Rate flow (requires SRCTIMESTAMP) */ + +//undef SRT_DEBUG_TSBPD_OUTJITTER 1 /* Packet Delivery histogram */ +//undef SRT_DEBUG_TSBPD_DRIFT 1 /* Debug Encoder-Decoder Drift) */ +//undef SRT_DEBUG_TSBPD_WRAP 1 /* Debug packet timestamp wraparound */ +//undef SRT_DEBUG_TLPKTDROP_DROPSEQ 1 +//undef SRT_DEBUG_SNDQ_HIGHRATE 1 + +#endif /* SRT_ENABLE_TSBPD */ + +/* +* SRT_ENABLE_FASTREXMIT +* Earlier [re-]retransmission of lost retransmitted packets +*/ +#define SRT_ENABLE_FASTREXMIT 1 + +/* +* SRT_ENABLE_CONNTIMEO +* Option UDT_CONNTIMEO added to the API to set/get the connection timeout. +* The UDT hard coded default of 3000 msec is too small for some large RTT (satellite) use cases. +* The SRT handshake (2 exchanges) needs 2 times the RTT to complete with no packet loss. +*/ +#define SRT_ENABLE_CONNTIMEO 1 + +/* +* SRT_ENABLE_NOCWND +* Set the congestion window at its max (then disabling it) to prevent stopping transmission +* when too many packets are not acknowledged. +* The congestion windows is the maximum distance in pkts since the last acknowledged packets. +*/ +#define SRT_ENABLE_NOCWND 1 + +/* +* SRT_ENABLE_NAKREPORT +* Send periodic NAK report for more efficient retransmission instead of relying on ACK timeout +* to retransmit all non-ACKed packets, very inefficient with real-time and no congestion window. +*/ +#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 +* No ack on each packet in DGRAM mode +*/ +#define SRT_ENABLE_LOWACKRATE 1 + +/* +* SRT_ENABLE_IPOPTS +* Enable IP TTL and ToS setting +*/ +#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 +*/ +#define SRT_ENABLE_SND2WAYPROTECT 1 + +/* +* SRT_FIX_KEEPALIVE +* +*/ +#define SRT_FIX_KEEPALIVE 1 + +#endif /* SRT4UDT_H */ diff --git a/srtcore/srt_c_api.cpp b/srtcore/srt_c_api.cpp new file mode 100644 index 000000000..4f85d40df --- /dev/null +++ b/srtcore/srt_c_api.cpp @@ -0,0 +1,244 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +#if __APPLE__ + #include "TargetConditionals.h" +#endif +#include "srt.h" +#include "common.h" + + +extern "C" { + +int srt_startup() { return UDT::startup(); } +int srt_cleanup() { return UDT::cleanup(); } +UDTSOCKET srt_socket(int af, int type, int protocol) { return UDT::socket(af, type, protocol); } +int srt_bind(UDTSOCKET u, const struct sockaddr * name, int namelen) { return UDT::bind(u, name, namelen); } +int srt_bind_peerof(UDTSOCKET u, UDPSOCKET udpsock) { return UDT::bind2(u, udpsock); } +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_close(UDTSOCKET u) +{ + SRT_SOCKSTATUS st = srt_getsockstate(u); + + if ((st == SRTS_NONEXIST) || + (st == SRTS_CLOSED) || + (st == SRTS_CLOSING) ) + { + // It's closed already. Do nothing. + return 0; + } + + return UDT::close(u); +} + +int srt_getpeername(UDTSOCKET u, struct sockaddr * name, int * namelen) { return UDT::getpeername(u, name, namelen); } +int srt_getsockname(UDTSOCKET u, struct sockaddr * name, int * namelen) { return UDT::getsockname(u, name, namelen); } +int srt_getsockopt(UDTSOCKET u, int level, SRT_SOCKOPT optname, void * optval, int * optlen) +{ return UDT::getsockopt(u, level, UDT::SOCKOPT(optname), optval, optlen); } +int srt_setsockopt(UDTSOCKET u, int level, SRT_SOCKOPT optname, const void * optval, int optlen) +{ return UDT::setsockopt(u, level, UDT::SOCKOPT(optname), optval, optlen); } + +int srt_getsockflag(UDTSOCKET u, SRT_SOCKOPT opt, void* optval, int* optlen) +{ return UDT::getsockopt(u, 0, UDT::SOCKOPT(opt), optval, optlen); } +int srt_setsockflag(UDTSOCKET u, SRT_SOCKOPT opt, const void* optval, int optlen) +{ return UDT::setsockopt(u, 0, UDT::SOCKOPT(opt), optval, optlen); } + +int srt_send(UDTSOCKET u, const char * buf, int len, int flags) { return UDT::send(u, buf, len, flags); } +int srt_recv(UDTSOCKET u, char * buf, int len, int flags) { return UDT::recv(u, buf, len, flags); } +int srt_sendmsg(UDTSOCKET u, const char * buf, int len, int ttl, int inorder) { return UDT::sendmsg(u, buf, len, ttl, inorder); } +int srt_recvmsg(UDTSOCKET u, char * buf, int len) { return UDT::recvmsg(u, buf, len); } + +int srt_sendmsg2(UDTSOCKET u, const char * buf, int len, SRT_MSGCTRL *mctrl) +{ + if (mctrl) + return UDT::sendmsg(u, buf, len, -1, true, mctrl->srctime); + else + return UDT::sendmsg(u, buf, len); +} + +int srt_recvmsg2(UDTSOCKET u, char * buf, int len, SRT_MSGCTRL *mctrl) +{ + uint64_t srctime = 0; + int rc = UDT::recvmsg(u, buf, len, srctime); + if (rc == UDT::ERROR) { + // error happen + return -1; + } + + if (mctrl) + mctrl->srctime = srctime; + return rc; +} + +const char* srt_getlasterror_str() { return UDT::getlasterror().getErrorMessage(); } + +int srt_getlasterror(int* loc_errno) +{ + if ( loc_errno ) + *loc_errno = UDT::getlasterror().getErrno(); + return UDT::getlasterror().getErrorCode(); +} + +const char* srt_strerror(int code, int err) +{ + static CUDTException e; + e = CUDTException(CodeMajor(code/1000), CodeMinor(code%1000), err); + return(e.getErrorMessage()); +} + + +void srt_clearlasterror() +{ + UDT::getlasterror().clear(); +} + +int srt_perfmon(UDTSOCKET u, SRT_TRACEINFO * perf, int clear) { return UDT::perfmon(u, perf, clear); } +int srt_bstats(UDTSOCKET u, SRT_TRACEBSTATS * perf, int clear) { return UDT::bstats(u, perf, clear); } + +SRT_SOCKSTATUS srt_getsockstate(UDTSOCKET u) { return SRT_SOCKSTATUS((int)UDT::getsockstate(u)); } + +// event mechanism +int srt_epoll_create() { return UDT::epoll_create(); } + +// You can use either SRT_EPOLL_* flags or EPOLL* flags from , both are the same. IN/OUT/ERR only. +// events == NULL accepted, in which case all flags are set. +int srt_epoll_add_usock(int eid, UDTSOCKET u, const int * events) { return UDT::epoll_add_usock(eid, u, events); } + +int srt_epoll_add_ssock(int eid, SYSSOCKET s, const int * events) +{ + int flag = 0; + +#ifdef LINUX + if (events) { + flag = *events; + } else { + flag = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; + } +#elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + if (events) { + flag = *events; + } else { + flag = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; + } +#else + flag = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; +#endif + + // call UDT native function + return UDT::epoll_add_ssock(eid, s, &flag); +} + +int srt_epoll_remove_usock(int eid, UDTSOCKET u) { return UDT::epoll_remove_usock(eid, u); } +int srt_epoll_remove_ssock(int eid, SYSSOCKET s) { return UDT::epoll_remove_ssock(eid, s); } + +int srt_epoll_update_usock(int eid, UDTSOCKET u, const int * events) +{ + int srt_ev = 0; + + if (events) { + srt_ev = *events; + } else { + srt_ev = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; + } + + return UDT::epoll_update_usock(eid, u, &srt_ev); +} + +int srt_epoll_update_ssock(int eid, SYSSOCKET s, const int * events) +{ + int flag = 0; + +#ifdef LINUX + if (events) { + flag = *events; + } else { + flag = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; + } +#elif defined(OSX) || defined(TARGET_OS_IOS) || defined(TARGET_OS_TV) + if (events) { + flag = *events; + } else { + flag = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; + } +#else + flag = UDT_EPOLL_IN | UDT_EPOLL_OUT | UDT_EPOLL_ERR; +#endif + + // call UDT native function + return UDT::epoll_update_ssock(eid, s, &flag); +} + +int srt_epoll_wait( + int eid, + UDTSOCKET* readfds, int* rnum, UDTSOCKET* writefds, int* wnum, + int64_t msTimeOut, + SYSSOCKET* lrfds, int* lrnum, SYSSOCKET* lwfds, int* lwnum) +{ + return UDT::epoll_wait2( + eid, + readfds, rnum, writefds, wnum, + msTimeOut, + lrfds, lrnum, lwfds, lwnum); +} + +int srt_epoll_release(int eid) { return UDT::epoll_release(eid); } + +void srt_setloglevel(int ll) +{ + UDT::setloglevel(logging::LogLevel::type(ll)); +} + +void srt_addlogfa(int fa) +{ + UDT::addlogfa(logging::LogFA(fa)); +} + +void srt_dellogfa(int fa) +{ + UDT::dellogfa(logging::LogFA(fa)); +} + +void srt_resetlogfa(const int* fara, size_t fara_size) +{ + std::set fas; + std::copy(fara, fara + fara_size, std::inserter(fas, fas.begin())); + UDT::resetlogfa(fas); +} + +void srt_setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler) +{ + UDT::setloghandler(opaque, handler); +} + +void srt_setlogflags(int flags) +{ + UDT::setlogflags(flags); +} + + + +} diff --git a/srtcore/threadname.h b/srtcore/threadname.h new file mode 100644 index 000000000..59b5659aa --- /dev/null +++ b/srtcore/threadname.h @@ -0,0 +1,94 @@ +/***************************************************************************** + * 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; } + + ThreadName(const char*) + { + } + + ~ThreadName() // just to make it "non-trivially-destructible" for compatibility with normal version + { + } +}; + + + +#endif +#endif diff --git a/srtcore/udt.h b/srtcore/udt.h new file mode 100644 index 000000000..ef57decf0 --- /dev/null +++ b/srtcore/udt.h @@ -0,0 +1,749 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/18/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +/* WARNING!!! + * Since now this file is a "C and C++ header". + * It should be then able to be interpreted by C compiler, so + * all C++-oriented things must be ifdef'd-out by __cplusplus. + * + * Mind also comments - to prevent any portability problems, + * B/C++ comments (// -> EOL) should not be used unless the + * area is under __cplusplus condition already. + * + * NOTE: this file contains _STRUCTURES_ that are common to C and C++, + * plus some functions and other functionalities ONLY FOR C++. This + * file doesn't contain _FUNCTIONS_ predicted to be used in C - see udtc.h + */ + +#ifndef __UDT_H__ +#define __UDT_H__ + +#include "srt4udt.h" +#include "logging_api.h" + +/* +* SRT_ENABLE_THREADCHECK (THIS IS SET IN MAKEFILE NOT HERE) +*/ +#if defined(SRT_ENABLE_THREADCHECK) +#include +#else +#define THREAD_STATE_INIT(name) +#define THREAD_EXIT() +#define THREAD_PAUSED() +#define THREAD_RESUMED() +#define INCREMENT_THREAD_ITERATIONS() +#endif + +/* Obsolete way to define MINGW */ +#ifndef __MINGW__ +#if defined(__MINGW32__) || defined(__MINGW64__) +#define __MINGW__ 1 +#endif +#endif + +#ifndef WIN32 + #include + #include + #include +#else + #ifdef __MINGW__ + #include + #include + #include + #include + #endif + #include + #if defined(_MSC_VER) + #pragma warning(disable:4251) + #endif +#endif + +#ifdef __cplusplus +#include +#include +#include +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// + +//if compiling on VC6.0 or pre-WindowsXP systems +//use -DLEGACY_WIN32 + +//if compiling with MinGW, it only works on XP or above +//use -D_WIN32_WINNT=0x0501 + + +#ifdef WIN32 + #ifndef __MINGW__ + // Explicitly define 32-bit and 64-bit numbers + typedef __int32 int32_t; + typedef __int64 int64_t; + typedef unsigned __int32 uint32_t; + #ifndef LEGACY_WIN32 + typedef unsigned __int64 uint64_t; + #else + // VC 6.0 does not support unsigned __int64: may cause potential problems. + typedef __int64 uint64_t; + #endif + + #ifdef UDT_DYNAMIC + #ifdef UDT_EXPORTS + #define UDT_API __declspec(dllexport) + #else + #define UDT_API __declspec(dllimport) + #endif + #else + #define UDT_API + #endif + #else + #define UDT_API + #endif +#else + #define UDT_API __attribute__ ((visibility("default"))) +#endif + +#define NO_BUSY_WAITING + +#ifdef WIN32 + #ifndef __MINGW__ + typedef SOCKET SYSSOCKET; + #else + typedef int SYSSOCKET; + #endif +#else + typedef int SYSSOCKET; +#endif + +typedef SYSSOCKET UDPSOCKET; +typedef int UDTSOCKET; + +//////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +// This facility is used only for select() function. +// This is considered obsolete and the epoll() functionality rather should be used. +typedef std::set ud_set; +#define UD_CLR(u, uset) ((uset)->erase(u)) +#define UD_ISSET(u, uset) ((uset)->find(u) != (uset)->end()) +#define UD_SET(u, uset) ((uset)->insert(u)) +#define UD_ZERO(uset) ((uset)->clear()) +#endif + +enum SRT_KM_STATE +{ + SRT_KM_S_UNSECURED = 0, //No encryption + SRT_KM_S_SECURING = 1, //Stream encrypted, exchanging Keying Material + SRT_KM_S_SECURED = 2, //Stream encrypted, keying Material exchanged, decrypting ok. + SRT_KM_S_NOSECRET = 3, //Stream encrypted and no secret to decrypt Keying Material + SRT_KM_S_BADSECRET = 4 //Stream encrypted and wrong secret, cannot decrypt Keying Material +}; + +enum UDT_EPOLL_OPT +{ + // this values are defined same as linux epoll.h + // so that if system values are used by mistake, they should have the same effect + UDT_EPOLL_IN = 0x1, + UDT_EPOLL_OUT = 0x4, + UDT_EPOLL_ERR = 0x8 +}; + +enum UDTSTATUS { + UDT_INIT = 1, + UDT_OPENED, + UDT_LISTENING, + UDT_CONNECTING, + UDT_CONNECTED, + UDT_BROKEN, + UDT_CLOSING, + UDT_CLOSED, + UDT_NONEXIST +}; + +//////////////////////////////////////////////////////////////////////////////// + +// XXX DEPRECATED, once the SRT(C) API is in use. All these values +// are duplicated with SRTO_ prefix in srt.h file. +enum UDT_SOCKOPT +{ + UDT_MSS, // the Maximum Transfer Unit + UDT_SNDSYN, // if sending is blocking + UDT_RCVSYN, // if receiving is blocking + UDT_CC, // custom congestion control algorithm + UDT_FC, // Flight flag size (window size) + UDT_SNDBUF, // maximum buffer in sending queue + UDT_RCVBUF, // UDT receiving buffer size + UDT_LINGER, // waiting for unsent data when closing + UDP_SNDBUF, // UDP sending buffer size + UDP_RCVBUF, // UDP receiving buffer size + UDT_MAXMSG, // maximum datagram message size + UDT_MSGTTL, // time-to-live of a datagram message + UDT_RENDEZVOUS, // rendezvous connection mode + UDT_SNDTIMEO, // send() timeout + UDT_RCVTIMEO, // recv() timeout + UDT_REUSEADDR, // reuse an existing port or create a new one + UDT_MAXBW, // maximum bandwidth (bytes per second) that the connection can use + UDT_STATE, // current socket state, see UDTSTATUS, read only + UDT_EVENT, // current avalable events associated with the socket + 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) +#ifdef SRT_ENABLE_IPOPTS + SRT_IPTTL = 29, + SRT_IPTOS, +#endif +#ifdef SRT_ENABLE_TLPKTDROP + SRT_TLPKTDROP = 31, // Enable/Disable receiver pkt drop + SRT_TSBPDMAXLAG, // Decoder's tolerated lag past TspPD delay (decoder's buffer) +#endif +#ifdef SRT_ENABLE_NAKREPORT + SRT_RCVNAKREPORT = 33, // Enable/Disable receiver's Periodic NAK Report to sender +#endif + SRT_AGENTVERSION = 34, + SRT_PEERVERSION, +#ifdef SRT_ENABLE_CONNTIMEO + SRT_CONNTIMEO = 36, +#endif + SRT_TWOWAYDATA = 37, + SRT_SNDPBKEYLEN = 38, + SRT_RCVPBKEYLEN, + SRT_SNDPEERKMSTATE, + SRT_RCVKMSTATE, + SRT_LOSSMAXTTL, +}; + +/* Binary backward compatibility obsolete options */ +#define SRT_NAKREPORT SRT_RCVNAKREPORT + +//////////////////////////////////////////////////////////////////////////////// + +struct CPerfMon +{ + // global measurements + int64_t msTimeStamp; // time since the UDT entity is started, in milliseconds + int64_t pktSentTotal; // total number of sent data packets, including retransmissions + int64_t pktRecvTotal; // total number of received packets + int pktSndLossTotal; // total number of lost packets (sender side) + int pktRcvLossTotal; // total number of lost packets (receiver side) + int pktRetransTotal; // total number of retransmitted packets + int pktRcvRetransTotal; // total number of retransmitted packets received + int pktSentACKTotal; // total number of sent ACK packets + int pktRecvACKTotal; // total number of received ACK packets + int pktSentNAKTotal; // total number of sent NAK packets + int pktRecvNAKTotal; // total number of received NAK packets + int64_t usSndDurationTotal; // total time duration when UDT is sending data (idle time exclusive) + + // local measurements + int64_t pktSent; // number of sent data packets, including retransmissions + int64_t pktRecv; // number of received packets + int pktSndLoss; // number of lost packets (sender side) + int pktRcvLoss; // number of lost packets (receiver side) + int pktRetrans; // number of retransmitted packets + int pktRcvRetrans; // number of retransmitted packets received + int pktSentACK; // number of sent ACK packets + int pktRecvACK; // number of received ACK packets + int pktSentNAK; // number of sent NAK packets + int pktRecvNAK; // number of received NAK packets + double mbpsSendRate; // sending rate in Mb/s + double mbpsRecvRate; // receiving rate in Mb/s + int64_t usSndDuration; // busy sending time (i.e., idle time exclusive) + int pktReorderDistance; // size of order discrepancy in received sequences + double pktRcvAvgBelatedTime; // average time of packet delay for belated packets (packets with sequence past the ACK) + int64_t pktRcvBelated; // number of received AND IGNORED packets due to having come too late + + // instant measurements + double usPktSndPeriod; // packet sending period, in microseconds + int pktFlowWindow; // flow window size, in number of packets + int pktCongestionWindow; // congestion window size, in number of packets + int pktFlightSize; // number of packets on flight + double msRTT; // RTT, in milliseconds + double mbpsBandwidth; // estimated bandwidth, in Mb/s + int byteAvailSndBuf; // available UDT sender buffer size + int byteAvailRcvBuf; // available UDT receiver buffer size +}; + +#ifdef SRT_ENABLE_BSTATS +struct CBytePerfMon +{ + // global measurements + int64_t msTimeStamp; // time since the UDT entity is started, in milliseconds + int64_t pktSentTotal; // total number of sent data packets, including retransmissions + int64_t pktRecvTotal; // total number of received packets + int pktSndLossTotal; // total number of lost packets (sender side) + int pktRcvLossTotal; // total number of lost packets (receiver side) + int pktRetransTotal; // total number of retransmitted packets + int pktSentACKTotal; // total number of sent ACK packets + int pktRecvACKTotal; // total number of received ACK packets + int pktSentNAKTotal; // total number of sent NAK packets + int pktRecvNAKTotal; // total number of received NAK packets + int64_t usSndDurationTotal; // total time duration when UDT is sending data (idle time exclusive) + //>new + int pktSndDropTotal; // number of too-late-to-send dropped packets + int pktRcvDropTotal; // number of too-late-to play missing packets + int pktRcvUndecryptTotal; // number of undecrypted packets + uint64_t byteSentTotal; // total number of sent data bytes, including retransmissions + uint64_t byteRecvTotal; // total number of received bytes +#ifdef SRT_ENABLE_LOSTBYTESCOUNT + uint64_t byteRcvLossTotal; // total number of lost bytes +#endif + uint64_t byteRetransTotal; // total number of retransmitted bytes + uint64_t byteSndDropTotal; // number of too-late-to-send dropped bytes + uint64_t byteRcvDropTotal; // number of too-late-to play missing bytes (estimate based on average packet size) + uint64_t byteRcvUndecryptTotal; // number of undecrypted bytes + //< + + // local measurements + int64_t pktSent; // number of sent data packets, including retransmissions + int64_t pktRecv; // number of received packets + int pktSndLoss; // number of lost packets (sender side) + int pktRcvLoss; // number of lost packets (receiver side) + int pktRetrans; // number of retransmitted packets + int pktSentACK; // number of sent ACK packets + int pktRecvACK; // number of received ACK packets + int pktSentNAK; // number of sent NAK packets + int pktRecvNAK; // number of received NAK packets + double mbpsSendRate; // sending rate in Mb/s + double mbpsRecvRate; // receiving rate in Mb/s + int64_t usSndDuration; // busy sending time (i.e., idle time exclusive) + //>new + int pktSndDrop; // number of too-late-to-send dropped packets + int pktRcvDrop; // number of too-late-to play missing packets + int pktRcvUndecrypt; // number of undecrypted packets + uint64_t byteSent; // number of sent data bytes, including retransmissions + uint64_t byteRecv; // number of received bytes +#ifdef SRT_ENABLE_LOSTBYTESCOUNT + uint64_t byteRcvLoss; // number of retransmitted bytes +#endif + uint64_t byteRetrans; // number of retransmitted bytes + uint64_t byteSndDrop; // number of too-late-to-send dropped bytes + uint64_t byteRcvDrop; // number of too-late-to play missing bytes (estimate based on average packet size) + uint64_t byteRcvUndecrypt; // number of undecrypted bytes + //< + + // instant measurements + double usPktSndPeriod; // packet sending period, in microseconds + int pktFlowWindow; // flow window size, in number of packets + int pktCongestionWindow; // congestion window size, in number of packets + int pktFlightSize; // number of packets on flight + double msRTT; // RTT, in milliseconds + double mbpsBandwidth; // estimated bandwidth, in Mb/s + int byteAvailSndBuf; // available UDT sender buffer size + int byteAvailRcvBuf; // available UDT receiver buffer size + //>new + double mbpsMaxBW; // Transmit Bandwidth ceiling (Mbps) + int byteMSS; // MTU + + int pktSndBuf; // UnACKed packets in UDT sender + int byteSndBuf; // UnACKed bytes in UDT sender + int msSndBuf; // UnACKed timespan (msec) of UDT sender + int msSndTsbPdDelay; // Timestamp-based Packet Delivery Delay + + int pktRcvBuf; // Undelivered packets in UDT receiver + int byteRcvBuf; // Undelivered bytes of UDT receiver + int msRcvBuf; // Undelivered timespan (msec) of UDT receiver + int msRcvTsbPdDelay; // Timestamp-based Packet Delivery Delay + //< +}; +#endif /* SRT_ENABLE_BSTATS */ + +//////////////////////////////////////////////////////////////////////////////// + +// Error codes - define outside the CUDTException class +// because otherwise you'd have to use CUDTException::MJ_SUCCESS etc. +// in all throw CUDTException expressions. +enum CodeMajor +{ + MJ_UNKNOWN = -1, + MJ_SUCCESS = 0, + MJ_SETUP = 1, + MJ_CONNECTION = 2, + MJ_SYSTEMRES = 3, + MJ_FILESYSTEM = 4, + MJ_NOTSUP = 5, + MJ_AGAIN = 6, + MJ_PEERERROR = 7 +}; + +enum CodeMinor +{ + // These are "minor" error codes from various "major" categories + // MJ_SETUP + MN_NONE = 0, + MN_TIMEOUT = 1, + MN_REJECTED = 2, + MN_NORES = 3, + MN_SECURITY = 4, + // MJ_CONNECTION + MN_CONNLOST = 1, + MN_NOCONN = 2, + // MJ_SYSTEMRES + MN_THREAD = 1, + MN_MEMORY = 2, + // MJ_FILESYSTEM + MN_SEEKGFAIL = 1, + MN_READFAIL = 2, + MN_SEEKPFAIL = 3, + MN_WRITEFAIL = 4, + // MJ_NOTSUP + MN_ISBOUND = 1, + MN_ISCONNECTED = 2, + MN_INVAL = 3, + MN_SIDINVAL = 4, + MN_ISUNBOUND = 5, + MN_NOLISTEN = 6, + MN_ISRENDEZVOUS = 7, + MN_ISRENDUNBOUND = 8, + MN_ISSTREAM = 9, + MN_ISDGRAM = 10, + MN_BUSY = 11, + MN_XSIZE = 12, + MN_EIDINVAL = 13, + // MJ_AGAIN + MN_WRAVAIL = 1, + MN_RDAVAIL = 2, + MN_XMTIMEOUT = 3, + MN_CONGESTION = 4 +}; + +#ifdef __cplusplus + +// Class CUDTException exposed for C++ API. +// This is actually useless, unless you'd use a DIRECT C++ API, +// however there's no such API so far. The current C++ API for UDT/SRT +// is predicted to NEVER LET ANY EXCEPTION out of implementation, +// so it's useless to catch this exception anyway. + +class UDT_API CUDTException +{ +public: + + CUDTException(CodeMajor major = MJ_SUCCESS, CodeMinor minor = MN_NONE, int err = -1); + CUDTException(const CUDTException& e); + + ~CUDTException(); + + /// Get the description of the exception. + /// @return Text message for the exception description. + + const char* getErrorMessage(); + + /// Get the system errno for the exception. + /// @return errno. + + 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(); + +private: + CodeMajor m_iMajor; // major exception categories + CodeMinor m_iMinor; // for specific error reasons + int m_iErrno; // errno returned by the system if there is any + std::string m_strMsg; // text error message + + std::string m_strAPI; // the name of UDT function that returns the error + std::string m_strDebug; // debug information, set to the original place that causes the error + +public: // Error Code + static const int SUCCESS; + static const int ECONNSETUP; + static const int ENOSERVER; + static const int ECONNREJ; + static const int ESOCKFAIL; + static const int ESECFAIL; + static const int ECONNFAIL; + static const int ECONNLOST; + static const int ENOCONN; + static const int ERESOURCE; + static const int ETHREAD; + static const int ENOBUF; + static const int EFILE; + static const int EINVRDOFF; + static const int ERDPERM; + static const int EINVWROFF; + static const int EWRPERM; + static const int EINVOP; + static const int EBOUNDSOCK; + static const int ECONNSOCK; + static const int EINVPARAM; + static const int EINVSOCK; + static const int EUNBOUNDSOCK; + static const int ENOLISTEN; + static const int ERDVNOSERV; + static const int ERDVUNBOUND; + static const int ESTREAMILL; + static const int EDGRAMILL; + static const int EDUPLISTEN; + static const int ELARGEMSG; + static const int EINVPOLLID; + static const int EASYNCFAIL; + static const int EASYNCSND; + static const int EASYNCRCV; + static const int ETIMEOUT; +#ifdef SRT_ENABLE_ECN + static const int ECONGEST; +#endif /* SRT_ENABLE_ECN */ + static const int EPEERERR; + static const int EUNKNOWN; +}; + +#endif // C++, exception class + +// Stupid, but effective. This will be #undefined, so don't worry. +#define MJ(major) (1000*MJ_##major) +#define MN(major, minor) (1000*MJ_##major + MN_##minor) + +// Some better way to define it, and better for C language. +enum UDT_ERRNO +{ + UDT_EUNKNOWN = -1, + UDT_SUCCESS = MJ_SUCCESS, + + UDT_ECONNSETUP = MJ(SETUP), + UDT_ENOSERVER = MN(SETUP, TIMEOUT), + UDT_ECONNREJ = MN(SETUP, REJECTED), + UDT_ESOCKFAIL = MN(SETUP, NORES), + UDT_ESECFAIL = MN(SETUP, SECURITY), + + UDT_ECONNFAIL = MJ(CONNECTION), + UDT_ECONNLOST = MN(CONNECTION, CONNLOST), + UDT_ENOCONN = MN(CONNECTION, NOCONN), + + UDT_ERESOURCE = MJ(SYSTEMRES), + UDT_ETHREAD = MN(SYSTEMRES, THREAD), + UDT_ENOBUF = MN(SYSTEMRES, MEMORY), + + UDT_EFILE = MJ(FILESYSTEM), + UDT_EINVRDOFF = MN(FILESYSTEM, SEEKGFAIL), + UDT_ERDPERM = MN(FILESYSTEM, READFAIL), + UDT_EINVWROFF = MN(FILESYSTEM, SEEKPFAIL), + UDT_EWRPERM = MN(FILESYSTEM, WRITEFAIL), + + UDT_EINVOP = MJ(NOTSUP), + UDT_EBOUNDSOCK = MN(NOTSUP, ISBOUND), + UDT_ECONNSOCK = MN(NOTSUP, ISCONNECTED), + UDT_EINVPARAM = MN(NOTSUP, INVAL), + UDT_EINVSOCK = MN(NOTSUP, SIDINVAL), + UDT_EUNBOUNDSOCK = MN(NOTSUP, ISUNBOUND), + UDT_ENOLISTEN = MN(NOTSUP, NOLISTEN), + UDT_ERDVNOSERV = MN(NOTSUP, ISRENDEZVOUS), + UDT_ERDVUNBOUND = MN(NOTSUP, ISRENDUNBOUND), + UDT_ESTREAMILL = MN(NOTSUP, ISSTREAM), + UDT_EDGRAMILL = MN(NOTSUP, ISDGRAM), + UDT_EDUPLISTEN = MN(NOTSUP, BUSY), + UDT_ELARGEMSG = MN(NOTSUP, XSIZE), + UDT_EINVPOLLID = MN(NOTSUP, EIDINVAL), + + UDT_EASYNCFAIL = MJ(AGAIN), + UDT_EASYNCSND = MN(AGAIN, WRAVAIL), + UDT_EASYNCRCV = MN(AGAIN, RDAVAIL), + UDT_ETIMEOUT = MN(AGAIN, XMTIMEOUT), + UDT_ECONGEST = MN(AGAIN, CONGESTION), + + UDT_EPEERERR = MJ(PEERERROR) +}; + +#undef MJ +#undef MN + +// Logging API - specialization for SRT. + +// Define logging functional areas for log selection. +// Use values greater than 0. Value 0 is reserved for LOGFA_GENERAL, +// which is considered always enabled. + +// Logger Functional Areas +// Note that 0 is "general". + +// Made by #define so that it's available also for C API. +#define SRT_LOGFA_GENERAL 0 +#define SRT_LOGFA_BSTATS 1 +#define SRT_LOGFA_CONTROL 2 +#define SRT_LOGFA_DATA 3 +#define SRT_LOGFA_TSBPD 4 +#define SRT_LOGFA_REXMIT 5 + +#define SRT_LOGFA_LASTNONE 99 + +// Rest of the file is C++ only. It defines functions to be +// called only from C++. For C equivalents, see udtc.h file. + +#ifdef __cplusplus + + +//////////////////////////////////////////////////////////////////////////////// + +// If you need to export these APIs to be used by a different language, +// declare extern "C" for them, and add a "udt_" prefix to each API. +// The following APIs: sendfile(), recvfile(), epoll_wait(), geterrormsg(), +// include C++ specific feature, please use the corresponding sendfile2(), etc. + +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; +#undef ERROR +UDT_API extern const int ERROR; + +UDT_API int startup(); +UDT_API int cleanup(); +UDT_API UDTSOCKET socket(int af, int type, int protocol); +UDT_API int bind(UDTSOCKET u, const struct sockaddr* name, int namelen); +UDT_API int bind2(UDTSOCKET u, UDPSOCKET udpsock); +UDT_API int listen(UDTSOCKET u, int backlog); +UDT_API UDTSOCKET accept(UDTSOCKET u, struct sockaddr* addr, int* addrlen); +UDT_API int connect(UDTSOCKET u, const struct sockaddr* name, int namelen); +UDT_API int close(UDTSOCKET u); +UDT_API int getpeername(UDTSOCKET u, struct sockaddr* name, int* namelen); +UDT_API int getsockname(UDTSOCKET u, struct sockaddr* name, int* namelen); +UDT_API int getsockopt(UDTSOCKET u, int level, SOCKOPT optname, void* optval, int* optlen); +UDT_API int setsockopt(UDTSOCKET u, int level, SOCKOPT optname, const void* optval, int optlen); +UDT_API int send(UDTSOCKET u, const char* buf, int len, int flags); +UDT_API int recv(UDTSOCKET u, char* buf, int len, int flags); + +// If SRT_ENABLE_SRCTIMESTAMP is NOT enabled, 'srctime' argument is ignored. +UDT_API int sendmsg(UDTSOCKET u, const char* buf, int len, int ttl = -1, bool inorder = false, uint64_t srctime = 0LL); +#ifdef SRT_ENABLE_SRCTIMESTAMP +// For non-SRCTIMESTAMP, this version is not available for the application +UDT_API int recvmsg(UDTSOCKET u, char* buf, int len, uint64_t& srctime); +#endif +// For SRCTIMESTAMP this version is still available, it just ignores the received timestamp. +UDT_API int recvmsg(UDTSOCKET u, char* buf, int len); + +UDT_API int64_t sendfile(UDTSOCKET u, std::fstream& ifs, int64_t& offset, int64_t size, int block = 364000); +UDT_API int64_t recvfile(UDTSOCKET u, std::fstream& ofs, int64_t& offset, int64_t size, int block = 7280000); +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, + std::vector* writefds, std::vector* exceptfds, int64_t msTimeOut); + +UDT_API int epoll_create(); +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, + SYSSOCKET* lrfds = NULL, int* lrnum = NULL, SYSSOCKET* lwfds = NULL, int* lwnum = NULL); +UDT_API int epoll_release(int eid); +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); +UDT_API void addlogfa(logging::LogFA fa); +UDT_API void dellogfa(logging::LogFA fa); +UDT_API void resetlogfa(std::set fas); +UDT_API void setlogstream(std::ostream& stream); +UDT_API void setloghandler(void* opaque, SRT_LOG_HANDLER_FN* handler); +UDT_API void setlogflags(int flags); + +} // namespace UDT + +#endif /* __cplusplus */ + +#endif diff --git a/srtcore/utilities.h b/srtcore/utilities.h new file mode 100644 index 000000000..37473fd7c --- /dev/null +++ b/srtcore/utilities.h @@ -0,0 +1,624 @@ +/***************************************************************************** + * 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__SRT_UTILITIES_H +#define INC__SRT_UTILITIES_H + + +#ifndef __UDT_H__ +#error Must include udt.h prior to this header! +#endif + +#ifdef __GNUG__ +#define ATR_UNUSED __attribute__((unused)) +#define ATR_DEPRECATED __attribute__((deprecated)) +#else +#define ATR_UNUSED +#define ATR_DEPRECATED +#endif + +#if defined(__cplusplus) && __cplusplus > 199711L +#define HAVE_CXX11 1 +#define ATR_NOEXCEPT noexcept +#else +#define ATR_NOEXCEPT // throw() - bad idea +#endif + +#include +#include +#include +#include +#include +#include +#include + +// -------------- UTILITIES ------------------------ + +// Bit numbering utility. +// Usage: Bits +// +// You can use it as a typedef (say, "MASKTYPE") and then use the following members: +// - MASKTYPE::mask - to get the int32_t value with bimask (used bits set to 1, others to 0) +// - MASKTYPE::offset - to get the lowermost bit number, or number of bits to shift +// - MASKTYPE::wrap(int value) - to create a bitset where given value is encoded in given bits +// - MASKTYPE::unwrap(int bitset) - to extract an integer value from the bitset basing on mask definition +// (rightmost defaults to leftmost) +// REMEMBER: leftmost > rightmost because bit 0 is the LEAST significant one! + +template +struct BitsetMask +{ + static const bool correct = L >= R; + static const uint32_t value = (1u << L) | BitsetMask::value; +}; + +// This is kind-of functional programming. This describes a special case that is +// a "terminal case" in case when decreased L-1 (see above) reached == R. +template +struct BitsetMask +{ + static const bool correct = true; + static const uint32_t value = 1 << R; +}; + +// This is a trap for a case that BitsetMask::correct in the master template definition +// evaluates to false. This trap causes compile error and prevents from continuing +// recursive unwinding in wrong direction (and challenging the compiler's resistiveness +// for infinite loops). +template +struct BitsetMask +{ +}; + +template +struct Bits +{ + // DID YOU GET kind-of error: ‘mask’ is not a member of ‘Bits<3u, 5u, false>’ ? + // See the the above declaration of 'correct' ! + static const uint32_t mask = BitsetMask::value; + static const uint32_t offset = R; + static const size_t size = L - R + 1; + + // Example: if our bitset mask is 00111100, this checks if given value fits in + // 00001111 mask (that is, does not exceed <0, 15>. + static bool fit(uint32_t value) { return (BitsetMask::value & value) == value; } + + /// 'wrap' gets some given value that should be placed in appropriate bit range and + /// returns a whole 32-bit word that has the value already at specified place. + /// To create a 32-bit container that contains already all values destined for different + /// bit ranges, simply use wrap() for each of them and bind them with | operator. + static uint32_t wrap(uint32_t baseval) { return (baseval << offset) & mask; } + + /// Extracts appropriate bit range and returns them as normal integer value. + static uint32_t unwrap(uint32_t bitset) { return (bitset & mask) >> offset; } +}; + + +template +struct DynamicStruct +{ + FieldType inarray[Size]; + + void clear() + { + // As a standard library, it can be believed that this call + // can be optimized when FieldType is some integer. + std::fill(inarray, inarray + Size, FieldType()); + } + + FieldType operator[](IndexerType ix) const { return inarray[size_t(ix)]; } + FieldType& operator[](IndexerType ix) { return inarray[size_t(ix)]; } + + template + FieldType operator[](AnyOther ix) const + { + // If you can see a compile error here ('int' is not a class or struct, or + // that there's no definition of 'type' in given type), it means that you + // have used invalid data type passed to [] operator. See the definition + // of this type as DynamicStruct and see which type is required for indexing. + typename AnyOther::type wrong_usage_of_operator_index = AnyOther::type; + return inarray[size_t(ix)]; + } + + template + FieldType& operator[](AnyOther ix) + { + // If you can see a compile error here ('int' is not a class or struct, or + // that there's no definition of 'type' in given type), it means that you + // have used invalid data type passed to [] operator. See the definition + // of this type as DynamicStruct and see which type is required for indexing. + typename AnyOther::type wrong_usage_of_operator_index = AnyOther::type; + return inarray[size_t(ix)]; + } + + operator FieldType* () { return inarray; } + operator const FieldType* () const { return inarray; } + + char* raw() { return (char*)inarray; } +}; + + +// ------------------------------------------------------------ + + + +inline bool IsSet(int32_t bitset, int32_t flagset) +{ + return (bitset & flagset) == flagset; +} + +#if HAVE_CXX11 + +// 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. +template +struct ref_t: public std::reference_wrapper +{ + typedef std::reference_wrapper base; + ref_t() {} + ref_t(const ref_t& i): base(i) {} + ref_t(const base& i): base(i) {} +}; + + +// Gluing string of any type, wrapper for operator << + +template +inline Stream& Print(Stream& in) { return in;} + +template +inline Stream& Print(Stream& sout, Arg1&& arg1, Args&&... args) +{ + sout << arg1; + return Print(sout, args...); +} + +template +inline std::string Sprint(Args&&... args) +{ + std::ostringstream sout; + Print(sout, args...); + return sout.str(); +} + +#endif + + +//////////////////////////////////////////////////////////////////////////////// + +class CTimer +{ +public: + CTimer(); + ~CTimer(); + +public: + + /// Sleep for "interval" CCs. + /// @param interval [in] CCs to sleep. + + void sleep(uint64_t interval); + + /// Seelp until CC "nexttime". + /// @param nexttime [in] next time the caller is waken up. + + void sleepto(uint64_t nexttime); + + /// Stop the sleep() or sleepto() methods. + + void interrupt(); + + /// trigger the clock for a tick, for better granuality in no_busy_waiting timer. + + void tick(); + +public: + + /// Read the CPU clock cycle into x. + /// @param x [out] to record cpu clock cycles. + + static void rdtsc(uint64_t &x); + + /// return the CPU frequency. + /// @return CPU frequency. + + static uint64_t getCPUFrequency(); + + /// check the current time, 64bit, in microseconds. + /// @return current time in microseconds. + + static uint64_t getTime(); + + /// trigger an event such as new connection, close, new data, etc. for "select" call. + + static void triggerEvent(); + + /// wait for an event to br triggered by "triggerEvent". + + static void waitForEvent(); + + /// sleep for a short interval. exact sleep time does not matter + + static void sleep(); + +private: + uint64_t getTimeInMicroSec(); + +private: + uint64_t m_ullSchedTime; // next schedulled time + + pthread_cond_t m_TickCond; + pthread_mutex_t m_TickLock; + + static pthread_cond_t m_EventCond; + static pthread_mutex_t m_EventLock; + +private: + static uint64_t s_ullCPUFrequency; // CPU frequency : clock cycles per microsecond + static uint64_t readCPUFrequency(); + static bool m_bUseMicroSecond; // No higher resolution timer available, use gettimeofday(). +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CGuard +{ +public: + CGuard(pthread_mutex_t& lock); + ~CGuard(); + +public: + static int enterCS(pthread_mutex_t& lock); + static int leaveCS(pthread_mutex_t& lock); + + static void createMutex(pthread_mutex_t& lock); + static void releaseMutex(pthread_mutex_t& lock); + + static void createCond(pthread_cond_t& cond); + static void releaseCond(pthread_cond_t& cond); + +private: + pthread_mutex_t& m_Mutex; // Alias name of the mutex to be protected + int m_iLocked; // Locking status + + CGuard& operator=(const CGuard&); +}; + +class InvertedGuard +{ + pthread_mutex_t* m_pMutex; +public: + + InvertedGuard(pthread_mutex_t* smutex): m_pMutex(smutex) + { + if ( !smutex ) + return; + + CGuard::leaveCS(*smutex); + } + + ~InvertedGuard() + { + if ( !m_pMutex ) + return; + + CGuard::enterCS(*m_pMutex); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +// UDT Sequence Number 0 - (2^31 - 1) + +// seqcmp: compare two seq#, considering the wraping +// seqlen: length from the 1st to the 2nd seq#, including both +// seqoff: offset from the 2nd to the 1st seq# +// incseq: increase the seq# by 1 +// decseq: decrease the seq# by 1 +// incseq: increase the seq# by a given offset + +class CSeqNo +{ +public: + inline static int seqcmp(int32_t seq1, int32_t seq2) + {return (abs(seq1 - seq2) < m_iSeqNoTH) ? (seq1 - seq2) : (seq2 - seq1);} + + inline static int seqlen(int32_t seq1, int32_t seq2) + {return (seq1 <= seq2) ? (seq2 - seq1 + 1) : (seq2 - seq1 + m_iMaxSeqNo + 2);} + + inline static int seqoff(int32_t seq1, int32_t seq2) + { + if (abs(seq1 - seq2) < m_iSeqNoTH) + return seq2 - seq1; + + if (seq1 < seq2) + return seq2 - seq1 - m_iMaxSeqNo - 1; + + return seq2 - seq1 + m_iMaxSeqNo + 1; + } + + inline static int32_t incseq(int32_t seq) + {return (seq == m_iMaxSeqNo) ? 0 : seq + 1;} + + inline static int32_t decseq(int32_t seq) + {return (seq == 0) ? m_iMaxSeqNo : seq - 1;} + + inline static int32_t incseq(int32_t seq, int32_t inc) + {return (m_iMaxSeqNo - seq >= inc) ? seq + inc : seq - m_iMaxSeqNo + inc - 1;} + // m_iMaxSeqNo >= inc + sec --- inc + sec <= m_iMaxSeqNo + // if inc + sec > m_iMaxSeqNo then return seq + inc - (m_iMaxSeqNo+1) + + inline static int32_t decseq(int32_t seq, int32_t dec) + { + // Check if seq - dec < 0, but before it would have happened + if ( seq < dec ) + { + int32_t left = dec - seq; // This is so many that is left after dragging dec to 0 + // So now decrement the (m_iMaxSeqNo+1) by "left" + return m_iMaxSeqNo - left + 1; + } + return seq - dec; + } + +public: + static const int32_t m_iSeqNoTH; // threshold for comparing seq. no. + static const int32_t m_iMaxSeqNo; // maximum sequence number used in UDT +}; + +//////////////////////////////////////////////////////////////////////////////// + +// UDT ACK Sub-sequence Number: 0 - (2^31 - 1) + +class CAckNo +{ +public: + inline static int32_t incack(int32_t ackno) + {return (ackno == m_iMaxAckSeqNo) ? 0 : ackno + 1;} + +public: + static const int32_t m_iMaxAckSeqNo; // maximum ACK sub-sequence number used in UDT +}; + + + +//////////////////////////////////////////////////////////////////////////////// + +struct CIPAddress +{ + static bool ipcmp(const sockaddr* addr1, const sockaddr* addr2, int ver = AF_INET); + static void ntop(const sockaddr* addr, uint32_t ip[4], int ver = AF_INET); + static void pton(sockaddr* addr, const uint32_t ip[4], int ver = AF_INET); + static std::string show(const sockaddr* adr); +}; + +//////////////////////////////////////////////////////////////////////////////// + +struct CMD5 +{ + static void compute(const char* input, unsigned char result[16]); +}; + +// Debug stats +template +class StatsLossRecords +{ + int32_t initseq; + std::bitset array; + +public: + + StatsLossRecords(): initseq(-1) {} + + // To check if this structure still keeps record of that sequence. + // This is to check if the information about this not being found + // is still reliable. + bool exists(int32_t seq) + { + return initseq != -1 && CSeqNo::seqcmp(seq, initseq) >= 0; + } + + int32_t base() { return initseq; } + + void clear() + { + initseq = -1; + array.reset(); + } + + void add(int32_t lo, int32_t hi) + { + int32_t end = lo + CSeqNo::seqcmp(hi, lo); + for (int32_t i = lo; i != end; i = CSeqNo::incseq(i)) + add(i); + } + + void add(int32_t seq) + { + if ( array.none() ) + { + // May happen it wasn't initialized. Set it as initial loss sequence. + initseq = seq; + array[0] = true; + return; + } + + // Calculate the distance between this seq and the oldest one. + int seqdiff = CSeqNo::seqcmp(seq, initseq); + if ( seqdiff > int(SIZE) ) + { + // Size exceeded. Drop the oldest sequences. + // First calculate how many must be removed. + size_t toremove = seqdiff - SIZE; + // Now, since that position, find the nearest 1 + while ( !array[toremove] && toremove <= SIZE ) + ++toremove; + + // All have to be dropped, so simply reset the array + if ( toremove == SIZE ) + { + initseq = seq; + array[0] = true; + return; + } + + // Now do the shift of the first found 1 to position 0 + // and its index add to initseq + initseq += toremove; + seqdiff -= toremove; + array >>= toremove; + } + + // Now set appropriate bit that represents this seq + array[seqdiff] = true; + } + + StatsLossRecords& operator << (int32_t seq) + { + add(seq); + return *this; + } + + void remove(int32_t seq) + { + // Check if is in range. If not, ignore. + int seqdiff = CSeqNo::seqcmp(seq, initseq); + if ( seqdiff < 0 ) + return; // already out of array + if ( seqdiff > SIZE ) + return; // never was added! + + array[seqdiff] = true; + } + + bool find(int32_t seq) const + { + int seqdiff = CSeqNo::seqcmp(seq, initseq); + if ( seqdiff < 0 ) + return false; // already out of array + if ( size_t(seqdiff) > SIZE ) + return false; // never was added! + + return array[seqdiff]; + } + +#if HAVE_CXX11 + + std::string to_string() const + { + std::string out; + for (size_t i = 0; i < SIZE; ++i) + { + if ( array[i] ) + out += std::to_string(initseq+i) + " "; + } + + return out; + } +#endif +}; + + +template +class DriftTracer +{ + int64_t m_qDrift; + int64_t m_qOverdrift; + + int64_t m_qDriftSum; + unsigned m_uDriftSpan; + +public: + DriftTracer() + : m_qDrift(), + m_qOverdrift(), + m_qDriftSum(), + m_uDriftSpan() + {} + + bool update(int64_t driftval) + { + m_qDriftSum += driftval; + ++m_uDriftSpan; + + if ( m_uDriftSpan >= MAX_SPAN ) + { + if ( CLEAR_ON_UPDATE ) + m_qOverdrift = 0; + + // Calculate the median of all drift values. + // In most cases, the divisor should be == MAX_SPAN. + m_qDrift = m_qDriftSum / m_uDriftSpan; + + // And clear the collection + m_qDriftSum = 0; + m_uDriftSpan = 0; + + // In case of "overdrift", save the overdriven value in 'm_qOverdrift'. + // In clear mode, you should add this value to the time base when update() + // returns true. The drift value will be since now measured with the + // overdrift assumed to be added to the base. + if (abs(m_qDrift) > MAX_DRIFT) + { + m_qOverdrift = m_qDrift < 0 ? -MAX_DRIFT : MAX_DRIFT; + m_qDrift -= m_qOverdrift; + } + + // printDriftOffset(m_qOverdrift, m_qDrift); + + // Timebase is separate + // m_qTimeBase += m_qOverdrift; + + return true; + } + return false; + } + + // These values can be read at any time, however if you want + // to depend on the fact that they have been changed lately, + // you have to check the return value from update(). + // + // IMPORTANT: drift() can be called at any time, just remember + // that this value may look different than before only if the + // last update() return true, which need not be important for you. + // + // CASE: CLEAR_ON_UPDATE = true + // overdrift() should be read only immediately after update() returned + // true. It will stay available with this value until the next time when + // update() returns true, in which case the value will be cleared. + // Therefore, after calling update() if it retuns true, you should read + // overdrift() immediately an make some use of it. Next valid overdrift + // will be then relative to every previous overdrift. + // + // CASE: CLEAR_ON_UPDATE = false + // overdrift() will start from 0, but it will always keep track on + // any changes in overdrift. By manipulating the MAX_DRIFT parameter + // you can decide how high the drift can go relatively to stay below + // overdrift. + int64_t drift() { return m_qDrift; } + int64_t overdrift() { return m_qOverdrift; } +}; + + +#endif diff --git a/srtcore/window.cpp b/srtcore/window.cpp new file mode 100644 index 000000000..1a39667e8 --- /dev/null +++ b/srtcore/window.cpp @@ -0,0 +1,250 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/22/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#include +#include +#include "common.h" +#include "window.h" +#include + +using namespace std; + +namespace ACKWindowTools +{ + +void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t ack) +{ + r_aSeq[r_iHead].iACKSeqNo = seq; + r_aSeq[r_iHead].iACK = ack; + r_aSeq[r_iHead].TimeStamp = CTimer::getTime(); + + r_iHead = (r_iHead + 1) % size; + + // overwrite the oldest ACK since it is not likely to be acknowledged + if (r_iHead == r_iTail) + r_iTail = (r_iTail + 1) % size; +} + +int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack) +{ + if (r_iHead >= r_iTail) + { + // Head has not exceeded the physical boundary of the window + + for (int i = r_iTail, n = r_iHead; i < n; ++ i) + { + // looking for indentical ACK Seq. No. + if (seq == r_aSeq[i].iACKSeqNo) + { + // return the Data ACK it carried + r_ack = r_aSeq[i].iACK; + + // calculate RTT + int rtt = int(CTimer::getTime() - r_aSeq[i].TimeStamp); + + if (i + 1 == r_iHead) + { + r_iTail = r_iHead = 0; + r_aSeq[0].iACKSeqNo = -1; + } + else + r_iTail = (i + 1) % size; + + return rtt; + } + } + + // Bad input, the ACK node has been overwritten + return -1; + } + + // Head has exceeded the physical window boundary, so it is behind tail + for (int j = r_iTail, n = r_iHead + size; j < n; ++ j) + { + // looking for indentical ACK seq. no. + if (seq == r_aSeq[j % size].iACKSeqNo) + { + // return Data ACK + j %= size; + r_ack = r_aSeq[j].iACK; + + // calculate RTT + int rtt = int(CTimer::getTime() - r_aSeq[j].TimeStamp); + + if (j == r_iHead) + { + r_iTail = r_iHead = 0; + r_aSeq[0].iACKSeqNo = -1; + } + else + r_iTail = (j + 1) % size; + + return rtt; + } + } + + // bad input, the ACK node has been overwritten + return -1; +} +} + +//////////////////////////////////////////////////////////////////////////////// + +void CPktTimeWindowTools::initializeWindowArrays(int* r_pktWindow, int* r_probeWindow, int* r_bytesWindow, size_t asize, size_t psize) +{ + for (size_t i = 0; i < asize; ++ i) + r_pktWindow[i] = 1000000; //1 sec -> 1 pkt/sec + + for (size_t k = 0; k < psize; ++ k) + 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] +} + + +int CPktTimeWindowTools::getPktRcvSpeed_in(const int* window, int* replica, const int* abytes, size_t asize, int& bytesps) +{ + // get median value, but cannot change the original value order in the window + std::copy(window, window + asize, replica); + std::nth_element(replica, replica + (asize / 2), replica + asize); + //std::sort(replica, replica + asize); + int median = replica[asize / 2]; + + unsigned count = 0; + int sum = 0; + int upper = median << 3; + int lower = median >> 3; + + bytesps = 0; + unsigned long bytes = 0; + const int* bp = abytes; + // median filtering + const int* p = window; + for (int i = 0, n = asize; i < n; ++ i) + { + if ((*p < upper) && (*p > lower)) + { + ++ count; //packet counter + sum += *p; //usec counter +////#ifdef SRT_ENABLE_BSTATS + bytes += (unsigned long)*bp; //byte counter + } + ++ p; //advance packet pointer + ++ bp; //advance bytes pointer + } + + // 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 + bytesps = (unsigned long)ceil(1000000.0 / (double(sum) / double(bytes))); + return (int)ceil(1000000.0 / (sum / count)); + } + else + { + 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) +{ + // 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); + int median = replica[psize / 2]; + + int count = 1; + int sum = median; + int upper = median << 3; // median*8 + int lower = median >> 3; // median/8 + + // median filtering + const int* p = window; + for (int i = 0, n = psize; i < n; ++ i) + { + if ((*p < upper) && (*p > lower)) + { + ++ count; + sum += *p; + } + ++ p; + } + + return (int)ceil(1000000.0 / (double(sum) / double(count))); +} + + diff --git a/srtcore/window.h b/srtcore/window.h new file mode 100644 index 000000000..e9f75174b --- /dev/null +++ b/srtcore/window.h @@ -0,0 +1,298 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the + above copyright notice, this list of conditions + and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the University of Illinois + nor the names of its contributors may be used to + endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*****************************************************************************/ + +/***************************************************************************** +written by + Yunhong Gu, last updated 01/22/2011 +modified by + Haivision Systems Inc. +*****************************************************************************/ + +#ifndef __UDT_WINDOW_H__ +#define __UDT_WINDOW_H__ + + +#ifndef WIN32 + #include + #include +#endif +#include "udt.h" + +namespace ACKWindowTools +{ + struct Seq + { + int32_t iACKSeqNo; // Seq. No. for the ACK packet + int32_t iACK; // Data Seq. No. carried by the ACK packet + uint64_t TimeStamp; // The timestamp when the ACK was sent + }; + + void store(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t ack); + int acknowledge(Seq* r_aSeq, const size_t size, int& r_iHead, int& r_iTail, int32_t seq, int32_t& r_ack); +} + +template +class CACKWindow +{ +public: + CACKWindow() : + m_aSeq(), + m_iHead(0), + m_iTail(0) + { + m_aSeq[0].iACKSeqNo = -1; + } + + ~CACKWindow() {} + + /// Write an ACK record into the window. + /// @param [in] seq ACK seq. no. + /// @param [in] ack DATA ACK no. + + void store(int32_t seq, int32_t ack) + { + return ACKWindowTools::store(m_aSeq, SIZE, m_iHead, m_iTail, seq, ack); + } + + /// Search the ACK-2 "seq" in the window, find out the DATA "ack" and caluclate RTT . + /// @param [in] seq ACK-2 seq. no. + /// @param [out] ack the DATA ACK no. that matches the ACK-2 no. + /// @return RTT. + + int acknowledge(int32_t seq, int32_t& r_ack) + { + return ACKWindowTools::acknowledge(m_aSeq, SIZE, m_iHead, m_iTail, seq, r_ack); + } + +private: + + typedef ACKWindowTools::Seq Seq; + + Seq m_aSeq[SIZE]; + int m_iHead; // Pointer to the lastest ACK record + int m_iTail; // Pointer to the oldest ACK record + +private: + CACKWindow(const CACKWindow&); + CACKWindow& operator=(const CACKWindow&); +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CPktTimeWindowTools +{ +public: + static int getPktRcvSpeed_in(const int* window, int* replica, const int* bytes, size_t asize, int& bytesps); + static int getBandwidth_in(const int* window, int* replica, size_t psize); + + static void initializeWindowArrays(int* r_pktWindow, int* r_probeWindow, int* r_bytesWindow, size_t asize, size_t psize); +}; + +template +class CPktTimeWindow: CPktTimeWindowTools +{ +public: + CPktTimeWindow(): + m_aPktWindow(), + m_aBytesWindow(), + m_iPktWindowPtr(0), + m_aProbeWindow(), + m_iProbeWindowPtr(0), + m_iLastSentTime(0), + m_iMinPktSndInt(1000000), + m_LastArrTime(), + m_CurrArrTime(), + m_ProbeTime() + { + pthread_mutex_init(&m_lockPktWindow, NULL); + pthread_mutex_init(&m_lockProbeWindow, NULL); + m_LastArrTime = CTimer::getTime(); + CPktTimeWindowTools::initializeWindowArrays(m_aPktWindow, m_aProbeWindow, m_aBytesWindow, ASIZE, PSIZE); + } + + ~CPktTimeWindow() + { + pthread_mutex_destroy(&m_lockPktWindow); + pthread_mutex_destroy(&m_lockProbeWindow); + } + + + /// read the minimum packet sending interval. + /// @return minimum packet sending interval (microseconds). + + int getMinPktSndInt() const { return m_iMinPktSndInt; } + + /// Calculate the packets arrival speed. + /// @return Packet arrival speed (packets per second). + + int getPktRcvSpeed(int& bytesps) const + { + // Lock access to the packet Window + CGuard cg(m_lockPktWindow); + + int pktReplica[ASIZE]; // packet information window (inter-packet time) + return getPktRcvSpeed_in(m_aPktWindow, pktReplica, m_aBytesWindow, ASIZE, bytesps); + } + + int getPktRcvSpeed() const + { + int bytesps; + return(getPktRcvSpeed(bytesps)); + } + + /// Estimate the bandwidth. + /// @return Estimated bandwidth (packets per second). + + int getBandwidth() const + { + // Lock access to the packet Window + CGuard cg(m_lockProbeWindow); + + int probeReplica[PSIZE]; + return getBandwidth_in(m_aProbeWindow, probeReplica, PSIZE); + } + + /// Record time information of a packet sending. + /// @param currtime timestamp of the packet sending. + + void onPktSent(int currtime) + { + int interval = currtime - m_iLastSentTime; + + if ((interval < m_iMinPktSndInt) && (interval > 0)) + m_iMinPktSndInt = interval; + + m_iLastSentTime = currtime; + } + + /// Record time information of an arrived packet. + + void onPktArrival(int pktsz = 0) + { + CGuard cg(m_lockPktWindow); + + m_CurrArrTime = CTimer::getTime(); + + // record the packet interval between the current and the last one + m_aPktWindow[m_iPktWindowPtr] = int(m_CurrArrTime - m_LastArrTime); + m_aBytesWindow[m_iPktWindowPtr] = pktsz; + + // the window is logically circular + ++ m_iPktWindowPtr; + if (m_iPktWindowPtr == ASIZE) + m_iPktWindowPtr = 0; + + // remember last packet arrival time + m_LastArrTime = m_CurrArrTime; + } + + /// Record the arrival time of the first probing packet. + + void probe1Arrival() + { + m_ProbeTime = CTimer::getTime(); + } + + /// Record the arrival time of the second probing packet and the interval between packet pairs. + + void probe2Arrival(int pktsz = 0) + { + // Lock access to the packet Window + CGuard cg(m_lockProbeWindow); + + m_CurrArrTime = CTimer::getTime(); + + // 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 ? timediff_times_pl_size / pktsz : int(timediff); + + // OLD CODE BEFORE BSTATS: + // record the probing packets interval + // m_aProbeWindow[m_iProbeWindowPtr] = int(m_CurrArrTime - m_ProbeTime); + + // the window is logically circular + ++ m_iProbeWindowPtr; + if (m_iProbeWindowPtr == PSIZE) + m_iProbeWindowPtr = 0; + } + + +private: + int m_aPktWindow[ASIZE]; // packet information window (inter-packet time) + int m_aBytesWindow[ASIZE]; // + int m_iPktWindowPtr; // position pointer of the packet info. window. + mutable pthread_mutex_t m_lockPktWindow; // used to synchronize access to the packet window + + int m_aProbeWindow[PSIZE]; // record inter-packet time for probing packet pairs + int m_iProbeWindowPtr; // position pointer to the probing window + mutable pthread_mutex_t m_lockProbeWindow; // used to synchronize access to the probe window + + int m_iLastSentTime; // last packet sending time + int m_iMinPktSndInt; // Minimum packet sending interval + + uint64_t m_LastArrTime; // last packet arrival time + uint64_t m_CurrArrTime; // current packet arrival time + uint64_t m_ProbeTime; // arrival time of the first probing packet + +private: + CPktTimeWindow(const CPktTimeWindow&); + CPktTimeWindow &operator=(const CPktTimeWindow&); +}; + + +#endif diff --git a/srtcore/windows/win_time.cpp b/srtcore/windows/win_time.cpp new file mode 100644 index 000000000..a4c7a5037 --- /dev/null +++ b/srtcore/windows/win_time.cpp @@ -0,0 +1,163 @@ +/***************************************************************************** + * 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 + * + * Based on UDT4 SDK version 4.11 + *****************************************************************************/ + +/***************************************************************************** +written by + Haivision Systems Inc. + *****************************************************************************/ + +#include +#include + +#if 0 +// Temporarily blocked. Needs to be fixed. +// Currently unused, but may be useful in future. +int clock_gettime(int X, struct timespec *ts) +{ + LARGE_INTEGER t; + FILETIME f; + double microseconds; + static LARGE_INTEGER offset; + static double frequencyToMicroseconds; + static int initialized = 0; + static BOOL usePerformanceCounter = 0; + + if (!initialized) { + LARGE_INTEGER performanceFrequency; + initialized = 1; + usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); + if (usePerformanceCounter) { + QueryPerformanceCounter(&offset); + frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; + } else { + offset = getFILETIMEoffset(); + frequencyToMicroseconds = 10.; + } + } + if (usePerformanceCounter) QueryPerformanceCounter(&t); + else { + GetSystemTimeAsFileTime(&f); + t.QuadPart = f.dwHighDateTime; + t.QuadPart <<= 32; + t.QuadPart |= f.dwLowDateTime; + } + + t.QuadPart -= offset.QuadPart; + microseconds = (double)t.QuadPart / frequencyToMicroseconds; + t.QuadPart = microseconds; + tv->tv_sec = t.QuadPart / 1000000; + tv->tv_usec = t.QuadPart % 1000000; + return (0); +} +#endif + +void timeradd(struct timeval *a, struct timeval *b, struct timeval *result) +{ + result->tv_sec = a->tv_sec + b->tv_sec; + result->tv_usec = a->tv_usec + b->tv_usec; + if (result->tv_usec >= 1000000) + { + result->tv_sec++; + result->tv_usec -= 1000000; + } +} + + +/* +int gettimeofday(struct timeval *tv, struct timezone *tz) +{ + FILETIME ft; + unsigned __int64 tmpres = 0; + static int tzflag; + long timezone = 0; + int daylight = 0; + + if (NULL != tv) + { + GetSystemTimeAsFileTime(&ft); + + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + + // converting file time to unix epoch + tmpres -= DELTA_EPOCH_IN_MICROSECS; + tmpres /= 10; //convert into microseconds + tv->tv_sec = (long)(tmpres / 1000000UL); + tv->tv_usec = (long)(tmpres % 1000000UL); + } + + if (NULL != tz) + { + if (!tzflag) + { + _tzset(); + tzflag++; + } + + _get_timezone(&timezone); + _get_daylight(&daylight); + + tz->tz_minuteswest = timezone / 60; + tz->tz_dsttime = daylight; + } + + return 0; +} +*/ + + +int gettimeofday(struct timeval* tp, struct timezone* tz) +{ + static LARGE_INTEGER tickFrequency, epochOffset; + + // For our first call, use "ftime()", so that we get a time with a proper epoch. + // For subsequent calls, use "QueryPerformanceCount()", because it's more fine-grain. + static int isFirstCall = 1; + + LARGE_INTEGER tickNow; + QueryPerformanceCounter(&tickNow); + + if (isFirstCall) + { + struct timeb tb; + ftime(&tb); + tp->tv_sec = (long)tb.time; + tp->tv_usec = 1000*tb.millitm; + + // Also get our counter frequency: + QueryPerformanceFrequency(&tickFrequency); + + // And compute an offset to add to subsequent counter times, so we get a proper epoch: + epochOffset.QuadPart = tb.time*tickFrequency.QuadPart + (tb.millitm*tickFrequency.QuadPart)/1000 - tickNow.QuadPart; + + isFirstCall = 0; // for next time + } + else + { + // Adjust our counter time so that we get a proper epoch: + tickNow.QuadPart += epochOffset.QuadPart; + + tp->tv_sec = (long) (tickNow.QuadPart / tickFrequency.QuadPart); + tp->tv_usec = (long) (((tickNow.QuadPart % tickFrequency.QuadPart) * 1000000L) / tickFrequency.QuadPart); + } + return 0; +} + diff --git a/submodules/pthread-win32/STUB b/submodules/pthread-win32/STUB new file mode 100644 index 000000000..3a1896b5c --- /dev/null +++ b/submodules/pthread-win32/STUB @@ -0,0 +1,9 @@ +This will be bound to a submodule 'pthread-win32'. This will be added in future. + +Predicted .gitmodule entry: + + +[submodule "submodules/pthread-win32"] + path = submodules/pthread-win32 + url = https://github.com/GerHobbelt/pthread-win32.git +