├── SECURITY.md ├── .github └── workflows │ ├── ubuntu.yml │ ├── macos.yml │ ├── windows.yml │ ├── unit_test.yml │ └── codeql-analysis.yml ├── SRTNetInternal.h ├── LICENSE.md ├── .gitignore ├── createioslibs.sh ├── CMakeLists.txt ├── README.md ├── SRTNet.h ├── main.cpp ├── SRTNet.cpp └── test └── TestSrt.cpp /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | All versions after 6th of july are security analysed. See the Security tab for all information. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | File a issue letting us know about the vulnerability 10 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: [push] 4 | 5 | jobs: 6 | buildubuntu: 7 | name: build_ubuntu_22.04 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: install 12 | run: sudo apt -y install tclsh pkg-config libssl-dev libgtest-dev 13 | - name: cmake 14 | run: cmake -DCMAKE_BUILD_TYPE=Release . 15 | - name: make 16 | run: make 17 | - uses: actions/upload-artifact@v2 18 | with: 19 | name: libsrtnet_linux 20 | path: ./libsrtnet.a 21 | - uses: actions/upload-artifact@v2 22 | with: 23 | name: libsrt_linux 24 | path: ./srt/libsrt.a 25 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install GTest 17 | run: brew install googletest 18 | - name: CMake set-up 19 | run: | 20 | cmake -DCMAKE_BUILD_TYPE=Release -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl . 21 | - name: make 22 | run: cmake --build . 23 | - uses: actions/upload-artifact@v2 24 | with: 25 | name: libsrtnet_osx 26 | path: ./libsrtnet.a 27 | - uses: actions/upload-artifact@v2 28 | with: 29 | name: libsrt_osx 30 | path: ./srt/libsrt.a 31 | -------------------------------------------------------------------------------- /SRTNetInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2019-04-22. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | // Global Logger -- Start 11 | #define LOGG_NOTIFY 1 12 | #define LOGG_WARN 2 13 | #define LOGG_ERROR 4 14 | #define LOGG_FATAL 8 15 | #define LOGG_MASK LOGG_NOTIFY | LOGG_WARN | LOGG_ERROR | LOGG_FATAL //What to logg? 16 | 17 | #ifdef DEBUG 18 | #define SRT_LOGGER(l,g,f) \ 19 | { \ 20 | std::ostringstream a; \ 21 | if (g == (LOGG_NOTIFY & (LOGG_MASK))) {a << "Notification: ";} \ 22 | else if (g == (LOGG_WARN & (LOGG_MASK))) {a << "Warning: ";} \ 23 | else if (g == (LOGG_ERROR & (LOGG_MASK))) {a << "Error: ";} \ 24 | else if (g == (LOGG_FATAL & (LOGG_MASK))) {a << "Fatal: ";} \ 25 | if (a.str().length()) { \ 26 | if (l) {a << __FILE__ << " " << __LINE__ << " ";} \ 27 | a << f << std::endl; \ 28 | std::cout << a.str(); \ 29 | } \ 30 | } 31 | #else 32 | #define SRT_LOGGER(l,g,f) 33 | #endif 34 | // GLobal Logger -- End 35 | 36 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Install OpenSSL 17 | run: choco install openssl 18 | - name: Install GTest 19 | run: | 20 | git clone --branch v1.13.0 https://github.com/google/googletest 21 | cd googletest 22 | cmake -DCMAKE_INSTALL_PREFIX=D:/gtest -Dgtest_force_shared_crt=ON -DBUILD_GMOCK=OFF -DBUILD_GTEST=ON . 23 | cmake --build . --config Release --target INSTALL 24 | - name: CMake set-up 25 | run: cmake -S . -DCMAKE_BUILD_TYPE=Release -DGTEST_ROOT=D:/gtest 26 | - name: make 27 | run: cmake --build . --config Release 28 | - uses: actions/upload-artifact@v2 29 | with: 30 | name: libsrtnet_win 31 | path: ./Release/srtnet.lib 32 | - uses: actions/upload-artifact@v2 33 | with: 34 | name: libsrt_win 35 | path: ./srt/Release/srt_static.lib 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Anders Cedronius 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/unit_test.yml: -------------------------------------------------------------------------------- 1 | name: unit_tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test-release: 11 | 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Install GTest 17 | run: sudo apt install libgtest-dev 18 | - name: CMake set-up for release 19 | run: | 20 | rm -rf build 21 | mkdir build 22 | cd build 23 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. 24 | - name: Build release 25 | run: | 26 | cd build 27 | make runUnitTests 28 | - name: Run tests with release 29 | run: | 30 | cd build 31 | ./runUnitTests 32 | 33 | test-debug: 34 | 35 | runs-on: ubuntu-22.04 36 | 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Install GTest 40 | run: sudo apt install libgtest-dev 41 | - name: CMake set-up for debug 42 | run: | 43 | rm -rf build 44 | mkdir build 45 | cd build 46 | cmake -DCMAKE_BUILD_TYPE=Debug .. 47 | - name: Build debug 48 | run: | 49 | cd build 50 | make runUnitTests 51 | - name: Run tests with debug 52 | run: | 53 | cd build 54 | ./runUnitTests 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### C++ template 2 | # Compiled Object files 3 | *.slo 4 | *.lo 5 | *.o 6 | *.obj 7 | 8 | # Precompiled Headers 9 | *.gch 10 | *.pch 11 | 12 | # Compiled Dynamic libraries 13 | *.so 14 | *.dylib 15 | *.dll 16 | 17 | # Fortran module files 18 | *.mod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | ### JetBrains template 32 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 33 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 34 | 35 | # User-specific stuff: 36 | .idea 37 | 38 | # Sensitive or high-churn files: 39 | .idea/dataSources.ids 40 | .idea/dataSources.xml 41 | .idea/dataSources.local.xml 42 | .idea/sqlDataSources.xml 43 | .idea/dynamic.xml 44 | .idea/uiDesigner.xml 45 | 46 | # Gradle: 47 | .idea/gradle.xml 48 | .idea/libraries 49 | 50 | # Mongo Explorer plugin: 51 | .idea/mongoSettings.xml 52 | 53 | ## File-based project format: 54 | *.iws 55 | 56 | ## Plugin-specific files: 57 | 58 | # IntelliJ 59 | /out/ 60 | 61 | # mpeltonen/sbt-idea plugin 62 | .idea_modules/ 63 | 64 | # JIRA plugin 65 | atlassian-ide-plugin.xml 66 | 67 | # Crashlytics plugin (for Android Studio and IntelliJ) 68 | com_crashlytics_export_strings.xml 69 | crashlytics.properties 70 | crashlytics-build.properties 71 | fabric.properties 72 | 73 | cmake-build* 74 | srt 75 | project_srt-prefix/ 76 | project_srt_win-prefix/ 77 | build/ 78 | 79 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | branches: [master, ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [master] 9 | schedule: 10 | - cron: '0 8 * * 5' 11 | 12 | jobs: 13 | CodeQL-Build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | with: 21 | # We must fetch at least the immediate parents so that if this is 22 | # a pull request then we can checkout the head. 23 | fetch-depth: 2 24 | 25 | # Initializes the CodeQL tools for scanning. 26 | - name: Initialize CodeQL 27 | uses: github/codeql-action/init@v1 28 | # Override language selection by uncommenting this and choosing your languages 29 | # with: 30 | # languages: go, javascript, csharp, python, cpp, java 31 | 32 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 33 | # If this step fails, then you should remove it and run the build manually (see below) 34 | - name: Autobuild 35 | uses: github/codeql-action/autobuild@v1 36 | 37 | # ℹ️ Command-line programs to run using the OS shell. 38 | # 📚 https://git.io/JvXDl 39 | 40 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 41 | # and modify them (or add more) to build your code if your project 42 | # uses a compiled language 43 | 44 | #- run: | 45 | # make bootstrap 46 | # make release 47 | 48 | - name: Perform CodeQL Analysis 49 | uses: github/codeql-action/analyze@v1 50 | -------------------------------------------------------------------------------- /createioslibs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #Based on Tomohiro Matsuzawa SRT build scrips found here -> https://github.com/cats-oss/VideoCast-Swift 4 | 5 | SDKVERSION=$(xcrun -sdk iphoneos --show-sdk-version) 6 | 7 | #Remove the file libcrypto.a to build a new version 8 | if [[ ! -f libcrypto.a ]]; then 9 | echo "libcrypto missing. Will try to build one." 10 | #since SRT depends on this lib remove libsrt.a 11 | rm -f libsrt.a 12 | if [ ! -d openssl ]; then 13 | mkdir openssl 14 | git clone https://github.com/x2on/OpenSSL-for-iPhone ./openssl/ 15 | fi 16 | 17 | #here we got the directory openssl and the repo OpenSSL-for-iPhone 18 | #Make sure we-re on latest master-commit //Change this if you want a branch or lock to a certain commit 19 | 20 | cd openssl 21 | git fetch --all 22 | git reset --hard origin/master 23 | ./build-libssl.sh 24 | cd .. 25 | cp ./openssl/lib/libcrypto.a . 26 | fi 27 | 28 | #Remove the file libsrt.a to build a new version 29 | if [[ ! -f libsrt.a ]]; then 30 | echo "libsrt missing. Will try to build one." 31 | if [ ! -d srt ]; then 32 | mkdir srt 33 | git clone https://github.com/Haivision/srt ./srt/ 34 | fi 35 | 36 | #here we got the directory srt and the repo Haivision/srt 37 | #Make sure we-re on latest master-commit //Change this if you want a branch or lock to a certain commit 38 | 39 | cd srt 40 | git fetch --all 41 | git reset --hard origin/master 42 | cd .. 43 | 44 | build_srt() { 45 | PLATFORM=$1 46 | IOS_PLATFORM=$2 47 | ARCH=$3 48 | IOS_OPENSSL=$(pwd)/openssl/bin/${PLATFORM}${SDKVERSION}-${ARCH}.sdk 49 | mkdir -p ./build/ios_${ARCH} 50 | pushd ./build/ios_${ARCH} 51 | ../../srt/configure --cmake-prefix-path=$IOS_OPENSSL --use-openssl-pc=OFF --cmake-toolchain-file=scripts/iOS.cmake --enable-debug=0 --ios-platform=${IOS_PLATFORM} --ios-arch=${ARCH} 52 | make 53 | popd 54 | } 55 | 56 | build_srt iPhoneSimulator SIMULATOR64 x86_64 57 | build_srt iPhoneOS OS arm64 58 | 59 | cp ./build/ios_arm64/version.h srt/srtcore/. 60 | 61 | awk ' 62 | /^struct CBytePerfMon/ { g = 1 } 63 | p == 1 { print } 64 | g == 1 { print "struct CBytePerfMonExpose"; p = 1; g = 0 } 65 | /};/ { p = 0 } 66 | ' srt/srtcore/srt.h > srt_status.h 67 | 68 | lipo -output libsrt.a -create ./build/ios_x86_64/libsrt.a ./build/ios_arm64/libsrt.a 69 | fi -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(cppSRTWrapper) 3 | set(CMAKE_CXX_STANDARD 17) 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | enable_testing() 6 | 7 | find_package(Threads REQUIRED) 8 | find_package(OpenSSL REQUIRED) 9 | find_package(GTest REQUIRED) 10 | 11 | #If no build type is set then force Release 12 | IF( NOT CMAKE_BUILD_TYPE ) 13 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING 14 | "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." 15 | FORCE) 16 | ENDIF() 17 | 18 | IF( CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") 19 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") 20 | ENDIF() 21 | 22 | if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 23 | message("MACOS build will set OpenSSL 1.1") 24 | set(MACOSX_OPENSSL_PATH "-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl") 25 | endif() 26 | 27 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 28 | 29 | include(ExternalProject) 30 | ExternalProject_Add(project_srt 31 | GIT_REPOSITORY https://github.com/Haivision/srt.git 32 | # We use the git commit hash because it is faster 33 | GIT_TAG 0bc3b03202b3159fc9b085b3ae6d66ec071c25d6 # v1.5.1 34 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/srt 35 | BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/srt 36 | BUILD_COMMAND cmake --build ${CMAKE_CURRENT_SOURCE_DIR}/srt --config ${CMAKE_BUILD_TYPE} --target srt_static 37 | CMAKE_ARGS -DCMAKE_POSITION_INDEPENDENT_CODE=ON ${MACOSX_OPENSSL_PATH} 38 | GIT_PROGRESS 1 39 | STEP_TARGETS build 40 | EXCLUDE_FROM_ALL TRUE 41 | INSTALL_COMMAND "" 42 | ) 43 | 44 | ExternalProject_Add(project_srt_win 45 | GIT_REPOSITORY https://github.com/Haivision/srt.git 46 | GIT_TAG v1.5.1 47 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/srt 48 | BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/srt 49 | CONFIGURE_COMMAND cmake -DCMAKE_GENERATOR_PLATFORM=x64 -DENABLE_STDCXX_SYNC=ON ${CMAKE_CURRENT_SOURCE_DIR}/srt 50 | BUILD_COMMAND cmake --build ${CMAKE_CURRENT_SOURCE_DIR}/srt --config ${CMAKE_BUILD_TYPE} --target srt_static 51 | CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_POSITION_INDEPENDENT_CODE=ON 52 | GIT_PROGRESS 1 53 | STEP_TARGETS build 54 | EXCLUDE_FROM_ALL TRUE 55 | INSTALL_COMMAND "" 56 | ) 57 | 58 | add_library(srt STATIC IMPORTED) 59 | IF (WIN32) 60 | set_property(TARGET srt PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/srt/${CMAKE_BUILD_TYPE}/srt_static.lib) 61 | add_dependencies(srt project_srt_win) 62 | ELSE() 63 | set_property(TARGET srt PROPERTY IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/srt/libsrt.a) 64 | add_dependencies(srt project_srt) 65 | ENDIF() 66 | 67 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/srt/) 68 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/srt/common) 69 | 70 | add_library(srtnet STATIC SRTNet.cpp) 71 | target_link_libraries(srtnet PUBLIC srt ${OPENSSL_LIBRARIES}) 72 | 73 | add_executable(cppSRTWrapper main.cpp) 74 | target_link_libraries(cppSRTWrapper srtnet Threads::Threads) 75 | 76 | # 77 | # Build unit tests using GoogleTest 78 | # 79 | 80 | add_executable(runUnitTests 81 | ${CMAKE_CURRENT_SOURCE_DIR}/test/TestSrt.cpp 82 | ) 83 | 84 | IF (NOT MSVC) 85 | target_compile_options(runUnitTests PRIVATE -Wall -Wextra -Wno-unused-parameter -Wno-unused-function) 86 | ENDIF() 87 | target_include_directories(runUnitTests 88 | PRIVATE 89 | ${CMAKE_CURRENT_SOURCE_DIR} 90 | ${CMAKE_CURRENT_SOURCE_DIR}/test 91 | SYSTEM 92 | ${GTEST_INCLUDE_DIRS}) 93 | 94 | target_link_libraries(runUnitTests srtnet GTest::gtest_main Threads::Threads) 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cppSRTWrapper 2 | 3 | *Simple C++ wrapper of the [SRT](https://github.com/Haivision/srt) protocol*. 4 | 5 | The SRT Protocol is a UDP based protocol. SRT acts as a mix of TCP and UDP where SRT tries to re-send lost data (as TCP would) but only to a deadline in time specified, when the deadline is reached and if the data has not arrived it's marked as lost (as UDP would). 6 | 7 | Streaming media solutions benefit from this approach and SRT is adopted widely across the media community. This C++ wrapper is simplifying implementations of SRT in C++ projects and with only a few lines of code you can create a server and/or a client. 8 | 9 | The API of SRT has more features than what's exposed in this C++ wrapper, however the base functions exists. If you feel this wrapper is missing any functionalty or features please let us know. 10 | 11 | 12 | **Current auto build status:** 13 | 14 | ![Ubuntu](https://github.com/andersc/cppSRTWrapper/workflows/Ubuntu%2018.04/badge.svg) 15 | 16 | ![MacOS](https://github.com/andersc/cppSRTWrapper/workflows/macos/badge.svg) 17 | 18 | ![Windows](https://github.com/andersc/cppSRTWrapper/workflows/Windows%20x64/badge.svg) 19 | 20 | **Code scanning alerts** 21 | 22 | ![Code scanning - action](https://github.com/andersc/cppSRTWrapper/workflows/Code%20scanning%20-%20action/badge.svg) 23 | 24 | [Code scanning details](https://github.com/andersc/cppSRTWrapper/security/code-scanning) 25 | 26 | ## Building 27 | 28 | Requires cmake version >= **3.10** and **C++17** 29 | 30 | *Linux, MacOS and Windows* 31 | 32 | (Read below for how to prepare the different systems) 33 | 34 | Observe *Windows* users need to point out where *GTest* is located 35 | **-DGTEST_ROOT={Installed location}**, please see the windows action for details. 36 | 37 | **Release:** 38 | 39 | ```sh 40 | mkdir build 41 | cd build 42 | cmake -DCMAKE_BUILD_TYPE=Release .. 43 | cmake --build . --config Release 44 | ``` 45 | 46 | ***Debug:*** 47 | 48 | ```sh 49 | mkdir build 50 | cd build 51 | cmake -DCMAKE_BUILD_TYPE=Debug .. 52 | cmake --build . --config Debug 53 | ``` 54 | 55 | ##Output (Linux and MacOS): 56 | 57 | **./libsrtnet.a** (The SRT-wrapper lib) 58 | 59 | **(source_root)/srt/libsrt.a** (The SRT-lib) 60 | 61 | **./cppSRTWrapper** (Runs trough the unit tests and returns 62 | EXIT_SUCCESS if all unit tests pass.) 63 | 64 | **./runUnitTests** (Runs unit tests using GoogleTest) 65 | 66 | 67 | ##Output (Windows): 68 | 69 | **./Release/srtnet.lib** (The wrapper lib Release build) 70 | 71 | **./Debug/srtnet.lib** (The wrapper lib Debug build) 72 | 73 | **(source_root)/srt/Release/srt_static.lib** (The SRT-lib Release build) 74 | 75 | **(source_root)/srt/Debug/srt_static.lib** (The SRT-lib Debug build) 76 | 77 | **./Release/cppSRTWrapper.exe** (Runs trough the unit tests and returns EXIT_SUCCESS if all unit tests pass. Release build) 78 | 79 | **./Debug/cppSRTWrapper.exe** (Runs trough the unit tests and returns EXIT_SUCCESS if all unit tests pass. Debug build) 80 | 81 | 82 | - 83 | 84 | # Preparing Linux 85 | 86 | ```sh 87 | sudo apt-get update 88 | sudo apt-get upgrade 89 | sudo apt-get install tclsh pkg-config cmake libssl-dev build-essential libgtest-dev 90 | ``` 91 | 92 | # Preparing MacOS 93 | 94 | Prepare your system by installing -> 95 | 96 | * [Xcode](https://itunes.apple.com/us/app/xcode/id497799835) 97 | . Then start Xcode and let xcode install Command Line Tools or run *xcode-select --install* from the terminal. 98 | 99 | * **Homebrew** -> [[https://brew.sh](https://brew.sh)) 100 | 101 | * Then Install dependencies 102 | 103 | ```sh 104 | brew install googletest 105 | brew install cmake 106 | brew install openssl 107 | export OPENSSL_ROOT_DIR=$(brew --prefix openssl) 108 | export OPENSSL_LIB_DIR=$(brew --prefix openssl)"/lib" 109 | export OPENSSL_INCLUDE_DIR=$(brew --prefix openssl)"/include" 110 | ``` 111 | 112 | # Preparing Windows 113 | 114 | 115 | Prepare your system by installing-> 116 | 117 | * [Visual Studio](https://visualstudio.microsoft.com/downloads/) 118 | (Also add the CMake build support if you plan to develop applications) 119 | 120 | * **chocolatey** -> [https://chocolatey.org](https://chocolatey.org) 121 | 122 | * Then Install dependencies 123 | 124 | ```sh 125 | choco install openssl 126 | choco install cmake 127 | choco install git 128 | ``` 129 | 130 | Install GTest in a terminal by: 131 | 132 | ``` 133 | git clone --branch v1.13.0 https://github.com/google/googletest 134 | cd googletest 135 | cmake -DCMAKE_INSTALL_PREFIX=C:/gtest -Dgtest_force_shared_crt=ON -DBUILD_GMOCK=OFF -DBUILD_GTEST=ON . 136 | cmake --build . --config Release --target INSTALL 137 | ``` 138 | 139 | The example above installs GTest to C:/gtest. Change the **CMAKE_INSTALL_PREFIX** if you want another location. 140 | 141 | ## Using the SRT wrapper 142 | 143 | **Server:** 144 | 145 | ```cpp 146 | 147 | //Create the server 148 | SRTNet mySRTNetServer; 149 | 150 | //Register the server callbacks 151 | //The validate connection callback -> 152 | mySRTNetServer.clientConnected=std::bind(&validateConnection, std::placeholders::_1, std::placeholders::_2); 153 | //The got data from client callback 154 | mySRTNetServer.recievedData=std::bind(&handleData, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); 155 | 156 | //Start the server 157 | 158 | //Listen IP 159 | //Listen Port 160 | //Number packet reorder window 161 | //Max re-send window (ms) / also the delay of transmission 162 | //% extra of the BW that will be allowed for re-transmission packets 163 | //MTU 164 | //(Optional) AES-128 key (No key or empty == no encryption) 165 | 166 | if (!mySRTNetServer.startServer("0.0.0.0", 8000, 16, 1000, 100, 1456,"Th1$_is_4_0pt10N4L_P$k")) { 167 | std::cout << "SRT Server failed to start." << std::endl; 168 | return EXIT_FAILURE; 169 | } 170 | 171 | //Send data to a client (you need the client handle) 172 | mySRTNetServer.sendData(content->data(), content->size(), &thisMSGCTRL,clientHandle); 173 | 174 | //Get client handles using the lambda 175 | mySRTNetServer.getActiveClients([](std::map> &clientList) 176 | { 177 | std::cout << "The server got " << clientList.size() << " clients." << std::endl; 178 | } 179 | ); 180 | 181 | 182 | 183 | //When a client connects you can validate the connection and also embedd one of your objects into that SRT connection. 184 | 185 | //Return a connection object. (Return nullptr if you don't want to connect to that client) 186 | std::shared_ptr validateConnection(struct sockaddr &sin, SRTSOCKET newSocket) { 187 | 188 | //sin contains the connections properties 189 | //newSocket contains the SRT handle for that particular client 190 | 191 | //To reject the connection 192 | return nullptr; 193 | 194 | //Accepting the connection by returning a 'NetworkConnection' 195 | //A NetworkConnection contains a 'object' where you can put a shared pointer 196 | //To any of your objects. When the connection drops from that 197 | //Client the destructor is called as long as you do not also keep a 198 | //handle to that class. 199 | //In all communication with you the 'NetworkConnection' clas is also provided. 200 | //You can throw generators or parsers in there if wanted (MPEG-TS or anything else) 201 | //See the example for how to retreive the object back. 202 | 203 | auto a1 = std::make_shared(); 204 | a1->object = std::make_shared(); 205 | return a1; 206 | 207 | } 208 | 209 | 210 | / 211 | ``` 212 | 213 | **Client:** 214 | 215 | ```cpp 216 | 217 | //Create the client 218 | SRTNet mySRTNetClient; 219 | 220 | //Register the callback (The server sends data to the client) 221 | mySRTNetClient.recievedData=std::bind(&handleDataClient, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); 222 | 223 | //Connect to a server 224 | 225 | //Server IP 226 | //Server Port 227 | //Number packet reorder window 228 | //Max re-send window (ms) / also the delay of transmission 229 | //% extra of the BW that will be allowed for re-transmission packets 230 | //MTU 231 | //(Optional) AES-128 key (No key or empty == no encryption) 232 | if (!mySRTNetClient.startClient("127.0.0.1", 8000, 16, 1000, 100,client1Connection, 1456,"Th1$_is_4_0pt10N4L_P$k")) { 233 | std::cout << "SRT client failed starting." << std::endl; 234 | return EXIT_FAILURE; 235 | } 236 | 237 | //Send data to the server 238 | SRT_MSGCTRL thisMSGCTRL = srt_msgctrl_default; 239 | mySRTNetClient.sendData(buffer2.data(), buffer2.size(), &thisMSGCTRL); 240 | 241 | ``` 242 | 243 | ## Credits 244 | 245 | The [SRT](https://github.com/Haivision/srt) team for all the help and positive feedback 246 | 247 | Thanks all [contributors](https://github.com/andersc/cppSRTWrapper/graphs/contributors) for bugfixes and enhancing the solution!! 248 | 249 | 250 | ## License 251 | 252 | *MIT* 253 | 254 | Read *LICENCE.md* for details 255 | -------------------------------------------------------------------------------- /SRTNet.h: -------------------------------------------------------------------------------- 1 | // __________ ____ _____ ____ ______ _ ______ ___ ____ ____ __________ 2 | // / ____/ __ \/ __ \ / ___// __ \/_ __/ | | / / __ \/ | / __ \/ __ \/ ____/ __ \ 3 | // / / / /_/ / /_/ / \__ \/ /_/ / / / | | /| / / /_/ / /| | / /_/ / /_/ / __/ / /_/ / 4 | // / /___/ ____/ ____/ ___/ / _, _/ / / | |/ |/ / _, _/ ___ |/ ____/ ____/ /___/ _, _/ 5 | // \____/_/ /_/ /____/_/ |_| /_/ |__/|__/_/ |_/_/ |_/_/ /_/ /_____/_/ |_| 6 | // 7 | // Created by Anders Cedronius on 2019-04-21. 8 | // 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "srt/srtcore/srt.h" 25 | 26 | #ifdef WIN32 27 | #include 28 | #define _WINSOCKAPI_ 29 | #include 30 | #include 31 | #pragma comment(lib, "ws2_32.lib") 32 | #else 33 | 34 | #include 35 | 36 | #endif 37 | 38 | #define MAX_WORKERS 5 // Max number of connections to deal with each epoll 39 | 40 | namespace SRTNetClearStats { 41 | enum SRTNetClearStats : int { no, yes }; 42 | } 43 | 44 | namespace SRTNetInstant { 45 | enum SRTNetInstant : int { no, yes }; 46 | } 47 | 48 | class SRTNet { 49 | public: 50 | 51 | enum class Mode { 52 | unknown, 53 | server, 54 | client 55 | }; 56 | 57 | // Fill this class with all information you need for the duration of the connection both client and server 58 | class NetworkConnection { 59 | public: 60 | std::any mObject; 61 | }; 62 | 63 | SRTNet(); 64 | 65 | virtual ~SRTNet(); 66 | 67 | /** 68 | * 69 | * Starts an SRT Server 70 | * 71 | * @param localIP Listen IP 72 | * @param localPort Listen Port 73 | * @param reorder number of packets in re-order window 74 | * @param latency Max re-send window (ms) / also the delay of transmission 75 | * @param overhead % extra of the BW that will be allowed for re-transmission packets 76 | * @param mtu sets the MTU 77 | * @param peerIdleTimeout Optional Connection considered broken if no packet received before this timeout. 78 | * Defaults to 5 seconds. 79 | * @param psk Optional Pre Shared Key (AES-128) 80 | * @param singleSender set to true to accept just one sender to connect to the server, otherwise the server will 81 | * keep waiting and accepting more incoming sender connections 82 | * @param ctx optional context used only in the clientConnected callback 83 | * @return true if server was able to start 84 | */ 85 | bool startServer(const std::string& localIP, 86 | uint16_t localPort, 87 | int reorder, 88 | int32_t latency, 89 | int overhead, 90 | int mtu, 91 | int32_t peerIdleTimeout = 5000, 92 | const std::string& psk = "", 93 | bool singleSender = false, 94 | std::shared_ptr ctx = {}); 95 | 96 | /** 97 | * 98 | * Starts an SRT Client 99 | * 100 | * @param host Remote host IP or hostname 101 | * @param port Remote host Port 102 | * @param reorder number of packets in re-order window 103 | * @param latency Max re-send window (ms) / also the delay of transmission 104 | * @param overhead % extra of the BW that will be allowed for re-transmission packets 105 | * @param ctx the context used in the receivedData and receivedDataNoCopy callback 106 | * @param mtu sets the MTU 107 | * @param peerIdleTimeout Optional Connection considered broken if no packet received before this timeout. 108 | * Defaults to 5 seconds. 109 | * @param psk Optional Pre Shared Key (AES-128) 110 | * @return true if client was able to connect to the server 111 | */ 112 | bool startClient(const std::string& host, 113 | uint16_t port, 114 | int reorder, 115 | int32_t latency, 116 | int overhead, 117 | std::shared_ptr& ctx, 118 | int mtu, 119 | int32_t peerIdleTimeout = 5000, 120 | const std::string& psk = ""); 121 | 122 | /** 123 | * 124 | * Starts an SRT Client with a specified local address to bind to 125 | * 126 | * @param host Remote host IP or hostname to connect to 127 | * @param port Remote host port to connect to 128 | * @param localHost Local host IP to bind to 129 | * @param localPort Local port to bind to, use 0 to automatically pick an unused port 130 | * @param reorder number of packets in re-order window 131 | * @param latency Max re-send window (ms) / also the delay of transmission 132 | * @param overhead % extra of the BW that will be allowed for re-transmission packets 133 | * @param ctx the context used in the receivedData and receivedDataNoCopy callback 134 | * @param mtu sets the MTU 135 | * @param peerIdleTimeout Optional Connection considered broken if no packet received before this timeout. 136 | * Defaults to 5 seconds. 137 | * @param psk Optional Pre Shared Key (AES-128) 138 | * @return true if client was able to connect to the server 139 | */ 140 | bool startClient(const std::string& host, 141 | uint16_t port, 142 | const std::string& localHost, 143 | uint16_t localPort, 144 | int reorder, 145 | int32_t latency, 146 | int overhead, 147 | std::shared_ptr& ctx, 148 | int mtu, 149 | int32_t peerIdleTimeout = 5000, 150 | const std::string& psk = ""); 151 | 152 | /** 153 | * 154 | * Stops the service 155 | * 156 | * @return true if the service stopped successfully. 157 | */ 158 | bool stop(); 159 | 160 | /** 161 | * 162 | * Send data 163 | * 164 | * @param data pointer to the data 165 | * @param size size of the data 166 | * @param msgCtrl pointer to a SRT_MSGCTRL struct. 167 | * @param targetSystem the target sending the data to (used in server mode only) 168 | * @return true if sendData was able to send the data to the target. 169 | */ 170 | bool sendData(const uint8_t* data, size_t size, SRT_MSGCTRL* msgCtrl, SRTSOCKET targetSystem = 0); 171 | 172 | /** 173 | * 174 | * Get connection statistics 175 | * 176 | * @param currentStats pointer to the statistics struct 177 | * @param clear Clears the data after reading SRTNetClearStats::yes or no 178 | * @param instantaneous Get the parameters now SRTNetInstant::yes or filtered values SRTNetInstant::no 179 | * @param targetSystem The target connection to get statistics about (used in server mode only) 180 | * @return true if statistics was populated. 181 | */ 182 | bool getStatistics(SRT_TRACEBSTATS* currentStats, int clear, int instantaneous, SRTSOCKET targetSystem = 0); 183 | 184 | /** 185 | * 186 | * Get active clients (A server method) 187 | * 188 | * @param function. pass a function getting the map containing the list of active connections 189 | * Where the map contains the SRTSocketHandle (SRTSOCKET) and it's associated NetworkConnection you provided. 190 | */ 191 | void 192 | getActiveClients(const std::function>&)>& function); 193 | 194 | /** 195 | * 196 | * @brief Get the SRT socket and the network connection context object associated with the connected server. This 197 | * method only works when in client mode. 198 | * @returns The SRT socket and network connection context of the connected server in case this SRTNet is in client 199 | * mode and is connected to a SRT server. Returns {0, nullptr} if not in client mode or if in client mode and not 200 | * connected yet. 201 | * 202 | */ 203 | std::pair> getConnectedServer(); 204 | 205 | /** 206 | * 207 | * @brief Get the underlying, bound SRT socket. Works both in client and server mode. 208 | * @returns The bound socket in case there is one, otherwise 0. 209 | * 210 | */ 211 | SRTSOCKET getBoundSocket() const; 212 | 213 | /** 214 | * 215 | * @brief Get the current operating mode. 216 | * @returns The operating mode. 217 | * 218 | */ 219 | Mode getCurrentMode() const; 220 | 221 | /// Callback handling connecting clients (only server mode) 222 | std::function(struct sockaddr& sin, 223 | SRTSOCKET newSocket, 224 | std::shared_ptr& ctx)> 225 | clientConnected = nullptr; 226 | /// Callback receiving data type vector 227 | std::function>& data, 228 | SRT_MSGCTRL& msgCtrl, 229 | std::shared_ptr& ctx, 230 | SRTSOCKET socket)> 231 | receivedData = nullptr; 232 | 233 | /// Callback receiving data no copy 234 | std::function& ctx, 238 | SRTSOCKET socket)> 239 | receivedDataNoCopy = nullptr; 240 | 241 | /// Callback handling disconnecting clients (server and client mode) 242 | std::function& ctx, SRTSOCKET lSocket)> clientDisconnected = nullptr; 243 | 244 | // delete copy and move constructors and assign operators 245 | SRTNet(SRTNet const&) = delete; // Copy construct 246 | SRTNet(SRTNet&&) = delete; // Move construct 247 | SRTNet& operator=(SRTNet const&) = delete; // Copy assign 248 | SRTNet& operator=(SRTNet&&) = delete; // Move assign 249 | 250 | private: 251 | // Internal variables and methods 252 | 253 | void waitForSRTClient(bool singleSender); 254 | 255 | void serverEventHandler(); 256 | 257 | void clientWorker(); 258 | 259 | void closeAllClientSockets(); 260 | 261 | // Server active? true == yes 262 | std::atomic mServerActive = {false}; 263 | // Client active? true == yes 264 | std::atomic mClientActive = {false}; 265 | 266 | std::thread mWorkerThread; 267 | std::thread mEventThread; 268 | 269 | SRTSOCKET mContext = 0; 270 | int mPollID = 0; 271 | mutable std::mutex mNetMtx; 272 | Mode mCurrentMode = Mode::unknown; 273 | std::map> mClientList = {}; 274 | std::mutex mClientListMtx; 275 | std::shared_ptr mClientContext = nullptr; 276 | std::shared_ptr mConnectionContext = nullptr; 277 | }; 278 | 279 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Anders Cedronius on 2019-04-21. 3 | // 4 | // 5 | 6 | #include 7 | #include 8 | #include 9 | #include "SRTNet.h" 10 | 11 | SRTNet gSRTNetServer; 12 | SRTNet gSRTNetClient1; 13 | SRTNet gSRTNetClient2; 14 | 15 | std::atomic_bool gCloseConnection1 = {false}; 16 | 17 | //This is my class managed by the network connection. 18 | class MyClass { 19 | public: 20 | MyClass(SRTSOCKET srtSocket) { 21 | mIsKnown = false; 22 | mClientSocket = srtSocket; 23 | }; 24 | 25 | ~MyClass() { 26 | if (mClientSocket) 27 | std::cout << "Client -> " << mClientSocket << " disconnected. (From Object)" << std::endl; 28 | }; 29 | int mTest = 0; 30 | int mCounter = 0; 31 | std::atomic_bool mIsKnown; 32 | SRTSOCKET mClientSocket; 33 | }; 34 | 35 | //This is a class used to test the optional connection context in the validateConnection callback. 36 | class ConnectionClass { 37 | public: 38 | int mSomeNumber = 111; 39 | }; 40 | 41 | //********************************** 42 | //Server part 43 | //********************************** 44 | 45 | //Return a connection object. (Return nullptr if you don't want to connect to that client) 46 | std::shared_ptr 47 | validateConnection(struct sockaddr &rSin, SRTSOCKET lNewSocket, std::shared_ptr &rpCtx) { 48 | 49 | if (rpCtx != nullptr) { 50 | auto lConnCls = std::any_cast &>(rpCtx->mObject); 51 | if (lConnCls->mSomeNumber != 111) { 52 | return nullptr; 53 | } 54 | } else { 55 | return nullptr; 56 | } 57 | 58 | char lAddrIPv6[INET6_ADDRSTRLEN]; 59 | 60 | if (rSin.sa_family == AF_INET) { 61 | struct sockaddr_in *lpInConnectionV4 = (struct sockaddr_in *) &rSin; 62 | auto *lIp = (unsigned char *) &lpInConnectionV4->sin_addr.s_addr; 63 | std::cout << "Connecting IPv4: " << unsigned(lIp[0]) << "." << unsigned(lIp[1]) << "." << unsigned(lIp[2]) << "." 64 | << unsigned(lIp[3]) << std::endl; 65 | 66 | //Do we want to accept this connection? 67 | //return nullptr; 68 | 69 | 70 | } else if (rSin.sa_family == AF_INET6) { 71 | struct sockaddr_in6 *pInConnectionV6 = (struct sockaddr_in6 *) &rSin; 72 | inet_ntop(AF_INET6, &pInConnectionV6->sin6_addr, lAddrIPv6, INET6_ADDRSTRLEN); 73 | printf("Connecting IPv6: %s\n", lAddrIPv6); 74 | 75 | //Do we want to accept this connection? 76 | //return nullptr; 77 | 78 | } else { 79 | //Not IPv4 and not IPv6. That's weird. don't connect. 80 | return nullptr; 81 | } 82 | 83 | auto lNetConn = std::make_shared(); 84 | lNetConn->mObject = std::make_shared(lNewSocket); 85 | return lNetConn; 86 | } 87 | 88 | //Data callback. 89 | bool 90 | handleData(std::unique_ptr> &rContent, SRT_MSGCTRL &rMsgCtrl, std::shared_ptr& rpCtx, 91 | SRTSOCKET lClientHandle) { 92 | 93 | //Try catch? 94 | auto lpMyClass = std::any_cast &>(rpCtx->mObject); 95 | 96 | if (!lpMyClass->mIsKnown) { //just a example/test. This connection is unknown. See what connection it is and set the test-parameter accordingly 97 | if (rContent->data()[0] == 1) { 98 | lpMyClass->mIsKnown = true; 99 | lpMyClass->mTest = 1; 100 | } 101 | 102 | if (rContent->data()[0] == 2) { 103 | lpMyClass->mIsKnown = true; 104 | lpMyClass->mTest = 2; 105 | } 106 | } 107 | 108 | if (lpMyClass->mIsKnown) { 109 | if (lpMyClass->mCounter++ == 100) { //every 100 packet you got respond back to the client using the same data. 110 | lpMyClass->mCounter = 0; 111 | SRT_MSGCTRL thisMSGCTRL = srt_msgctrl_default; 112 | gSRTNetServer.sendData(rContent->data(), rContent->size(), &thisMSGCTRL, lClientHandle); 113 | } 114 | } 115 | 116 | // std::cout << "Got ->" << content->size() << " " << std::endl; 117 | 118 | return true; 119 | }; 120 | 121 | //Client disconnect callback. 122 | void clientDisconnect(std::shared_ptr& pCtx, SRTSOCKET lClientHandle) { 123 | std::cout << "Client -> " << lClientHandle << " disconnected. (From callback)" << std::endl; 124 | } 125 | 126 | 127 | //********************************** 128 | //Client part 129 | //********************************** 130 | 131 | //Server sent back data callback. 132 | void handleDataClient(std::unique_ptr> &rContent, SRT_MSGCTRL &rMsgCtrl, 133 | std::shared_ptr &rpCtx, SRTSOCKET lServerHandle) { 134 | 135 | std::cout << "Got data ->" << rContent->size() << std::endl; 136 | 137 | //Try catch? 138 | auto lpMyClass = std::any_cast &>(rpCtx->mObject); 139 | 140 | int lPacketSize = rContent->size(); 141 | if (lpMyClass->mTest == 1) { 142 | std::cout << "Got client1 data ->" << lPacketSize << std::endl; 143 | lpMyClass->mCounter++; 144 | if (lpMyClass->mCounter == 1) { //kill this connection after 1 packet from server 145 | gCloseConnection1 = true; 146 | } 147 | return; 148 | } 149 | if (lpMyClass->mTest == 2) { 150 | std::cout << "Got client2 data ->" << lPacketSize << std::endl; 151 | return; 152 | } 153 | return; 154 | }; 155 | 156 | int main(int argc, const char *argv[]) { 157 | std::cout << "SRT wrapper start." << std::endl; 158 | srt_startup(); 159 | 160 | bool lRunOnce = true; 161 | //Register the server callbacks 162 | gSRTNetServer.clientConnected = std::bind(&validateConnection, std::placeholders::_1, std::placeholders::_2, 163 | std::placeholders::_3); 164 | gSRTNetServer.receivedData = std::bind(&handleData, std::placeholders::_1, std::placeholders::_2, 165 | std::placeholders::_3, std::placeholders::_4); 166 | 167 | //The disconnect callback is optional you can use the destructor in your embedded object to detect disconnects also 168 | gSRTNetServer.clientDisconnected = std::bind(&clientDisconnect, std::placeholders::_1, std::placeholders::_2); 169 | 170 | //Create a optional connection context 171 | auto lConn1 = std::make_shared(); 172 | lConn1->mObject = std::make_shared(); 173 | 174 | /*Start the server 175 | * ip: bind to this ip (can be IPv4 or IPv6) 176 | * port: bind to this port 177 | * reorder: Number of packets in the reorder window 178 | * latency: the max latency in milliseconds before dropping the data 179 | * overhead: The % overhead tolerated for retransmits relative the original data stream. 180 | * mtu: max 1456 181 | */ 182 | if (!gSRTNetServer.startServer("0.0.0.0", 8000, 16, 1000, 100, 1456, 5000, "Th1$_is_4_0pt10N4L_P$k", false, lConn1)) { 183 | std::cout << "SRT Server failed to start." << std::endl; 184 | return EXIT_FAILURE; 185 | } 186 | 187 | //The SRT connection is bidirectional and you are able to set different parameters for a particular direction 188 | //The parameters have the same meaning as for the above server but on the client side. 189 | auto lClient1Connection = std::make_shared(); 190 | std::shared_ptr lpMyClass1 = std::make_shared(0); 191 | lpMyClass1->mTest = 1; 192 | lClient1Connection->mObject = std::move(lpMyClass1); 193 | 194 | gSRTNetClient1.receivedData = std::bind(&handleDataClient, std::placeholders::_1, std::placeholders::_2, 195 | std::placeholders::_3, std::placeholders::_4); 196 | if (!gSRTNetClient1.startClient("127.0.0.1", 8000, 16, 1000, 100, lClient1Connection, 1456, 5000, 197 | "Th1$_is_4_0pt10N4L_P$k")) { 198 | std::cout << "SRT client1 failed starting." << std::endl; 199 | return EXIT_FAILURE; 200 | } 201 | 202 | auto lClient2Connection = std::make_shared(); 203 | std::shared_ptr lpMyClass2 = std::make_shared(0); 204 | lpMyClass2->mTest = 2; 205 | lClient2Connection->mObject = std::move(lpMyClass2); 206 | gSRTNetClient2.receivedData = std::bind(&handleDataClient, std::placeholders::_1, std::placeholders::_2, 207 | std::placeholders::_3, std::placeholders::_4); 208 | if (!gSRTNetClient2.startClient("127.0.0.1", 8000, 16, 1000, 100, lClient2Connection, 1456, 5000, 209 | "Th1$_is_4_0pt10N4L_P$k")) { 210 | std::cout << "SRT client2 failed starting." << std::endl; 211 | return EXIT_FAILURE; 212 | } 213 | 214 | std::this_thread::sleep_for(std::chrono::seconds(2)); 215 | 216 | // auto clients = mySRTNetServer.getActiveClients(); 217 | // std::cout << "The server got " << clients->mClientList->size() << " clients." << std::endl; 218 | // clients = nullptr; 219 | 220 | gSRTNetServer.getActiveClients([](std::map> &clientList) { 221 | std::cout << "The server got " << clientList.size() << " client(s)." << std::endl; 222 | } 223 | ); 224 | 225 | //Send 300 packets with 10 milliseconds spacing. Packets are 1000 bytes long 226 | int lTimes = 0; 227 | 228 | std::vector lBuffer1(1000); 229 | std::fill(lBuffer1.begin(), lBuffer1.end(), 1); 230 | std::vector lBuffer2(1000); 231 | std::fill(lBuffer2.begin(), lBuffer2.end(), 2); 232 | 233 | std::cout << "SRT Start send." << std::endl; 234 | 235 | bool lStillSendClient1Data = true; 236 | 237 | while (true) { 238 | SRT_MSGCTRL lThisMSGCTRL1 = srt_msgctrl_default; 239 | if (lStillSendClient1Data) { 240 | lStillSendClient1Data = gSRTNetClient1.sendData(lBuffer1.data(), lBuffer1.size(), &lThisMSGCTRL1); 241 | } 242 | 243 | if (gCloseConnection1 && lRunOnce) { 244 | lRunOnce = false; 245 | gSRTNetClient1.stop(); 246 | } 247 | 248 | SRT_MSGCTRL lThisMSGCTRL2 = srt_msgctrl_default; 249 | gSRTNetClient2.sendData(lBuffer2.data(), lBuffer2.size(), &lThisMSGCTRL2); 250 | 251 | std::this_thread::sleep_for(std::chrono::microseconds(10000)); 252 | if (lTimes++ == 300) { 253 | break; 254 | } 255 | } 256 | 257 | std::cout << "Done sending" << std::endl; 258 | std::this_thread::sleep_for(std::chrono::seconds(4)); 259 | std::cout << "Get statistics." << std::endl; 260 | 261 | //Get statistics like this -> 262 | /* 263 | * SRTNetClearStats::no == Do not reset statistics after being read SRTNetClearStats::yes == clear statistics after being read 264 | * instantaneous == 1 if the statistics should be the instant data, not moving averages 265 | */ 266 | SRT_TRACEBSTATS lCurrentClientStats1 = {0}; 267 | gSRTNetClient1.getStatistics(&lCurrentClientStats1, SRTNetClearStats::yes, SRTNetInstant::no); 268 | 269 | SRT_TRACEBSTATS lCurrentClientStats2 = {0}; 270 | gSRTNetClient2.getStatistics(&lCurrentClientStats2, SRTNetClearStats::yes, SRTNetInstant::no); 271 | 272 | //SRT_TRACEBSTATS lCurrentServerStats = {0}; 273 | //mySRTNetServer.getStatistics(¤tServerStats,SRTNetClearStats::yes,SRTNetInstant::no); 274 | 275 | gSRTNetServer.getActiveClients([](std::map> &rClientList) { 276 | std::cout << "The server got " << rClientList.size() << " clients." << std::endl; 277 | } 278 | ); 279 | 280 | std::cout << "SRT garbage collect" << std::endl; 281 | gSRTNetServer.stop(); 282 | std::this_thread::sleep_for(std::chrono::seconds(2)); 283 | std::cout << "stopClient 1" << std::endl; 284 | gSRTNetClient1.stop(); 285 | std::cout << "stopClient 2" << std::endl; 286 | gSRTNetClient2.stop(); 287 | 288 | gSRTNetServer.getActiveClients([](std::map> &clientList) { 289 | std::cout << "The server got " << clientList.size() << " clients." << std::endl; 290 | } 291 | ); 292 | 293 | srt_cleanup(); 294 | std::cout << "SRT wrapper did end." << std::endl; 295 | return EXIT_SUCCESS; 296 | } 297 | -------------------------------------------------------------------------------- /SRTNet.cpp: -------------------------------------------------------------------------------- 1 | // __________ ____ _____ ____ ______ _ ______ ___ ____ ____ __________ 2 | // / ____/ __ \/ __ \ / ___// __ \/_ __/ | | / / __ \/ | / __ \/ __ \/ ____/ __ \ 3 | // / / / /_/ / /_/ / \__ \/ /_/ / / / | | /| / / /_/ / /| | / /_/ / /_/ / __/ / /_/ / 4 | // / /___/ ____/ ____/ ___/ / _, _/ / / | |/ |/ / _, _/ ___ |/ ____/ ____/ /___/ _, _/ 5 | // \____/_/ /_/ /____/_/ |_| /_/ |__/|__/_/ |_/_/ |_/_/ /_/ /_____/_/ |_| 6 | // 7 | // Created by Anders Cedronius on 2019-04-21. 8 | // 9 | 10 | #include "SRTNet.h" 11 | 12 | #include 13 | 14 | #include "SRTNetInternal.h" 15 | 16 | namespace { 17 | 18 | /// Wrapper around sockaddr_in 19 | class SocketAddress { 20 | public: 21 | SocketAddress(const std::string& ip, uint16_t port) 22 | : mIP(ip) 23 | , mPort(port) { 24 | } 25 | 26 | /// 27 | /// @return True if this SocketAddress is an IPv4 address 28 | bool isIPv4() { 29 | sockaddr_in sa{}; 30 | return inet_pton(AF_INET, mIP.c_str(), &sa.sin_addr) != 0; 31 | } 32 | 33 | /// 34 | /// @return True if this SocketAddress is an IPv6 address 35 | bool isIPv6() { 36 | sockaddr_in6 sa{}; 37 | return inet_pton(AF_INET6, mIP.c_str(), &sa.sin6_addr) != 0; 38 | } 39 | 40 | /// 41 | /// @return Get this address as an IPv4 sockaddr_in, nullopt if not a valid IPv4 address 42 | [[nodiscard]] std::optional getIPv4() const { 43 | sockaddr_in socketAddressV4 = {0}; 44 | socketAddressV4.sin_family = AF_INET; 45 | socketAddressV4.sin_port = htons(mPort); 46 | if (inet_pton(AF_INET, mIP.c_str(), &socketAddressV4.sin_addr) != 1) { 47 | return std::nullopt; 48 | } 49 | return socketAddressV4; 50 | } 51 | 52 | /// 53 | /// @return Get this address as an IPv6 sockaddr_in6, nullopt if not a valid IPv6 address 54 | [[nodiscard]] std::optional getIPv6() const { 55 | sockaddr_in6 socketAddressV6 = {0}; 56 | socketAddressV6.sin6_family = AF_INET6; 57 | socketAddressV6.sin6_port = htons(mPort); 58 | if (inet_pton(AF_INET6, mIP.c_str(), &socketAddressV6.sin6_addr) != 1) { 59 | return std::nullopt; 60 | } 61 | return socketAddressV6; 62 | } 63 | 64 | private: 65 | std::string mIP; 66 | uint16_t mPort; 67 | }; 68 | 69 | } // namespace 70 | 71 | SRTNet::SRTNet() { 72 | SRT_LOGGER(true, LOGG_NOTIFY, "SRTNet constructed"); 73 | } 74 | 75 | SRTNet::~SRTNet() { 76 | stop(); 77 | SRT_LOGGER(true, LOGG_NOTIFY, "SRTNet destruct") 78 | } 79 | 80 | void SRTNet::closeAllClientSockets() { 81 | std::lock_guard lock(mClientListMtx); 82 | for (auto& client : mClientList) { 83 | SRTSOCKET socket = client.first; 84 | int result = srt_close(socket); 85 | if (clientDisconnected) { 86 | clientDisconnected(client.second, socket); 87 | } 88 | if (result == SRT_ERROR) { 89 | SRT_LOGGER(true, LOGG_ERROR, "srt_close failed: " << srt_getlasterror_str()); 90 | } 91 | } 92 | mClientList.clear(); 93 | } 94 | 95 | bool SRTNet::startServer(const std::string& ip, 96 | uint16_t port, 97 | int reorder, 98 | int32_t latency, 99 | int overhead, 100 | int mtu, 101 | int32_t peerIdleTimeout, 102 | const std::string& psk, 103 | bool singleSender, 104 | std::shared_ptr ctx) { 105 | std::lock_guard lock(mNetMtx); 106 | 107 | SocketAddress socketAddress(ip, port); 108 | if (!socketAddress.isIPv4() && !socketAddress.isIPv6()) { 109 | SRT_LOGGER(true, LOGG_ERROR, "Failed to parse socket address"); 110 | return false; 111 | } 112 | 113 | if (mCurrentMode != Mode::unknown) { 114 | SRT_LOGGER(true, LOGG_ERROR, 115 | " " 116 | << "SRTNet mode is already set"); 117 | return false; 118 | } 119 | 120 | if (!clientConnected) { 121 | SRT_LOGGER(true, LOGG_FATAL, "waitForSRTClient needs clientConnected callback method terminating server!"); 122 | return false; 123 | } 124 | 125 | mConnectionContext = ctx; // retain the optional context 126 | 127 | mContext = srt_create_socket(); 128 | if (mContext == SRT_ERROR) { 129 | SRT_LOGGER(true, LOGG_FATAL, "srt_socket: " << srt_getlasterror_str()); 130 | return false; 131 | } 132 | 133 | int32_t yes = 1; 134 | int result = srt_setsockflag(mContext, SRTO_RCVSYN, &yes, sizeof(yes)); 135 | if (result == SRT_ERROR) { 136 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_RCVSYN: " << srt_getlasterror_str()); 137 | return false; 138 | } 139 | 140 | result = srt_setsockflag(mContext, SRTO_LATENCY, &latency, sizeof(latency)); 141 | if (result == SRT_ERROR) { 142 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_LATENCY: " << srt_getlasterror_str()); 143 | return false; 144 | } 145 | 146 | result = srt_setsockflag(mContext, SRTO_LOSSMAXTTL, &reorder, sizeof(reorder)); 147 | if (result == SRT_ERROR) { 148 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_LOSSMAXTTL: " << srt_getlasterror_str()); 149 | return false; 150 | } 151 | 152 | result = srt_setsockflag(mContext, SRTO_OHEADBW, &overhead, sizeof(overhead)); 153 | if (result == SRT_ERROR) { 154 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_OHEADBW: " << srt_getlasterror_str()); 155 | return false; 156 | } 157 | 158 | result = srt_setsockflag(mContext, SRTO_PAYLOADSIZE, &mtu, sizeof(mtu)); 159 | if (result == SRT_ERROR) { 160 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_PAYLOADSIZE: " << srt_getlasterror_str()); 161 | return false; 162 | } 163 | 164 | if (psk.length()) { 165 | int32_t aes128 = 16; 166 | result = srt_setsockflag(mContext, SRTO_PBKEYLEN, &aes128, sizeof(aes128)); 167 | if (result == SRT_ERROR) { 168 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_PBKEYLEN: " << srt_getlasterror_str()); 169 | return false; 170 | } 171 | 172 | result = srt_setsockflag(mContext, SRTO_PASSPHRASE, psk.c_str(), psk.length()); 173 | if (result == SRT_ERROR) { 174 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_PASSPHRASE: " << srt_getlasterror_str()); 175 | return false; 176 | } 177 | } 178 | 179 | result = srt_setsockflag(mContext, SRTO_PEERIDLETIMEO, &peerIdleTimeout, sizeof(peerIdleTimeout)); 180 | if (result == SRT_ERROR) { 181 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag : SRTO_PEERIDLETIMEO" << srt_getlasterror_str()); 182 | return false; 183 | } 184 | 185 | std::optional ipv4Address = socketAddress.getIPv4(); 186 | if (ipv4Address.has_value()) { 187 | result = srt_bind(mContext, reinterpret_cast(&ipv4Address.value()), sizeof(ipv4Address.value())); 188 | if (result == SRT_ERROR) { 189 | SRT_LOGGER(true, LOGG_FATAL, "srt_bind: " << srt_getlasterror_str()); 190 | srt_close(mContext); 191 | return false; 192 | } 193 | } 194 | 195 | std::optional ipv6Address = socketAddress.getIPv6(); 196 | if (ipv6Address.has_value()) { 197 | result = srt_bind(mContext, reinterpret_cast(&ipv6Address.value()), sizeof(ipv6Address.value())); 198 | if (result == SRT_ERROR) { 199 | SRT_LOGGER(true, LOGG_FATAL, "srt_bind: " << srt_getlasterror_str()); 200 | srt_close(mContext); 201 | return false; 202 | } 203 | } 204 | 205 | result = srt_listen(mContext, 2); 206 | if (result == SRT_ERROR) { 207 | SRT_LOGGER(true, LOGG_FATAL, "srt_listen: " << srt_getlasterror_str()); 208 | srt_close(mContext); 209 | return false; 210 | } 211 | mServerActive = true; 212 | mCurrentMode = Mode::server; 213 | mWorkerThread = std::thread(&SRTNet::waitForSRTClient, this, singleSender); 214 | return true; 215 | } 216 | 217 | void SRTNet::serverEventHandler() { 218 | SRT_EPOLL_EVENT ready[MAX_WORKERS]; 219 | while (mServerActive) { 220 | int ret = srt_epoll_uwait(mPollID, &ready[0], MAX_WORKERS, 1000); 221 | 222 | if (ret > 0) { 223 | for (size_t i = 0; i < ret; i++) { 224 | uint8_t msg[2048]; 225 | SRT_MSGCTRL thisMSGCTRL = srt_msgctrl_default; 226 | SRTSOCKET thisSocket = ready[i].fd; 227 | int result = srt_recvmsg2(thisSocket, reinterpret_cast(msg), sizeof(msg), &thisMSGCTRL); 228 | 229 | std::lock_guard lock(mClientListMtx); 230 | auto iterator = mClientList.find(thisSocket); 231 | if (result == SRT_ERROR) { 232 | SRT_LOGGER(true, LOGG_ERROR, "srt_recvmsg error: " << result << " " << srt_getlasterror_str()); 233 | if (iterator == mClientList.end()) { 234 | continue; // This client has already been removed by closeAllClientSockets() 235 | } 236 | auto ctx = iterator->second; 237 | mClientList.erase(iterator->first); 238 | srt_epoll_remove_usock(mPollID, thisSocket); 239 | srt_close(thisSocket); 240 | if (clientDisconnected) { 241 | clientDisconnected(ctx, thisSocket); 242 | } 243 | } else if (result > 0 && receivedData) { 244 | auto pointer = std::make_unique>(msg, msg + result); 245 | receivedData(pointer, thisMSGCTRL, iterator->second, thisSocket); 246 | } else if (result > 0 && receivedDataNoCopy) { 247 | receivedDataNoCopy(msg, result, thisMSGCTRL, iterator->second, thisSocket); 248 | } 249 | } 250 | if (mClientList.empty()) { 251 | break; 252 | } 253 | } else if (ret == -1) { 254 | SRT_LOGGER(true, LOGG_ERROR, "epoll error: " << srt_getlasterror_str()); 255 | } 256 | } 257 | SRT_LOGGER(true, LOGG_NOTIFY, "serverEventHandler exit"); 258 | 259 | srt_epoll_release(mPollID); 260 | } 261 | 262 | void SRTNet::waitForSRTClient(bool singleSender) { 263 | int result = SRT_ERROR; 264 | mPollID = srt_epoll_create(); 265 | srt_epoll_set(mPollID, SRT_EPOLL_ENABLE_EMPTY); 266 | mEventThread = std::thread(&SRTNet::serverEventHandler, this); 267 | 268 | closeAllClientSockets(); 269 | 270 | while (mServerActive) { 271 | struct sockaddr_storage theirAddr = {0}; 272 | SRT_LOGGER(true, LOGG_NOTIFY, "SRT Server wait for client"); 273 | int addrSize = sizeof(theirAddr); 274 | SRTSOCKET newSocketCandidate = srt_accept(mContext, reinterpret_cast(&theirAddr), &addrSize); 275 | if (newSocketCandidate == -1) { 276 | continue; 277 | } 278 | SRT_LOGGER(true, LOGG_NOTIFY, "Client connected: " << newSocketCandidate); 279 | auto ctx = clientConnected(*reinterpret_cast(&theirAddr), newSocketCandidate, mConnectionContext); 280 | 281 | if (ctx) { 282 | const int events = SRT_EPOLL_IN | SRT_EPOLL_ERR; 283 | std::lock_guard lock(mClientListMtx); 284 | mClientList[newSocketCandidate] = ctx; 285 | result = srt_epoll_add_usock(mPollID, newSocketCandidate, &events); 286 | if (result == SRT_ERROR) { 287 | SRT_LOGGER(true, LOGG_FATAL, "srt_epoll_add_usock error: " << srt_getlasterror_str()); 288 | } 289 | 290 | if (singleSender) { 291 | int result = srt_close(mContext); 292 | if (result == SRT_ERROR) { 293 | SRT_LOGGER(true, LOGG_ERROR, "srt_close failed: " << srt_getlasterror_str()); 294 | } 295 | break; 296 | } 297 | } else { 298 | close(newSocketCandidate); 299 | } 300 | } 301 | } 302 | 303 | void SRTNet::getActiveClients( 304 | const std::function>&)>& function) { 305 | std::lock_guard lock(mClientListMtx); 306 | function(mClientList); 307 | } 308 | 309 | bool SRTNet::startClient(const std::string& host, 310 | uint16_t port, 311 | int reorder, 312 | int32_t latency, 313 | int overhead, 314 | std::shared_ptr& ctx, 315 | int mtu, 316 | int32_t peerIdleTimeout, 317 | const std::string& psk) { 318 | return startClient(host, port, "", 0, reorder, latency, overhead, ctx, mtu, peerIdleTimeout, psk); 319 | } 320 | 321 | // Host can provide a IP or name meaning any IPv4 or IPv6 address or name type www.google.com 322 | // There is no IP-Version preference if a name is given. the first IP-version found will be used 323 | bool SRTNet::startClient(const std::string& host, 324 | uint16_t port, 325 | const std::string& localHost, 326 | uint16_t localPort, 327 | int reorder, 328 | int32_t latency, 329 | int overhead, 330 | std::shared_ptr& ctx, 331 | int mtu, 332 | int32_t peerIdleTimeout, 333 | const std::string& psk) { 334 | std::lock_guard lock(mNetMtx); 335 | if (mCurrentMode != Mode::unknown) { 336 | SRT_LOGGER(true, LOGG_ERROR, 337 | " " 338 | << "SRTNet mode is already set"); 339 | return false; 340 | } 341 | 342 | mClientContext = ctx; 343 | 344 | int result = 0; 345 | int32_t yes = 1; 346 | SRT_LOGGER(true, LOGG_NOTIFY, "SRT client startup"); 347 | 348 | mContext = srt_create_socket(); 349 | if (mContext == SRT_ERROR) { 350 | SRT_LOGGER(true, LOGG_FATAL, "srt_socket: " << srt_getlasterror_str()); 351 | return false; 352 | } 353 | 354 | result = srt_setsockflag(mContext, SRTO_SENDER, &yes, sizeof(yes)); 355 | if (result == SRT_ERROR) { 356 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_SENDER: " << srt_getlasterror_str()); 357 | return false; 358 | } 359 | 360 | result = srt_setsockflag(mContext, SRTO_LATENCY, &latency, sizeof(latency)); 361 | if (result == SRT_ERROR) { 362 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_LATENCY: " << srt_getlasterror_str()); 363 | return false; 364 | } 365 | 366 | result = srt_setsockflag(mContext, SRTO_LOSSMAXTTL, &reorder, sizeof(reorder)); 367 | if (result == SRT_ERROR) { 368 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_LOSSMAXTTL: " << srt_getlasterror_str()); 369 | return false; 370 | } 371 | 372 | result = srt_setsockflag(mContext, SRTO_OHEADBW, &overhead, sizeof(overhead)); 373 | if (result == SRT_ERROR) { 374 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_OHEADBW: " << srt_getlasterror_str()); 375 | return false; 376 | } 377 | 378 | result = srt_setsockflag(mContext, SRTO_PAYLOADSIZE, &mtu, sizeof(mtu)); 379 | if (result == SRT_ERROR) { 380 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_PAYLOADSIZE: " << srt_getlasterror_str()); 381 | return false; 382 | } 383 | 384 | if (psk.length()) { 385 | int32_t aes128 = 16; 386 | result = srt_setsockflag(mContext, SRTO_PBKEYLEN, &aes128, sizeof(aes128)); 387 | if (result == SRT_ERROR) { 388 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_PBKEYLEN: " << srt_getlasterror_str()); 389 | return false; 390 | } 391 | 392 | result = srt_setsockflag(mContext, SRTO_PASSPHRASE, psk.c_str(), psk.length()); 393 | if (result == SRT_ERROR) { 394 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag SRTO_PASSPHRASE: " << srt_getlasterror_str()); 395 | return false; 396 | } 397 | } 398 | 399 | result = srt_setsockflag(mContext, SRTO_PEERIDLETIMEO, &peerIdleTimeout, sizeof(peerIdleTimeout)); 400 | if (result == SRT_ERROR) { 401 | SRT_LOGGER(true, LOGG_FATAL, "srt_setsockflag : SRTO_PEERIDLETIMEO" << srt_getlasterror_str()); 402 | return false; 403 | } 404 | 405 | if (!localHost.empty() || localPort != 0) { 406 | // Set local interface to bind to 407 | 408 | if (localHost.empty()) { 409 | SRT_LOGGER(true, LOGG_FATAL, 410 | "Local port was provided but local IP is not set, cannot bind to local address"); 411 | srt_close(mContext); 412 | return false; 413 | } 414 | 415 | SocketAddress localSocketAddress(localHost, localPort); 416 | 417 | std::optional localIPv4Address = localSocketAddress.getIPv4(); 418 | if (localIPv4Address.has_value()) { 419 | result = srt_bind(mContext, reinterpret_cast(&localIPv4Address.value()), 420 | sizeof(localIPv4Address.value())); 421 | if (result == SRT_ERROR) { 422 | SRT_LOGGER(true, LOGG_FATAL, "srt_bind: " << srt_getlasterror_str()); 423 | srt_close(mContext); 424 | return false; 425 | } 426 | } 427 | 428 | std::optional localIPv6Address = localSocketAddress.getIPv6(); 429 | if (localIPv6Address.has_value()) { 430 | result = srt_bind(mContext, reinterpret_cast(&localIPv6Address.value()), 431 | sizeof(localIPv6Address.value())); 432 | if (result == SRT_ERROR) { 433 | SRT_LOGGER(true, LOGG_FATAL, "srt_bind: " << srt_getlasterror_str()); 434 | srt_close(mContext); 435 | return false; 436 | } 437 | } 438 | 439 | if (!localIPv4Address.has_value() && !localIPv6Address.has_value()) { 440 | SRT_LOGGER(true, LOGG_FATAL, "Failed to parse local socket address."); 441 | srt_close(mContext); 442 | return false; 443 | } 444 | } 445 | 446 | // Get all remote addresses for connection 447 | struct addrinfo hints = {0}; 448 | struct addrinfo* svr; 449 | struct addrinfo* hld; 450 | hints.ai_socktype = SOCK_DGRAM; 451 | hints.ai_protocol = IPPROTO_UDP; 452 | hints.ai_family = AF_UNSPEC; 453 | std::stringstream portAsString; 454 | portAsString << port; 455 | result = getaddrinfo(host.c_str(), portAsString.str().c_str(), &hints, &svr); 456 | if (result) { 457 | SRT_LOGGER(true, LOGG_FATAL, 458 | "Failed getting the IP target for > " << host << ":" << port << " Errno: " << result); 459 | return false; 460 | } 461 | 462 | SRT_LOGGER(true, LOGG_NOTIFY, "SRT connect"); 463 | for (hld = svr; hld; hld = hld->ai_next) { 464 | result = srt_connect(mContext, reinterpret_cast(hld->ai_addr), hld->ai_addrlen); 465 | if (result != SRT_ERROR) { 466 | SRT_LOGGER(true, LOGG_NOTIFY, "Connected to SRT Server " << std::endl) 467 | break; 468 | } 469 | } 470 | if (result == SRT_ERROR) { 471 | srt_close(mContext); 472 | freeaddrinfo(svr); 473 | SRT_LOGGER(true, LOGG_FATAL, "srt_connect failed " << std::endl); 474 | return false; 475 | } 476 | freeaddrinfo(svr); 477 | mCurrentMode = Mode::client; 478 | mClientActive = true; 479 | mWorkerThread = std::thread(&SRTNet::clientWorker, this); 480 | return true; 481 | } 482 | 483 | void SRTNet::clientWorker() { 484 | while (mClientActive) { 485 | uint8_t msg[2048]; 486 | SRT_MSGCTRL thisMSGCTRL = srt_msgctrl_default; 487 | int result = srt_recvmsg2(mContext, reinterpret_cast(msg), sizeof(msg), &thisMSGCTRL); 488 | if (result == SRT_ERROR) { 489 | if (mClientActive) { 490 | SRT_LOGGER(true, LOGG_ERROR, "srt_recvmsg error: " << srt_getlasterror_str()); 491 | } 492 | if (clientDisconnected) { 493 | clientDisconnected(mClientContext, mContext); 494 | } 495 | break; 496 | } else if (result > 0 && receivedData) { 497 | auto data = std::make_unique>(msg, msg + result); 498 | receivedData(data, thisMSGCTRL, mClientContext, mContext); 499 | } else if (result > 0 && receivedDataNoCopy) { 500 | receivedDataNoCopy(msg, result, thisMSGCTRL, mClientContext, mContext); 501 | } 502 | } 503 | mClientActive = false; 504 | } 505 | 506 | std::pair> SRTNet::getConnectedServer() { 507 | if (mCurrentMode == Mode::client) { 508 | return {mContext, mClientContext}; 509 | } 510 | return {0, nullptr}; 511 | } 512 | 513 | SRTSOCKET SRTNet::getBoundSocket() const { 514 | return mContext; 515 | } 516 | 517 | SRTNet::Mode SRTNet::getCurrentMode() const { 518 | std::lock_guard lock(mNetMtx); 519 | return mCurrentMode; 520 | } 521 | 522 | bool SRTNet::sendData(const uint8_t* data, size_t len, SRT_MSGCTRL* msgCtrl, SRTSOCKET targetSystem) { 523 | int result; 524 | 525 | if (mCurrentMode == Mode::client && mContext && mClientActive) { 526 | result = srt_sendmsg2(mContext, reinterpret_cast(data), len, msgCtrl); 527 | } else if (mCurrentMode == Mode::server && targetSystem && mServerActive) { 528 | result = srt_sendmsg2(targetSystem, reinterpret_cast(data), len, msgCtrl); 529 | } else { 530 | SRT_LOGGER(true, LOGG_WARN, "Can't send data, the client is not active."); 531 | return false; 532 | } 533 | 534 | if (result == SRT_ERROR) { 535 | SRT_LOGGER(true, LOGG_ERROR, "srt_sendmsg2 failed: " << srt_getlasterror_str()); 536 | return false; 537 | } 538 | 539 | if (result != len) { 540 | SRT_LOGGER(true, LOGG_ERROR, "Failed sending all data"); 541 | return false; 542 | } 543 | 544 | return true; 545 | } 546 | 547 | bool SRTNet::stop() { 548 | std::lock_guard lock(mNetMtx); 549 | if (mCurrentMode == Mode::server) { 550 | mServerActive = false; 551 | if (mContext) { 552 | int result = srt_close(mContext); 553 | if (result == SRT_ERROR) { 554 | SRT_LOGGER(true, LOGG_ERROR, "srt_close failed: " << srt_getlasterror_str()); 555 | return false; 556 | } 557 | } 558 | closeAllClientSockets(); 559 | if (mWorkerThread.joinable()) { 560 | mWorkerThread.join(); 561 | } 562 | if (mEventThread.joinable()) { 563 | mEventThread.join(); 564 | } 565 | SRT_LOGGER(true, LOGG_NOTIFY, "Server stopped"); 566 | mCurrentMode = Mode::unknown; 567 | return true; 568 | } else if (mCurrentMode == Mode::client) { 569 | mClientActive = false; 570 | if (mContext) { 571 | int result = srt_close(mContext); 572 | if (result == SRT_ERROR) { 573 | SRT_LOGGER(true, LOGG_ERROR, "srt_close failed: " << srt_getlasterror_str()); 574 | return false; 575 | } 576 | } 577 | 578 | if (mWorkerThread.joinable()) { 579 | mWorkerThread.join(); 580 | } 581 | SRT_LOGGER(true, LOGG_NOTIFY, "Client stopped"); 582 | mCurrentMode = Mode::unknown; 583 | return true; 584 | } 585 | return true; 586 | } 587 | 588 | bool SRTNet::getStatistics(SRT_TRACEBSTATS* currentStats, int clear, int instantaneous, SRTSOCKET targetSystem) { 589 | std::lock_guard lock(mNetMtx); 590 | if (mCurrentMode == Mode::client && mClientActive && mContext) { 591 | int result = srt_bistats(mContext, currentStats, clear, instantaneous); 592 | if (result == SRT_ERROR) { 593 | SRT_LOGGER(true, LOGG_ERROR, "srt_bistats failed: " << srt_getlasterror_str()); 594 | return false; 595 | } 596 | } else if (mCurrentMode == Mode::server && mServerActive && targetSystem) { 597 | int result = srt_bistats(targetSystem, currentStats, clear, instantaneous); 598 | if (result == SRT_ERROR) { 599 | SRT_LOGGER(true, LOGG_ERROR, "srt_bistats failed: " << srt_getlasterror_str()); 600 | return false; 601 | } 602 | } else { 603 | SRT_LOGGER(true, LOGG_ERROR, "Statistics not available"); 604 | return false; 605 | } 606 | return true; 607 | } 608 | -------------------------------------------------------------------------------- /test/TestSrt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "SRTNet.h" 7 | 8 | std::string kValidPsk = "Th1$_is_4n_0pt10N4L_P$k"; 9 | std::string kInvalidPsk = "Th1$_is_4_F4k3_P$k"; 10 | 11 | size_t kMaxMessageSize = SRT_LIVE_MAX_PLSIZE; 12 | 13 | namespace { 14 | /// 15 | /// @brief Get the bind IP address and port of an SRT socket 16 | /// @param socket The SRT socket to get the bind IP and Port from 17 | /// @return The bind IP address and port of the SRT socket 18 | std::pair getBindIpAndPortFromSRTSocket(SRTSOCKET socket) { 19 | sockaddr_storage address{}; 20 | int32_t addressSize = sizeof(decltype(address)); 21 | EXPECT_EQ(srt_getsockname(socket, reinterpret_cast(&address), &addressSize), 0); 22 | if (address.ss_family == AF_INET) { 23 | const auto* socketAddressV4 = reinterpret_cast(&address); 24 | char ipv4Address[INET_ADDRSTRLEN]; 25 | EXPECT_NE(inet_ntop(AF_INET, &(socketAddressV4->sin_addr), ipv4Address, INET_ADDRSTRLEN), nullptr); 26 | return {ipv4Address, ntohs(socketAddressV4->sin_port)}; 27 | } else if (address.ss_family == AF_INET6) { 28 | const auto* socketAddressV6 = reinterpret_cast(&address); 29 | char ipv6Address[INET6_ADDRSTRLEN]; 30 | EXPECT_NE(inet_ntop(AF_INET, &(socketAddressV6->sin6_addr), ipv6Address, INET6_ADDRSTRLEN), nullptr); 31 | return {ipv6Address, ntohs(socketAddressV6->sin6_port)}; 32 | } 33 | return {"Unsupported", 0}; 34 | } 35 | 36 | /// 37 | /// @brief Get the remote peer IP address and port of an SRT socket 38 | /// @param socket The SRT socket to get the peer IP and Port from 39 | /// @return The peer IP address and port of the SRT socket 40 | std::pair getPeerIpAndPortFromSRTSocket(SRTSOCKET socket) { 41 | sockaddr_storage address{}; 42 | int32_t addressSize = sizeof(decltype(address)); 43 | EXPECT_EQ(srt_getpeername(socket, reinterpret_cast(&address), &addressSize), 0); 44 | if (address.ss_family == AF_INET) { 45 | const auto* socketAddressV4 = reinterpret_cast(&address); 46 | char ipv4Address[INET_ADDRSTRLEN]; 47 | EXPECT_NE(inet_ntop(AF_INET, &(socketAddressV4->sin_addr), ipv4Address, INET_ADDRSTRLEN), nullptr); 48 | return {ipv4Address, ntohs(socketAddressV4->sin_port)}; 49 | } else if (address.ss_family == AF_INET6) { 50 | const auto* socketAddressV6 = reinterpret_cast(&address); 51 | char ipv6Address[INET6_ADDRSTRLEN]; 52 | EXPECT_NE(inet_ntop(AF_INET, &(socketAddressV6->sin6_addr), ipv6Address, INET6_ADDRSTRLEN), nullptr); 53 | return {ipv6Address, ntohs(socketAddressV6->sin6_port)}; 54 | } 55 | return {"Unsupported", 0}; 56 | } 57 | 58 | class TestSRTFixture : public ::testing::Test { 59 | public: 60 | void SetUp() override { 61 | mClientCtx->mObject = 42; 62 | 63 | mConnectionCtx->mObject = 1111; 64 | 65 | // notice when client connects to server 66 | mServer.clientConnected = [&](struct sockaddr& sin, SRTSOCKET newSocket, 67 | std::shared_ptr& ctx) { 68 | { 69 | std::lock_guard lock(mConnectedMutex); 70 | mConnected = true; 71 | } 72 | mConnectedCondition.notify_one(); 73 | return mConnectionCtx; 74 | }; 75 | 76 | mServer.clientDisconnected = [&](std::shared_ptr& ctx, SRTSOCKET lSocket) { 77 | EXPECT_EQ(ctx, mConnectionCtx); 78 | }; 79 | 80 | } 81 | 82 | bool waitForClientToConnect(std::chrono::seconds timeout) { 83 | std::unique_lock lock(mConnectedMutex); 84 | bool successfulWait = mConnectedCondition.wait_for(lock, timeout, [&]() { return mConnected; }); 85 | return successfulWait; 86 | } 87 | 88 | protected: 89 | const uint16_t kAnyPort = 0; 90 | 91 | SRTNet mServer; 92 | SRTNet mClient; 93 | 94 | std::shared_ptr mServerCtx = std::make_shared(); 95 | std::shared_ptr mClientCtx = std::make_shared(); 96 | std::shared_ptr mConnectionCtx = std::make_shared(); 97 | 98 | std::condition_variable mConnectedCondition; 99 | std::mutex mConnectedMutex; 100 | bool mConnected = false; 101 | }; 102 | 103 | } // namespace 104 | 105 | TEST(TestSrt, StartStop) { 106 | SRTNet server; 107 | SRTNet client; 108 | 109 | auto serverCtx = std::make_shared(); 110 | EXPECT_FALSE( 111 | server.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, serverCtx)) 112 | << "Expect to fail without providing clientConnected callback"; 113 | auto clientCtx = std::make_shared(); 114 | clientCtx->mObject = 42; 115 | EXPECT_FALSE(client.startClient("127.0.0.1", 8009, 16, 1000, 100, clientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)) 116 | << "Expect to fail with no server started"; 117 | 118 | std::condition_variable connectedCondition; 119 | std::mutex connectedMutex; 120 | bool connected = false; 121 | 122 | // notice when client connects to server 123 | server.clientConnected = [&](struct sockaddr& sin, SRTSOCKET newSocket, 124 | std::shared_ptr& ctx) { 125 | { 126 | std::lock_guard lock(connectedMutex); 127 | connected = true; 128 | } 129 | connectedCondition.notify_one(); 130 | auto connectionCtx = std::make_shared(); 131 | connectionCtx->mObject = 1111; 132 | return connectionCtx; 133 | }; 134 | 135 | ASSERT_TRUE( 136 | server.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, serverCtx)); 137 | ASSERT_TRUE(client.startClient("127.0.0.1", 8009, 16, 1000, 100, clientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 138 | 139 | // check for client connecting 140 | { 141 | std::unique_lock lock(connectedMutex); 142 | bool successfulWait = connectedCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return connected; }); 143 | ASSERT_TRUE(successfulWait) << "Timeout waiting for client to connect"; 144 | } 145 | 146 | auto nClients = 0; 147 | server.getActiveClients([&](std::map>& activeClients) { 148 | nClients = activeClients.size(); 149 | for (const auto& socketNetworkConnectionPair : activeClients) { 150 | int32_t number = 0; 151 | EXPECT_NO_THROW(number = std::any_cast(socketNetworkConnectionPair.second->mObject)); 152 | EXPECT_EQ(number, 1111); 153 | } 154 | }); 155 | EXPECT_EQ(nClients, 1); 156 | 157 | auto [srtSocket, networkConnection] = client.getConnectedServer(); 158 | EXPECT_NE(networkConnection, nullptr); 159 | int32_t number = 0; 160 | EXPECT_NO_THROW(number = std::any_cast(networkConnection->mObject)); 161 | EXPECT_EQ(number, 42); 162 | 163 | // notice when client disconnects 164 | std::condition_variable disconnectCondition; 165 | std::mutex disconnectMutex; 166 | bool disconnected = false; 167 | server.clientDisconnected = [&](std::shared_ptr& ctx, SRTSOCKET lSocket) { 168 | { 169 | std::lock_guard lock(disconnectMutex); 170 | disconnected = true; 171 | } 172 | disconnectCondition.notify_one(); 173 | }; 174 | 175 | EXPECT_TRUE(client.stop()); 176 | { 177 | std::unique_lock lock(disconnectMutex); 178 | bool successfulWait = 179 | disconnectCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return disconnected; }); 180 | EXPECT_TRUE(successfulWait) << "Timeout waiting for client disconnect"; 181 | } 182 | 183 | // start a new client and stop the server 184 | connected = false; 185 | SRTNet client2; 186 | ASSERT_TRUE(client2.startClient("127.0.0.1", 8009, 16, 1000, 100, clientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 187 | // check for client connecting 188 | { 189 | std::unique_lock lock(connectedMutex); 190 | bool successfulWait = connectedCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return connected; }); 191 | ASSERT_TRUE(successfulWait) << "Timeout waiting for client2 to connect"; 192 | } 193 | 194 | disconnected = false; 195 | EXPECT_TRUE(server.stop()); 196 | { 197 | std::unique_lock lock(disconnectMutex); 198 | bool successfulWait = 199 | disconnectCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return disconnected; }); 200 | EXPECT_TRUE(successfulWait) << "Timeout waiting for client disconnect"; 201 | } 202 | } 203 | 204 | TEST(TestSrt, TestPsk) { 205 | SRTNet server; 206 | SRTNet client; 207 | 208 | auto ctx = std::make_shared(); 209 | server.clientConnected = [&](struct sockaddr& sin, SRTSOCKET newSocket, 210 | std::shared_ptr& ctx) { return ctx; }; 211 | ASSERT_TRUE(server.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, ctx)); 212 | EXPECT_FALSE(client.startClient("127.0.0.1", 8009, 16, 1000, 100, ctx, SRT_LIVE_MAX_PLSIZE, 5000, kInvalidPsk)) 213 | << "Expect to fail when using incorrect PSK"; 214 | 215 | ASSERT_TRUE(server.stop()); 216 | ASSERT_TRUE(server.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, ctx)); 217 | EXPECT_TRUE(client.startClient("127.0.0.1", 8009, 16, 1000, 100, ctx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 218 | 219 | ASSERT_TRUE(server.stop()); 220 | ASSERT_TRUE(client.stop()); 221 | ASSERT_TRUE(server.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, "", false, ctx)); 222 | EXPECT_TRUE(client.startClient("127.0.0.1", 8009, 16, 1000, 100, ctx, SRT_LIVE_MAX_PLSIZE)); 223 | } 224 | 225 | TEST_F(TestSRTFixture, SendReceive) { 226 | // start server and client 227 | ASSERT_TRUE( 228 | mServer.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, mServerCtx)); 229 | ASSERT_TRUE(mClient.startClient("127.0.0.1", 8009, 16, 1000, 100, mClientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 230 | 231 | std::vector sendBuffer(1000); 232 | std::condition_variable serverCondition; 233 | std::mutex serverMutex; 234 | bool serverGotData = false; 235 | SRTSOCKET clientSocket; 236 | mServer.receivedData = [&](std::unique_ptr>& data, SRT_MSGCTRL& msgCtrl, 237 | std::shared_ptr& ctx, SRTSOCKET socket) { 238 | EXPECT_EQ(ctx, mConnectionCtx); 239 | EXPECT_EQ(*data, sendBuffer); 240 | clientSocket = socket; 241 | { 242 | std::lock_guard lock(serverMutex); 243 | serverGotData = true; 244 | } 245 | SRT_MSGCTRL msgSendCtrl = srt_msgctrl_default; 246 | EXPECT_TRUE(mServer.sendData(data->data(), data->size(), &msgSendCtrl, socket)); 247 | serverCondition.notify_one(); 248 | return true; 249 | }; 250 | 251 | std::condition_variable clientCondition; 252 | std::mutex clientMutex; 253 | bool clientGotData = false; 254 | mClient.receivedData = [&](std::unique_ptr>& data, SRT_MSGCTRL& msgCtrl, 255 | std::shared_ptr& ctx, SRTSOCKET socket) { 256 | EXPECT_EQ(ctx, mClientCtx); 257 | EXPECT_EQ(*data, sendBuffer); 258 | { 259 | std::lock_guard lock(clientMutex); 260 | clientGotData = true; 261 | } 262 | clientCondition.notify_one(); 263 | return true; 264 | }; 265 | 266 | std::fill(sendBuffer.begin(), sendBuffer.end(), 1); 267 | SRT_MSGCTRL msgCtrl = srt_msgctrl_default; 268 | EXPECT_TRUE(mClient.sendData(sendBuffer.data(), sendBuffer.size(), &msgCtrl)); 269 | 270 | { 271 | std::unique_lock lock(serverMutex); 272 | bool successfulWait = serverCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return serverGotData; }); 273 | EXPECT_TRUE(successfulWait) << "Timeout waiting for receiving data from client"; 274 | } 275 | 276 | { 277 | std::unique_lock lock(clientMutex); 278 | bool successfulWait = clientCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return clientGotData; }); 279 | EXPECT_TRUE(successfulWait) << "Timeout waiting for receiving data from server"; 280 | } 281 | 282 | SRT_TRACEBSTATS clientStats; 283 | mClient.getStatistics(&clientStats, SRTNetClearStats::no, SRTNetInstant::yes); 284 | SRT_TRACEBSTATS serverStats; 285 | mServer.getStatistics(&serverStats, SRTNetClearStats::no, SRTNetInstant::yes, clientSocket); 286 | EXPECT_EQ(clientStats.pktSentTotal, 1); 287 | EXPECT_EQ(clientStats.pktRecvTotal, 1); 288 | EXPECT_EQ(clientStats.pktSentTotal, serverStats.pktRecvTotal); 289 | EXPECT_EQ(clientStats.pktRecvTotal, serverStats.pktSentTotal); 290 | 291 | // verify that sending to a stopped client fails 292 | ASSERT_TRUE(mClient.stop()); 293 | std::this_thread::sleep_for(std::chrono::seconds(1)); 294 | SRT_MSGCTRL msgSendCtrl = srt_msgctrl_default; 295 | EXPECT_FALSE(mServer.sendData(sendBuffer.data(), sendBuffer.size(), &msgSendCtrl, clientSocket)); 296 | } 297 | 298 | TEST_F(TestSRTFixture, SendReceiveIPv6) { 299 | // start server and client 300 | ASSERT_TRUE(mServer.startServer("::", 8020, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, "", true, mServerCtx)); 301 | ASSERT_TRUE(mClient.startClient("::1", 8020, 16, 1000, 100, mClientCtx, SRT_LIVE_MAX_PLSIZE, 5000, "")); 302 | 303 | std::vector sendBuffer(1000); 304 | std::condition_variable serverCondition; 305 | std::mutex serverMutex; 306 | bool serverGotData = false; 307 | SRTSOCKET clientSocket; 308 | mServer.receivedData = [&](std::unique_ptr>& data, SRT_MSGCTRL& msgCtrl, 309 | std::shared_ptr& ctx, SRTSOCKET socket) { 310 | EXPECT_EQ(ctx, mConnectionCtx); 311 | EXPECT_EQ(*data, sendBuffer); 312 | clientSocket = socket; 313 | { 314 | std::lock_guard lock(serverMutex); 315 | serverGotData = true; 316 | } 317 | SRT_MSGCTRL msgSendCtrl = srt_msgctrl_default; 318 | EXPECT_TRUE(mServer.sendData(data->data(), data->size(), &msgSendCtrl, socket)); 319 | serverCondition.notify_one(); 320 | return true; 321 | }; 322 | 323 | std::condition_variable clientCondition; 324 | std::mutex clientMutex; 325 | bool clientGotData = false; 326 | mClient.receivedData = [&](std::unique_ptr>& data, SRT_MSGCTRL& msgCtrl, 327 | std::shared_ptr& ctx, SRTSOCKET socket) { 328 | EXPECT_EQ(ctx, mClientCtx); 329 | EXPECT_EQ(*data, sendBuffer); 330 | { 331 | std::lock_guard lock(clientMutex); 332 | clientGotData = true; 333 | } 334 | clientCondition.notify_one(); 335 | return true; 336 | }; 337 | 338 | std::fill(sendBuffer.begin(), sendBuffer.end(), 1); 339 | SRT_MSGCTRL msgCtrl = srt_msgctrl_default; 340 | EXPECT_TRUE(mClient.sendData(sendBuffer.data(), sendBuffer.size(), &msgCtrl)); 341 | 342 | { 343 | std::unique_lock lock(serverMutex); 344 | bool successfulWait = serverCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return serverGotData; }); 345 | EXPECT_TRUE(successfulWait) << "Timeout waiting for receiving data from client"; 346 | } 347 | 348 | { 349 | std::unique_lock lock(clientMutex); 350 | bool successfulWait = clientCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return clientGotData; }); 351 | EXPECT_TRUE(successfulWait) << "Timeout waiting for receiving data from server"; 352 | } 353 | 354 | SRT_TRACEBSTATS clientStats; 355 | mClient.getStatistics(&clientStats, SRTNetClearStats::no, SRTNetInstant::yes); 356 | SRT_TRACEBSTATS serverStats; 357 | mServer.getStatistics(&serverStats, SRTNetClearStats::no, SRTNetInstant::yes, clientSocket); 358 | EXPECT_EQ(clientStats.pktSentTotal, 1); 359 | EXPECT_EQ(clientStats.pktRecvTotal, 1); 360 | EXPECT_EQ(clientStats.pktSentTotal, serverStats.pktRecvTotal); 361 | EXPECT_EQ(clientStats.pktRecvTotal, serverStats.pktSentTotal); 362 | 363 | // verify that sending to a stopped client fails 364 | ASSERT_TRUE(mClient.stop()); 365 | std::this_thread::sleep_for(std::chrono::seconds(1)); 366 | SRT_MSGCTRL msgSendCtrl = srt_msgctrl_default; 367 | EXPECT_FALSE(mServer.sendData(sendBuffer.data(), sendBuffer.size(), &msgSendCtrl, clientSocket)); 368 | } 369 | 370 | TEST_F(TestSRTFixture, LargeMessage) { 371 | // start server and client 372 | ASSERT_TRUE( 373 | mServer.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, mServerCtx)); 374 | ASSERT_TRUE(mClient.startClient("127.0.0.1", 8009, 16, 1000, 100, mClientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 375 | 376 | std::vector sendBuffer(kMaxMessageSize + 1); 377 | std::fill(sendBuffer.begin(), sendBuffer.end(), 1); 378 | SRT_MSGCTRL msgCtrl = srt_msgctrl_default; 379 | EXPECT_FALSE(mClient.sendData(sendBuffer.data(), sendBuffer.size(), &msgCtrl)); 380 | } 381 | 382 | // TODO Enable test when STAR-238 is fixed 383 | TEST_F(TestSRTFixture, DISABLED_RejectConnection) { 384 | auto ctx = std::make_shared(); 385 | EXPECT_TRUE(mServer.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, ctx)); 386 | EXPECT_FALSE(mClient.startClient("127.0.0.1", 8009, 16, 1000, 100, ctx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)) 387 | << "Expected client connection rejected"; 388 | 389 | ASSERT_TRUE(waitForClientToConnect(std::chrono::seconds(2))); 390 | 391 | auto numberOfClients = 0; 392 | mServer.getActiveClients([&](std::map>& activeClients) { 393 | numberOfClients = activeClients.size(); 394 | }); 395 | EXPECT_EQ(numberOfClients, 0); 396 | 397 | auto [srtSocket, networkConnection] = mClient.getConnectedServer(); 398 | EXPECT_EQ(networkConnection, nullptr); 399 | 400 | std::condition_variable receiveCondition; 401 | std::mutex receiveMutex; 402 | size_t receivedBytes = 0; 403 | 404 | mServer.receivedData = [&](std::unique_ptr>& data, SRT_MSGCTRL& msgCtrl, 405 | std::shared_ptr& ctx, SRTSOCKET socket) { 406 | { 407 | std::lock_guard lock(receiveMutex); 408 | receivedBytes = data->size(); 409 | } 410 | receiveCondition.notify_one(); 411 | return true; 412 | }; 413 | 414 | std::vector sendBuffer(1000); 415 | std::fill(sendBuffer.begin(), sendBuffer.end(), 1); 416 | SRT_MSGCTRL msgCtrl = srt_msgctrl_default; 417 | EXPECT_FALSE(mClient.sendData(sendBuffer.data(), sendBuffer.size(), &msgCtrl)) 418 | << "Expect to fail sending data from unconnected client"; 419 | 420 | { 421 | std::unique_lock lock(receiveMutex); 422 | bool successfulWait = 423 | receiveCondition.wait_for(lock, std::chrono::seconds(2), [&]() { return receivedBytes == 1000; }); 424 | EXPECT_FALSE(successfulWait) << "Did not expect to receive data from unconnected client"; 425 | } 426 | } 427 | 428 | TEST_F(TestSRTFixture, SingleSender) { 429 | ASSERT_TRUE( 430 | mServer.startServer("127.0.0.1", 8009, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, true, mServerCtx)); 431 | ASSERT_TRUE(mClient.startClient("127.0.0.1", 8009, 16, 1000, 100, mClientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 432 | 433 | ASSERT_TRUE(waitForClientToConnect(std::chrono::seconds(2))); 434 | 435 | auto numberOfClients = 0; 436 | mServer.getActiveClients([&](std::map>& activeClients) { 437 | numberOfClients = activeClients.size(); 438 | for (const auto& socketNetworkConnectionPair : activeClients) { 439 | int32_t number = 0; 440 | EXPECT_NO_THROW(number = std::any_cast(socketNetworkConnectionPair.second->mObject)); 441 | EXPECT_EQ(number, 1111); 442 | } 443 | }); 444 | EXPECT_EQ(numberOfClients, 1); 445 | 446 | auto [srtSocket, networkConnection] = mClient.getConnectedServer(); 447 | EXPECT_NE(networkConnection, nullptr); 448 | int32_t number = 0; 449 | EXPECT_NO_THROW(number = std::any_cast(networkConnection->mObject)); 450 | EXPECT_EQ(number, 42); 451 | 452 | // start a new client, should fail since we only accept one single client 453 | mConnected = false; 454 | SRTNet client2; 455 | ASSERT_FALSE( 456 | client2.startClient("127.0.0.1", 8009, 16, 1000, 100, mClientCtx, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 457 | 458 | mServer.getActiveClients([&](std::map>& activeClients) { 459 | numberOfClients = activeClients.size(); 460 | for (const auto& socketNetworkConnectionPair : activeClients) { 461 | int32_t number = 0; 462 | EXPECT_NO_THROW(number = std::any_cast(socketNetworkConnectionPair.second->mObject)); 463 | EXPECT_EQ(number, 1111); 464 | } 465 | }); 466 | EXPECT_EQ(numberOfClients, 1); 467 | 468 | EXPECT_TRUE(mServer.stop()); 469 | } 470 | 471 | TEST_F(TestSRTFixture, BindAddressForCaller) { 472 | ASSERT_TRUE( 473 | mServer.startServer("127.0.0.1", 8010, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, mServerCtx)); 474 | ASSERT_TRUE(mClient.startClient("127.0.0.1", 8010, "0.0.0.0", 8011, 16, 1000, 100, mClientCtx, SRT_LIVE_MAX_PLSIZE, 475 | 5000, kValidPsk)); 476 | 477 | ASSERT_TRUE(waitForClientToConnect(std::chrono::seconds(2))); 478 | 479 | size_t numberOfClients = 0; 480 | mServer.getActiveClients([&](std::map>& activeClients) { 481 | numberOfClients = activeClients.size(); 482 | for (const auto& socketNetworkConnectionPair : activeClients) { 483 | std::pair peerIPAndPort = 484 | getPeerIpAndPortFromSRTSocket(socketNetworkConnectionPair.first); 485 | EXPECT_EQ(peerIPAndPort.first, "127.0.0.1"); 486 | EXPECT_EQ(peerIPAndPort.second, 8011); 487 | 488 | std::pair ipAndPort = 489 | getBindIpAndPortFromSRTSocket(socketNetworkConnectionPair.first); 490 | EXPECT_EQ(ipAndPort.first, "127.0.0.1"); 491 | EXPECT_EQ(ipAndPort.second, 8010); 492 | } 493 | }); 494 | EXPECT_EQ(numberOfClients, 1); 495 | 496 | std::pair serverIPAndPort = getBindIpAndPortFromSRTSocket(mServer.getBoundSocket()); 497 | EXPECT_EQ(serverIPAndPort.first, "127.0.0.1"); 498 | EXPECT_EQ(serverIPAndPort.second, 8010); 499 | 500 | std::pair clientIPAndPort = getBindIpAndPortFromSRTSocket(mClient.getBoundSocket()); 501 | EXPECT_EQ(clientIPAndPort.first, "127.0.0.1"); 502 | EXPECT_EQ(clientIPAndPort.second, 8011); 503 | } 504 | 505 | TEST_F(TestSRTFixture, AutomaticPortSelection) { 506 | const uint16_t kAnyPort = 0; 507 | ASSERT_TRUE( 508 | mServer.startServer("0.0.0.0", kAnyPort, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, mServerCtx)); 509 | 510 | std::pair serverIPAndPort = getBindIpAndPortFromSRTSocket(mServer.getBoundSocket()); 511 | EXPECT_EQ(serverIPAndPort.first, "0.0.0.0"); 512 | EXPECT_GT(serverIPAndPort.second, 1024); // We expect it won't pick a privileged port 513 | 514 | ASSERT_TRUE(mClient.startClient("127.0.0.1", serverIPAndPort.second, "0.0.0.0", kAnyPort, 16, 1000, 100, mClientCtx, 515 | SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 516 | 517 | ASSERT_TRUE(waitForClientToConnect(std::chrono::seconds(2))); 518 | 519 | std::pair clientIPAndPort = getBindIpAndPortFromSRTSocket(mClient.getBoundSocket()); 520 | EXPECT_EQ(clientIPAndPort.first, "127.0.0.1"); 521 | EXPECT_GT(clientIPAndPort.second, 1024); // We expect it won't pick a privileged port 522 | EXPECT_NE(clientIPAndPort.second, serverIPAndPort.second); 523 | 524 | size_t nClients = 0; 525 | mServer.getActiveClients([&](std::map>& activeClients) { 526 | nClients = activeClients.size(); 527 | for (const auto& socketNetworkConnectionPair : activeClients) { 528 | std::pair peerIPAndPort = 529 | getPeerIpAndPortFromSRTSocket(socketNetworkConnectionPair.first); 530 | EXPECT_EQ(peerIPAndPort.first, "127.0.0.1"); 531 | EXPECT_EQ(peerIPAndPort.second, clientIPAndPort.second); 532 | 533 | std::pair ipAndPort = 534 | getBindIpAndPortFromSRTSocket(socketNetworkConnectionPair.first); 535 | EXPECT_EQ(ipAndPort.first, "127.0.0.1"); 536 | EXPECT_EQ(ipAndPort.second, serverIPAndPort.second); 537 | } 538 | }); 539 | EXPECT_EQ(nClients, 1); 540 | } 541 | 542 | TEST_F(TestSRTFixture, FailToBindWhenLocalIPIsMissing) { 543 | uint16_t kPort = 8021; 544 | ASSERT_TRUE( 545 | mServer.startServer("0.0.0.0", kPort, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, mServerCtx)); 546 | std::string kEmptyIP; 547 | uint16_t kLocalPort = 8022; 548 | ASSERT_FALSE(mClient.startClient("127.0.0.1", kPort, kEmptyIP, kLocalPort, 16, 1000, 100, mClientCtx, 549 | SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 550 | } 551 | 552 | TEST_F(TestSRTFixture, FailToBindWhenLocalIPIsCorrupt) { 553 | uint16_t kPort = 8021; 554 | ASSERT_TRUE( 555 | mServer.startServer("0.0.0.0", kPort, 16, 1000, 100, SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk, false, mServerCtx)); 556 | std::string kIllFormattedIP = "123.456.789.012"; 557 | uint16_t kLocalPort = 8022; 558 | ASSERT_FALSE(mClient.startClient("127.0.0.1", kPort, kIllFormattedIP, kLocalPort, 16, 1000, 100, mClientCtx, 559 | SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 560 | } 561 | 562 | TEST_F(TestSRTFixture, FailToConnectWhenRemoteHostnameIsCorrupt) { 563 | uint16_t kPort = 8023; 564 | std::string kIllFormattedIP = "thi$i$not_a(host)name.com"; 565 | ASSERT_FALSE(mClient.startClient(kIllFormattedIP, kPort, 16, 1000, 100, mClientCtx, 566 | SRT_LIVE_MAX_PLSIZE, 5000, kValidPsk)); 567 | } 568 | --------------------------------------------------------------------------------